Skip to content

Improve quoted pattern type inference #8766

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
4 changes: 3 additions & 1 deletion compiler/src/dotty/tools/dotc/typer/Applications.scala
Original file line number Diff line number Diff line change
Expand Up @@ -969,7 +969,9 @@ trait Applications extends Compatibility {
}
}
else {
val app = realApply
val app = tree.fun match
case _: untpd.Splice if ctx.mode.is(Mode.QuotedPattern) => typedAppliedSplice(tree, pt)
case _ => realApply
app match {
case Apply(fn @ Select(left, _), right :: Nil) if fn.hasType =>
val op = fn.symbol
Expand Down
21 changes: 21 additions & 0 deletions compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import dotty.tools.dotc.core.Types._
import dotty.tools.dotc.reporting._
import dotty.tools.dotc.typer.Implicits._
import dotty.tools.dotc.typer.Inferencing._
import dotty.tools.dotc.typer.ProtoTypes._
import dotty.tools.dotc.util.Spans._
import dotty.tools.dotc.util.Stats.record

Expand Down Expand Up @@ -108,6 +109,26 @@ trait QuotesAndSplices {
}
}

/** Types a splice applied to some arguments `$f(arg1, ..., argn)` in a quote pattern.
*
* The tree is desugared into `$f.apply(arg1, ..., argn)` where the expression `$f`
* is expected to type as a function type `(T1, ..., Tn) => R`.
* `Ti` is the type of the argument `argi` and R if the type of the prototype.
* The prototype must be fully defined to be able to infer the type of `R`.
*/
def typedAppliedSplice(tree: untpd.Apply, pt: Type)(using Context): Tree = {
assert(ctx.mode.is(Mode.QuotedPattern))
val untpd.Apply(splice: untpd.Splice, args) = tree
if (isFullyDefined(pt, ForceDegree.flipBottom)) then
val typedArgs = args.map(arg => typedExpr(arg))
val argTypes = typedArgs.map(_.tpe.widenTermRefExpr)
val splice1 = typedSplice(splice, defn.FunctionOf(argTypes, pt))
Apply(splice1.select(nme.apply), typedArgs).withType(pt).withSpan(tree.span)
else
ctx.error(i"Type must be fully defined.", splice.sourcePos)
tree.withType(UnspecifiedErrorType)
}

/** Translate ${ t: Type[T] }` into type `t.splice` while tracking the quotation level in the context */
def typedTypSplice(tree: untpd.TypSplice, pt: Type)(using Context): Tree = {
record("typedTypSplice")
Expand Down
12 changes: 12 additions & 0 deletions tests/pos/quoted-splice-pattern-applied.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import scala.quoted._

def f(x: Expr[Int])(using QuoteContext) = x match {
case '{ $f($a: Int): Int } =>
val f1: Expr[Int => Int] = f
val a1: Expr[Int] = a
case '{ def a: Int = $f($b: Int); () } =>
val f1: Expr[Int => Int] = f
val b1: Expr[Int] = b
case '{ val a: Int = 3; $f(a): Int } =>
val f1: Expr[Int => Int] = f
}
6 changes: 3 additions & 3 deletions tests/run-macros/quote-matcher-symantics-2/quoted_1.scala
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ object Macros {

case '{ ($x: DSL) * ($y: DSL) } => sym.times(lift(x), lift(y))

case '{ ($f: DSL => DSL)($x: DSL) } => sym.app(liftFun(f), lift(x))
case '{ $f($x: DSL): DSL } => sym.app(liftFun(f), lift(x))

case '{ val x: DSL = $value; ($bodyFn: DSL => DSL)(x) } =>
case '{ val x: DSL = $value; $bodyFn(x): DSL } =>
UnsafeExpr.open(bodyFn) { (body1, close) =>
val (i, nEnvVar) = freshEnvVar()
lift(close(body1)(nEnvVar))(env + (i -> lift(value)))
Expand All @@ -37,7 +37,7 @@ object Macros {
}

def liftFun(e: Expr[DSL => DSL])(implicit env: Map[Int, Expr[T]]): Expr[T => T] = e match {
case '{ (x: DSL) => ($bodyFn: DSL => DSL)(x) } =>
case '{ (x: DSL) => $bodyFn(x): DSL } =>
sym.lam((y: Expr[T]) =>
UnsafeExpr.open(bodyFn) { (body1, close) =>
val (i, nEnvVar) = freshEnvVar()
Expand Down
8 changes: 4 additions & 4 deletions tests/run-macros/quote-matcher-symantics-3/quoted_1.scala
Original file line number Diff line number Diff line change
Expand Up @@ -39,23 +39,23 @@ object Macros {
case '{ ($x: Int) <= ($y: Int) } =>
'{ $sym.leq(${lift(x)}, ${lift(y)}).asInstanceOf[R[T]] }

case '{ ($f: $t => $u)($arg) } =>
case '{ $f($arg: $t): $u } =>
'{ $sym.app[$t, $u](${lift(f)}, ${lift(arg)}).asInstanceOf[R[T]] }

case '{ (if ($cond) $thenp else $elsep): $t } =>
'{ $sym.ifThenElse[$t](${lift(cond)}, ${lift(thenp)}, ${lift(elsep)}) }.asInstanceOf[Expr[R[T]]]

case '{ (x0: Int) => ($bodyFn: Int => Any)(x0) } =>
case '{ (x0: Int) => $bodyFn(x0): Any } =>
val (i, nEnvVar) = freshEnvVar[Int]()
val body2 = UnsafeExpr.open(bodyFn) { (body1, close) => close(body1)(nEnvVar) }
'{ $sym.lam((x: R[Int]) => ${given Env = envWith(i, 'x)(using env); lift(body2)}).asInstanceOf[R[T]] }

case '{ (x0: Boolean) => ($bodyFn: Boolean => Any)(x0) } =>
case '{ (x0: Boolean) => $bodyFn(x0): Any } =>
val (i, nEnvVar) = freshEnvVar[Boolean]()
val body2 = UnsafeExpr.open(bodyFn) { (body1, close) => close(body1)(nEnvVar) }
'{ $sym.lam((x: R[Boolean]) => ${given Env = envWith(i, 'x)(using env); lift(body2)}).asInstanceOf[R[T]] }

