@@ -15,16 +15,19 @@ import printing.Formatting
15
15
import ErrorMessageID ._
16
16
import ast .Trees
17
17
import config .{Feature , ScalaVersion }
18
- import typer .ErrorReporting .{err , matchReductionAddendum }
18
+ import typer .ErrorReporting .{err , matchReductionAddendum , substitutableTypeSymbolsInScope }
19
19
import typer .ProtoTypes .ViewProto
20
- import typer .Implicits .Candidate
20
+ import typer .Implicits .*
21
+ import typer .Inferencing
21
22
import scala .util .control .NonFatal
22
23
import StdNames .nme
23
24
import printing .Formatting .hl
24
25
import ast .Trees ._
25
26
import ast .untpd
26
27
import ast .tpd
27
28
import transform .SymUtils ._
29
+ import scala .util .matching .Regex
30
+ import java .util .regex .Matcher .quoteReplacement
28
31
import cc .CaptureSet .IdentityCaptRefMap
29
32
30
33
/** Messages
@@ -2533,3 +2536,193 @@ import cc.CaptureSet.IdentityCaptRefMap
2533
2536
def msg = ex " $tp is not a class type "
2534
2537
def explain = " "
2535
2538
2539
+ class MissingImplicitArgument (
2540
+ arg : tpd.Tree ,
2541
+ pt : Type ,
2542
+ where : String ,
2543
+ paramSymWithMethodCallTree : Option [(Symbol , tpd.Tree )] = None ,
2544
+ ignoredInstanceNormalImport : => Option [SearchSuccess ],
2545
+ importSuggestionAddendum : Type => String
2546
+ )(using ctx : Context ) extends TypeMsg (MissingImplicitArgumentID ):
2547
+
2548
+ def msg : String =
2549
+
2550
+ def formatMsg (shortForm : String )(headline : String = shortForm) = arg match
2551
+ case arg : Trees .SearchFailureIdent [? ] =>
2552
+ arg.tpe match
2553
+ case _ : NoMatchingImplicits => headline
2554
+ case tpe : SearchFailureType =>
2555
+ i " $headline. ${tpe.explanation}"
2556
+ case _ => headline
2557
+ case _ =>
2558
+ arg.tpe match
2559
+ case tpe : SearchFailureType =>
2560
+ val original = arg match
2561
+ case Inlined (call, _, _) => call
2562
+ case _ => arg
2563
+ i """ $headline.
2564
+ |I found:
2565
+ |
2566
+ | ${original.show.replace(" \n " , " \n " )}
2567
+ |
2568
+ |But ${tpe.explanation}. """
2569
+ case _ => headline
2570
+
2571
+ /** Format `raw` implicitNotFound or implicitAmbiguous argument, replacing
2572
+ * all occurrences of `${X}` where `X` is in `paramNames` with the
2573
+ * corresponding shown type in `args`.
2574
+ */
2575
+ def userDefinedErrorString (raw : String , paramNames : List [String ], args : List [Type ]): String = {
2576
+ def translate (name : String ): Option [String ] = {
2577
+ val idx = paramNames.indexOf(name)
2578
+ if (idx >= 0 ) Some (ex " ${args(idx)}" ) else None
2579
+ }
2580
+
2581
+ """ \$\{\s*([^}\s]+)\s*\}""" .r.replaceAllIn(raw, (_ : Regex .Match ) match {
2582
+ case Regex .Groups (v) => quoteReplacement(translate(v).getOrElse(" " )).nn
2583
+ })
2584
+ }
2585
+
2586
+ /** Extract a user defined error message from a symbol `sym`
2587
+ * with an annotation matching the given class symbol `cls`.
2588
+ */
2589
+ def userDefinedMsg (sym : Symbol , cls : Symbol ) = for {
2590
+ ann <- sym.getAnnotation(cls)
2591
+ msg <- ann.argumentConstantString(0 )
2592
+ } yield msg
2593
+
2594
+ def location (preposition : String ) = if (where.isEmpty) " " else s " $preposition $where"
2595
+
2596
+ def defaultAmbiguousImplicitMsg (ambi : AmbiguousImplicits ) =
2597
+ s " Ambiguous given instances: ${ambi.explanation}${location(" of" )}"
2598
+
2599
+ def defaultImplicitNotFoundMessage =
2600
+ ex " No given instance of type $pt was found ${location(" for" )}"
2601
+
2602
+ /** Construct a custom error message given an ambiguous implicit
2603
+ * candidate `alt` and a user defined message `raw`.
2604
+ */
2605
+ def userDefinedAmbiguousImplicitMsg (alt : SearchSuccess , raw : String ) = {
2606
+ val params = alt.ref.underlying match {
2607
+ case p : PolyType => p.paramNames.map(_.toString)
2608
+ case _ => Nil
2609
+ }
2610
+ def resolveTypes (targs : List [tpd.Tree ])(using Context ) =
2611
+ targs.map(a => Inferencing .fullyDefinedType(a.tpe, " type argument" , a.srcPos))
2612
+
2613
+ // We can extract type arguments from:
2614
+ // - a function call:
2615
+ // @implicitAmbiguous("msg A=${A}")
2616
+ // implicit def f[A](): String = ...
2617
+ // implicitly[String] // found: f[Any]()
2618
+ //
2619
+ // - an eta-expanded function:
2620
+ // @implicitAmbiguous("msg A=${A}")
2621
+ // implicit def f[A](x: Int): String = ...
2622
+ // implicitly[Int => String] // found: x => f[Any](x)
2623
+
2624
+ val call = tpd.closureBody(alt.tree) // the tree itself if not a closure
2625
+ val targs = tpd.typeArgss(call).flatten
2626
+ val args = resolveTypes(targs)(using ctx.fresh.setTyperState(alt.tstate))
2627
+ userDefinedErrorString(raw, params, args)
2628
+ }
2629
+
2630
+ /** @param rawMsg Message template with variables, e.g. "Variable A is ${A}"
2631
+ * @param sym Symbol of the annotated type or of the method whose parameter was annotated
2632
+ * @param substituteType Function substituting specific types for abstract types associated with variables, e.g A -> Int
2633
+ */
2634
+ def formatAnnotationMessage (rawMsg : String , sym : Symbol , substituteType : Type => Type ): String = {
2635
+ val substitutableTypesSymbols = substitutableTypeSymbolsInScope(sym)
2636
+
2637
+ userDefinedErrorString(
2638
+ rawMsg,
2639
+ paramNames = substitutableTypesSymbols.map(_.name.unexpandedName.toString),
2640
+ args = substitutableTypesSymbols.map(_.typeRef).map(substituteType)
2641
+ )
2642
+ }
2643
+
2644
+ /** Extracting the message from a method parameter, e.g. in
2645
+ *
2646
+ * trait Foo
2647
+ *
2648
+ * def foo(implicit @annotation.implicitNotFound("Foo is missing") foo: Foo): Any = ???
2649
+ */
2650
+ def userDefinedImplicitNotFoundParamMessage : Option [String ] = paramSymWithMethodCallTree.flatMap { (sym, applTree) =>
2651
+ userDefinedMsg(sym, defn.ImplicitNotFoundAnnot ).map { rawMsg =>
2652
+ val fn = tpd.funPart(applTree)
2653
+ val targs = tpd.typeArgss(applTree).flatten
2654
+ val methodOwner = fn.symbol.owner
2655
+ val methodOwnerType = tpd.qualifier(fn).tpe
2656
+ val methodTypeParams = fn.symbol.paramSymss.flatten.filter(_.isType)
2657
+ val methodTypeArgs = targs.map(_.tpe)
2658
+ val substituteType = (_ : Type ).asSeenFrom(methodOwnerType, methodOwner).subst(methodTypeParams, methodTypeArgs)
2659
+ formatAnnotationMessage(rawMsg, sym.owner, substituteType)
2660
+ }
2661
+ }
2662
+
2663
+ /** Extracting the message from a type, e.g. in
2664
+ *
2665
+ * @annotation.implicitNotFound("Foo is missing")
2666
+ * trait Foo
2667
+ *
2668
+ * def foo(implicit foo: Foo): Any = ???
2669
+ */
2670
+ def userDefinedImplicitNotFoundTypeMessage : Option [String ] =
2671
+ def recur (tp : Type ): Option [String ] = tp match
2672
+ case tp : TypeRef =>
2673
+ val sym = tp.symbol
2674
+ userDefinedImplicitNotFoundTypeMessageFor(sym).orElse(recur(tp.info))
2675
+ case tp : ClassInfo =>
2676
+ tp.baseClasses.iterator
2677
+ .map(userDefinedImplicitNotFoundTypeMessageFor)
2678
+ .find(_.isDefined).flatten
2679
+ case tp : TypeProxy =>
2680
+ recur(tp.superType)
2681
+ case tp : AndType =>
2682
+ recur(tp.tp1).orElse(recur(tp.tp2))
2683
+ case _ =>
2684
+ None
2685
+ recur(pt)
2686
+
2687
+ def userDefinedImplicitNotFoundTypeMessageFor (sym : Symbol ): Option [String ] =
2688
+ for
2689
+ rawMsg <- userDefinedMsg(sym, defn.ImplicitNotFoundAnnot )
2690
+ if Feature .migrateTo3 || sym != defn.Function1
2691
+ // Don't inherit "No implicit view available..." message if subtypes of Function1 are not treated as implicit conversions anymore
2692
+ yield
2693
+ val substituteType = (_ : Type ).asSeenFrom(pt, sym)
2694
+ formatAnnotationMessage(rawMsg, sym, substituteType)
2695
+
2696
+ def hiddenImplicitsAddendum : String =
2697
+ def hiddenImplicitNote (s : SearchSuccess ) =
2698
+ i " \n\n Note: ${s.ref.symbol.showLocated} was not considered because it was not imported with `import given`. "
2699
+ val normalImports = ignoredInstanceNormalImport.map(hiddenImplicitNote)
2700
+ normalImports.getOrElse(importSuggestionAddendum(pt))
2701
+
2702
+ object AmbiguousImplicitMsg {
2703
+ def unapply (search : SearchSuccess ): Option [String ] =
2704
+ userDefinedMsg(search.ref.symbol, defn.ImplicitAmbiguousAnnot )
2705
+ }
2706
+
2707
+ arg.tpe match
2708
+ case ambi : AmbiguousImplicits =>
2709
+ (ambi.alt1, ambi.alt2) match
2710
+ case (alt @ AmbiguousImplicitMsg (msg), _) =>
2711
+ userDefinedAmbiguousImplicitMsg(alt, msg)
2712
+ case (_, alt @ AmbiguousImplicitMsg (msg)) =>
2713
+ userDefinedAmbiguousImplicitMsg(alt, msg)
2714
+ case _ =>
2715
+ defaultAmbiguousImplicitMsg(ambi)
2716
+ case ambi @ TooUnspecific (target) =>
2717
+ ex """ No implicit search was attempted ${location(" for" )}
2718
+ |since the expected type $target is not specific enough """
2719
+ case _ =>
2720
+ val shortMessage = userDefinedImplicitNotFoundParamMessage
2721
+ .orElse(userDefinedImplicitNotFoundTypeMessage)
2722
+ .getOrElse(defaultImplicitNotFoundMessage)
2723
+ formatMsg(shortMessage)()
2724
+ ++ hiddenImplicitsAddendum
2725
+ ++ matchReductionAddendum(pt)
2726
+ end msg
2727
+ def explain = " "
2728
+ end MissingImplicitArgument
0 commit comments