Skip to content

Commit 73e6bf2

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 5054a74 commit 73e6bf2

File tree

3 files changed

+71
-45
lines changed

3 files changed

+71
-45
lines changed

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

Lines changed: 54 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -226,13 +226,13 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] {
226226
implicit val ctx: Context = this.ctx
227227
tp2.info match {
228228
case info2: TypeAlias =>
229-
recur(tp1, info2.alias) || tryPackagePrefix2(tp1, tp2)
229+
recur(tp1, info2.alias)
230230
case _ => tp1 match {
231231
case tp1: NamedType =>
232232
tp1.info match {
233233
case info1: TypeAlias =>
234234
if (recur(info1.alias, tp2)) return true
235-
if (tp1.prefix.isStable) return tryPackagePrefix1(tp1, tp2)
235+
if (tp1.prefix.isStable) return false
236236
// If tp1.prefix is stable, the alias does contain all information about the original ref, so
237237
// there's no need to try something else. (This is important for performance).
238238
// To see why we cannot in general stop here, consider:
@@ -254,7 +254,7 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] {
254254
if ((sym1 ne NoSymbol) && (sym1 eq sym2))
255255
ctx.erasedTypes ||
256256
sym1.isStaticOwner ||
257-
isSubType(stripPackageObject(tp1.prefix), stripPackageObject(tp2.prefix)) ||
257+
isSubType(tp1.prefix, tp2.prefix) ||
258258
thirdTryNamed(tp2)
259259
else
260260
( (tp1.name eq tp2.name)
@@ -353,7 +353,7 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] {
353353
tp1.info match {
354354
case info1: TypeAlias =>
355355
if (recur(info1.alias, tp2)) return true
356-
if (tp1.prefix.isStable) return tryPackagePrefix1(tp1, tp2)
356+
if (tp1.prefix.isStable) return tryLiftedToThis1
357357
case _ =>
358358
if (tp1 eq NothingType) return true
359359
}
@@ -456,7 +456,7 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] {
456456
narrowGADTBounds(tp2, tp1, approx, isUpper = false)) &&
457457
{ tp1.isRef(NothingClass) || GADTusage(tp2.symbol) }
458458
}
459-
isSubApproxHi(tp1, info2.lo) || compareGADT || fourthTry
459+
isSubApproxHi(tp1, info2.lo) || compareGADT || tryLiftedToThis2 || fourthTry
460460

461461
case _ =>
462462
val cls2 = tp2.symbol
@@ -715,7 +715,7 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] {
715715
narrowGADTBounds(tp1, tp2, approx, isUpper = true)) &&
716716
{ tp2.isRef(AnyClass) || GADTusage(tp1.symbol) }
717717
}
718-
isSubType(hi1, tp2, approx.addLow) || compareGADT
718+
isSubType(hi1, tp2, approx.addLow) || compareGADT || tryLiftedToThis1
719719
case _ =>
720720
def isNullable(tp: Type): Boolean = tp.widenDealias match {
721721
case tp: TypeRef => tp.symbol.isNullableClass
@@ -959,7 +959,8 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] {
959959
case _ =>
960960
fourthTry
961961
}
962-
}
962+
} || tryLiftedToThis2
963+
963964
case _: TypeVar =>
964965
recur(tp1, tp2.superType)
965966
case tycon2: AnnotatedType if !tycon2.isRefining =>
@@ -986,9 +987,11 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] {
986987
isSubType(bounds(param1).hi.applyIfParameterized(args1), tp2, approx.addLow)
987988
case tycon1: TypeRef =>
988989
val sym = tycon1.symbol
989-
!sym.isClass && (
990+
!sym.isClass && {
990991
defn.isCompiletime_S(sym) && compareS(tp1, tp2, fromBelow = false) ||
991-
recur(tp1.superType, tp2))
992+
recur(tp1.superType, tp2) ||
993+
tryLiftedToThis1
994+
}
992995
case tycon1: TypeProxy =>
993996
recur(tp1.superType, tp2)
994997
case _ =>
@@ -1029,6 +1032,16 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] {
10291032
def isSubApproxHi(tp1: Type, tp2: Type): Boolean =
10301033
tp1.eq(tp2) || tp2.ne(NothingType) && isSubType(tp1, tp2, approx.addHigh)
10311034

1035+
def tryLiftedToThis1: Boolean = {
1036+
val tp1a = liftToThis(tp1)
1037+
(tp1a ne tp1) && recur(tp1a, tp2)
1038+
}
1039+
1040+
def tryLiftedToThis2: Boolean = {
1041+
val tp2a = liftToThis(tp2)
1042+
(tp2a ne tp2) && recur(tp1, tp2a)
1043+
}
1044+
10321045
// begin recur
10331046
if (tp2 eq NoType) false
10341047
else if (tp1 eq tp2) true
@@ -1058,31 +1071,39 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] {
10581071
}
10591072
}
10601073

