Skip to content

Require QuoteContext for all quotes #6769

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
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
33 changes: 21 additions & 12 deletions compiler/src/dotty/tools/dotc/transform/Splicer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@ object Splicer {
/** Tree interpreter that evaluates the tree */
private class Interpreter(pos: SourcePosition, classLoader: ClassLoader)(implicit ctx: Context) extends AbstractInterpreter {

def checking: Boolean = false

type Result = Object

/** Returns the interpreted result of interpreting the code a call to the symbol with default arguments.
Expand Down Expand Up @@ -277,6 +279,7 @@ object Splicer {

/** Tree interpreter that tests if tree can be interpreted */
private class CheckValidMacroBody(implicit ctx: Context) extends AbstractInterpreter {
def checking: Boolean = true

type Result = Unit

Expand Down Expand Up @@ -312,6 +315,9 @@ object Splicer {

/** Abstract Tree interpreter that can interpret calls to static methods with quoted or inline arguments */
private abstract class AbstractInterpreter(implicit ctx: Context) {

def checking: Boolean

type Env = Map[Name, Result]
type Result

Expand Down Expand Up @@ -370,20 +376,10 @@ object Splicer {

// Interpret `foo(j = x, i = y)` which it is expanded to
// `val j$1 = x; val i$1 = y; foo(i = y, j = x)`
case Block(stats, expr) =>
var unexpected: Option[Result] = None
val newEnv = stats.foldLeft(env)((accEnv, stat) => stat match {
case stat: ValDef if stat.symbol.is(Synthetic) =>
accEnv.updated(stat.name, interpretTree(stat.rhs)(accEnv))
case stat =>
if (unexpected.isEmpty)
unexpected = Some(unexpectedTree(stat))
accEnv
})
unexpected.getOrElse(interpretTree(expr)(newEnv))
case Block(stats, expr) => interpretBlock(stats, expr)
case NamedArg(_, arg) => interpretTree(arg)

case Inlined(_, Nil, expansion) => interpretTree(expansion)
case Inlined(_, bindings, expansion) => interpretBlock(bindings, expansion)

case Typed(expr, _) =>
interpretTree(expr)
Expand All @@ -395,6 +391,19 @@ object Splicer {
unexpectedTree(tree)
}

private def interpretBlock(stats: List[Tree], expr: Tree)(implicit env: Env) = {
var unexpected: Option[Result] = None
val newEnv = stats.foldLeft(env)((accEnv, stat) => stat match {
case stat: ValDef if stat.symbol.is(Synthetic) || !checking =>
accEnv.updated(stat.name, interpretTree(stat.rhs)(accEnv))
case stat =>
if (unexpected.isEmpty)
unexpected = Some(unexpectedTree(stat))
accEnv
})
unexpected.getOrElse(interpretTree(expr)(newEnv))
}

object Call {
def unapply(arg: Tree): Option[(RefTree, List[Tree])] = arg match {
case Select(Call(fn, args), nme.apply) if defn.isImplicitFunctionType(fn.tpe.widenDealias.finalResultType) =>
Expand Down
21 changes: 12 additions & 9 deletions compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1962,15 +1962,18 @@ class Typer extends Namer
case untpd.TypSplice(innerType) if tree.isType =>
ctx.warning("Canceled splice directly inside a quote. '[ ${ XYZ } ] is equivalent to XYZ.", tree.sourcePos)
typed(innerType, pt)
case quoted if quoted.isType =>
ctx.compilationUnit.needsStaging = true
typedTypeApply(untpd.TypeApply(untpd.ref(defn.InternalQuoted_typeQuoteR), quoted :: Nil), pt)(quoteContext).withSpan(tree.span)
case quoted =>
ctx.compilationUnit.needsStaging = true
if (ctx.mode.is(Mode.Pattern) && level == 0)
typedQuotePattern(quoted, pt, tree.span)
else
typedApply(untpd.Apply(untpd.ref(defn.InternalQuoted_exprQuoteR), quoted), pt)(quoteContext).withSpan(tree.span)

val qctx = inferImplicitArg(defn.QuoteContextType, tree.span)
if (level == 0 && qctx.tpe.isInstanceOf[SearchFailureType])
ctx.error(missingArgMsg(qctx, defn.QuoteContextType, ""), ctx.source.atSpan(tree.span))

val tree1 =
if (quoted.isType) typedTypeApply(untpd.TypeApply(untpd.ref(defn.InternalQuoted_typeQuoteR), quoted :: Nil), pt)(quoteContext)
else if (ctx.mode.is(Mode.Pattern) && level == 0) typedQuotePattern(quoted, pt, qctx)
else typedApply(untpd.Apply(untpd.ref(defn.InternalQuoted_exprQuoteR), quoted), pt)(quoteContext)
tree1.withSpan(tree.span)
}
}

Expand Down Expand Up @@ -2019,7 +2022,7 @@ class Typer extends Namer
* ) => ...
* ```
*/
private def typedQuotePattern(quoted: untpd.Tree, pt: Type, quoteSpan: Span)(implicit ctx: Context): Tree = {
private def typedQuotePattern(quoted: untpd.Tree, pt: Type, qctx: tpd.Tree)(implicit ctx: Context): Tree = {
val exprPt = pt.baseType(defn.QuotedExprClass)
val quotedPt = if (exprPt.exists) exprPt.argTypesHi.head else defn.AnyType
val quoted1 = typedExpr(quoted, quotedPt)(quoteContext.addMode(Mode.QuotedPattern))
Expand Down Expand Up @@ -2068,7 +2071,7 @@ class Typer extends Namer
implicits =
ref(defn.InternalQuoted_exprQuoteR).appliedToType(shape.tpe).appliedTo(shape) ::
Literal(Constant(typeBindings.nonEmpty)) ::
implicitArgTree(defn.QuoteContextType, quoteSpan) :: Nil,
qctx :: Nil,
patterns = splicePat :: Nil,
proto = pt)
}
Expand Down
5 changes: 3 additions & 2 deletions compiler/test-resources/repl-macros/i6007
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
scala> import scala.quoted._
scala> implicit def toolbox: scala.quoted.Toolbox = scala.quoted.Toolbox.make(getClass.getClassLoader)
def toolbox: quoted.Toolbox
scala> val v = '{ (if true then Some(1) else None).map(v => v+1) }
val v: quoted.Expr[Option[Int]] = Expr(<pickled tasty>)
scala> def v given QuoteContext = '{ (if true then Some(1) else None).map(v => v+1) }
def v given (x$1: quoted.QuoteContext): quoted.Expr[Option[Int]]
scala> scala.quoted.withQuoteContext(v.show)
val res0: String = (if (true) scala.Some.apply[scala.Int](1) else scala.None).map[scala.Int](((v: scala.Int) => v.+(1)))
scala> scala.quoted.run(v)
Expand Down
8 changes: 4 additions & 4 deletions compiler/test-resources/repl/i5551
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
scala> import scala.quoted._

scala> def assertImpl(expr: Expr[Boolean]) = '{ if !($expr) then throw new AssertionError("failed assertion")}
def assertImpl(expr: quoted.Expr[Boolean]): quoted.Expr[Unit]

scala> def assertImpl(expr: Expr[Boolean]) given (qctx: QuoteContext) = '{ if !($expr) then throw new AssertionError("failed assertion")}
def assertImpl
(expr: quoted.Expr[Boolean])
given (qctx: quoted.QuoteContext): quoted.Expr[Unit]
scala> inline def assert(expr: => Boolean): Unit = ${ assertImpl('{expr}) }
def assert(expr: => Boolean): Unit

Expand Down
1 change: 1 addition & 0 deletions library/src-3.x/scala/tasty/reflect/utils/TreeUtils.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ trait TreeUtils {

/** Bind the `rhs` to a `val` and use it in `body` */
def let(rhs: Term)(body: Ident => Term): Term = {
delegate for QuoteContext = new QuoteContext(reflect)
type T // TODO probably it is better to use the Sealed contruct rather than let the user create their own existential type
implicit val rhsTpe: quoted.Type[T] = rhs.tpe.seal.asInstanceOf[quoted.Type[T]]
val rhsExpr = rhs.seal.cast[T]
Expand Down
2 changes: 1 addition & 1 deletion library/src-bootstrapped/scala/quoted/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ package object quoted {
implicit object ExprOps {
def (x: T) toExpr[T: Liftable] given QuoteContext: Expr[T] = the[Liftable[T]].toExpr(x)

def (list: List[Expr[T]]) toExprOfList[T] given Type[T]: Expr[List[T]] = list match {
def (list: List[Expr[T]]) toExprOfList[T: Type] given QuoteContext: Expr[List[T]] = list match {
case x :: xs => '{ $x :: ${xs.toExprOfList} }
case Nil => '{ Nil }
}
Expand Down
4 changes: 3 additions & 1 deletion tests/neg-custom-args/fatal-warnings/quote-simple-hole.scala
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
class Test {
import scala.quoted.QuoteContext

def test given QuoteContext = {
val x = '{0}
val y = '{ // error: Canceled splice directly inside a quote. '{ ${ XYZ } } is equivalent to XYZ.
$x
Expand Down
2 changes: 1 addition & 1 deletion tests/neg-macros/quote-complex-top-splice.scala
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,6 @@ object Test {
impl(1)
}

def impl(i: Int): Expr[Unit] = '{}
def impl(i: Int) given QuoteContext: Expr[Unit] = '{}

}
2 changes: 1 addition & 1 deletion tests/neg-macros/quote-error/Macro_1.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import quoted._

object Macro_1 {
inline def foo(inline b: Boolean): Unit = ${fooImpl(b)}
def fooImpl(b: Boolean): Expr[Unit] =
def fooImpl(b: Boolean) given QuoteContext: Expr[Unit] =
if (b) '{println("foo(true)")}
else QuoteError("foo cannot be called with false")
}
2 changes: 1 addition & 1 deletion tests/neg-macros/quote-exception/Macro_1.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import quoted._

object Macro_1 {
inline def foo(inline b: Boolean): Unit = ${fooImpl(b)}
def fooImpl(b: Boolean): Expr[Unit] =
def fooImpl(b: Boolean) given QuoteContext: Expr[Unit] =
if (b) '{println("foo(true)")}
else ???
}
2 changes: 1 addition & 1 deletion tests/neg-macros/quote-this-b.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@ class Foo {
}

object Foo {
def impl[T](x: Any): Expr[Unit] = '{}
def impl[T](x: Any) given QuoteContext: Expr[Unit] = '{}
}
4 changes: 2 additions & 2 deletions tests/neg-macros/quote-this.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import scala.quoted._

class Foo {

def f: Unit = '{
def f given QuoteContext: Unit = '{
def bar[T](x: T): T = x
bar[
this.type // error
Expand All @@ -22,5 +22,5 @@ class Foo {
}

object Foo {
def impl[T](x: Any): Expr[Unit] = '{}
def impl[T](x: Any) given QuoteContext: Expr[Unit] = '{}
}
2 changes: 1 addition & 1 deletion tests/neg-macros/splice-in-top-level-splice-1.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@ import scala.quoted.autolift._

object Foo {
inline def foo(): Int = ${bar(${x})} // error
def x: Expr[Int] = '{1}
def x given QuoteContext: Expr[Int] = '{1}
def bar(i: Int) given QuoteContext: Expr[Int] = i
}
2 changes: 1 addition & 1 deletion tests/neg-macros/splice-in-top-level-splice-2.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@ import scala.quoted._

object Foo {
inline def foo(): Int = ${$x} // error
def x: Expr[Expr[Int]] = '{ '{1} }
def x given QuoteContext: Expr[Expr[Int]] = '{ '{1} }
}
2 changes: 1 addition & 1 deletion tests/neg/i4044a.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import scala.quoted._

class Test {
def test given QuoteContext = {

val a = '{1}
'{
Expand Down
2 changes: 1 addition & 1 deletion tests/neg/i4044b.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import scala.quoted._

class Test {
def test given QuoteContext = {

'{

Expand Down
2 changes: 1 addition & 1 deletion tests/neg/i4774b.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import scala.quoted._

object Test {
def loop[T](x: Expr[T])(implicit t: Type[T]): Expr[T] = '{
def loop[T](x: Expr[T])(implicit t: Type[T], qctx: QuoteContext): Expr[T] = '{
val y: $t = $x;
${loop[$t]( // error
'y
Expand Down
3 changes: 2 additions & 1 deletion tests/neg/i6530b.scala
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import scala.quoted._
object Foo {
val program = '{
def program given QuoteContext = '{
val tpe: quoted.Type[Int] = ???
val expr: quoted.Expr[Int] = ???

Expand Down
2 changes: 1 addition & 1 deletion tests/neg/quote-0.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import scala.quoted._

class Test {
def test given QuoteContext = {

val x: Int = 0

Expand Down
4 changes: 2 additions & 2 deletions tests/neg/quote-1.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ import scala.quoted._

class Test {

def f[T](t: Type[T], x: Expr[T]) = '{
def f[T](t: Type[T], x: Expr[T]) given QuoteContext = '{
val z2 = $x // error // error: wrong staging level
}

def g[T](implicit t: Type[T], x: Expr[T]) = '{
def g[T](implicit t: Type[T], x: Expr[T], qctx: QuoteContext) = '{
val z2 = $x // ok
}

Expand Down
2 changes: 1 addition & 1 deletion tests/neg/quote-macro-2-splices.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@ object Macro {
else ${ bar(false) }
}

def bar(b: Boolean): Expr[Int] = if (b) '{1} else '{0}
def bar(b: Boolean) given QuoteContext: Expr[Int] = if (b) '{1} else '{0}
}
2 changes: 1 addition & 1 deletion tests/neg/quote-spliceNonStaged.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@ package quotes
import scala.quoted._

object Quotes_1 {
def printHello: Expr[Unit] = '{ println("Hello") }
def printHello given QuoteContext: Expr[Unit] = '{ println("Hello") }
$printHello // error
}
5 changes: 3 additions & 2 deletions tests/neg/splice-non-expr.scala
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import scala.quoted._
class Foo {
'{
def test given QuoteContext = '{
${3} // error
${new Object} // error
${"abc"} // error
${()} // error
${new Foo} // error
}

def unary_~ : Int = 9
def unary_$ : Int = 9
}
12 changes: 5 additions & 7 deletions tests/pos-macros/i3898c/quoted_2.scala
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
object Test {
def main(args: Array[String]): Unit = {
val a = '{
def z: Int = 5
Macro.ff(z, 5)
}

import scala.quoted._
def test given QuoteContext = {
val a = '{
def z: Int = 5
Macro.ff(z, 5)
}
}
4 changes: 2 additions & 2 deletions tests/pos-macros/i4023c/Macro_1.scala
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import scala.quoted._
object Macro {
inline def ff[T](x: T): T = ${ impl('x)('[T]) }
def impl[T](x: Expr[T])(implicit t: Type[T]): Expr[T] = '{ $x: $t }
inline def ff[T](x: T): T = ${ impl('x)('[T], the[QuoteContext]) }
def impl[T](x: Expr[T])(implicit t: Type[T], qctx: QuoteContext): Expr[T] = '{ $x: $t }
}
4 changes: 2 additions & 2 deletions tests/pos-macros/i4734/Macro_1.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ import scala.quoted._

object Macros {
inline def unrolledForeach(f: Int => Int): Int =
${unrolledForeachImpl('f)}
${unrolledForeachImpl('f)}

def unrolledForeachImpl(f: Expr[Int => Int]): Expr[Int] = '{
def unrolledForeachImpl(f: Expr[Int => Int]) given QuoteContext: Expr[Int] = '{
val size: Int = 5
($f)(3)
}
Expand Down
2 changes: 1 addition & 1 deletion tests/pos-macros/i6210/Macros_1.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ object Macro {
inline def test[A, B]: Any =
${ impl[A, B] }

def impl[A : Type, B : Type]: Expr[Any] = {
def impl[A : Type, B : Type] given QuoteContext: Expr[Any] = {
val t = '[Map[A, B]]
'{
new Object().asInstanceOf[$t]
Expand Down
2 changes: 1 addition & 1 deletion tests/pos-with-compiler/quote-assert/quoted_1.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import scala.quoted._

object Macros {
def assertImpl(expr: Expr[Boolean]) =
def assertImpl(expr: Expr[Boolean]) given QuoteContext =
'{ if !($expr) then throw new AssertionError(s"failed assertion: ${$expr}") }
}
4 changes: 2 additions & 2 deletions tests/pos-with-compiler/quote-assert/quoted_2.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@ object Test {
${ assertImpl('expr) }


val program = '{
def program given QuoteContext = '{
val x = 1
assert(x != 0)

${ assertImpl('{x != 0}) }
}

implicit val toolbox: scala.quoted.Toolbox = scala.quoted.Toolbox.make(getClass.getClassLoader)
program.run
run(program)
}
4 changes: 2 additions & 2 deletions tests/pos/i4350.scala
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import scala.quoted.Type
import scala.quoted._

class Foo[T: Type] {
class Foo[T: Type] given QuoteContext {
'{null.asInstanceOf[T]}
}
Loading