Skip to content

Commit 4adc435

Browse files
authored
Merge pull request #7610 from dotty-staging/change-match-syntax
Change match syntax
2 parents 4ba3710 + 09e5aec commit 4adc435

File tree

14 files changed

+161
-111
lines changed

14 files changed

+161
-111
lines changed

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

Lines changed: 35 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -951,7 +951,7 @@ object Parsers {
951951
recur(top)
952952
}
953953

954-
/** operand { infixop operand | ‘given’ (operand | ParArgumentExprs) } [postfixop],
954+
/** operand { infixop operand | MatchClause } [postfixop],
955955
*
956956
* respecting rules of associativity and precedence.
957957
* @param isOperator the current token counts as an operator.
@@ -981,18 +981,14 @@ object Parsers {
981981
}
982982
else recur(operand())
983983
}
984-
else reduceStack(base, top, minPrec, leftAssoc = true, in.name, isType)
984+
else
985+
val t = reduceStack(base, top, minPrec, leftAssoc = true, in.name, isType)
986+
if !isType && in.token == MATCH then recur(matchClause(t))
987+
else t
985988

986989
recur(first)
987990
}
988991

989-
def applyGiven(t: Tree, operand: () => Tree): Tree =
990-
atSpan(startOffset(t), in.offset) {
991-
in.nextToken()
992-
val args = if (in.token == LPAREN) parArgumentExprs()._1 else operand() :: Nil
993-
Apply(t, args)
994-
}.setGivenApply()
995-
996992
/* -------- IDENTIFIERS AND LITERALS ------------------------------------------- */
997993

