Skip to content

Commit

Permalink
No warn when case class uses deprecated members
Browse files Browse the repository at this point in the history
  • Loading branch information
som-snytt committed Mar 19, 2024
1 parent 4554131 commit 1327f1d
Show file tree
Hide file tree
Showing 6 changed files with 68 additions and 23 deletions.
67 changes: 47 additions & 20 deletions compiler/src/dotty/tools/dotc/typer/CrossVersionChecks.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down Expand Up @@ -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
Expand All @@ -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
2 changes: 1 addition & 1 deletion compiler/test-resources/repl/reset-command
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion compiler/test-resources/repl/settings-command
Original file line number Diff line number Diff line change
Expand Up @@ -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>
2 changes: 2 additions & 0 deletions tests/pos/i11022.scala
Original file line number Diff line number Diff line change
@@ -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)
8 changes: 8 additions & 0 deletions tests/warn/i11022.check
Original file line number Diff line number Diff line change
@@ -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
| ^^^^^^^^^
Expand All @@ -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
10 changes: 9 additions & 1 deletion tests/warn/i11022.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
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

0 comments on commit 1327f1d

Please sign in to comment.