diff --git a/compiler/src/dotty/tools/dotc/tastyreflect/KernelImpl.scala b/compiler/src/dotty/tools/dotc/tastyreflect/KernelImpl.scala index b7cb208c2767..1db12eece0df 100644 --- a/compiler/src/dotty/tools/dotc/tastyreflect/KernelImpl.scala +++ b/compiler/src/dotty/tools/dotc/tastyreflect/KernelImpl.scala @@ -4,7 +4,7 @@ package tastyreflect import dotty.tools.dotc.ast.Trees.SeqLiteral import dotty.tools.dotc.ast.{Trees, tpd, untpd} import dotty.tools.dotc.ast.tpd.TreeOps -import dotty.tools.dotc.typer.Typer +import dotty.tools.dotc.typer.{Implicits, Typer} import dotty.tools.dotc.core._ import dotty.tools.dotc.core.Flags._ import dotty.tools.dotc.core.StdNames.nme @@ -13,6 +13,7 @@ import dotty.tools.dotc.core.Symbols._ import dotty.tools.dotc.core.Decorators._ import dotty.tools.dotc.tastyreflect.FromSymbol.{definitionFromSym, packageDefFromSym} import dotty.tools.dotc.parsing.Parsers.Parser +import dotty.tools.dotc.typer.Implicits.{AmbiguousImplicits, DivergingImplicit, NoMatchingImplicits, SearchFailure, SearchFailureType} import dotty.tools.dotc.util.SourceFile import scala.tasty.reflect.Kernel @@ -1849,6 +1850,48 @@ class KernelImpl(val rootContext: core.Contexts.Context, val rootPosition: util. def Definitions_NullType: Type = defn.NullType def Definitions_StringType: Type = defn.StringType + // + // IMPLICITS + // + + type ImplicitSearchResult = Tree + + def searchImplicit(tpe: Type)(implicit ctx: Context): ImplicitSearchResult = + ctx.typer.inferImplicitArg(tpe, rootPosition.span) + + type ImplicitSearchSuccess = Tree + def matchImplicitSearchSuccess(isr: ImplicitSearchResult)(implicit ctx: Context): Option[ImplicitSearchSuccess] = isr.tpe match { + case _: SearchFailureType => None + case _ => Some(isr) + } + def ImplicitSearchSuccess_tree(self: ImplicitSearchSuccess)(implicit ctx: Context): Term = self + + type ImplicitSearchFailure = Tree + def matchImplicitSearchFailure(isr: ImplicitSearchResult)(implicit ctx: Context): Option[ImplicitSearchFailure] = isr.tpe match { + case _: SearchFailureType => Some(isr) + case _ => None + } + def ImplicitSearchFailure_explanation(self: ImplicitSearchFailure)(implicit ctx: Context): String = + self.tpe.asInstanceOf[SearchFailureType].explanation + + type DivergingImplicit = Tree + def matchDivergingImplicit(isr: ImplicitSearchResult)(implicit ctx: Context): Option[DivergingImplicit] = isr.tpe match { + case _: Implicits.DivergingImplicit => Some(isr) + case _ => None + } + + type NoMatchingImplicits = Tree + def matchNoMatchingImplicits(isr: ImplicitSearchResult)(implicit ctx: Context): Option[NoMatchingImplicits] = isr.tpe match { + case _: Implicits.NoMatchingImplicits => Some(isr) + case _ => None + } + + type AmbiguousImplicits = Tree + def matchAmbiguousImplicits(isr: ImplicitSearchResult)(implicit ctx: Context): Option[AmbiguousImplicits] = isr.tpe match { + case _: Implicits.AmbiguousImplicits => Some(isr) + case _ => None + } + // // HELPERS // diff --git a/docs/docs/reference/metaprogramming/macros.md b/docs/docs/reference/metaprogramming/macros.md index 6943199c7c44..199b48db7526 100644 --- a/docs/docs/reference/metaprogramming/macros.md +++ b/docs/docs/reference/metaprogramming/macros.md @@ -569,6 +569,22 @@ while (i < arr.length) { sum ``` +### Find implicits within a macro + +Similarly to the `implicit match` construct, it is possible to make implicit search available +in a quote context. For this we simply provide `scala.quoted.matching.searchImplicitExpr: + +```scala +inline def setFor[T]: Set[T] = ${ setForExpr[T] } + +def setForExpr[T: Type] given QuoteContext: Expr[Set[T]] = { + searchImplicitExpr[Ordering[T]] match { + case Some(ord) => '{ new TreeSet[T]()($ord) } + case _ => '{ new HashSet[T] } + } +} +``` + ### Relationship with Whitebox Inline [Inline](./inline.md) documents inlining. The code below introduces a whitebox diff --git a/library/src/scala/quoted/matching/package.scala b/library/src/scala/quoted/matching/package.scala new file mode 100644 index 000000000000..cae5def95dfb --- /dev/null +++ b/library/src/scala/quoted/matching/package.scala @@ -0,0 +1,21 @@ +package scala.quoted + +package object matching { + + /** Find an implicit of type `T` in the current scope given by `qctx`. + * Return `Some` containing the expression of the implicit or + * `None` if implicit resolution failed. + * + * @tparam T type of the implicit parameter + * @param tpe quoted type of the implicit parameter + * @param qctx current context + */ + def searchImplicitExpr[T] given (tpe: Type[T], qctx: QuoteContext): Option[Expr[T]] = { + import qctx.tasty._ + searchImplicit(tpe.unseal.tpe) match { + case IsImplicitSearchSuccess(iss) => Some(iss.tree.seal.asInstanceOf[Expr[T]]) + case IsImplicitSearchFailure(isf) => None + } + } + +} diff --git a/library/src/scala/tasty/Reflection.scala b/library/src/scala/tasty/Reflection.scala index cf4f88f7288f..a72c80a59237 100644 --- a/library/src/scala/tasty/Reflection.scala +++ b/library/src/scala/tasty/Reflection.scala @@ -9,6 +9,7 @@ class Reflection(val kernel: Kernel) with CommentOps with FlagsOps with IdOps + with ImplicitsOps with ImportSelectorOps with QuotedOps with PatternOps diff --git a/library/src/scala/tasty/reflect/Core.scala b/library/src/scala/tasty/reflect/Core.scala index 6776c7f4c22b..b96dc7829aa2 100644 --- a/library/src/scala/tasty/reflect/Core.scala +++ b/library/src/scala/tasty/reflect/Core.scala @@ -456,4 +456,17 @@ trait Core { /** FlagSet of a Symbol */ type Flags = kernel.Flags + + type ImplicitSearchResult = kernel.ImplicitSearchResult + + type ImplicitSearchSuccess = kernel.ImplicitSearchSuccess + + type ImplicitSearchFailure = kernel.ImplicitSearchFailure + + type DivergingImplicit = kernel.DivergingImplicit + + type NoMatchingImplicits = kernel.NoMatchingImplicits + + type AmbiguousImplicits = kernel.AmbiguousImplicits + } diff --git a/library/src/scala/tasty/reflect/ImplicitsOps.scala b/library/src/scala/tasty/reflect/ImplicitsOps.scala new file mode 100644 index 000000000000..508fdda60cec --- /dev/null +++ b/library/src/scala/tasty/reflect/ImplicitsOps.scala @@ -0,0 +1,41 @@ +package scala.tasty.reflect + +trait ImplicitsOps extends Core { + + def searchImplicit(tpe: Type)(implicit ctx: Context): ImplicitSearchResult = + kernel.searchImplicit(tpe) + + object IsImplicitSearchSuccess { + def unapply(isr: ImplicitSearchResult)(implicit ctx: Context): Option[ImplicitSearchSuccess] = + kernel.matchImplicitSearchSuccess(isr) + } + + implicit class IsImplicitSearchSuccessAPI(self: ImplicitSearchSuccess) { + def tree(implicit ctx: Context): Term = kernel.ImplicitSearchSuccess_tree(self) + } + + object IsImplicitSearchFailure { + def unapply(isr: ImplicitSearchResult)(implicit ctx: Context): Option[ImplicitSearchFailure] = + kernel.matchImplicitSearchFailure(isr) + } + + implicit class ImplicitSearchFailureAPI(self: ImplicitSearchFailure) { + def explanation(implicit ctx: Context): String = kernel.ImplicitSearchFailure_explanation(self) + } + + object IsDivergingImplicit { + def unapply(isr: ImplicitSearchResult)(implicit ctx: Context): Option[DivergingImplicit] = + kernel.matchDivergingImplicit(isr) + } + + object IsNoMatchingImplicits { + def unapply(isr: ImplicitSearchResult)(implicit ctx: Context): Option[NoMatchingImplicits] = + kernel.matchNoMatchingImplicits(isr) + } + + object IsAmbiguousImplicits { + def unapply(isr: ImplicitSearchResult)(implicit ctx: Context): Option[AmbiguousImplicits] = + kernel.matchAmbiguousImplicits(isr) + } + +} diff --git a/library/src/scala/tasty/reflect/Kernel.scala b/library/src/scala/tasty/reflect/Kernel.scala index 9d17a3094f53..9f07ae8065e9 100644 --- a/library/src/scala/tasty/reflect/Kernel.scala +++ b/library/src/scala/tasty/reflect/Kernel.scala @@ -1,5 +1,7 @@ package scala.tasty.reflect +import scala.quoted.QuoteContext + /** Tasty reflect abstract types * * ```none @@ -1506,4 +1508,35 @@ trait Kernel { def Definitions_NullType: Type def Definitions_StringType: Type + // + // IMPLICITS + // + + type ImplicitSearchResult <: AnyRef + + type ImplicitSearchSuccess <: ImplicitSearchResult + def matchImplicitSearchSuccess(isr: ImplicitSearchResult)(implicit ctx: Context): Option[ImplicitSearchSuccess] + def ImplicitSearchSuccess_tree(self: ImplicitSearchSuccess)(implicit ctx: Context): Term + + type ImplicitSearchFailure <: ImplicitSearchResult + def matchImplicitSearchFailure(isr: ImplicitSearchResult)(implicit ctx: Context): Option[ImplicitSearchFailure] + def ImplicitSearchFailure_explanation(self: ImplicitSearchFailure)(implicit ctx: Context): String + + type DivergingImplicit <: ImplicitSearchFailure + def matchDivergingImplicit(isr: ImplicitSearchResult)(implicit ctx: Context): Option[DivergingImplicit] + + type NoMatchingImplicits <: ImplicitSearchFailure + def matchNoMatchingImplicits(isr: ImplicitSearchResult)(implicit ctx: Context): Option[NoMatchingImplicits] + + type AmbiguousImplicits <: ImplicitSearchFailure + def matchAmbiguousImplicits(isr: ImplicitSearchResult)(implicit ctx: Context): Option[AmbiguousImplicits] + + /** Find an implicit of type `T` in the current scope given by `ctx`. + * Return an `ImplicitSearchResult`. + * + * @param tpe type of the implicit parameter + * @param ctx current context + */ + def searchImplicit(tpe: Type)(implicit ctx: Context): ImplicitSearchResult + } diff --git a/tests/neg-macros/delegate-match-1.check b/tests/neg-macros/delegate-match-1.check new file mode 100644 index 000000000000..f6b1c7e1b4c4 --- /dev/null +++ b/tests/neg-macros/delegate-match-1.check @@ -0,0 +1,7 @@ + +-- Error: tests/neg-macros/delegate-match-1/Test_2.scala:6:2 ----------------------------------------------------------- +6 | f // error + | ^ + | AmbiguousImplicits + | both value a1 in class Test1 and value a2 in class Test1 match type A + | This location is in code that was inlined at Test_2.scala:6 diff --git a/tests/neg-macros/delegate-match-1/Macro_1.scala b/tests/neg-macros/delegate-match-1/Macro_1.scala new file mode 100644 index 000000000000..93c9b7865993 --- /dev/null +++ b/tests/neg-macros/delegate-match-1/Macro_1.scala @@ -0,0 +1,23 @@ +import scala.quoted._ +import scala.quoted.matching._ + +inline def f: Any = ${ fImpl } + +private def fImpl given (qctx: QuoteContext): Expr[Unit] = { + import qctx.tasty._ + searchImplicit(('[A]).unseal.tpe) match { + case IsImplicitSearchSuccess(x) => + '{} + case IsDivergingImplicit(x) => '{} + error("DivergingImplicit\n" + x.explanation, rootPosition) + '{} + case IsNoMatchingImplicits(x) => + error("NoMatchingImplicits\n" + x.explanation, rootPosition) + '{} + case IsAmbiguousImplicits(x) => + error("AmbiguousImplicits\n" + x.explanation, rootPosition) + '{} + } +} + +class A diff --git a/tests/neg-macros/delegate-match-1/Test_2.scala b/tests/neg-macros/delegate-match-1/Test_2.scala new file mode 100644 index 000000000000..b84d3fd750a9 --- /dev/null +++ b/tests/neg-macros/delegate-match-1/Test_2.scala @@ -0,0 +1,8 @@ + +class Test1 extends App { + + implicit val a1: A = new A + implicit val a2: A = new A + f // error + +} diff --git a/tests/neg-macros/delegate-match-2.check b/tests/neg-macros/delegate-match-2.check new file mode 100644 index 000000000000..20b358a4d3b9 --- /dev/null +++ b/tests/neg-macros/delegate-match-2.check @@ -0,0 +1,7 @@ + +-- Error: tests/neg-macros/delegate-match-2/Test_2.scala:5:2 ----------------------------------------------------------- +5 | f // error + | ^ + | DivergingImplicit + | method a1 in class Test produces a diverging implicit search when trying to match type A + | This location is in code that was inlined at Test_2.scala:5 diff --git a/tests/neg-macros/delegate-match-2/Macro_1.scala b/tests/neg-macros/delegate-match-2/Macro_1.scala new file mode 100644 index 000000000000..93c9b7865993 --- /dev/null +++ b/tests/neg-macros/delegate-match-2/Macro_1.scala @@ -0,0 +1,23 @@ +import scala.quoted._ +import scala.quoted.matching._ + +inline def f: Any = ${ fImpl } + +private def fImpl given (qctx: QuoteContext): Expr[Unit] = { + import qctx.tasty._ + searchImplicit(('[A]).unseal.tpe) match { + case IsImplicitSearchSuccess(x) => + '{} + case IsDivergingImplicit(x) => '{} + error("DivergingImplicit\n" + x.explanation, rootPosition) + '{} + case IsNoMatchingImplicits(x) => + error("NoMatchingImplicits\n" + x.explanation, rootPosition) + '{} + case IsAmbiguousImplicits(x) => + error("AmbiguousImplicits\n" + x.explanation, rootPosition) + '{} + } +} + +class A diff --git a/tests/neg-macros/delegate-match-2/Test_2.scala b/tests/neg-macros/delegate-match-2/Test_2.scala new file mode 100644 index 000000000000..a29bc3759155 --- /dev/null +++ b/tests/neg-macros/delegate-match-2/Test_2.scala @@ -0,0 +1,7 @@ + +class Test extends App { + + implicit def a1(implicit a: A): A = new A + f // error + +} diff --git a/tests/neg-macros/delegate-match-3.check b/tests/neg-macros/delegate-match-3.check new file mode 100644 index 000000000000..55c24808f416 --- /dev/null +++ b/tests/neg-macros/delegate-match-3.check @@ -0,0 +1,7 @@ + +-- Error: tests/neg-macros/delegate-match-3/Test_2.scala:3:2 ----------------------------------------------------------- +3 | f // error + | ^ + | NoMatchingImplicits + | no implicit values were found that match type A + | This location is in code that was inlined at Test_2.scala:3 diff --git a/tests/neg-macros/delegate-match-3/Macro_1.scala b/tests/neg-macros/delegate-match-3/Macro_1.scala new file mode 100644 index 000000000000..93c9b7865993 --- /dev/null +++ b/tests/neg-macros/delegate-match-3/Macro_1.scala @@ -0,0 +1,23 @@ +import scala.quoted._ +import scala.quoted.matching._ + +inline def f: Any = ${ fImpl } + +private def fImpl given (qctx: QuoteContext): Expr[Unit] = { + import qctx.tasty._ + searchImplicit(('[A]).unseal.tpe) match { + case IsImplicitSearchSuccess(x) => + '{} + case IsDivergingImplicit(x) => '{} + error("DivergingImplicit\n" + x.explanation, rootPosition) + '{} + case IsNoMatchingImplicits(x) => + error("NoMatchingImplicits\n" + x.explanation, rootPosition) + '{} + case IsAmbiguousImplicits(x) => + error("AmbiguousImplicits\n" + x.explanation, rootPosition) + '{} + } +} + +class A diff --git a/tests/neg-macros/delegate-match-3/Test_2.scala b/tests/neg-macros/delegate-match-3/Test_2.scala new file mode 100644 index 000000000000..ecd03189e964 --- /dev/null +++ b/tests/neg-macros/delegate-match-3/Test_2.scala @@ -0,0 +1,4 @@ + +class Test extends App { + f // error +} diff --git a/tests/run-macros/quote-implicitMatch.check b/tests/run-macros/quote-implicitMatch.check new file mode 100644 index 000000000000..2e0c093c74d4 --- /dev/null +++ b/tests/run-macros/quote-implicitMatch.check @@ -0,0 +1,3 @@ +class scala.collection.immutable.TreeSet +class scala.collection.immutable.HashSet +B diff --git a/tests/run-macros/quote-implicitMatch/Macro_1.scala b/tests/run-macros/quote-implicitMatch/Macro_1.scala new file mode 100644 index 000000000000..b36ed0c09552 --- /dev/null +++ b/tests/run-macros/quote-implicitMatch/Macro_1.scala @@ -0,0 +1,24 @@ +import collection.immutable.TreeSet +import collection.immutable.HashSet +import scala.quoted._ +import scala.quoted.matching._ + +inline def f1[T]() = ${ f1Impl[T] } + +def f1Impl[T: Type] given QuoteContext = { + searchImplicitExpr[Ordering[T]] match { + case Some(ord) => '{ new TreeSet[T]()($ord) } + case _ => '{ new HashSet[T] } + } +} + +class A +class B + +inline def g = ${ gImpl } + +def gImpl given QuoteContext = { + if (searchImplicitExpr[A].isDefined) '{ println("A") } + else if (searchImplicitExpr[B].isDefined) '{ println("B") } + else throw new MatchError("") +} diff --git a/tests/run-macros/quote-implicitMatch/Test_2.scala b/tests/run-macros/quote-implicitMatch/Test_2.scala new file mode 100644 index 000000000000..f03f31cf9304 --- /dev/null +++ b/tests/run-macros/quote-implicitMatch/Test_2.scala @@ -0,0 +1,12 @@ +object Test extends App { + + implicitly[Ordering[String]] + + println(f1[String]().getClass) + println(f1[AnyRef]().getClass) + + implicit val b: B = new B + implicitly[B] + g + +} \ No newline at end of file