From 28b33fdec7b5d7e4d37278d9d37bb9e7303e3f2b Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 6 Nov 2019 11:54:26 +0100 Subject: [PATCH 1/3] Syntax change for single cases Allow a single case clause after a match or catch without requiring braces or indent tokens around it. E.g. try ... catch case ex: Ex => ... s match case p => ... Ratiionale: With indentation syntax, it feels natural to shorten try ... catch case ex: Ex => ... to try ... catch case ex: Ex => ... --- .../dotty/tools/dotc/parsing/Parsers.scala | 33 +++++++++++-------- docs/docs/internals/syntax.md | 9 ++--- tests/pos/single-case.scala | 11 +++++++ 3 files changed, 35 insertions(+), 18 deletions(-) create mode 100644 tests/pos/single-case.scala diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 18344de0019b..30c874c54225 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -1248,6 +1248,9 @@ object Parsers { // note: next is defined here because current == NEWLINE if (in.token == NEWLINE && in.next.token == token) in.nextToken() + def newLinesOptWhenFollowedBy(token: Int): Unit = + if in.isNewLine && in.next.token == token then in.nextToken() + def newLinesOptWhenFollowedBy(name: Name): Unit = if in.isNewLine && in.next.token == IDENTIFIER && in.next.name == name then in.nextToken() @@ -1742,8 +1745,7 @@ object Parsers { * | [SimpleExpr `.'] id `=' Expr * | SimpleExpr1 ArgumentExprs `=' Expr * | Expr2 - * | [‘inline’] Expr2 `match' `{' CaseClauses `}' - * | `given' `match' `{' ImplicitCaseClauses `}' + * | [‘inline’] Expr2 `match' (`{' CaseClauses `}' | CaseClause) * Bindings ::= `(' [Binding {`,' Binding}] `)' * Binding ::= (id | `_') [`:' Type] * Expr2 ::= PostfixExpr [Ascription] @@ -1829,11 +1831,12 @@ object Parsers { atSpan(in.skipToken()) { val body = expr() val (handler, handlerStart) = - if (in.token == CATCH) { + if in.token == CATCH then val span = in.offset in.nextToken() - (subExpr(), span) - } + (if in.token == CASE then Match(EmptyTree, caseClause() :: Nil) + else subExpr(), + span) else (EmptyTree, -1) handler match { @@ -1955,20 +1958,20 @@ object Parsers { mkIf(cond, thenp, elsep) } - /** `match' { CaseClauses } + /** `match' (`{' CaseClauses `}' | CaseClause) */ def matchExpr(t: Tree, start: Offset, mkMatch: (Tree, List[CaseDef]) => Match) = indentRegion(MATCH) { atSpan(start, in.skipToken()) { - mkMatch(t, inBracesOrIndented(caseClauses(caseClause))) + mkMatch(t, casesExpr(caseClause)) } } - /** `match' { TypeCaseClauses } + /** `match' (`{' TypeCaseClauses `}' | TypeCaseClause) */ def matchType(t: Tree): MatchTypeTree = atSpan(t.span.start, accept(MATCH)) { - inBracesOrIndented(MatchTypeTree(EmptyTree, t, caseClauses(typeCaseClause))) + MatchTypeTree(EmptyTree, t, casesExpr(typeCaseClause)) } /** FunParams ::= Bindings @@ -2399,8 +2402,11 @@ object Parsers { } } + def casesExpr(clause: () => CaseDef): List[CaseDef] = + if in.token == CASE then clause() :: Nil + else inBracesOrIndented(caseClauses(clause)) + /** CaseClauses ::= CaseClause {CaseClause} - * ImplicitCaseClauses ::= ImplicitCaseClause {ImplicitCaseClause} * TypeCaseClauses ::= TypeCaseClause {TypeCaseClause} */ def caseClauses(clause: () => CaseDef): List[CaseDef] = { @@ -2410,9 +2416,8 @@ object Parsers { buf.toList } - /** CaseClause ::= ‘case’ Pattern [Guard] `=>' Block - * ImplicitCaseClause ::= ‘case’ PatVar [Ascription] [Guard] `=>' Block - */ + /** CaseClause ::= ‘case’ Pattern [Guard] `=>' Block + */ val caseClause: () => CaseDef = () => atSpan(in.offset) { val (pat, grd) = inSepRegion(LPAREN, RPAREN) { accept(CASE) @@ -2430,7 +2435,7 @@ object Parsers { } CaseDef(pat, EmptyTree, atSpan(accept(ARROW)) { val t = typ() - if (isStatSep) in.nextToken() + newLinesOptWhenFollowedBy(CASE) t }) } diff --git a/docs/docs/internals/syntax.md b/docs/docs/internals/syntax.md index d9998a1e09b5..91c6a1308d45 100644 --- a/docs/docs/internals/syntax.md +++ b/docs/docs/internals/syntax.md @@ -147,7 +147,8 @@ FunArgTypes ::= InfixType | ‘(’ [ ‘[given]’ FunArgType {‘,’ FunArgType } ] ‘)’ | ‘(’ ‘[given]’ TypedFunParam {‘,’ TypedFunParam } ‘)’ TypedFunParam ::= id ‘:’ Type -MatchType ::= InfixType `match` TypeCaseClauses +MatchType ::= InfixType `match` + (‘{’ TypeCaseClauses ‘}’ | TypeCaseClause) InfixType ::= RefinedType {id [nl] RefinedType} InfixOp(t1, op, t2) RefinedType ::= WithType {[nl | ‘with’] Refinement} RefinedTypeTree(t, ds) WithType ::= AnnotType {‘with’ AnnotType} (deprecated) @@ -198,12 +199,12 @@ Expr1 ::= ‘if’ ‘(’ Expr ‘)’ {nl} | [SimpleExpr ‘.’] id ‘=’ Expr Assign(expr, expr) | SimpleExpr1 ArgumentExprs ‘=’ Expr Assign(expr, expr) | Expr2 - | [‘inline’] Expr2 ‘match’ ‘{’ CaseClauses ‘}’ Match(expr, cases) -- point on match - | ‘given’ ‘match’ ‘{’ ImplicitCaseClauses ‘}’ + | [‘inline’] Expr2 ‘match’ + (‘{’ CaseClauses ‘}’ | CaseClause) Match(expr, cases) -- point on match Expr2 ::= PostfixExpr [Ascription] Ascription ::= ‘:’ InfixType Typed(expr, tp) | ‘:’ Annotation {Annotation} Typed(expr, Annotated(EmptyTree, annot)*) -Catches ::= ‘catch’ Expr +Catches ::= ‘catch’ (Expr | CaseClause) PostfixExpr ::= InfixExpr [id] PostfixOp(expr, op) InfixExpr ::= PrefixExpr | InfixExpr id [nl] InfixExpr InfixOp(expr, op, expr) diff --git a/tests/pos/single-case.scala b/tests/pos/single-case.scala new file mode 100644 index 000000000000..3701bdc3e7f8 --- /dev/null +++ b/tests/pos/single-case.scala @@ -0,0 +1,11 @@ +object test with + + type T[X] = X match case Int => X + + try + println("hi") + catch case ex: java.io.IOException => println("ho") + + 1 match case x: Int => println(x) + + From 85e062fe55d5afb6d70c891a14901cfe4aed0389 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 6 Nov 2019 12:33:00 +0100 Subject: [PATCH 2/3] Fix test Semicolons in match types are not allowed. They were never allowed in syntax.md, but the parser accepted them. The fix is to just drop the semicolon. --- tests/run-macros/tasty-tree-map/quoted_2.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/run-macros/tasty-tree-map/quoted_2.scala b/tests/run-macros/tasty-tree-map/quoted_2.scala index 256a864f88cd..40e8aeaa7386 100644 --- a/tests/run-macros/tasty-tree-map/quoted_2.scala +++ b/tests/run-macros/tasty-tree-map/quoted_2.scala @@ -42,7 +42,7 @@ object Test { println(identityMaped({ val x: Int | Any = 60; x })) println(identityMaped({ def f(x: => Int): Int = x; f(61) })) println(identityMaped({ type T[X] = X; val x: T[Int] = 62; x })) - println(identityMaped({ type T[X] = X match { case Int => String; case String => Int }; val x: T[String] = 63; x })) + println(identityMaped({ type T[X] = X match { case Int => String case String => Int }; val x: T[String] = 63; x })) println(identityMaped((Nil: List[Int]) match { case _: List[t] => 64 })) println(identityMaped({ object F { type T = Int }; val x: F.T = 65; x })) println(identityMaped({ val x: Foo { type T = Int } = new Foo { type T = Int; def y: Int = 66 }; x.y })) From c878c01805bf61affa8b899ba93772167146e617 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 7 Nov 2019 11:10:55 +0100 Subject: [PATCH 3/3] Add test --- tests/neg/case-semi.scala | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 tests/neg/case-semi.scala diff --git a/tests/neg/case-semi.scala b/tests/neg/case-semi.scala new file mode 100644 index 000000000000..d05919805fa6 --- /dev/null +++ b/tests/neg/case-semi.scala @@ -0,0 +1,9 @@ +object Test with + type X = Int + val x: X = 1 + + type T2 = X match { case Int => String case String => Int } + x match { case 1 => 3; case 2 => 4 } + x match { case 1 => 3 case 2 => 4 } + + type T1 = X match { case Int => String; case String => Int } // error // error