Skip to content

Commit 853b0bf

Browse files
Merge pull request #9350 from TheElectronWill/bytecode-deprecated
Emit "deprecated" bytecode attribute
2 parents 7ed93ad + 3f6858b commit 853b0bf

File tree

8 files changed

+91
-73
lines changed

8 files changed

+91
-73
lines changed

compiler/src/dotty/tools/backend/jvm/BTypesFromSymbols.scala

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -298,10 +298,9 @@ class BTypesFromSymbols[I <: DottyBackendInterface](val int: I) extends BTypes {
298298
*/
299299
final def javaFlags(sym: Symbol): Int = {
300300

301-
302301
val privateFlag = sym.is(Private) || (sym.isPrimaryConstructor && sym.owner.isTopLevelModuleClass)
303302

304-
val finalFlag = sym.is(Final) && !toDenot(sym).isClassConstructor && !(sym.is(Mutable)) && !(sym.enclosingClass.is(Trait))
303+
val finalFlag = sym.is(Final) && !toDenot(sym).isClassConstructor && !sym.is(Mutable) && !sym.enclosingClass.is(Trait)
305304

306305
import asm.Opcodes._
307306
GenBCodeOps.mkFlags(
@@ -318,23 +317,25 @@ class BTypesFromSymbols[I <: DottyBackendInterface](val int: I) extends BTypes {
318317
// Mixin forwarders are bridges and can be final, but final bridges confuse some frameworks
319318
!sym.is(Bridge))
320319
ACC_FINAL else 0,
320+
321321
if (sym.isStaticMember) ACC_STATIC else 0,
322322
if (sym.is(Bridge)) ACC_BRIDGE | ACC_SYNTHETIC else 0,
323323
if (sym.is(Artifact)) ACC_SYNTHETIC else 0,
324324
if (sym.isClass && !sym.isInterface) ACC_SUPER else 0,
325325
if (sym.isAllOf(JavaEnumTrait)) ACC_ENUM else 0,
326326
if (sym.is(JavaVarargs)) ACC_VARARGS else 0,
327327
if (sym.is(Synchronized)) ACC_SYNCHRONIZED else 0,
328-
if (false /*sym.isDeprecated*/) asm.Opcodes.ACC_DEPRECATED else 0, // TODO: add an isDeprecated method in SymUtils
329-
if (sym.is(Enum)) asm.Opcodes.ACC_ENUM else 0
328+
if (sym.isDeprecated) ACC_DEPRECATED else 0,
329+
if (sym.is(Enum)) ACC_ENUM else 0
330330
)
331331
}
332332

333333
def javaFieldFlags(sym: Symbol) = {
334+
import asm.Opcodes._
334335
javaFlags(sym) | GenBCodeOps.mkFlags(
335-
if (sym hasAnnotation TransientAttr) asm.Opcodes.ACC_TRANSIENT else 0,
336-
if (sym hasAnnotation VolatileAttr) asm.Opcodes.ACC_VOLATILE else 0,
337-
if (sym.is(Mutable)) 0 else asm.Opcodes.ACC_FINAL
336+
if (sym.hasAnnotation(TransientAttr)) ACC_TRANSIENT else 0,
337+
if (sym.hasAnnotation(VolatileAttr)) ACC_VOLATILE else 0,
338+
if (sym.is(Mutable)) 0 else ACC_FINAL
338339
)
339340
}
340341
}

compiler/src/dotty/tools/dotc/core/Annotations.scala

Lines changed: 0 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -206,36 +206,6 @@ object Annotations {
206206
Annotation(defn.ThrowsAnnot.typeRef.appliedTo(tref), Ident(tref))
207207
}
208208

