From 14e8847eb1da589833ef4ec9e90b87c0c4abfdfa Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 22 Nov 2019 16:44:26 +0100 Subject: [PATCH 1/7] Drop special single-case syntax for match - A match usually has more than one case anyway - That way, we increase the precedence of `match`, since what follows is always atomic. --- .../src/dotty/tools/dotc/parsing/Parsers.scala | 12 ++++-------- docs/docs/internals/syntax.md | 6 ++---- tests/pos/i6997c.scala | 16 ++++++++-------- tests/pos/single-case.scala | 4 ---- 4 files changed, 14 insertions(+), 24 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 9d47db6171c1..190822ff387c 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -1951,20 +1951,20 @@ object Parsers { mkIf(cond, thenp, elsep) } - /** `match' (`{' CaseClauses `}' | CaseClause) + /** `match' `{' CaseClauses `}' */ def matchExpr(t: Tree, start: Offset, mkMatch: (Tree, List[CaseDef]) => Match) = in.endMarkerScope(MATCH) { atSpan(start, in.skipToken()) { - mkMatch(t, casesExpr(caseClause)) + mkMatch(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 @@ -2395,10 +2395,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} */ diff --git a/docs/docs/internals/syntax.md b/docs/docs/internals/syntax.md index 481f5e78dfe8..8130731661ad 100644 --- a/docs/docs/internals/syntax.md +++ b/docs/docs/internals/syntax.md @@ -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) @@ -199,8 +198,7 @@ Expr1 ::= ‘if’ ‘(’ Expr ‘)’ {nl} | [SimpleExpr ‘.’] id ‘=’ Expr Assign(expr, expr) | SimpleExpr1 ArgumentExprs ‘=’ Expr Assign(expr, expr) | Expr2 - | [‘inline’] Expr2 ‘match’ - (‘{’ CaseClauses ‘}’ | CaseClause) Match(expr, cases) -- point on match + | [‘inline’] Expr2 ‘match’ ‘{’ CaseClauses ‘}’ Match(expr, cases) -- point on match Expr2 ::= PostfixExpr [Ascription] Ascription ::= ‘:’ InfixType Typed(expr, tp) | ‘:’ Annotation {Annotation} Typed(expr, Annotated(EmptyTree, annot)*) diff --git a/tests/pos/i6997c.scala b/tests/pos/i6997c.scala index 7702b5e998c9..9a9b481b335b 100644 --- a/tests/pos/i6997c.scala +++ b/tests/pos/i6997c.scala @@ -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 + } diff --git a/tests/pos/single-case.scala b/tests/pos/single-case.scala index 3701bdc3e7f8..82466ec31733 100644 --- a/tests/pos/single-case.scala +++ b/tests/pos/single-case.scala @@ -1,11 +1,7 @@ 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 02617beead380f8b2921461514ac60b2467162df Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 23 Nov 2019 19:06:58 +0100 Subject: [PATCH 2/7] Change match syntax `match` is treated like an alphabetic operator, can be appear infix as well as after `.`. Change the `inline` syntax from ``` inline if ... inline s match ... ``` to ``` if inline ... s match inline ... ``` this makes it work better with the new rules for `match`. --- .../dotty/tools/dotc/parsing/Parsers.scala | 68 ++++++++++--------- .../dotty/tools/dotc/parsing/Scanners.scala | 3 +- docs/docs/internals/syntax.md | 10 +-- docs/docs/reference/contextual/typeclasses.md | 17 ++--- .../fatal-warnings/unchecked-scrutinee.scala | 2 +- tests/pos/matches.scala | 20 ++++++ tests/pos/multi-given.scala | 2 + tests/run/typeclass-derivation2.scala | 8 +-- 8 files changed, 76 insertions(+), 54 deletions(-) create mode 100644 tests/pos/matches.scala diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 190822ff387c..c9823678d760 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -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. @@ -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, t.span.start, Match)) + 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. */ @@ -1028,9 +1024,12 @@ 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, t.span.start, Match) + else Select(t, ident()) + } /** Selectors ::= id { `.' id } * @@ -1877,7 +1876,7 @@ object Parsers { } } case _ => - if isIdent(nme.inline) + if isIdent(nme.inline) // TODO: drop this clause && !in.inModifierPosition() && in.lookaheadIn(in.canStartExprTokens) then @@ -1886,31 +1885,27 @@ object Parsers { case IF => ifExpr(start, InlineIf) case _ => - val t = postfixExpr() - if (in.token == MATCH) matchExpr(t, start, InlineMatch) + val t = prefixExpr() + if (in.token == MATCH) matchClause(t, start, InlineMatch) else - syntaxErrorOrIncomplete(i"`match` or `if` expected but ${in.token} found") + syntaxErrorOrIncomplete(i"`match` or `if` expected but ${in} found") t else expr1Rest(postfixExpr(), location) } - 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 { @@ -1938,11 +1933,14 @@ object Parsers { } } - /** `if' `(' Expr `)' {nl} Expr [[semi] else Expr] - * `if' Expr `then' Expr [[semi] else Expr] + /** `if' [‘inline’] `(' Expr `)' {nl} Expr [[semi] else Expr] + * `if' [‘inline’] Expr `then' Expr [[semi] else Expr] */ - def ifExpr(start: Offset, mkIf: (Tree, Tree, Tree) => If): If = + def ifExpr(start: Offset, mkIf0: (Tree, Tree, Tree) => If): If = atSpan(start, in.skipToken()) { + val mkIf = + if isIdent(nme.inline) then { in.nextToken(); InlineIf } + else mkIf0 val cond = condExpr(THEN) newLinesOpt() val thenp = subExpr() @@ -1951,11 +1949,14 @@ object Parsers { mkIf(cond, thenp, elsep) } - /** `match' `{' CaseClauses `}' + /** MatchClause ::= `match' [‘inline’] `{' CaseClauses `}' */ - def matchExpr(t: Tree, start: Offset, mkMatch: (Tree, List[CaseDef]) => Match) = + def matchClause(t: Tree, start: Offset, mkMatch0: (Tree, List[CaseDef]) => Match) = in.endMarkerScope(MATCH) { atSpan(start, in.skipToken()) { + val mkMatch = + if isIdent(nme.inline) then { in.nextToken(); InlineMatch } + else mkMatch0 mkMatch(t, inBracesOrIndented(caseClauses(caseClause))) } } @@ -2032,7 +2033,7 @@ object Parsers { /** PostfixExpr ::= InfixExpr [id [nl]] * InfixExpr ::= PrefixExpr * | InfixExpr id [nl] InfixExpr - * | InfixExpr ‘given’ (InfixExpr | ParArgumentExprs) + * | InfixExpr MatchClause */ def postfixExpr(): Tree = postfixExprRest(prefixExpr()) @@ -2063,6 +2064,7 @@ object Parsers { * | Path * | `(' [ExprsInParens] `)' * | SimpleExpr `.' id + * | SimpleExpr `.' MatchClause * | SimpleExpr (TypeArgs | NamedTypeArgs) * | SimpleExpr1 ArgumentExprs * Quoted ::= ‘'’ ‘{’ Block ‘}’ diff --git a/compiler/src/dotty/tools/dotc/parsing/Scanners.scala b/compiler/src/dotty/tools/dotc/parsing/Scanners.scala index 5238dce9b313..b57a4d636667 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Scanners.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Scanners.scala @@ -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. diff --git a/docs/docs/internals/syntax.md b/docs/docs/internals/syntax.md index 8130731661ad..c345591a1b85 100644 --- a/docs/docs/internals/syntax.md +++ b/docs/docs/internals/syntax.md @@ -184,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 ::= ‘if’ [‘inline’]‘(’ Expr ‘)’ {nl} Expr [[semi] ‘else’ Expr] If(Parens(cond), thenp, elsep?) + | ‘if’ [‘inline’] 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?) @@ -198,7 +197,6 @@ 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 Expr2 ::= PostfixExpr [Ascription] Ascription ::= ‘:’ InfixType Typed(expr, tp) | ‘:’ Annotation {Annotation} Typed(expr, Annotated(EmptyTree, annot)*) @@ -206,7 +204,8 @@ 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’ [‘inline’] ‘{’ CaseClauses ‘}’ Match(expr, cases) PrefixExpr ::= [‘-’ | ‘+’ | ‘~’ | ‘!’] SimpleExpr PrefixOp(expr, op) SimpleExpr ::= Path | Literal @@ -219,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, _) diff --git a/docs/docs/reference/contextual/typeclasses.md b/docs/docs/reference/contextual/typeclasses.md index 11c042a49c8f..01b2fd048a32 100644 --- a/docs/docs/reference/contextual/typeclasses.md +++ b/docs/docs/reference/contextual/typeclasses.md @@ -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(_)) diff --git a/tests/pos-special/fatal-warnings/unchecked-scrutinee.scala b/tests/pos-special/fatal-warnings/unchecked-scrutinee.scala index a51a833f93da..cde3d2ff5f91 100644 --- a/tests/pos-special/fatal-warnings/unchecked-scrutinee.scala +++ b/tests/pos-special/fatal-warnings/unchecked-scrutinee.scala @@ -1,5 +1,5 @@ object Test { - List(1: @unchecked, 2, 3): @unchecked match { + (List(1: @unchecked, 2, 3): @unchecked) match { case a :: as => } } \ No newline at end of file diff --git a/tests/pos/matches.scala b/tests/pos/matches.scala new file mode 100644 index 000000000000..7f42addbe491 --- /dev/null +++ b/tests/pos/matches.scala @@ -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 \ No newline at end of file diff --git a/tests/pos/multi-given.scala b/tests/pos/multi-given.scala index e171e03da2f2..b3d8e59626c1 100644 --- a/tests/pos/multi-given.scala +++ b/tests/pos/multi-given.scala @@ -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 {} \ No newline at end of file diff --git a/tests/run/typeclass-derivation2.scala b/tests/run/typeclass-derivation2.scala index aed502804720..e7758c92f24e 100644 --- a/tests/run/typeclass-derivation2.scala +++ b/tests/run/typeclass-derivation2.scala @@ -293,7 +293,7 @@ object Pickler { } inline def pickleElems[Elems <: Tuple](buf: mutable.ListBuffer[Int], elems: Mirror, n: Int): Unit = - inline erasedValue[Elems] match { + erasedValue[Elems] match inline { case _: (elem *: elems1) => tryPickle[elem](buf, elems(n).asInstanceOf[elem]) pickleElems[elems1](buf, elems, n + 1) @@ -304,9 +304,9 @@ object Pickler { pickleElems[Elems](buf, r.reflect(x), 0) inline def pickleCases[T, Alts <: Tuple](r: Reflected[T], buf: mutable.ListBuffer[Int], x: T, n: Int): Unit = - inline erasedValue[Alts] match { + erasedValue[Alts] match inline { case _: (Shape.Case[alt, elems] *: alts1) => - inline typeOf[alt] match { + typeOf[alt] match inline { case _: Subtype[T] => x match { case x: `alt` => @@ -326,7 +326,7 @@ object Pickler { } inline def unpickleElems[Elems <: Tuple](buf: mutable.ListBuffer[Int], elems: Array[AnyRef], n: Int): Unit = - inline erasedValue[Elems] match { + erasedValue[Elems] match inline { case _: (elem *: elems1) => elems(n) = tryUnpickle[elem](buf).asInstanceOf[AnyRef] unpickleElems[elems1](buf, elems, n + 1) From a5d640e1fed5b98b44fd86f6433e4534ad34b504 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 23 Nov 2019 21:47:46 +0100 Subject: [PATCH 3/7] Drop single case match in test The syntax is no longer supported --- .../interpreter/jvm/Interpreter.scala | 48 +++++++++---------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/tests/run-with-compiler-custom-args/tasty-interpreter/interpreter/jvm/Interpreter.scala b/tests/run-with-compiler-custom-args/tasty-interpreter/interpreter/jvm/Interpreter.scala index 1a6aabd1b09a..42d05ba11211 100644 --- a/tests/run-with-compiler-custom-args/tasty-interpreter/interpreter/jvm/Interpreter.scala +++ b/tests/run-with-compiler-custom-args/tasty-interpreter/interpreter/jvm/Interpreter.scala @@ -17,30 +17,30 @@ class Interpreter[R <: Reflection & Singleton](reflect0: R) extends TreeInterpre // Best effort to try to create a proxy val sym = fn.symbol.owner if (sym.isClassDef) { - sym.tree match case tree: ClassDef => - val parentSymbols = tree.parents.tail.map(_.asInstanceOf[TypeTree].symbol).head - import java.lang.reflect._ - val handler: InvocationHandler = new InvocationHandler() { - - def invoke(proxy: Object, method: Method, args: scala.Array[Object]): Object = { - if (LOG) { - val proxyString = if (method.getName == "toString") method.invoke(this) else proxy.toString - println(s"%> proxy call `$method` on `$proxyString` with args=${if (args == null) Nil else args.toList}") - } - - // println(method) - val symbol = sym.methods.find(_.name == method.getName).get - - if (symbol.isDefinedInCurrentRun) { - val argsList = if (args == null) Nil else args.toList - interpretCall(this, symbol, argsList).asInstanceOf[Object] - } - else { - assert(method.getClass == classOf[Object]) - method.invoke(this, args: _*) - } - } - + sym.tree match + case tree: ClassDef => + val parentSymbols = tree.parents.tail.map(_.asInstanceOf[TypeTree].symbol).head + import java.lang.reflect._ + val handler: InvocationHandler = new InvocationHandler() { + + def invoke(proxy: Object, method: Method, args: scala.Array[Object]): Object = { + if (LOG) { + val proxyString = if (method.getName == "toString") method.invoke(this) else proxy.toString + println(s"%> proxy call `$method` on `$proxyString` with args=${if (args == null) Nil else args.toList}") + } + + // println(method) + val symbol = sym.methods.find(_.name == method.getName).get + + if (symbol.isDefinedInCurrentRun) { + val argsList = if (args == null) Nil else args.toList + interpretCall(this, symbol, argsList).asInstanceOf[Object] + } + else { + assert(method.getClass == classOf[Object]) + method.invoke(this, args: _*) + } + } } val proxyClass: Class[_] = Proxy.getProxyClass(getClass.getClassLoader, jvmReflection.loadClass(parentSymbols.fullName)) proxyClass.getConstructor(classOf[InvocationHandler]).newInstance(handler); From 92a6b2596c6a23b5ff899d08e3f3070cfd60a2a0 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 23 Nov 2019 22:21:34 +0100 Subject: [PATCH 4/7] Fix indentation in test --- .../tasty-interpreter/interpreter/jvm/Interpreter.scala | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tests/run-with-compiler-custom-args/tasty-interpreter/interpreter/jvm/Interpreter.scala b/tests/run-with-compiler-custom-args/tasty-interpreter/interpreter/jvm/Interpreter.scala index 42d05ba11211..84b5e582aff6 100644 --- a/tests/run-with-compiler-custom-args/tasty-interpreter/interpreter/jvm/Interpreter.scala +++ b/tests/run-with-compiler-custom-args/tasty-interpreter/interpreter/jvm/Interpreter.scala @@ -22,7 +22,6 @@ class Interpreter[R <: Reflection & Singleton](reflect0: R) extends TreeInterpre val parentSymbols = tree.parents.tail.map(_.asInstanceOf[TypeTree].symbol).head import java.lang.reflect._ val handler: InvocationHandler = new InvocationHandler() { - def invoke(proxy: Object, method: Method, args: scala.Array[Object]): Object = { if (LOG) { val proxyString = if (method.getName == "toString") method.invoke(this) else proxy.toString @@ -41,9 +40,9 @@ class Interpreter[R <: Reflection & Singleton](reflect0: R) extends TreeInterpre method.invoke(this, args: _*) } } - } - val proxyClass: Class[_] = Proxy.getProxyClass(getClass.getClassLoader, jvmReflection.loadClass(parentSymbols.fullName)) - proxyClass.getConstructor(classOf[InvocationHandler]).newInstance(handler); + } + val proxyClass: Class[_] = Proxy.getProxyClass(getClass.getClassLoader, jvmReflection.loadClass(parentSymbols.fullName)) + proxyClass.getConstructor(classOf[InvocationHandler]).newInstance(handler); } } else jvmReflection.interpretNew(fn.symbol, evaluatedArgss(argss)) From c764daf11031c306c62969b5b7af839a8e6c7e8f Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 23 Nov 2019 22:52:56 +0100 Subject: [PATCH 5/7] Drop single case match in another test --- tests/run-macros/expr-map-2/Macro_1.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/run-macros/expr-map-2/Macro_1.scala b/tests/run-macros/expr-map-2/Macro_1.scala index ede05f1f9f87..2cbb2297746e 100644 --- a/tests/run-macros/expr-map-2/Macro_1.scala +++ b/tests/run-macros/expr-map-2/Macro_1.scala @@ -10,7 +10,8 @@ private object StringRewriter extends util.ExprMap { def transform[T](e: Expr[T])(given QuoteContext, Type[T]): Expr[T] = e match case '{ ($x: Foo).x } => - '{ new Foo(4).x } match case '{ $e: T } => e + '{ new Foo(4).x } match + case '{ $e: T } => e case _ => transformChildren(e) From 1a2cf426220e4ee53070880b79d1d0b81d6a73d1 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 24 Nov 2019 12:07:49 +0100 Subject: [PATCH 6/7] Revert to previous syntax for inline match and inline if Better not to mix up to syntax changes in one PR. Also, we might prefer inline match and inline if in the end. --- .../dotty/tools/dotc/parsing/Parsers.scala | 45 ++++++++----------- docs/docs/internals/syntax.md | 10 ++--- tests/neg/cannot-reduce-inline-match.check | 12 ++--- tests/run/typeclass-derivation2.scala | 8 ++-- 4 files changed, 34 insertions(+), 41 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index c9823678d760..8ee15f489034 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -983,7 +983,7 @@ object Parsers { } else val t = reduceStack(base, top, minPrec, leftAssoc = true, in.name, isType) - if !isType && in.token == MATCH then recur(matchClause(t, t.span.start, Match)) + if !isType && in.token == MATCH then recur(matchClause(t)) else t recur(first) @@ -1027,8 +1027,7 @@ object Parsers { /** Accept identifier or match clause acting as a selector on given tree `t` */ def selector(t: Tree): Tree = atSpan(startOffset(t), in.offset) { - if in.token == MATCH then matchClause(t, t.span.start, Match) - else Select(t, ident()) + if in.token == MATCH then matchClause(t) else Select(t, ident()) } /** Selectors ::= id { `.' id } @@ -1736,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} * | `:' `_' `*' @@ -1779,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 => @@ -1876,7 +1874,7 @@ object Parsers { } } case _ => - if isIdent(nme.inline) // TODO: drop this clause + if isIdent(nme.inline) && !in.inModifierPosition() && in.lookaheadIn(in.canStartExprTokens) then @@ -1885,13 +1883,14 @@ object Parsers { case IF => ifExpr(start, InlineIf) case _ => - val t = prefixExpr() - if (in.token == MATCH) matchClause(t, start, InlineMatch) - else - syntaxErrorOrIncomplete(i"`match` or `if` expected but ${in} 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 case EQUALS => @@ -1933,14 +1932,11 @@ object Parsers { } } - /** `if' [‘inline’] `(' Expr `)' {nl} Expr [[semi] else Expr] - * `if' [‘inline’] Expr `then' Expr [[semi] else Expr] + /** `if' `(' Expr `)' {nl} Expr [[semi] else Expr] + * `if' Expr `then' Expr [[semi] else Expr] */ - def ifExpr(start: Offset, mkIf0: (Tree, Tree, Tree) => If): If = + def ifExpr(start: Offset, mkIf: (Tree, Tree, Tree) => If): If = atSpan(start, in.skipToken()) { - val mkIf = - if isIdent(nme.inline) then { in.nextToken(); InlineIf } - else mkIf0 val cond = condExpr(THEN) newLinesOpt() val thenp = subExpr() @@ -1951,13 +1947,10 @@ object Parsers { /** MatchClause ::= `match' [‘inline’] `{' CaseClauses `}' */ - def matchClause(t: Tree, start: Offset, mkMatch0: (Tree, List[CaseDef]) => Match) = + def matchClause(t: Tree): Match = in.endMarkerScope(MATCH) { - atSpan(start, in.skipToken()) { - val mkMatch = - if isIdent(nme.inline) then { in.nextToken(); InlineMatch } - else mkMatch0 - mkMatch(t, inBracesOrIndented(caseClauses(caseClause))) + atSpan(t.span.start, in.skipToken()) { + Match(t, inBracesOrIndented(caseClauses(caseClause))) } } diff --git a/docs/docs/internals/syntax.md b/docs/docs/internals/syntax.md index c345591a1b85..c4784d5d4e4e 100644 --- a/docs/docs/internals/syntax.md +++ b/docs/docs/internals/syntax.md @@ -184,8 +184,8 @@ BlockResult ::= [‘implicit’] FunParams ‘=>’ Block FunParams ::= Bindings | id | ‘_’ -Expr1 ::= ‘if’ [‘inline’]‘(’ Expr ‘)’ {nl} Expr [[semi] ‘else’ Expr] If(Parens(cond), thenp, elsep?) - | ‘if’ [‘inline’] 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?) @@ -196,8 +196,8 @@ Expr1 ::= ‘if’ [‘inline’]‘(’ Expr ‘)’ {nl} Expr [[s | HkTypeParamClause ‘=>’ Expr PolyFunction(ts, expr) | [SimpleExpr ‘.’] id ‘=’ Expr Assign(expr, expr) | SimpleExpr1 ArgumentExprs ‘=’ Expr Assign(expr, expr) - | Expr2 -Expr2 ::= PostfixExpr [Ascription] + | PostfixExpr [Ascription] + | ‘inline’ InfixExpr MatchClause Ascription ::= ‘:’ InfixType Typed(expr, tp) | ‘:’ Annotation {Annotation} Typed(expr, Annotated(EmptyTree, annot)*) Catches ::= ‘catch’ (Expr | CaseClause) @@ -205,7 +205,7 @@ PostfixExpr ::= InfixExpr [id] InfixExpr ::= PrefixExpr | InfixExpr id [nl] InfixExpr InfixOp(expr, op, expr) | InfixExpr MatchClause -MatchClause ::= ‘match’ [‘inline’] ‘{’ CaseClauses ‘}’ Match(expr, cases) +MatchClause ::= ‘match’ ‘{’ CaseClauses ‘}’ Match(expr, cases) PrefixExpr ::= [‘-’ | ‘+’ | ‘~’ | ‘!’] SimpleExpr PrefixOp(expr, op) SimpleExpr ::= Path | Literal diff --git a/tests/neg/cannot-reduce-inline-match.check b/tests/neg/cannot-reduce-inline-match.check index 8f063f0003d2..adeab2fc97ee 100644 --- a/tests/neg/cannot-reduce-inline-match.check +++ b/tests/neg/cannot-reduce-inline-match.check @@ -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 | } diff --git a/tests/run/typeclass-derivation2.scala b/tests/run/typeclass-derivation2.scala index e7758c92f24e..aed502804720 100644 --- a/tests/run/typeclass-derivation2.scala +++ b/tests/run/typeclass-derivation2.scala @@ -293,7 +293,7 @@ object Pickler { } inline def pickleElems[Elems <: Tuple](buf: mutable.ListBuffer[Int], elems: Mirror, n: Int): Unit = - erasedValue[Elems] match inline { + inline erasedValue[Elems] match { case _: (elem *: elems1) => tryPickle[elem](buf, elems(n).asInstanceOf[elem]) pickleElems[elems1](buf, elems, n + 1) @@ -304,9 +304,9 @@ object Pickler { pickleElems[Elems](buf, r.reflect(x), 0) inline def pickleCases[T, Alts <: Tuple](r: Reflected[T], buf: mutable.ListBuffer[Int], x: T, n: Int): Unit = - erasedValue[Alts] match inline { + inline erasedValue[Alts] match { case _: (Shape.Case[alt, elems] *: alts1) => - typeOf[alt] match inline { + inline typeOf[alt] match { case _: Subtype[T] => x match { case x: `alt` => @@ -326,7 +326,7 @@ object Pickler { } inline def unpickleElems[Elems <: Tuple](buf: mutable.ListBuffer[Int], elems: Array[AnyRef], n: Int): Unit = - erasedValue[Elems] match inline { + inline erasedValue[Elems] match { case _: (elem *: elems1) => elems(n) = tryUnpickle[elem](buf).asInstanceOf[AnyRef] unpickleElems[elems1](buf, elems, n + 1) From 09e5aec2cac4ca7048fb5c5ecad54cc8a4140ecc Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 24 Nov 2019 12:29:16 +0100 Subject: [PATCH 7/7] Add reference doc page --- .../dotty/tools/dotc/parsing/Parsers.scala | 2 +- .../changed-features/match-syntax.md | 43 +++++++++++++++++++ docs/sidebar.yml | 2 + 3 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 docs/docs/reference/changed-features/match-syntax.md diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 8ee15f489034..62d9a086eb63 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -1945,7 +1945,7 @@ object Parsers { mkIf(cond, thenp, elsep) } - /** MatchClause ::= `match' [‘inline’] `{' CaseClauses `}' + /** MatchClause ::= `match' `{' CaseClauses `}' */ def matchClause(t: Tree): Match = in.endMarkerScope(MATCH) { diff --git a/docs/docs/reference/changed-features/match-syntax.md b/docs/docs/reference/changed-features/match-syntax.md new file mode 100644 index 000000000000..6841d922dbd1 --- /dev/null +++ b/docs/docs/reference/changed-features/match-syntax.md @@ -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 { + case Nil => false + case _ => true + } + then "nonempty" + else "empty" + + 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 ‘}’ +``` diff --git a/docs/sidebar.yml b/docs/sidebar.yml index a41c87e4f420..d7a909e9dd31 100644 --- a/docs/sidebar.yml +++ b/docs/sidebar.yml @@ -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