diff --git a/compiler/src/dotty/tools/dotc/interactive/Completion.scala b/compiler/src/dotty/tools/dotc/interactive/Completion.scala index bdd60d062814..8a1cc10373db 100644 --- a/compiler/src/dotty/tools/dotc/interactive/Completion.scala +++ b/compiler/src/dotty/tools/dotc/interactive/Completion.scala @@ -663,7 +663,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 diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 9d273ebca866..66e54cde33b3 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -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 @@ -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, @@ -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 @@ -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 @@ -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") @@ -1670,7 +1670,7 @@ 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) { @@ -1678,6 +1678,9 @@ trait Implicits: } 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 diff --git a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala index c35d259ffea8..a20979d8702c 100644 --- a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala +++ b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala @@ -2544,6 +2544,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 diff --git a/library/src/scala/quoted/Expr.scala b/library/src/scala/quoted/Expr.scala index d1385a0193d6..bfb26023e2c8 100644 --- a/library/src/scala/quoted/Expr.scala +++ b/library/src/scala/quoted/Expr.scala @@ -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 + } + } + } diff --git a/library/src/scala/quoted/Quotes.scala b/library/src/scala/quoted/Quotes.scala index 9cfd85c9e433..5ada585c23a1 100644 --- a/library/src/scala/quoted/Quotes.scala +++ b/library/src/scala/quoted/Quotes.scala @@ -3708,6 +3708,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 */ diff --git a/project/MiMaFilters.scala b/project/MiMaFilters.scala index 00e7153bcb83..55c46a017f34 100644 --- a/project/MiMaFilters.scala +++ b/project/MiMaFilters.scala @@ -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"), ), diff --git a/tests/run-macros/summonIgnoring-nonrecursive.check b/tests/run-macros/summonIgnoring-nonrecursive.check new file mode 100644 index 000000000000..643b49550f62 --- /dev/null +++ b/tests/run-macros/summonIgnoring-nonrecursive.check @@ -0,0 +1,3 @@ +TC[C2] generated in macro using: +TC2[_] generated in macro using: +TC[C1] generated in macro diff --git a/tests/run-macros/summonIgnoring-nonrecursive/Macro_1.scala b/tests/run-macros/summonIgnoring-nonrecursive/Macro_1.scala new file mode 100644 index 000000000000..b34a9a878483 --- /dev/null +++ b/tests/run-macros/summonIgnoring-nonrecursive/Macro_1.scala @@ -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() + } +} diff --git a/tests/run-macros/summonIgnoring-nonrecursive/Test_2.scala b/tests/run-macros/summonIgnoring-nonrecursive/Test_2.scala new file mode 100644 index 000000000000..abd947d3e7fb --- /dev/null +++ b/tests/run-macros/summonIgnoring-nonrecursive/Test_2.scala @@ -0,0 +1,6 @@ +//> using options -experimental + +@main def Test(): Unit = { + class C2 + summon[TC[C2]].print() +} diff --git a/tests/run-macros/summonIgnoring.check b/tests/run-macros/summonIgnoring.check new file mode 100644 index 000000000000..5369c42c4888 --- /dev/null +++ b/tests/run-macros/summonIgnoring.check @@ -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 diff --git a/tests/run-macros/summonIgnoring/Macro_1.scala b/tests/run-macros/summonIgnoring/Macro_1.scala new file mode 100644 index 000000000000..e4771588ce4e --- /dev/null +++ b/tests/run-macros/summonIgnoring/Macro_1.scala @@ -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]") + } + } + } + +} diff --git a/tests/run-macros/summonIgnoring/Test_2.scala b/tests/run-macros/summonIgnoring/Test_2.scala new file mode 100644 index 000000000000..ca9007f269e2 --- /dev/null +++ b/tests/run-macros/summonIgnoring/Test_2.scala @@ -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() + } +}