Skip to content

Commit b420867

Browse files
author
Niklas Vest
committed
Implement mvp of #4008
1 parent fd18546 commit b420867

File tree

5 files changed

+135
-3
lines changed

5 files changed

+135
-3
lines changed

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,8 @@ enum ErrorMessageID extends java.lang.Enum[ErrorMessageID] {
164164
UnexpectedPatternForSummonFromID,
165165
AnonymousInstanceCannotBeEmptyID,
166166
TypeSpliceInValPatternID,
167-
ModifierNotAllowedForDefinitionID
167+
ModifierNotAllowedForDefinitionID,
168+
InvalidReferenceInImplicitNotFoundAnnotationID
168169

169170
def errorNumber = ordinal - 2
170171
}

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2390,3 +2390,14 @@ import ast.tpd
23902390
def msg = s"Modifier `${flag.flagsString}` is not allowed for this definition"
23912391
def explain = ""
23922392
}
2393+
2394+
class InvalidReferenceInImplicitNotFoundAnnotation(typeVar: String,full: String, owner: String)(using Context)
2395+
extends ReferenceMsg(InvalidReferenceInImplicitNotFoundAnnotationID) {
2396+
def msg = em"""|Invalid reference to a type variable "${hl(typeVar)}" found
2397+
| in the user-defined error message ${hl("\"" + full + "\"")}
2398+
| in the @${ctx.definitions.ImplicitNotFoundAnnot.name.show} annotation argument.
2399+
|
2400+
|The variable does not occur in the signature of ${hl(owner)}.
2401+
|""".stripMargin
2402+
def explain = ""
2403+
}

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

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ package typer
44
import transform._
55
import core._
66
import Symbols._, Types._, Contexts._, Flags._, Names._, NameOps._
7-
import StdNames._, Denotations._, SymUtils._, Phases._
7+
import StdNames._, Denotations._, SymUtils._, Phases._, SymDenotations._
88
import NameKinds.DefaultGetterName
99
import Annotations._
1010
import util.Spans._
@@ -19,9 +19,11 @@ import Decorators._
1919
import typer.ErrorReporting._
2020
import config.Feature.warnOnMigration
2121
import reporting._
22+
import scala.util.matching.Regex._
23+
import Constants.Constant
2224

2325
object RefChecks {
24-
import tpd.{Tree, MemberDef}
26+
import tpd.{Tree, MemberDef, Literal, Template, DefDef}
2527

2628
val name: String = "refchecks"
2729

@@ -926,6 +928,24 @@ object RefChecks {
926928
}
927929

928930
val NoLevelInfo: RefChecks.OptLevelInfo = new OptLevelInfo()
931+
932+
/**
933+
* Verify that references in the implicitNotFound annotation user-defined message are valid.
934+
* (i.e. they refer to a type variable that really occurs in the signature of the annotated symbol.)
935+
*/
936+
private def checkImplicitNotFoundAnnotation(tree: Template, sd: SymDenotation)(using Context): Unit =
937+
for
938+
annotation <- sd.annotations if annotation.symbol == ctx.definitions.ImplicitNotFoundAnnot
939+
arg <- annotation.arguments.collect { case Literal(c: Constant) => c.stringValue }
940+
do
941+
// matches quoted references such as "${(A)}", "${(Abc)}", etc.
942+
val reference = """(?<=\$\{)[a-zA-Z]+(?=\})""".r
943+
val matches = reference.findAllMatchIn(arg)
944+
for Match(ref) <- matches if !sd.typeParams.exists(_.denot.name.show == ref) do
945+
val msg = InvalidReferenceInImplicitNotFoundAnnotation(ref, arg, sd.name.show)
946+
val pos = ctx.source.atSpan(sd.symbol.span)
947+
report.warning(msg, pos)
948+
929949
}
930950
import RefChecks._
931951

