Skip to content

Commit fd91ce1

Browse files
Allow @implicitNotFound messages as explanations (#16893)
A problem of the @implicitNotFOund mechanism so far was that the user defined message replaced the compiler-generated one, which might lose valuable information. This commit adds an alternative where an @implicitNotFound message that starts with `...` is taken as an explanation (without the ...) enabled under -explain. The compiler-generated message is then kept as the explicit error message. We apply the mechanism for an @implicitNotFound message for `boundary.Label`. This now produces messages like this one: ``` -- [E172] Type Error: tests/neg-custom-args/explain/labelNotFound.scala:2:30 ------------------------------------------- 2 | scala.util.boundary.break(1) // error | ^ |No given instance of type scala.util.boundary.Label[Int] was found for parameter label of method break in object boundary |--------------------------------------------------------------------------------------------------------------------- | Explanation (enabled by `-explain`) |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | A Label is generated from an enclosing `scala.util.boundary` call. | Maybe that boundary is missing? --------------------------------------------------------------------------------------------------------------------- ```
2 parents c466fa0 + 4b83f1f commit fd91ce1

File tree

19 files changed

+183
-127
lines changed

19 files changed

+183
-127
lines changed

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

Lines changed: 104 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -2573,6 +2573,107 @@ class MissingImplicitArgument(
25732573
case ambi: AmbiguousImplicits => withoutDisambiguation()
25742574
case _ =>
25752575

2576+
/** Format `raw` implicitNotFound or implicitAmbiguous argument, replacing
2577+
* all occurrences of `${X}` where `X` is in `paramNames` with the
2578+
* corresponding shown type in `args`.
2579+
*/
2580+
def userDefinedErrorString(raw: String, paramNames: List[String], args: List[Type])(using Context): String =
2581+
def translate(name: String): Option[String] =
2582+
val idx = paramNames.indexOf(name)
2583+
if (idx >= 0) Some(i"${args(idx)}") else None
2584+
"""\$\{\s*([^}\s]+)\s*\}""".r.replaceAllIn(raw, (_: Regex.Match) match
2585+
case Regex.Groups(v) => quoteReplacement(translate(v).getOrElse("")).nn
2586+
)
2587+
2588+
/** @param rawMsg Message template with variables, e.g. "Variable A is ${A}"
2589+
* @param sym Symbol of the annotated type or of the method whose parameter was annotated
2590+
* @param substituteType Function substituting specific types for abstract types associated with variables, e.g A -> Int
2591+
*/
2592+
def formatAnnotationMessage(rawMsg: String, sym: Symbol, substituteType: Type => Type)(using Context): String =
2593+
val substitutableTypesSymbols = substitutableTypeSymbolsInScope(sym)
2594+
userDefinedErrorString(
2595+
rawMsg,
2596+
paramNames = substitutableTypesSymbols.map(_.name.unexpandedName.toString),
2597+
args = substitutableTypesSymbols.map(_.typeRef).map(substituteType)
2598+
)
2599+
2600+
/** Extract a user defined error message from a symbol `sym`
2601+
* with an annotation matching the given class symbol `cls`.
2602+
*/
2603+
def userDefinedMsg(sym: Symbol, cls: Symbol)(using Context) =
2604+
for
2605+
ann <- sym.getAnnotation(cls)
2606+
msg <- ann.argumentConstantString(0)
2607+
yield msg
2608+
2609+
def userDefinedImplicitNotFoundTypeMessageFor(sym: Symbol)(using Context): Option[String] =
2610+
for
2611+
rawMsg <- userDefinedMsg(sym, defn.ImplicitNotFoundAnnot)
2612+
if Feature.migrateTo3 || sym != defn.Function1
2613+
// Don't inherit "No implicit view available..." message if subtypes of Function1 are not treated as implicit conversions anymore
2614+
yield
2615+
val substituteType = (_: Type).asSeenFrom(pt, sym)
2616+
formatAnnotationMessage(rawMsg, sym, substituteType)
2617+
2618+
/** Extracting the message from a method parameter, e.g. in
2619+
*
2620+
* trait Foo
2621+
*
2622+
* def foo(implicit @annotation.implicitNotFound("Foo is missing") foo: Foo): Any = ???
2623+
*/
2624+
def userDefinedImplicitNotFoundParamMessage(using Context): Option[String] =
2625+
paramSymWithMethodCallTree.flatMap: (sym, applTree) =>
2626+
userDefinedMsg(sym, defn.ImplicitNotFoundAnnot).map: rawMsg =>
2627+
val fn = tpd.funPart(applTree)
2628+
val targs = tpd.typeArgss(applTree).flatten
2629+
val methodOwner = fn.symbol.owner
2630+
val methodOwnerType = tpd.qualifier(fn).tpe
2631+
val methodTypeParams = fn.symbol.paramSymss.flatten.filter(_.isType)
2632+
val methodTypeArgs = targs.map(_.tpe)
2633+
val substituteType = (_: Type).asSeenFrom(methodOwnerType, methodOwner).subst(methodTypeParams, methodTypeArgs)
2634+
formatAnnotationMessage(rawMsg, sym.owner, substituteType)
2635+
2636+
def userDefinedImplicitNotFoundTypeMessage(using Context): Option[String] =
2637+
def recur(tp: Type): Option[String] = tp match
2638+
case tp: TypeRef =>
2639+
val sym = tp.symbol
2640+
userDefinedImplicitNotFoundTypeMessageFor(sym).orElse(recur(tp.info))
2641+
case tp: ClassInfo =>
2642+
tp.baseClasses.iterator
2643+
.map(userDefinedImplicitNotFoundTypeMessageFor)
2644+
.find(_.isDefined).flatten
2645+
case tp: TypeProxy =>
2646+
recur(tp.superType)
2647+
case tp: AndType =>
2648+
recur(tp.tp1).orElse(recur(tp.tp2))
2649+
case _ =>
2650+
None
2651+
recur(pt)
2652+
2653+
/** The implicitNotFound annotation on the parameter, or else on the type.
2654+
* implicitNotFound message strings starting with `explain=` are intended for
2655+
* additional explanations, not the message proper. The leading `explain=` is
2656+
* dropped in this case.
2657+
* @param explain The message is used for an additional explanation, not
2658+
* the message proper.
2659+
*/
2660+
def userDefinedImplicitNotFoundMessage(explain: Boolean)(using Context): Option[String] =
2661+
val explainTag = "explain="
2662+
def filter(msg: Option[String]) = msg match
2663+
case Some(str) =>
2664+
if str.startsWith(explainTag) then
2665+
if explain then Some(str.drop(explainTag.length)) else None
2666+
else if explain then None
2667+
else msg
2668+
case None => None
2669+
filter(userDefinedImplicitNotFoundParamMessage)
2670+
.orElse(filter(userDefinedImplicitNotFoundTypeMessage))
2671+
2672+
object AmbiguousImplicitMsg {
2673+
def unapply(search: SearchSuccess): Option[String] =
2674+
userDefinedMsg(search.ref.symbol, defn.ImplicitAmbiguousAnnot)
2675+
}
2676+
25762677
def msg(using Context): String =
25772678

25782679
def formatMsg(shortForm: String)(headline: String = shortForm) = arg match
@@ -2596,29 +2697,6 @@ class MissingImplicitArgument(
25962697
|But ${tpe.explanation}."""
25972698
case _ => headline
25982699

2599-
/** Format `raw` implicitNotFound or implicitAmbiguous argument, replacing
2600-
* all occurrences of `${X}` where `X` is in `paramNames` with the
2601-
* corresponding shown type in `args`.
2602-
*/
2603-
def userDefinedErrorString(raw: String, paramNames: List[String], args: List[Type]): String = {
2604-
def translate(name: String): Option[String] = {
2605-
val idx = paramNames.indexOf(name)
2606-
if (idx >= 0) Some(i"${args(idx)}") else None
2607-
}
2608-
2609-
"""\$\{\s*([^}\s]+)\s*\}""".r.replaceAllIn(raw, (_: Regex.Match) match {
2610-
case Regex.Groups(v) => quoteReplacement(translate(v).getOrElse("")).nn
2611-
})
2612-
}
2613-
2614-
/** Extract a user defined error message from a symbol `sym`
2615-
* with an annotation matching the given class symbol `cls`.
2616-
*/
2617-
def userDefinedMsg(sym: Symbol, cls: Symbol) = for {
2618-
ann <- sym.getAnnotation(cls)
2619-
msg <- ann.argumentConstantString(0)
2620-
} yield msg
2621-
26222700
def location(preposition: String) = if (where.isEmpty) "" else s" $preposition $where"
26232701

26242702
def defaultAmbiguousImplicitMsg(ambi: AmbiguousImplicits) =
@@ -2655,77 +2733,13 @@ class MissingImplicitArgument(
26552733
userDefinedErrorString(raw, params, args)
26562734
}
26572735

2658-
/** @param rawMsg Message template with variables, e.g. "Variable A is ${A}"
2659-
* @param sym Symbol of the annotated type or of the method whose parameter was annotated
2660-
* @param substituteType Function substituting specific types for abstract types associated with variables, e.g A -> Int
2661-
*/
2662-
def formatAnnotationMessage(rawMsg: String, sym: Symbol, substituteType: Type => Type): String = {
2663-
val substitutableTypesSymbols = substitutableTypeSymbolsInScope(sym)
2664-
2665-
userDefinedErrorString(
2666-
rawMsg,
2667-
paramNames = substitutableTypesSymbols.map(_.name.unexpandedName.toString),
2668-
args = substitutableTypesSymbols.map(_.typeRef).map(substituteType)
2669-
)
2670-
}
2671-
2672-
/** Extracting the message from a method parameter, e.g. in
2673-
*
2674-
* trait Foo
2675-
*
2676-
* def foo(implicit @annotation.implicitNotFound("Foo is missing") foo: Foo): Any = ???
2677-
*/
2678-
def userDefinedImplicitNotFoundParamMessage: Option[String] = paramSymWithMethodCallTree.flatMap { (sym, applTree) =>
2679-
userDefinedMsg(sym, defn.ImplicitNotFoundAnnot).map { rawMsg =>
2680-
val fn = tpd.funPart(applTree)
2681-
val targs = tpd.typeArgss(applTree).flatten
2682-
val methodOwner = fn.symbol.owner
2683-
val methodOwnerType = tpd.qualifier(fn).tpe
2684-
val methodTypeParams = fn.symbol.paramSymss.flatten.filter(_.isType)
2685-
val methodTypeArgs = targs.map(_.tpe)
2686-
val substituteType = (_: Type).asSeenFrom(methodOwnerType, methodOwner).subst(methodTypeParams, methodTypeArgs)
2687-
formatAnnotationMessage(rawMsg, sym.owner, substituteType)
2688-
}
2689-
}
2690-
26912736
/** Extracting the message from a type, e.g. in
26922737
*
26932738
* @annotation.implicitNotFound("Foo is missing")
26942739
* trait Foo
26952740
*
26962741
* def foo(implicit foo: Foo): Any = ???
26972742
*/
2698-
def userDefinedImplicitNotFoundTypeMessage: Option[String] =
2699-
def recur(tp: Type): Option[String] = tp match
2700-
case tp: TypeRef =>
2701-
val sym = tp.symbol
2702-
userDefinedImplicitNotFoundTypeMessageFor(sym).orElse(recur(tp.info))
2703-
case tp: ClassInfo =>
2704-
tp.baseClasses.iterator
2705-
.map(userDefinedImplicitNotFoundTypeMessageFor)
2706-
.find(_.isDefined).flatten
2707-
case tp: TypeProxy =>
2708-
recur(tp.superType)
2709-
case tp: AndType =>
2710-
recur(tp.tp1).orElse(recur(tp.tp2))
2711-
case _ =>
2712-
None
2713-
recur(pt)
2714-
2715-
def userDefinedImplicitNotFoundTypeMessageFor(sym: Symbol): Option[String] =
2716-
for
2717-
rawMsg <- userDefinedMsg(sym, defn.ImplicitNotFoundAnnot)
2718-
if Feature.migrateTo3 || sym != defn.Function1
2719-
// Don't inherit "No implicit view available..." message if subtypes of Function1 are not treated as implicit conversions anymore
2720-
yield
2721-
val substituteType = (_: Type).asSeenFrom(pt, sym)
2722-
formatAnnotationMessage(rawMsg, sym, substituteType)
2723-
2724-
object AmbiguousImplicitMsg {
2725-
def unapply(search: SearchSuccess): Option[String] =
2726-
userDefinedMsg(search.ref.symbol, defn.ImplicitAmbiguousAnnot)
2727-
}
2728-
27292743
arg.tpe match
27302744
case ambi: AmbiguousImplicits =>
27312745
(ambi.alt1, ambi.alt2) match
@@ -2739,8 +2753,7 @@ class MissingImplicitArgument(
27392753
i"""No implicit search was attempted${location("for")}
27402754
|since the expected type $target is not specific enough"""
27412755
case _ =>
2742-
val shortMessage = userDefinedImplicitNotFoundParamMessage
2743-
.orElse(userDefinedImplicitNotFoundTypeMessage)
2756+
val shortMessage = userDefinedImplicitNotFoundMessage(explain = false)
27442757
.getOrElse(defaultImplicitNotFoundMessage)
27452758
formatMsg(shortMessage)()
27462759
end msg
@@ -2769,7 +2782,8 @@ class MissingImplicitArgument(
27692782
.orElse(noChainConversionsNote(ignoredConvertibleImplicits))
27702783
.getOrElse(ctx.typer.importSuggestionAddendum(pt))
27712784

2772-
def explain(using Context) = ""
2785+
def explain(using Context) = userDefinedImplicitNotFoundMessage(explain = true)
2786+
.getOrElse("")
27732787
end MissingImplicitArgument
27742788

27752789
class CannotBeAccessed(tpe: NamedType, superAccess: Boolean)(using Context)

compiler/test/dotty/tools/dotc/CompilationTests.scala

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ class CompilationTests {
143143
compileFilesInDir("tests/neg-custom-args/feature", defaultOptions.and("-Xfatal-warnings", "-feature")),
144144
compileFilesInDir("tests/neg-custom-args/no-experimental", defaultOptions.and("-Yno-experimental")),
145145
compileFilesInDir("tests/neg-custom-args/captures", defaultOptions.and("-language:experimental.captureChecking")),
146+
compileFilesInDir("tests/neg-custom-args/explain", defaultOptions.and("-explain")),
146147
compileFile("tests/neg-custom-args/avoid-warn-deprecation.scala", defaultOptions.and("-Xfatal-warnings", "-feature")),
147148
compileFile("tests/neg-custom-args/i3246.scala", scala2CompatMode),
148149
compileFile("tests/neg-custom-args/overrideClass.scala", scala2CompatMode),
@@ -155,9 +156,6 @@ class CompilationTests {
155156
compileFile("tests/neg-custom-args/i1754.scala", allowDeepSubtypes),
156157
compileFile("tests/neg-custom-args/i12650.scala", allowDeepSubtypes),
157158
compileFile("tests/neg-custom-args/i9517.scala", defaultOptions.and("-Xprint-types")),
158-
compileFile("tests/neg-custom-args/i11637.scala", defaultOptions.and("-explain")),
159-
compileFile("tests/neg-custom-args/i15575.scala", defaultOptions.and("-explain")),
160-
compileFile("tests/neg-custom-args/i16601a.scala", defaultOptions.and("-explain")),
161159
compileFile("tests/neg-custom-args/interop-polytypes.scala", allowDeepSubtypes.and("-Yexplicit-nulls")),
162160
compileFile("tests/neg-custom-args/conditionalWarnings.scala", allowDeepSubtypes.and("-deprecation").and("-Xfatal-warnings")),
163161
compileFilesInDir("tests/neg-custom-args/isInstanceOf", allowDeepSubtypes and "-Xfatal-warnings"),
@@ -182,7 +180,6 @@ class CompilationTests {
182180
compileFile("tests/neg-custom-args/matchable.scala", defaultOptions.and("-Xfatal-warnings", "-source", "future")),
183181
compileFile("tests/neg-custom-args/i7314.scala", defaultOptions.and("-Xfatal-warnings", "-source", "future")),
184182
compileFile("tests/neg-custom-args/capt-wf.scala", defaultOptions.and("-language:experimental.captureChecking", "-Xfatal-warnings")),
185-
compileDir("tests/neg-custom-args/hidden-type-errors", defaultOptions.and("-explain")),
186183
compileFile("tests/neg-custom-args/i13026.scala", defaultOptions.and("-print-lines")),
187184
compileFile("tests/neg-custom-args/i13838.scala", defaultOptions.and("-Ximplicit-search-limit", "1000")),
188185
compileFile("tests/neg-custom-args/jdk-9-app.scala", defaultOptions.and("-release:8")),

library/src/scala/quoted/Quotes.scala

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package scala.quoted
22

33
import scala.annotation.experimental
4+
import scala.annotation.implicitNotFound
45
import scala.reflect.TypeTest
56

67
/** Current Quotes in scope
@@ -21,7 +22,25 @@ transparent inline def quotes(using q: Quotes): q.type = q
2122
*
2223
* It contains the low-level Typed AST API metaprogramming API.
2324
* This API does not have the static type guarantees that `Expr` and `Type` provide.
25+
* `Quotes` are generated from an enclosing `${ ... }` or `scala.staging.run`. For example:
26+
* ```scala sc:nocompile
27+
* import scala.quoted._
28+
* inline def myMacro: Expr[T] =
29+
* ${ /* (quotes: Quotes) ?=> */ myExpr }
30+
* def myExpr(using Quotes): Expr[T] =
31+
* '{ f(${ /* (quotes: Quotes) ?=> */ myOtherExpr }) }
32+
* }
33+
* def myOtherExpr(using Quotes): Expr[U] = '{ ... }
34+
* ```
2435
*/
36+
37+
@implicitNotFound("""explain=Maybe this method is missing a `(using Quotes)` parameter.
38+
39+
Maybe that splice `$ { ... }` is missing?
40+
Given instances of `Quotes` are generated from an enclosing splice `$ { ... }` (or `scala.staging.run` call).
41+
A splice can be thought as a method with the following signature.
42+
def $[T](body: Quotes ?=> Expr[T]): T
43+
""")
2544
trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching =>
2645

2746
// Extension methods for `Expr[T]`

library/src/scala/util/boundary.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
package scala.util
2+
import scala.annotation.implicitNotFound
23

34
/** A boundary that can be exited by `break` calls.
45
* `boundary` and `break` represent a unified and superior alternative for the
@@ -34,6 +35,7 @@ object boundary:
3435

3536
/** Labels are targets indicating which boundary will be exited by a `break`.
3637
*/
38+
@implicitNotFound("explain=A Label is generated from an enclosing `scala.util.boundary` call.\nMaybe that boundary is missing?")
3739
final class Label[-T]
3840

3941
/** Abort current computation and instead return `value` as the value of
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
-- [E007] Type Mismatch Error: tests/neg-custom-args/explain/hidden-type-errors/Test.scala:6:24 ------------------------
2+
6 | val x = X.doSomething("XXX") // error
3+
| ^^^^^^^^^^^^^^^^^^^^
4+
| Found: String
5+
| Required: Int
6+
|---------------------------------------------------------------------------------------------------------------------
7+
| Explanation (enabled by `-explain`)
8+
|- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
9+
|
10+
| Tree: t12717.A.bar("XXX")
11+
| I tried to show that
12+
| String
13+
| conforms to
14+
| Int
15+
| but the comparison trace ended with `false`:
16+
|
17+
| ==> String <: Int
18+
| ==> String <: Int
19+
| <== String <: Int = false
20+
| <== String <: Int = false
21+
|
22+
| The tests were made under the empty constraint
23+
---------------------------------------------------------------------------------------------------------------------

tests/neg-custom-args/i11637.check renamed to tests/neg-custom-args/explain/i11637.check

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
-- [E057] Type Mismatch Error: tests/neg-custom-args/i11637.scala:11:33 ------------------------------------------------
1+
-- [E057] Type Mismatch Error: tests/neg-custom-args/explain/i11637.scala:11:33 ----------------------------------------
22
11 | var h = new HKT3_1[FunctorImpl](); // error // error
33
| ^
44
| Type argument test2.FunctorImpl does not conform to upper bound [Generic2[T <: String] <: Set[T]] =>> Any
@@ -26,7 +26,7 @@
2626
|
2727
| The tests were made under the empty constraint
2828
--------------------------------------------------------------------------------------------------------------------
29-
-- [E057] Type Mismatch Error: tests/neg-custom-args/i11637.scala:11:21 ------------------------------------------------
29+
-- [E057] Type Mismatch Error: tests/neg-custom-args/explain/i11637.scala:11:21 ----------------------------------------
3030
11 | var h = new HKT3_1[FunctorImpl](); // error // error
3131
| ^
3232
| Type argument test2.FunctorImpl does not conform to upper bound [Generic2[T <: String] <: Set[T]] =>> Any

tests/neg-custom-args/i15575.check renamed to tests/neg-custom-args/explain/i15575.check

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
-- [E057] Type Mismatch Error: tests/neg-custom-args/i15575.scala:3:27 -------------------------------------------------
1+
-- [E057] Type Mismatch Error: tests/neg-custom-args/explain/i15575.scala:3:27 -----------------------------------------
22
3 | def bar[T]: Unit = foo[T & Any] // error
33
| ^
44
| Type argument T & Any does not conform to lower bound Any
@@ -18,7 +18,7 @@
1818
|
1919
| The tests were made under the empty constraint
2020
---------------------------------------------------------------------------------------------------------------------
21-
-- [E057] Type Mismatch Error: tests/neg-custom-args/i15575.scala:7:14 -------------------------------------------------
21+
-- [E057] Type Mismatch Error: tests/neg-custom-args/explain/i15575.scala:7:14 -----------------------------------------
2222
7 | val _ = foo[String] // error
2323
| ^
2424
| Type argument String does not conform to lower bound CharSequence

tests/neg-custom-args/i16601a.check renamed to tests/neg-custom-args/explain/i16601a.check

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
-- [E042] Type Error: tests/neg-custom-args/i16601a.scala:1:27 ---------------------------------------------------------
1+
-- [E042] Type Error: tests/neg-custom-args/explain/i16601a.scala:1:27 -------------------------------------------------
22
1 |@main def Test: Unit = new concurrent.ExecutionContext // error
33
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
44
| ExecutionContext is a trait; it cannot be instantiated
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
-- [E172] Type Error: tests/neg-custom-args/explain/i16888.scala:1:38 --------------------------------------------------
2+
1 |def test = summon[scala.quoted.Quotes] // error
3+
| ^
4+
| No given instance of type quoted.Quotes was found for parameter x of method summon in object Predef
5+
|---------------------------------------------------------------------------------------------------------------------
6+
| Explanation (enabled by `-explain`)
7+
|- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
8+
| Maybe this method is missing a `(using Quotes)` parameter.
9+
|
10+
| Maybe that splice `$ { ... }` is missing?
11+
| Given instances of `Quotes` are generated from an enclosing splice `$ { ... }` (or `scala.staging.run` call).
12+
| A splice can be thought as a method with the following signature.
13+
| def $[T](body: Quotes ?=> Expr[T]): T
14+
---------------------------------------------------------------------------------------------------------------------
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
def test = summon[scala.quoted.Quotes] // error

0 commit comments

Comments
 (0)