Skip to content

Commit 300a8b6

Browse files
Merge pull request #14277 from adampauls/splice_pattern
Parse splices inside quoted patterns as patterns
2 parents 2849aed + 9ae3b87 commit 300a8b6

File tree

8 files changed

+89
-11
lines changed

8 files changed

+89
-11
lines changed

compiler/src/dotty/tools/dotc/parsing/Parsers.scala

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,8 @@ object Parsers {
5656
object StageKind {
5757
val None = 0
5858
val Quoted = 1
59-
val Spliced = 2
59+
val Spliced = 1 << 1
60+
val QuotedPattern = 1 << 2
6061
}
6162

6263
extension (buf: ListBuffer[Tree])
@@ -1566,15 +1567,19 @@ object Parsers {
15661567
/** The block in a quote or splice */
15671568
def stagedBlock() = inBraces(block(simplify = true))
15681569

1569-
/** SimpleEpxr ::= spliceId | ‘$’ ‘{’ Block ‘}’)
1570-
* SimpleType ::= spliceId | ‘$’ ‘{’ Block ‘}’)
1570+
/** SimpleExpr ::= spliceId | ‘$’ ‘{’ Block ‘}’) unless inside quoted pattern
1571+
* SimpleType ::= spliceId | ‘$’ ‘{’ Block ‘}’) unless inside quoted pattern
1572+
*
1573+
* SimpleExpr ::= spliceId | ‘$’ ‘{’ Pattern ‘}’) when inside quoted pattern
1574+
* SimpleType ::= spliceId | ‘$’ ‘{’ Pattern ‘}’) when inside quoted pattern
15711575
*/
15721576
def splice(isType: Boolean): Tree =
15731577
atSpan(in.offset) {
15741578
val expr =
15751579
if (in.name.length == 1) {
15761580
in.nextToken()
1577-
withinStaged(StageKind.Spliced)(stagedBlock())
1581+
val inPattern = (staged & StageKind.QuotedPattern) != 0
1582+
withinStaged(StageKind.Spliced)(if (inPattern) inBraces(pattern()) else stagedBlock())
15781583
}
15791584
else atSpan(in.offset + 1) {
15801585
val id = Ident(in.name.drop(1))
@@ -2271,7 +2276,7 @@ object Parsers {
22712276
blockExpr()
22722277
case QUOTE =>
22732278
atSpan(in.skipToken()) {
2274-
withinStaged(StageKind.Quoted) {
2279+
withinStaged(StageKind.Quoted | (if (location.inPattern) StageKind.QuotedPattern else 0)) {
22752280
Quote {
22762281
if (in.token == LBRACKET) inBrackets(typ())
22772282
else stagedBlock()
@@ -2714,7 +2719,7 @@ object Parsers {
27142719
case LPAREN =>
27152720
atSpan(in.offset) { makeTupleOrParens(inParens(patternsOpt())) }
27162721
case QUOTE =>
2717-
simpleExpr(Location.ElseWhere)
2722+
simpleExpr(Location.InPattern)
27182723
case XMLSTART =>
27192724
xmlLiteralPattern()
27202725
case GIVEN =>

docs/docs/internals/syntax.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,8 @@ SimpleType1 ::= id
183183
| Singleton ‘.’ ‘type’ SingletonTypeTree(p)
184184
| ‘(’ Types ‘)’ Tuple(ts)
185185
| Refinement RefinedTypeTree(EmptyTree, refinement)
186-
| ‘$’ ‘{’ Block ‘}’
186+
| ‘$’ ‘{’ Block ‘}’ -- unless inside quoted pattern
187+
| ‘$’ ‘{’ Pattern ‘}’ -- only inside quoted pattern
187188
| SimpleType1 TypeArgs AppliedTypeTree(t, args)
188189
| SimpleType1 ‘#’ id Select(t, name)
189190
Singleton ::= SimpleRef
@@ -242,7 +243,8 @@ SimpleExpr ::= SimpleRef
242243
| Literal
243244
| ‘_’
244245
| BlockExpr
245-
| ‘$’ ‘{’ Block ‘}’
246+
| ‘$’ ‘{’ Block ‘}’ -- unless inside quoted pattern
247+
| ‘$’ ‘{’ Pattern ‘}’ -- only inside quoted pattern
246248
| Quoted
247249
| quoteId -- only inside splices
248250
| ‘new’ ConstrApp {‘with’ ConstrApp} [TemplateBody] New(constr | templ)
@@ -257,7 +259,7 @@ SimpleExpr ::= SimpleRef
257259
| SimpleExpr ‘_’ PostfixOp(expr, _) (to be dropped)
258260
| XmlExpr -- to be dropped
259261
IndentedExpr ::= indent CaseClauses | Block outdent
260-
Quoted ::= ‘'’ ‘{’ Block ‘}’
262+
Quoted ::= ‘'’ ‘{’ Block ‘}’
261263
| ‘'’ ‘[’ Type ‘]’
262264
ExprsInParens ::= ExprInParens {‘,’ ExprInParens}
263265
ExprInParens ::= PostfixExpr ‘:’ Type -- normal Expr allows only RefinedType here

docs/docs/reference/syntax.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,8 @@ SimpleType ::= SimpleLiteral
182182
| Singleton ‘.’ ‘type’
183183
| ‘(’ Types ‘)’
184184
| Refinement
185-
| ‘$’ ‘{’ Block ‘}’
185+
| ‘$’ ‘{’ Block ‘}’ -- unless inside quoted pattern
186+
| ‘$’ ‘{’ Pattern ‘}’ -- only inside quoted pattern
186187
| SimpleType1 TypeArgs
187188
| SimpleType1 ‘#’ id
188189
Singleton ::= SimpleRef
@@ -240,7 +241,8 @@ SimpleExpr ::= SimpleRef
240241
| Literal
241242
| ‘_’
242243
| BlockExpr
243-
| ‘$’ ‘{’ Block ‘}’
244+
| ‘$’ ‘{’ Block ‘}’ -- unless inside quoted pattern
245+
| ‘$’ ‘{’ Pattern ‘}’ -- only inside quoted pattern
244246
| Quoted
245247
| quoteId -- only inside splices
246248
| ‘new’ ConstrApp {‘with’ ConstrApp} [TemplateBody]

tests/neg/splice-pat.check

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
-- [E032] Syntax Error: tests/neg/splice-pat.scala:12:16 ---------------------------------------------------------------
2+
12 | case '{ foo(${ // error: pattern expected
3+
| ^
4+
| pattern expected
5+
6+
longer explanation available when compiling with `-explain`
7+
-- [E040] Syntax Error: tests/neg/splice-pat.scala:15:5 ----------------------------------------------------------------
8+
15 | })} => ??? // error
9+
| ^
10+
| '=>' expected, but ')' found

tests/neg/splice-pat.scala

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import scala.quoted.*
2+
3+
object MyMatcher {
4+
def unapply(expr: Expr[Any])(using Quotes): Option[Expr[Int]] = ???
5+
}
6+
7+
def foo(x: Any): Unit = ???
8+
9+
def bar(): Expr[Any] = ???
10+
11+
def f(expr: Expr[Any])(using Quotes): Expr[Int] = expr match
12+
case '{ foo(${ // error: pattern expected
13+
import scala.Int
14+
bar()
15+
})} => ??? // error
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import scala.quoted.*
2+
3+
object Macro {
4+
object MyMatcher {
5+
def unapply(expr: Expr[Any])(using Quotes): Option[Expr[Int]] = expr match {
6+
case '{ (${a}: Int) + (${_}: Int) } => Some(a)
7+
case _ => None
8+
}
9+
}
10+
11+
def foo(x: Int): Int = x - 1
12+
13+
def impl(expr: Expr[Any])(using Quotes): Expr[(Int, Int)] = expr match
14+
case '{foo(${bound@MyMatcher(x)})}=> '{($bound, $x)}
15+
16+
inline def macr(inline x: Int): (Int, Int) = ${impl('x)}
17+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
object Test {
2+
assert(Macro.macr(Macro.foo(1 + 2)) == (3, 1))
3+
}

tests/pos/splice-pat.scala

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import scala.quoted.*
2+
3+
object MyMatcher {
4+
def unapply(expr: Expr[Any])(using Quotes): Option[Expr[Int]] = ???
5+
}
6+
7+
object MyMatcher2 {
8+
def unapply(expr: Expr[Int])(using Quotes): Boolean = ???
9+
}
10+
11+
def foo(x: Any): Unit = ???
12+
def bar(x: Int): Int = ???
13+
14+
def oneLevel(expr: Expr[Any])(using Quotes): Expr[Int] = expr match
15+
case '{ foo(${MyMatcher(y@MyMatcher2())}) } => y
16+
17+
def twoLevel(expr: Expr[Any])(using Quotes): Expr[Int] = expr match
18+
case '{ foo(${MyMatcher('{ bar(${y@MyMatcher2()}).getClass}) }) } => y
19+
20+
def bindQuote(expr: Expr[Any])(using Quotes): Expr[Int] = expr match
21+
case '{ foo(${y@'{bar($_)}})} => y
22+
23+
def noop(expr: Expr[Any])(using Quotes): Expr[Int] = expr match
24+
case '{ bar(${ '{ $y } }) } => y

0 commit comments

Comments
 (0)