From d637cce1d64d1b97c4a330e2da3ebcf858d77e0c Mon Sep 17 00:00:00 2001 From: adampauls Date: Sat, 15 Jan 2022 13:03:40 -0800 Subject: [PATCH 1/2] Parse splices inside patterns as patterns --- .../dotty/tools/dotc/parsing/Parsers.scala | 17 +++++++++------ docs/docs/internals/syntax.md | 8 ++++--- docs/docs/reference/syntax.md | 6 ++++-- tests/neg/splice-pat.check | 10 +++++++++ tests/neg/splice-pat.scala | 15 +++++++++++++ tests/pos/splice-pat.scala | 21 +++++++++++++++++++ 6 files changed, 66 insertions(+), 11 deletions(-) create mode 100644 tests/neg/splice-pat.check create mode 100644 tests/neg/splice-pat.scala create mode 100644 tests/pos/splice-pat.scala diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 63b6e9f8ac51..c98cc9d51a2f 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -56,7 +56,8 @@ object Parsers { object StageKind { val None = 0 val Quoted = 1 - val Spliced = 2 + val Spliced = 1 << 1 + val Pattern = 1 << 2 } extension (buf: ListBuffer[Tree]) @@ -1566,15 +1567,19 @@ object Parsers { /** The block in a quote or splice */ def stagedBlock() = inBraces(block(simplify = true)) - /** SimpleEpxr ::= spliceId | ‘$’ ‘{’ Block ‘}’) - * SimpleType ::= spliceId | ‘$’ ‘{’ Block ‘}’) + /** SimpleExpr ::= spliceId | ‘$’ ‘{’ Block ‘}’) unless inside quoted pattern + * SimpleType ::= spliceId | ‘$’ ‘{’ Block ‘}’) unless inside quoted pattern + * + * SimpleExpr ::= spliceId | ‘$’ ‘{’ Pattern ‘}’) when inside quoted pattern + * SimpleType ::= spliceId | ‘$’ ‘{’ Pattern ‘}’) when inside quoted pattern */ def splice(isType: Boolean): Tree = atSpan(in.offset) { val expr = if (in.name.length == 1) { in.nextToken() - withinStaged(StageKind.Spliced)(stagedBlock()) + val inPattern = (staged & StageKind.Pattern) != 0 + withinStaged(StageKind.Spliced)(if (inPattern) inBraces(pattern()) else stagedBlock()) } else atSpan(in.offset + 1) { val id = Ident(in.name.drop(1)) @@ -2271,7 +2276,7 @@ object Parsers { blockExpr() case QUOTE => atSpan(in.skipToken()) { - withinStaged(StageKind.Quoted) { + withinStaged(StageKind.Quoted | (if (location.inPattern) StageKind.Pattern else 0)) { Quote { if (in.token == LBRACKET) inBrackets(typ()) else stagedBlock() @@ -2714,7 +2719,7 @@ object Parsers { case LPAREN => atSpan(in.offset) { makeTupleOrParens(inParens(patternsOpt())) } case QUOTE => - simpleExpr(Location.ElseWhere) + simpleExpr(Location.InPattern) case XMLSTART => xmlLiteralPattern() case GIVEN => diff --git a/docs/docs/internals/syntax.md b/docs/docs/internals/syntax.md index 5cb03d5ab322..f8c1aa500490 100644 --- a/docs/docs/internals/syntax.md +++ b/docs/docs/internals/syntax.md @@ -183,7 +183,8 @@ SimpleType1 ::= id | Singleton ‘.’ ‘type’ SingletonTypeTree(p) | ‘(’ Types ‘)’ Tuple(ts) | Refinement RefinedTypeTree(EmptyTree, refinement) - | ‘$’ ‘{’ Block ‘}’ + | ‘$’ ‘{’ Block ‘}’ -- unless inside quoted pattern + | ‘$’ ‘{’ Pattern ‘}’ -- only inside quoted pattern | SimpleType1 TypeArgs AppliedTypeTree(t, args) | SimpleType1 ‘#’ id Select(t, name) Singleton ::= SimpleRef @@ -242,7 +243,8 @@ SimpleExpr ::= SimpleRef | Literal | ‘_’ | BlockExpr - | ‘$’ ‘{’ Block ‘}’ + | ‘$’ ‘{’ Block ‘}’ -- unless inside quoted pattern + | ‘$’ ‘{’ Pattern ‘}’ -- only inside quoted pattern | Quoted | quoteId -- only inside splices | ‘new’ ConstrApp {‘with’ ConstrApp} [TemplateBody] New(constr | templ) @@ -257,7 +259,7 @@ SimpleExpr ::= SimpleRef | SimpleExpr ‘_’ PostfixOp(expr, _) (to be dropped) | XmlExpr -- to be dropped IndentedExpr ::= indent CaseClauses | Block outdent -Quoted ::= ‘'’ ‘{’ Block ‘}’ +Quoted ::= ‘'’ ‘{’ Block ‘}’ | ‘'’ ‘[’ Type ‘]’ ExprsInParens ::= ExprInParens {‘,’ ExprInParens} ExprInParens ::= PostfixExpr ‘:’ Type -- normal Expr allows only RefinedType here diff --git a/docs/docs/reference/syntax.md b/docs/docs/reference/syntax.md index c2f990b0a953..ff219a46081c 100644 --- a/docs/docs/reference/syntax.md +++ b/docs/docs/reference/syntax.md @@ -182,7 +182,8 @@ SimpleType ::= SimpleLiteral | Singleton ‘.’ ‘type’ | ‘(’ Types ‘)’ | Refinement - | ‘$’ ‘{’ Block ‘}’ + | ‘$’ ‘{’ Block ‘}’ -- unless inside quoted pattern + | ‘$’ ‘{’ Pattern ‘}’ -- only inside quoted pattern | SimpleType1 TypeArgs | SimpleType1 ‘#’ id Singleton ::= SimpleRef @@ -240,7 +241,8 @@ SimpleExpr ::= SimpleRef | Literal | ‘_’ | BlockExpr - | ‘$’ ‘{’ Block ‘}’ + | ‘$’ ‘{’ Block ‘}’ -- unless inside quoted pattern + | ‘$’ ‘{’ Pattern ‘}’ -- only inside quoted pattern | Quoted | quoteId -- only inside splices | ‘new’ ConstrApp {‘with’ ConstrApp} [TemplateBody] diff --git a/tests/neg/splice-pat.check b/tests/neg/splice-pat.check new file mode 100644 index 000000000000..68fda3e96e72 --- /dev/null +++ b/tests/neg/splice-pat.check @@ -0,0 +1,10 @@ +-- [E032] Syntax Error: tests/neg/splice-pat.scala:12:16 --------------------------------------------------------------- +12 | case '{ foo(${ // error: pattern expected + | ^ + | pattern expected + +longer explanation available when compiling with `-explain` +-- [E040] Syntax Error: tests/neg/splice-pat.scala:15:5 ---------------------------------------------------------------- +15 | })} => ??? // error + | ^ + | '=>' expected, but ')' found diff --git a/tests/neg/splice-pat.scala b/tests/neg/splice-pat.scala new file mode 100644 index 000000000000..a43659c5e323 --- /dev/null +++ b/tests/neg/splice-pat.scala @@ -0,0 +1,15 @@ +import scala.quoted.* + +object MyMatcher { + def unapply(expr: Expr[Any])(using Quotes): Option[Expr[Int]] = ??? +} + +def foo(x: Any): Unit = ??? + +def bar(): Expr[Any] = ??? + +def f(expr: Expr[Any])(using Quotes): Expr[Int] = expr match + case '{ foo(${ // error: pattern expected + import scala.Int + bar() + })} => ??? // error diff --git a/tests/pos/splice-pat.scala b/tests/pos/splice-pat.scala new file mode 100644 index 000000000000..babebb9e21d1 --- /dev/null +++ b/tests/pos/splice-pat.scala @@ -0,0 +1,21 @@ +import scala.quoted.* + +object MyMatcher { + def unapply(expr: Expr[Any])(using Quotes): Option[Expr[Int]] = ??? +} + +object MyMatcher2 { + def unapply(expr: Expr[Int])(using Quotes): Boolean = ??? +} + +def foo(x: Any): Unit = ??? +def bar(x: Int): Int = ??? + +def oneLevel(expr: Expr[Any])(using Quotes): Expr[Int] = expr match + case '{ foo(${MyMatcher(y@MyMatcher2())}) } => y + +def twoLevel(expr: Expr[Any])(using Quotes): Expr[Int] = expr match + case '{ foo(${MyMatcher('{ bar(${y@MyMatcher2()}).getClass}) }) } => y + +def bindQuote(expr: Expr[Any])(using Quotes): Expr[Int] = expr match + case '{ foo(${y@'{bar($_)}})} => y From 9ae3b8710a86c7878410151250b54ddc63686252 Mon Sep 17 00:00:00 2001 From: adampauls Date: Mon, 17 Jan 2022 13:55:02 -0800 Subject: [PATCH 2/2] PR comments. --- .../src/dotty/tools/dotc/parsing/Parsers.scala | 6 +++--- tests/pos-macros/splice-pat/Macro_1.scala | 17 +++++++++++++++++ tests/pos-macros/splice-pat/Test_1.scala | 3 +++ tests/pos/splice-pat.scala | 3 +++ 4 files changed, 26 insertions(+), 3 deletions(-) create mode 100644 tests/pos-macros/splice-pat/Macro_1.scala create mode 100644 tests/pos-macros/splice-pat/Test_1.scala diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index c98cc9d51a2f..fc607e16dd74 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -57,7 +57,7 @@ object Parsers { val None = 0 val Quoted = 1 val Spliced = 1 << 1 - val Pattern = 1 << 2 + val QuotedPattern = 1 << 2 } extension (buf: ListBuffer[Tree]) @@ -1578,7 +1578,7 @@ object Parsers { val expr = if (in.name.length == 1) { in.nextToken() - val inPattern = (staged & StageKind.Pattern) != 0 + val inPattern = (staged & StageKind.QuotedPattern) != 0 withinStaged(StageKind.Spliced)(if (inPattern) inBraces(pattern()) else stagedBlock()) } else atSpan(in.offset + 1) { @@ -2276,7 +2276,7 @@ object Parsers { blockExpr() case QUOTE => atSpan(in.skipToken()) { - withinStaged(StageKind.Quoted | (if (location.inPattern) StageKind.Pattern else 0)) { + withinStaged(StageKind.Quoted | (if (location.inPattern) StageKind.QuotedPattern else 0)) { Quote { if (in.token == LBRACKET) inBrackets(typ()) else stagedBlock() diff --git a/tests/pos-macros/splice-pat/Macro_1.scala b/tests/pos-macros/splice-pat/Macro_1.scala new file mode 100644 index 000000000000..b3fabb046da6 --- /dev/null +++ b/tests/pos-macros/splice-pat/Macro_1.scala @@ -0,0 +1,17 @@ +import scala.quoted.* + +object Macro { + object MyMatcher { + def unapply(expr: Expr[Any])(using Quotes): Option[Expr[Int]] = expr match { + case '{ (${a}: Int) + (${_}: Int) } => Some(a) + case _ => None + } + } + + def foo(x: Int): Int = x - 1 + + def impl(expr: Expr[Any])(using Quotes): Expr[(Int, Int)] = expr match + case '{foo(${bound@MyMatcher(x)})}=> '{($bound, $x)} + + inline def macr(inline x: Int): (Int, Int) = ${impl('x)} +} diff --git a/tests/pos-macros/splice-pat/Test_1.scala b/tests/pos-macros/splice-pat/Test_1.scala new file mode 100644 index 000000000000..672001167616 --- /dev/null +++ b/tests/pos-macros/splice-pat/Test_1.scala @@ -0,0 +1,3 @@ +object Test { + assert(Macro.macr(Macro.foo(1 + 2)) == (3, 1)) +} diff --git a/tests/pos/splice-pat.scala b/tests/pos/splice-pat.scala index babebb9e21d1..6a8852b4a7c0 100644 --- a/tests/pos/splice-pat.scala +++ b/tests/pos/splice-pat.scala @@ -19,3 +19,6 @@ def twoLevel(expr: Expr[Any])(using Quotes): Expr[Int] = expr match def bindQuote(expr: Expr[Any])(using Quotes): Expr[Int] = expr match case '{ foo(${y@'{bar($_)}})} => y + +def noop(expr: Expr[Any])(using Quotes): Expr[Int] = expr match + case '{ bar(${ '{ $y } }) } => y