Skip to content

Commit be9faa8

Browse files
committed
Make asSeenFrom idempotent
Let asSeenFrom generate a marker annotated type for any unsafe instantiation. Then cleanup in typedSelect.
1 parent 7a1dddd commit be9faa8

File tree

7 files changed

+100
-35
lines changed

7 files changed

+100
-35
lines changed
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package dotty.annotation.internal
2+
3+
import scala.annotation.Annotation
4+
5+
/** This annotation is used as a marker for unsafe
6+
* instantiations in asSeenFrom. See TypeOps.asSeenfrom for an explanation.
7+
*/
8+
class UnsafeNonvariant extends Annotation

src/dotty/tools/dotc/core/Contexts.scala

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -585,10 +585,16 @@ object Contexts {
585585
* of underlying during a controlled operation exists. */
586586
private[core] val pendingUnderlying = new mutable.HashSet[Type]
587587

588+
/** A flag that some unsafe nonvariant instantiation was encountered
589+
* in this run. Used as a shortcut to a avoid scans of types in
590+
* Typer.typedSelect.
591+
*/
592+
private[dotty] var unsafeNonvariant: RunId = NoRunId
593+
594+
// Phases state
588595

589596
private[core] var phasesPlan: List[List[Phase]] = _
590597

591-
// Phases state
592598
/** Phases by id */
593599
private[core] var phases: Array[Phase] = _
594600

src/dotty/tools/dotc/core/Definitions.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -475,6 +475,8 @@ class Definitions {
475475
def UncheckedStableAnnot(implicit ctx: Context) = UncheckedStableAnnotType.symbol.asClass
476476
lazy val UncheckedVarianceAnnotType = ctx.requiredClassRef("scala.annotation.unchecked.uncheckedVariance")
477477
def UncheckedVarianceAnnot(implicit ctx: Context) = UncheckedVarianceAnnotType.symbol.asClass
478+
lazy val UnsafeNonvariantAnnotType = ctx.requiredClassRef("dotty.annotation.internal.UnsafeNonvariant")
479+
def UnsafeNonvariantAnnot(implicit ctx: Context) = UnsafeNonvariantAnnotType.symbol.asClass
478480
lazy val VolatileAnnotType = ctx.requiredClassRef("scala.volatile")
479481
def VolatileAnnot(implicit ctx: Context) = VolatileAnnotType.symbol.asClass
480482
lazy val FieldMetaAnnotType = ctx.requiredClassRef("scala.annotation.meta.field")

src/dotty/tools/dotc/core/TypeOps.scala

Lines changed: 22 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import config.Printers._
88
import util.Positions._
99
import Decorators._
1010
import StdNames._
11+
import Annotations._
1112
import util.SimpleMap
1213
import collection.mutable
1314
import ast.tpd._
@@ -23,31 +24,29 @@ trait TypeOps { this: Context => // TODO: Make standalone object.
2324
*
2425
* and an expression `e` of type `C`. Then computing the type of `e.f` leads
2526
* to the query asSeenFrom(`C`, `(x: T)T`). What should its result be? The
26-
* naive answer `(x: C.T)C.T` is incorrect given that we treat `C.T` as the existential
27+
* naive answer `(x: C#T)C#T` is incorrect given that we treat `C#T` as the existential
2728
* `exists(c: C)c.T`. What we need to do instead is to skolemize the existential. So
2829
* the answer would be `(x: c.T)c.T` for some (unknown) value `c` of type `C`.
2930
* `c.T` is expressed in the compiler as a skolem type `Skolem(C)`.
3031
*
3132
* Now, skolemization is messy and expensive, so we want to do it only if we absolutely
32-
* must. We must skolemize if an unstable prefix is used in nonvariant or
33-
* contravariant position of the return type of asSeenFrom.
33+
* must. Also, skolemizing immediately would mean that asSeenFrom was no longer
34+
* idempotent - each call would return a type with a different skolem.
35+
* Instead we produce an annotated type that marks the prefix as unsafe:
3436
*
35-
* In the implementation of asSeenFrom, we first try to run asSeenFrom without
36-
* skolemizing. If that would be incorrect we will be told by the fact that
37-
* `unstable` is set in the passed AsSeenFromMap. In that case we run asSeenFrom
38-
* again with a skolemized prefix.
37+
* (x: (C @ UnsafeNonvariant)#T)C#T
38+
39+
* We also set a global state flag `unsafeNonvariant` to the current run.
40+
* When typing a Select node, typer will check that flag, and if it
41+
* points to the current run will scan the result type of the select for
42+
* @UnsafeNonvariant annotations. If it finds any, it will introduce a skolem
43+
* constant for the prefix and try again.
3944
*
40-
* In the interest of speed we want to avoid creating an AsSeenFromMap every time
41-
* asSeenFrom is called. So we do this here only if the prefix is unstable
42-
* (because then we need the map as a container for the unstable field). For
43-
* stable prefixes the map is `null`; it might however be instantiated later
44-
* for more complicated types.
45+
* The scheme is efficient in particular because we expect that unsafe situations are rare;
46+
* most compiles would contain none, so no scanning would be necessary.
4547
*/
46-
final def asSeenFrom(tp: Type, pre: Type, cls: Symbol): Type = {
47-
val m = if (isLegalPrefix(pre)) null else new AsSeenFromMap(pre, cls)
48-
var res = asSeenFrom(tp, pre, cls, m)
49-
if (m != null && m.unstable) asSeenFrom(tp, SkolemType(pre), cls) else res
50-
}
48+
final def asSeenFrom(tp: Type, pre: Type, cls: Symbol): Type =
49+
asSeenFrom(tp, pre, cls, null)
5150

5251
/** Helper method, taking a map argument which is instantiated only for more
5352
* complicated cases of asSeenFrom.
@@ -65,9 +64,11 @@ trait TypeOps { this: Context => // TODO: Make standalone object.
6564
case pre: SuperType => toPrefix(pre.thistpe, cls, thiscls)
6665
case _ =>
6766
if (thiscls.derivesFrom(cls) && pre.baseTypeRef(thiscls).exists) {
68-
if (theMap != null && theMap.currentVariance <= 0 && !isLegalPrefix(pre))
69-
theMap.unstable = true
70-
pre
67+
if (theMap != null && theMap.currentVariance <= 0 && !isLegalPrefix(pre)) {
68+
ctx.base.unsafeNonvariant = ctx.runId
69+
AnnotatedType(pre, Annotation(defn.UnsafeNonvariantAnnot, Nil))
70+
}
71+
else pre
7172
}
7273
else if ((pre.termSymbol is Package) && !(thiscls is Package))
7374
toPrefix(pre.select(nme.PACKAGE), cls, thiscls)
@@ -82,17 +83,14 @@ trait TypeOps { this: Context => // TODO: Make standalone object.
8283
val sym = tp.symbol
8384
if (sym.isStatic) tp
8485
else {
85-
val prevStable = theMap == null || !theMap.unstable
8686
val pre1 = asSeenFrom(tp.prefix, pre, cls, theMap)
87-
if (theMap != null && theMap.unstable && prevStable) {
87+
if (pre1.isUnsafeNonvariant)
8888
pre1.member(tp.name).info match {
8989
case TypeAlias(alias) =>
9090
// try to follow aliases of this will avoid skolemization.
91-
theMap.unstable = false
9291
return alias
9392
case _ =>
9493
}
95-
}
9694
tp.derivedSelect(pre1)
9795
}
9896
case tp: ThisType =>
@@ -122,11 +120,6 @@ trait TypeOps { this: Context => // TODO: Make standalone object.
122120

123121
/** A method to export the current variance of the map */
124122
def currentVariance = variance
125-
126-
/** A field which indicates whether an unstable argument in nonvariant
127-
* or contravariant position was encountered.
128-
*/
129-
var unstable = false
130123
}
131124

132125
/** Approximate a type `tp` with a type that does not contain skolem types.

src/dotty/tools/dotc/core/Types.scala

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,16 @@ object Types {
187187
def isRepeatedParam(implicit ctx: Context): Boolean =
188188
typeSymbol eq defn.RepeatedParamClass
189189

190+
/** Does this type carry an UnsafeNonvariant annotation? */
191+
final def isUnsafeNonvariant(implicit ctx: Context): Boolean = this match {
192+
case AnnotatedType(_, annot) => annot.symbol == defn.UnsafeNonvariantAnnot
193+
case _ => false
194+
}
195+
196+
/** Does this type have an UnsafeNonvariant annotation on one of its parts? */
197+
final def hasUnsafeNonvariant(implicit ctx: Context): Boolean =
198+
new HasUnsafeNonAccumulator().apply(false, this)
199+
190200
/** Is this the type of a method that has a repeated parameter type as
191201
* last parameter type?
192202
*/
@@ -741,6 +751,12 @@ object Types {
741751
case _ => this
742752
}
743753

754+
/** If this is a skolem, its underlying type, otherwise the type itself */
755+
final def widenSkolem(implicit ctx: Context): Type = this match {
756+
case tp: SkolemType => tp.underlying
757+
case _ => this
758+
}
759+
744760
/** Follow aliases and dereferences LazyRefs and instantiated TypeVars until type
745761
* is no longer alias type, LazyRef, or instantiated type variable.
746762
*/
@@ -3180,6 +3196,10 @@ object Types {
31803196
def apply(x: Unit, tp: Type): Unit = foldOver(p(tp), tp)
31813197
}
31823198

3199+
class HasUnsafeNonAccumulator(implicit ctx: Context) extends TypeAccumulator[Boolean] {
3200+
def apply(x: Boolean, tp: Type) = x || tp.isUnsafeNonvariant || foldOver(x, tp)
3201+
}
3202+
31833203
class NamedPartsAccumulator(p: NamedType => Boolean)(implicit ctx: Context) extends TypeAccumulator[mutable.Set[NamedType]] {
31843204
override def stopAtStatic = false
31853205
def maybeAdd(x: mutable.Set[NamedType], tp: NamedType) = if (p(tp)) x += tp else x

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

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -282,12 +282,35 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit
282282
checkValue(tree1, pt)
283283
}
284284

285+
private def typedSelect(tree: untpd.Select, pt: Type, qual: Tree)(implicit ctx: Context): Select =
286+
healNonvariant(
287+
checkValue(assignType(cpy.Select(tree)(qual, tree.name), qual), pt),
288+
pt)
289+
290+
/** Let `tree = p.n` where `p: T`. If tree's type is an unsafe instantiation
291+
* (see TypeOps#asSeenFrom for how this can happen), rewrite the prefix `p`
292+
* to `(p: <unknown skolem of type T>)` and try again with the new (stable)
293+
* prefix. If the result has another unsafe instantiation, raise an error.
294+
*/
295+
private def healNonvariant[T <: Tree](tree: T, pt: Type)(implicit ctx: Context): T =
296+
if (ctx.unsafeNonvariant == ctx.runId && tree.tpe.widen.hasUnsafeNonvariant)
297+
tree match {
298+
case tree @ Select(qual, _) if !qual.tpe.isStable =>
299+
val alt = typedSelect(tree, pt, Typed(qual, TypeTree(SkolemType(qual.tpe.widen))))
300+
/*typr.*/println(d"healed type: ${tree.tpe} --> $alt")
301+
alt.asInstanceOf[T]
302+
case _ =>
303+
ctx.error(d"unsafe instantiation of type ${tree.tpe}", tree.pos)
304+
tree
305+
}
306+
else tree
307+
285308
def typedSelect(tree: untpd.Select, pt: Type)(implicit ctx: Context): Tree = track("typedSelect") {
286309
def asSelect(implicit ctx: Context): Tree = {
287310
val qual1 = typedExpr(tree.qualifier, selectionProto(tree.name, pt, this))
288311
if (tree.name.isTypeName) checkStable(qual1.tpe, qual1.pos)
289-
checkValue(assignType(cpy.Select(tree)(qual1, tree.name), qual1), pt)
290-
}
312+
typedSelect(tree, pt, qual1)
313+
}
291314

292315
def asJavaSelectFromTypeTree(implicit ctx: Context): Tree = {
293316
// Translate names in Select/Ident nodes to type names.
@@ -382,7 +405,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit
382405
checkSimpleKinded(typedType(tree.tpt))
383406
val expr1 =
384407
if (isWildcard) tree.expr withType tpt1.tpe
385-
else typed(tree.expr, tpt1.tpe)
408+
else typed(tree.expr, tpt1.tpe.widenSkolem)
386409
assignType(cpy.Typed(tree)(expr1, tpt1), tpt1)
387410
}
388411
tree.expr match {
@@ -438,9 +461,10 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit
438461
val setter = pre.member(setterName)
439462
lhsCore match {
440463
case lhsCore: RefTree if setter.exists =>
441-
val setterTypeRaw = pre select (setterName, setter)
464+
val setterTypeRaw = pre.select(setterName, setter)
442465
val setterType = ensureAccessible(setterTypeRaw, isSuperSelection(lhsCore), tree.pos)
443-
val lhs2 = untpd.rename(lhsCore, setterName).withType(setterType)
466+
val lhs2 = healNonvariant(
467+
untpd.rename(lhsCore, setterName).withType(setterType), WildcardType)
444468
typedUnadapted(cpy.Apply(tree)(untpd.TypedSplice(lhs2), tree.rhs :: Nil))
445469
case _ =>
446470
reassignmentToVal

tests/pos/i583a.scala

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,15 @@
1+
object Test {
2+
3+
class C {
4+
type T
5+
val f: T = ???
6+
def foo(x: T): T = x
7+
}
8+
9+
var x = new C
10+
val y = x.foo(???)
11+
12+
}
113
object Test1 {
214

315
class Box[B](x: B)

0 commit comments

Comments
 (0)