-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- 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.
- Loading branch information
Showing
4 changed files
with
303 additions
and
242 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
111 changes: 111 additions & 0 deletions
111
compiler/src/dotty/tools/dotc/transform/ForwardDepChecks.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
package dotty.tools | ||
package dotc | ||
package transform | ||
|
||
import core.* | ||
import Symbols.*, Types.*, Contexts.*, Flags.*, Decorators.*, reporting.* | ||
import util.Spans.Span | ||
import util.Store | ||
import collection.immutable | ||
import ast.tpd | ||
import MegaPhase.MiniPhase | ||
|
||
object ForwardDepChecks: | ||
|
||
import tpd.* | ||
|
||
val name: String = "forwardDepChecks" | ||
|
||
type LevelAndIndex = immutable.Map[Symbol, (LevelInfo, Int)] | ||
|
||
class OptLevelInfo { | ||
def levelAndIndex: LevelAndIndex = Map() | ||
def enterReference(sym: Symbol, span: Span): Unit = () | ||
} | ||
|
||
/** A class to help in forward reference checking */ | ||
class LevelInfo(outerLevelAndIndex: LevelAndIndex, stats: List[Tree])(using Context) | ||
extends OptLevelInfo { | ||
override val levelAndIndex: LevelAndIndex = | ||
stats.foldLeft(outerLevelAndIndex, 0) {(mi, stat) => | ||
val (m, idx) = mi | ||
val m1 = stat match { | ||
case stat: MemberDef => m.updated(stat.symbol, (this, idx)) | ||
case _ => m | ||
} | ||
(m1, idx + 1) | ||
}._1 | ||
var maxIndex: Int = Int.MinValue | ||
var refSpan: Span = _ | ||
var refSym: Symbol = _ | ||
|
||
override def enterReference(sym: Symbol, span: Span): Unit = | ||
if (sym.exists && sym.owner.isTerm) | ||
levelAndIndex.get(sym) match { | ||
case Some((level, idx)) if (level.maxIndex < idx) => | ||
level.maxIndex = idx | ||
level.refSpan = span | ||
level.refSym = sym | ||
case _ => | ||
} | ||
} | ||
|
||
val NoLevelInfo: OptLevelInfo = new OptLevelInfo() | ||
|
||
class ForwardDepChecks extends MiniPhase: | ||
import ForwardDepChecks.* | ||
import tpd.* | ||
|
||
override def phaseName: String = ForwardDepChecks.name | ||
|
||
override def runsAfter: Set[String] = Set(ElimByName.name) | ||
|
||
private var LevelInfo: Store.Location[OptLevelInfo] = _ | ||
private def currentLevel(using Context): OptLevelInfo = ctx.store(LevelInfo) | ||
|
||
override def initContext(ctx: FreshContext): Unit = | ||
LevelInfo = ctx.addLocation(NoLevelInfo) | ||
|
||
override def prepareForStats(trees: List[Tree])(using Context): Context = | ||
if (ctx.owner.isTerm) | ||
ctx.fresh.updateStore(LevelInfo, new LevelInfo(currentLevel.levelAndIndex, trees)) | ||
else ctx | ||
|
||
override def transformValDef(tree: ValDef)(using Context): ValDef = | ||
val sym = tree.symbol | ||
if sym.exists && sym.owner.isTerm && !sym.is(Lazy) then | ||
currentLevel.levelAndIndex.get(sym) match | ||
case Some((level, symIdx)) if symIdx <= level.maxIndex => | ||
report.error(ForwardReferenceExtendsOverDefinition(sym, level.refSym), | ||
ctx.source.atSpan(level.refSpan)) | ||
case _ => | ||
tree | ||
|
||
override def transformIdent(tree: Ident)(using Context): Ident = { | ||
currentLevel.enterReference(tree.symbol, tree.span) | ||
tree | ||
} | ||
|
||
override def transformApply(tree: Apply)(using Context): Apply = { | ||
if (isSelfConstrCall(tree)) { | ||
assert(currentLevel.isInstanceOf[LevelInfo], s"${ctx.owner}/" + i"$tree") | ||
val level = currentLevel.asInstanceOf[LevelInfo] | ||
if (level.maxIndex > 0) { | ||
// An implementation restriction to avoid VerifyErrors and lazyvals mishaps; see SI-4717 | ||
report.debuglog("refsym = " + level.refSym) | ||
report.error("forward reference not allowed from self constructor invocation", | ||
ctx.source.atSpan(level.refSpan)) | ||
} | ||
} | ||
tree | ||
} | ||
|
||
override def transformNew(tree: New)(using Context): New = { | ||
currentLevel.enterReference(tree.tpe.typeSymbol, tree.span) | ||
tree.tpe.dealias.foreachPart { | ||
case TermRef(_, s: Symbol) => currentLevel.enterReference(s, tree.span) | ||
case _ => | ||
} | ||
tree | ||
} | ||
end ForwardDepChecks |
175 changes: 175 additions & 0 deletions
175
compiler/src/dotty/tools/dotc/typer/CrossVersionChecks.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,175 @@ | ||
package dotty.tools | ||
package dotc | ||
package transform | ||
|
||
import core.* | ||
import Symbols.*, Types.*, Contexts.*, Flags.*, SymUtils.*, Decorators.*, reporting.* | ||
import util.SrcPos | ||
import config.{ScalaVersion, NoScalaVersion, Feature} | ||
import MegaPhase.MiniPhase | ||
import scala.util.{Failure, Success} | ||
import ast.tpd | ||
|
||
class CrossVersionChecks extends MiniPhase: | ||
import tpd.* | ||
|
||
def phaseName = "crossVersionChecks" | ||
|
||
override def runsAfterGroupsOf: Set[String] = Set(FirstTransform.name) | ||
// We assume all type trees except TypeTree have been eliminated | ||
|
||
// Note: if a symbol has both @deprecated and @migration annotations and both | ||
// warnings are enabled, only the first one checked here will be emitted. | ||
// I assume that's a consequence of some code trying to avoid noise by suppressing | ||
// warnings after the first, but I think it'd be better if we didn't have to | ||
// arbitrarily choose one as more important than the other. | ||
private def checkUndesiredProperties(sym: Symbol, pos: SrcPos)(using Context): Unit = | ||
checkDeprecated(sym, pos) | ||
checkExperimental(sym, pos) | ||
|
||
val xMigrationValue = ctx.settings.Xmigration.value | ||
if xMigrationValue != NoScalaVersion then | ||
checkMigration(sym, pos, xMigrationValue) | ||
end checkUndesiredProperties | ||
|
||
/** If @deprecated is present, and the point of reference is not enclosed | ||
* in either a deprecated member or a scala bridge method, issue a warning. | ||
*/ | ||
private def checkDeprecated(sym: Symbol, pos: SrcPos)(using Context): Unit = | ||
|
||
/** is the owner an enum or its companion and also the owner of sym */ | ||
def isEnumOwner(owner: Symbol)(using Context) = | ||
// pre: sym is an enumcase | ||
if owner.isEnumClass then owner.companionClass eq sym.owner | ||
else if owner.is(ModuleClass) && owner.companionClass.isEnumClass then owner eq sym.owner | ||
else false | ||
|
||
def isDeprecatedOrEnum(owner: Symbol)(using Context) = | ||
// pre: sym is an enumcase | ||
owner.isDeprecated | ||
|| isEnumOwner(owner) | ||
|
||
/**Scan the chain of outer declaring scopes from the current context | ||
* a deprecation warning will be skipped if one the following holds | ||
* for a given declaring scope: | ||
* - the symbol associated with the scope is also deprecated. | ||
* - if and only if `sym` is an enum case, the scope is either | ||
* a module that declares `sym`, or the companion class of the | ||
* module that declares `sym`. | ||
*/ | ||
def skipWarning(using Context) = | ||
ctx.owner.ownersIterator.exists(if sym.isEnumCase then isDeprecatedOrEnum else _.isDeprecated) | ||
|
||
for annot <- sym.getAnnotation(defn.DeprecatedAnnot) do | ||
if !skipWarning then | ||
val msg = annot.argumentConstant(0).map(": " + _.stringValue).getOrElse("") | ||
val since = annot.argumentConstant(1).map(" since " + _.stringValue).getOrElse("") | ||
report.deprecationWarning(s"${sym.showLocated} is deprecated${since}${msg}", pos) | ||
|
||
private def checkExperimental(sym: Symbol, pos: SrcPos)(using Context): Unit = | ||
if sym.isExperimental && !ctx.owner.isInExperimentalScope then | ||
Feature.checkExperimentalDef(sym, pos) | ||
|
||
private def checkExperimentalSignature(sym: Symbol, pos: SrcPos)(using Context): Unit = | ||
class Checker extends TypeTraverser: | ||
def traverse(tp: Type): Unit = | ||
if tp.typeSymbol.isExperimental then | ||
Feature.checkExperimentalDef(tp.typeSymbol, pos) | ||
else | ||
traverseChildren(tp) | ||
if !sym.isInExperimentalScope then | ||
new Checker().traverse(sym.info) | ||
|
||
private def checkExperimentalAnnots(sym: Symbol)(using Context): Unit = | ||
if !sym.isInExperimentalScope then | ||
for annot <- sym.annotations if annot.symbol.isExperimental do | ||
Feature.checkExperimentalDef(annot.symbol, annot.tree) | ||
|
||
/** If @migration is present (indicating that the symbol has changed semantics between versions), | ||
* emit a warning. | ||
*/ | ||
private def checkMigration(sym: Symbol, pos: SrcPos, xMigrationValue: ScalaVersion)(using Context): Unit = | ||
for annot <- sym.getAnnotation(defn.MigrationAnnot) do | ||
val migrationVersion = ScalaVersion.parse(annot.argumentConstant(1).get.stringValue) | ||
migrationVersion match | ||
case Success(symVersion) if xMigrationValue < symVersion => | ||
val msg = annot.argumentConstant(0).get.stringValue | ||
report.warning(SymbolChangedSemanticsInVersion(sym, symVersion, msg), pos) | ||
case Failure(ex) => | ||
report.warning(SymbolHasUnparsableVersionNumber(sym, ex.getMessage), pos) | ||
case _ => | ||
|
||
/** Check that a deprecated val or def does not override a | ||
* concrete, non-deprecated method. If it does, then | ||
* deprecation is meaningless. | ||
*/ | ||
private def checkDeprecatedOvers(tree: Tree)(using Context): Unit = { | ||
val symbol = tree.symbol | ||
if (symbol.isDeprecated) { | ||
val concrOvers = | ||
symbol.allOverriddenSymbols.filter(sym => | ||
!sym.isDeprecated && !sym.is(Deferred)) | ||
if (!concrOvers.isEmpty) | ||
report.deprecationWarning( | ||
symbol.toString + " overrides concrete, non-deprecated symbol(s):" + | ||
concrOvers.map(_.name).mkString(" ", ", ", ""), tree.srcPos) | ||
} | ||
} | ||
|
||
/** Check that classes extending experimental classes or nested in experimental classes have the @experimental annotation. */ | ||
private def checkExperimentalInheritance(cls: ClassSymbol)(using Context): Unit = | ||
if !cls.isAnonymousClass && !cls.hasAnnotation(defn.ExperimentalAnnot) then | ||
cls.info.parents.find(_.typeSymbol.isExperimental) match | ||
case Some(parent) => | ||
report.error(em"extension of experimental ${parent.typeSymbol} must have @experimental annotation", cls.srcPos) | ||
case _ => | ||
end checkExperimentalInheritance | ||
|
||
override def transformValDef(tree: ValDef)(using Context): ValDef = | ||
checkExperimentalAnnots(tree.symbol) | ||
checkExperimentalSignature(tree.symbol, tree) | ||
checkDeprecatedOvers(tree) | ||
tree | ||
|
||
override def transformDefDef(tree: DefDef)(using Context): DefDef = | ||
checkExperimentalAnnots(tree.symbol) | ||
checkExperimentalSignature(tree.symbol, tree) | ||
checkDeprecatedOvers(tree) | ||
tree | ||
|
||
override def transformTemplate(tree: Template)(using Context): Tree = | ||
val cls = ctx.owner.asClass | ||
checkExperimentalInheritance(cls) | ||
checkExperimentalAnnots(cls) | ||
tree | ||
|
||
override def transformIdent(tree: Ident)(using Context): Ident = | ||
checkUndesiredProperties(tree.symbol, tree.srcPos) | ||
tree | ||
|
||
override def transformSelect(tree: Select)(using Context): Select = | ||
checkUndesiredProperties(tree.symbol, tree.srcPos) | ||
tree | ||
|
||
override def transformNew(tree: New)(using Context): New = | ||
checkUndesiredProperties(tree.tpe.typeSymbol, tree.srcPos) | ||
tree | ||
|
||
override def transformTypeTree(tree: TypeTree)(using Context): TypeTree = | ||
val tpe = tree.tpe | ||
tpe.foreachPart { | ||
case TypeRef(_, sym: Symbol) => | ||
checkDeprecated(sym, tree.srcPos) | ||
checkExperimental(sym, tree.srcPos) | ||
case TermRef(_, sym: Symbol) => | ||
checkDeprecated(sym, tree.srcPos) | ||
checkExperimental(sym, tree.srcPos) | ||
case _ => | ||
} | ||
tree | ||
|
||
override def transformTypeDef(tree: TypeDef)(using Context): TypeDef = | ||
checkExperimentalAnnots(tree.symbol) | ||
tree | ||
|
||
end CrossVersionChecks |
Oops, something went wrong.