Skip to content

Commit 2ae6999

Browse files
committed
Fix #4986: Support implicitNotFound on parameters
1 parent 4b8a1de commit 2ae6999

20 files changed

+283
-124
lines changed

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2425,8 +2425,8 @@ import ast.tpd
24252425

24262426
class InvalidReferenceInImplicitNotFoundAnnotation(typeVar: String, owner: String)(using Context)
24272427
extends ReferenceMsg(InvalidReferenceInImplicitNotFoundAnnotationID) {
2428-
def msg = em"""|Invalid reference to a type variable "${hl(typeVar)}" found in the annotation argument.
2429-
|The variable does not occur in the signature of ${hl(owner)}.
2428+
def msg = em"""|Invalid reference to a type variable ${hl(typeVar)} found in the annotation argument.
2429+
|The variable does not occur as a parameter in the scope of ${hl(owner)}.
24302430
|""".stripMargin
24312431
def explain = ""
24322432
}

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

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import config.Feature
1212
import java.util.regex.Matcher.quoteReplacement
1313
import reporting._
1414

15+
import scala.util.matching.Regex
16+
1517
object ErrorReporting {
1618

1719
import tpd._
@@ -133,11 +135,13 @@ object ErrorReporting {
133135
*/
134136
def userDefinedErrorString(raw: String, paramNames: List[String], args: List[Type]): String = {
135137
def translate(name: String): Option[String] = {
136-
assert(paramNames.length == args.length)
137138
val idx = paramNames.indexOf(name)
138-
if (idx >= 0) Some(quoteReplacement(ex"${args(idx)}")) else None
139+
if (idx >= 0) Some(ex"${args(idx)}") else None
139140
}
140-
"""\$\{\w*\}""".r.replaceSomeIn(raw, m => translate(m.matched.drop(2).init))
141+
142+
"""\$\{\s*([^}\s]+)\s*\}""".r.replaceAllIn(raw, (_: Regex.Match) match {
143+
case Regex.Groups(v) => quoteReplacement(translate(v).getOrElse(""))
144+
})
141145
}
142146

143147
def rewriteNotice: String =

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

Lines changed: 57 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -804,9 +804,14 @@ trait Implicits:
804804
arg
805805
}
806806

807-
def missingArgMsg(arg: Tree, pt: Type, where: String)(using Context): String = {
807+
private def typeVariablesInScope(sym: Symbol)(using Context): List[Symbol] =
808+
sym.ownersIterator.flatMap{ ownerSym =>
809+
ownerSym.typeParams ++ ownerSym.paramSymss.flatten.filter(_.isType)
810+
}.toList
808811

809-
def msg(shortForm: String)(headline: String = shortForm) = arg match {
812+
def missingArgMsg(arg: Tree, pt: Type, where: String, paramSymWithMethodTree: Option[(Symbol, Tree)] = None)(using Context): String = {
813+
814+
def formatMsg(shortForm: String)(headline: String = shortForm) = arg match {
810815
case arg: Trees.SearchFailureIdent[?] =>
811816
shortForm
812817
case _ =>
@@ -878,17 +883,11 @@ trait Implicits:
878883
case (_, alt @ AmbiguousImplicitMsg(msg)) =>
879884
userDefinedAmbiguousImplicitMsg(alt, msg)
880885
case _ =>
881-
msg(s"ambiguous implicit arguments: ${ambi.explanation}${location("of")}")(
886+
formatMsg(s"ambiguous implicit arguments: ${ambi.explanation}${location("of")}")(
882887
s"ambiguous implicit arguments of type ${pt.show} found${location("for")}")
883888
}
884889

885890
case _ =>
886-
val userDefined = userDefinedMsg(pt.typeSymbol, defn.ImplicitNotFoundAnnot).map(raw =>
887-
err.userDefinedErrorString(
888-
raw,
889-
pt.typeSymbol.typeParams.map(_.name.unexpandedName.toString),
890-
pt.widenExpr.dropDependentRefinement.argInfos))
891-
892891
def hiddenImplicitsAddendum: String =
893892

894893
def hiddenImplicitNote(s: SearchSuccess) =
@@ -920,9 +919,55 @@ trait Implicits:
920919
if normalImports.isEmpty then suggestedImports else normalImports
921920
end hiddenImplicitsAddendum
922921

923-
msg(userDefined.getOrElse(
924-
em"no implicit argument of type $pt was found${location("for")}"))() ++
925-
hiddenImplicitsAddendum
922+
val userDefinedParamMessage = paramSymWithMethodTree.flatMap{ (sym, tree) =>
923+
userDefinedMsg(sym, defn.ImplicitNotFoundAnnot).map{ rawMsg =>
924+
val paramSyms = typeVariablesInScope(sym)
925+
val paramTypeRefs = paramSyms.map(_.typeRef.typeConstructor)
926+
927+
val argTypes = tree match {
928+
case TypeApply(Select(qual, _), targs) =>
929+
val methodOwnerType = qual.tpe
930+
val methodOwner = tree.symbol.owner
931+
val methodTypeParams = tree.symbol.paramSymss.flatten.filter(_.isType)
932+
val methodTypeArgs = targs.map(_.tpe)
933+
paramTypeRefs.map(_.asSeenFrom(methodOwnerType, methodOwner).subst(methodTypeParams, methodTypeArgs))
934+
case _ => paramTypeRefs
935+
}
936+
937+
err.userDefinedErrorString(
938+
rawMsg,
939+
paramSyms.map(_.name.unexpandedName.toString),
940+
argTypes
941+
)
942+
}
943+
}
944+
945+
val userDefinedTypeMessage = userDefinedMsg(pt.typeSymbol, defn.ImplicitNotFoundAnnot).map{ rawMsg =>
946+
def extractTypeParams(tpe: Type): List[(String, Type)] =
947+
tpe match {
948+
case ap @ AppliedType(tycon, _) =>
949+
val paramsNames = ap.typeSymbol.typeParams.map(_.name.unexpandedName.toString)
950+
val paramsValues = ap.widenExpr.dropDependentRefinement.argInfos
951+
paramsNames.zip(paramsValues) ++ extractTypeParams(tycon)
952+
case RefinedType(parent, _, refinedInfo) => extractTypeParams(parent) ++ extractTypeParams(refinedInfo)
953+
case TypeRef(prefix, _) => extractTypeParams(prefix)
954+
case _ => List.empty
955+
}
956+
957+
val (paramsNames, paramsValues) = extractTypeParams(pt).unzip
958+
959+
err.userDefinedErrorString(
960+
rawMsg,
961+
paramsNames,
962+
paramsValues
963+
)
964+
}
965+
966+
val shortMessage = userDefinedParamMessage.orElse(userDefinedTypeMessage).getOrElse(
967+
em"no implicit argument of type $pt was found${location("for")}"
968+
)
969+
970+
formatMsg(shortMessage)() ++ hiddenImplicitsAddendum
926971
}
927972
}
928973

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

Lines changed: 46 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import scala.util.matching.Regex._
2323
import Constants.Constant
2424

2525
object RefChecks {
26-
import tpd.{Tree, MemberDef, Literal, Template, DefDef}
26+
import tpd.{Tree, MemberDef, NamedArg, Literal, Template, DefDef}
2727

2828
val name: String = "refchecks"
2929

@@ -943,46 +943,58 @@ object RefChecks {
943943
* (i.e. they refer to a type variable that really occurs in the signature of the annotated symbol.)
944944
*/
945945
private object checkImplicitNotFoundAnnotation:
946-
947946
/** Warns if the class or trait has an @implicitNotFound annotation
948947
* with invalid type variable references.
949948
*/
950949
def template(sd: SymDenotation)(using Context): Unit =
951-
for
952-
annotation <- sd.getAnnotation(defn.ImplicitNotFoundAnnot)
953-
l@Literal(c: Constant) <- annotation.argument(0)
954-
do forEachTypeVariableReferenceIn(c.stringValue) { case (ref, start) =>
955-
if !sd.typeParams.exists(_.denot.name.show == ref) then
956-
reportInvalidReferences(l, ref, start, sd)
957-
}
950+
val typeParamsNames = sd.ownersIterator.toSeq.flatMap(_.typeParams).map(_.denot.name.show)
951+
checkReferences(sd, typeParamsNames)
958952

959953
/** Warns if the def has parameters with an `@implicitNotFound` annotation
960954
* with invalid type variable references.
961955
*/
962956
def defDef(sd: SymDenotation)(using Context): Unit =
957+
val typeParamsNames = sd.ownersIterator.toSeq.flatMap{ ownerSym =>
958+
ownerSym.typeParams ++ ownerSym.paramSymss.flatten.filter(_.isType)
959+
}.map(_.name.show)
960+
963961
for
964962
paramSymss <- sd.paramSymss
965963
param <- paramSymss
966-
do
967-
for
968-
annotation <- param.getAnnotation(defn.ImplicitNotFoundAnnot)
969-
l@Literal(c: Constant) <- annotation.argument(0)
970-
do forEachTypeVariableReferenceIn(c.stringValue) { case (ref, start) =>
971-
if !sd.paramSymss.flatten.exists(_.name.show == ref) then
972-
reportInvalidReferences(l, ref, start, sd)
973-
}
964+
if param.isTerm
965+
do checkReferences(param.denot, typeParamsNames)
966+
967+
private object PositionedStringLiteralArgument:
968+
def unapply(tree: Tree): Option[(String, Span)] = tree match {
969+
case l@Literal(Constant(s: String)) => Some((s, l.span))
970+
case NamedArg(_, l@Literal(Constant(s: String))) => Some((s, l.span))
971+
case _ => None
972+
}
973+
974+
private def checkReferences(sd: SymDenotation, typeParamsNames: Seq[String])(using Context): Unit =
975+
for
976+
annotation <- sd.getAnnotation(defn.ImplicitNotFoundAnnot)
977+
PositionedStringLiteralArgument(msg, span) <- annotation.argument(0)
978+
do forEachTypeVariableReferenceIn(msg) { case (ref, start) =>
979+
if !typeParamsNames.contains(ref) then
980+
reportInvalidReference(span, ref, start, sd)
981+
}
974982

975-
/** Reports an invalid reference to a type variable `typeRef` that was found in `l` */
976-
private def reportInvalidReferences(
977-
l: Literal,
983+
/** Reports an invalid reference to a type variable `typeRef` that was found in `span` */
984+
private def reportInvalidReference(
985+
span: Span,
978986
typeRef: String,
979-
offsetInLiteral: Int,
987+
variableOffsetinArgumentLiteral: Int,
980988
sd: SymDenotation
981989
)(using Context) =
982-
val msg = InvalidReferenceInImplicitNotFoundAnnotation(
983-
typeRef, if (sd.isConstructor) "the constructor" else sd.name.show)
984-
val span = l.span.shift(offsetInLiteral + 1) // +1 because of 0-based index
985-
val pos = ctx.source.atSpan(span.startPos)
990+
val typeRefName = s"`$typeRef`"
991+
val ownerName =
992+
if sd.isType then s"type `${sd.name.show}`"
993+
else if sd.owner.isConstructor then s"the constructor of `${sd.owner.owner.name.show}`"
994+
else s"method `${sd.owner.name.show}`"
995+
val msg = InvalidReferenceInImplicitNotFoundAnnotation(typeRefName, ownerName)
996+
val startPos = span.shift(variableOffsetinArgumentLiteral + 1).startPos // +1 because of 0-based index
997+
val pos = ctx.source.atSpan(startPos)
986998
report.warning(msg, pos)
987999

9881000
/** Calls the supplied function for each quoted reference to a type variable in <pre>s</pre>.
@@ -999,10 +1011,15 @@ object RefChecks {
9991011
* @param f A function to apply to every pair of (\<type variable>, \<position in string>).
10001012
*/
10011013
private def forEachTypeVariableReferenceIn(s: String)(f: (String, Int) => Unit) =
1002-
// matches quoted references such as "${(A)}", "${(Abc)}", etc.
1003-
val reference = """(?<=\$\{)[a-zA-Z]+(?=\})""".r
1004-
val matches = reference.findAllIn(s)
1005-
for m <- matches do f(m, matches.start)
1014+
// matches quoted references such as "${A}", "${ Abc }", etc.
1015+
val referencePattern = """\$\{\s*([^}\s]+)\s*\}""".r
1016+
val matches = referencePattern.findAllIn(s)
1017+
for reference <- matches do
1018+
val referenceOffset = matches.start
1019+
val prefixlessReference = reference.replaceFirst("""\$\{\s*""", "")
1020+
val variableOffset = referenceOffset + reference.length - prefixlessReference.length
1021+
val variableName = prefixlessReference.replaceFirst("""\s*\}""", "")
1022+
f(variableName, variableOffset)
10061023

10071024
end checkImplicitNotFoundAnnotation
10081025

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3107,11 +3107,13 @@ class Typer extends Namer
31073107
val propFail = propagatedFailure(args)
31083108

31093109
def issueErrors(): Tree = {
3110+
val paramSyms = tree.symbol.paramSymss.flatten.map(sym => sym.name -> sym).toMap
3111+
31103112
wtp.paramNames.lazyZip(wtp.paramInfos).lazyZip(args).foreach { (paramName, formal, arg) =>
31113113
arg.tpe match {
31123114
case failure: SearchFailureType =>
31133115
report.error(
3114-
missingArgMsg(arg, formal, implicitParamString(paramName, methodStr, tree)),
3116+
missingArgMsg(arg, formal, implicitParamString(paramName, methodStr, tree), Some((paramSyms(paramName), tree))),
31153117
tree.srcPos.endPos)
31163118
case _ =>
31173119
}
Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,40 @@
11
-- [E158] Reference Error: tests/neg-custom-args/fatal-warnings/i4008.scala:5:56 ---------------------------------------
22
5 |@annotation.implicitNotFound("An implicit ShouldWarn1[${B}] is not in scope") // error
33
| ^
4-
| Invalid reference to a type variable "B" found in the annotation argument.
5-
| The variable does not occur in the signature of ShouldWarn1.
4+
| Invalid reference to a type variable `B` found in the annotation argument.
5+
| The variable does not occur as a parameter in the scope of type `ShouldWarn1`.
66
-- [E158] Reference Error: tests/neg-custom-args/fatal-warnings/i4008.scala:9:56 ---------------------------------------
77
9 |@annotation.implicitNotFound("An implicit ShouldWarn2[${A}] is not in scope") // error
88
| ^
9-
| Invalid reference to a type variable "A" found in the annotation argument.
10-
| The variable does not occur in the signature of ShouldWarn2.
9+
| Invalid reference to a type variable `A` found in the annotation argument.
10+
| The variable does not occur as a parameter in the scope of type `ShouldWarn2`.
1111
-- [E158] Reference Error: tests/neg-custom-args/fatal-warnings/i4008.scala:13:56 --------------------------------------
1212
13 |@annotation.implicitNotFound("An implicit ShouldWarn3[${A},${B}] is not in scope") // error
1313
| ^
14-
| Invalid reference to a type variable "A" found in the annotation argument.
15-
| The variable does not occur in the signature of ShouldWarn3.
14+
| Invalid reference to a type variable `A` found in the annotation argument.
15+
| The variable does not occur as a parameter in the scope of type `ShouldWarn3`.
1616
-- [E158] Reference Error: tests/neg-custom-args/fatal-warnings/i4008.scala:17:56 --------------------------------------
1717
17 |@annotation.implicitNotFound("An implicit ShouldWarn4[${A},${B}] is not in scope") // error // error
1818
| ^
19-
| Invalid reference to a type variable "A" found in the annotation argument.
20-
| The variable does not occur in the signature of ShouldWarn4.
19+
| Invalid reference to a type variable `A` found in the annotation argument.
20+
| The variable does not occur as a parameter in the scope of type `ShouldWarn4`.
2121
-- [E158] Reference Error: tests/neg-custom-args/fatal-warnings/i4008.scala:17:61 --------------------------------------
2222
17 |@annotation.implicitNotFound("An implicit ShouldWarn4[${A},${B}] is not in scope") // error // error
2323
| ^
24-
| Invalid reference to a type variable "B" found in the annotation argument.
25-
| The variable does not occur in the signature of ShouldWarn4.
24+
| Invalid reference to a type variable `B` found in the annotation argument.
25+
| The variable does not occur as a parameter in the scope of type `ShouldWarn4`.
2626
-- [E158] Reference Error: tests/neg-custom-args/fatal-warnings/i4008.scala:21:61 --------------------------------------
2727
21 |@annotation.implicitNotFound("An implicit ShouldWarn5[${C},${Abc}] is not in scope") // error
2828
| ^
29-
| Invalid reference to a type variable "Abc" found in the annotation argument.
30-
| The variable does not occur in the signature of ShouldWarn5.
29+
| Invalid reference to a type variable `Abc` found in the annotation argument.
30+
| The variable does not occur as a parameter in the scope of type `ShouldWarn5`.
3131
-- [E158] Reference Error: tests/neg-custom-args/fatal-warnings/i4008.scala:44:54 --------------------------------------
3232
44 |class C[A](using @annotation.implicitNotFound("No C[${B}] found") c: Class[A]) // error
3333
| ^
34-
| Invalid reference to a type variable "B" found in the annotation argument.
35-
| The variable does not occur in the signature of the constructor.
34+
| Invalid reference to a type variable `B` found in the annotation argument.
35+
| The variable does not occur as a parameter in the scope of the constructor of `C`.
3636
-- [E158] Reference Error: tests/neg-custom-args/fatal-warnings/i4008.scala:46:62 --------------------------------------
3737
46 |def someMethod1[A](using @annotation.implicitNotFound("No C[${B}] found") sc: C[A]) = 0 // error
3838
| ^
39-
| Invalid reference to a type variable "B" found in the annotation argument.
40-
| The variable does not occur in the signature of someMethod1.
39+
| Invalid reference to a type variable `B` found in the annotation argument.
40+
| The variable does not occur as a parameter in the scope of method `someMethod1`.

0 commit comments

Comments
 (0)