From f1faab64f6af946b32c3cc15c0487a99996a90ad Mon Sep 17 00:00:00 2001 From: Guillaume Martres Date: Thu, 30 Jul 2020 02:26:59 +0200 Subject: [PATCH] safe_&: handle higher-kinded arguments like regular & Unfortunately, i9346.scala has to be put in pending because it still crashes (due to a cycle involving a LazyRef) after this fix, and because `safe_&` is only called from `recoverable_&` when there is some sort of cycle in the first place, I haven't been able to make another testcase that exercises this codepath. It would be good if we could figure out how to get i9346.scala to compile (see also #9346 for discussions). It is is a minimization of a pattern heavily used in akka-stream, similar to the use of the `CC` type parameter in the scala-library collections but using a type member instead. Unfortunately, it seems that Dotty is not really prepared to handle F-bounds in type members currently. I was able to get the testcase as well as akka-stream to compile by tweaking `findMember` to not compute an intersection when the refinement is an alias: ```diff --- compiler/src/dotty/tools/dotc/core/Types.scala +++ compiler/src/dotty/tools/dotc/core/Types.scala @@ -671,7 +671,8 @@ object Types { val rinfo = tp.refinedInfo if (name.isTypeName && !pinfo.isInstanceOf[ClassInfo]) { // simplified case that runs more efficiently val jointInfo = - if (ctx.base.pendingMemberSearches.contains(name)) pinfo safe_& rinfo + if (rinfo.isInstanceOf[TypeAlias]) rinfo + else if (ctx.base.pendingMemberSearches.contains(name)) pinfo safe_& rinfo else pinfo recoverable_& rinfo pdenot.asSingleDenotation.derivedSingleDenotation(pdenot.symbol, jointInfo) } ``` This seems to work, but to be sound it means that we need to check for invalid bounds in PostTyper (see tests/neg/i5556.scala for an example where this matters), like we do for type parameters in `checkAppliedType`. --- .../src/dotty/tools/dotc/core/TypeComparer.scala | 2 +- compiler/src/dotty/tools/dotc/core/Types.scala | 15 +++++++++++++-- tests/pending/pos/i9346.scala | 9 +++++++++ 3 files changed, 23 insertions(+), 3 deletions(-) create mode 100644 tests/pending/pos/i9346.scala diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index 4ba902218ee5..1ceb4d5428ab 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -2078,7 +2078,7 @@ class TypeComparer(using val comparerCtx: Context) extends ConstraintHandling wi * * [X1, ..., Xn] -> op(tp1[X1, ..., Xn], tp2[X1, ..., Xn]) */ - private def liftIfHK(tp1: Type, tp2: Type, + def liftIfHK(tp1: Type, tp2: Type, op: (Type, Type) => Type, original: (Type, Type) => Type, combineVariance: (Variance, Variance) => Variance) = { val tparams1 = tp1.typeParams val tparams2 = tp2.typeParams diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 777f43931e13..6757e3170f91 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -1018,8 +1018,11 @@ object Types { */ def safe_& (that: Type)(using Context): Type = (this, that) match { case (TypeBounds(lo1, hi1), TypeBounds(lo2, hi2)) => - TypeBounds(OrType(lo1.stripLazyRef, lo2.stripLazyRef), AndType(hi1.stripLazyRef, hi2.stripLazyRef)) - case _ => this & that + TypeBounds( + OrType.makeHk(lo1.stripLazyRef, lo2.stripLazyRef), + AndType.makeHk(hi1.stripLazyRef, hi2.stripLazyRef)) + case _ => + this & that } /** `this & that`, but handle CyclicReferences by falling back to `safe_&`. @@ -2934,6 +2937,10 @@ object Types { tp2 else if (checkValid) apply(tp1, tp2) else unchecked(tp1, tp2) + + /** Like `make`, but also supports higher-kinded types as argument */ + def makeHk(tp1: Type, tp2: Type)(using Context): Type = + ctx.typeComparer.liftIfHK(tp1, tp2, AndType.make(_, _, checkValid = false), makeHk, _ | _) } abstract case class OrType(tp1: Type, tp2: Type) extends AndOrType { @@ -3020,6 +3027,10 @@ object Types { def make(tp1: Type, tp2: Type)(using Context): Type = if (tp1 eq tp2) tp1 else apply(tp1, tp2) + + /** Like `make`, but also supports higher-kinded types as argument */ + def makeHk(tp1: Type, tp2: Type)(using Context): Type = + ctx.typeComparer.liftIfHK(tp1, tp2, OrType(_, _), makeHk, _ & _) } /** An extractor object to pattern match against a nullable union. diff --git a/tests/pending/pos/i9346.scala b/tests/pending/pos/i9346.scala new file mode 100644 index 000000000000..51e747fd976e --- /dev/null +++ b/tests/pending/pos/i9346.scala @@ -0,0 +1,9 @@ +trait Foo[+A] { + type Repr[+O] <: Foo[O] { + type Repr[+OO] = Foo.this.Repr[OO] + } + + def foo: Repr[A] + + def bar: Repr[A] = this.foo.foo +}