Skip to content

Commit c2f0db4

Browse files
committed
Revised scheme:Mark non-local assigns at Typer
We need to mark such assigns in the phase before PostTyper since they can appear in companion objects that come after the variable declaration.
1 parent 3cc86de commit c2f0db4

File tree

8 files changed

+58
-32
lines changed

8 files changed

+58
-32
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -993,6 +993,7 @@ class Definitions {
993993
// Annotation classes
994994
@tu lazy val AllowConversionsAnnot: ClassSymbol = requiredClass("scala.annotation.allowConversions")
995995
@tu lazy val AnnotationDefaultAnnot: ClassSymbol = requiredClass("scala.annotation.internal.AnnotationDefault")
996+
@tu lazy val AssignedNonLocallyAnnot: ClassSymbol = requiredClass("scala.annotation.internal.AssignedNonLocally")
996997
@tu lazy val BeanPropertyAnnot: ClassSymbol = requiredClass("scala.beans.BeanProperty")
997998
@tu lazy val BooleanBeanPropertyAnnot: ClassSymbol = requiredClass("scala.beans.BooleanBeanProperty")
998999
@tu lazy val BodyAnnot: ClassSymbol = requiredClass("scala.annotation.internal.Body")

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -867,7 +867,7 @@ object SymDenotations {
867867
/** Is `pre` the same as C.this, where C is exactly the owner of this symbol,
868868
* or, if this symbol is protected, a subclass of the owner?
869869
*/
870-
def isCorrectThisType(pre: Type)(using Context): Boolean = pre match
870+
def isAccessPrivilegedThisType(pre: Type)(using Context): Boolean = pre match
871871
case pre: ThisType =>
872872
(pre.cls eq owner) || this.is(Protected) && pre.cls.derivesFrom(owner)
873873
case pre: TermRef =>
@@ -932,7 +932,7 @@ object SymDenotations {
932932
|| boundary.isRoot
933933
|| (accessWithin(boundary) || accessWithinLinked(boundary)) &&
934934
( !this.is(Local)
935-
|| isCorrectThisType(pre)
935+
|| isAccessPrivilegedThisType(pre)
936936
|| canBeLocal(name, flags)
937937
&& {
938938
resetFlag(Local)

compiler/src/dotty/tools/dotc/transform/PostTyper.scala

Lines changed: 1 addition & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -109,13 +109,6 @@ class PostTyper extends MacroTransform with InfoTransformer { thisPhase =>
109109
try op finally noCheckNews = saved
110110
}
111111

112-
/** The set of all private class variables that are assigned
113-
* when selected with a qualifier other than the `this` of the owning class.
114-
* Such variables can contain only invariant type parameters in
115-
* their types.
116-
*/
117-
private var privateVarsSetNonLocally: Set[Symbol] = Set()
118-
119112
def isCheckable(t: New): Boolean = !inJavaAnnot && !noCheckNews.contains(t)
120113

121114
/** Mark parameter accessors that are aliases of like-named parameters
@@ -156,8 +149,6 @@ class PostTyper extends MacroTransform with InfoTransformer { thisPhase =>
156149
private def processMemberDef(tree: Tree)(using Context): tree.type = {
157150
val sym = tree.symbol
158151
Checking.checkValidOperator(sym)
159-
if sym.isClass then
160-
VarianceChecker.check(tree, privateVarsSetNonLocally)
161152
sym.transformAnnotations(transformAnnot)
162153
sym.defTree = tree
163154
tree
@@ -266,14 +257,6 @@ class PostTyper extends MacroTransform with InfoTransformer { thisPhase =>
266257
}
267258
}
268259

269-
/** Update privateVarsSetNonLocally is symbol is a private variable
270-
* that is selected from something other than `this` when assigned
271-
*/
272-
private def markVarAccess(tree: Tree, qual: Tree)(using Context): Unit =
273-
val sym = tree.symbol
274-
if sym.is(Private, butNot = Local) && !sym.isCorrectThisType(qual.tpe) then
275-
privateVarsSetNonLocally += sym
276-
277260
def checkNoConstructorProxy(tree: Tree)(using Context): Unit =
278261
if tree.symbol.is(ConstructorProxy) then
279262
report.error(em"constructor proxy ${tree.symbol} cannot be used as a value", tree.srcPos)
@@ -412,6 +395,7 @@ class PostTyper extends MacroTransform with InfoTransformer { thisPhase =>
412395
registerIfHasMacroAnnotations(tree)
413396
val sym = tree.symbol
414397
if (sym.isClass)
398+
VarianceChecker.check(tree)
415399
annotateExperimental(sym)
416400
checkMacroAnnotation(sym)
417401
if sym.isOneOf(GivenOrImplicit) then
@@ -488,9 +472,6 @@ class PostTyper extends MacroTransform with InfoTransformer { thisPhase =>
488472
case tpe => tpe
489473
}
490474
)
491-
case Assign(lhs @ Select(qual, _), _) =>
492-
markVarAccess(lhs, qual)
493-
super.transform(tree)
494475
case Typed(Ident(nme.WILDCARD), _) =>
495476
withMode(Mode.Pattern)(super.transform(tree))
496477
// The added mode signals that bounds in a pattern need not

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

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1103,6 +1103,18 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
11031103
// allow assignments from the primary constructor to class fields
11041104
ctx.owner.name.is(TraitSetterName) || ctx.owner.isStaticConstructor
11051105

1106+
/** Mark private variables that are assigned with a prefix other than
1107+
* the `this` type of their owner with a `annotation.internal.AssignedNonLocally`
1108+
* annotation. The annotation influences the variance check for these
1109+
* variables, which is done at PostTyper. It will be removed after the
1110+
* variance check.
1111+
*/
1112+
def rememberNonLocalAssignToPrivate(sym: Symbol) = lhs1 match
1113+
case Select(qual, _)
1114+
if sym.is(Private, butNot = Local) && !sym.isAccessPrivilegedThisType(qual.tpe) =>
1115+
sym.addAnnotation(Annotation(defn.AssignedNonLocallyAnnot, lhs1.span))
1116+
case _ =>
1117+
11061118
lhsCore match
11071119
case Apply(fn, _) if fn.symbol.is(ExtensionMethod) =>
11081120
def toSetter(fn: Tree): untpd.Tree = fn match
@@ -1136,15 +1148,16 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
11361148
case _ => lhsCore.tpe match {
11371149
case ref: TermRef =>
11381150
val lhsVal = lhsCore.denot.suchThat(!_.is(Method))
1139-
if (canAssign(lhsVal.symbol)) {
1140-
// lhsBounds: (T .. Any) as seen from lhs prefix, where T is the type of lhsVal.symbol
1151+
val lhsSym = lhsVal.symbol
1152+
if canAssign(lhsSym) then
1153+
rememberNonLocalAssignToPrivate(lhsSym)
1154+
// lhsBounds: (T .. Any) as seen from lhs prefix, where T is the type of lhsSym
11411155
// This ensures we do the as-seen-from on T with variance -1. Test case neg/i2928.scala
11421156
val lhsBounds =
1143-
TypeBounds.lower(lhsVal.symbol.info).asSeenFrom(ref.prefix, lhsVal.symbol.owner)
1157+
TypeBounds.lower(lhsSym.info).asSeenFrom(ref.prefix, lhsSym.owner)
11441158
assignType(cpy.Assign(tree)(lhs1, typed(tree.rhs, lhsBounds.loBound)))
11451159
.computeAssignNullable()
1146-
}
1147-
else {
1160+
else
11481161
val pre = ref.prefix
11491162
val setterName = ref.name.setterName
11501163
val setter = pre.member(setterName)
@@ -1157,7 +1170,6 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
11571170
case _ =>
11581171
reassignmentToVal
11591172
}
1160-
}
11611173
case TryDynamicCallType =>
11621174
typedDynamicAssign(tree, pt)
11631175
case tpe =>

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

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ import printing.Formatting.hl
1919
*/
2020
object VarianceChecker {
2121
case class VarianceError(tvar: Symbol, required: Variance)
22-
def check(tree: tpd.Tree, privateVarsSetNonLocally: collection.Set[Symbol])(using Context): Unit =
23-
VarianceChecker(privateVarsSetNonLocally).Traverser.traverse(tree)
22+
def check(tree: tpd.Tree)(using Context): Unit =
23+
VarianceChecker().Traverser.traverse(tree)
2424

2525
/** Check that variances of type lambda correspond to their occurrences in its body.
2626
* Note: this is achieved by a mechanism separate from checking class type parameters.
@@ -62,7 +62,7 @@ object VarianceChecker {
6262
end checkLambda
6363
}
6464

65-
class VarianceChecker(privateVarsSetNonLocally: collection.Set[Symbol])(using Context) {
65+
class VarianceChecker(using Context) {
6666
import VarianceChecker._
6767
import tpd._
6868

@@ -154,8 +154,9 @@ class VarianceChecker(privateVarsSetNonLocally: collection.Set[Symbol])(using Co
154154
val savedVariance = variance
155155
def isLocal =
156156
base.isAllOf(PrivateLocal)
157-
|| base.is(Private) && !privateVarsSetNonLocally.contains(base)
157+
|| base.is(Private) && !base.hasAnnotation(defn.AssignedNonLocallyAnnot)
158158
if base.is(Mutable, butNot = Method) && !isLocal then
159+
base.removeAnnotation(defn.AssignedNonLocallyAnnot)
159160
variance = 0
160161
try checkInfo(base.info)
161162
finally
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package scala.annotation
2+
package internal
3+
4+
/** An annotation to indicate that a private `var` was assigned with a prefix
5+
* other than the `this` type of its owner.
6+
*/
7+
class AssignedNonLocally() extends Annotation

tests/neg/i18588.check

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,7 @@
22
7 | private var cached: A = value // error
33
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
44
| covariant type A occurs in invariant position in type A of variable cached
5+
-- Error: tests/neg/i18588.scala:17:14 ---------------------------------------------------------------------------------
6+
17 | private var cached: A = value // error
7+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
8+
| covariant type A occurs in invariant position in type A of variable cached

tests/neg/i18588.scala

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,30 @@ class Box[+A](value: A) {
1313
}
1414
}
1515

16+
class BoxWithCompanion[+A](value: A) {
17+
private var cached: A = value // error
18+
def get: A = cached
19+
}
20+
21+
class BoxValid[+A](value: A, orig: A) {
22+
private var cached: A = value // ok
23+
def get: A = cached
24+
25+
def reset(): Unit =
26+
cached = orig // ok: mutated through this prefix
27+
}
28+
1629
trait Animal
1730
object Dog extends Animal
1831
object Cat extends Animal
1932

2033
val dogBox: Box[Dog.type] = new Box(Dog)
2134
val _ = dogBox.put(Cat)
2235
val dog: Dog.type = dogBox.get
36+
37+
38+
object BoxWithCompanion {
39+
def put[A](box: BoxWithCompanion[A], value: A): Unit = {
40+
box.cached = value
41+
}
42+
}

0 commit comments

Comments
 (0)