Skip to content

Commit 76c4ef4

Browse files
authored
Merge pull request scala#6340 from lrytz/implicitNotFoundParam
Support implicitNotFound on parameters
2 parents 5d88875 + e907e31 commit 76c4ef4

File tree

7 files changed

+155
-25
lines changed

7 files changed

+155
-25
lines changed

src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -143,15 +143,15 @@ trait ContextErrors {
143143
def errMsg = {
144144
val paramName = param.name
145145
val paramTp = param.tpe
146-
def evOrParam = (
146+
def evOrParam =
147147
if (paramName startsWith nme.EVIDENCE_PARAM_PREFIX)
148148
"evidence parameter of type"
149149
else
150-
s"parameter $paramName:")
151-
paramTp.typeSymbolDirect match {
152-
case ImplicitNotFoundMsg(msg) => msg.format(paramName, paramTp)
153-
case _ => s"could not find implicit value for $evOrParam $paramTp"
154-
}
150+
s"parameter $paramName:"
151+
152+
ImplicitNotFoundMsg.unapply(param).map(_.formatParameterMessage(tree))
153+
.orElse(ImplicitNotFoundMsg.unapply(paramTp.typeSymbolDirect).map(_.formatDefSiteMessage(paramTp)))
154+
.getOrElse(s"could not find implicit value for $evOrParam $paramTp")
155155
}
156156
issueNormalTypeError(tree, errMsg)
157157
}
@@ -1385,8 +1385,8 @@ trait ContextErrors {
13851385

13861386
context0.issueAmbiguousError(AmbiguousImplicitTypeError(tree,
13871387
(info1.sym, info2.sym) match {
1388-
case (ImplicitAmbiguousMsg(msg), _) => msg.format(treeTypeArgs(tree1))
1389-
case (_, ImplicitAmbiguousMsg(msg)) => msg.format(treeTypeArgs(tree2))
1388+
case (ImplicitAmbiguousMsg(msg), _) => msg.formatDefSiteMessage(treeTypeArgs(tree1))
1389+
case (_, ImplicitAmbiguousMsg(msg)) => msg.formatDefSiteMessage(treeTypeArgs(tree2))
13901390
case (_, _) if isView => viewMsg
13911391
case (_, _) => s"ambiguous implicit values:\n${coreMsg}match expected type $pt"
13921392
}

src/compiler/scala/tools/nsc/typechecker/Implicits.scala

Lines changed: 59 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1595,7 +1595,7 @@ trait Implicits {
15951595
}
15961596

15971597
class ImplicitAnnotationMsg(f: Symbol => Option[String], clazz: Symbol, annotationName: String) {
1598-
def unapply(sym: Symbol): Option[(Message)] = f(sym) match {
1598+
def unapply(sym: Symbol): Option[Message] = f(sym) match {
15991599
case Some(m) => Some(new Message(sym, m, annotationName))
16001600
case None if sym.isAliasType =>
16011601
// perform exactly one step of dealiasing
@@ -1628,25 +1628,75 @@ trait Implicits {
16281628
// #3915: need to quote replacement string since it may include $'s (such as the interpreter's $iw)
16291629
})
16301630

1631-
private lazy val typeParamNames: List[String] = sym.typeParams.map(_.decodedName)
1631+
def referencedTypeParams: List[String] = Intersobralator.findAllMatchIn(msg).map(_.group(1)).distinct.toList
1632+
1633+
private def symTypeParamNames: List[String] = sym.typeParams.map(_.decodedName)
1634+
1635+
def lookupTypeParam(name: String): Symbol = {
1636+
val n = newTypeName(name)
1637+
var r: Symbol = NoSymbol
1638+
var o = sym.owner
1639+
while (r == NoSymbol && o != NoSymbol) {
1640+
o.typeParams.find(_.name == n) match {
1641+
case Some(p) => r = p
1642+
case _ =>
1643+
do { o = o.owner } while (!(o.isClass || o.isMethod || o == NoSymbol))
1644+
}
1645+
}
1646+
r
1647+
}
1648+
16321649
private def typeArgsAtSym(paramTp: Type) = paramTp.baseType(sym).typeArgs
16331650

1634-
def format(paramName: Name, paramTp: Type): String = format(typeArgsAtSym(paramTp) map (_.toString))
1651+
def formatDefSiteMessage(paramTp: Type): String =
1652+
formatDefSiteMessage(typeArgsAtSym(paramTp) map (_.toString))
16351653

1636-
def format(typeArgs: List[String]): String =
1637-
interpolate(msg, Map((typeParamNames zip typeArgs): _*)) // TODO: give access to the name and type of the implicit argument, etc?
1654+
def formatDefSiteMessage(typeArgs: List[String]): String =
1655+
interpolate(msg, Map(symTypeParamNames zip typeArgs: _*))
1656+
1657+
def formatParameterMessage(fun: Tree): String = {
1658+
val paramNames = referencedTypeParams
1659+
val paramSyms = paramNames.map(lookupTypeParam).filterNot(_ == NoSymbol)
1660+
val paramTypeRefs = paramSyms.map(_.typeConstructor.etaExpand) // make polytypes for type constructors -- we'll abbreviate them below
1661+
val prefix = fun match {
1662+
case treeInfo.Applied(Select(qual, _), _, _) => qual.tpe
1663+
case _ => NoType
1664+
}
1665+
1666+
val argTypes1 = if (prefix == NoType) paramTypeRefs else paramTypeRefs.map(t => t.asSeenFrom(prefix, fun.symbol.owner))
1667+
val argTypes2 = fun match {
1668+
case TypeApply(_, targs) => argTypes1.map(_.instantiateTypeParams(fun.symbol.info.typeParams, targs.map(_.tpe)))
1669+
case _ => argTypes1
1670+
}
1671+
1672+
val argTypes = argTypes2.map{
1673+
case PolyType(tps, tr@TypeRef(_, _, tprefs)) =>
1674+
if (tps.corresponds(tprefs)((p, r) => p == r.typeSymbol)) tr.typeConstructor.toString
1675+
else {
1676+
val freshTpars = tps.mapConserve { case p if p.name == tpnme.WILDCARD => p.cloneSymbol.setName(newTypeName("?T" + tps.indexOf(p))) case p => p }
1677+
freshTpars.map(_.name).mkString("[", ", ", "] -> ") + tr.instantiateTypeParams(tps, freshTpars.map(_.typeConstructor)).toString
1678+
}
1679+
1680+
case tp => tp.toString
1681+
}
1682+
interpolate(msg, Map(paramNames zip argTypes: _*))
1683+
}
16381684

16391685
def validate: Option[String] = {
1640-
val refs = Intersobralator.findAllMatchIn(msg).map(_ group 1).toSeq.distinct
1641-
val decls = typeParamNames.toSeq.distinct
1686+
val refs = referencedTypeParams
1687+
val isMessageOnParameter = sym.isParameter
1688+
val decls =
1689+
if (isMessageOnParameter) referencedTypeParams.filterNot(p => lookupTypeParam(p) == NoSymbol)
1690+
else symTypeParamNames.distinct
16421691

1643-
(refs.diff(decls)) match {
1692+
refs.diff(decls) match {
16441693
case s if s.isEmpty => None
16451694
case unboundNames =>
16461695
val singular = unboundNames.size == 1
16471696
val ess = if (singular) "" else "s"
16481697
val bee = if (singular) "is" else "are"
1649-
Some(s"The type parameter$ess ${unboundNames mkString ", "} referenced in the message of the @$annotationName annotation $bee not defined by $sym.")
1698+
val where = if (isMessageOnParameter) s"in scope" else s"defined by $sym"
1699+
Some(s"The type parameter$ess ${unboundNames mkString ", "} referenced in the message of the @$annotationName annotation $bee not $where.")
16501700
}
16511701
}
16521702
}

src/library/scala/annotation/implicitNotFound.scala

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,44 @@ package scala.annotation
1010

1111
/**
1212
* To customize the error message that's emitted when an implicit of type
13-
* C[T1,..., TN] cannot be found, annotate the class C with @implicitNotFound.
14-
* Assuming C has type parameters X1,..., XN, the error message will be the
15-
* result of replacing all occurrences of ${Xi} in the string msg with the
16-
* string representation of the corresponding type argument Ti. *
13+
* `C[T1,..., TN]` cannot be found, annotate the class `C` with `@implicitNotFound`.
14+
* Assuming `C` has type parameters `X1, ..., XN`, the error message will be the
15+
* result of replacing all occurrences of `${Xi}` in the string `msg` with the
16+
* string representation of the corresponding type argument `Ti`.
17+
*
18+
* The annotation can also be attached to implicit parameters. In this case, `${Xi}`
19+
* can refer to type parameters in the current scope. The `@implicitNotFound` message
20+
* on the parameter takes precedence over the one on the parameter's type.
21+
*
22+
* {{{
23+
* import scala.annotation.implicitNotFound
24+
*
25+
* @implicitNotFound("Could not find an implicit C[${T}, ${U}]")
26+
* class C[T, U]
27+
*
28+
* class K[A] {
29+
* def m[B](implicit c: C[List[A], B]) = 0
30+
* def n[B](implicit @implicitNotFound("Specific message for C of list of ${A} and ${B}") c: C[List[A], B]) = 1
31+
* }
32+
*
33+
* object Test {
34+
* val k = new K[Int]
35+
* k.m[String]
36+
* k.n[String]
37+
* }
38+
* }}}
39+
*
40+
* The compiler issues the following error messages:
41+
*
42+
* <pre>
43+
* Test.scala:13: error: Could not find an implicit C[List[Int], String]
44+
* k.m[String]
45+
* ^
46+
* Test.scala:14: error: Specific message for C of list of Int and String
47+
* k.n[String]
48+
* ^
49+
* </pre>
50+
*
1751
*
1852
* @author Adriaan Moors
1953
* @since 2.8.1

test/files/neg/t2462b.check

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,15 @@ t2462b.scala:9: warning: Invalid implicitNotFound message for trait Meh2 in pack
66
The type parameter Elem referenced in the message of the @implicitNotFound annotation is not defined by trait Meh2.
77
trait Meh2[-From, +To]
88
^
9+
t2462b.scala:13: warning: Invalid implicitNotFound message for value theC:
10+
The type parameter Uuh referenced in the message of the @implicitNotFound annotation is not in scope.
11+
def m[Aaa](implicit @implicitNotFound("I see no C[${Uuh}]") theC: C[Aaa]) = ???
12+
^
13+
t2462b.scala:19: warning: Invalid implicitNotFound message for value i:
14+
The type parameters XX, ZZ, Nix referenced in the message of the @implicitNotFound annotation are not in scope.
15+
def m[S](implicit @implicitNotFound("${X} ${Y} ${ Z } ${R} ${S} -- ${XX} ${ZZ} ${ Nix }") i: Int) = ???
16+
^
17+
warning: there were two feature warnings; re-run with -feature for details
918
error: No warnings can be incurred under -Xfatal-warnings.
10-
two warnings found
19+
5 warnings found
1120
one error found

test/files/neg/t2462b.scala

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,15 @@ trait Meh[-From, +To]
77

88
@implicitNotFound(msg = "Cannot construct a collection of type ${To} ${Elem}.")
99
trait Meh2[-From, +To]
10+
11+
class C[T]
12+
trait T {
13+
def m[Aaa](implicit @implicitNotFound("I see no C[${Uuh}]") theC: C[Aaa]) = ???
14+
def n[Aaa](implicit @implicitNotFound("I see no C[${Aaa}]") theC: C[Aaa]) = ???
15+
}
16+
17+
trait U[X, Y[_], Z[_, ZZ]] {
18+
class I[R] {
19+
def m[S](implicit @implicitNotFound("${X} ${Y} ${ Z } ${R} ${S} -- ${XX} ${ZZ} ${ Nix }") i: Int) = ???
20+
}
21+
}

test/files/neg/t2462c.check

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,16 @@
1-
t2462c.scala:18: error: No C of X$Y
1+
t2462c.scala:24: error: No C of X$Y
22
f[X$Y]
33
^
4-
t2462c.scala:24: error: No C of Foo[Int]
4+
t2462c.scala:30: error: No C of Foo[Int]
55
f[Foo[Int]]
66
^
7-
two errors found
7+
t2462c.scala:33: error: No C of Foo[Int]
8+
g[Foo[Int]]
9+
^
10+
t2462c.scala:36: error: I see no C[Foo[Int]]
11+
h[Foo[Int]]
12+
^
13+
t2462c.scala:40: error: String List [?T0, ZZ] -> List[C[_]] Int Option[Long] -- .
14+
i.m[Option[Long]]
15+
^
16+
5 errors found

test/files/neg/t2462c.scala

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,12 @@ trait X$$$$Y
1313

1414
trait Foo[A]
1515

16+
trait U[X, Y[_], Z[_, ZZ]] {
17+
class I[R] {
18+
def m[S](implicit @implicitNotFound("${X} ${Y} ${ Z } ${R} ${S} -- ${XX}.") i: Int) = ???
19+
}
20+
}
21+
1622
class Test {
1723
def f[A: C] = ???
1824
f[X$Y]
@@ -22,4 +28,14 @@ class Test {
2228
f[X$$$$Y]
2329
*/
2430
f[Foo[Int]]
31+
32+
def g[Aaa](implicit theC: C[Aaa]) = ???
33+
g[Foo[Int]]
34+
35+
def h[Aaa](implicit @implicitNotFound("I see no C[${Aaa}]") theC: C[Aaa]) = ???
36+
h[Foo[Int]]
37+
38+
val u = new U[String, List, ({type T[A, _] = List[C[_]]})#T] { }
39+
val i = new u.I[Int]
40+
i.m[Option[Long]]
2541
}

0 commit comments

Comments
 (0)