Skip to content

Change match syntax #7610

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 7 commits into from
Nov 29, 2019
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
79 changes: 35 additions & 44 deletions compiler/src/dotty/tools/dotc/parsing/Parsers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -951,7 +951,7 @@ object Parsers {
recur(top)
}

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

recur(first)
}

def applyGiven(t: Tree, operand: () => Tree): Tree =
atSpan(startOffset(t), in.offset) {
in.nextToken()
val args = if (in.token == LPAREN) parArgumentExprs()._1 else operand() :: Nil
Apply(t, args)
}.setGivenApply()

/* -------- IDENTIFIERS AND LITERALS ------------------------------------------- */

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

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

/** Selectors ::= id { `.' id }
*
Expand Down Expand Up @@ -1737,11 +1735,10 @@ object Parsers {
* | HkTypeParamClause ‘=>’ Expr
* | [SimpleExpr `.'] id `=' Expr
* | SimpleExpr1 ArgumentExprs `=' Expr
* | Expr2
* | [‘inline’] Expr2 `match' (`{' CaseClauses `}' | CaseClause)
* | PostfixExpr [Ascription]
* | ‘inline’ InfixExpr MatchClause
* Bindings ::= `(' [Binding {`,' Binding}] `)'
* Binding ::= (id | `_') [`:' Type]
* Expr2 ::= PostfixExpr [Ascription]
* Ascription ::= `:' InfixType
* | `:' Annotation {Annotation}
* | `:' `_' `*'
Expand Down Expand Up @@ -1780,7 +1777,7 @@ object Parsers {
}
}

def expr1(location: Location.Value = Location.ElseWhere): Tree = in.token match {
def expr1(location: Location.Value = Location.ElseWhere): Tree = in.token match
case IF =>
in.endMarkerScope(IF) { ifExpr(in.offset, If) }
case WHILE =>
Expand Down Expand Up @@ -1886,31 +1883,28 @@ object Parsers {
case IF =>
ifExpr(start, InlineIf)
case _ =>
val t = postfixExpr()
if (in.token == MATCH) matchExpr(t, start, InlineMatch)
else
syntaxErrorOrIncomplete(i"`match` or `if` expected but ${in.token} found")
t
postfixExpr() match
case t @ Match(scrut, cases) =>
InlineMatch(scrut, cases).withSpan(t.span)
case t =>
syntaxError(em"`inline` must be followed by an `if` or a `match`", start)
t
else expr1Rest(postfixExpr(), location)
}
end expr1

def expr1Rest(t: Tree, location: Location.Value): Tree = in.token match {
def expr1Rest(t: Tree, location: Location.Value): Tree = in.token match
case EQUALS =>
t match {
case Ident(_) | Select(_, _) | Apply(_, _) =>
atSpan(startOffset(t), in.skipToken()) { Assign(t, subExpr()) }
case _ =>
t
}
t match
case Ident(_) | Select(_, _) | Apply(_, _) =>
atSpan(startOffset(t), in.skipToken()) { Assign(t, subExpr()) }
case _ =>
t
case COLON =>
in.nextToken()
val t1 = ascription(t, location)
if (in.token == MATCH) expr1Rest(t1, location) else t1
case MATCH =>
matchExpr(t, startOffset(t), Match)
ascription(t, location)
case _ =>
t
}
end expr1Rest

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

/** `match' (`{' CaseClauses `}' | CaseClause)
/** MatchClause ::= `match' `{' CaseClauses `}'
*/
def matchExpr(t: Tree, start: Offset, mkMatch: (Tree, List[CaseDef]) => Match) =
def matchClause(t: Tree): Match =
in.endMarkerScope(MATCH) {
atSpan(start, in.skipToken()) {
mkMatch(t, casesExpr(caseClause))
atSpan(t.span.start, in.skipToken()) {
Match(t, inBracesOrIndented(caseClauses(caseClause)))
}
}

/** `match' (`{' TypeCaseClauses `}' | TypeCaseClause)
/** `match' `{' TypeCaseClauses `}'
*/
def matchType(t: Tree): MatchTypeTree =
atSpan(t.span.start, accept(MATCH)) {
MatchTypeTree(EmptyTree, t, casesExpr(typeCaseClause))
MatchTypeTree(EmptyTree, t, inBracesOrIndented(caseClauses(typeCaseClause)))
}

/** FunParams ::= Bindings
Expand Down Expand Up @@ -2032,7 +2026,7 @@ object Parsers {
/** PostfixExpr ::= InfixExpr [id [nl]]
* InfixExpr ::= PrefixExpr
* | InfixExpr id [nl] InfixExpr
* | InfixExpr ‘given’ (InfixExpr | ParArgumentExprs)
* | InfixExpr MatchClause
*/
def postfixExpr(): Tree = postfixExprRest(prefixExpr())

Expand Down Expand Up @@ -2063,6 +2057,7 @@ object Parsers {
* | Path
* | `(' [ExprsInParens] `)'
* | SimpleExpr `.' id
* | SimpleExpr `.' MatchClause
* | SimpleExpr (TypeArgs | NamedTypeArgs)
* | SimpleExpr1 ArgumentExprs
* Quoted ::= ‘'’ ‘{’ Block ‘}’
Expand Down Expand Up @@ -2395,10 +2390,6 @@ object Parsers {
}
}

