@@ -6,12 +6,16 @@ import ast._
6
6
import core ._
7
7
import Types ._ , ProtoTypes ._ , Contexts ._ , Decorators ._ , Denotations ._ , Symbols ._
8
8
import Implicits ._ , Flags ._ , Constants .Constant
9
+ import Trees ._
10
+ import NameOps ._
9
11
import util .Spans ._
10
12
import util .SrcPos
11
13
import config .Feature
12
14
import java .util .regex .Matcher .quoteReplacement
13
15
import reporting ._
14
16
17
+ import scala .util .matching .Regex
18
+
15
19
object ErrorReporting {
16
20
17
21
import tpd ._
@@ -131,14 +135,6 @@ object ErrorReporting {
131
135
* all occurrences of `${X}` where `X` is in `paramNames` with the
132
136
* corresponding shown type in `args`.
133
137
*/
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
- }
142
138
143
139
def rewriteNotice : String =
144
140
if Feature .migrateTo3 then " \n This patch can be inserted automatically under -rewrite."
@@ -180,9 +176,180 @@ object ErrorReporting {
180
176
end selectErrorAddendum
181
177
}
182
178
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
+
183
185
def dependentStr =
184
186
""" Term-dependent types are experimental,
185
187
|they must be enabled with a `experimental.dependent` language import or setting""" .stripMargin
186
188
187
189
def err (using Context ): Errors = new Errors
188
190
}
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