case '{ (x0: Int => Int) => ($bodyFn: (Int => Int) => Any)(x0) } =>
case '{ (x0: Int => Int) => $bodyFn(x0): Any } =>
val (i, nEnvVar) = freshEnvVar[Int => Int]()
val body2 = UnsafeExpr.open(bodyFn) { (body1, close) => close(body1)(nEnvVar) }
'{ $sym.lam((x: R[Int => Int]) => ${given Env = envWith(i, 'x)(using env); lift(body2)}).asInstanceOf[R[T]] }
Expand Down
6 changes: 3 additions & 3 deletions tests/run-macros/quote-matching-open/Macro_1.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ object Macro {

def impl(x: Expr[Any])(using QuoteContext): Expr[Any] = {
x match {
case '{ (x: Int) => ($body: Int => Int)(x) } => UnsafeExpr.open(body) { (body, close) => close(body)(Expr(2)) }
case '{ (x1: Int, x2: Int) => ($body: (Int, Int) => Int)(x1, x2) } => UnsafeExpr.open(body) { (body, close) => close(body)(Expr(2), Expr(3)) }
case '{ (x1: Int, x2: Int, x3: Int) => ($body: (Int, Int, Int) => Int)(x1, x2, x3) } => UnsafeExpr.open(body) { (body, close) => close(body)(Expr(2), Expr(3), Expr(4)) }
case '{ (x: Int) => $body(x): Int } => UnsafeExpr.open(body) { (body, close) => close(body)(Expr(2)) }
case '{ (x1: Int, x2: Int) => $body(x1, x2): Int } => UnsafeExpr.open(body) { (body, close) => close(body)(Expr(2), Expr(3)) }
case '{ (x1: Int, x2: Int, x3: Int) => $body(x1, x2, x3): Int } => UnsafeExpr.open(body) { (body, close) => close(body)(Expr(2), Expr(3), Expr(4)) }
}
}

Expand Down
6 changes: 3 additions & 3 deletions tests/run-macros/quoted-pattern-open-expr/Macro_1.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ inline def test(inline e: Int): String = ${testExpr('e)}
private def testExpr(e: Expr[Int])(using QuoteContext): Expr[String] = {
e match {
case '{ val y: Int = 4; $body } => Expr("Matched closed\n" + body.show)
case '{ val y: Int = 4; ($body: Int => Int)(y) } => Expr("Matched open\n" + body.show)
case '{ val y: Int => Int = x => x + 1; ($body: (Int => Int) => Int)(y) } => Expr("Matched open\n" + body.show)
case '{ def g(x: Int): Int = ($body: (Int => Int, Int) => Int)(g, x); g(5) } => Expr("Matched open\n" + body.show)
case '{ val y: Int = 4; $body(y): Int } => Expr("Matched open\n" + body.show)
case '{ val y: Int => Int = x => x + 1; $body(y): Int } => Expr("Matched open\n" + body.show)
case '{ def g(x: Int): Int = $body(g, x); g(5) } => Expr("Matched open\n" + body.show)
}
}