Skip to content

Implement summonIgnoring #22417

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Feb 18, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/interactive/Completion.scala
Original file line number Diff line number Diff line change
Expand Up @@ -662,7 +662,7 @@ object Completion:
*/
private def implicitConversionTargets(qual: tpd.Tree)(using Context): Set[SearchSuccess] = {
val typer = ctx.typer
val conversions = new typer.ImplicitSearch(defn.AnyType, qual, pos.span).allImplicits
val conversions = new typer.ImplicitSearch(defn.AnyType, qual, pos.span, Set.empty).allImplicits

interactiv.println(i"implicit conversion targets considered: ${conversions.toList}%, %")
conversions
Expand Down
19 changes: 11 additions & 8 deletions compiler/src/dotty/tools/dotc/typer/Implicits.scala
Original file line number Diff line number Diff line change
Expand Up @@ -928,8 +928,8 @@ trait Implicits:
/** Find an implicit argument for parameter `formal`.
* Return a failure as a SearchFailureType in the type of the returned tree.
*/
def inferImplicitArg(formal: Type, span: Span)(using Context): Tree =
inferImplicit(formal, EmptyTree, span) match
def inferImplicitArg(formal: Type, span: Span, ignored: Set[Symbol] = Set.empty)(using Context): Tree =
inferImplicit(formal, EmptyTree, span, ignored) match
case SearchSuccess(arg, _, _, _) => arg
case fail @ SearchFailure(failed) =>
if fail.isAmbiguous then failed
Expand Down Expand Up @@ -1082,7 +1082,7 @@ trait Implicits:
* it should be applied, EmptyTree otherwise.
* @param span The position where errors should be reported.
*/
def inferImplicit(pt: Type, argument: Tree, span: Span)(using Context): SearchResult = ctx.profiler.onImplicitSearch(pt):
def inferImplicit(pt: Type, argument: Tree, span: Span, ignored: Set[Symbol] = Set.empty)(using Context): SearchResult = ctx.profiler.onImplicitSearch(pt):
trace(s"search implicit ${pt.show}, arg = ${argument.show}: ${argument.tpe.show}", implicits, show = true) {
record("inferImplicit")
assert(ctx.phase.allowsImplicitSearch,
Expand Down Expand Up @@ -1110,7 +1110,7 @@ trait Implicits:
else i"conversion from ${argument.tpe} to $pt"

CyclicReference.trace(i"searching for an implicit $searchStr"):
try ImplicitSearch(pt, argument, span)(using searchCtx).bestImplicit
try ImplicitSearch(pt, argument, span, ignored)(using searchCtx).bestImplicit
catch case ce: CyclicReference =>
ce.inImplicitSearch = true
throw ce
Expand All @@ -1130,9 +1130,9 @@ trait Implicits:
result
case result: SearchFailure if result.isAmbiguous =>
val deepPt = pt.deepenProto
if (deepPt ne pt) inferImplicit(deepPt, argument, span)
if (deepPt ne pt) inferImplicit(deepPt, argument, span, ignored)
else if (migrateTo3 && !ctx.mode.is(Mode.OldImplicitResolution))
withMode(Mode.OldImplicitResolution)(inferImplicit(pt, argument, span)) match {
withMode(Mode.OldImplicitResolution)(inferImplicit(pt, argument, span, ignored)) match {
case altResult: SearchSuccess =>
report.migrationWarning(
result.reason.msg
Expand Down Expand Up @@ -1243,7 +1243,7 @@ trait Implicits:
}

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

Expand Down Expand Up @@ -1670,14 +1670,17 @@ trait Implicits:
SearchFailure(TooUnspecific(pt), span)
else
val contextual = ctxImplicits != null
val preEligible = // the eligible candidates, ignoring positions
var preEligible = // the eligible candidates, ignoring positions
if ctxImplicits != null then
if ctx.gadt.isNarrowing then
withoutMode(Mode.ImplicitsEnabled) {
ctxImplicits.uncachedEligible(wildProto)
}
else ctxImplicits.eligible(wildProto)
else implicitScope(wildProto).eligible
if !ignored.isEmpty then
preEligible =
preEligible.filter(candidate => !ignored.contains(candidate.implicitRef.underlyingRef.symbol))

/** Does candidate `cand` come too late for it to be considered as an
* eligible candidate? This is the case if `cand` appears in the same
Expand Down
8 changes: 8 additions & 0 deletions compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2533,6 +2533,14 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler
// See tests/pos-macros/exprSummonWithTypeVar with -Xcheck-macros.
dotc.typer.Inferencing.fullyDefinedType(implicitTree.tpe, "", implicitTree)
implicitTree
def searchIgnoring(tpe: TypeRepr)(ignored: Symbol*): ImplicitSearchResult =
import tpd.TreeOps
val implicitTree = ctx.typer.inferImplicitArg(tpe, Position.ofMacroExpansion.span, ignored.toSet)
// Make sure that we do not have any uninstantiated type variables.
// See tests/pos-macros/i16636.
// See tests/pos-macros/exprSummonWithTypeVar with -Xcheck-macros.
dotc.typer.Inferencing.fullyDefinedType(implicitTree.tpe, "", implicitTree)
implicitTree
end Implicits

type ImplicitSearchResult = Tree
Expand Down
19 changes: 19 additions & 0 deletions library/src/scala/quoted/Expr.scala
Original file line number Diff line number Diff line change
Expand Up @@ -280,4 +280,23 @@ object Expr {
}
}

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

}
12 changes: 12 additions & 0 deletions library/src/scala/quoted/Quotes.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3705,6 +3705,18 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching =>
* @param tpe type of the implicit parameter
*/
def search(tpe: TypeRepr): ImplicitSearchResult

/** Find a given instance of type `T` in the current scope provided by the current enclosing splice,
* while excluding certain symbols from the initial implicit search.
* Return an `ImplicitSearchResult`.
*
* @param tpe type of the implicit parameter
* @param ignored Symbols ignored during the initial implicit search
*
* @note if an found given requires additional search for other given instances,
* this additional search will NOT exclude the symbols from the `ignored` list.
*/
def searchIgnoring(tpe: TypeRepr)(ignored: Symbol*): ImplicitSearchResult
}

/** Result of a given instance search */
Expand Down
1 change: 1 addition & 0 deletions project/MiMaFilters.scala
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ object MiMaFilters {
ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#MethodTypeModule.apply"),
ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#MethodTypeMethods.methodTypeKind"),
ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#MethodTypeMethods.isContextual"),
ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#ImplicitsModule.searchIgnoring"),
// Change `experimental` annotation to a final class
ProblemFilters.exclude[FinalClassProblem]("scala.annotation.experimental"),
),
Expand Down
3 changes: 3 additions & 0 deletions tests/run-macros/summonIgnoring-nonrecursive.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
TC[C2] generated in macro using:
TC2[_] generated in macro using:
TC[C1] generated in macro
50 changes: 50 additions & 0 deletions tests/run-macros/summonIgnoring-nonrecursive/Macro_1.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
//> using options -experimental
import scala.quoted._
class C1
trait TC[T] {
def print(): Unit
}

object TC {
implicit transparent inline def auto[T]: TC[T] = ${autoImpl[T]}
def autoImpl[T: Type](using Quotes): Expr[TC[T]] =
import quotes.reflect._
if (TypeRepr.of[T].typeSymbol == Symbol.classSymbol("C1")){
'{
new TC[T] {
def print() = {
println("TC[C1] generated in macro")
}
}
}
} else {
Expr.summonIgnoring[TC2[C1]](Symbol.classSymbol("TC").companionModule.methodMember("auto")*) match
case Some(a) =>
'{
new TC[T] {
def print(): Unit =
println(s"TC[${${Expr(TypeRepr.of[T].show)}}] generated in macro using:")
$a.print()
}
}
case None =>
'{
new TC[T]{
def print(): Unit =
println(s"TC[${${Expr(TypeRepr.of[T].show)}}] generated in macro without TC2[_]")
}
}
}
}

trait TC2[T] {
def print(): Unit
}

object TC2 {
implicit def auto2[T](using tc: TC[T]): TC2[T] = new TC2[T] {
def print(): Unit =
println(s"TC2[_] generated in macro using:")
tc.print()
}
}
6 changes: 6 additions & 0 deletions tests/run-macros/summonIgnoring-nonrecursive/Test_2.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
//> using options -experimental

@main def Test(): Unit = {
class C2
summon[TC[C2]].print()
}
5 changes: 5 additions & 0 deletions tests/run-macros/summonIgnoring.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
No given in scope:
TC[C2] generated in macro without TC[C1]
Given in scope:
TC[C2] generated in macro using:
TC[C1] defined by a user
38 changes: 38 additions & 0 deletions tests/run-macros/summonIgnoring/Macro_1.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
//> using options -experimental
import scala.quoted._
class C1
trait TC[T] {
def print(): Unit
}
object TC {
implicit transparent inline def auto[T]: TC[T] = ${autoImpl[T]}
def autoImpl[T: Type](using Quotes): Expr[TC[T]] =
import quotes.reflect._
if(TypeRepr.of[T].typeSymbol == Symbol.classSymbol("C1")){
'{
new TC[T] {
def print() = {
println("TC[C1] generated in macro")
}
}
}
} else {
Expr.summonIgnoring[TC[C1]](Symbol.classSymbol("TC").companionModule.methodMember("auto")*) match
case Some(a) =>
'{
new TC[T] {
def print(): Unit =
println(s"TC[${${Expr(TypeRepr.of[T].show)}}] generated in macro using:")
$a.print()
}
}
case None =>
'{
new TC[T]{
def print(): Unit =
println(s"TC[${${Expr(TypeRepr.of[T].show)}}] generated in macro without TC[C1]")
}
}
}

}
15 changes: 15 additions & 0 deletions tests/run-macros/summonIgnoring/Test_2.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//> using options -experimental

@main def Test(): Unit = {
class C2
println("No given in scope:")
summon[TC[C2]].print()

{
println("Given in scope:")
given TC[C1] = new TC[C1] {
def print() = println("TC[C1] defined by a user")
}
summon[TC[C2]].print()
}
}