Skip to content

Commit e64014f

Browse files
committed
Turn more strings into messages
In particular: The missingArg message of ImplicitSearchError becomes a regular Message now.
1 parent 77dbd3b commit e64014f

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

59 files changed

+326
-328
lines changed

compiler/src/dotty/tools/dotc/inlines/Inlines.scala

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -442,8 +442,7 @@ object Inlines:
442442
val evidence = evTyper.inferImplicitArg(tpt.tpe, tpt.span)
443443
evidence.tpe match
444444
case fail: Implicits.SearchFailureType =>
445-
val msg = evTyper.missingArgMsg(evidence, tpt.tpe, "")
446-
errorTree(call, em"$msg")
445+
errorTree(call, evTyper.missingArgMsg(evidence, tpt.tpe, ""))
447446
case _ =>
448447
evidence
449448
}

compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,7 @@ enum ErrorMessageID(val isActive: Boolean = true) extends java.lang.Enum[ErrorMe
185185
case TargetNameOnTopLevelClassID // errorNumber: 169
186186
case NotClassTypeID // errorNumber 170
187187
case MissingArgumentID // errorNumer 171
188+
case MissingImplicitArgumentID // errorNumber 172
188189

189190
def errorNumber = ordinal - 1
190191

compiler/src/dotty/tools/dotc/reporting/messages.scala

Lines changed: 195 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,19 @@ import printing.Formatting
1515
import ErrorMessageID._
1616
import ast.Trees
1717
import config.{Feature, ScalaVersion}
18-
import typer.ErrorReporting.{err, matchReductionAddendum}
18+
import typer.ErrorReporting.{err, matchReductionAddendum, substitutableTypeSymbolsInScope}
1919
import typer.ProtoTypes.ViewProto
20-
import typer.Implicits.Candidate
20+
import typer.Implicits.*
21+
import typer.Inferencing
2122
import scala.util.control.NonFatal
2223
import StdNames.nme
2324
import printing.Formatting.hl
2425
import ast.Trees._
2526
import ast.untpd
2627
import ast.tpd
2728
import transform.SymUtils._
29+
import scala.util.matching.Regex
30+
import java.util.regex.Matcher.quoteReplacement
2831
import cc.CaptureSet.IdentityCaptRefMap
2932

3033
/** Messages
@@ -2533,3 +2536,193 @@ import cc.CaptureSet.IdentityCaptRefMap
25332536
def msg = ex"$tp is not a class type"
25342537
def explain = ""
25352538

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\nNote: ${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

compiler/src/dotty/tools/dotc/transform/PCPCheckAndHeal.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,7 @@ class PCPCheckAndHeal(@constructorOnly ictx: Context) extends TreeMapWithStages(
247247
getQuoteTypeTags.getTagRef(tp)
248248
case _: SearchFailureType =>
249249
report.error(i"""Reference to $tp within quotes requires a given $reqType in scope.
250-
|${ctx.typer.missingArgMsg(tag, reqType, "")}
250+
|${ctx.typer.missingArgMsg(tag, reqType, "").message}
251251
|
252252
|""", pos)
253253
tp

compiler/src/dotty/tools/dotc/typer/Applications.scala

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -478,7 +478,7 @@ trait Applications extends Compatibility {
478478
matchArgs(orderedArgs, methType.paramInfos, 0)
479479
case _ =>
480480
if (methType.isError) ok = false
481-
else fail(s"$methString does not take parameters".toMessage)
481+
else fail(em"$methString does not take parameters")
482482
}
483483

484484
/** The application was successful */
@@ -518,10 +518,10 @@ trait Applications extends Compatibility {
518518
else { // name not (or no longer) available for named arg
519519
def msg =
520520
if (methodType.paramNames contains aname)
521-
s"parameter $aname of $methString is already instantiated"
521+
em"parameter $aname of $methString is already instantiated"
522522
else
523-
s"$methString does not have a parameter $aname"
524-
fail(msg.toMessage, arg.asInstanceOf[Arg])
523+
em"$methString does not have a parameter $aname"
524+
fail(msg, arg.asInstanceOf[Arg])
525525
arg :: handleNamed(pnamesRest, args1, nameToArg, toDrop)
526526
}
527527
case arg :: args1 =>
@@ -647,10 +647,10 @@ trait Applications extends Compatibility {
647647
def msg = arg match
648648
case untpd.Tuple(Nil)
649649
if applyKind == ApplyKind.InfixTuple && funType.widen.isNullaryMethod =>
650-
i"can't supply unit value with infix notation because nullary $methString takes no arguments; use dotted invocation instead: (...).${methRef.name}()"
650+
em"can't supply unit value with infix notation because nullary $methString takes no arguments; use dotted invocation instead: (...).${methRef.name}()"
651651
case _ =>
652-
i"too many arguments for $methString"
653-
fail(msg.toMessage, arg)
652+
em"too many arguments for $methString"
653+
fail(msg, arg)
654654
case nil =>
655655
}
656656
}

compiler/src/dotty/tools/dotc/typer/Checking.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -843,9 +843,9 @@ trait Checking {
843843
case Select(id, _) => id
844844
case _ => EmptyTree
845845
if extractor.isEmpty then
846-
e"pattern binding uses refutable extractor"
846+
i"pattern binding uses refutable extractor"
847847
else
848-
e"pattern binding uses refutable extractor `$extractor`"
848+
i"pattern binding uses refutable extractor `$extractor`"
849849

850850
val fix =
851851
if isPatDef then "adding `: @unchecked` after the expression"

0 commit comments

Comments
 (0)