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 , ScalaRelease }
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+ checkSinceAnnot(sym, pos)
30+
31+ val xMigrationValue = ctx.settings.Xmigration .value
32+ if xMigrationValue != NoScalaVersion then
33+ checkMigration(sym, pos, xMigrationValue)
34+
35+
36+ /** If @deprecated is present, and the point of reference is not enclosed
37+ * in either a deprecated member or a scala bridge method, issue a warning.
38+ */
39+ private def checkDeprecated (sym : Symbol , pos : SrcPos )(using Context ): Unit =
40+
41+ /** is the owner an enum or its companion and also the owner of sym */
42+ def isEnumOwner (owner : Symbol )(using Context ) =
43+ // pre: sym is an enumcase
44+ if owner.isEnumClass then owner.companionClass eq sym.owner
45+ else if owner.is(ModuleClass ) && owner.companionClass.isEnumClass then owner eq sym.owner
46+ else false
47+
48+ def isDeprecatedOrEnum (owner : Symbol )(using Context ) =
49+ // pre: sym is an enumcase
50+ owner.isDeprecated
51+ || isEnumOwner(owner)
52+
53+ /** Scan the chain of outer declaring scopes from the current context
54+ * a deprecation warning will be skipped if one the following holds
55+ * for a given declaring scope:
56+ * - the symbol associated with the scope is also deprecated.
57+ * - if and only if `sym` is an enum case, the scope is either
58+ * a module that declares `sym`, or the companion class of the
59+ * module that declares `sym`.
60+ */
61+ def skipWarning (using Context ) =
62+ ctx.owner.ownersIterator.exists(if sym.isEnumCase then isDeprecatedOrEnum else _.isDeprecated)
63+
64+ for annot <- sym.getAnnotation(defn.DeprecatedAnnot ) do
65+ if ! skipWarning then
66+ val msg = annot.argumentConstant(0 ).map(" : " + _.stringValue).getOrElse(" " )
67+ val since = annot.argumentConstant(1 ).map(" since " + _.stringValue).getOrElse(" " )
68+ report.deprecationWarning(s " ${sym.showLocated} is deprecated ${since}${msg}" , pos)
69+
70+ private def checkExperimental (sym : Symbol , pos : SrcPos )(using Context ): Unit =
71+ if sym.isExperimental && ! ctx.owner.isInExperimentalScope then
72+ Feature .checkExperimentalDef(sym, pos)
73+
74+ private def checkExperimentalSignature (sym : Symbol , pos : SrcPos )(using Context ): Unit =
75+ class Checker extends TypeTraverser :
76+ def traverse (tp : Type ): Unit =
77+ if tp.typeSymbol.isExperimental then
78+ Feature .checkExperimentalDef(tp.typeSymbol, pos)
79+ else
80+ traverseChildren(tp)
81+ if ! sym.isInExperimentalScope then
82+ new Checker ().traverse(sym.info)
83+
84+ private def checkExperimentalAnnots (sym : Symbol )(using Context ): Unit =
85+ if ! sym.isInExperimentalScope then
86+ for annot <- sym.annotations if annot.symbol.isExperimental do
87+ Feature .checkExperimentalDef(annot.symbol, annot.tree)
88+
89+ private def checkSinceAnnot (sym : Symbol , pos : SrcPos )(using Context ): Unit =
90+ for
91+ annot <- sym.getAnnotation(defn.SinceAnnot )
92+ releaseName <- annot.argumentConstantString(0 )
93+ do
94+ ScalaRelease .parse(releaseName) match
95+ case Some (release) if release > ctx.scalaRelease =>
96+ report.error(
97+ i " $sym was added in Scala release ${releaseName.show}, therefore it cannot be used in the code targeting Scala ${ctx.scalaRelease.show}" ,
98+ pos)
99+ case None =>
100+ report.error(i " $sym has an unparsable release name: ' ${releaseName}' " , annot.tree.srcPos)
101+ case _ =>
102+
103+ private def checkSinceAnnotInSignature (sym : Symbol , pos : SrcPos )(using Context ) =
104+ new TypeTraverser :
105+ def traverse (tp : Type ) =
106+ if tp.typeSymbol.hasAnnotation(defn.SinceAnnot ) then
107+ checkSinceAnnot(tp.typeSymbol, pos)
108+ else
109+ traverseChildren(tp)
110+ .traverse(sym.info)
111+
112+ /** If @migration is present (indicating that the symbol has changed semantics between versions),
113+ * emit a warning.
114+ */
115+ private def checkMigration (sym : Symbol , pos : SrcPos , xMigrationValue : ScalaVersion )(using Context ): Unit =
116+ for annot <- sym.getAnnotation(defn.MigrationAnnot ) do
117+ val migrationVersion = ScalaVersion .parse(annot.argumentConstant(1 ).get.stringValue)
118+ migrationVersion match
119+ case Success (symVersion) if xMigrationValue < symVersion =>
120+ val msg = annot.argumentConstant(0 ).get.stringValue
121+ report.warning(SymbolChangedSemanticsInVersion (sym, symVersion, msg), pos)
122+ case Failure (ex) =>
123+ report.warning(SymbolHasUnparsableVersionNumber (sym, ex.getMessage), pos)
124+ case _ =>
125+
126+ /** Check that a deprecated val or def does not override a
127+ * concrete, non-deprecated method. If it does, then
128+ * deprecation is meaningless.
129+ */
130+ private def checkDeprecatedOvers (tree : Tree )(using Context ): Unit = {
131+ val symbol = tree.symbol
132+ if (symbol.isDeprecated) {
133+ val concrOvers =
134+ symbol.allOverriddenSymbols.filter(sym =>
135+ ! sym.isDeprecated && ! sym.is(Deferred ))
136+ if (! concrOvers.isEmpty)
137+ report.deprecationWarning(
138+ symbol.toString + " overrides concrete, non-deprecated symbol(s):" +
139+ concrOvers.map(_.name).mkString(" " , " , " , " " ), tree.srcPos)
140+ }
141+ }
142+
143+ /** Check that classes extending experimental classes or nested in experimental classes have the @experimental annotation. */
144+ private def checkExperimentalInheritance (cls : ClassSymbol )(using Context ): Unit =
145+ if ! cls.isAnonymousClass && ! cls.hasAnnotation(defn.ExperimentalAnnot ) then
146+ cls.info.parents.find(_.typeSymbol.isExperimental) match
147+ case Some (parent) =>
148+ report.error(em " extension of experimental ${parent.typeSymbol} must have @experimental annotation " , cls.srcPos)
149+ case _ =>
150+ end checkExperimentalInheritance
151+
152+ override def transformValDef (tree : ValDef )(using Context ): ValDef =
153+ checkDeprecatedOvers(tree)
154+ checkExperimentalAnnots(tree.symbol)
155+ checkExperimentalSignature(tree.symbol, tree)
156+ checkSinceAnnot(tree.symbol, tree.srcPos)
157+ checkSinceAnnotInSignature(tree.symbol, tree)
158+ tree
159+
160+ override def transformDefDef (tree : DefDef )(using Context ): DefDef =
161+ checkDeprecatedOvers(tree)
162+ checkExperimentalAnnots(tree.symbol)
163+ checkExperimentalSignature(tree.symbol, tree)
164+ checkSinceAnnotInSignature(tree.symbol, tree)
165+ tree
166+
167+ override def transformTemplate (tree : Template )(using Context ): Tree =
168+ val cls = ctx.owner.asClass
169+ checkExperimentalInheritance(cls)
170+ checkExperimentalAnnots(cls)
171+ tree
172+
173+ override def transformIdent (tree : Ident )(using Context ): Ident = {
174+ checkUndesiredProperties(tree.symbol, tree.srcPos)
175+ tree
176+ }
177+
178+ override def transformSelect (tree : Select )(using Context ): Select = {
179+ checkUndesiredProperties(tree.symbol, tree.srcPos)
180+ tree
181+ }
182+
183+ override def transformNew (tree : New )(using Context ): New = {
184+ checkUndesiredProperties(tree.tpe.typeSymbol, tree.srcPos)
185+ tree
186+ }
187+
188+ override def transformTypeTree (tree : TypeTree )(using Context ): TypeTree = {
189+ val tpe = tree.tpe
190+ tpe.foreachPart {
191+ case TypeRef (_, sym : Symbol ) =>
192+ checkDeprecated(sym, tree.srcPos)
193+ checkExperimental(sym, tree.srcPos)
194+ checkSinceAnnot(sym, tree.srcPos)
195+ case TermRef (_, sym : Symbol ) =>
196+ checkDeprecated(sym, tree.srcPos)
197+ checkExperimental(sym, tree.srcPos)
198+ checkSinceAnnot(sym, tree.srcPos)
199+ case _ =>
200+ }
201+ tree
202+ }
203+
204+ override def transformTypeDef (tree : TypeDef )(using Context ): TypeDef = {
205+ checkExperimentalAnnots(tree.symbol)
206+ checkSinceAnnot(tree.symbol, tree.srcPos)
207+ tree
208+ }
209+
210+ end CrossVersionChecks
0 commit comments