From 7ecc2ed46f3547bd81d0cfdc322d434b55d75e96 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Wed, 22 Apr 2020 11:33:55 +0200 Subject: [PATCH 1/2] Improve quoted pattern type inference 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`. --- .../dotty/tools/dotc/typer/Applications.scala | 4 +++- .../tools/dotc/typer/QuotesAndSplices.scala | 21 +++++++++++++++++++ .../quote-matcher-symantics-2/quoted_1.scala | 6 +++--- .../quote-matcher-symantics-3/quoted_1.scala | 8 +++---- .../quote-matching-open/Macro_1.scala | 6 +++--- .../quoted-pattern-open-expr/Macro_1.scala | 6 +++--- 6 files changed, 37 insertions(+), 14 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 464a14d51cec..063d7f1e8014 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -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 diff --git a/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala b/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala index 27452c835c30..74ecf639ab19 100644 --- a/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala +++ b/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala @@ -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 @@ -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") diff --git a/tests/run-macros/quote-matcher-symantics-2/quoted_1.scala b/tests/run-macros/quote-matcher-symantics-2/quoted_1.scala index f23247232763..ae8ce7d4e7e8 100644 --- a/tests/run-macros/quote-matcher-symantics-2/quoted_1.scala +++ b/tests/run-macros/quote-matcher-symantics-2/quoted_1.scala @@ -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))) @@ -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() diff --git a/tests/run-macros/quote-matcher-symantics-3/quoted_1.scala b/tests/run-macros/quote-matcher-symantics-3/quoted_1.scala index 45b1b16f4642..63be08612d1f 100644 --- a/tests/run-macros/quote-matcher-symantics-3/quoted_1.scala +++ b/tests/run-macros/quote-matcher-symantics-3/quoted_1.scala @@ -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]] } diff --git a/tests/run-macros/quote-matching-open/Macro_1.scala b/tests/run-macros/quote-matching-open/Macro_1.scala index c54809207ea1..c06bfaaaa5b6 100644 --- a/tests/run-macros/quote-matching-open/Macro_1.scala +++ b/tests/run-macros/quote-matching-open/Macro_1.scala @@ -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)) } } } diff --git a/tests/run-macros/quoted-pattern-open-expr/Macro_1.scala b/tests/run-macros/quoted-pattern-open-expr/Macro_1.scala index 8f3f3b54331f..5718c7cf2bf2 100644 --- a/tests/run-macros/quoted-pattern-open-expr/Macro_1.scala +++ b/tests/run-macros/quoted-pattern-open-expr/Macro_1.scala @@ -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) } } From 3acc97a0f97d1722853e09c3d746e1edf7c0f9e2 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Thu, 23 Apr 2020 19:33:05 +0200 Subject: [PATCH 2/2] Add test --- tests/pos/quoted-splice-pattern-applied.scala | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 tests/pos/quoted-splice-pattern-applied.scala diff --git a/tests/pos/quoted-splice-pattern-applied.scala b/tests/pos/quoted-splice-pattern-applied.scala new file mode 100644 index 000000000000..5014d7f82d2f --- /dev/null +++ b/tests/pos/quoted-splice-pattern-applied.scala @@ -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 +}