diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 63b6e9f8ac51..fc607e16dd74 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 QuotedPattern = 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.QuotedPattern) != 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.QuotedPattern 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-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 new file mode 100644 index 000000000000..6a8852b4a7c0 --- /dev/null +++ b/tests/pos/splice-pat.scala @@ -0,0 +1,24 @@ +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 + +def noop(expr: Expr[Any])(using Quotes): Expr[Int] = expr match + case '{ bar(${ '{ $y } }) } => y