998994
/** Accept identifier and return its name as a term name. */
@@ -1028,9 +1024,11 @@ object Parsers {
10281024
def wildcardIdent(): Ident =
10291025
atSpan(accept(USCORE)) { Ident(nme.WILDCARD) }
10301026

1031-
/** Accept identifier acting as a selector on given tree `t`. */
1027+
/** Accept identifier or match clause acting as a selector on given tree `t` */
10321028
def selector(t: Tree): Tree =
1033-
atSpan(startOffset(t), in.offset) { Select(t, ident()) }
1029+
atSpan(startOffset(t), in.offset) {
1030+
if in.token == MATCH then matchClause(t) else Select(t, ident())
1031+
}
10341032

10351033
/** Selectors ::= id { `.' id }
10361034
*
@@ -1737,11 +1735,10 @@ object Parsers {
17371735
* | HkTypeParamClause ‘=>’ Expr
17381736
* | [SimpleExpr `.'] id `=' Expr
17391737
* | SimpleExpr1 ArgumentExprs `=' Expr
1740-
* | Expr2
1741-
* | [‘inline’] Expr2 `match' (`{' CaseClauses `}' | CaseClause)
1738+
* | PostfixExpr [Ascription]
1739+
* | ‘inline’ InfixExpr MatchClause
17421740
* Bindings ::= `(' [Binding {`,' Binding}] `)'
17431741
* Binding ::= (id | `_') [`:' Type]
1744-
* Expr2 ::= PostfixExpr [Ascription]
17451742
* Ascription ::= `:' InfixType
17461743
* | `:' Annotation {Annotation}
17471744
* | `:' `_' `*'
@@ -1780,7 +1777,7 @@ object Parsers {
17801777
}
17811778
}
17821779

1783-
def expr1(location: Location.Value = Location.ElseWhere): Tree = in.token match {
1780+
def expr1(location: Location.Value = Location.ElseWhere): Tree = in.token match
17841781
case IF =>
17851782
in.endMarkerScope(IF) { ifExpr(in.offset, If) }
17861783
case WHILE =>
@@ -1886,31 +1883,28 @@ object Parsers {
18861883
case IF =>
18871884
ifExpr(start, InlineIf)
18881885
case _ =>
1889-
val t = postfixExpr()
1890-
if (in.token == MATCH) matchExpr(t, start, InlineMatch)
1891-
else
1892-
syntaxErrorOrIncomplete(i"`match` or `if` expected but ${in.token} found")
1893-
t
1886+
postfixExpr() match
1887+
case t @ Match(scrut, cases) =>
1888+
InlineMatch(scrut, cases).withSpan(t.span)
1889+
case t =>
1890+
syntaxError(em"`inline` must be followed by an `if` or a `match`", start)
1891+
t
18941892
else expr1Rest(postfixExpr(), location)
1895-
}
1893+
end expr1
18961894

1897-
def expr1Rest(t: Tree, location: Location.Value): Tree = in.token match {
1895+
def expr1Rest(t: Tree, location: Location.Value): Tree = in.token match
18981896
case EQUALS =>
1899-
t match {
1900-
case Ident(_) | Select(_, _) | Apply(_, _) =>
1901-
atSpan(startOffset(t), in.skipToken()) { Assign(t, subExpr()) }
1902-
case _ =>
1903-
t
1904-
}
1897+
t match
1898+
case Ident(_) | Select(_, _) | Apply(_, _) =>
1899+
atSpan(startOffset(t), in.skipToken()) { Assign(t, subExpr()) }
1900+
case _ =>
1901+
t
19051902
case COLON =>
19061903
in.nextToken()
1907-
val t1 = ascription(t, location)
1908-
if (in.token == MATCH) expr1Rest(t1, location) else t1
1909-
case MATCH =>
1910-
matchExpr(t, startOffset(t), Match)
1904+
ascription(t, location)
19111905
case _ =>
19121906
t
1913-
}
1907+
end expr1Rest
19141908

19151909
def ascription(t: Tree, location: Location.Value): Tree = atSpan(startOffset(t)) {
19161910
in.token match {
@@ -1951,20 +1945,20 @@ object Parsers {
19511945
mkIf(cond, thenp, elsep)
19521946
}
19531947

1954-
/** `match' (`{' CaseClauses `}' | CaseClause)
1948+
/** MatchClause ::= `match' `{' CaseClauses `}'
19551949
*/
1956-
def matchExpr(t: Tree, start: Offset, mkMatch: (Tree, List[CaseDef]) => Match) =
1950+
def matchClause(t: Tree): Match =
19571951
in.endMarkerScope(MATCH) {
1958-
atSpan(start, in.skipToken()) {
1959-
mkMatch(t, casesExpr(caseClause))
1952+
atSpan(t.span.start, in.skipToken()) {
1953+
Match(t, inBracesOrIndented(caseClauses(caseClause)))
19601954
}
19611955
}
19621956

1963-
/** `match' (`{' TypeCaseClauses `}' | TypeCaseClause)
1957+
/** `match' `{' TypeCaseClauses `}'
19641958
*/
19651959
def matchType(t: Tree): MatchTypeTree =
19661960
atSpan(t.span.start, accept(MATCH)) {
1967-
MatchTypeTree(EmptyTree, t, casesExpr(typeCaseClause))
1961+
MatchTypeTree(EmptyTree, t, inBracesOrIndented(caseClauses(typeCaseClause)))
19681962
}
19691963

19701964
/** FunParams ::= Bindings
@@ -2032,7 +2026,7 @@ object Parsers {
20322026
/** PostfixExpr ::= InfixExpr [id [nl]]
20332027
* InfixExpr ::= PrefixExpr
20342028
* | InfixExpr id [nl] InfixExpr
2035-
* | InfixExpr ‘given’ (InfixExpr | ParArgumentExprs)
2029+
* | InfixExpr MatchClause
20362030
*/
20372031
def postfixExpr(): Tree = postfixExprRest(prefixExpr())
20382032

@@ -2063,6 +2057,7 @@ object Parsers {
20632057
* | Path
20642058
* | `(' [ExprsInParens] `)'
20652059
* | SimpleExpr `.' id
2060+
* | SimpleExpr `.' MatchClause
20662061
* | SimpleExpr (TypeArgs | NamedTypeArgs)
20672062
* | SimpleExpr1 ArgumentExprs
20682063
* Quoted ::= ‘'’ ‘{’ Block ‘}’
@@ -2395,10 +2390,6 @@ object Parsers {
23952390
}
23962391
}
23972392

2398-
def casesExpr(clause: () => CaseDef): List[CaseDef] =
2399-
if in.token == CASE then clause() :: Nil
2400-
else inBracesOrIndented(caseClauses(clause))
2401-
24022393
/** CaseClauses ::= CaseClause {CaseClause}
24032394
* TypeCaseClauses ::= TypeCaseClause {TypeCaseClause}
24042395
*/

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -524,7 +524,8 @@ object Scanners {
524524
else if (lastWidth != nextWidth)
525525
errorButContinue(spaceTabMismatchMsg(lastWidth, nextWidth))
526526
currentRegion match {
527-
case Indented(curWidth, others, prefix, outer) if curWidth < nextWidth && !others.contains(nextWidth) =>
527+
case Indented(curWidth, others, prefix, outer)
528+
if curWidth < nextWidth && !others.contains(nextWidth) && nextWidth != lastWidth =>
528529
if (token == OUTDENT)
529530
errorButContinue(
530531
i"""The start of this line does not match any of the previous indentation widths.

docs/docs/internals/syntax.md

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -147,8 +147,7 @@ FunArgTypes ::= InfixType
147147
| ‘(’ [ ‘[given]’ FunArgType {‘,’ FunArgType } ] ‘)’
148148
| ‘(’ ‘[given]’ TypedFunParam {‘,’ TypedFunParam } ‘)’
149149
TypedFunParam ::= id ‘:’ Type
150-
MatchType ::= InfixType `match`
151-
(‘{’ TypeCaseClauses ‘}’ | TypeCaseClause)
150+
MatchType ::= InfixType `match` ‘{’ TypeCaseClauses ‘}’
152151
InfixType ::= RefinedType {id [nl] RefinedType} InfixOp(t1, op, t2)
153152
RefinedType ::= WithType {[nl | ‘with’] Refinement} RefinedTypeTree(t, ds)
154153
WithType ::= AnnotType {‘with’ AnnotType} (deprecated)
@@ -185,9 +184,8 @@ BlockResult ::= [‘implicit’] FunParams ‘=>’ Block
185184
FunParams ::= Bindings
186185
| id
187186
| ‘_’
188-
Expr1 ::= ‘if’ ‘(’ Expr ‘)’ {nl}
189-
Expr [[semi] ‘else’ Expr] If(Parens(cond), thenp, elsep?)
190-
| ‘if’ Expr ‘then’ Expr [[semi] ‘else’ Expr] If(cond, thenp, elsep?)
187+
Expr1 ::= [‘inline’] ‘if’ ‘(’ Expr ‘)’ {nl} Expr [[semi] ‘else’ Expr] If(Parens(cond), thenp, elsep?)
188+
| [‘inline’] ‘if’ Expr ‘then’ Expr [[semi] ‘else’ Expr] If(cond, thenp, elsep?)
191189
| ‘while’ ‘(’ Expr ‘)’ {nl} Expr WhileDo(Parens(cond), body)
192190
| ‘while’ Expr ‘do’ Expr WhileDo(cond, body)
193191
| ‘try’ Expr Catches [‘finally’ Expr] Try(expr, catches, expr?)
@@ -198,17 +196,16 @@ Expr1 ::= ‘if’ ‘(’ Expr ‘)’ {nl}
198196
| HkTypeParamClause ‘=>’ Expr PolyFunction(ts, expr)
199197
| [SimpleExpr ‘.’] id ‘=’ Expr Assign(expr, expr)
200198
| SimpleExpr1 ArgumentExprs ‘=’ Expr Assign(expr, expr)
201-
| Expr2
202-
| [‘inline’] Expr2 ‘match’
203-
(‘{’ CaseClauses ‘}’ | CaseClause) Match(expr, cases) -- point on match
204-
Expr2 ::= PostfixExpr [Ascription]
199+
| PostfixExpr [Ascription]
200+
| ‘inline’ InfixExpr MatchClause
205201
Ascription ::= ‘:’ InfixType Typed(expr, tp)
206202
| ‘:’ Annotation {Annotation} Typed(expr, Annotated(EmptyTree, annot)*)
207203
Catches ::= ‘catch’ (Expr | CaseClause)
208204
PostfixExpr ::= InfixExpr [id] PostfixOp(expr, op)
209205
InfixExpr ::= PrefixExpr
210206
| InfixExpr id [nl] InfixExpr InfixOp(expr, op, expr)
211-
| InfixExpr ‘given’ (InfixExpr | ParArgumentExprs)
207+
| InfixExpr MatchClause
208+
MatchClause ::= ‘match’ ‘{’ CaseClauses ‘}’ Match(expr, cases)
212209
PrefixExpr ::= [‘-’ | ‘+’ | ‘~’ | ‘!’] SimpleExpr PrefixOp(expr, op)
213210
SimpleExpr ::= Path
214211
| Literal
@@ -221,6 +218,7 @@ SimpleExpr ::= Path
221218
| ‘new’ TemplateBody
222219
| ‘(’ ExprsInParens ‘)’ Parens(exprs)
223220
| SimpleExpr ‘.’ id Select(expr, id)
221+
| SimpleExpr ‘.’ MatchClause
224222
| SimpleExpr TypeArgs TypeApply(expr, args)
225223
| SimpleExpr ArgumentExprs Apply(expr, args)
226224
| SimpleExpr ‘_’ PostfixOp(expr, _)
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
---
2+
layout: doc-page
3+
title: Match Expressions
4+
---
5+
6+
The syntactical precedence of match expressions has been changed.
7+
`match` is still a keyword, but it is used like an alphabetical operator. This has several consequences:
8+
9+
1. `match` expressions can be chained:
10+
11+
```scala
12+
xs match {
13+
case Nil => "empty"
14+
case x :: xs1 => "nonempty"
15+
} match {
16+
case "empty" => 0
17+
case "nonempty" => 1
18+
}
19+
20+
2. `match` may follow a period:
21+
22+
```scala
23+
if xsDefined
24+
&& xs.match {
25+
case Nil => false
26+
case _ => true
27+
}
28+
then "nonempty"
29+
else "empty"
30+
31+
3. The scrutinee of a match expression must be an `InfixExpr`. Previously the scrutinee could be followed by a type ascription `: T`, but this is no longer supported. So `x : T match { ... }` now has to be
32+
written `(x: T) match { ... }`.
33+
34+
## Syntax
35+
36+
The new syntax of match expressions is as follows.
37+
```
38+
InfixExpr ::= ...
39+
| InfixExpr MatchClause
40+
SimpleExpr ::= ...
41+
| SimpleExpr ‘.’ MatchClause
42+
MatchClause ::= ‘match’ ‘{’ CaseClauses ‘}’
43+
```

docs/docs/reference/contextual/typeclasses.md

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,25 +10,22 @@ with canonical implementations defined by given instances. Here are some example
1010
### Semigroups and monoids:
1111

1212
```scala
13-
trait SemiGroup[T] {
13+
trait SemiGroup[T] with
1414
def (x: T) combine (y: T): T
15-
}
16-
trait Monoid[T] extends SemiGroup[T] {
15+
16+
trait Monoid[T] extends SemiGroup[T] with
1717
def unit: T
18-
}
19-
object Monoid {
18+
19+
object Monoid with
2020
def apply[T](given Monoid[T]) = summon[Monoid[T]]
21-
}
2221

23-
given Monoid[String] {
22+
given Monoid[String] with
2423
def (x: String) combine (y: String): String = x.concat(y)
2524
def unit: String = ""
26-
}
2725

28-
given Monoid[Int] {
26+
given Monoid[Int] with
2927
def (x: Int) combine (y: Int): Int = x + y
3028
def unit: Int = 0
31-
}
3229

3330
def sum[T: Monoid](xs: List[T]): T =
3431
xs.foldLeft(Monoid[T].unit)(_.combine(_))

docs/sidebar.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,8 @@ sidebar:
127127
url: docs/reference/changed-features/implicit-conversions.html
128128
- title: Overload Resolution
129129
url: docs/reference/changed-features/overload-resolution.html
130+
- title: Match Expressions
131+
url: docs/reference/changed-features/match-syntax.html
130132
- title: Vararg Patterns
131133
url: docs/reference/changed-features/vararg-patterns.html
132134
- title: Pattern Bindings
Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
-- Error: tests/neg/cannot-reduce-inline-match.scala:3:13 --------------------------------------------------------------
22
3 | inline x match { // error
3-
| ^
4-
| cannot reduce inline match with
5-
| scrutinee: {
6-
| "f"
7-
| } : String("f")
8-
| patterns : case _:Int
3+
| ^
4+
| cannot reduce inline match with
5+
| scrutinee: {
6+
| "f"
7+
| } : String("f")
8+
| patterns : case _:Int
99
| This location is in code that was inlined at cannot-reduce-inline-match.scala:9
1010
4 | case _: Int =>
1111
5 | }
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
object Test {
2-
List(1: @unchecked, 2, 3): @unchecked match {
2+
(List(1: @unchecked, 2, 3): @unchecked) match {
33
case a :: as =>
44
}
55
}

tests/pos/i6997c.scala

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@ import scala.quoted.{_, given}, scala.quoted.matching._
44

55
inline def mcr(x: => Any): Any = ${mcrImpl('x)}
66

7-
def mcrImpl(body: Expr[Any])(given ctx: QuoteContext): Expr[Any] = {
8-
body match case '{$x: $t} =>
9-
'{
10-
val tmp: $t = $x
11-
println(tmp)
12-
tmp
13-
}
14-
}
7+
def mcrImpl(body: Expr[Any])(given ctx: QuoteContext): Expr[Any] =
8+
body match
9+
case '{$x: $t} =>
10+
'{
11+
val tmp: $t = $x
12+
println(tmp)
13+
tmp
14+
}

tests/pos/matches.scala

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package matches
2+
object Test with
3+
2 min 3 match
4+
case 2 => "OK"
5+
case 3 => "?"
6+
match
7+
case "OK" =>
8+
case "?" => assertFail()
9+
10+
val x = 4
11+
if 2 < 3
12+
|| x.match
13+
case 4 => true
14+
case _ => false
15+
|| (2 + 2).match
16+
case 4 => true
17+
case _ => false
18+
then
19+
println("ok")
20+
end Test

tests/pos/multi-given.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,5 @@ trait C
44

55
def fancy(given a: A, b: B, c: C) = "Fancy!"
66
def foo(implicit a: A, b: B, c: C) = "foo"
7+
8+
given A, B {}

0 commit comments

Comments
 (0)