def casesExpr(clause: () => CaseDef): List[CaseDef] =
if in.token == CASE then clause() :: Nil
else inBracesOrIndented(caseClauses(clause))

/** CaseClauses ::= CaseClause {CaseClause}
* TypeCaseClauses ::= TypeCaseClause {TypeCaseClause}
*/
Expand Down
3 changes: 2 additions & 1 deletion compiler/src/dotty/tools/dotc/parsing/Scanners.scala
Original file line number Diff line number Diff line change
Expand Up @@ -525,7 +525,8 @@ object Scanners {
else if (lastWidth != nextWidth)
errorButContinue(spaceTabMismatchMsg(lastWidth, nextWidth))
currentRegion match {
case Indented(curWidth, others, prefix, outer) if curWidth < nextWidth && !others.contains(nextWidth) =>
case Indented(curWidth, others, prefix, outer)
if curWidth < nextWidth && !others.contains(nextWidth) && nextWidth != lastWidth =>
if (token == OUTDENT)
errorButContinue(
i"""The start of this line does not match any of the previous indentation widths.
Expand Down
18 changes: 8 additions & 10 deletions docs/docs/internals/syntax.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,8 +147,7 @@ FunArgTypes ::= InfixType
| ‘(’ [ ‘[given]’ FunArgType {‘,’ FunArgType } ] ‘)’
| ‘(’ ‘[given]’ TypedFunParam {‘,’ TypedFunParam } ‘)’
TypedFunParam ::= id ‘:’ Type
MatchType ::= InfixType `match`
(‘{’ TypeCaseClauses ‘}’ | TypeCaseClause)
MatchType ::= InfixType `match` ‘{’ TypeCaseClauses ‘}’
InfixType ::= RefinedType {id [nl] RefinedType} InfixOp(t1, op, t2)
RefinedType ::= WithType {[nl | ‘with’] Refinement} RefinedTypeTree(t, ds)
WithType ::= AnnotType {‘with’ AnnotType} (deprecated)
Expand Down Expand Up @@ -185,9 +184,8 @@ BlockResult ::= [‘implicit’] FunParams ‘=>’ Block
FunParams ::= Bindings
| id
| ‘_’
Expr1 ::= ‘if’ ‘(’ Expr ‘)’ {nl}
Expr [[semi] ‘else’ Expr] If(Parens(cond), thenp, elsep?)
| ‘if’ Expr ‘then’ Expr [[semi] ‘else’ Expr] If(cond, thenp, elsep?)
Expr1 ::= [‘inline’] ‘if’ ‘(’ Expr ‘)’ {nl} Expr [[semi] ‘else’ Expr] If(Parens(cond), thenp, elsep?)
| [‘inline’] ‘if’ Expr ‘then’ Expr [[semi] ‘else’ Expr] If(cond, thenp, elsep?)
| ‘while’ ‘(’ Expr ‘)’ {nl} Expr WhileDo(Parens(cond), body)
| ‘while’ Expr ‘do’ Expr WhileDo(cond, body)
| ‘try’ Expr Catches [‘finally’ Expr] Try(expr, catches, expr?)
Expand All @@ -198,17 +196,16 @@ Expr1 ::= ‘if’ ‘(’ Expr ‘)’ {nl}
| HkTypeParamClause ‘=>’ Expr PolyFunction(ts, expr)
| [SimpleExpr ‘.’] id ‘=’ Expr Assign(expr, expr)
| SimpleExpr1 ArgumentExprs ‘=’ Expr Assign(expr, expr)
| Expr2
| [‘inline’] Expr2 ‘match’
(‘{’ CaseClauses ‘}’ | CaseClause) Match(expr, cases) -- point on match
Expr2 ::= PostfixExpr [Ascription]
| PostfixExpr [Ascription]
| ‘inline’ InfixExpr MatchClause
Ascription ::= ‘:’ InfixType Typed(expr, tp)
| ‘:’ Annotation {Annotation} Typed(expr, Annotated(EmptyTree, annot)*)
Catches ::= ‘catch’ (Expr | CaseClause)
PostfixExpr ::= InfixExpr [id] PostfixOp(expr, op)
InfixExpr ::= PrefixExpr
| InfixExpr id [nl] InfixExpr InfixOp(expr, op, expr)
| InfixExpr ‘given’ (InfixExpr | ParArgumentExprs)
| InfixExpr MatchClause
MatchClause ::= ‘match’ ‘{’ CaseClauses ‘}’ Match(expr, cases)
PrefixExpr ::= [‘-’ | ‘+’ | ‘~’ | ‘!’] SimpleExpr PrefixOp(expr, op)
SimpleExpr ::= Path
| Literal
Expand All @@ -221,6 +218,7 @@ SimpleExpr ::= Path
| ‘new’ TemplateBody
| ‘(’ ExprsInParens ‘)’ Parens(exprs)
| SimpleExpr ‘.’ id Select(expr, id)
| SimpleExpr ‘.’ MatchClause
| SimpleExpr TypeArgs TypeApply(expr, args)
| SimpleExpr ArgumentExprs Apply(expr, args)
| SimpleExpr ‘_’ PostfixOp(expr, _)
Expand Down
43 changes: 43 additions & 0 deletions docs/docs/reference/changed-features/match-syntax.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
---
layout: doc-page
title: Match Expressions
---

The syntactical precedence of match expressions has been changed.
`match` is still a keyword, but it is used like an alphabetical operator. This has several consequences:

1. `match` expressions can be chained:

```scala
xs match {
case Nil => "empty"
case x :: xs1 => "nonempty"
} match {
case "empty" => 0
case "nonempty" => 1
}

2. `match` may follow a period:

```scala
if xsDefined
&& xs.match {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So that's why they call it "dotty".

case Nil => false
case _ => true
}
then "nonempty"
else "empty"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I fear the selection may break the regularity of Scala syntax. It seems the use case is not important enough to compensate for the loss of regularity.


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
written `(x: T) match { ... }`.

## Syntax

The new syntax of match expressions is as follows.
```
InfixExpr ::= ...
| InfixExpr MatchClause
SimpleExpr ::= ...
| SimpleExpr ‘.’ MatchClause
MatchClause ::= ‘match’ ‘{’ CaseClauses ‘}’
```
17 changes: 7 additions & 10 deletions docs/docs/reference/contextual/typeclasses.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,25 +10,22 @@ with canonical implementations defined by given instances. Here are some example
### Semigroups and monoids:

```scala
trait SemiGroup[T] {
trait SemiGroup[T] with
def (x: T) combine (y: T): T
}
trait Monoid[T] extends SemiGroup[T] {

trait Monoid[T] extends SemiGroup[T] with
def unit: T
}
object Monoid {

object Monoid with
def apply[T](given Monoid[T]) = summon[Monoid[T]]
}

given Monoid[String] {
given Monoid[String] with
def (x: String) combine (y: String): String = x.concat(y)
def unit: String = ""
}

given Monoid[Int] {
given Monoid[Int] with
def (x: Int) combine (y: Int): Int = x + y
def unit: Int = 0
}

def sum[T: Monoid](xs: List[T]): T =
xs.foldLeft(Monoid[T].unit)(_.combine(_))
Expand Down
2 changes: 2 additions & 0 deletions docs/sidebar.yml
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,8 @@ sidebar:
url: docs/reference/changed-features/implicit-conversions.html
- title: Overload Resolution
url: docs/reference/changed-features/overload-resolution.html
- title: Match Expressions
url: docs/reference/changed-features/match-syntax.html
- title: Vararg Patterns
url: docs/reference/changed-features/vararg-patterns.html
- title: Pattern Bindings
Expand Down
12 changes: 6 additions & 6 deletions tests/neg/cannot-reduce-inline-match.check
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
-- Error: tests/neg/cannot-reduce-inline-match.scala:3:13 --------------------------------------------------------------
3 | inline x match { // error
| ^
| cannot reduce inline match with
| scrutinee: {
| "f"
| } : String("f")
| patterns : case _:Int
| ^
| cannot reduce inline match with
| scrutinee: {
| "f"
| } : String("f")
| patterns : case _:Int
| This location is in code that was inlined at cannot-reduce-inline-match.scala:9
4 | case _: Int =>
5 | }
2 changes: 1 addition & 1 deletion tests/pos-special/fatal-warnings/unchecked-scrutinee.scala
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
object Test {
List(1: @unchecked, 2, 3): @unchecked match {
(List(1: @unchecked, 2, 3): @unchecked) match {
case a :: as =>
}
}
16 changes: 8 additions & 8 deletions tests/pos/i6997c.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ import scala.quoted.{_, given}, scala.quoted.matching._

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

def mcrImpl(body: Expr[Any])(given ctx: QuoteContext): Expr[Any] = {
body match case '{$x: $t} =>
'{
val tmp: $t = $x
println(tmp)
tmp
}
}
def mcrImpl(body: Expr[Any])(given ctx: QuoteContext): Expr[Any] =
body match
case '{$x: $t} =>
'{
val tmp: $t = $x
println(tmp)
tmp
}
20 changes: 20 additions & 0 deletions tests/pos/matches.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package matches
object Test with
2 min 3 match
case 2 => "OK"
case 3 => "?"
match
case "OK" =>
case "?" => assertFail()

val x = 4
if 2 < 3
|| x.match
case 4 => true
case _ => false
|| (2 + 2).match
case 4 => true
case _ => false
then
println("ok")
end Test
2 changes: 2 additions & 0 deletions tests/pos/multi-given.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,5 @@ trait C

def fancy(given a: A, b: B, c: C) = "Fancy!"
def foo(implicit a: A, b: B, c: C) = "foo"

given A, B {}
Loading