Skip to content

Commit 90b580b

Browse files
committed
Change comparisons of opaque types.
Previously we had for any type `T` in ``` object m { type T } ``` that `m.this.T =:= m.T` as long as we are inside object `m` (outside, `m.this.T` makes no sense). Now, assume `T` is an opaque type. ``` object m { opaque type T = Int } ``` Inside `m` we have `this.m.T =:= Int`. Is it also true inside `m` that `m.T =:= Int`? Previously, we said no, which means that subtyping and =:= equality were not transitive in this case. We now say yes. We achieve this by lifting the external reference `m` to `m.this` if we are inside object `m`. An example that shows the difference is pos//opaque-groups-params.scala compiled from Tasty.
1 parent 6b3b446 commit 90b580b

File tree

3 files changed

+71
-45
lines changed

3 files changed

+71
-45
lines changed

compiler/src/dotty/tools/dotc/core/TypeComparer.scala

+54-33
Original file line numberDiff line numberDiff line change
@@ -233,13 +233,13 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] w
233233
implicit val ctx: Context = this.ctx
234234
tp2.info match {
235235
case info2: TypeAlias =>
236-
recur(tp1, info2.alias) || tryPackagePrefix2(tp1, tp2)
236+
recur(tp1, info2.alias)
237237
case _ => tp1 match {
238238
case tp1: NamedType =>
239239
tp1.info match {
240240
case info1: TypeAlias =>
241241
if (recur(info1.alias, tp2)) return true
242-
if (tp1.prefix.isStable) return tryPackagePrefix1(tp1, tp2)
242+
if (tp1.prefix.isStable) return false
243243
// If tp1.prefix is stable, the alias does contain all information about the original ref, so
244244
// there's no need to try something else. (This is important for performance).
245245
// To see why we cannot in general stop here, consider:
@@ -261,7 +261,7 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] w
261261
if ((sym1 ne NoSymbol) && (sym1 eq sym2))
262262
ctx.erasedTypes ||
263263
sym1.isStaticOwner ||
264-
isSubType(stripPackageObject(tp1.prefix), stripPackageObject(tp2.prefix)) ||
264+
isSubType(tp1.prefix, tp2.prefix) ||
265265
thirdTryNamed(tp2)
266266
else
267267
( (tp1.name eq tp2.name)
@@ -360,7 +360,7 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] w
360360
tp1.info match {
361361
case info1: TypeAlias =>
362362
if (recur(info1.alias, tp2)) return true
363-
if (tp1.prefix.isStable) return tryPackagePrefix1(tp1, tp2)
363+
if (tp1.prefix.isStable) return tryLiftedToThis1
364364
case _ =>
365365
if (tp1 eq NothingType) return true
366366
}
@@ -463,7 +463,7 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] w
463463
narrowGADTBounds(tp2, tp1, approx, isUpper = false)) &&
464464
{ tp1.isRef(NothingClass) || GADTusage(tp2.symbol) }
465465
}
466-
isSubApproxHi(tp1, info2.lo) || compareGADT || fourthTry
466+
isSubApproxHi(tp1, info2.lo) || compareGADT || tryLiftedToThis2 || fourthTry
467467

468468
case _ =>
469469
val cls2 = tp2.symbol
@@ -722,7 +722,7 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] w
722722
narrowGADTBounds(tp1, tp2, approx, isUpper = true)) &&
723723
{ tp2.isRef(AnyClass) || GADTusage(tp1.symbol) }
724724
}
725-
isSubType(hi1, tp2, approx.addLow) || compareGADT
725+
isSubType(hi1, tp2, approx.addLow) || compareGADT || tryLiftedToThis1
726726
case _ =>
727727
def isNullable(tp: Type): Boolean = tp.widenDealias match {
728728
case tp: TypeRef => tp.symbol.isNullableClass
@@ -976,7 +976,8 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] w
976976
case _ =>
977977
fourthTry
978978
}
979-
}
979+
} || tryLiftedToThis2
980+
980981
case _: TypeVar =>
981982
recur(tp1, tp2.superType)
982983
case tycon2: AnnotatedType if !tycon2.isRefining =>
@@ -1003,9 +1004,11 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] w
10031004
isSubType(bounds(param1).hi.applyIfParameterized(args1), tp2, approx.addLow)
10041005
case tycon1: TypeRef =>
10051006
val sym = tycon1.symbol
1006-
!sym.isClass && (
1007+
!sym.isClass && {
10071008
defn.isCompiletime_S(sym) && compareS(tp1, tp2, fromBelow = false) ||
1008-
recur(tp1.superType, tp2))
1009+
recur(tp1.superType, tp2) ||
1010+
tryLiftedToThis1
1011+
}
10091012
case tycon1: TypeProxy =>
10101013
recur(tp1.superType, tp2)
10111014
case _ =>
@@ -1046,6 +1049,16 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] w
10461049
def isSubApproxHi(tp1: Type, tp2: Type): Boolean =
10471050
tp1.eq(tp2) || tp2.ne(NothingType) && isSubType(tp1, tp2, approx.addHigh)
10481051

