diff --git a/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala b/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala
index 02d0215ee0ff..a2a5aaeafaff 100644
--- a/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala
+++ b/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala
@@ -165,7 +165,8 @@ enum ErrorMessageID extends java.lang.Enum[ErrorMessageID] {
AnonymousInstanceCannotBeEmptyID,
TypeSpliceInValPatternID,
ModifierNotAllowedForDefinitionID,
- CannotExtendJavaEnumID
+ CannotExtendJavaEnumID,
+ InvalidReferenceInImplicitNotFoundAnnotationID
def errorNumber = ordinal - 2
}
diff --git a/compiler/src/dotty/tools/dotc/reporting/messages.scala b/compiler/src/dotty/tools/dotc/reporting/messages.scala
index ef682c49e402..9885cba642c6 100644
--- a/compiler/src/dotty/tools/dotc/reporting/messages.scala
+++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala
@@ -2404,3 +2404,11 @@ import ast.tpd
def msg = s"Modifier `${flag.flagsString}` is not allowed for this definition"
def explain = ""
}
+
+ class InvalidReferenceInImplicitNotFoundAnnotation(typeVar: String, owner: String)(using Context)
+ extends ReferenceMsg(InvalidReferenceInImplicitNotFoundAnnotationID) {
+ def msg = em"""|Invalid reference to a type variable "${hl(typeVar)}" found in the annotation argument.
+ |The variable does not occur in the signature of ${hl(owner)}.
+ |""".stripMargin
+ def explain = ""
+ }
\ No newline at end of file
diff --git a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala
index d29ad943c90c..afb5e1a03b41 100644
--- a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala
+++ b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala
@@ -4,7 +4,7 @@ package typer
import transform._
import core._
import Symbols._, Types._, Contexts._, Flags._, Names._, NameOps._
-import StdNames._, Denotations._, SymUtils._, Phases._
+import StdNames._, Denotations._, SymUtils._, Phases._, SymDenotations._
import NameKinds.DefaultGetterName
import Annotations._
import util.Spans._
@@ -19,9 +19,11 @@ import Decorators._
import typer.ErrorReporting._
import config.Feature.{warnOnMigration, migrateTo3}
import reporting._
+import scala.util.matching.Regex._
+import Constants.Constant
object RefChecks {
- import tpd.{Tree, MemberDef}
+ import tpd.{Tree, MemberDef, Literal, Template, DefDef}
val name: String = "refchecks"
@@ -936,6 +938,74 @@ object RefChecks {
}
val NoLevelInfo: RefChecks.OptLevelInfo = new OptLevelInfo()
+
+ /** Verify that references in the user-defined `@implicitNotFound` message are valid.
+ * (i.e. they refer to a type variable that really occurs in the signature of the annotated symbol.)
+ */
+ private object checkImplicitNotFoundAnnotation:
+
+ /** Warns if the class or trait has an @implicitNotFound annotation
+ * with invalid type variable references.
+ */
+ def template(sd: SymDenotation)(using Context): Unit =
+ for
+ annotation <- sd.getAnnotation(defn.ImplicitNotFoundAnnot)
+ l@Literal(c: Constant) <- annotation.argument(0)
+ do forEachTypeVariableReferenceIn(c.stringValue) { case (ref, start) =>
+ if !sd.typeParams.exists(_.denot.name.show == ref) then
+ reportInvalidReferences(l, ref, start, sd)
+ }
+
+ /** Warns if the def has parameters with an `@implicitNotFound` annotation
+ * with invalid type variable references.
+ */
+ def defDef(sd: SymDenotation)(using Context): Unit =
+ for
+ paramSymss <- sd.paramSymss
+ param <- paramSymss
+ do
+ for
+ annotation <- param.getAnnotation(defn.ImplicitNotFoundAnnot)
+ l@Literal(c: Constant) <- annotation.argument(0)
+ do forEachTypeVariableReferenceIn(c.stringValue) { case (ref, start) =>
+ if !sd.paramSymss.flatten.exists(_.name.show == ref) then
+ reportInvalidReferences(l, ref, start, sd)
+ }
+
+ /** Reports an invalid reference to a type variable `typeRef` that was found in `l` */
+ private def reportInvalidReferences(
+ l: Literal,
+ typeRef: String,
+ offsetInLiteral: Int,
+ sd: SymDenotation
+ )(using Context) =
+ val msg = InvalidReferenceInImplicitNotFoundAnnotation(
+ typeRef, if (sd.isConstructor) "the constructor" else sd.name.show)
+ val span = l.span.shift(offsetInLiteral + 1) // +1 because of 0-based index
+ val pos = ctx.source.atSpan(span.startPos)
+ report.warning(msg, pos)
+
+ /** Calls the supplied function for each quoted reference to a type variable in
s
.
+ * The input
+ *
+ * ```scala
+ * "This is a ${T}ype re${F}erence"
+ * // ^0 ^12 ^22
+ * ```
+ *
+ * will lead to two invocations of `f`, once with `(T, 12)` and once with `(F, 22)` as argument.
+ *
+ * @param s The string to query for type variable references.
+ * @param f A function to apply to every pair of (\, \).
+ */
+ private def forEachTypeVariableReferenceIn(s: String)(f: (String, Int) => Unit) =
+ // matches quoted references such as "${(A)}", "${(Abc)}", etc.
+ val reference = """(?<=\$\{)[a-zA-Z]+(?=\})""".r
+ val matches = reference.findAllIn(s)
+ for m <- matches do f(m, matches.start)
+
+ end checkImplicitNotFoundAnnotation
+
}
import RefChecks._
@@ -1012,6 +1082,7 @@ class RefChecks extends MiniPhase { thisPhase =>
override def transformDefDef(tree: DefDef)(using Context): DefDef = {
checkNoPrivateOverrides(tree)
checkDeprecatedOvers(tree)
+ checkImplicitNotFoundAnnotation.defDef(tree.symbol.denot)
tree
}
@@ -1022,6 +1093,7 @@ class RefChecks extends MiniPhase { thisPhase =>
if (cls.is(Trait)) tree.parents.foreach(checkParentPrefix(cls, _))
checkCompanionNameClashes(cls)
checkAllOverrides(cls)
+ checkImplicitNotFoundAnnotation.template(cls.classDenot)
tree
}
catch {
diff --git a/tests/neg-custom-args/fatal-warnings/i4008.check b/tests/neg-custom-args/fatal-warnings/i4008.check
new file mode 100644
index 000000000000..c04a7949e9fd
--- /dev/null
+++ b/tests/neg-custom-args/fatal-warnings/i4008.check
@@ -0,0 +1,40 @@
+-- [E158] Reference Error: tests/neg-custom-args/fatal-warnings/i4008.scala:5:56 ---------------------------------------
+5 |@annotation.implicitNotFound("An implicit ShouldWarn1[${B}] is not in scope") // error
+ | ^
+ | Invalid reference to a type variable "B" found in the annotation argument.
+ | The variable does not occur in the signature of ShouldWarn1.
+-- [E158] Reference Error: tests/neg-custom-args/fatal-warnings/i4008.scala:9:56 ---------------------------------------
+9 |@annotation.implicitNotFound("An implicit ShouldWarn2[${A}] is not in scope") // error
+ | ^
+ | Invalid reference to a type variable "A" found in the annotation argument.
+ | The variable does not occur in the signature of ShouldWarn2.
+-- [E158] Reference Error: tests/neg-custom-args/fatal-warnings/i4008.scala:13:56 --------------------------------------
+13 |@annotation.implicitNotFound("An implicit ShouldWarn3[${A},${B}] is not in scope") // error
+ | ^
+ | Invalid reference to a type variable "A" found in the annotation argument.
+ | The variable does not occur in the signature of ShouldWarn3.
+-- [E158] Reference Error: tests/neg-custom-args/fatal-warnings/i4008.scala:17:56 --------------------------------------
+17 |@annotation.implicitNotFound("An implicit ShouldWarn4[${A},${B}] is not in scope") // error // error
+ | ^
+ | Invalid reference to a type variable "A" found in the annotation argument.
+ | The variable does not occur in the signature of ShouldWarn4.
+-- [E158] Reference Error: tests/neg-custom-args/fatal-warnings/i4008.scala:17:61 --------------------------------------
+17 |@annotation.implicitNotFound("An implicit ShouldWarn4[${A},${B}] is not in scope") // error // error
+ | ^
+ | Invalid reference to a type variable "B" found in the annotation argument.
+ | The variable does not occur in the signature of ShouldWarn4.
+-- [E158] Reference Error: tests/neg-custom-args/fatal-warnings/i4008.scala:21:61 --------------------------------------
+21 |@annotation.implicitNotFound("An implicit ShouldWarn5[${C},${Abc}] is not in scope") // error
+ | ^
+ | Invalid reference to a type variable "Abc" found in the annotation argument.
+ | The variable does not occur in the signature of ShouldWarn5.
+-- [E158] Reference Error: tests/neg-custom-args/fatal-warnings/i4008.scala:44:54 --------------------------------------
+44 |class C[A](using @annotation.implicitNotFound("No C[${B}] found") c: Class[A]) // error
+ | ^
+ | Invalid reference to a type variable "B" found in the annotation argument.
+ | The variable does not occur in the signature of the constructor.
+-- [E158] Reference Error: tests/neg-custom-args/fatal-warnings/i4008.scala:46:62 --------------------------------------
+46 |def someMethod1[A](using @annotation.implicitNotFound("No C[${B}] found") sc: C[A]) = 0 // error
+ | ^
+ | Invalid reference to a type variable "B" found in the annotation argument.
+ | The variable does not occur in the signature of someMethod1.
diff --git a/tests/neg-custom-args/fatal-warnings/i4008.scala b/tests/neg-custom-args/fatal-warnings/i4008.scala
new file mode 100644
index 000000000000..e6e46dc83a78
--- /dev/null
+++ b/tests/neg-custom-args/fatal-warnings/i4008.scala
@@ -0,0 +1,48 @@
+// ===== Template annotations =====
+
+
+// class, 1TP, invalid ref
+@annotation.implicitNotFound("An implicit ShouldWarn1[${B}] is not in scope") // error
+class ShouldWarn1[A]
+
+// trait, 1TP, invalid ref
+@annotation.implicitNotFound("An implicit ShouldWarn2[${A}] is not in scope") // error
+trait ShouldWarn2[B]
+
+// trait, 2TP, 1 invalid ref
+@annotation.implicitNotFound("An implicit ShouldWarn3[${A},${B}] is not in scope") // error
+trait ShouldWarn3[B, C]
+
+// class, 2TP, 2 invalid refs
+@annotation.implicitNotFound("An implicit ShouldWarn4[${A},${B}] is not in scope") // error // error
+class ShouldWarn4[C, D]
+
+// class, 2TP, 1 invalid multi-char refs
+@annotation.implicitNotFound("An implicit ShouldWarn5[${C},${Abc}] is not in scope") // error
+class ShouldWarn5[C, D]
+
+// trait, 1TP, valid ref
+@annotation.implicitNotFound("An implicit ShouldntWarn1[${A}] is not in scope")
+trait ShouldntWarn1[A]
+
+// class, 2TP, only one ref but that one is valid
+@annotation.implicitNotFound("An implicit ShouldntWarn2[${A}, ...] is not in scope")
+class ShouldntWarn2[A, B]
+
+// trait, 2TP, 2 valid refs
+@annotation.implicitNotFound("An implicit ShouldntWarn3[${A}, ${B}] is not in scope")
+trait ShouldntWarn3[A, B]
+
+// class, 2TP, 2 valid refs
+@annotation.implicitNotFound("An implicit ShouldntWarn4[${Hello},${World}] is not in scope")
+class ShouldntWarn4[Hello, World]
+
+// ===== DefDef param annotations =====
+
+
+@annotation.implicitNotFound("Hopefully you don't see this!")
+class C[A](using @annotation.implicitNotFound("No C[${B}] found") c: Class[A]) // error
+
+def someMethod1[A](using @annotation.implicitNotFound("No C[${B}] found") sc: C[A]) = 0 // error
+
+def someMethod2[A](using @annotation.implicitNotFound("No C[${A}] found") sc: C[A]) = ""
\ No newline at end of file