209-
/** A decorator that provides queries for specific annotations
210-
* of a symbol.
211-
*/
212-
implicit class AnnotInfo(val sym: Symbol) extends AnyVal {
213-
214-
def isDeprecated(using Context): Boolean =
215-
sym.hasAnnotation(defn.DeprecatedAnnot)
216-
217-
def deprecationMessage(using Context): Option[String] =
218-
for {
219-
annot <- sym.getAnnotation(defn.DeprecatedAnnot)
220-
arg <- annot.argumentConstant(0)
221-
}
222-
yield arg.stringValue
223-
224-
def migrationVersion(using Context): Option[Try[ScalaVersion]] =
225-
for {
226-
annot <- sym.getAnnotation(defn.MigrationAnnot)
227-
arg <- annot.argumentConstant(1)
228-
}
229-
yield ScalaVersion.parse(arg.stringValue)
230-
231-
def migrationMessage(using Context): Option[Try[ScalaVersion]] =
232-
for {
233-
annot <- sym.getAnnotation(defn.MigrationAnnot)
234-
arg <- annot.argumentConstant(0)
235-
}
236-
yield ScalaVersion.parse(arg.stringValue)
237-
}
238-
239209
/** Extracts the type of the thrown exception from an annotation.
240210
*
241211
* Supports both "old-style" `@throws(classOf[Exception])`

compiler/src/dotty/tools/dotc/reporting/messages.scala

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1925,27 +1925,25 @@ object messages {
19251925
def explain = "A sealed class or trait can only be extended in the same file as its declaration"
19261926
}
19271927

1928-
class SymbolHasUnparsableVersionNumber(symbol: Symbol, migrationMessage: => String)(using Context)
1928+
class SymbolHasUnparsableVersionNumber(symbol: Symbol, errorMessage: String)(using Context)
19291929
extends SyntaxMsg(SymbolHasUnparsableVersionNumberID) {
1930-
def msg = em"${symbol.showLocated} has an unparsable version number: $migrationMessage"
1930+
def msg = em"${symbol.showLocated} has an unparsable version number: $errorMessage"
19311931
def explain =
1932-
em"""$migrationMessage
1933-
|
1934-
|The ${symbol.showLocated} is marked with ${hl("@migration")} indicating it has changed semantics
1932+
em"""The ${symbol.showLocated} is marked with ${hl("@migration")} indicating it has changed semantics
19351933
|between versions and the ${hl("-Xmigration")} settings is used to warn about constructs
19361934
|whose behavior may have changed since version change."""
19371935
}
19381936

19391937
class SymbolChangedSemanticsInVersion(
19401938
symbol: Symbol,
1941-
migrationVersion: ScalaVersion
1939+
migrationVersion: ScalaVersion,
1940+
migrationMessage: String
19421941
)(using Context) extends SyntaxMsg(SymbolChangedSemanticsInVersionID) {
1943-
def msg = em"${symbol.showLocated} has changed semantics in version $migrationVersion"
1944-
def explain = {
1942+
def msg = em"${symbol.showLocated} has changed semantics in version $migrationVersion: $migrationMessage"
1943+
def explain =
19451944
em"""The ${symbol.showLocated} is marked with ${hl("@migration")} indicating it has changed semantics
19461945
|between versions and the ${hl("-Xmigration")} settings is used to warn about constructs
19471946
|whose behavior may have changed since version change."""
1948-
}
19491947
}
19501948

19511949
class UnableToEmitSwitch(tooFewCases: Boolean)(using Context)

compiler/src/dotty/tools/dotc/transform/Memoize.scala

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -96,10 +96,13 @@ class Memoize extends MiniPhase with IdentityDenotTransformer { thisPhase =>
9696
case _ => ()
9797
}
9898

99-
def removeAnnotations(denot: SymDenotation): Unit =
99+
def removeUnwantedAnnotations(denot: SymDenotation): Unit =
100100
if (sym.annotations.nonEmpty) {
101101
val cpy = sym.copySymDenotation()
102-
cpy.annotations = Nil
102+
// Keep @deprecated annotation so that accessors can
103+
// be marked as deprecated in the bytecode.
104+
// TODO check the meta-annotations to know what to keep
105+
cpy.filterAnnotations(_.matches(defn.DeprecatedAnnot))
103106
cpy.installAfter(thisPhase)
104107
}
105108

@@ -135,7 +138,7 @@ class Memoize extends MiniPhase with IdentityDenotTransformer { thisPhase =>
135138
else transformFollowingDeep(ref(field))(using ctx.withOwner(sym))
136139
val getterDef = cpy.DefDef(tree)(rhs = getterRhs)
137140
addAnnotations(fieldDef.denot)
138-
removeAnnotations(sym)
141+
removeUnwantedAnnotations(sym)
139142
Thicket(fieldDef, getterDef)
140143
}
141144
else if (sym.isSetter) {
@@ -145,7 +148,7 @@ class Memoize extends MiniPhase with IdentityDenotTransformer { thisPhase =>
145148
if (isErasableBottomField(tree.vparamss.head.head.tpt.tpe.classSymbol)) Literal(Constant(()))
146149
else Assign(ref(field), adaptToField(ref(tree.vparamss.head.head.symbol)))
147150
val setterDef = cpy.DefDef(tree)(rhs = transformFollowingDeep(initializer)(using ctx.withOwner(sym)))
148-
removeAnnotations(sym)
151+
removeUnwantedAnnotations(sym)
149152
setterDef
150153
}
151154
else tree // curiously, some accessors from Scala2 have ' ' suffixes. They count as

compiler/src/dotty/tools/dotc/transform/SymUtils.scala

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,10 @@ class SymUtils(val self: Symbol) extends AnyVal {
223223
self.is(ModuleClass) && self.sourceModule.is(Extension) && !self.sourceModule.isExtensionMethod
224224

225225
def isScalaStatic(using Context): Boolean =
226-
self.hasAnnotation(ctx.definitions.ScalaStaticAnnot)
226+
self.hasAnnotation(defn.ScalaStaticAnnot)
227+
228+
def isDeprecated(using Context): Boolean =
229+
self.hasAnnotation(defn.DeprecatedAnnot)
227230

228231
/** Is symbol assumed or declared as an infix symbol? */
229232
def isDeclaredInfix(using Context): Boolean =

