Skip to content

Commit 9274cce

Browse files
committed
Reorganize RefChecks tasks
- Split out forward reference checks and cross version (i.e. experimental/deprecated) checks into their own miniphases. These have nothing to do with the core tasks of RefChecks. - Move RefChecks earlier in the pipeline, so that it is not affected by ElimByName's questionable type manipulations.
1 parent b340d82 commit 9274cce

File tree

4 files changed

+303
-242
lines changed

4 files changed

+303
-242
lines changed

compiler/src/dotty/tools/dotc/Compiler.scala

+7-5
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@ import typer.{TyperPhase, RefChecks}
77
import parsing.Parser
88
import Phases.Phase
99
import transform._
10-
import dotty.tools.backend.jvm.{CollectSuperCalls, GenBCode}
1110
import dotty.tools.backend
12-
import dotty.tools.dotc.transform.localopt.StringInterpolatorOpt
11+
import backend.jvm.{CollectSuperCalls, GenBCode}
12+
import localopt.StringInterpolatorOpt
1313

1414
/** The central class of the dotc compiler. The job of a compiler is to create
1515
* runs, which process given `phases` in a given `rootContext`.
@@ -68,15 +68,17 @@ class Compiler {
6868
new BetaReduce, // Reduce closure applications
6969
new InlineVals, // Check right hand-sides of an `inline val`s
7070
new ExpandSAMs, // Expand single abstract method closures to anonymous classes
71-
new ElimRepeated) :: // Rewrite vararg parameters and arguments
71+
new ElimRepeated, // Rewrite vararg parameters and arguments
72+
new RefChecks) :: // Various checks mostly related to abstract members and overriding
7273
List(new init.Checker) :: // Check initialization of objects
73-
List(new ProtectedAccessors, // Add accessors for protected members
74+
List(new CrossVersionChecks, // Check issues related to deprecated and experimental
75+
new ProtectedAccessors, // Add accessors for protected members
7476
new ExtensionMethods, // Expand methods of value classes with extension methods
7577
new UncacheGivenAliases, // Avoid caching RHS of simple parameterless given aliases
7678
new ElimByName, // Map by-name parameters to functions
7779
new HoistSuperArgs, // Hoist complex arguments of supercalls to enclosing scope
80+
new ForwardDepChecks, // Check that there are no forward references to local vals
7881
new SpecializeApplyMethods, // Adds specialized methods to FunctionN
79-
new RefChecks, // Various checks mostly related to abstract members and overriding
8082
new TryCatchPatterns, // Compile cases in try/catch
8183
new PatternMatcher) :: // Compile pattern matches
8284
List(new ElimOpaque, // Turn opaque into normal aliases
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
package dotty.tools
2+
package dotc
3+
package transform
4+
5+
import core.*
6+
import Symbols.*, Types.*, Contexts.*, Flags.*, Decorators.*, reporting.*
7+
import util.Spans.Span
8+
import util.Store
9+
import collection.immutable
10+
import ast.tpd
11+
import MegaPhase.MiniPhase
12+
13+
object ForwardDepChecks:
14+
15+
import tpd.*
16+
17+
val name: String = "forwardDepChecks"
18+
19+
type LevelAndIndex = immutable.Map[Symbol, (LevelInfo, Int)]
20+
21+
class OptLevelInfo {
22+
def levelAndIndex: LevelAndIndex = Map()
23+
def enterReference(sym: Symbol, span: Span): Unit = ()
24+
}
25+
26+
/** A class to help in forward reference checking */
27+
class LevelInfo(outerLevelAndIndex: LevelAndIndex, stats: List[Tree])(using Context)
28+
extends OptLevelInfo {
29+
override val levelAndIndex: LevelAndIndex =
30+
stats.foldLeft(outerLevelAndIndex, 0) {(mi, stat) =>
31+
val (m, idx) = mi
32+
val m1 = stat match {
33+
case stat: MemberDef => m.updated(stat.symbol, (this, idx))
34+
case _ => m
35+
}
36+
(m1, idx + 1)
37+
}._1
38+
var maxIndex: Int = Int.MinValue
39+
var refSpan: Span = _
40+
var refSym: Symbol = _
41+
42+
override def enterReference(sym: Symbol, span: Span): Unit =
43+
if (sym.exists && sym.owner.isTerm)
44+
levelAndIndex.get(sym) match {
45+
case Some((level, idx)) if (level.maxIndex < idx) =>
46+
level.maxIndex = idx
47+
level.refSpan = span
48+
level.refSym = sym
49+
case _ =>
50+
}
51+
}
52+
53+
val NoLevelInfo: OptLevelInfo = new OptLevelInfo()
54+
55+
class ForwardDepChecks extends MiniPhase:
56+
import ForwardDepChecks.*
57+
import tpd.*
58+
59+
override def phaseName: String = ForwardDepChecks.name
60+
61+
override def runsAfter: Set[String] = Set(ElimByName.name)
62+
63+
private var LevelInfo: Store.Location[OptLevelInfo] = _
64+
private def currentLevel(using Context): OptLevelInfo = ctx.store(LevelInfo)
65+
66+
override def initContext(ctx: FreshContext): Unit =
67+
LevelInfo = ctx.addLocation(NoLevelInfo)
68+
69+
override def prepareForStats(trees: List[Tree])(using Context): Context =
70+
if (ctx.owner.isTerm)
71+
ctx.fresh.updateStore(LevelInfo, new LevelInfo(currentLevel.levelAndIndex, trees))
72+
else ctx
73+
74+
override def transformValDef(tree: ValDef)(using Context): ValDef =
75+
val sym = tree.symbol
76+
if sym.exists && sym.owner.isTerm && !sym.is(Lazy) then
77+
currentLevel.levelAndIndex.get(sym) match
78+
case Some((level, symIdx)) if symIdx <= level.maxIndex =>
79+
report.error(ForwardReferenceExtendsOverDefinition(sym, level.refSym),
80+
ctx.source.atSpan(level.refSpan))
81+
case _ =>
82+
tree
83+
84+
override def transformIdent(tree: Ident)(using Context): Ident = {
85+
currentLevel.enterReference(tree.symbol, tree.span)
86+
tree
87+
}
88+
89+
override def transformApply(tree: Apply)(using Context): Apply = {
90+
if (isSelfConstrCall(tree)) {
91+
assert(currentLevel.isInstanceOf[LevelInfo], s"${ctx.owner}/" + i"$tree")
92+
val level = currentLevel.asInstanceOf[LevelInfo]
93+
if (level.maxIndex > 0) {
94+
// An implementation restriction to avoid VerifyErrors and lazyvals mishaps; see SI-4717
95+
report.debuglog("refsym = " + level.refSym)
96+
report.error("forward reference not allowed from self constructor invocation",
97+
ctx.source.atSpan(level.refSpan))
98+
}
99+
}
100+
tree
101+
}
102+
103+
override def transformNew(tree: New)(using Context): New = {
104+
currentLevel.enterReference(tree.tpe.typeSymbol, tree.span)
105+
tree.tpe.dealias.foreachPart {
106+
case TermRef(_, s: Symbol) => currentLevel.enterReference(s, tree.span)
107+
case _ =>
108+
}
109+
tree
110+
}
111+
end ForwardDepChecks
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
package dotty.tools
2+
package dotc
3+
package transform
4+
5+
import core.*
6+
import Symbols.*, Types.*, Contexts.*, Flags.*, SymUtils.*, Decorators.*, reporting.*
7+
import util.SrcPos
8+
import config.{ScalaVersion, NoScalaVersion, Feature}
9+
import MegaPhase.MiniPhase
10+
import scala.util.{Failure, Success}
11+
import ast.tpd
12+
13+
class CrossVersionChecks extends MiniPhase:
14+
import tpd.*
15+
16+
def phaseName = "crossVersionChecks"
17+
18+
override def runsAfterGroupsOf: Set[String] = Set(FirstTransform.name)
19+
// We assume all type trees except TypeTree have been eliminated
20+
21+
// Note: if a symbol has both @deprecated and @migration annotations and both
22+
// warnings are enabled, only the first one checked here will be emitted.
23+
// I assume that's a consequence of some code trying to avoid noise by suppressing
24+
// warnings after the first, but I think it'd be better if we didn't have to
25+
// arbitrarily choose one as more important than the other.
26+
private def checkUndesiredProperties(sym: Symbol, pos: SrcPos)(using Context): Unit =
27+
checkDeprecated(sym, pos)
28+
checkExperimental(sym, pos)
29+
30+
val xMigrationValue = ctx.settings.Xmigration.value
31+
if xMigrationValue != NoScalaVersion then
32+
checkMigration(sym, pos, xMigrationValue)
33+
end checkUndesiredProperties
34+
35+
/** If @deprecated is present, and the point of reference is not enclosed
36+
* in either a deprecated member or a scala bridge method, issue a warning.
37+
*/
38+
private def checkDeprecated(sym: Symbol, pos: SrcPos)(using Context): Unit =
39+
40+
/** is the owner an enum or its companion and also the owner of sym */
41+
def isEnumOwner(owner: Symbol)(using Context) =
42+
// pre: sym is an enumcase
43+
if owner.isEnumClass then owner.companionClass eq sym.owner
44+
else if owner.is(ModuleClass) && owner.companionClass.isEnumClass then owner eq sym.owner
45+
else false
46+
47+
def isDeprecatedOrEnum(owner: Symbol)(using Context) =
48+
// pre: sym is an enumcase
49+
owner.isDeprecated
50+
|| isEnumOwner(owner)
51+
52+
/**Scan the chain of outer declaring scopes from the current context
53+
* a deprecation warning will be skipped if one the following holds
54+
* for a given declaring scope:
55+
* - the symbol associated with the scope is also deprecated.
56+
* - if and only if `sym` is an enum case, the scope is either
57+
* a module that declares `sym`, or the companion class of the
58+
* module that declares `sym`.
59+
*/
60+
def skipWarning(using Context) =
61+
ctx.owner.ownersIterator.exists(if sym.isEnumCase then isDeprecatedOrEnum else _.isDeprecated)
62+
63+
for annot <- sym.getAnnotation(defn.DeprecatedAnnot) do
64+
if !skipWarning then
65+
val msg = annot.argumentConstant(0).map(": " + _.stringValue).getOrElse("")
66+
val since = annot.argumentConstant(1).map(" since " + _.stringValue).getOrElse("")
67+
report.deprecationWarning(s"${sym.showLocated} is deprecated${since}${msg}", pos)
68+
69+
private def checkExperimental(sym: Symbol, pos: SrcPos)(using Context): Unit =
70+
if sym.isExperimental && !ctx.owner.isInExperimentalScope then
71+
Feature.checkExperimentalDef(sym, pos)
72+
73+
private def checkExperimentalSignature(sym: Symbol, pos: SrcPos)(using Context): Unit =
74+
class Checker extends TypeTraverser:
75+
def traverse(tp: Type): Unit =
76+
if tp.typeSymbol.isExperimental then
77+
Feature.checkExperimentalDef(tp.typeSymbol, pos)
78+
else
79+
traverseChildren(tp)
80+
if !sym.isInExperimentalScope then
81+
new Checker().traverse(sym.info)
82+
83+
private def checkExperimentalAnnots(sym: Symbol)(using Context): Unit =
84+
if !sym.isInExperimentalScope then
85+
for annot <- sym.annotations if annot.symbol.isExperimental do
86+
Feature.checkExperimentalDef(annot.symbol, annot.tree)
87+
88+
/** If @migration is present (indicating that the symbol has changed semantics between versions),
89+
* emit a warning.
90+
*/
91+
private def checkMigration(sym: Symbol, pos: SrcPos, xMigrationValue: ScalaVersion)(using Context): Unit =
92+
for annot <- sym.getAnnotation(defn.MigrationAnnot) do
93+
val migrationVersion = ScalaVersion.parse(annot.argumentConstant(1).get.stringValue)
94+
migrationVersion match
95+
case Success(symVersion) if xMigrationValue < symVersion =>
96+
val msg = annot.argumentConstant(0).get.stringValue
97+
report.warning(SymbolChangedSemanticsInVersion(sym, symVersion, msg), pos)
98+
case Failure(ex) =>
99+
report.warning(SymbolHasUnparsableVersionNumber(sym, ex.getMessage), pos)
100+
case _ =>
101+
102+
/** Check that a deprecated val or def does not override a
103+
* concrete, non-deprecated method. If it does, then
104+
* deprecation is meaningless.
105+
*/
106+
private def checkDeprecatedOvers(tree: Tree)(using Context): Unit = {
107+
val symbol = tree.symbol
108+
if (symbol.isDeprecated) {
109+
val concrOvers =
110+
symbol.allOverriddenSymbols.filter(sym =>
111+
!sym.isDeprecated && !sym.is(Deferred))
112+
if (!concrOvers.isEmpty)
113+
report.deprecationWarning(
114+
symbol.toString + " overrides concrete, non-deprecated symbol(s):" +
115+
concrOvers.map(_.name).mkString(" ", ", ", ""), tree.srcPos)
116+
}
117+
}
118+
119+
/** Check that classes extending experimental classes or nested in experimental classes have the @experimental annotation. */
120+
private def checkExperimentalInheritance(cls: ClassSymbol)(using Context): Unit =
121+
if !cls.isAnonymousClass && !cls.hasAnnotation(defn.ExperimentalAnnot) then
122+
cls.info.parents.find(_.typeSymbol.isExperimental) match
123+
case Some(parent) =>
124+
report.error(em"extension of experimental ${parent.typeSymbol} must have @experimental annotation", cls.srcPos)
125+
case _ =>
126+
end checkExperimentalInheritance
127+
128+
override def transformValDef(tree: ValDef)(using Context): ValDef =
129+
checkExperimentalAnnots(tree.symbol)
130+
checkExperimentalSignature(tree.symbol, tree)
131+
checkDeprecatedOvers(tree)
132+
tree
133+
134+
override def transformDefDef(tree: DefDef)(using Context): DefDef =
135+
checkExperimentalAnnots(tree.symbol)
136+
checkExperimentalSignature(tree.symbol, tree)
137+
checkDeprecatedOvers(tree)
138+
tree
139+
140+
override def transformTemplate(tree: Template)(using Context): Tree =
141+
val cls = ctx.owner.asClass
142+
checkExperimentalInheritance(cls)
143+
checkExperimentalAnnots(cls)
144+
tree
145+
146+
override def transformIdent(tree: Ident)(using Context): Ident =
147+
checkUndesiredProperties(tree.symbol, tree.srcPos)
148+
tree
149+
150+
override def transformSelect(tree: Select)(using Context): Select =
151+
checkUndesiredProperties(tree.symbol, tree.srcPos)
152+
tree
153+
154+
override def transformNew(tree: New)(using Context): New =
155+
checkUndesiredProperties(tree.tpe.typeSymbol, tree.srcPos)
156+
tree
157+
158+
override def transformTypeTree(tree: TypeTree)(using Context): TypeTree =
159+
val tpe = tree.tpe
160+
tpe.foreachPart {
161+
case TypeRef(_, sym: Symbol) =>
162+
checkDeprecated(sym, tree.srcPos)
163+
checkExperimental(sym, tree.srcPos)
164+
case TermRef(_, sym: Symbol) =>
165+
checkDeprecated(sym, tree.srcPos)
166+
checkExperimental(sym, tree.srcPos)
167+
case _ =>
168+
}
169+
tree
170+
171+
override def transformTypeDef(tree: TypeDef)(using Context): TypeDef =
172+
checkExperimentalAnnots(tree.symbol)
173+
tree
174+
175+
end CrossVersionChecks

0 commit comments

Comments
 (0)