Skip to content

Commit 1d77fbe

Browse files
committed
Use new higher-order quasi-pattern syntax
```scala case ${ val a = 3; ${body}{a}: Int } => ```
1 parent 51de940 commit 1d77fbe

File tree

9 files changed

+53
-60
lines changed

9 files changed

+53
-60
lines changed

compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala

Lines changed: 35 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -75,41 +75,13 @@ trait QuotesAndSplices {
7575
}
7676
if (ctx.mode.is(Mode.QuotedPattern))
7777
if (isFullyDefined(pt, ForceDegree.flipBottom)) {
78-
object HigherOrderQuasipattern {
79-
/** Matches a splice of the form `${<pat> $$ <args*>}` */
80-
def unapply(tree: untpd.Tree): Option[(untpd.Tree, List[untpd.Tree])] =
81-
tree match
82-
case untpd.InfixOp(pat, untpd.Ident(nme.EXPAND_SEPARATOR), args0) =>
83-
val args = args0 match
84-
case untpd.Tuple(args) => args
85-
case arg => List(arg)
86-
Some((pat, args))
87-
case Apply(Select(pat, nme.EXPAND_SEPARATOR), args) => Some((pat, args))
88-
case _ => None
89-
}
90-
tree.expr match
91-
case HigherOrderQuasipattern(pat, args) =>
92-
val typedArgs = args.map {
93-
case arg: untpd.Ident =>
94-
typedExpr(arg)
95-
case arg =>
96-
ctx.error("Exprected an identifier", arg.sourcePos)
97-
EmptyTree
98-
}
99-
if args.isEmpty then
100-
ctx.error("Missing arguments for open pattern", tree.expr.sourcePos)
101-
val argTypes = typedArgs.map(_.tpe.widenTermRefExpr)
102-
val splice1 = untpd.cpy.Splice(tree)(pat).asInstanceOf[untpd.Splice]
103-
val typedPat = typedSplice(splice1, defn.FunctionOf(argTypes, pt))
104-
ref(defn.InternalQuoted_patternHigherOrderHole).appliedToType(pt).appliedTo(typedPat, SeqLiteral(typedArgs, TypeTree(defn.AnyType)))
105-
case _ =>
106-
def spliceOwner(ctx: Context): Symbol =
107-
if (ctx.mode.is(Mode.QuotedPattern)) spliceOwner(ctx.outer) else ctx.owner
108-
val pat = typedPattern(tree.expr, defn.QuotedExprClass.typeRef.appliedTo(pt))(
109-
using spliceContext.retractMode(Mode.QuotedPattern).withOwner(spliceOwner(ctx)))
110-
val baseType = pat.tpe.baseType(defn.QuotedExprClass)
111-
val argType = if baseType != NoType then baseType.argTypesHi.head else defn.NothingType
112-
ref(defn.InternalQuoted_exprSplice).appliedToType(argType).appliedTo(pat)
78+
def spliceOwner(ctx: Context): Symbol =
79+
if (ctx.mode.is(Mode.QuotedPattern)) spliceOwner(ctx.outer) else ctx.owner
80+
val pat = typedPattern(tree.expr, defn.QuotedExprClass.typeRef.appliedTo(pt))(
81+
using spliceContext.retractMode(Mode.QuotedPattern).withOwner(spliceOwner(ctx)))
82+
val baseType = pat.tpe.baseType(defn.QuotedExprClass)
83+
val argType = if baseType != NoType then baseType.argTypesHi.head else defn.NothingType
84+
ref(defn.InternalQuoted_exprSplice).appliedToType(argType).appliedTo(pat)
11385
}
11486
else {
11587
ctx.error(i"Type must be fully defined.\nConsider annotating the splice using a type ascription:\n ($tree: XYZ).", tree.expr.sourcePos)
@@ -151,18 +123,39 @@ trait QuotesAndSplices {
151123
val suggestionArgs = args match
152124
case arg :: Nil => arg.show
153125
case args => args.map(_.show).mkString("(", ", ", ")")
154-
val suggestion = s"$${${splice.expr.show} $$$$ $suggestionArgs}"
126+
val suggestion = s"$${${splice.expr.show}}{$suggestionArgs}"
155127
ctx.warning(s"""Possibly using open pattern syntax instead application pattern.
156128
|If this is a open pattern replace it with $suggestion.
157129
|""".stripMargin, tree.sourcePos)
158-
if (isFullyDefined(pt, ForceDegree.flipBottom)) then
159-
val typedArgs = args.map(arg => typedExpr(arg))
160-
val argTypes = typedArgs.map(_.tpe.widenTermRefExpr)
161-
val splice1 = typedSplice(splice, defn.FunctionOf(argTypes, pt))
162-
Apply(splice1.select(nme.apply), typedArgs).withType(pt).withSpan(tree.span)
163-
else
130+
object HigherOrderQuasipatternArgs {
131+
/** Matches a args of splice of the form `${body}{<args*>}` */
132+
def unapply(args: List[untpd.Tree]): Option[List[untpd.Tree]] = args match
133+
case Block(Nil, untpd.Tuple(args)) :: Nil => Some(args)
134+
case Block(Nil, arg) :: Nil => Some(List(arg))
135+
case _ => None
136+
}
137+
if !isFullyDefined(pt, ForceDegree.flipBottom) then
164138
ctx.error(i"Type must be fully defined.", splice.sourcePos)
165139
tree.withType(UnspecifiedErrorType)
140+
else args match
141+
case HigherOrderQuasipatternArgs(args) =>
142+
val typedArgs = args.map {
143+
case arg: untpd.Ident =>
144+
typedExpr(arg)
145+
case arg =>
146+
ctx.error("Exprected an identifier", arg.sourcePos)
147+
EmptyTree
148+
}
149+
if args.isEmpty then
150+
ctx.error("Missing arguments for open pattern", tree.sourcePos)
151+
val argTypes = typedArgs.map(_.tpe.widenTermRefExpr)
152+
val typedPat = typedSplice(splice, defn.FunctionOf(argTypes, pt))
153+
ref(defn.InternalQuoted_patternHigherOrderHole).appliedToType(pt).appliedTo(typedPat, SeqLiteral(typedArgs, TypeTree(defn.AnyType)))
154+
case _ =>
155+
val typedArgs = args.map(arg => typedExpr(arg))
156+
val argTypes = typedArgs.map(_.tpe.widenTermRefExpr)
157+
val splice1 = typedSplice(splice, defn.FunctionOf(argTypes, pt))
158+
Apply(splice1.select(nme.apply), typedArgs).withType(pt).withSpan(tree.span)
166159
}
167160

168161
/** Translate ${ t: Type[T] }` into type `t.splice` while tracking the quotation level in the context */

docs/docs/reference/metaprogramming/macros.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -757,17 +757,17 @@ then the rest of the quote can refer to this definition.
757757

758758
To match such a term we need to match the definition and the rest of the code, but we need to expicilty state that the rest of the code may refer to this definition.
759759
```scala
760-
case '{ val y: Int = $x; ${body $$ y}: Int } =>
760+
case '{ val y: Int = $x; ${body}{y}: Int } =>
761761
```
762-
Here `$x` will match any closed expression while `${body $$ y}` will match expression that is closed under `y`. Then
762+
Here `$x` will match any closed expression while `${body}{y}` will match expression that is closed under `y`. Then
763763
the subxpression of type `Expr[Int]` is bound to `body` as an `Expr[Int => Int]`. The extra argument represents the references to `y`. Usually this expression is used in compination with `Expr.betaReduce` to replace the extra argument.
764764

765765
```scala
766766
inline def eval(inline e: Int): Int = ${ evalExpr('e) }
767767

768768
private def evalExpr(using QuoteContext)(e: Expr[Int]): Expr[Int] = {
769769
e match {
770-
case '{ val y: Int = $x; ${body $$ y}: Int } =>
770+
case '{ val y: Int = $x; ${body}{y}: Int } =>
771771
// body: Expr[Int => Int] where the argument represents references to y
772772
evalExpr(Expr.betaReduce(body)(evalExpr(x)))
773773
case '{ ($x: Int) * ($y: Int) } =>
@@ -786,7 +786,7 @@ eval { // expands to the code: (16: Int)
786786
}
787787
```
788788

789-
We can also close over several bindings using `${b $$ (a1, a2, ..., an)}`.
789+
We can also close over several bindings using `${b}{(a1, a2, ..., an)}`.
790790

791791

792792
### More details
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import scala.quoted._
22

33
def f(using QuoteContext)(x: Expr[Any]) = x match {
4-
case '{ identity(${y $$ x}) } => // error: access to value x from wrong staging level
4+
case '{ identity(${y}{x}) } => // error: access to value x from wrong staging level
55
}
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import scala.quoted._
22

33
def f(using QuoteContext)(x: Expr[Any]) = x match {
4-
case '{ val a: Int = 3; ${y $$ identity(a)} } => // error: Exprected an identifier
5-
case '{ identity(${y $$ () }) } => // error: Missing arguments for open pattern
4+
case '{ val a: Int = 3; ${y}{identity(a)} } => // error: Exprected an identifier
5+
case '{ identity(${y}{()}) } => // error: Missing arguments for open pattern
66
}

tests/run-macros/quote-matcher-symantics-3/quoted_1.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,17 +45,17 @@ object Macros {
4545
case '{ (if ($cond) $thenp else $elsep): $t } =>
4646
'{ $sym.ifThenElse[$t](${lift(cond)}, ${lift(thenp)}, ${lift(elsep)}) }.asInstanceOf[Expr[R[T]]]
4747

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

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

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

tests/run-macros/quote-matching-open/Macro_1.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ object Macro {
66

77
def impl(x: Expr[Any])(using QuoteContext): Expr[Any] = {
88
x match {
9-
case '{ (x: Int) => ${body $$ x}: Int } => UnsafeExpr.open(body) { (body, close) => close(body)(Expr(2)) }
10-
case '{ (x1: Int, x2: Int) => ${body $$ (x1, x2)}: Int } => UnsafeExpr.open(body) { (body, close) => close(body)(Expr(2), Expr(3)) }
11-
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)) }
9+
case '{ (x: Int) => ${body}{x}: Int } => UnsafeExpr.open(body) { (body, close) => close(body)(Expr(2)) }
10+
case '{ (x1: Int, x2: Int) => ${body}{(x1, x2)}: Int } => UnsafeExpr.open(body) { (body, close) => close(body)(Expr(2), Expr(3)) }
11+
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)) }
1212
}
1313
}
1414

