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