Skip to content

Allow to search for implicits in macros #6819

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

Merged
merged 1 commit into from
Jul 9, 2019
Merged
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
45 changes: 44 additions & 1 deletion compiler/src/dotty/tools/dotc/tastyreflect/KernelImpl.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
//
Expand Down
16 changes: 16 additions & 0 deletions docs/docs/reference/metaprogramming/macros.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
21 changes: 21 additions & 0 deletions library/src/scala/quoted/matching/package.scala
Original file line number Diff line number Diff line change
@@ -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
}
}

}
1 change: 1 addition & 0 deletions library/src/scala/tasty/Reflection.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ class Reflection(val kernel: Kernel)
with CommentOps
with FlagsOps
with IdOps
with ImplicitsOps
with ImportSelectorOps
with QuotedOps
with PatternOps
Expand Down
13 changes: 13 additions & 0 deletions library/src/scala/tasty/reflect/Core.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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

}
41 changes: 41 additions & 0 deletions library/src/scala/tasty/reflect/ImplicitsOps.scala
Original file line number Diff line number Diff line change
@@ -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)
}

}
33 changes: 33 additions & 0 deletions library/src/scala/tasty/reflect/Kernel.scala
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package scala.tasty.reflect

import scala.quoted.QuoteContext

/** Tasty reflect abstract types
*
* ```none
Expand Down Expand Up @@ -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

}
7 changes: 7 additions & 0 deletions tests/neg-macros/delegate-match-1.check
Original file line number Diff line number Diff line change
@@ -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
23 changes: 23 additions & 0 deletions tests/neg-macros/delegate-match-1/Macro_1.scala
Original file line number Diff line number Diff line change
@@ -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
8 changes: 8 additions & 0 deletions tests/neg-macros/delegate-match-1/Test_2.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@

class Test1 extends App {

implicit val a1: A = new A
implicit val a2: A = new A
f // error

}
7 changes: 7 additions & 0 deletions tests/neg-macros/delegate-match-2.check
Original file line number Diff line number Diff line change
@@ -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
23 changes: 23 additions & 0 deletions tests/neg-macros/delegate-match-2/Macro_1.scala
Original file line number Diff line number Diff line change
@@ -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
7 changes: 7 additions & 0 deletions tests/neg-macros/delegate-match-2/Test_2.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@

class Test extends App {

implicit def a1(implicit a: A): A = new A
f // error

}
7 changes: 7 additions & 0 deletions tests/neg-macros/delegate-match-3.check
Original file line number Diff line number Diff line change
@@ -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
23 changes: 23 additions & 0 deletions tests/neg-macros/delegate-match-3/Macro_1.scala
Original file line number Diff line number Diff line change
@@ -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
4 changes: 4 additions & 0 deletions tests/neg-macros/delegate-match-3/Test_2.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@

class Test extends App {
f // error
}
3 changes: 3 additions & 0 deletions tests/run-macros/quote-implicitMatch.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
class scala.collection.immutable.TreeSet
class scala.collection.immutable.HashSet
B
24 changes: 24 additions & 0 deletions tests/run-macros/quote-implicitMatch/Macro_1.scala
Original file line number Diff line number Diff line change
@@ -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("")
}
12 changes: 12 additions & 0 deletions tests/run-macros/quote-implicitMatch/Test_2.scala
Original file line number Diff line number Diff line change
@@ -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

}