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