diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 4be905beefcc..a0018d2e4abd 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -452,16 +452,14 @@ object Types extends TypeUtils { /** Is this a MethodType for which the parameters will not be used? */ def hasErasedParams(using Context): Boolean = false - /** Is this a match type or a higher-kinded abstraction of one? - */ - def isMatch(using Context): Boolean = underlyingMatchType.exists + /** Is this a match type or a higher-kinded abstraction of one? */ + def isMatch(using Context): Boolean = stripped match + case tp: MatchType => true + case tp: HKTypeLambda => tp.resType.isMatch + case _ => false - def underlyingMatchType(using Context): Type = stripped match { - case tp: MatchType => tp - case tp: HKTypeLambda => tp.resType.underlyingMatchType - case tp: AppliedType if tp.isMatchAlias => tp.superType.underlyingMatchType - case _ => NoType - } + /** Does this application expand to a match type? */ + def isMatchAlias(using Context): Boolean = underlyingNormalizable.isMatch /** Is this a higher-kinded type lambda with given parameter variances? * These lambdas are used as the RHS of higher-kinded abstract types or @@ -1477,19 +1475,24 @@ object Types extends TypeUtils { } deskolemizer(this) - /** The result of normalization using `tryNormalize`, or the type itself if - * tryNormlize yields NoType + /** The result of normalization, or the type itself if none apply. */ + final def normalized(using Context): Type = tryNormalize.orElse(this) + + /** If this type has an underlying match type or applied compiletime.ops, + * then the result after applying all toplevel normalizations, otherwise NoType. */ - final def normalized(using Context): Type = { - val normed = tryNormalize - if (normed.exists) normed else this - } + def tryNormalize(using Context): Type = underlyingNormalizable match + case mt: MatchType => mt.reduced.normalized + case tp: AppliedType => tp.tryCompiletimeConstantFold + case _ => NoType - /** If this type can be normalized at the top-level by rewriting match types - * of S[n] types, the result after applying all toplevel normalizations, - * otherwise NoType + /** Perform successive strippings, and beta-reductions of applied types until + * a match type or applied compiletime.ops is reached, if any, otherwise NoType. */ - def tryNormalize(using Context): Type = NoType + def underlyingNormalizable(using Context): Type = stripped.stripLazyRef match + case tp: MatchType => tp + case tp: AppliedType => tp.underlyingNormalizable + case _ => NoType private def widenDealias1(keep: AnnotatedType => Context ?=> Boolean)(using Context): Type = { val res = this.widen.dealias1(keep, keepOpaques = false) @@ -3106,8 +3109,6 @@ object Types extends TypeUtils { private var myRef: Type | Null = null private var computed = false - override def tryNormalize(using Context): Type = ref.tryNormalize - def ref(using Context): Type = if computed then if myRef == null then @@ -4455,6 +4456,9 @@ object Types extends TypeUtils { private var myEvalRunId: RunId = NoRunId private var myEvalued: Type = uninitialized + private var validUnderlyingNormalizable: Period = Nowhere + private var cachedUnderlyingNormalizable: Type = uninitialized + def isGround(acc: TypeAccumulator[Boolean])(using Context): Boolean = if myGround == 0 then myGround = if acc.foldOver(true, this) then 1 else -1 myGround > 0 @@ -4511,30 +4515,25 @@ object Types extends TypeUtils { case nil => x foldArgs(op(x, tycon), args) - override def tryNormalize(using Context): Type = tycon.stripTypeVar match { - case tycon: TypeRef => - def tryMatchAlias = tycon.info match { - case MatchAlias(alias) => - trace(i"normalize $this", typr, show = true) { - MatchTypeTrace.recurseWith(this) { - alias.applyIfParameterized(args.map(_.normalized)).tryNormalize - } - } - case _ => - NoType - } - tryCompiletimeConstantFold.orElse(tryMatchAlias) - case _ => - NoType - } + /** Exists if the tycon is a TypeRef of an alias with an underlying match type, + * or a compiletime applied type. Anything else should have already been + * reduced in `appliedTo` by the TypeAssigner. This may reduce several + * HKTypeLambda applications before the underlying normalizable type is reached. + */ + override def underlyingNormalizable(using Context): Type = + if ctx.period != validUnderlyingNormalizable then tycon match + case tycon: TypeRef if defn.isCompiletimeAppliedType(tycon.symbol) => + cachedUnderlyingNormalizable = this + validUnderlyingNormalizable = ctx.period + case _ => + cachedUnderlyingNormalizable = superType.underlyingNormalizable + validUnderlyingNormalizable = validSuper + cachedUnderlyingNormalizable - /** Does this application expand to a match type? */ - def isMatchAlias(using Context): Boolean = tycon.stripTypeVar match - case tycon: TypeRef => - tycon.info match - case _: MatchAlias => true - case _ => false - case _ => false + override def tryNormalize(using Context): Type = + if isMatchAlias && MatchTypeTrace.isRecording then + MatchTypeTrace.recurseWith(this)(superType.tryNormalize) + else super.tryNormalize /** Is this an unreducible application to wildcard arguments? * This is the case if tycon is higher-kinded. This means @@ -4957,12 +4956,6 @@ object Types extends TypeUtils { private var myReduced: Type | Null = null private var reductionContext: util.MutableMap[Type, Type] = _ - override def tryNormalize(using Context): Type = - try - reduced.normalized - catch - case ex: Throwable => - handleRecursive("normalizing", s"${scrutinee.show} match ..." , ex) def reduced(using Context): Type = atPhaseNoLater(elimOpaquePhase) { @@ -5037,10 +5030,9 @@ object Types extends TypeUtils { def apply(bound: Type, scrutinee: Type, cases: List[Type])(using Context): MatchType = unique(new CachedMatchType(bound, scrutinee, cases)) - def thatReducesUsingGadt(tp: Type)(using Context): Boolean = tp match - case MatchType.InDisguise(mt) => mt.reducesUsingGadt - case mt: MatchType => mt.reducesUsingGadt - case _ => false + def thatReducesUsingGadt(tp: Type)(using Context): Boolean = tp.underlyingNormalizable match + case mt: MatchType => mt.reducesUsingGadt + case _ => false /** Extractor for match types hidden behind an AppliedType/MatchAlias. */ object InDisguise: diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index ecbcb7b2efda..827df499f470 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1789,7 +1789,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer case _ => false } - val result = pt match { + val result = pt.underlyingNormalizable match { case mt: MatchType if isMatchTypeShaped(mt) => typedDependentMatchFinish(tree, sel1, selType, tree.cases, mt) case MatchType.InDisguise(mt) if isMatchTypeShaped(mt) => diff --git a/compiler/test/dotc/neg-best-effort-pickling.blacklist b/compiler/test/dotc/neg-best-effort-pickling.blacklist new file mode 100644 index 000000000000..a582f085dd30 --- /dev/null +++ b/compiler/test/dotc/neg-best-effort-pickling.blacklist @@ -0,0 +1,22 @@ +export-in-extension.scala +i12456.scala +i8623.scala +i1642.scala +i16696.scala +constructor-proxy-values.scala +i9328.scala +i15414.scala +i6796.scala +i14013.scala +toplevel-cyclic +curried-dependent-ift.scala +i17121.scala +illegal-match-types.scala +i13780-1.scala +i20317a.scala +i11226.scala +i974.scala + +# semantic db generation fails in the first compilation +i1642.scala +i15158.scala diff --git a/tests/neg/i12049d.check b/tests/neg/i12049d.check new file mode 100644 index 000000000000..fdb13aae4e43 --- /dev/null +++ b/tests/neg/i12049d.check @@ -0,0 +1,14 @@ +-- [E007] Type Mismatch Error: tests/neg/i12049d.scala:14:52 ----------------------------------------------------------- +14 |val x: M[NotRelevant[Nothing], Relevant[Nothing]] = 2 // error + | ^ + | Found: (2 : Int) + | Required: M[NotRelevant[Nothing], Relevant[Nothing]] + | + | Note: a match type could not be fully reduced: + | + | trying to reduce M[NotRelevant[Nothing], Relevant[Nothing]] + | trying to reduce Relevant[Nothing] + | failed since selector Nothing + | is uninhabited (there are no values of that type). + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg/i12049d.scala b/tests/neg/i12049d.scala new file mode 100644 index 000000000000..0011ec1f00b1 --- /dev/null +++ b/tests/neg/i12049d.scala @@ -0,0 +1,14 @@ + +trait A +trait B + +type M[X, Y] = Y match + case A => Int + case B => String + +type Relevant[Z] = Z match + case A => B +type NotRelevant[Z] = Z match + case B => A + +val x: M[NotRelevant[Nothing], Relevant[Nothing]] = 2 // error diff --git a/tests/pos/i20482.scala b/tests/pos/i20482.scala new file mode 100644 index 000000000000..2a7680df054d --- /dev/null +++ b/tests/pos/i20482.scala @@ -0,0 +1,16 @@ +trait WrapperType[A] + +case class Foo[A]() + +case class Bar[A]() + +type FooToBar[D[_]] = [A] =>> D[Unit] match { + case Foo[Unit] => Bar[A] +} + +case class Test() +object Test { + implicit val wrapperType: WrapperType[Bar[Test]] = new WrapperType[Bar[Test]] {} +} + +val test = summon[WrapperType[FooToBar[Foo][Test]]] diff --git a/tests/pos/matchtype-unusedArg/A_1.scala b/tests/pos/matchtype-unusedArg/A_1.scala new file mode 100644 index 000000000000..4364a812f12c --- /dev/null +++ b/tests/pos/matchtype-unusedArg/A_1.scala @@ -0,0 +1,8 @@ + +type Rec[X] = X match + case Int => Rec[X] + +type M[Unused, Y] = Y match + case String => Double + +def foo[X](d: M[Rec[X], "hi"]) = ??? diff --git a/tests/pos/matchtype-unusedArg/B_2.scala b/tests/pos/matchtype-unusedArg/B_2.scala new file mode 100644 index 000000000000..437e53a1691d --- /dev/null +++ b/tests/pos/matchtype-unusedArg/B_2.scala @@ -0,0 +1,2 @@ + +def Test = foo[Int](3d) // crash before changes