Skip to content

Parse splices inside quoted patterns as patterns #14277

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jan 20, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 11 additions & 6 deletions compiler/src/dotty/tools/dotc/parsing/Parsers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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])
Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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 =>
Expand Down
8 changes: 5 additions & 3 deletions docs/docs/internals/syntax.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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
Expand Down
6 changes: 4 additions & 2 deletions docs/docs/reference/syntax.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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]
Expand Down
10 changes: 10 additions & 0 deletions tests/neg/splice-pat.check
Original file line number Diff line number Diff line change
@@ -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
15 changes: 15 additions & 0 deletions tests/neg/splice-pat.scala
Original file line number Diff line number Diff line change
@@ -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
17 changes: 17 additions & 0 deletions tests/pos-macros/splice-pat/Macro_1.scala
Original file line number Diff line number Diff line change
@@ -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)}
}
3 changes: 3 additions & 0 deletions tests/pos-macros/splice-pat/Test_1.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
object Test {
assert(Macro.macr(Macro.foo(1 + 2)) == (3, 1))
}
24 changes: 24 additions & 0 deletions tests/pos/splice-pat.scala
Original file line number Diff line number Diff line change
@@ -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