Skip to content

Commit fada1ef

Browse files
committed
SI-6815 untangle isStable and hasVolatileType
`Symbol::isStable` is now independent of `Symbol::hasVolatileType`, so that we can allow stable identifiers that are volatile in ident patterns. This split is validated by SI-6815 and the old logic in RefChecks, which seems to assume this independence, and thus I don't think ever worked: ``` if (member.isStable && !otherTp.isVolatile) { if (memberTp.isVolatile) overrideError("has a volatile type; cannot override a member with non-volatile type") ``` Introduces `admitsTypeSelection` and `isStableIdentifierPattern` in treeInfo, and uses them instead of duplicating that logic all over the place. Since volatility only matters in the context of type application, `isStableIdentifierPattern` is used to check patterns (resulting in `==` checks) and imports.
1 parent 97d5179 commit fada1ef

File tree

17 files changed

+215
-54
lines changed

17 files changed

+215
-54
lines changed

src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ trait ContextErrors {
141141
}
142142
issueNormalTypeError(tree,
143143
"stable identifier required, but "+tree+" found." + (
144-
if (isStableExceptVolatile(tree)) addendum else ""))
144+
if (treeInfo.hasVolatileType(tree)) addendum else ""))
145145
setError(tree)
146146
}
147147

src/compiler/scala/tools/nsc/typechecker/Namers.scala

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1342,13 +1342,16 @@ trait Namers extends MethodSynthesis {
13421342
private def importSig(imp: Import) = {
13431343
val Import(expr, selectors) = imp
13441344
val expr1 = typer.typedQualifier(expr)
1345-
typer checkStable expr1
1345+
13461346
if (expr1.symbol != null && expr1.symbol.isRootPackage)
13471347
RootImportError(imp)
13481348

13491349
if (expr1.isErrorTyped)
13501350
ErrorType
13511351
else {
1352+
if (!treeInfo.isStableIdentifierPattern(expr1))
1353+
typer.TyperErrorGen.UnstableTreeError(expr1)
1354+
13521355
val newImport = treeCopy.Import(imp, expr1, selectors).asInstanceOf[Import]
13531356
checkSelectors(newImport)
13541357
transformed(imp) = newImport

src/compiler/scala/tools/nsc/typechecker/NamesDefaults.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ trait NamesDefaults { self: Analyzer =>
202202
if (module == NoSymbol) None
203203
else {
204204
val ref = atPos(pos.focus)(gen.mkAttributedRef(pre, module))
205-
if (module.isStable && pre.isStable) // fixes #4524. the type checker does the same for
205+
if (treeInfo.admitsTypeSelection(ref)) // fixes #4524. the type checker does the same for
206206
ref.setType(singleType(pre, module)) // typedSelect, it calls "stabilize" on the result.
207207
Some(ref)
208208
}

src/compiler/scala/tools/nsc/typechecker/RefChecks.scala

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -511,7 +511,10 @@ abstract class RefChecks extends InfoTransform with scala.reflect.internal.trans
511511
}
512512

513513
if (member.isStable && !otherTp.isVolatile) {
514-
if (memberTp.isVolatile)
514+
// (1.4), pt 2 -- member.isStable && memberTp.isVolatile started being possible after SI-6815
515+
// (before SI-6815, !symbol.tpe.isVolatile was implied by symbol.isStable)
516+
// TODO: allow overriding when @uncheckedStable?
517+
if (memberTp.isVolatile)
515518
overrideError("has a volatile type; cannot override a member with non-volatile type")
516519
else memberTp.dealiasWiden.resultType match {
517520
case rt: RefinedType if !(rt =:= otherTp) && !(checkedCombinations contains rt.parents) =>

src/compiler/scala/tools/nsc/typechecker/Typers.scala

Lines changed: 29 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -587,7 +587,7 @@ trait Typers extends Adaptations with Tags {
587587
}
588588

589589
/** Post-process an identifier or selection node, performing the following:
590-
* 1. Check that non-function pattern expressions are stable
590+
* 1. Check that non-function pattern expressions are stable (ignoring volatility concerns -- SI-6815)
591591
* 2. Check that packages and static modules are not used as values
592592
* 3. Turn tree type into stable type if possible and required by context.
593593
* 4. Give getClass calls a more precise type based on the type of the target of the call.
@@ -602,16 +602,18 @@ trait Typers extends Adaptations with Tags {
602602
if (tree.isErrorTyped) tree
603603
else if (mode.inPatternNotFunMode && tree.isTerm) { // (1)
604604
if (sym.isValue) {
605-
val tree1 = checkStable(tree)
606-
// A module reference in a pattern has type Foo.type, not "object Foo"
607-
if (sym.isModuleNotMethod) tree1 setType singleType(pre, sym)
608-
else tree1
605+
if (tree.isErrorTyped) tree
606+
else if (treeInfo.isStableIdentifierPattern(tree)) {
607+
// A module reference in a pattern has type Foo.type, not "object Foo"
608+
if (sym.isModuleNotMethod) tree setType singleType(pre, sym)
609+
else tree
610+
} else UnstableTreeError(tree)
609611
}
610612
else fail()
611613
} else if ((mode & (EXPRmode | QUALmode)) == EXPRmode && !sym.isValue && !phase.erasedTypes) { // (2)
612614
fail()
613615
} else {
614-
if (sym.isStable && pre.isStable && !isByNameParamType(tree.tpe) &&
616+
if (treeInfo.admitsTypeSelection(tree) &&
615617
(isStableContext(tree, mode, pt) || sym.isModuleNotMethod))
616618
tree.setType(singleType(pre, sym))
617619
// To fully benefit from special casing the return type of
@@ -4442,6 +4444,7 @@ trait Typers extends Adaptations with Tags {
44424444
}
44434445

44444446
def normalTypedApply(tree: Tree, fun: Tree, args: List[Tree]) = {
4447+
// TODO: replace `fun.symbol.isStable` by `treeInfo.isStableIdentifierPattern(fun)`
44454448
val stableApplication = (fun.symbol ne null) && fun.symbol.isMethod && fun.symbol.isStable
44464449
val funpt = if (isPatternMode) pt else WildcardType
44474450
val appStart = if (Statistics.canEnable) Statistics.startTimer(failedApplyNanos) else null
@@ -4744,31 +4747,35 @@ trait Typers extends Adaptations with Tags {
47444747
typedSelect(tree, qual1, nme.CONSTRUCTOR)
47454748
case _ =>
47464749
if (Statistics.canEnable) Statistics.incCounter(typedSelectCount)
4747-
var qual1 = checkDead(typedQualifier(qual, mode))
4748-
if (name.isTypeName) qual1 = checkStable(qual1)
4750+
val qualTyped = checkDead(typedQualifier(qual, mode))
4751+
val qualStableOrError =
4752+
if (qualTyped.isErrorTyped || !name.isTypeName || treeInfo.admitsTypeSelection(qualTyped))
4753+
qualTyped
4754+
else
4755+
UnstableTreeError(qualTyped)
47494756

47504757
val tree1 = // temporarily use `filter` and an alternative for `withFilter`
47514758
if (name == nme.withFilter)
4752-
silent(_ => typedSelect(tree, qual1, name)) orElse { _ =>
4753-
silent(_ => typed1(Select(qual1, nme.filter) setPos tree.pos, mode, pt)) match {
4759+
silent(_ => typedSelect(tree, qualStableOrError, name)) orElse { _ =>
4760+
silent(_ => typed1(Select(qualStableOrError, nme.filter) setPos tree.pos, mode, pt)) match {
47544761
case SilentResultValue(result2) =>
47554762
unit.deprecationWarning(
4756-
tree.pos, "`withFilter' method does not yet exist on " + qual1.tpe.widen +
4763+
tree.pos, "`withFilter' method does not yet exist on " + qualStableOrError.tpe.widen +
47574764
", using `filter' method instead")
47584765
result2
47594766
case SilentTypeError(err) =>
47604767
WithFilterError(tree, err)
47614768
}
47624769
}
47634770
else
4764-
typedSelect(tree, qual1, name)
4771+
typedSelect(tree, qualStableOrError, name)
47654772

47664773
if (tree.isInstanceOf[PostfixSelect])
47674774
checkFeature(tree.pos, PostfixOpsFeature, name.decode)
47684775
if (tree1.symbol != null && tree1.symbol.isOnlyRefinementMember)
47694776
checkFeature(tree1.pos, ReflectiveCallsFeature, tree1.symbol.toString)
47704777

4771-
if (qual1.hasSymbolWhich(_.isRootPackage)) treeCopy.Ident(tree1, name)
4778+
if (qualStableOrError.hasSymbolWhich(_.isRootPackage)) treeCopy.Ident(tree1, name)
47724779
else tree1
47734780
}
47744781
}
@@ -5161,12 +5168,16 @@ trait Typers extends Adaptations with Tags {
51615168
}
51625169

51635170
def typedSingletonTypeTree(tree: SingletonTypeTree) = {
5164-
val ref1 = checkStable(
5165-
context.withImplicitsDisabled(
5171+
val refTyped =
5172+
context.withImplicitsDisabled {
51665173
typed(tree.ref, EXPRmode | QUALmode | (mode & TYPEPATmode), AnyRefClass.tpe)
5167-
)
5168-
)
5169-
tree setType ref1.tpe.resultType
5174+
}
5175+
5176+
if (!refTyped.isErrorTyped)
5177+
tree setType refTyped.tpe.resultType
5178+
5179+
if (treeInfo.admitsTypeSelection(refTyped)) tree
5180+
else UnstableTreeError(refTyped)
51705181
}
51715182

51725183
def typedSelectFromTypeTree(tree: SelectFromTypeTree) = {

src/interactive/scala/tools/nsc/interactive/Global.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -925,14 +925,14 @@ class Global(settings: Settings, _reporter: Reporter, projectName: String = "")
925925
}
926926

927927
def stabilizedType(tree: Tree): Type = tree match {
928-
case Ident(_) if tree.symbol.isStable =>
928+
case Ident(_) if treeInfo.admitsTypeSelection(tree) =>
929929
singleType(NoPrefix, tree.symbol)
930-
case Select(qual, _) if qual.tpe != null && tree.symbol.isStable =>
930+
case Select(qual, _) if treeInfo.admitsTypeSelection(tree) =>
931931
singleType(qual.tpe, tree.symbol)
932932
case Import(expr, selectors) =>
933933
tree.symbol.info match {
934934
case analyzer.ImportType(expr) => expr match {
935-
case s@Select(qual, name) => singleType(qual.tpe, s.symbol)
935+
case s@Select(qual, name) if treeInfo.admitsTypeSelection(expr) => singleType(qual.tpe, s.symbol)
936936
case i : Ident => i.tpe
937937
case _ => tree.tpe
938938
}

src/interactive/scala/tools/nsc/interactive/Picklers.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ trait Picklers { self: Global =>
9696
if (!sym.isRoot) {
9797
ownerNames(sym.owner, buf)
9898
buf += (if (sym.isModuleClass) sym.sourceModule else sym).name
99-
if (!sym.isType && !sym.isStable) {
99+
if (!sym.isType && !sym.isStable) { // TODO: what's the reasoning behind this condition!?
100100
val sym1 = sym.owner.info.decl(sym.name)
101101
if (sym1.isOverloaded) {
102102
val index = sym1.alternatives.indexOf(sym)

src/reflect/scala/reflect/internal/Symbols.scala

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -790,8 +790,12 @@ trait Symbols extends api.Symbols { self: SymbolTable =>
790790
/** Is this symbol an accessor method for outer? */
791791
final def isOuterField = isArtifact && (unexpandedName == nme.OUTER_LOCAL)
792792

793-
/** Does this symbol denote a stable value? */
794-
def isStable = false
793+
/** Does this symbol denote a stable value, ignoring volatility?
794+
*
795+
* Stability and volatility are checked separately to allow volatile paths in patterns that amount to equality checks. SI-6815
796+
*/
797+
final def isStable = isTerm && !isMutable && !(hasFlag(BYNAMEPARAM)) && (!isMethod || hasStableFlag)
798+
final def hasVolatileType = tpe.isVolatile && !hasAnnotation(uncheckedStableClass)
795799

796800
/** Does this symbol denote the primary constructor of its enclosing class? */
797801
final def isPrimaryConstructor =
@@ -2589,13 +2593,7 @@ trait Symbols extends api.Symbols { self: SymbolTable =>
25892593
override def isMixinConstructor = name == nme.MIXIN_CONSTRUCTOR
25902594
override def isConstructor = nme.isConstructorName(name)
25912595

2592-
override def isPackageObject = isModule && (name == nme.PACKAGE)
2593-
override def isStable = !isUnstable
2594-
private def isUnstable = (
2595-
isMutable
2596-
|| (hasFlag(METHOD | BYNAMEPARAM) && !hasFlag(STABLE))
2597-
|| (tpe.isVolatile && !hasAnnotation(uncheckedStableClass))
2598-
)
2596+
override def isPackageObject = isModule && (name == nme.PACKAGE)
25992597

26002598
// The name in comments is what it is being disambiguated from.
26012599
// TODO - rescue CAPTURED from BYNAMEPARAM so we can see all the names.

src/reflect/scala/reflect/internal/TreeGen.scala

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -142,17 +142,15 @@ abstract class TreeGen extends macros.TreeBuilder {
142142
}
143143

144144
/** Computes stable type for a tree if possible */
145-
def stableTypeFor(tree: Tree): Option[Type] = tree match {
146-
case This(_) if tree.symbol != null && !tree.symbol.isError =>
147-
Some(ThisType(tree.symbol))
148-
case Ident(_) if tree.symbol.isStable =>
149-
Some(singleType(tree.symbol.owner.thisType, tree.symbol))
150-
case Select(qual, _) if ((tree.symbol ne null) && (qual.tpe ne null)) && // turned assert into guard for #4064
151-
tree.symbol.isStable && qual.tpe.isStable =>
152-
Some(singleType(qual.tpe, tree.symbol))
153-
case _ =>
154-
None
155-
}
145+
def stableTypeFor(tree: Tree): Option[Type] =
146+
if (treeInfo.admitsTypeSelection(tree))
147+
tree match {
148+
case This(_) => Some(ThisType(tree.symbol))
149+
case Ident(_) => Some(singleType(tree.symbol.owner.thisType, tree.symbol))
150+
case Select(qual, _) => Some(singleType(qual.tpe, tree.symbol))
151+
case _ => None
152+
}
153+
else None
156154

157155
/** Builds a reference with stable type to given symbol */
158156
def mkAttributedStableRef(pre: Type, sym: Symbol): Tree =

src/reflect/scala/reflect/internal/TreeInfo.scala

Lines changed: 78 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ abstract class TreeInfo {
1818
val global: SymbolTable
1919

2020
import global._
21-
import definitions.{ isTupleSymbol, isVarArgsList, isCastSymbol, ThrowableClass, TupleClass, MacroContextClass, MacroContextPrefixType }
21+
import definitions.{ isTupleSymbol, isVarArgsList, isCastSymbol, ThrowableClass, TupleClass, MacroContextClass, MacroContextPrefixType, uncheckedStableClass }
2222

2323
/* Does not seem to be used. Not sure what it does anyway.
2424
def isOwnerDefinition(tree: Tree): Boolean = tree match {
@@ -66,6 +66,80 @@ abstract class TreeInfo {
6666
false
6767
}
6868

69+
/** Is `tree` a path, defined as follows? (Spec: 3.1 Paths)
70+
*
71+
* - The empty path ε (which cannot be written explicitly in user programs).
72+
* - C.this, where C references a class.
73+
* - p.x where p is a path and x is a stable member of p.
74+
* - C.super.x or C.super[M].x where C references a class
75+
* and x references a stable member of the super class or designated parent class M of C.
76+
*
77+
* NOTE: Trees with errors are (mostly) excluded.
78+
*
79+
* Path ::= StableId | [id ‘.’] this
80+
*
81+
*/
82+
def isPath(tree: Tree, allowVolatile: Boolean): Boolean =
83+
tree match {
84+
// Super is not technically a path.
85+
// However, syntactically, it can only occur nested in a Select.
86+
// This gives a nicer definition of isStableIdentifier that's equivalent to the spec's.
87+
// must consider Literal(_) a path for typedSingletonTypeTree
88+
case EmptyTree | Literal(_) => true
89+
case This(_) | Super(_, _) => symOk(tree.symbol)
90+
case _ => isStableIdentifier(tree, allowVolatile)
91+
}
92+
93+
/** Is `tree` a stable identifier, a path which ends in an identifier?
94+
*
95+
* StableId ::= id
96+
* | Path ‘.’ id
97+
* | [id ’.’] ‘super’ [‘[’ id ‘]’] ‘.’ id
98+
*/
99+
def isStableIdentifier(tree: Tree, allowVolatile: Boolean): Boolean =
100+
tree match {
101+
case Ident(_) => symOk(tree.symbol) && tree.symbol.isStable && !tree.symbol.hasVolatileType // TODO SPEC: not required by spec
102+
case Select(qual, _) => isStableMemberOf(tree.symbol, qual, allowVolatile) && isPath(qual, allowVolatile)
103+
case Apply(Select(free @ Ident(_), nme.apply), _) if free.symbol.name endsWith nme.REIFY_FREE_VALUE_SUFFIX =>
104+
// see a detailed explanation of this trick in `GenSymbols.reifyFreeTerm`
105+
free.symbol.hasStableFlag && isPath(free, allowVolatile)
106+
case _ => false
107+
}
108+
109+
private def symOk(sym: Symbol) = sym != null && !sym.isError && sym != NoSymbol
110+
private def typeOk(tp: Type) = tp != null && ! tp.isError
111+
112+
/** Assuming `sym` is a member of `tree`, is it a "stable member"?
113+
*
114+
* Stable members are packages or members introduced
115+
* by object definitions or by value definitions of non-volatile types (§3.6).
116+
*/
117+
def isStableMemberOf(sym: Symbol, tree: Tree, allowVolatile: Boolean): Boolean = (
118+
symOk(sym) && (!sym.isTerm || (sym.isStable && (allowVolatile || !sym.hasVolatileType))) &&
119+
typeOk(tree.tpe) && (allowVolatile || !hasVolatileType(tree)) && !definitions.isByNameParamType(tree.tpe)
120+
)
121+
122+
/** Is `tree`'s type volatile? (Ignored if its symbol has the @uncheckedStable annotation.)
123+
*/
124+
def hasVolatileType(tree: Tree): Boolean =
125+
symOk(tree.symbol) && tree.tpe.isVolatile && !tree.symbol.hasAnnotation(uncheckedStableClass)
126+
127+
/** Is `tree` either a non-volatile type,
128+
* or a path that does not include any of:
129+
* - a reference to a mutable variable/field
130+
* - a reference to a by-name parameter
131+
* - a member selection on a volatile type (Spec: 3.6 Volatile Types)?
132+
*
133+
* Such a tree is a suitable target for type selection.
134+
*/
135+
def admitsTypeSelection(tree: Tree): Boolean = isPath(tree, allowVolatile = false)
136+
137+
/** Is `tree` admissible as a stable identifier pattern (8.1.5 Stable Identifier Patterns)?
138+
*
139+
* We disregard volatility, as it's irrelevant in patterns (SI-6815)
140+
*/
141+
def isStableIdentifierPattern(tree: Tree): Boolean = isStableIdentifier(tree, allowVolatile = true)
142+
69143
// TODO SI-5304 tighten this up so we don't elide side effect in module loads
70144
def isQualifierSafeToElide(tree: Tree): Boolean = isExprSafeToInline(tree)
71145

@@ -473,9 +547,9 @@ abstract class TreeInfo {
473547
/** Does this CaseDef catch Throwable? */
474548
def catchesThrowable(cdef: CaseDef) = (
475549
cdef.guard.isEmpty && (unbind(cdef.pat) match {
476-
case Ident(nme.WILDCARD) => true
477-
case i@Ident(name) => hasNoSymbol(i)
478-
case _ => false
550+
case Ident(nme.WILDCARD) => true
551+
case i@Ident(name) => hasNoSymbol(i)
552+
case _ => false
479553
})
480554
)
481555

0 commit comments

Comments
 (0)