Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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 =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think in this case it's overall simpler to use a default = Set.empty for ignored.

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):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also use a default argument here.

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))
Copy link
Contributor

@odersky odersky Feb 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will copy each list returned by eligible, so it's a significant memory churn to cater for an edge case. An improvement would use filterConserve, but I think it's even better to add a conditional. Something like that:

var preEligible = ... // rhs of prePreEligible
if !ignored.isEmpty then 
  preEligible = preEligible.filter(...)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, good point. Thank you!

/** 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