From 6c0b801727f008112a496bf0a7b34d2894709041 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 15 Jan 2020 11:43:40 +0100 Subject: [PATCH 1/4] Fix #7965: Deal with AndTypes in joins Allow that the base type of a join may be a conjunction. --- .../src/dotty/tools/dotc/core/TypeOps.scala | 4 ++ tests/pos/i7965.scala | 46 +++++++++++++++++++ 2 files changed, 50 insertions(+) create mode 100644 tests/pos/i7965.scala diff --git a/compiler/src/dotty/tools/dotc/core/TypeOps.scala b/compiler/src/dotty/tools/dotc/core/TypeOps.scala index e71416828505..b9edb90ce252 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeOps.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeOps.scala @@ -212,6 +212,8 @@ trait TypeOps { this: Context => // TODO: Make standalone object. tp1.derivedAppliedType( mergeRefinedOrApplied(tycon1, tycon2), ctx.typeComparer.lubArgs(args1, args2, tycon1.typeParams)) + case AndType(tp21, tp22) => + mergeRefinedOrApplied(tp1, tp21) & mergeRefinedOrApplied(tp1, tp22) case _ => fail } case tp1 @ TypeRef(pre1, _) => @@ -220,6 +222,8 @@ trait TypeOps { this: Context => // TODO: Make standalone object. tp1.derivedSelect(pre1 | pre2) case _ => fail } + case AndType(tp11, tp12) => + mergeRefinedOrApplied(tp11, tp2) & mergeRefinedOrApplied(tp12, tp2) case _ => fail } } diff --git a/tests/pos/i7965.scala b/tests/pos/i7965.scala new file mode 100644 index 000000000000..5fcdc5387a3c --- /dev/null +++ b/tests/pos/i7965.scala @@ -0,0 +1,46 @@ +class Has[A] +trait X +trait Y +trait Z + +abstract class Test { + val x: Has[X] | (Has[Y] & Has[Z]) + def foo[T <: Has[_]](has: T): T = has + foo(x) +} + +trait ZLayer[-RIn, +E, +ROut <: Has[_]] { + def >>>[E1 >: E, ROut2 <: Has[_]](that: ZLayer[ROut, E1, ROut2]): ZLayer[RIn, E1, ROut2] + def ++[E1 >: E, RIn2, ROut1 >: ROut <: Has[_], ROut2 <: Has[_]](that: ZLayer[RIn2, E1, ROut2]): ZLayer[RIn with RIn2, E1, ROut1 with ROut2] +} +object ZLayer { + type NoDeps[+E, +B <: Has[_]] = ZLayer[Any, E, B] +} + +type ServiceA = Has[ServiceA.Service] +object ServiceA { + trait Service + val live: ZLayer.NoDeps[Nothing, ServiceA] = ??? +} + +type ServiceB = Has[ServiceB.Service] +object ServiceB { + trait Service + val live: ZLayer.NoDeps[Nothing, ServiceB] = ??? +} + +type ServiceC = Has[ServiceC.Service] +object ServiceC { + trait Service + val live: ZLayer.NoDeps[Nothing, ServiceC] = ??? +} + +type ServiceD = Has[ServiceD.Service] +object ServiceD { + trait Service + val live: ZLayer.NoDeps[ServiceC, ServiceD with ServiceC] = ??? +} + +val combined = + ServiceA.live >>> + (ServiceB.live ++ (ServiceC.live >>> ServiceD.live)) \ No newline at end of file From 744b5b07a15a94e97f82c31780985eb70de8b236 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 15 Jan 2020 18:12:51 +0100 Subject: [PATCH 2/4] Avoid -Ycheck error by refining <:< testing --- .../dotty/tools/dotc/core/TypeComparer.scala | 17 ++++++++++++++++- tests/pos/i7965.scala | 4 +++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index 452fa2d898ed..4cb9a232f9ac 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -417,6 +417,7 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] w if (tp11.stripTypeVar eq tp12.stripTypeVar) recur(tp11, tp2) else thirdTry case tp1 @ OrType(tp11, tp12) => + def joinOK = tp2.dealiasKeepRefiningAnnots match { case tp2: AppliedType if !tp2.tycon.typeSymbol.isClass => // If we apply the default algorithm for `A[X] | B[Y] <: C[Z]` where `C` is a @@ -427,6 +428,12 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] w case _ => false } + + def containsAnd(tp: Type): Boolean = tp.dealiasKeepRefiningAnnots match + case tp: AndType => true + case OrType(tp1, tp2) => containsAnd(tp1) || containsAnd(tp2) + case _ => false + def widenOK = (tp2.widenSingletons eq tp2) && (tp1.widenSingletons ne tp1) && @@ -435,7 +442,15 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] w if (tp2.atoms.nonEmpty && canCompare(tp2.atoms)) tp1.atoms.nonEmpty && tp1.atoms.subsetOf(tp2.atoms) else - widenOK || joinOK || recur(tp11, tp2) && recur(tp12, tp2) + widenOK + || joinOK + || recur(tp11, tp2) && recur(tp12, tp2) + || containsAnd(tp1) && recur(tp1.join, tp2) + // An & on the left side loses information. Compensate by also trying the join. + // This is less ad-hoc than it looks since we produce joins in type inference, + // and then need to check that they are indeed supertypes of the original types + // under -Ycheck. Test case is i7965.scala. + case tp1: MatchType => val reduced = tp1.reduced if (reduced.exists) recur(reduced, tp2) else thirdTry diff --git a/tests/pos/i7965.scala b/tests/pos/i7965.scala index 5fcdc5387a3c..f6a4e8026f91 100644 --- a/tests/pos/i7965.scala +++ b/tests/pos/i7965.scala @@ -4,7 +4,9 @@ trait Y trait Z abstract class Test { - val x: Has[X] | (Has[Y] & Has[Z]) + def x: Has[X] | (Has[Y] & Has[Z]) + val y: Has[? >: (X & Y) | (X & Z) <: (X | Y) & (X | Z)] = x + def foo[T <: Has[_]](has: T): T = has foo(x) } From 9878eb1ab7afb8cc3d82c821f30504e81d618689 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 15 Jan 2020 18:20:09 +0100 Subject: [PATCH 3/4] Also handle refinements in joins with AndTypes --- compiler/src/dotty/tools/dotc/core/TypeOps.scala | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/TypeOps.scala b/compiler/src/dotty/tools/dotc/core/TypeOps.scala index b9edb90ce252..1f55990089fe 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeOps.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeOps.scala @@ -198,13 +198,18 @@ trait TypeOps { this: Context => // TODO: Make standalone object. def mergeRefinedOrApplied(tp1: Type, tp2: Type): Type = { def fail = throw new AssertionError(i"Failure to join alternatives $tp1 and $tp2") + def fallback = tp2 match + case AndType(tp21, tp22) => + mergeRefinedOrApplied(tp1, tp21) & mergeRefinedOrApplied(tp1, tp22) + case _ => + fail tp1 match { case tp1 @ RefinedType(parent1, name1, rinfo1) => tp2 match { case RefinedType(parent2, `name1`, rinfo2) => tp1.derivedRefinedType( mergeRefinedOrApplied(parent1, parent2), name1, rinfo1 | rinfo2) - case _ => fail + case _ => fallback } case tp1 @ AppliedType(tycon1, args1) => tp2 match { @@ -212,9 +217,7 @@ trait TypeOps { this: Context => // TODO: Make standalone object. tp1.derivedAppliedType( mergeRefinedOrApplied(tycon1, tycon2), ctx.typeComparer.lubArgs(args1, args2, tycon1.typeParams)) - case AndType(tp21, tp22) => - mergeRefinedOrApplied(tp1, tp21) & mergeRefinedOrApplied(tp1, tp22) - case _ => fail + case _ => fallback } case tp1 @ TypeRef(pre1, _) => tp2 match { From 91cf80d825d8c03fa16a9cdeea5302c23fa38032 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 15 Jan 2020 18:51:02 +0100 Subject: [PATCH 4/4] Address review comments --- .../src/dotty/tools/dotc/core/TypeOps.scala | 2 +- tests/pos/i7965.scala | 20 ++++++++++++++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/TypeOps.scala b/compiler/src/dotty/tools/dotc/core/TypeOps.scala index 1f55990089fe..65a42cd42561 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeOps.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeOps.scala @@ -223,7 +223,7 @@ trait TypeOps { this: Context => // TODO: Make standalone object. tp2 match { case tp2 @ TypeRef(pre2, _) if tp1.name eq tp2.name => tp1.derivedSelect(pre1 | pre2) - case _ => fail + case _ => fallback } case AndType(tp11, tp12) => mergeRefinedOrApplied(tp11, tp2) & mergeRefinedOrApplied(tp12, tp2) diff --git a/tests/pos/i7965.scala b/tests/pos/i7965.scala index f6a4e8026f91..ee9a9e64a01b 100644 --- a/tests/pos/i7965.scala +++ b/tests/pos/i7965.scala @@ -11,6 +11,8 @@ abstract class Test { foo(x) } +// ------------------------------------------- + trait ZLayer[-RIn, +E, +ROut <: Has[_]] { def >>>[E1 >: E, ROut2 <: Has[_]](that: ZLayer[ROut, E1, ROut2]): ZLayer[RIn, E1, ROut2] def ++[E1 >: E, RIn2, ROut1 >: ROut <: Has[_], ROut2 <: Has[_]](that: ZLayer[RIn2, E1, ROut2]): ZLayer[RIn with RIn2, E1, ROut1 with ROut2] @@ -45,4 +47,20 @@ object ServiceD { val combined = ServiceA.live >>> - (ServiceB.live ++ (ServiceC.live >>> ServiceD.live)) \ No newline at end of file + (ServiceB.live ++ (ServiceC.live >>> ServiceD.live)) + +// ------------------------------------------- + +class Outer { + class Elem +} + +abstract class Test2 { + val o1: Outer = ??? + val o2: Outer = ??? + val o3: Outer = ??? + + val x: o1.Elem | (o2.Elem & o3.Elem) + def foo[T <: Outer#Elem](has: T): T = ??? + foo(x) +} \ No newline at end of file