@@ -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,189 @@ object ErrorReporting {
180176 end selectErrorAddendum
181177 }
182178
179+ def substitutableTypeSymbolsInScope (sym : Symbol )(using Context ): List [Symbol ] =
180+ sym.ownersIterator.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 methodOwner = applTree.symbol.owner
319+ val methodOwnerType : Type = applTree match {
320+ case Select (qual, _) => qual.tpe
321+ case TypeApply (Select (qual, _), _) => qual.tpe
322+ case _ => methodOwner.typeRef
323+ }
324+ val substituteTypesFromMethodOwner = (_ : Type ).asSeenFrom(methodOwnerType, methodOwner)
325+ val substituteMethodTypeParams : Type => Type = applTree match {
326+ case TypeApply (_, targs) =>
327+ val methodTypeParams = applTree.symbol.paramSymss.flatten.filter(_.isType)
328+ val methodTypeArgs = targs.map(_.tpe)
329+ _.subst(methodTypeParams, methodTypeArgs)
330+ case _ =>
331+ identity
332+ }
333+
334+ val substituteType = substituteTypesFromMethodOwner andThen substituteMethodTypeParams
335+ formatAnnotationMessage(rawMsg, sym.owner, substituteType)
336+ }
337+ }
338+
339+ /** Extracting the message from a type, e.g. in
340+ *
341+ * @annotation.implicitNotFound("Foo is missing")
342+ * trait Foo
343+ *
344+ * def foo(implicit foo: Foo): Any = ???
345+ */
346+ private def userDefinedImplicitNotFoundTypeMessage = userDefinedMsg(pt.typeSymbol, defn.ImplicitNotFoundAnnot ).map { rawMsg =>
347+ val substituteType = (_ : Type ).asSeenFrom(pt, pt.typeSymbol)
348+ formatAnnotationMessage(rawMsg, pt.typeSymbol, substituteType)
349+ }
350+
351+ private def hiddenImplicitsAddendum : String =
352+ def hiddenImplicitNote (s : SearchSuccess ) =
353+ em " \n\n Note: given instance ${s.ref.symbol.showLocated} was not considered because it was not imported with `import given`. "
354+
355+ val normalImports = ignoredInstanceNormalImport.map(hiddenImplicitNote)
356+
357+ normalImports.getOrElse(importSuggestionAddendum)
358+ end hiddenImplicitsAddendum
359+
360+ private object AmbiguousImplicitMsg {
361+ def unapply (search : SearchSuccess ): Option [String ] =
362+ userDefinedMsg(search.ref.symbol, defn.ImplicitAmbiguousAnnot )
363+ }
364+ }
0 commit comments