Skip to content

Commit 6052353

Browse files
committed
Fix #4986: Support implicitNotFound on parameters
Also fixes some complex cases of resolving type variables in `implicitNotFound` message for annotations put on type definitions
1 parent c900cde commit 6052353

21 files changed

+366
-125
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: 78 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -812,9 +812,20 @@ trait Implicits:
812812
arg
813813
}
814814

815-
def missingArgMsg(arg: Tree, pt: Type, where: String)(using Context): String = {
815+
private def typeVariablesInScope(sym: Symbol)(using Context): List[Symbol] =
816+
sym.ownersIterator.flatMap { ownerSym =>
817+
ownerSym.typeParams ++ ownerSym.paramSymss.flatten.filter(_.isType)
818+
}.toList
816819

817-
def msg(shortForm: String)(headline: String = shortForm) = arg match {
820+
821+
/** @param arg Tree representing a failed result of implicit search
822+
* @param pt Type for which an implicit value was searched
823+
* @param where Description of where the search was performed. Might be empty
824+
* @param paramSymWithMethodTree Symbol of the parameter for which the implicit was searched and tree of the method call that triggered the implicit search
825+
*/
826+
def missingArgMsg(arg: Tree, pt: Type, where: String, paramSymWithMethodTree: Option[(Symbol, Tree)] = None)(using Context): String = {
827+
828+
def formatMsg(shortForm: String)(headline: String = shortForm) = arg match {
818829
case arg: Trees.SearchFailureIdent[?] =>
819830
shortForm
820831
case _ =>
@@ -886,17 +897,11 @@ trait Implicits:
886897
case (_, alt @ AmbiguousImplicitMsg(msg)) =>
887898
userDefinedAmbiguousImplicitMsg(alt, msg)
888899
case _ =>
889-
msg(s"ambiguous implicit arguments: ${ambi.explanation}${location("of")}")(
900+
formatMsg(s"ambiguous implicit arguments: ${ambi.explanation}${location("of")}")(
890901
s"ambiguous implicit arguments of type ${pt.show} found${location("for")}")
891902
}
892903

893904
case _ =>
894-
val userDefined = userDefinedMsg(pt.typeSymbol, defn.ImplicitNotFoundAnnot).map(raw =>
895-
err.userDefinedErrorString(
896-
raw,
897-
pt.typeSymbol.typeParams.map(_.name.unexpandedName.toString),
898-
pt.widenExpr.dropDependentRefinement.argInfos))
899-
900905
def hiddenImplicitsAddendum: String =
901906

902907
def hiddenImplicitNote(s: SearchSuccess) =
@@ -928,9 +933,70 @@ trait Implicits:
928933
if normalImports.isEmpty then suggestedImports else normalImports
929934
end hiddenImplicitsAddendum
930935

931-
msg(userDefined.getOrElse(
932-
em"no implicit argument of type $pt was found${location("for")}"))() ++
933-
hiddenImplicitsAddendum
936+
937+
/** Extracting the message from a method parameter, e.g. in
938+
*
939+
* trait Foo
940+
*
941+
* def foo(implicit @annotation.implicitNotFound foo: Foo)
942+
*/
943+
def userDefinedParamMessage = paramSymWithMethodTree.flatMap { (sym, tree) =>
944+
userDefinedMsg(sym, defn.ImplicitNotFoundAnnot).map { rawMsg =>
945+
val paramSyms = typeVariablesInScope(sym)
946+
val paramTypeRefs = paramSyms.map(_.typeRef.typeConstructor)
947+
948+
val argTypes = tree match {
949+
case TypeApply(Select(qual, _), targs) =>
950+
val methodOwnerType = qual.tpe
951+
val methodOwner = tree.symbol.owner
952+
val methodTypeParams = tree.symbol.paramSymss.flatten.filter(_.isType)
953+
val methodTypeArgs = targs.map(_.tpe)
954+
paramTypeRefs.map(_.asSeenFrom(methodOwnerType, methodOwner).subst(methodTypeParams, methodTypeArgs))
955+
case _ => paramTypeRefs
956+
}
957+
958+
err.userDefinedErrorString(
959+
rawMsg,
960+
paramSyms.map(_.name.unexpandedName.toString),
961+
argTypes
962+
)
963+
}
964+
}
965+
966+
/** Extracting the message from a type, e.g. in
967+
*
968+
* implicit @annotation.implicitNotFound
969+
* trait Foo
970+
*
971+
* def foo(foo: Foo)
972+
*/
973+
def userDefinedTypeMessage = userDefinedMsg(pt.typeSymbol, defn.ImplicitNotFoundAnnot).map { rawMsg =>
974+
def extractTypeParams(tpe: Type): List[(String, Type)] =
975+
tpe match {
976+
case ap @ AppliedType(tycon, _) =>
977+
val paramsNames = ap.typeSymbol.typeParams.map(_.name.unexpandedName.toString)
978+
val paramsValues = ap.widenExpr.dropDependentRefinement.argInfos
979+
paramsNames.zip(paramsValues) ++ extractTypeParams(tycon)
980+
case RefinedType(parent, _, refinedInfo) => extractTypeParams(parent)
981+
case TypeRef(prefix, _) => extractTypeParams(prefix)
982+
case AnnotatedType(parent, _) => extractTypeParams(parent)
983+
case _ => List.empty
984+
}
985+
986+
val (paramsNames, paramsValues) = extractTypeParams(pt).unzip
987+
988+
err.userDefinedErrorString(
989+
rawMsg,
990+
paramsNames,
991+
paramsValues
992+
)
993+
}
994+
995+
val shortMessage = userDefinedParamMessage.orElse(userDefinedTypeMessage).getOrElse(
996+
em"no implicit argument of type $pt was found${location("for")}"
997+
)
998+
999+
formatMsg(shortMessage)() ++ hiddenImplicitsAddendum
9341000
}
9351001
}
9361002

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: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2003,6 +2003,13 @@ class Typer extends Namer
20032003
checkThisConstrCall(rhs1)
20042004
}
20052005

2006+
if sym.is(Method) && sym.owner.denot.isRefinementClass then
2007+
for annot <- sym.paramSymss.flatten.filter(_.isTerm).flatMap(_.getAnnotation(defn.ImplicitNotFoundAnnot)) do
2008+
report.warning(
2009+
i"The annotation ${defn.ImplicitNotFoundAnnot} is not allowed on parameters of methods defined inside a refinement and it will have no effect",
2010+
annot.tree.sourcePos
2011+
)
2012+
20062013
val ddef2 = assignType(cpy.DefDef(ddef)(name, tparams1, vparamss1, tpt1, rhs1), sym)
20072014

20082015
checkSignatureRepeatedParam(sym)
@@ -3108,12 +3115,20 @@ class Typer extends Namer
31083115
val propFail = propagatedFailure(args)
31093116

31103117
def issueErrors(): Tree = {
3118+
def paramSymWithMethodTree(paramName: TermName) =
3119+
if tree.symbol.exists then
3120+
val paramSyms = tree.symbol.paramSymss.flatten.map(sym => sym.name -> sym).toMap
3121+
Some((paramSyms(paramName), tree))
3122+
else
3123+
None
3124+
31113125
wtp.paramNames.lazyZip(wtp.paramInfos).lazyZip(args).foreach { (paramName, formal, arg) =>
31123126
arg.tpe match {
31133127
case failure: SearchFailureType =>
31143128
report.error(
3115-
missingArgMsg(arg, formal, implicitParamString(paramName, methodStr, tree)),
3116-
tree.srcPos.endPos)
3129+
missingArgMsg(arg, formal, implicitParamString(paramName, methodStr, tree), paramSymWithMethodTree(paramName)),
3130+
tree.srcPos.endPos
3131+
)
31173132
case _ =>
31183133
}
31193134
}
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)