From be9c8becec97d1406bc74ba7bea439fde9c12611 Mon Sep 17 00:00:00 2001 From: Guillaume Martres Date: Wed, 20 Apr 2022 20:31:32 +0200 Subject: [PATCH 1/2] More aggressive reduction of type selection (fixes parboiled2) Previously, when reducing `a.T` we checked if the type of `a` was a subtype of `RefinedType(.., T, TypeAlias(...))`, now we extend this check to handle refinements where the `info` is a `TypeBounds` where both bounds are equal. This solves two big issues at once: - We can restore tests/pos/13491.scala to its original form from before #13780. The check for abstract types introduced by #13780 for soundness reasons is no longer hit because the type selection is reduced before we get to that point. This is important because parboiled2 relies on this and is therefore currently broken on 3.1.3-RC1 and main (https://github.com/sirthias/parboiled2/issues/365). - This fixes #14903 (slow compilation issue affecting parboiled2) without caching skolems (as in the alternative fix #14909). Again, this is due to the type containing skolem being reducible to a simpler type and therefore cacheable. --- .../src/dotty/tools/dotc/core/Types.scala | 9 ++-- tests/pos/13491.scala | 2 +- tests/pos/i14903a.scala | 22 ++++++++ tests/pos/i14903b.scala | 54 +++++++++++++++++++ 4 files changed, 82 insertions(+), 5 deletions(-) create mode 100644 tests/pos/i14903a.scala create mode 100644 tests/pos/i14903b.scala diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index ce7e93b51989..1b1fb41e3756 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -1548,9 +1548,10 @@ object Types { @tailrec def loop(pre: Type): Type = pre.stripTypeVar match { case pre: RefinedType => pre.refinedInfo match { - case TypeAlias(alias) => - if (pre.refinedName ne name) loop(pre.parent) else alias - case _ => loop(pre.parent) + case TypeBounds(lo, hi) if lo eq hi => + if (pre.refinedName ne name) loop(pre.parent) else lo + case _ => + loop(pre.parent) } case pre: RecType => val candidate = pre.parent.lookupRefined(name) @@ -4640,7 +4641,7 @@ object Types { myRepr.nn } - override def toString: String = s"Skolem($hashCode)" + override def toString: String = s"SkolemType($hashCode)" } /** A skolem type used to wrap the type of the qualifier of a selection. diff --git a/tests/pos/13491.scala b/tests/pos/13491.scala index b2c7941e15a0..d764494c6048 100644 --- a/tests/pos/13491.scala +++ b/tests/pos/13491.scala @@ -87,7 +87,7 @@ object Rule { def rule[I <: HList, O <: HList](r: Rule[I, O]): Rule[I, O] = ??? - implicit def valueMap[T, Out0 <: HList](m: Map[String, T])(implicit h: HListable[T] { type Out = Out0 }): RuleN[Out0] = ??? + implicit def valueMap[T, Out0 <: HList](m: Map[String, T])(implicit h: HListable[T]): RuleN[h.Out] = ??? } object Test { diff --git a/tests/pos/i14903a.scala b/tests/pos/i14903a.scala new file mode 100644 index 000000000000..9e4e28320987 --- /dev/null +++ b/tests/pos/i14903a.scala @@ -0,0 +1,22 @@ +trait Wrapper[T] { + type Out + } + + type Func[T] = + T match { + case String => Long + case Long => Int + case Int => Float + case Float => Double + case Double => Unit + case Unit => String + } + + implicit def infer[A]: Wrapper[One[A]] { type Out = Func[A] } = ??? + + trait One[A] { + def use(implicit w: Wrapper[One[A]]): One[w.Out] + } + + val x: One[Long] = null + val _ = x.use.use.use.use.use.use.use diff --git a/tests/pos/i14903b.scala b/tests/pos/i14903b.scala new file mode 100644 index 000000000000..cd61a12e858c --- /dev/null +++ b/tests/pos/i14903b.scala @@ -0,0 +1,54 @@ +import annotation.unchecked.uncheckedVariance + +sealed trait HList +sealed trait HNil extends HList +case object HNil extends HNil +case class ::[+H, +T <: HList](head: H, tail: T) extends HList + +type Concat[X <: HList, Y <: HList] <: HList = X match + case HNil => Y + case h :: t => h :: Concat[t, Y] + +/** + * Decompose L into Prefix ++ Suffix if possible +*/ +type StripSuffix[L <: HList, Suffix <: HList] <: Option[HList] = L match + case Suffix => Some[HNil] + case h :: t => StripSuffix[t, Suffix] match + case Some[x] => Some[h :: x] + case _ => None.type + case _ => None.type + +/** + * type-level implementation of this logic: + * Out = + * R if T has a tail of type L + * (L dropRight T) ++ R if L has a tail of type T +*/ +sealed trait TailSwitch[L <: HList, T <: HList, R <: HList]: + type Out <: HList + +object TailSwitch: + type TS[L <: HList, T <: HList, R <: HList] <: HList = + StripSuffix[T, L] match + case Some[_] => R + case _ => StripSuffix[L, T] match + case Some[x] => Concat[x, R] + + implicit def tailSwitch[L <: HList, T <: HList, R <: HList]: (TailSwitch[L, T, R] { + type Out = TS[L, T, R] + }) = new TailSwitch[L, T, R] { type Out = TS[L, T, R] } + +/** + * Rule popping I from stack and pushing back O +*/ +sealed class Rule[-I <: HList, +O <: HList]: + def ~[I2 <: HList, O2 <: HList](that: Rule[I2, O2])(implicit + i: TailSwitch[I2, O @uncheckedVariance, I @uncheckedVariance], + o: TailSwitch[O @uncheckedVariance, I2, O2] + ): Rule[i.Out, o.Out] = ??? + +object Test: + def dot = new Rule[HNil, HNil] {} + def num = new Rule[HNil, Byte :: HNil] {} + def pattern = num ~ dot ~ num ~ dot ~ num ~ dot ~ num // error From e0cce9a951c77a84e97d654fae326d7474410b62 Mon Sep 17 00:00:00 2001 From: Guillaume Martres Date: Wed, 20 Apr 2022 22:33:17 +0200 Subject: [PATCH 2/2] lookupRefined: check for AliasingBounds instead of TypeBounds with equal bounds It turns out that the problematic cases fixed in the previous commit only occurs because we create type aliases with info of type MatchAlias instead of TypeAlias if their rhs is an applied match type (Namer#TypeDefCompleter#typeSig calls `toBounds` on the rhs which does `if (self.isMatch) MatchAlias(self)`). I'm not sure if there is a good reason for that (and if so, do we need to be careful to avoid loops when dealiasing MatchAlias?) or if we should change the logic in Namer to return a TypeAlias instead. --- compiler/src/dotty/tools/dotc/core/Types.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 1b1fb41e3756..9ab49d2f2062 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -1548,8 +1548,8 @@ object Types { @tailrec def loop(pre: Type): Type = pre.stripTypeVar match { case pre: RefinedType => pre.refinedInfo match { - case TypeBounds(lo, hi) if lo eq hi => - if (pre.refinedName ne name) loop(pre.parent) else lo + case tp: AliasingBounds => + if (pre.refinedName ne name) loop(pre.parent) else tp.alias case _ => loop(pre.parent) }