From f0cd5650853109d45475d558ef7adffe4fbd268b Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Mon, 4 Mar 2024 09:20:06 +0000 Subject: [PATCH 1/2] Rework MatchType recursion in collectParts This patch fixes a recursion situation in collectParts, by reimplementing a previous fix. Recursion is already attempted to be cutoff with `partSeen`, and `handleRecursion` is also used to prevent any unhandled recursion situations (like the one fixed here) from crashing the compiler. For context, AppliedType aren't hash-consed (i.e. the flyweight pattern) which means that every time you apply the same type arguments to the same type constructor you get a fresh AppliedType. Using i18171 as an example, the sequence of events where this matters is: 0. When typing BAZ, so much earlier than the collectParts call, because the MatchType on the rhs of BAZ reduces at definition site, the RHS is reduced to the `DFVal[BAZREC[T]]`, which means that BAZ's info is a TypeAlias rather than a MatchAlias, meaning it can dealias. 1. `BAZREC[Any]` is extracted by MatchType.InDisguise, which applies the Any to return a fresh MatchType 2. `Tuple.Map[Any, BAZ]` is also extracted by MatchType.InDisguise, which returns its own fresh MatchType 3. `BAZ[h]` dealiases to a fresh `DFVal[BAZREC[h]]` instance (see step 0) 4. `BAZREC[h]` is extracted by MatchType.InDisguise, repeating the cycle The reason that the cases with MatchType.InDisguise and MatchType were introduced is i17395. Looking back, it seems the only need is to capture any parts that are in the reduction of an applied MatchType. With this patch applied in the case of i18171, this now cuts off quickly, as `BAZREC[Any]` doesn't reduce to anything. In the case of i17395, `ExtractPart[ValuePartHolder]` reduces to `Value`, so `Value` is successfully recorded as a part. --- .../dotty/tools/dotc/typer/Implicits.scala | 8 +------ tests/pos/i17395-spec.ordered.scala | 17 ++++++++++++++ tests/pos/i19857.scala | 23 +++++++++++++++++++ 3 files changed, 41 insertions(+), 7 deletions(-) create mode 100644 tests/pos/i17395-spec.ordered.scala create mode 100644 tests/pos/i19857.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 0a26ea697a6a..5162b3fed1b9 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -661,15 +661,9 @@ trait ImplicitRunInfo: case t: TypeLambda => for p <- t.paramRefs do partSeen += p traverseChildren(t) - case t: MatchType => - traverseChildren(t) - traverse(t.normalized) - case MatchType.InDisguise(mt) - if !t.isInstanceOf[LazyRef] // skip recursive applications (eg. Tuple.Map) - => - traverse(mt) case t => traverseChildren(t) + traverse(t.normalized) catch case ex: Throwable => handleRecursive("collectParts of", t.show, ex) def apply(tp: Type): collection.Set[Type] = diff --git a/tests/pos/i17395-spec.ordered.scala b/tests/pos/i17395-spec.ordered.scala new file mode 100644 index 000000000000..6e6ae8dd1e59 --- /dev/null +++ b/tests/pos/i17395-spec.ordered.scala @@ -0,0 +1,17 @@ +trait ThingWithPart { type Part } +type PartField[A] = ThingWithPart { type Part = A } +type ExtractPart[B] = B match { case PartField[a] => a } + +trait TC[C] +object TC: + def tcForOptionPart[D](implicit tc: TC[ExtractPart[D]]): TC[Option[ExtractPart[D]]] = new {} + +class Value +object Value: + implicit val tcValue: TC[Value] = new {} + +class ValuePartHolder extends ThingWithPart { type Part = Value } + +class Test: + def t1: Unit = + val tc = TC.tcForOptionPart[ValuePartHolder] diff --git a/tests/pos/i19857.scala b/tests/pos/i19857.scala new file mode 100644 index 000000000000..aeb6e49111c6 --- /dev/null +++ b/tests/pos/i19857.scala @@ -0,0 +1,23 @@ +sealed trait DFTypeAny + +sealed trait DFTuple[T <: NonEmptyTuple] extends DFTypeAny + +sealed trait DFBit extends DFTypeAny + +sealed trait DFValOf[T] + +type Of[T] <: DFTypeAny = T match + case DFTypeAny => T & DFTypeAny + case Product => FromProduct[T] + +type JUSTVAL[T] = DFValOf[Of[T]] + +type FromProduct[T <: Product] <: DFTypeAny = T match + case NonEmptyTuple => DFTuple[Tuple.Map[T, JUSTVAL]] + +trait Width2[T] + +object Width2: + inline given [T]: Width2[T] = new Width2[T] {} + +val x = summon[Width2[Of[(DFBit, DFBit)]]] From 64d6ec797481e997b1efc798705d098cefa87fc3 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Sat, 2 Mar 2024 12:47:44 +0000 Subject: [PATCH 2/2] Dealias an applied match alias --- .../src/dotty/tools/dotc/core/Types.scala | 5 +++- .../dotty/tools/dotc/inlines/Inlines.scala | 5 +--- tests/pos/i19821.scala | 23 +++++++++++++++++++ 3 files changed, 28 insertions(+), 5 deletions(-) create mode 100644 tests/pos/i19821.scala diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index e38fbbb4b355..2dc5da4871a5 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -1441,9 +1441,12 @@ object Types extends TypeUtils { private def dealias1(keep: AnnotatedType => Context ?=> Boolean, keepOpaques: Boolean)(using Context): Type = this match { case tp: TypeRef => if (tp.symbol.isClass) tp + else if keepOpaques && tp.symbol.is(Opaque) then tp else tp.info match { - case TypeAlias(alias) if !(keepOpaques && tp.symbol.is(Opaque)) => + case TypeAlias(alias) => alias.dealias1(keep, keepOpaques) + case MatchAlias(alias: AppliedType) => + (alias: Type).dealias1(keep, keepOpaques) case _ => tp } case app @ AppliedType(tycon, _) => diff --git a/compiler/src/dotty/tools/dotc/inlines/Inlines.scala b/compiler/src/dotty/tools/dotc/inlines/Inlines.scala index a97917e28771..616d764d18a2 100644 --- a/compiler/src/dotty/tools/dotc/inlines/Inlines.scala +++ b/compiler/src/dotty/tools/dotc/inlines/Inlines.scala @@ -446,16 +446,13 @@ object Inlines: evidence } - def unrollTupleTypes(tpe: Type): Option[List[Type]] = tpe.dealias match + def unrollTupleTypes(tpe: Type): Option[List[Type]] = tpe.dealias.normalized match case AppliedType(tycon, args) if defn.isTupleClass(tycon.typeSymbol) => Some(args) case AppliedType(tycon, head :: tail :: Nil) if tycon.isRef(defn.PairClass) => unrollTupleTypes(tail).map(head :: _) case tpe: TermRef if tpe.symbol == defn.EmptyTupleModule => Some(Nil) - case tpRef: TypeRef => tpRef.info match - case MatchAlias(alias) => unrollTupleTypes(alias.tryNormalize) - case _ => None case _ => None diff --git a/tests/pos/i19821.scala b/tests/pos/i19821.scala new file mode 100644 index 000000000000..2dbbd1c82447 --- /dev/null +++ b/tests/pos/i19821.scala @@ -0,0 +1,23 @@ +type F[X] = X match { case String => Option[Int] } +type G[X] = X match { case Option[x] => Int } + +trait T: + type S + val opt1: F[S] + val opt2: O1 + type O1 = F[S] + +class Test: + def test: Unit = + val t: T { type S = String } = ??? + + val i1: G[t.opt1.type] = ??? + val j1: Int = i1 + + val i2: G[t.opt2.type] = ??? + val j2: Int = i2 // was: +//[E007] Type Mismatch Error: tests/pos/i19821.scala:17:18 -------------------- +// val j2: Int = i2 +// ^^ +// Found: (i2 : G[(t.bar : t.O)]) +// Required: Int