@@ -6,12 +6,16 @@ import ast._
66import core ._
77import Types ._ , ProtoTypes ._ , Contexts ._ , Decorators ._ , Denotations ._ , Symbols ._
88import Implicits ._ , Flags ._ , Constants .Constant
9+ import Trees ._
10+ import NameOps ._
911import util .Spans ._
1012import util .SrcPos
1113import config .Feature
1214import java .util .regex .Matcher .quoteReplacement
1315import reporting ._
1416
17+ import scala .util .matching .Regex
18+
1519object ErrorReporting {
1620
1721 import tpd ._
@@ -131,14 +135,6 @@ object ErrorReporting {
131135 * all occurrences of `${X}` where `X` is in `paramNames` with the
132136 * corresponding shown type in `args`.
133137 */
134- def userDefinedErrorString (raw : String , paramNames : List [String ], args : List [Type ]): String = {
135- def translate (name : String ): Option [String ] = {
136- assert(paramNames.length == args.length)
137- val idx = paramNames.indexOf(name)
138- if (idx >= 0 ) Some (quoteReplacement(ex " ${args(idx)}" )) else None
139- }
140- """ \$\{\w*\}""" .r.replaceSomeIn(raw, m => translate(m.matched.drop(2 ).init))
141- }
142138
143139 def rewriteNotice : String =
144140 if Feature .migrateTo3 then " \n This patch can be inserted automatically under -rewrite."
@@ -180,9 +176,180 @@ object ErrorReporting {
180176 end selectErrorAddendum
181177 }
182178
179+ def substitutableTypeSymbolsInScope (sym : Symbol )(using Context ): List [Symbol ] =
180+ sym.ownersIterator.takeWhile(! _.is(Flags .Package )).flatMap { ownerSym =>
181+ ownerSym.paramSymss.flatten.filter(_.isType) ++
182+ ownerSym.typeRef.nonClassTypeMembers.map(_.symbol)
183+ }.toList
184+
183185 def dependentStr =
184186 """ Term-dependent types are experimental,
185187 |they must be enabled with a `experimental.dependent` language import or setting""" .stripMargin
186188
187189 def err (using Context ): Errors = new Errors
188190}
191+
192+
193+ class ImplicitSearchError (
194+ arg : tpd.Tree ,
195+ pt : Type ,
196+ where : String ,
197+ paramSymWithMethodCallTree : Option [(Symbol , tpd.Tree )] = None ,
198+ ignoredInstanceNormalImport : => Option [SearchSuccess ],
199+ importSuggestionAddendum : => String
200+ )(using ctx : Context ) {
201+ def missingArgMsg = arg.tpe match {
202+ case ambi : AmbiguousImplicits =>
203+ (ambi.alt1, ambi.alt2) match {
204+ case (alt @ AmbiguousImplicitMsg (msg), _) =>
205+ userDefinedAmbiguousImplicitMsg(alt, msg)
206+ case (_, alt @ AmbiguousImplicitMsg (msg)) =>
207+ userDefinedAmbiguousImplicitMsg(alt, msg)
208+ case _ =>
209+ defaultAmbiguousImplicitMsg(ambi)
210+ }
211+ case _ =>
212+ val shortMessage = userDefinedImplicitNotFoundParamMessage
213+ .orElse(userDefinedImplicitNotFoundTypeMessage)
214+ .getOrElse(defaultImplicitNotFoundMessage)
215+ formatMsg(shortMessage)() ++ hiddenImplicitsAddendum
216+ }
217+
218+ private def formatMsg (shortForm : String )(headline : String = shortForm) = arg match {
219+ case arg : Trees .SearchFailureIdent [? ] =>
220+ shortForm
221+ case _ =>
222+ arg.tpe match {
223+ case tpe : SearchFailureType =>
224+ val original = arg match
225+ case Inlined (call, _, _) => call
226+ case _ => arg
227+
228+ i """ $headline.
229+ |I found:
230+ |
231+ | ${original.show.replace(" \n " , " \n " )}
232+ |
233+ |But ${tpe.explanation}. """
234+ }
235+ }
236+
237+ private def userDefinedErrorString (raw : String , paramNames : List [String ], args : List [Type ]): String = {
238+ def translate (name : String ): Option [String ] = {
239+ val idx = paramNames.indexOf(name)
240+ if (idx >= 0 ) Some (ex " ${args(idx)}" ) else None
241+ }
242+
243+ """ \$\{\s*([^}\s]+)\s*\}""" .r.replaceAllIn(raw, (_ : Regex .Match ) match {
244+ case Regex .Groups (v) => quoteReplacement(translate(v).getOrElse(" " ))
245+ })
246+ }
247+
248+ /** Extract a user defined error message from a symbol `sym`
249+ * with an annotation matching the given class symbol `cls`.
250+ */
251+ private def userDefinedMsg (sym : Symbol , cls : Symbol ) = for {
252+ ann <- sym.getAnnotation(cls)
253+ Trees .Literal (Constant (msg : String )) <- ann.argument(0 )
254+ } yield msg
255+
256+ private def location (preposition : String ) = if (where.isEmpty) " " else s " $preposition $where"
257+
258+ private def defaultAmbiguousImplicitMsg (ambi : AmbiguousImplicits ) = {
259+ formatMsg(s " ambiguous implicit arguments: ${ambi.explanation}${location(" of" )}" )(
260+ s " ambiguous implicit arguments of type ${pt.show} found ${location(" for" )}"
261+ )
262+ }
263+
264+ private def defaultImplicitNotFoundMessage = {
265+ em " no implicit argument of type $pt was found ${location(" for" )}"
266+ }
267+
268+ /** Construct a custom error message given an ambiguous implicit
269+ * candidate `alt` and a user defined message `raw`.
270+ */
271+ private def userDefinedAmbiguousImplicitMsg (alt : SearchSuccess , raw : String ) = {
272+ val params = alt.ref.underlying match {
273+ case p : PolyType => p.paramNames.map(_.toString)
274+ case _ => Nil
275+ }
276+ def resolveTypes (targs : List [tpd.Tree ])(using Context ) =
277+ targs.map(a => Inferencing .fullyDefinedType(a.tpe, " type argument" , a.span))
278+
279+ // We can extract type arguments from:
280+ // - a function call:
281+ // @implicitAmbiguous("msg A=${A}")
282+ // implicit def f[A](): String = ...
283+ // implicitly[String] // found: f[Any]()
284+ //
285+ // - an eta-expanded function:
286+ // @implicitAmbiguous("msg A=${A}")
287+ // implicit def f[A](x: Int): String = ...
288+ // implicitly[Int => String] // found: x => f[Any](x)
289+
290+ val call = tpd.closureBody(alt.tree) // the tree itself if not a closure
291+ val (_, targs, _) = tpd.decomposeCall(call)
292+ val args = resolveTypes(targs)(using ctx.fresh.setTyperState(alt.tstate))
293+ userDefinedErrorString(raw, params, args)
294+ }
295+
296+ /** @param rawMsg Message template with variables, e.g. "Variable A is ${A}"
297+ * @param sym Symbol of the annotated type or of the method whose parameter was annotated
298+ * @param substituteType Function substituting specific types for abstract types associated with variables, e.g A -> Int
299+ */
300+ private def formatAnnotationMessage (rawMsg : String , sym : Symbol , substituteType : Type => Type ): String = {
301+ val substitutableTypesSymbols = ErrorReporting .substitutableTypeSymbolsInScope(sym)
302+
303+ userDefinedErrorString(
304+ rawMsg,
305+ paramNames = substitutableTypesSymbols.map(_.name.unexpandedName.toString),
306+ args = substitutableTypesSymbols.map(_.typeRef).map(substituteType)
307+ )
308+ }
309+
310+ /** Extracting the message from a method parameter, e.g. in
311+ *
312+ * trait Foo
313+ *
314+ * def foo(implicit @annotation.implicitNotFound("Foo is missing") foo: Foo): Any = ???
315+ */
316+ private def userDefinedImplicitNotFoundParamMessage = paramSymWithMethodCallTree.flatMap { (sym, applTree) =>
317+ userDefinedMsg(sym, defn.ImplicitNotFoundAnnot ).map { rawMsg =>
318+ val (fn, targs, _) = tpd.decomposeCall(applTree)
319+ val methodOwner = fn.symbol.owner
320+ val methodOwnerType = tpd.qualifier(fn).tpe
321+ val methodTypeParams = fn.symbol.paramSymss.flatten.filter(_.isType)
322+ val methodTypeArgs = targs.map(_.tpe)
323+ val substituteType = (_ : Type ).asSeenFrom(methodOwnerType, methodOwner).subst(methodTypeParams, methodTypeArgs)
324+ formatAnnotationMessage(rawMsg, sym.owner, substituteType)
325+ }
326+ }
327+
328+ /** Extracting the message from a type, e.g. in
329+ *
330+ * @annotation.implicitNotFound("Foo is missing")
331+ * trait Foo
332+ *
333+ * def foo(implicit foo: Foo): Any = ???
334+ */
335+ private def userDefinedImplicitNotFoundTypeMessage =
336+ val classSym = pt.classSymbol
337+ userDefinedMsg(classSym, defn.ImplicitNotFoundAnnot ).map { rawMsg =>
338+ val substituteType = (_ : Type ).asSeenFrom(pt, classSym)
339+ formatAnnotationMessage(rawMsg, classSym, substituteType)
340+ }
341+
342+ private def hiddenImplicitsAddendum : String =
343+ def hiddenImplicitNote (s : SearchSuccess ) =
344+ em " \n\n Note: given instance ${s.ref.symbol.showLocated} was not considered because it was not imported with `import given`. "
345+
346+ val normalImports = ignoredInstanceNormalImport.map(hiddenImplicitNote)
347+
348+ normalImports.getOrElse(importSuggestionAddendum)
349+ end hiddenImplicitsAddendum
350+
351+ private object AmbiguousImplicitMsg {
352+ def unapply (search : SearchSuccess ): Option [String ] =
353+ userDefinedMsg(search.ref.symbol, defn.ImplicitAmbiguousAnnot )
354+ }
355+ }
0 commit comments