diff --git a/compiler/src/dotty/tools/dotc/core/TypeApplications.scala b/compiler/src/dotty/tools/dotc/core/TypeApplications.scala index eeb18eaa9cc7..efcad3307937 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeApplications.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeApplications.scala @@ -461,7 +461,7 @@ class TypeApplications(val self: Type) extends AnyVal { */ final def toBounds(using Context): TypeBounds = self match { case self: TypeBounds => self // this can happen for wildcard args - case _ => if (self.isMatch) MatchAlias(self) else TypeAlias(self) + case _ => AliasingBounds(self) } /** Translate a type of the form From[T] to either To[T] or To[? <: T] (if `wildcardArg` is set). Keep other types as they are. diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index 7af2f21bc56d..cb9961defb17 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -1375,7 +1375,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling * tp1 <:< app2 using isSubType (this might instantiate params in tp2) */ def compareLower(tycon2bounds: TypeBounds, tyconIsTypeRef: Boolean): Boolean = - if ((tycon2bounds.lo `eq` tycon2bounds.hi) && !tycon2bounds.isInstanceOf[MatchAlias]) + if ((tycon2bounds.lo `eq` tycon2bounds.hi) && !tycon2bounds.isMatchAlias) if (tyconIsTypeRef) recur(tp1, tp2.superTypeNormalized) && recordGadtUsageIf(MatchType.thatReducesUsingGadt(tp2)) else isSubApproxHi(tp1, tycon2bounds.lo.applyIfParameterized(args2)) else diff --git a/compiler/src/dotty/tools/dotc/core/TypeOps.scala b/compiler/src/dotty/tools/dotc/core/TypeOps.scala index 1bec455c5495..d88b61d41e2f 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeOps.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeOps.scala @@ -143,7 +143,7 @@ object TypeOps: defn.MatchCase(simplify(pat, theMap), body) case tp: AppliedType => tp.tycon match - case tycon: TypeRef if tycon.info.isInstanceOf[MatchAlias] => + case tycon: TypeRef if tp.isMatchAlias => isFullyDefined(tp, ForceDegree.all) case _ => val normed = tp.tryNormalize diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 62844a54bf48..7c647935ee32 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -456,14 +456,19 @@ 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 + + /** Does this application expand to a match type? */ + def isMatchAlias(using Context): Boolean = underlyingMatchType.exists 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 tp: AppliedType => tp.underlyingMatchType case _ => NoType } @@ -4529,6 +4534,9 @@ object Types extends TypeUtils { private var myEvalRunId: RunId = NoRunId private var myEvalued: Type = uninitialized + private var validUnderlyingMatch: Period = Nowhere + private var cachedUnderlyingMatch: 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 @@ -4585,31 +4593,38 @@ object Types extends TypeUtils { case nil => x foldArgs(op(x, tycon), args) + /** Exists if the tycon is a TypeRef of an alias with an underlying match type. + * Anything else should have already been reduced in `appliedTo` by the TypeAssigner. + */ + override def underlyingMatchType(using Context): Type = + if ctx.period != validUnderlyingMatch then + validUnderlyingMatch = if tycon.isProvisional then Nowhere else ctx.period + cachedUnderlyingMatch = superType.underlyingMatchType + cachedUnderlyingMatch + override def tryNormalize(using Context): Type = tycon.stripTypeVar match { case tycon: TypeRef => - def tryMatchAlias = tycon.info match { - case MatchAlias(alias) => + def tryMatchAlias = tycon.info match + case AliasingBounds(alias) if isMatchAlias => trace(i"normalize $this", typr, show = true) { MatchTypeTrace.recurseWith(this) { alias.applyIfParameterized(args.map(_.normalized)).tryNormalize + /* `applyIfParameterized` may reduce several HKTypeLambda applications + * before the underlying MatchType is reached. + * Even if they do not involve any match type normalizations yet, + * we still want to record these reductions in the MatchTypeTrace. + * They should however only be attempted if they eventually expand + * to a match type, which is ensured by the `isMatchAlias` guard. + */ } } case _ => NoType - } tryCompiletimeConstantFold.orElse(tryMatchAlias) case _ => NoType } - /** 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 - /** Is this an unreducible application to wildcard arguments? * This is the case if tycon is higher-kinded. This means * it is a subtype of a hk-lambda, but not a match alias. @@ -5137,20 +5152,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 - - /** Extractor for match types hidden behind an AppliedType/MatchAlias. */ - object InDisguise: - def unapply(tp: AppliedType)(using Context): Option[MatchType] = tp match - case AppliedType(tycon: TypeRef, args) => tycon.info match - case MatchAlias(alias) => alias.applyIfParameterized(args) match - case mt: MatchType => Some(mt) - case _ => None - case _ => None - case _ => None + def thatReducesUsingGadt(tp: Type)(using Context): Boolean = tp.underlyingMatchType match + case mt: MatchType => mt.reducesUsingGadt + case _ => false } enum MatchTypeCasePattern: @@ -5636,6 +5640,14 @@ object Types extends TypeUtils { def lower(lo: Type)(using Context): TypeBounds = apply(lo, defn.AnyType) } + object AliasingBounds: + /** A MatchAlias if alias is a match type and a TypeAlias o.w. + * Note that aliasing a MatchAlias returns a normal TypeAlias. + */ + def apply(alias: Type)(using Context): AliasingBounds = + if alias.isMatch then MatchAlias(alias) else TypeAlias(alias) + def unapply(tp: AliasingBounds): Option[Type] = Some(tp.alias) + object TypeAlias { def apply(alias: Type)(using Context): TypeAlias = unique(new TypeAlias(alias)) def unapply(tp: TypeAlias): Option[Type] = Some(tp.alias) diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index 57c0b2217e9d..a75cc6c666d0 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -412,9 +412,7 @@ class TreeUnpickler(reader: TastyReader, readType().appliedTo(until(end)(readType())) case TYPEBOUNDS => val lo = readType() - if nothingButMods(end) then - if lo.isMatch then MatchAlias(readVariances(lo)) - else TypeAlias(readVariances(lo)) + if nothingButMods(end) then AliasingBounds(readVariances(lo)) else val hi = readVariances(readType()) createNullableTypeBounds(lo, hi) diff --git a/compiler/src/dotty/tools/dotc/inlines/Inlines.scala b/compiler/src/dotty/tools/dotc/inlines/Inlines.scala index 65792d09f88c..fffe87c3f57a 100644 --- a/compiler/src/dotty/tools/dotc/inlines/Inlines.scala +++ b/compiler/src/dotty/tools/dotc/inlines/Inlines.scala @@ -452,9 +452,8 @@ object Inlines: 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 tpe: AppliedType if tpe.isMatchAlias => + unrollTupleTypes(tpe.tryNormalize) case _ => None diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 5162b3fed1b9..aa210a83cb4a 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -622,7 +622,7 @@ trait ImplicitRunInfo: sym.isClass && !isExcluded(sym) || sym.isOpaqueAlias || sym.is(Deferred, butNot = Param) - || sym.info.isInstanceOf[MatchAlias] + || sym.info.isMatchAlias private def computeIScope(rootTp: Type): OfTypeImplicits = @@ -636,7 +636,7 @@ trait ImplicitRunInfo: else if implicitScopeCache.contains(t) then parts += t else partSeen += t - t.dealias match + t.dealias.normalized match case t: TypeRef => if isAnchor(t.symbol) then parts += t @@ -663,7 +663,6 @@ trait ImplicitRunInfo: traverseChildren(t) case t => traverseChildren(t) - traverse(t.normalized) catch case ex: Throwable => handleRecursive("collectParts of", t.show, ex) def apply(tp: Type): collection.Set[Type] = @@ -775,6 +774,7 @@ trait ImplicitRunInfo: * if `T` is of the form `(P#x).type`, the anchors of `P`. * - If `T` is the this-type of a static object, the anchors of a term reference to that object. * - If `T` is some other this-type `P.this.type`, the anchors of `P`. + * - If `T` is match type or an applied match alias, the anchors of the normalization of `T`. * - If `T` is some other type, the union of the anchors of each constituent type of `T`. * * The _implicit scope_ of a type `tp` is the smallest set S of term references (i.e. TermRefs) @@ -787,7 +787,7 @@ trait ImplicitRunInfo: * - If `T` is a reference to an opaque type alias named `A`, S includes * a reference to an object `A` defined in the same scope as the type, if it exists, * as well as the implicit scope of `T`'s underlying type or bounds. - * - If `T` is a reference to an an abstract type or match type alias named `A`, + * - If `T` is a reference to an an abstract type or unreducible match type alias named `A`, * S includes a reference to an object `A` defined in the same scope as the type, * if it exists, as well as the implicit scopes of `T`'s lower and upper bound, * if present. diff --git a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala index 96c5e57dde0e..c7476f5d9777 100644 --- a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala +++ b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala @@ -519,9 +519,7 @@ trait TypeAssigner { def assignType(tree: untpd.TypeBoundsTree, lo: Tree, hi: Tree, alias: Tree)(using Context): TypeBoundsTree = tree.withType( if !alias.isEmpty then alias.tpe - else if lo eq hi then - if lo.tpe.isMatch then MatchAlias(lo.tpe) - else TypeAlias(lo.tpe) + else if lo eq hi then AliasingBounds(lo.tpe) else TypeBounds(lo.tpe, hi.tpe)) def assignType(tree: untpd.Bind, sym: Symbol)(using Context): Bind = diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 0b05bcd078ff..0ea83a5a011a 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1841,11 +1841,9 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer case _ => false } - val result = pt match { + val result = pt.underlyingMatchType match { case mt: MatchType if isMatchTypeShaped(mt) => typedDependentMatchFinish(tree, sel1, selType, tree.cases, mt) - case MatchType.InDisguise(mt) if isMatchTypeShaped(mt) => - typedDependentMatchFinish(tree, sel1, selType, tree.cases, mt) case _ => typedMatchFinish(tree, sel1, selType, tree.cases, pt) } diff --git a/compiler/test/dotc/pos-test-pickling.blacklist b/compiler/test/dotc/pos-test-pickling.blacklist index 3785f8fa6e06..81661e87b84e 100644 --- a/compiler/test/dotc/pos-test-pickling.blacklist +++ b/compiler/test/dotc/pos-test-pickling.blacklist @@ -64,6 +64,7 @@ i17149.scala tuple-fold.scala mt-redux-norm.perspective.scala i18211.scala +10867.scala # Opaque type i5720.scala diff --git a/tests/neg-macros/i11795.scala b/tests/neg-macros/i11795.scala deleted file mode 100644 index 2a7f89831e0e..000000000000 --- a/tests/neg-macros/i11795.scala +++ /dev/null @@ -1,10 +0,0 @@ -import scala.quoted._ -import scala.deriving._ - -def blah[P <: Product] - (m: Mirror.ProductOf[P]) - (using Quotes, Type[m.MirroredElemLabels], Type[m.MirroredElemTypes]) = { - type z = Tuple.Zip[m.MirroredElemLabels, m.MirroredElemTypes] - Type.of[z] // error - () -} diff --git a/tests/neg/i20071.scala b/tests/neg/i20071.scala new file mode 100644 index 000000000000..2d3dd5fe17d1 --- /dev/null +++ b/tests/neg/i20071.scala @@ -0,0 +1,28 @@ + +trait Scope +object Scope: + given i: Int = ??? + +type ReferencesScope[S] >: Int <: Int + +type ScopeToInt[Why] = Why match + case Scope => Int + +def foo[T](using d: ReferencesScope[T]): Any = ??? + +def bar[T](using d: ScopeToInt[T]): Any = ??? + +def test: Unit = + foo[Scope] // ok + bar[Scope] // error + + import Scope.i + bar[Scope] // ok + + /* + Before the changes: + `ScopeToInt[Scope]` may or may not be reduced before implicit search, + thereby impacting the scope considered for the search. `Scope.i` is included + iff `Scope` still appears in the type, which is the case only before reduction. + In contrast, `ReferencesScope[Scope]` is ok since it will never lose the anchor. + */ diff --git a/tests/pos-macros/i11795.scala b/tests/pos-macros/i11795.scala index 32eaccf2f4e2..26d1c4da1417 100644 --- a/tests/pos-macros/i11795.scala +++ b/tests/pos-macros/i11795.scala @@ -1,7 +1,17 @@ import scala.quoted._ import scala.deriving._ -def blah2[P <: Product, MEL <: Tuple: Type, MET <: Tuple: Type](m: Mirror.ProductOf[P] { type MirroredElemLabels = MEL; type MirroredElemTypes = MET})(using Quotes) = { +def blah[P <: Product] + (m: Mirror.ProductOf[P]) + (using Quotes, Type[m.MirroredElemLabels], Type[m.MirroredElemTypes]) = { + type z = Tuple.Zip[m.MirroredElemLabels, m.MirroredElemTypes] + Type.of[z] // error + () +} + +def blah2[P <: Product, MEL <: Tuple: Type, MET <: Tuple: Type] + (m: Mirror.ProductOf[P] { type MirroredElemLabels = MEL; type MirroredElemTypes = MET}) + (using Quotes) = { Type.of[Tuple.Zip[MEL, MET]] () } diff --git a/tests/pos/i15183/test_2.scala b/tests/pos/i15183/test_2.scala index 2069d5637734..eeb3848449be 100644 --- a/tests/pos/i15183/test_2.scala +++ b/tests/pos/i15183/test_2.scala @@ -1,4 +1,8 @@ // Fails in each cases below +import Decoder.{derived as _, given} +// NOTE Decoder.derived is already in the implicit scope +// but the others require an import as they depend on match type reduction + enum Env derives Decoder: case Local,Sit,Prod diff --git a/tests/pos/i19821.scala b/tests/pos/i19821.scala new file mode 100644 index 000000000000..0dcad965a38b --- /dev/null +++ b/tests/pos/i19821.scala @@ -0,0 +1,26 @@ + +object Test: + + trait T: + type S + type F = T.F[S] + + def foo: F + def bar: T.F[S] + + object T: + type F[X] = X match + case String => Option[Int] + + type G[X] = X match + case Option[x] => Int + + val t: T {type S = String} = ??? + + val b = t.bar + val m1: T.G[b.type] = ??? + val _: Int = m1 // Ok + + val f = t.foo + val m: T.G[f.type] = ??? + val _: Int = m // Error before changes