tests/run-macros/quoted-pattern-open-expr-0/Macro_1.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,6 @@ inline def test(inline e: Int): String = ${testExpr('e)}
44

55
private def testExpr(e: Expr[Int])(using QuoteContext): Expr[String] = {
66
e match {
7-
case '{ val y: Int = 4; ${body $$ y}: Int } => Expr("Matched open\n" + body.show)
7+
case '{ val y: Int = 4; ${body}{y}: Int } => Expr("Matched open\n" + body.show)
88
}
99
}

tests/run-macros/quoted-pattern-open-expr-simple-eval/Macro_1.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ inline def eval(inline e: Int): Int = ${ evalExpr('e) }
44

55
private def evalExpr(using QuoteContext)(e: Expr[Int]): Expr[Int] = {
66
e match {
7-
case '{ val y: Int = $x; ${body $$ y}: Int } =>
7+
case '{ val y: Int = $x; ${body}{y}: Int } =>
88
evalExpr(Expr.betaReduce(body)(evalExpr(x)))
99
case '{ ($x: Int) * ($y: Int) } =>
1010
(x, y) match

tests/run-macros/quoted-pattern-open-expr/Macro_1.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ inline def test(inline e: Int): String = ${testExpr('e)}
55
private def testExpr(e: Expr[Int])(using QuoteContext): Expr[String] = {
66
e match {
77
case '{ val y: Int = 4; $body } => Expr("Matched closed\n" + body.show)
8-
case '{ val y: Int = 4; ${body $$ y}: Int } => Expr("Matched open\n" + body.show)
9-
case '{ val y: Int => Int = x => x + 1; ${body $$ y}: Int } => Expr("Matched open\n" + body.show)
10-
case '{ def g(x: Int): Int = ${body $$ (g, x)}; g(5) } => Expr("Matched open\n" + body.show)
8+
case '{ val y: Int = 4; ${body}{y}: Int } => Expr("Matched open\n" + body.show)
9+
case '{ val y: Int => Int = x => x + 1; ${body}{y}: Int } => Expr("Matched open\n" + body.show)
10+
case '{ def g(x: Int): Int = ${body}{(g, x)}; g(5) } => Expr("Matched open\n" + body.show)
1111
}
1212
}

0 commit comments

Comments
 (0)