1061-
/** If `tp` is a reference to a package object, a reference to the package itself,
1062-
* otherwise `tp`.
1063-
*/
1064-
private def stripPackageObject(tp: Type) = tp match {
1065-
case tp: TermRef if tp.symbol.isPackageObject => tp.symbol.owner.thisType
1066-
case tp: ThisType if tp.cls.isPackageObject => tp.cls.owner.thisType
1067-
case _ => tp
1068-
}
1069-
1070-
/** If prefix of `tp1` is a reference to a package object, retry with
1071-
* the prefix pointing to the package itself, otherwise `false`
1072-
*/
1073-
private def tryPackagePrefix1(tp1: NamedType, tp2: Type) = {
1074-
val pre1 = tp1.prefix
1075-
val pre1a = stripPackageObject(pre1)
1076-
(pre1a ne pre1) && isSubType(tp1.withPrefix(pre1a), tp2)
1077-
}
1078-
1079-
/** If prefix of `tp2` is a reference to a package object, retry with
1080-
* the prefix pointing to the package itself, otherwise `false`
1074+
/** If `tp` is an external reference to an enclosing module M that contains opaque types,
1075+
* convert to M.this.
1076+
* Note: It would be legal to do the lifting also if M does not contain opaque types,
1077+
* but in this case the retries in tryLiftedToThis would be redundant.
10811078
*/
1082-
private def tryPackagePrefix2(tp1: Type, tp2: NamedType) = {
1083-
val pre2 = tp2.prefix
1084-
val pre2a = stripPackageObject(pre2)
1085-
(pre2a ne pre2) && isSubType(tp1, tp2.withPrefix(pre2a))
1079+
private def liftToThis(tp: Type): Type = {
1080+
1081+
def findEnclosingThis(moduleClass: Symbol, from: Symbol): Type =
1082+
if ((from.owner eq moduleClass) && from.isPackageObject && from.is(Opaque)) from.thisType
1083+
else if (from.is(Package)) tp
1084+
else if ((from eq moduleClass) && from.is(Opaque)) from.thisType
1085+
else if (from eq NoSymbol) tp
1086+
else findEnclosingThis(moduleClass, from.owner)
1087+
1088+
tp.stripTypeVar.stripAnnots match {
1089+
case tp: TermRef if tp.symbol.is(Module) =>
1090+
findEnclosingThis(tp.symbol.moduleClass, ctx.owner)
1091+
case tp: TypeRef =>
1092+
val pre1 = liftToThis(tp.prefix)
1093+
if (pre1 ne tp.prefix) tp.withPrefix(pre1) else tp
1094+
case tp: ThisType if tp.cls.is(Package) =>
1095+
findEnclosingThis(tp.cls, ctx.owner)
1096+
case tp: AppliedType =>
1097+
val tycon1 = liftToThis(tp.tycon)
1098+
if (tycon1 ne tp.tycon) tp.derivedAppliedType(tycon1, tp.args) else tp
1099+
case tp: TypeVar if tp.isInstantiated =>
1100+
liftToThis(tp.inst)
1101+
case tp: AnnotatedType =>
1102+
val parent1 = liftToThis(tp.parent)
1103+
if (parent1 ne tp.parent) tp.derivedAnnotatedType(parent1, tp.annot) else tp
1104+
case _ =>
1105+
tp
1106+
}
10861107
}
10871108

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

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

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,18 @@ class Typer extends Namer
260260

261261
val curOwner = ctx.owner
262262

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

288293
if (isNewDefScope) {
289294
val defDenot = ctx.denotNamed(name, required)
@@ -1668,7 +1673,7 @@ class Typer extends Namer
16681673
val parentsWithClass = ensureFirstTreeIsClass(parents.mapconserve(typedParent).filterConserve(!_.isEmpty), cdef.nameSpan)
16691674
val parents1 = ensureConstrCall(cls, parentsWithClass)(superCtx)
16701675

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

tests/run/implied-priority.scala

Lines changed: 3 additions & 3 deletions
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)