From 1327f1d153eaa38bd380bb99a1187bf7c9c2b35c Mon Sep 17 00:00:00 2001 From: Som Snytt Date: Tue, 6 Jun 2023 01:32:48 -0700 Subject: [PATCH] No warn when case class uses deprecated members --- .../tools/dotc/typer/CrossVersionChecks.scala | 67 +++++++++++++------ compiler/test-resources/repl/reset-command | 2 +- compiler/test-resources/repl/settings-command | 2 +- tests/pos/i11022.scala | 2 + tests/warn/i11022.check | 8 +++ tests/warn/i11022.scala | 10 ++- 6 files changed, 68 insertions(+), 23 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/CrossVersionChecks.scala b/compiler/src/dotty/tools/dotc/typer/CrossVersionChecks.scala index 1e0907ee74a6..790ffb2ad343 100644 --- a/compiler/src/dotty/tools/dotc/typer/CrossVersionChecks.scala +++ b/compiler/src/dotty/tools/dotc/typer/CrossVersionChecks.scala @@ -3,6 +3,7 @@ package dotc package transform import core.* +import Annotations.Annotation import Symbols.*, Types.*, Contexts.*, Flags.*, Decorators.*, reporting.* import util.SrcPos import config.{ScalaVersion, NoScalaVersion, Feature, ScalaRelease} @@ -161,29 +162,42 @@ object CrossVersionChecks: /** If @deprecated is present, and the point of reference is not enclosed * in either a deprecated member or a scala bridge method, issue a warning. + * + * Also check for deprecation of the companion class for synthetic methods in the companion module. */ private[CrossVersionChecks] def checkDeprecatedRef(sym: Symbol, pos: SrcPos)(using Context): Unit = - - // Also check for deprecation of the companion class for synthetic methods - val toCheck = sym :: (if sym.isAllOf(SyntheticMethod) then sym.owner.companionClass :: Nil else Nil) - for sym <- toCheck; annot <- sym.getAnnotation(defn.DeprecatedAnnot) do - if !skipWarning(sym) then - val msg = annot.argumentConstant(0).map(": " + _.stringValue).getOrElse("") - val since = annot.argumentConstant(1).map(" since " + _.stringValue).getOrElse("") - report.deprecationWarning(em"${sym.showLocated} is deprecated${since}${msg}", pos) - - /** Skip warnings for synthetic members of case classes during declaration and - * scan the chain of outer declaring scopes from the current context - * a deprecation warning will be skipped if one the following holds - * for a given declaring scope: - * - the symbol associated with the scope is also deprecated. - * - if and only if `sym` is an enum case, the scope is either - * a module that declares `sym`, or the companion class of the - * module that declares `sym`. + def maybeWarn(annotee: Symbol, annot: Annotation) = if !skipWarning(sym) then + val message = annot.argumentConstantString(0).filter(!_.isEmpty).map(": " + _).getOrElse("") + val since = annot.argumentConstantString(1).filter(!_.isEmpty).map(" since " + _).getOrElse("") + report.deprecationWarning(em"${annotee.showLocated} is deprecated${since}${message}", pos) + sym.getAnnotation(defn.DeprecatedAnnot) match + case Some(annot) => maybeWarn(sym, annot) + case _ => + if sym.isAllOf(SyntheticMethod) then + val companion = sym.owner.companionClass + if companion.is(CaseClass) then companion.getAnnotation(defn.DeprecatedAnnot).foreach(maybeWarn(companion, _)) + + /** Decide whether the deprecation of `sym` should be ignored in this context. + * + * The warning is skipped if any symbol in the context owner chain is deprecated, + * that is, an enclosing scope is associated with a deprecated symbol. + * + * Further exclusions are needed for enums and case classes, + * since they typically need to refer to deprecated members + * even if the enclosing enum or case class is not deprecated. + * + * If and only if `sym` is an enum case, the warning is skipped + * if an enclosing scope is either a module that declares `sym`, + * or the companion class of the module that declares `sym`. + * + * For a deprecated case class or case class element, + * the warning is skipped for synthetic sites where the enclosing + * class (or its companion) is either the deprecated case class + * or the case class of the deprecated element. */ private def skipWarning(sym: Symbol)(using Context): Boolean = - /** is the owner an enum or its companion and also the owner of sym */ + // is the owner an enum or its companion and also the owner of sym def isEnumOwner(owner: Symbol)(using Context) = // pre: sym is an enumcase if owner.isEnumClass then owner.companionClass eq sym.owner @@ -194,6 +208,19 @@ object CrossVersionChecks: // pre: sym is an enumcase owner.isDeprecated || isEnumOwner(owner) - (ctx.owner.is(Synthetic) && sym.is(CaseClass)) - || ctx.owner.ownersIterator.exists(if sym.isEnumCase then isDeprecatedOrEnum else _.isDeprecated) + def siteIsEnclosedByDeprecatedElement = + ctx.owner.ownersIterator.exists: + if sym.isEnumCase then isDeprecatedOrEnum else _.isDeprecated + + def siteIsSyntheticCaseClassMember = + val owner = ctx.owner + def symIsCaseOrMember = + val enclosing = owner.enclosingClass + val companion = enclosing.companionClass + // deprecated sym is either enclosing case class or a sibling member + def checkSym(k: Symbol) = sym == k || sym.owner == k + (enclosing.is(CaseClass) || companion.is(CaseClass)) && (checkSym(enclosing) || checkSym(companion)) + owner.is(Synthetic) && symIsCaseOrMember + + siteIsSyntheticCaseClassMember || siteIsEnclosedByDeprecatedElement end skipWarning diff --git a/compiler/test-resources/repl/reset-command b/compiler/test-resources/repl/reset-command index 0adf0d93a0d8..3977504d4f5a 100644 --- a/compiler/test-resources/repl/reset-command +++ b/compiler/test-resources/repl/reset-command @@ -12,7 +12,7 @@ scala> def f(thread: Thread) = thread.stop() -- Deprecation Warning: -------------------------------------------------------- 1 | def f(thread: Thread) = thread.stop() | ^^^^^^^^^^^ - |method stop in class Thread is deprecated since : see corresponding Javadoc for more information. + |method stop in class Thread is deprecated: see corresponding Javadoc for more information. def f(thread: Thread): Unit scala> def resetNoArgsStillWorks = 1 diff --git a/compiler/test-resources/repl/settings-command b/compiler/test-resources/repl/settings-command index 5e9912384435..dce782b92836 100644 --- a/compiler/test-resources/repl/settings-command +++ b/compiler/test-resources/repl/settings-command @@ -11,7 +11,7 @@ scala> def f(thread: Thread) = thread.stop() -- Deprecation Warning: -------------------------------------------------------- 1 | def f(thread: Thread) = thread.stop() | ^^^^^^^^^^^ - |method stop in class Thread is deprecated since : see corresponding Javadoc for more information. + |method stop in class Thread is deprecated: see corresponding Javadoc for more information. def f(thread: Thread): Unit scala> diff --git a/tests/pos/i11022.scala b/tests/pos/i11022.scala index aa211426387d..6cccea0ac77c 100644 --- a/tests/pos/i11022.scala +++ b/tests/pos/i11022.scala @@ -1,3 +1,5 @@ //> using options -Werror -deprecation @deprecated("no CaseClass") case class CaseClass(rgb: Int) + +case class K(@deprecated("don't use k, ok?","0.1") k: Int) diff --git a/tests/warn/i11022.check b/tests/warn/i11022.check index 4257bb64652c..79b8b990ed59 100644 --- a/tests/warn/i11022.check +++ b/tests/warn/i11022.check @@ -1,3 +1,7 @@ +-- Deprecation Warning: tests/warn/i11022.scala:19:22 ------------------------------------------------------------------ +19 | def usage(k: K) = k.k // warn + | ^^^ + | value k in class K is deprecated since 0.1: don't use k, ok? -- Deprecation Warning: tests/warn/i11022.scala:10:7 ------------------------------------------------------------------- 10 |val a: CaseClass = CaseClass(42) // warn: deprecated type // warn: deprecated apply method | ^^^^^^^^^ @@ -18,3 +22,7 @@ 12 |val c: Unit = CaseClass(42).magic() // warn: deprecated apply method | ^^^^^^^^^ | class CaseClass is deprecated: no CaseClass +-- Deprecation Warning: tests/warn/i11022.scala:14:4 ------------------------------------------------------------------- +14 |val CaseClass(rgb) = b // warn + | ^^^^^^^^^ + | class CaseClass is deprecated: no CaseClass diff --git a/tests/warn/i11022.scala b/tests/warn/i11022.scala index 8e2de9d8f519..60a7299a49b1 100644 --- a/tests/warn/i11022.scala +++ b/tests/warn/i11022.scala @@ -10,4 +10,12 @@ object CaseClass: val a: CaseClass = CaseClass(42) // warn: deprecated type // warn: deprecated apply method val b: CaseClass = new CaseClass(42) // warn: deprecated type // warn: deprecated class val c: Unit = CaseClass(42).magic() // warn: deprecated apply method -val d: Unit = CaseClass.notDeprecated() // compiles \ No newline at end of file +val d: Unit = CaseClass.notDeprecated() // compiles +val CaseClass(rgb) = b // warn + +case class K(@deprecated("don't use k, ok?","0.1") k: Int) + +object K: + def usage(k: K) = k.k // warn + +val s: String = CaseClass.toString