1052+
def tryLiftedToThis1: Boolean = {
1053+
val tp1a = liftToThis(tp1)
1054+
(tp1a ne tp1) && recur(tp1a, tp2)
1055+
}
1056+
1057+
def tryLiftedToThis2: Boolean = {
1058+
val tp2a = liftToThis(tp2)
1059+
(tp2a ne tp2) && recur(tp1, tp2a)
1060+
}
1061+
10491062
// begin recur
10501063
if (tp2 eq NoType) false
10511064
else if (tp1 eq tp2) true
@@ -1075,31 +1088,39 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] w
10751088
}
10761089
}
10771090

1078-
/** If `tp` is a reference to a package object, a reference to the package itself,
1079-
* otherwise `tp`.
1080-
*/
1081-
private def stripPackageObject(tp: Type) = tp match {
1082-
case tp: TermRef if tp.symbol.isPackageObject => tp.symbol.owner.thisType
1083-
case tp: ThisType if tp.cls.isPackageObject => tp.cls.owner.thisType
1084-
case _ => tp
1085-
}
1086-
1087-
/** If prefix of `tp1` is a reference to a package object, retry with
1088-
* the prefix pointing to the package itself, otherwise `false`
1089-
*/
1090-
private def tryPackagePrefix1(tp1: NamedType, tp2: Type) = {
1091-
val pre1 = tp1.prefix
1092-
val pre1a = stripPackageObject(pre1)
1093-
(pre1a ne pre1) && isSubType(tp1.withPrefix(pre1a), tp2)
1094-
}
1095-
1096-
/** If prefix of `tp2` is a reference to a package object, retry with
1097-
* the prefix pointing to the package itself, otherwise `false`
1091+
/** If `tp` is an external reference to an enclosing module M that contains opaque types,
1092+
* convert to M.this.
1093+
* Note: It would be legal to do the lifting also if M does not contain opaque types,
1094+
* but in this case the retries in tryLiftedToThis would be redundant.
10981095
*/
1099-
private def tryPackagePrefix2(tp1: Type, tp2: NamedType) = {
1100-
val pre2 = tp2.prefix
1101-
val pre2a = stripPackageObject(pre2)
1102-
(pre2a ne pre2) && isSubType(tp1, tp2.withPrefix(pre2a))
1096+
private def liftToThis(tp: Type): Type = {
1097+
1098+
def findEnclosingThis(moduleClass: Symbol, from: Symbol): Type =
1099+
if ((from.owner eq moduleClass) && from.isPackageObject && from.is(Opaque)) from.thisType
1100+
else if (from.is(Package)) tp
1101+
else if ((from eq moduleClass) && from.is(Opaque)) from.thisType
1102+
else if (from eq NoSymbol) tp
1103+
else findEnclosingThis(moduleClass, from.owner)
1104+
1105+
tp.stripTypeVar.stripAnnots match {
1106+
case tp: TermRef if tp.symbol.is(Module) =>
1107+
findEnclosingThis(tp.symbol.moduleClass, ctx.owner)
1108+
case tp: TypeRef =>
1109+
val pre1 = liftToThis(tp.prefix)
1110+
if (pre1 ne tp.prefix) tp.withPrefix(pre1) else tp
1111+
case tp: ThisType if tp.cls.is(Package) =>
1112+
findEnclosingThis(tp.cls, ctx.owner)
1113+
case tp: AppliedType =>
1114+
val tycon1 = liftToThis(tp.tycon)
1115+
if (tycon1 ne tp.tycon) tp.derivedAppliedType(tycon1, tp.args) else tp
1116+
case tp: TypeVar if tp.isInstantiated =>
1117+
liftToThis(tp.inst)
1118+
case tp: AnnotatedType =>
1119+
val parent1 = liftToThis(tp.parent)
1120+
if (parent1 ne tp.parent) tp.derivedAnnotatedType(parent1, tp.annot) else tp
1121+
case _ =>
1122+
tp
1123+
}
11031124
}
11041125

11051126
/** Optionally, the `n` such that `tp <:< ConstantType(Constant(n: Int))` */

compiler/src/dotty/tools/dotc/typer/Typer.scala

+14-9
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,18 @@ class Typer extends Namer
263263

264264
val curOwner = ctx.owner
265265

266+
/** Is curOwner a package object that should be skipped?
267+
* A package object should always be skipped if we look for a term.
268+
* That way we make sure we consider all overloaded alternatives of
269+
* a definition, even if they are in different source files.
270+
* If we are looking for a type, a package object should ne skipped
271+
* only if it does not contain opaque definitions. Package objects
272+
* with opaque definitions are significant, since opaque aliases
273+
* are only seen if the prefix is the this-type of the package object.
274+
*/
275+
def isTransparentPackageObject =
276+
curOwner.isPackageObject && (name.isTermName || !curOwner.is(Opaque))
277+
266278
// Can this scope contain new definitions? This is usually the first
267279
// context where either the scope or the owner changes wrt the
268280
// context immediately nested in it. But for package contexts, it's
@@ -279,14 +291,7 @@ class Typer extends Namer
279291
val isNewDefScope =
280292
if (curOwner.is(Package) && !curOwner.isRoot) curOwner ne ctx.outer.owner
281293
else ((ctx.scope ne lastCtx.scope) || (curOwner ne lastCtx.owner)) &&
282-
(name.isTypeName || !curOwner.isPackageObject)
283-
// If we are looking for a term, skip package objects and wait until we
284-
// hit the enclosing package. That way we make sure we consider
285-
// all overloaded alternatives of a definition, even if they are
286-
// in different source files.
287-
// On the other hand, for a type we should stop at the package object
288-
// since the type might be opaque, so we need to have the package object's
289-
// thisType as prefix in order to see the alias.
294+
!isTransparentPackageObject
290295

291296
if (isNewDefScope) {
292297
val defDenot = ctx.denotNamed(name, required)
@@ -1663,7 +1668,7 @@ class Typer extends Namer
16631668
val parentsWithClass = ensureFirstTreeIsClass(parents.mapconserve(typedParent).filterConserve(!_.isEmpty), cdef.nameSpan)
16641669
val parents1 = ensureConstrCall(cls, parentsWithClass)(superCtx)
16651670

1666-
var self1 = typed(self)(ctx.outer).asInstanceOf[ValDef] // outer context where class members are not visible
1671+
val self1 = typed(self)(ctx.outer).asInstanceOf[ValDef] // outer context where class members are not visible
16671672
if (self1.tpt.tpe.isError || classExistsOnSelf(cls.unforcedDecls, self1)) {
16681673
// fail fast to avoid typing the body with an error type
16691674
cdef.withType(UnspecifiedErrorType)

tests/run/implied-priority.scala

+3-3
Original file line numberDiff line numberDiff line change
@@ -127,13 +127,13 @@ def test4 = {
127127
*
128128
* It employs a more re-usable version of the result refinement trick.
129129
*/
130-
opaque type HigherPriority = Any
131130
object HigherPriority {
132-
def inject[T](x: T): T & HigherPriority = x
131+
opaque type Type = Any
132+
def inject[T](x: T): T & Type = x
133133
}
134134

135135
object fallback5 {
136-
implied [T] for (E[T] & HigherPriority) given (ev: E[T] = new E[T]("fallback")) = HigherPriority.inject(ev)
136+
implied [T] for (E[T] & HigherPriority.Type) given (ev: E[T] = new E[T]("fallback")) = HigherPriority.inject(ev)
137137
}
138138

139139
def test5 = {

0 commit comments

Comments
 (0)