Skip to content
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

Implement summonIgnoring #22417

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
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/inlines/InlineReducer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ class InlineReducer(inliner: Inliner)(using Context):
val evTyper = new Typer(ctx.nestingLevel + 1)
val evCtx = ctx.fresh.setTyper(evTyper)
inContext(evCtx) {
val evidence = evTyper.inferImplicitArg(tpt.tpe, tpt.span)
val evidence = evTyper.inferImplicitArg(tpt.tpe, tpt.span, ignored = Set.empty)
evidence.tpe match {
case fail: Implicits.AmbiguousImplicits =>
report.error(evTyper.missingArgMsg(evidence, tpt.tpe, ""), tpt.srcPos)
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/inlines/Inlines.scala
Original file line number Diff line number Diff line change
Expand Up @@ -506,7 +506,7 @@ object Inlines:
val evTyper = new Typer(ctx.nestingLevel + 1)
val evCtx = ctx.fresh.setTyper(evTyper)
inContext(evCtx) {
val evidence = evTyper.inferImplicitArg(tpe, callTypeArgs.head.span)
val evidence = evTyper.inferImplicitArg(tpe, callTypeArgs.head.span, ignored = Set.empty)
evidence.tpe match
case fail: Implicits.SearchFailureType =>
errorTree(call, evTyper.missingArgMsg(evidence, tpe, ""))
Expand Down
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
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/staging/HealType.scala
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ class HealType(pos: SrcPos)(using Context) extends TypeMap {
*/
protected def tryHeal(tp: TypeRef): Type = {
val reqType = defn.QuotedTypeClass.typeRef.appliedTo(tp)
val tag = ctx.typer.inferImplicitArg(reqType, pos.span)
val tag = ctx.typer.inferImplicitArg(reqType, pos.span, ignored = Set.empty)
tag.tpe match
case tp: TermRef =>
ctx.typer.checkStable(tp, pos, "type witness")
Expand Down
24 changes: 13 additions & 11 deletions compiler/src/dotty/tools/dotc/typer/Implicits.scala
Original file line number Diff line number Diff line change
Expand Up @@ -901,7 +901,7 @@ trait Implicits:
}

try
val inferred = inferImplicit(adjust(to), from, from.span)
val inferred = inferImplicit(adjust(to), from, from.span, ignored = Set.empty)

inferred match {
case SearchSuccess(_, ref, _, false) if isOldStyleFunctionConversion(ref.underlying) =>
Expand All @@ -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])(using Context): Tree =
inferImplicit(formal, EmptyTree, span, ignored) match
case SearchSuccess(arg, _, _, _) => arg
case fail @ SearchFailure(failed) =>
if fail.isAmbiguous then failed
Expand All @@ -944,7 +944,7 @@ trait Implicits:

/** Search an implicit argument and report error if not found */
def implicitArgTree(formal: Type, span: Span, where: => String = "")(using Context): Tree = {
val arg = inferImplicitArg(formal, span)
val arg = inferImplicitArg(formal, span, ignored = Set.empty)
if (arg.tpe.isInstanceOf[SearchFailureType])
report.error(missingArgMsg(arg, formal, where), ctx.source.atSpan(span))
arg
Expand All @@ -968,7 +968,7 @@ trait Implicits:
def ignoredInstanceNormalImport = arg.tpe match
case fail: SearchFailureType =>
if (fail.expectedType eq pt) || isFullyDefined(fail.expectedType, ForceDegree.none) then
inferImplicit(fail.expectedType, fail.argument, arg.span)(
inferImplicit(fail.expectedType, fail.argument, arg.span, Set.empty)(
using findHiddenImplicitsCtx(ctx)) match {
case s: SearchSuccess => Some(s)
case f: SearchFailure =>
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])(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,7 +1670,7 @@ trait Implicits:
SearchFailure(TooUnspecific(pt), span)
else
val contextual = ctxImplicits != null
val preEligible = // the eligible candidates, ignoring positions
val prePreEligible = // the eligible candidates, ignoring positions
if ctxImplicits != null then
if ctx.gadt.isNarrowing then
withoutMode(Mode.ImplicitsEnabled) {
Expand All @@ -1679,6 +1679,8 @@ trait Implicits:
else ctxImplicits.eligible(wildProto)
else implicitScope(wildProto).eligible

val preEligible =
prePreEligible.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
* scope as a given definition of the form `given ... = ...` that
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ trait QuotesAndSplices {
report.warning("Canceled splice directly inside a quote. '{ ${ XYZ } } is equivalent to XYZ.", tree.srcPos)
case _ =>
}
val quotes = inferImplicitArg(defn.QuotesClass.typeRef, tree.span)
val quotes = inferImplicitArg(defn.QuotesClass.typeRef, tree.span, ignored = Set.empty)

if quotes.tpe.isInstanceOf[SearchFailureType] then
report.error(missingArgMsg(quotes, defn.QuotesClass.typeRef, ""), ctx.source.atSpan(tree.span))
Expand Down
4 changes: 2 additions & 2 deletions compiler/src/dotty/tools/dotc/typer/Synthesizer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context):
case arg :: Nil =>
instArg(arg) match
case defn.ArrayOf(elemTp) =>
val etag = typer.inferImplicitArg(defn.ClassTagClass.typeRef.appliedTo(elemTp), span)
val etag = typer.inferImplicitArg(defn.ClassTagClass.typeRef.appliedTo(elemTp), span, Set.empty)
if etag.tpe.isError then EmptyTree else etag.select(nme.wrap)
case tp if hasStableErasure(tp) && !tp.isBottomTypeAfterErasure =>
val sym = tp.typeSymbol
Expand Down Expand Up @@ -148,7 +148,7 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context):

/** Is there an `CanEqual[T, T]` instance, assuming -strictEquality? */
def hasEq(tp: Type)(using Context): Boolean =
val inst = typer.inferImplicitArg(defn.CanEqualClass.typeRef.appliedTo(tp, tp), span)
val inst = typer.inferImplicitArg(defn.CanEqualClass.typeRef.appliedTo(tp, tp), span, ignored = Set.empty)
!inst.isEmpty && !inst.tpe.isError

/** Can we assume the canEqualAny instance for `tp1`, `tp2`?
Expand Down
6 changes: 3 additions & 3 deletions compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1107,7 +1107,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
case Decimal => defn.FromDigits_DecimalClass
case Floating => defn.FromDigits_FloatingClass
}
inferImplicit(fromDigitsCls.typeRef.appliedTo(target), EmptyTree, tree.span) match {
inferImplicit(fromDigitsCls.typeRef.appliedTo(target), EmptyTree, tree.span, ignored = Set.empty) match {
case SearchSuccess(arg, _, _, _) =>
val fromDigits = untpd.Select(untpd.TypedSplice(arg), nme.fromDigits).withSpan(tree.span)
val firstArg = Literal(Constant(digits))
Expand Down Expand Up @@ -1282,7 +1282,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
def withTag(tpe: Type): Option[Tree] = {
require(ctx.mode.is(Mode.Pattern))
withoutMode(Mode.Pattern)(
inferImplicit(tpe, EmptyTree, tree.tpt.span)
inferImplicit(tpe, EmptyTree, tree.tpt.span, ignored = Set.empty)
) match
case SearchSuccess(clsTag, _, _, _) =>
withMode(Mode.InTypeTest) {
Expand Down Expand Up @@ -4201,7 +4201,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
else formals1
implicitArgs(formals2, argIndex + 1, pt)

val arg = inferImplicitArg(formal, tree.span.endPos)
val arg = inferImplicitArg(formal, tree.span.endPos, ignored = Set.empty)
arg.tpe match
case failed: AmbiguousImplicits =>
val pt1 = pt.deepenProtoTrans
Expand Down
10 changes: 9 additions & 1 deletion compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2527,7 +2527,15 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler
object Implicits extends ImplicitsModule:
def search(tpe: TypeRepr): ImplicitSearchResult =
import tpd.TreeOps
val implicitTree = ctx.typer.inferImplicitArg(tpe, Position.ofMacroExpansion.span)
val implicitTree = ctx.typer.inferImplicitArg(tpe, Position.ofMacroExpansion.span, ignored = Set.empty)
// 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
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.
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()
}
}
Loading