From 90c3fbde37a70fdd366ed25d2082e05ddf24ac3e Mon Sep 17 00:00:00 2001 From: Eugene Flesselle Date: Wed, 3 Apr 2024 13:29:38 +0200 Subject: [PATCH 1/5] Normalize types before collecting parts determining implicit scope This is necessary to ensure the implicit scope is consistent when involving match types, since they may or may not have been reduced before implicit search. We can for example get different results when loading from tasty than when in the same run. Fixes #20071 --- .../dotty/tools/dotc/typer/Implicits.scala | 6 ++-- tests/neg/i20071.scala | 28 +++++++++++++++++++ tests/pos/i15183/test_2.scala | 4 +++ 3 files changed, 35 insertions(+), 3 deletions(-) create mode 100644 tests/neg/i20071.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 5162b3fed1b9..949e791d0496 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -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/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/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 From ef7db7ad8142d205feae1dfcfef59c61670767b6 Mon Sep 17 00:00:00 2001 From: Eugene Flesselle Date: Wed, 28 Feb 2024 21:58:08 +0100 Subject: [PATCH 2/5] Make aliases of `MatchAlias`es normal `TypeAlias`es Make `isMatch` false for applied `MatchAlias`es, i.e. true only for `MatchType`s and higher-kinded abstraction of them. As a result, code using `isMatch` to choose between a `TypeAlias` and `MatchAlias` will now use a `TypeAlias` when aliasing a `MatchAlias`. Which in turn allows for better de-aliasing, since `dealias` only de-aliases standard type aliases. The logic for this distinction has also been extracted to the common `AliasingBounds` supertype. `tryNormalize` on `AppliedType`s should only attempt reduction if there is an underlying match type. This could previously be identified by a `MatchAlias` tycon. We now need a recursive check. --- .../tools/dotc/core/TypeApplications.scala | 2 +- .../src/dotty/tools/dotc/core/Types.scala | 32 ++++++++++++++++--- .../tools/dotc/core/tasty/TreeUnpickler.scala | 4 +-- .../dotty/tools/dotc/inlines/Inlines.scala | 5 ++- .../dotty/tools/dotc/typer/TypeAssigner.scala | 4 +-- .../test/dotc/pos-test-pickling.blacklist | 1 + tests/neg-macros/i11795.scala | 10 ------ tests/pos-macros/i11795.scala | 12 ++++++- tests/pos/i19821.scala | 26 +++++++++++++++ 9 files changed, 70 insertions(+), 26 deletions(-) delete mode 100644 tests/neg-macros/i11795.scala create mode 100644 tests/pos/i19821.scala 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/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 62844a54bf48..9158062e10b7 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -458,7 +458,10 @@ object Types extends TypeUtils { /** Is this a match type or a higher-kinded abstraction of one? */ - def isMatch(using Context): Boolean = underlyingMatchType.exists + 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 @@ -4587,16 +4590,22 @@ object Types extends TypeUtils { 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 @@ -4606,7 +4615,12 @@ object Types extends TypeUtils { def isMatchAlias(using Context): Boolean = tycon.stripTypeVar match case tycon: TypeRef => tycon.info match - case _: MatchAlias => true + case AliasingBounds(alias) => + alias.underlyingMatchType.exists + /* This is the only true case since anything other than + * a TypeRef of an alias with an underlying match type + * should have been already reduced by `appliedTo` in the TypeAssigner. + */ case _ => false case _ => false @@ -5636,6 +5650,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/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/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/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/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 From 259a16c64fc7e2868cb71f988299d23dd1d8e887 Mon Sep 17 00:00:00 2001 From: Eugene Flesselle Date: Sun, 31 Mar 2024 19:11:18 +0200 Subject: [PATCH 3/5] Replace usages of `MatchType.InDisguise` by `underlyingMatchType` --- .../src/dotty/tools/dotc/core/TypeOps.scala | 2 +- compiler/src/dotty/tools/dotc/core/Types.scala | 17 +++-------------- compiler/src/dotty/tools/dotc/typer/Typer.scala | 4 +--- 3 files changed, 5 insertions(+), 18 deletions(-) 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 9158062e10b7..bbfd3953c983 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -5151,20 +5151,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: 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) } From 389f5be29879f44178a61a262880ae986d1512a8 Mon Sep 17 00:00:00 2001 From: Eugene Flesselle Date: Mon, 8 Apr 2024 19:59:17 +0200 Subject: [PATCH 4/5] Replace usages of `isInstanceOf[MatchAlias]` by `isMatchAlias` --- .../dotty/tools/dotc/core/TypeComparer.scala | 2 +- .../src/dotty/tools/dotc/core/Types.scala | 19 ++++--------------- .../dotty/tools/dotc/typer/Implicits.scala | 2 +- 3 files changed, 6 insertions(+), 17 deletions(-) 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/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index bbfd3953c983..68f76f2c2500 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -456,13 +456,15 @@ 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? - */ + /** 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 @@ -4611,19 +4613,6 @@ object Types extends TypeUtils { NoType } - /** Does this application expand to a match type? */ - def isMatchAlias(using Context): Boolean = tycon.stripTypeVar match - case tycon: TypeRef => - tycon.info match - case AliasingBounds(alias) => - alias.underlyingMatchType.exists - /* This is the only true case since anything other than - * a TypeRef of an alias with an underlying match type - * should have been already reduced by `appliedTo` in the TypeAssigner. - */ - 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. diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 949e791d0496..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 = From 1dc5b995cfb84dd3e2673041d6e954ed6043900e Mon Sep 17 00:00:00 2001 From: Eugene Flesselle Date: Mon, 8 Apr 2024 20:00:47 +0200 Subject: [PATCH 5/5] Cache underlyingMatchType for AppliedTypes `def underlyingMatchType` had an `isMatchAlias` guard for `AppliedType`s. This used to be a quick check preventing unnecessary recursions and superType computations. But `isMatchAlias` is now itself mutually recursive with `underlyingMatchType`, so we cache it for AppliedTypes to alleviate this. --- compiler/src/dotty/tools/dotc/core/Types.scala | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 68f76f2c2500..7c647935ee32 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -468,7 +468,7 @@ object Types extends TypeUtils { 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 } @@ -4534,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 @@ -4590,6 +4593,15 @@ 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