Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make aliases of MatchAliases normal TypeAliases #19871

Merged
merged 5 commits into from
Apr 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/core/TypeApplications.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/core/TypeComparer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/core/TypeOps.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
70 changes: 41 additions & 29 deletions compiler/src/dotty/tools/dotc/core/Types.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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)
Expand Down
4 changes: 1 addition & 3 deletions compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
5 changes: 2 additions & 3 deletions compiler/src/dotty/tools/dotc/inlines/Inlines.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
8 changes: 4 additions & 4 deletions compiler/src/dotty/tools/dotc/typer/Implicits.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 =

Expand All @@ -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
Expand All @@ -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] =
Expand Down Expand Up @@ -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)
Expand All @@ -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.
Expand Down
4 changes: 1 addition & 3 deletions compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand Down
4 changes: 1 addition & 3 deletions compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
1 change: 1 addition & 0 deletions compiler/test/dotc/pos-test-pickling.blacklist
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ i17149.scala
tuple-fold.scala
mt-redux-norm.perspective.scala
i18211.scala
10867.scala

# Opaque type
i5720.scala
Expand Down
10 changes: 0 additions & 10 deletions tests/neg-macros/i11795.scala

This file was deleted.

28 changes: 28 additions & 0 deletions tests/neg/i20071.scala
Original file line number Diff line number Diff line change
@@ -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.
*/
12 changes: 11 additions & 1 deletion tests/pos-macros/i11795.scala
Original file line number Diff line number Diff line change
@@ -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]]
()
}
4 changes: 4 additions & 0 deletions tests/pos/i15183/test_2.scala
Original file line number Diff line number Diff line change
@@ -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

Expand Down
26 changes: 26 additions & 0 deletions tests/pos/i19821.scala
Original file line number Diff line number Diff line change
@@ -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