Skip to content

Commit 863872b

Browse files
authored
Merge pull request #6819 from dotty-staging/quote-implicit-search
Allow to search for implicits in macros
2 parents 7c33774 + 041647c commit 863872b

File tree

19 files changed

+317
-1
lines changed

19 files changed

+317
-1
lines changed

compiler/src/dotty/tools/dotc/tastyreflect/KernelImpl.scala

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ package tastyreflect
44
import dotty.tools.dotc.ast.Trees.SeqLiteral
55
import dotty.tools.dotc.ast.{Trees, tpd, untpd}
66
import dotty.tools.dotc.ast.tpd.TreeOps
7-
import dotty.tools.dotc.typer.Typer
7+
import dotty.tools.dotc.typer.{Implicits, Typer}
88
import dotty.tools.dotc.core._
99
import dotty.tools.dotc.core.Flags._
1010
import dotty.tools.dotc.core.StdNames.nme
@@ -13,6 +13,7 @@ import dotty.tools.dotc.core.Symbols._
1313
import dotty.tools.dotc.core.Decorators._
1414
import dotty.tools.dotc.tastyreflect.FromSymbol.{definitionFromSym, packageDefFromSym}
1515
import dotty.tools.dotc.parsing.Parsers.Parser
16+
import dotty.tools.dotc.typer.Implicits.{AmbiguousImplicits, DivergingImplicit, NoMatchingImplicits, SearchFailure, SearchFailureType}
1617
import dotty.tools.dotc.util.SourceFile
1718

