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