@@ -1012,6 +1032,7 @@ class RefChecks extends MiniPhase { thisPhase =>
10121032
if (cls.is(Trait)) tree.parents.foreach(checkParentPrefix(cls, _))
10131033
checkCompanionNameClashes(cls)
10141034
checkAllOverrides(cls)
1035+
checkImplicitNotFoundAnnotation(tree, cls.classDenot)
10151036
tree
10161037
}
10171038
catch {
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
-- [E157] Reference Error: ./tests/neg-custom-args/fatal-warnings/i4008.scala:6:6
2+
6 |class ShouldWarn1[A]
3+
| ^
4+
|Invalid reference to a type variable "B" found
5+
| in the user-defined error message "An implicit ShouldWarn1[${B}] is not in scope"
6+
| in the @implicitNotFound annotation argument.
7+
|
8+
|The variable does not occur in the signature of ShouldWarn1.
9+
-- [E157] Reference Error: ./tests/neg-custom-args/fatal-warnings/i4008.scala:10:6
10+
10 |trait ShouldWarn2[B]
11+
| ^
12+
|Invalid reference to a type variable "A" found
13+
| in the user-defined error message "An implicit ShouldWarn2[${A}] is not in scope"
14+
| in the @implicitNotFound annotation argument.
15+
|
16+
|The variable does not occur in the signature of ShouldWarn2.
17+
-- [E157] Reference Error: ./tests/neg-custom-args/fatal-warnings/i4008.scala:14:6
18+
14 |trait ShouldWarn3[B, C]
19+
| ^
20+
|Invalid reference to a type variable "A" found
21+
| in the user-defined error message "An implicit ShouldWarn3[${A},${B}] is not in scope"
22+
| in the @implicitNotFound annotation argument.
23+
|
24+
|The variable does not occur in the signature of ShouldWarn3.
25+
-- [E157] Reference Error: ./tests/neg-custom-args/fatal-warnings/i4008.scala:18:6
26+
18 |class ShouldWarn4[C, D]
27+
| ^
28+
|Invalid reference to a type variable "A" found
29+
| in the user-defined error message "An implicit ShouldWarn4[${A},${B}] is not in scope"
30+
| in the @implicitNotFound annotation argument.
31+
|
32+
|The variable does not occur in the signature of ShouldWarn4.
33+
-- [E157] Reference Error: ./tests/neg-custom-args/fatal-warnings/i4008.scala:22:6
34+
22 |class ShouldWarn5[C, D]
35+
| ^
36+
|Invalid reference to a type variable "Abc" found
37+
| in the user-defined error message "An implicit ShouldWarn5[${C},${Abc}] is not in scope"
38+
| in the @implicitNotFound annotation argument.
39+
|
40+
|The variable does not occur in the signature of ShouldWarn5.
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// ===== Template annotations =====
2+
3+
4+
// class, 1TP, invalid ref
5+
@annotation.implicitNotFound("An implicit ShouldWarn1[${B}] is not in scope")
6+
class ShouldWarn1[A]
7+
8+
// trait, 1TP, invalid ref
9+
@annotation.implicitNotFound("An implicit ShouldWarn2[${A}] is not in scope")
10+
trait ShouldWarn2[B]
11+
12+
// trait, 2TP, 1 invalid ref
13+
@annotation.implicitNotFound("An implicit ShouldWarn3[${A},${B}] is not in scope")
14+
trait ShouldWarn3[B, C]
15+
16+
// class, 2TP, 2 invalid refs
17+
@annotation.implicitNotFound("An implicit ShouldWarn4[${A},${B}] is not in scope")
18+
class ShouldWarn4[C, D]
19+
20+
// class, 2TP, 1 invalid multi-char refs
21+
@annotation.implicitNotFound("An implicit ShouldWarn5[${C},${Abc}] is not in scope")
22+
class ShouldWarn5[C, D]
23+
24+
// trait, 1TP, valid ref
25+
@annotation.implicitNotFound("An implicit ShouldntWarn1[${A}] is not in scope")
26+
trait ShouldntWarn1[A]
27+
28+
// class, 2TP, only one ref but that one is valid
29+
@annotation.implicitNotFound("An implicit ShouldntWarn2[${A}, ...] is not in scope")
30+
class ShouldntWarn2[A, B]
31+
32+
// trait, 2TP, 2 valid refs
33+
@annotation.implicitNotFound("An implicit ShouldntWarn3[${A}, ${B}] is not in scope")
34+
trait ShouldntWarn3[A, B]
35+
36+
// class, 2TP, 2 valid refs
37+
@annotation.implicitNotFound("An implicit ShouldntWarn4[${Hello},${World}] is not in scope")
38+
class ShouldntWarn4[Hello, World]
39+
40+
41+
// ===== DefDef param annotations =====
42+
43+
44+
@annotation.implicitNotFound("Hopefully you don't see this!")
45+
class C[A]
46+
47+
object x {
48+
def someMethod[A](using @annotation.implicitNotFound("No C[${A}] found") sc: C[A]) = ???
49+
}
50+
51+
52+
// ===== use site =====
53+
54+
55+
@main def run = {
56+
// println(implicitly[C[String]])
57+
println("Hai")
58+
// someMethod
59+
}

0 commit comments

Comments
 (0)