1819
import scala.tasty.reflect.Kernel
@@ -1849,6 +1850,48 @@ class KernelImpl(val rootContext: core.Contexts.Context, val rootPosition: util.
18491850
def Definitions_NullType: Type = defn.NullType
18501851
def Definitions_StringType: Type = defn.StringType
18511852

1853+
//
1854+
// IMPLICITS
1855+
//
1856+
1857+
type ImplicitSearchResult = Tree
1858+
1859+
def searchImplicit(tpe: Type)(implicit ctx: Context): ImplicitSearchResult =
1860+
ctx.typer.inferImplicitArg(tpe, rootPosition.span)
1861+
1862+
type ImplicitSearchSuccess = Tree
1863+
def matchImplicitSearchSuccess(isr: ImplicitSearchResult)(implicit ctx: Context): Option[ImplicitSearchSuccess] = isr.tpe match {
1864+
case _: SearchFailureType => None
1865+
case _ => Some(isr)
1866+
}
1867+
def ImplicitSearchSuccess_tree(self: ImplicitSearchSuccess)(implicit ctx: Context): Term = self
1868+
1869+
type ImplicitSearchFailure = Tree
1870+
def matchImplicitSearchFailure(isr: ImplicitSearchResult)(implicit ctx: Context): Option[ImplicitSearchFailure] = isr.tpe match {
1871+
case _: SearchFailureType => Some(isr)
1872+
case _ => None
1873+
}
1874+
def ImplicitSearchFailure_explanation(self: ImplicitSearchFailure)(implicit ctx: Context): String =
1875+
self.tpe.asInstanceOf[SearchFailureType].explanation
1876+
1877+
type DivergingImplicit = Tree
1878+
def matchDivergingImplicit(isr: ImplicitSearchResult)(implicit ctx: Context): Option[DivergingImplicit] = isr.tpe match {
1879+
case _: Implicits.DivergingImplicit => Some(isr)
1880+
case _ => None
1881+
}
1882+
1883+
type NoMatchingImplicits = Tree
1884+
def matchNoMatchingImplicits(isr: ImplicitSearchResult)(implicit ctx: Context): Option[NoMatchingImplicits] = isr.tpe match {
1885+
case _: Implicits.NoMatchingImplicits => Some(isr)
1886+
case _ => None
1887+
}
1888+
1889+
type AmbiguousImplicits = Tree
1890+
def matchAmbiguousImplicits(isr: ImplicitSearchResult)(implicit ctx: Context): Option[AmbiguousImplicits] = isr.tpe match {
1891+
case _: Implicits.AmbiguousImplicits => Some(isr)
1892+
case _ => None
1893+
}
1894+
18521895
//
18531896
// HELPERS
18541897
//

docs/docs/reference/metaprogramming/macros.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -569,6 +569,22 @@ while (i < arr.length) {
569569
sum
570570
```
571571

572+
### Find implicits within a macro
573+
574+
Similarly to the `implicit match` construct, it is possible to make implicit search available
575+
in a quote context. For this we simply provide `scala.quoted.matching.searchImplicitExpr:
576+
577+
```scala
578+
inline def setFor[T]: Set[T] = ${ setForExpr[T] }
579+
580+
def setForExpr[T: Type] given QuoteContext: Expr[Set[T]] = {
581+
searchImplicitExpr[Ordering[T]] match {
582+
case Some(ord) => '{ new TreeSet[T]()($ord) }
583+
case _ => '{ new HashSet[T] }
584+
}
585+
}
586+
```
587+
572588
### Relationship with Whitebox Inline
573589

574590
[Inline](./inline.md) documents inlining. The code below introduces a whitebox
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package scala.quoted
2+
3+
package object matching {
4+
5+
/** Find an implicit of type `T` in the current scope given by `qctx`.
6+
* Return `Some` containing the expression of the implicit or
7+
* `None` if implicit resolution failed.
8+
*
9+
* @tparam T type of the implicit parameter
10+
* @param tpe quoted type of the implicit parameter
11+
* @param qctx current context
12+
*/
13+
def searchImplicitExpr[T] given (tpe: Type[T], qctx: QuoteContext): Option[Expr[T]] = {
14+
import qctx.tasty._
15+
searchImplicit(tpe.unseal.tpe) match {
16+
case IsImplicitSearchSuccess(iss) => Some(iss.tree.seal.asInstanceOf[Expr[T]])
17+
case IsImplicitSearchFailure(isf) => None
18+
}
19+
}
20+
21+
}

library/src/scala/tasty/Reflection.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ class Reflection(val kernel: Kernel)
99
with CommentOps
1010
with FlagsOps
1111
with IdOps
12+
with ImplicitsOps
1213
with ImportSelectorOps
1314
with QuotedOps
1415
with PatternOps

library/src/scala/tasty/reflect/Core.scala

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -456,4 +456,17 @@ trait Core {
456456

457457
/** FlagSet of a Symbol */
458458
type Flags = kernel.Flags
459+
460+
type ImplicitSearchResult = kernel.ImplicitSearchResult
461+
462+
type ImplicitSearchSuccess = kernel.ImplicitSearchSuccess
463+
464+
type ImplicitSearchFailure = kernel.ImplicitSearchFailure
465+
466+
type DivergingImplicit = kernel.DivergingImplicit
467+
468+
type NoMatchingImplicits = kernel.NoMatchingImplicits
469+
470+
type AmbiguousImplicits = kernel.AmbiguousImplicits
471+
459472
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package scala.tasty.reflect
2+
3+
trait ImplicitsOps extends Core {
4+
5+
def searchImplicit(tpe: Type)(implicit ctx: Context): ImplicitSearchResult =
6+
kernel.searchImplicit(tpe)
7+
8+
object IsImplicitSearchSuccess {
9+
def unapply(isr: ImplicitSearchResult)(implicit ctx: Context): Option[ImplicitSearchSuccess] =
10+
kernel.matchImplicitSearchSuccess(isr)
11+
}
12+
13+
implicit class IsImplicitSearchSuccessAPI(self: ImplicitSearchSuccess) {
14+
def tree(implicit ctx: Context): Term = kernel.ImplicitSearchSuccess_tree(self)
15+
}
16+
17+
object IsImplicitSearchFailure {
18+
def unapply(isr: ImplicitSearchResult)(implicit ctx: Context): Option[ImplicitSearchFailure] =
19+
kernel.matchImplicitSearchFailure(isr)
20+
}
21+
22+
implicit class ImplicitSearchFailureAPI(self: ImplicitSearchFailure) {
23+
def explanation(implicit ctx: Context): String = kernel.ImplicitSearchFailure_explanation(self)
24+
}
25+
26+
object IsDivergingImplicit {
27+
def unapply(isr: ImplicitSearchResult)(implicit ctx: Context): Option[DivergingImplicit] =
28+
kernel.matchDivergingImplicit(isr)
29+
}
30+
31+
object IsNoMatchingImplicits {
32+
def unapply(isr: ImplicitSearchResult)(implicit ctx: Context): Option[NoMatchingImplicits] =
33+
kernel.matchNoMatchingImplicits(isr)
34+
}
35+
36+
object IsAmbiguousImplicits {
37+
def unapply(isr: ImplicitSearchResult)(implicit ctx: Context): Option[AmbiguousImplicits] =
38+
kernel.matchAmbiguousImplicits(isr)
39+
}
40+
41+
}

library/src/scala/tasty/reflect/Kernel.scala

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package scala.tasty.reflect
22

3+
import scala.quoted.QuoteContext
4+
35
/** Tasty reflect abstract types
46
*
57
* ```none
@@ -1506,4 +1508,35 @@ trait Kernel {
15061508
def Definitions_NullType: Type
15071509
def Definitions_StringType: Type
15081510

1511+
//
1512+
// IMPLICITS
1513+
//
1514+
1515+
type ImplicitSearchResult <: AnyRef
1516+
1517+
type ImplicitSearchSuccess <: ImplicitSearchResult
1518+
def matchImplicitSearchSuccess(isr: ImplicitSearchResult)(implicit ctx: Context): Option[ImplicitSearchSuccess]
1519+
def ImplicitSearchSuccess_tree(self: ImplicitSearchSuccess)(implicit ctx: Context): Term
1520+
1521+
type ImplicitSearchFailure <: ImplicitSearchResult
1522+
def matchImplicitSearchFailure(isr: ImplicitSearchResult)(implicit ctx: Context): Option[ImplicitSearchFailure]
1523+
def ImplicitSearchFailure_explanation(self: ImplicitSearchFailure)(implicit ctx: Context): String
1524+
1525+
type DivergingImplicit <: ImplicitSearchFailure
1526+
def matchDivergingImplicit(isr: ImplicitSearchResult)(implicit ctx: Context): Option[DivergingImplicit]
1527+
1528+
type NoMatchingImplicits <: ImplicitSearchFailure
1529+
def matchNoMatchingImplicits(isr: ImplicitSearchResult)(implicit ctx: Context): Option[NoMatchingImplicits]
1530+
1531+
type AmbiguousImplicits <: ImplicitSearchFailure
1532+
def matchAmbiguousImplicits(isr: ImplicitSearchResult)(implicit ctx: Context): Option[AmbiguousImplicits]
1533+
1534+
/** Find an implicit of type `T` in the current scope given by `ctx`.
1535+
* Return an `ImplicitSearchResult`.
1536+
*
1537+
* @param tpe type of the implicit parameter
1538+
* @param ctx current context
1539+
*/
1540+
def searchImplicit(tpe: Type)(implicit ctx: Context): ImplicitSearchResult
1541+
15091542
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
2+
-- Error: tests/neg-macros/delegate-match-1/Test_2.scala:6:2 -----------------------------------------------------------
3+
6 | f // error
4+
| ^
5+
| AmbiguousImplicits
6+
| both value a1 in class Test1 and value a2 in class Test1 match type A
7+
| This location is in code that was inlined at Test_2.scala:6
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import scala.quoted._
2+
import scala.quoted.matching._
3+
4+
inline def f: Any = ${ fImpl }
5+
6+
private def fImpl given (qctx: QuoteContext): Expr[Unit] = {
7+
import qctx.tasty._
8+
searchImplicit(('[A]).unseal.tpe) match {
9+
case IsImplicitSearchSuccess(x) =>
10+
'{}
11+
case IsDivergingImplicit(x) => '{}
12+
error("DivergingImplicit\n" + x.explanation, rootPosition)
13+
'{}
14+
case IsNoMatchingImplicits(x) =>
15+
error("NoMatchingImplicits\n" + x.explanation, rootPosition)
16+
'{}
17+
case IsAmbiguousImplicits(x) =>
18+
error("AmbiguousImplicits\n" + x.explanation, rootPosition)
19+
'{}
20+
}
21+
}
22+
23+
class A
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
2+
class Test1 extends App {
3+
4+
implicit val a1: A = new A
5+
implicit val a2: A = new A
6+
f // error
7+
8+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
2+
-- Error: tests/neg-macros/delegate-match-2/Test_2.scala:5:2 -----------------------------------------------------------
3+
5 | f // error
4+
| ^
5+
| DivergingImplicit
6+
| method a1 in class Test produces a diverging implicit search when trying to match type A
7+
| This location is in code that was inlined at Test_2.scala:5
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import scala.quoted._
2+
import scala.quoted.matching._
3+
4+
inline def f: Any = ${ fImpl }
5+
6+
private def fImpl given (qctx: QuoteContext): Expr[Unit] = {
7+
import qctx.tasty._
8+
searchImplicit(('[A]).unseal.tpe) match {
9+
case IsImplicitSearchSuccess(x) =>
10+
'{}
11+
case IsDivergingImplicit(x) => '{}
12+
error("DivergingImplicit\n" + x.explanation, rootPosition)
13+
'{}
14+
case IsNoMatchingImplicits(x) =>
15+
error("NoMatchingImplicits\n" + x.explanation, rootPosition)
16+
'{}
17+
case IsAmbiguousImplicits(x) =>
18+
error("AmbiguousImplicits\n" + x.explanation, rootPosition)
19+
'{}
20+
}
21+
}
22+
23+
class A
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
2+
class Test extends App {
3+
4+
implicit def a1(implicit a: A): A = new A
5+
f // error
6+
7+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
2+
-- Error: tests/neg-macros/delegate-match-3/Test_2.scala:3:2 -----------------------------------------------------------
3+
3 | f // error
4+
| ^
5+
| NoMatchingImplicits
6+
| no implicit values were found that match type A
7+
| This location is in code that was inlined at Test_2.scala:3
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import scala.quoted._
2+
import scala.quoted.matching._
3+
4+
inline def f: Any = ${ fImpl }
5+
6+
private def fImpl given (qctx: QuoteContext): Expr[Unit] = {
7+
import qctx.tasty._
8+
searchImplicit(('[A]).unseal.tpe) match {
9+
case IsImplicitSearchSuccess(x) =>
10+
'{}
11+
case IsDivergingImplicit(x) => '{}
12+
error("DivergingImplicit\n" + x.explanation, rootPosition)
13+
'{}
14+
case IsNoMatchingImplicits(x) =>
15+
error("NoMatchingImplicits\n" + x.explanation, rootPosition)
16+
'{}
17+
case IsAmbiguousImplicits(x) =>
18+
error("AmbiguousImplicits\n" + x.explanation, rootPosition)
19+
'{}
20+
}
21+
}
22+
23+
class A
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
2+
class Test extends App {
3+
f // error
4+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
class scala.collection.immutable.TreeSet
2+
class scala.collection.immutable.HashSet
3+
B
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import collection.immutable.TreeSet
2+
import collection.immutable.HashSet
3+
import scala.quoted._
4+
import scala.quoted.matching._
5+
6+
inline def f1[T]() = ${ f1Impl[T] }
7+
8+
def f1Impl[T: Type] given QuoteContext = {
9+
searchImplicitExpr[Ordering[T]] match {
10+
case Some(ord) => '{ new TreeSet[T]()($ord) }
11+
case _ => '{ new HashSet[T] }
12+
}
13+
}
14+
15+
class A
16+
class B
17+
18+
inline def g = ${ gImpl }
19+
20+
def gImpl given QuoteContext = {
21+
if (searchImplicitExpr[A].isDefined) '{ println("A") }
22+
else if (searchImplicitExpr[B].isDefined) '{ println("B") }
23+
else throw new MatchError("")
24+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
object Test extends App {
2+
3+
implicitly[Ordering[String]]
4+
5+
println(f1[String]().getClass)
6+
println(f1[AnyRef]().getClass)
7+
8+
implicit val b: B = new B
9+
implicitly[B]
10+
g
11+
12+
}

0 commit comments

Comments
 (0)