Skip to content

Commit da6c61c

Browse files
authored
Implement summonIgnoring (#22417)
Adds `quotes.reflect.searchIgnoring` and `Expr.summonIgnoring` methods to allow summoning implicits/givens in macros while ignoring certain symbols
1 parent 65ca99d commit da6c61c

File tree

12 files changed

+169
-9
lines changed

12 files changed

+169
-9
lines changed

Diff for: compiler/src/dotty/tools/dotc/interactive/Completion.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -663,7 +663,7 @@ object Completion:
663663
*/
664664
private def implicitConversionTargets(qual: tpd.Tree)(using Context): Set[SearchSuccess] = {
665665
val typer = ctx.typer
666-
val conversions = new typer.ImplicitSearch(defn.AnyType, qual, pos.span).allImplicits
666+
val conversions = new typer.ImplicitSearch(defn.AnyType, qual, pos.span, Set.empty).allImplicits
667667

668668
interactiv.println(i"implicit conversion targets considered: ${conversions.toList}%, %")
669669
conversions

Diff for: compiler/src/dotty/tools/dotc/typer/Implicits.scala

+11-8
Original file line numberDiff line numberDiff line change
@@ -928,8 +928,8 @@ trait Implicits:
928928
/** Find an implicit argument for parameter `formal`.
929929
* Return a failure as a SearchFailureType in the type of the returned tree.
930930
*/
931-
def inferImplicitArg(formal: Type, span: Span)(using Context): Tree =
932-
inferImplicit(formal, EmptyTree, span) match
931+
def inferImplicitArg(formal: Type, span: Span, ignored: Set[Symbol] = Set.empty)(using Context): Tree =
932+
inferImplicit(formal, EmptyTree, span, ignored) match
933933
case SearchSuccess(arg, _, _, _) => arg
934934
case fail @ SearchFailure(failed) =>
935935
if fail.isAmbiguous then failed
@@ -1082,7 +1082,7 @@ trait Implicits:
10821082
* it should be applied, EmptyTree otherwise.
10831083
* @param span The position where errors should be reported.
10841084
*/
1085-
def inferImplicit(pt: Type, argument: Tree, span: Span)(using Context): SearchResult = ctx.profiler.onImplicitSearch(pt):
1085+
def inferImplicit(pt: Type, argument: Tree, span: Span, ignored: Set[Symbol] = Set.empty)(using Context): SearchResult = ctx.profiler.onImplicitSearch(pt):
10861086
trace(s"search implicit ${pt.show}, arg = ${argument.show}: ${argument.tpe.show}", implicits, show = true) {
10871087
record("inferImplicit")
10881088
assert(ctx.phase.allowsImplicitSearch,
@@ -1110,7 +1110,7 @@ trait Implicits:
11101110
else i"conversion from ${argument.tpe} to $pt"
11111111

11121112
CyclicReference.trace(i"searching for an implicit $searchStr"):
1113-
try ImplicitSearch(pt, argument, span)(using searchCtx).bestImplicit
1113+
try ImplicitSearch(pt, argument, span, ignored)(using searchCtx).bestImplicit
11141114
catch case ce: CyclicReference =>
11151115
ce.inImplicitSearch = true
11161116
throw ce
@@ -1130,9 +1130,9 @@ trait Implicits:
11301130
result
11311131
case result: SearchFailure if result.isAmbiguous =>
11321132
val deepPt = pt.deepenProto
1133-
if (deepPt ne pt) inferImplicit(deepPt, argument, span)
1133+
if (deepPt ne pt) inferImplicit(deepPt, argument, span, ignored)
11341134
else if (migrateTo3 && !ctx.mode.is(Mode.OldImplicitResolution))
1135-
withMode(Mode.OldImplicitResolution)(inferImplicit(pt, argument, span)) match {
1135+
withMode(Mode.OldImplicitResolution)(inferImplicit(pt, argument, span, ignored)) match {
11361136
case altResult: SearchSuccess =>
11371137
report.migrationWarning(
11381138
result.reason.msg
@@ -1243,7 +1243,7 @@ trait Implicits:
12431243
}
12441244

12451245
/** An implicit search; parameters as in `inferImplicit` */
1246-
class ImplicitSearch(protected val pt: Type, protected val argument: Tree, span: Span)(using Context):
1246+
class ImplicitSearch(protected val pt: Type, protected val argument: Tree, span: Span, ignored: Set[Symbol])(using Context):
12471247
assert(argument.isEmpty || argument.tpe.isValueType || argument.tpe.isInstanceOf[ExprType],
12481248
em"found: $argument: ${argument.tpe}, expected: $pt")
12491249

@@ -1670,14 +1670,17 @@ trait Implicits:
16701670
SearchFailure(TooUnspecific(pt), span)
16711671
else
16721672
val contextual = ctxImplicits != null
1673-
val preEligible = // the eligible candidates, ignoring positions
1673+
var preEligible = // the eligible candidates, ignoring positions
16741674
if ctxImplicits != null then
16751675
if ctx.gadt.isNarrowing then
16761676
withoutMode(Mode.ImplicitsEnabled) {
16771677
ctxImplicits.uncachedEligible(wildProto)
16781678
}
16791679
else ctxImplicits.eligible(wildProto)
16801680
else implicitScope(wildProto).eligible
1681+
if !ignored.isEmpty then
1682+
preEligible =
1683+
preEligible.filter(candidate => !ignored.contains(candidate.implicitRef.underlyingRef.symbol))
16811684

16821685
/** Does candidate `cand` come too late for it to be considered as an
16831686
* eligible candidate? This is the case if `cand` appears in the same

Diff for: compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala

+8
Original file line numberDiff line numberDiff line change
@@ -2544,6 +2544,14 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler
25442544
// See tests/pos-macros/exprSummonWithTypeVar with -Xcheck-macros.
25452545
dotc.typer.Inferencing.fullyDefinedType(implicitTree.tpe, "", implicitTree)
25462546
implicitTree
2547+
def searchIgnoring(tpe: TypeRepr)(ignored: Symbol*): ImplicitSearchResult =
2548+
import tpd.TreeOps
2549+
val implicitTree = ctx.typer.inferImplicitArg(tpe, Position.ofMacroExpansion.span, ignored.toSet)
2550+
// Make sure that we do not have any uninstantiated type variables.
2551+
// See tests/pos-macros/i16636.
2552+
// See tests/pos-macros/exprSummonWithTypeVar with -Xcheck-macros.
2553+
dotc.typer.Inferencing.fullyDefinedType(implicitTree.tpe, "", implicitTree)
2554+
implicitTree
25472555
end Implicits
25482556

25492557
type ImplicitSearchResult = Tree

Diff for: library/src/scala/quoted/Expr.scala

+19
Original file line numberDiff line numberDiff line change
@@ -280,4 +280,23 @@ object Expr {
280280
}
281281
}
282282

283+
/** Find a given instance of type `T` in the current scope,
284+
* while excluding certain symbols from the initial implicit search.
285+
* Return `Some` containing the expression of the implicit or
286+
* `None` if implicit resolution failed.
287+
*
288+
* @tparam T type of the implicit parameter
289+
* @param ignored Symbols ignored during the initial implicit search
290+
*
291+
* @note if the found given requires additional search for other given instances,
292+
* this additional search will NOT exclude the symbols from the `ignored` list.
293+
*/
294+
def summonIgnoring[T](using Type[T])(using quotes: Quotes)(ignored: quotes.reflect.Symbol*): Option[Expr[T]] = {
295+
import quotes.reflect._
296+
Implicits.searchIgnoring(TypeRepr.of[T])(ignored*) match {
297+
case iss: ImplicitSearchSuccess => Some(iss.tree.asExpr.asInstanceOf[Expr[T]])
298+
case isf: ImplicitSearchFailure => None
299+
}
300+
}
301+
283302
}

Diff for: library/src/scala/quoted/Quotes.scala

+12
Original file line numberDiff line numberDiff line change
@@ -3708,6 +3708,18 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching =>
37083708
* @param tpe type of the implicit parameter
37093709
*/
37103710
def search(tpe: TypeRepr): ImplicitSearchResult
3711+
3712+
/** Find a given instance of type `T` in the current scope provided by the current enclosing splice,
3713+
* while excluding certain symbols from the initial implicit search.
3714+
* Return an `ImplicitSearchResult`.
3715+
*
3716+
* @param tpe type of the implicit parameter
3717+
* @param ignored Symbols ignored during the initial implicit search
3718+
*
3719+
* @note if an found given requires additional search for other given instances,
3720+
* this additional search will NOT exclude the symbols from the `ignored` list.
3721+
*/
3722+
def searchIgnoring(tpe: TypeRepr)(ignored: Symbol*): ImplicitSearchResult
37113723
}
37123724

37133725
/** Result of a given instance search */

Diff for: project/MiMaFilters.scala

+1
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ object MiMaFilters {
9595
ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#MethodTypeModule.apply"),
9696
ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#MethodTypeMethods.methodTypeKind"),
9797
ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#MethodTypeMethods.isContextual"),
98+
ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#ImplicitsModule.searchIgnoring"),
9899
// Change `experimental` annotation to a final class
99100
ProblemFilters.exclude[FinalClassProblem]("scala.annotation.experimental"),
100101
),

Diff for: tests/run-macros/summonIgnoring-nonrecursive.check

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
TC[C2] generated in macro using:
2+
TC2[_] generated in macro using:
3+
TC[C1] generated in macro
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
//> using options -experimental
2+
import scala.quoted._
3+
class C1
4+
trait TC[T] {
5+
def print(): Unit
6+
}
7+
8+
object TC {
9+
implicit transparent inline def auto[T]: TC[T] = ${autoImpl[T]}
10+
def autoImpl[T: Type](using Quotes): Expr[TC[T]] =
11+
import quotes.reflect._
12+
if (TypeRepr.of[T].typeSymbol == Symbol.classSymbol("C1")){
13+
'{
14+
new TC[T] {
15+
def print() = {
16+
println("TC[C1] generated in macro")
17+
}
18+
}
19+
}
20+
} else {
21+
Expr.summonIgnoring[TC2[C1]](Symbol.classSymbol("TC").companionModule.methodMember("auto")*) match
22+
case Some(a) =>
23+
'{
24+
new TC[T] {
25+
def print(): Unit =
26+
println(s"TC[${${Expr(TypeRepr.of[T].show)}}] generated in macro using:")
27+
$a.print()
28+
}
29+
}
30+
case None =>
31+
'{
32+
new TC[T]{
33+
def print(): Unit =
34+
println(s"TC[${${Expr(TypeRepr.of[T].show)}}] generated in macro without TC2[_]")
35+
}
36+
}
37+
}
38+
}
39+
40+
trait TC2[T] {
41+
def print(): Unit
42+
}
43+
44+
object TC2 {
45+
implicit def auto2[T](using tc: TC[T]): TC2[T] = new TC2[T] {
46+
def print(): Unit =
47+
println(s"TC2[_] generated in macro using:")
48+
tc.print()
49+
}
50+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
//> using options -experimental
2+
3+
@main def Test(): Unit = {
4+
class C2
5+
summon[TC[C2]].print()
6+
}

Diff for: tests/run-macros/summonIgnoring.check

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
No given in scope:
2+
TC[C2] generated in macro without TC[C1]
3+
Given in scope:
4+
TC[C2] generated in macro using:
5+
TC[C1] defined by a user

Diff for: tests/run-macros/summonIgnoring/Macro_1.scala

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
//> using options -experimental
2+
import scala.quoted._
3+
class C1
4+
trait TC[T] {
5+
def print(): Unit
6+
}
7+
object TC {
8+
implicit transparent inline def auto[T]: TC[T] = ${autoImpl[T]}
9+
def autoImpl[T: Type](using Quotes): Expr[TC[T]] =
10+
import quotes.reflect._
11+
if(TypeRepr.of[T].typeSymbol == Symbol.classSymbol("C1")){
12+
'{
13+
new TC[T] {
14+
def print() = {
15+
println("TC[C1] generated in macro")
16+
}
17+
}
18+
}
19+
} else {
20+
Expr.summonIgnoring[TC[C1]](Symbol.classSymbol("TC").companionModule.methodMember("auto")*) match
21+
case Some(a) =>
22+
'{
23+
new TC[T] {
24+
def print(): Unit =
25+
println(s"TC[${${Expr(TypeRepr.of[T].show)}}] generated in macro using:")
26+
$a.print()
27+
}
28+
}
29+
case None =>
30+
'{
31+
new TC[T]{
32+
def print(): Unit =
33+
println(s"TC[${${Expr(TypeRepr.of[T].show)}}] generated in macro without TC[C1]")
34+
}
35+
}
36+
}
37+
38+
}

Diff for: tests/run-macros/summonIgnoring/Test_2.scala

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
//> using options -experimental
2+
3+
@main def Test(): Unit = {
4+
class C2
5+
println("No given in scope:")
6+
summon[TC[C2]].print()
7+
8+
{
9+
println("Given in scope:")
10+
given TC[C1] = new TC[C1] {
11+
def print() = println("TC[C1] defined by a user")
12+
}
13+
summon[TC[C2]].print()
14+
}
15+
}

0 commit comments

Comments
 (0)