compiler/src/dotty/tools/dotc/typer/RefChecks.scala

Lines changed: 33 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,16 @@ import util.Spans._
1111
import util.{Store, SourcePosition}
1212
import scala.collection.{ mutable, immutable }
1313
import ast._
14-
import Trees._
1514
import MegaPhase._
1615
import config.Printers.{checks, noPrinter}
17-
import scala.util.Failure
18-
import config.NoScalaVersion
16+
import scala.util.{Try, Failure, Success}
17+
import config.{ScalaVersion, NoScalaVersion}
1918
import Decorators._
2019
import typer.ErrorReporting._
2120
import config.Feature.warnOnMigration
2221

2322
object RefChecks {
24-
import tpd._
23+
import tpd.{Tree, MemberDef}
2524
import reporting.messages._
2625

2726
val name: String = "refchecks"
@@ -817,24 +816,39 @@ object RefChecks {
817816
// I assume that's a consequence of some code trying to avoid noise by suppressing
818817
// warnings after the first, but I think it'd be better if we didn't have to
819818
// arbitrarily choose one as more important than the other.
820-
private def checkUndesiredProperties(sym: Symbol, pos: SourcePosition)(using Context): Unit = {
821-
// If symbol is deprecated, and the point of reference is not enclosed
822-
// in either a deprecated member or a scala bridge method, issue a warning.
823-
if (sym.isDeprecated && !ctx.owner.ownersIterator.exists(_.isDeprecated))
824-
ctx.deprecationWarning("%s is deprecated%s".format(
825-
sym.showLocated, sym.deprecationMessage map (": " + _) getOrElse ""), pos)
826-
// Similar to deprecation: check if the symbol is marked with @migration
827-
// indicating it has changed semantics between versions.
819+
private def checkUndesiredProperties(sym: Symbol, pos: SourcePosition)(using Context): Unit =
820+
checkDeprecated(sym, pos)
821+
828822
val xMigrationValue = ctx.settings.Xmigration.value
829-
if (sym.hasAnnotation(defn.MigrationAnnot) && xMigrationValue != NoScalaVersion)
830-
sym.migrationVersion.get match {
831-
case scala.util.Success(symVersion) if xMigrationValue < symVersion=>
832-
ctx.warning(SymbolChangedSemanticsInVersion(sym, symVersion), pos)
823+
if xMigrationValue != NoScalaVersion then
824+
checkMigration(sym, pos, xMigrationValue)
825+
826+
827+
/** If @deprecated is present, and the point of reference is not enclosed
828+
* in either a deprecated member or a scala bridge method, issue a warning.
829+
*/
830+
private def checkDeprecated(sym: Symbol, pos: SourcePosition)(using Context): Unit =
831+
for
832+
annot <- sym.getAnnotation(defn.DeprecatedAnnot)
833+
if !ctx.owner.ownersIterator.exists(_.isDeprecated)
834+
do
835+
val msg = annot.argumentConstant(0).map(": " + _.stringValue).getOrElse("")
836+
val since = annot.argumentConstant(1).map(" since " + _.stringValue).getOrElse("")
837+
ctx.deprecationWarning(s"${sym.showLocated} is deprecated${since}${msg}", pos)
838+
839+
/** If @migration is present (indicating that the symbol has changed semantics between versions),
840+
* emit a warning.
841+
*/
842+
private def checkMigration(sym: Symbol, pos: SourcePosition, xMigrationValue: ScalaVersion)(using Context): Unit =
843+
for annot <- sym.getAnnotation(defn.MigrationAnnot) do
844+
val migrationVersion = ScalaVersion.parse(annot.argumentConstant(1).get.stringValue)
845+
migrationVersion match
846+
case Success(symVersion) if xMigrationValue < symVersion =>
847+
val msg = annot.argumentConstant(0).get.stringValue
848+
ctx.warning(SymbolChangedSemanticsInVersion(sym, symVersion, msg), pos)
833849
case Failure(ex) =>
834-
ctx.warning(SymbolHasUnparsableVersionNumber(sym, ex.getMessage()), pos)
850+
ctx.warning(SymbolHasUnparsableVersionNumber(sym, ex.getMessage), pos)
835851
case _ =>
836-
}
837-
}
838852

839853
/** Check that a deprecated val or def does not override a
840854
* concrete, non-deprecated method. If it does, then

compiler/test/dotty/tools/backend/jvm/DottyBytecodeTests.scala

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -938,7 +938,7 @@ class TestBCode extends DottyBytecodeTest {
938938
|
939939
|}
940940
""".stripMargin
941-
checkBCode(List(code)) { dir =>
941+
checkBCode(code) { dir =>
942942
val c = loadClassNode(dir.lookupName("C.class", directory = false).input)
943943

944944
assertInvoke(getMethod(c, "f1"), "[Ljava/lang/String;", "clone") // array descriptor as receiver
@@ -947,6 +947,36 @@ class TestBCode extends DottyBytecodeTest {
947947
assertInvoke(getMethod(c, "f4"), "java/lang/Object", "toString")
948948
}
949949
}
950+
951+
@Test
952+
def deprecation(): Unit = {
953+
val code =
954+
"""@deprecated
955+
|class Test {
956+
| @deprecated
957+
| val v = 0
958+
|
959+
| @deprecated
960+
| var x = 0
961+
|
962+
| @deprecated("do not use this function!")
963+
| def f(): Unit = ()
964+
|}
965+
""".stripMargin
966+
967+
checkBCode(code) { dir =>
968+
val c = loadClassNode(dir.lookupName("Test.class", directory = false).input)
969+
assert((c.access & Opcodes.ACC_DEPRECATED) != 0)
970+
assert((getMethod(c, "f").access & Opcodes.ACC_DEPRECATED) != 0)
971+
972+
assert((getField(c, "v").access & Opcodes.ACC_DEPRECATED) != 0)
973+
assert((getMethod(c, "v").access & Opcodes.ACC_DEPRECATED) != 0)
974+
975+
assert((getField(c, "x").access & Opcodes.ACC_DEPRECATED) != 0)
976+
assert((getMethod(c, "x").access & Opcodes.ACC_DEPRECATED) != 0)
977+
assert((getMethod(c, "x_$eq").access & Opcodes.ACC_DEPRECATED) != 0)
978+
}
979+
}
950980
}
951981

952982
object invocationReceiversTestCode {

language-server/src/dotty/tools/languageserver/DottyLanguageServer.scala

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ import scala.io.Codec
1919
import dotc._
2020
import ast.{Trees, tpd, untpd}
2121
import core._, core.Decorators._
22-
import Annotations.AnnotInfo
2322
import Comments._, Constants._, Contexts._, Flags._, Names._, NameOps._, Symbols._, SymDenotations._, Trees._, Types._
2423
import classpath.ClassPathEntries
2524
import reporting._
@@ -856,7 +855,7 @@ object DottyLanguageServer {
856855
item.setDocumentation(hoverContent(None, documentation))
857856
}
858857

859-
item.setDeprecated(completion.symbols.forall(_.isDeprecated))
858+
item.setDeprecated(completion.symbols.forall(_.hasAnnotation(defn.DeprecatedAnnot)))
860859
completion.symbols.headOption.foreach(s => item.setKind(completionItemKind(s)))
861860
item
862861
}

0 commit comments

Comments
 (0)