@@ -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,189 @@ 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 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