diff --git a/compiler/src/dotty/tools/dotc/config/Feature.scala b/compiler/src/dotty/tools/dotc/config/Feature.scala index 70a77c9560b2..6bad5d6b7c77 100644 --- a/compiler/src/dotty/tools/dotc/config/Feature.scala +++ b/compiler/src/dotty/tools/dotc/config/Feature.scala @@ -38,6 +38,7 @@ object Feature: val quotedPatternsWithPolymorphicFunctions = experimental("quotedPatternsWithPolymorphicFunctions") val packageObjectValues = experimental("packageObjectValues") val subCases = experimental("subCases") + val relaxedLambdaSyntax = experimental("relaxedLambdaSyntax") def experimentalAutoEnableFeatures(using Context): List[TermName] = defn.languageExperimentalFeatures diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 16afbe9ad433..004aeb12b0a2 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -1085,19 +1085,40 @@ object Parsers { } /** Is the token sequence following the current `:` token classified as a lambda? - * This is the case if the input starts with an identifier, a wildcard, or - * something enclosed in (...) or [...], and this is followed by a `=>` or `?=>` - * and an INDENT. - */ - def followingIsLambdaAfterColon(): Boolean = + * If yes return a defined parsing function to parse the lambda body, if not + * return None. The case is triggered in two situations: + * 1. If the input starts with an identifier, a wildcard, or something + * enclosed in (...) or [...], this is followed by a `=>` or `?=>`, + * and one of the following two subcases applies: + * 1a. The next token is an indent. In this case the return parsing function parses + * an Expr in location Location.InColonArg. + * 1b. Under relaxedLambdaSyntax: the next token is on the same line and the enclosing region is not `(...)`. + * In this case the parsing function parses an Expr in location Location.InColonArg + * enclosed in a SingleLineLambda region, and then eats the ENDlambda token + * generated by the Scanner at the end of that region. + * The reason for excluding (1b) in regions enclosed in parentheses is to avoid + * an ambiguity with type ascription `(x: A => B)`, where function types are only + * allowed inside parentheses. + * 2. Under relaxedLambdaSyntax: the input starts with a `case`. + */ + def followingIsLambdaAfterColon(): Option[() => Tree] = val lookahead = in.LookaheadScanner(allowIndent = true) .tap(_.currentRegion.knownWidth = in.currentRegion.indentWidth) - def isArrowIndent() = - lookahead.isArrow - && { + def isArrowIndent(): Option[() => Tree] = + if lookahead.isArrow then lookahead.observeArrowIndented() - lookahead.token == INDENT || lookahead.token == EOF - } + if lookahead.token == INDENT || lookahead.token == EOF then + Some(() => expr(Location.InColonArg)) + else if in.featureEnabled(Feature.relaxedLambdaSyntax) + && !in.currentRegion.isInstanceOf[InParens] + then + Some: () => + val t = inSepRegion(SingleLineLambda(_)): + expr(Location.InColonArg) + accept(ENDlambda) + t + else None + else None lookahead.nextToken() if lookahead.isIdent || lookahead.token == USCORE then lookahead.nextToken() @@ -1105,7 +1126,10 @@ object Parsers { else if lookahead.token == LPAREN || lookahead.token == LBRACKET then lookahead.skipParens() isArrowIndent() - else false + else if lookahead.token == CASE && in.featureEnabled(Feature.relaxedLambdaSyntax) then + Some(() => singleCaseMatch()) + else + None /** Can the next lookahead token start an operand as defined by * leadingOperandTokens, or is postfix ops enabled? @@ -1170,12 +1194,19 @@ object Parsers { case _ => infixOp } - /** True if we are seeing a lambda argument after a colon of the form: + /** Optionally, if we are seeing a lambda argument after a colon of the form * : (params) => * body + * or a single-line lambda (under relaxedLambdaSyntax) + * : (params) => body + * or a case clause (under relaxedLambdaSyntax) + * : case pat guard => rhs + * then return the function used to parse `body` or the case clause. */ - def isColonLambda = - sourceVersion.enablesFewerBraces && in.token == COLONfollow && followingIsLambdaAfterColon() + def detectColonLambda: Option[() => Tree] = + if sourceVersion.enablesFewerBraces && in.token == COLONfollow + then followingIsLambdaAfterColon() + else None /** operand { infixop operand | MatchClause } [postfixop], * @@ -1199,17 +1230,19 @@ object Parsers { opStack = OpInfo(top1, op, in.offset) :: opStack colonAtEOLOpt() newLineOptWhenFollowing(canStartOperand) - if isColonLambda then - in.nextToken() - recur(expr(Location.InColonArg)) - else if maybePostfix && !canStartOperand(in.token) then - val topInfo = opStack.head - opStack = opStack.tail - val od = reduceStack(base, topInfo.operand, 0, true, in.name, isType) - atSpan(startOffset(od), topInfo.offset) { - PostfixOp(od, topInfo.operator) - } - else recur(operand(location)) + detectColonLambda match + case Some(parseExpr) => + in.nextToken() + recur(parseExpr()) + case _ => + if maybePostfix && !canStartOperand(in.token) then + val topInfo = opStack.head + opStack = opStack.tail + val od = reduceStack(base, topInfo.operand, 0, true, in.name, isType) + atSpan(startOffset(od), topInfo.offset) { + PostfixOp(od, topInfo.operator) + } + else recur(operand(location)) else val t = reduceStack(base, top, minPrec, leftAssoc = true, in.name, isType) if !isType && in.token == MATCH then recurAtMinPrec(matchClause(t)) @@ -2353,6 +2386,7 @@ object Parsers { /** Expr ::= [`implicit'] FunParams (‘=>’ | ‘?=>’) Expr * | TypTypeParamClause ‘=>’ Expr + * | ExprCaseClause -- under experimental.relaxedLambdaSyntax * | Expr1 * FunParams ::= Bindings * | id @@ -2404,6 +2438,8 @@ object Parsers { val arrowOffset = accept(ARROW) val body = expr(location) makePolyFunction(tparams, body, "literal", errorTermTree(arrowOffset), start, arrowOffset) + case CASE if in.featureEnabled(Feature.relaxedLambdaSyntax) => + singleCaseMatch() case _ => val saved = placeholderParams placeholderParams = Nil @@ -2467,9 +2503,8 @@ object Parsers { if in.token == CATCH then val span = in.offset in.nextToken() - (if in.token == CASE then Match(EmptyTree, caseClause(exprOnly = true) :: Nil) - else subExpr(), - span) + (if in.token == CASE then singleCaseMatch() else subExpr(), + span) else (EmptyTree, -1) handler match { @@ -2766,6 +2801,8 @@ object Parsers { * | SimpleExpr1 ColonArgument * ColonArgument ::= colon [LambdaStart] * indent (CaseClauses | Block) outdent + * | colon LambdaStart expr ENDlambda -- under experimental.relaxedLambdaSyntax + * | colon ExprCaseClause -- under experimental.relaxedLambdaSyntax * LambdaStart ::= FunParams (‘=>’ | ‘?=>’) * | TypTypeParamClause ‘=>’ * ColonArgBody ::= indent (CaseClauses | Block) outdent @@ -2848,12 +2885,14 @@ object Parsers { makeParameter(name.asTermName, typedOpt(), Modifiers(), isBackquoted = isBackquoted(id)) } case _ => t - else if isColonLambda then - val app = atSpan(startOffset(t), in.skipToken()) { - Apply(t, expr(Location.InColonArg) :: Nil) - } - simpleExprRest(app, location, canApply = true) - else t + else detectColonLambda match + case Some(parseExpr) => + val app = + atSpan(startOffset(t), in.skipToken()): + Apply(t, parseExpr() :: Nil) + simpleExprRest(app, location, canApply = true) + case None => + t end simpleExprRest /** SimpleExpr ::= ‘new’ ConstrApp {`with` ConstrApp} [TemplateBody] @@ -3160,9 +3199,9 @@ object Parsers { case ARROW => atSpan(in.skipToken()): if exprOnly then if in.indentSyntax && in.isAfterLineEnd && in.token != INDENT then - warning(em"""Misleading indentation: this expression forms part of the preceding catch case. + warning(em"""Misleading indentation: this expression forms part of the preceding case. |If this is intended, it should be indented for clarity. - |Otherwise, if the handler is intended to be empty, use a multi-line catch with + |Otherwise, if the handler is intended to be empty, use a multi-line match or catch with |an indented case.""") expr() else block() @@ -3178,6 +3217,9 @@ object Parsers { CaseDef(pat, grd1, body) } + def singleCaseMatch() = + Match(EmptyTree, caseClause(exprOnly = true) :: Nil) + /** TypeCaseClause ::= ‘case’ (InfixType | ‘_’) ‘=>’ Type [semi] */ def typeCaseClause(): CaseDef = atSpan(in.offset) { diff --git a/compiler/src/dotty/tools/dotc/parsing/Scanners.scala b/compiler/src/dotty/tools/dotc/parsing/Scanners.scala index 52e03de60dea..ec246f7a3742 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Scanners.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Scanners.scala @@ -617,7 +617,9 @@ object Scanners { && !statCtdTokens.contains(lastToken) && !isTrailingBlankLine - if newlineIsSeparating + if currentRegion.closedBy == ENDlambda then + insert(ENDlambda, lineOffset) + else if newlineIsSeparating && canEndStatTokens.contains(lastToken) && canStartStatTokens.contains(token) && !isLeadingInfixOperator(nextWidth) @@ -1599,6 +1601,8 @@ object Scanners { * InParens a pair of parentheses (...) or brackets [...] * InBraces a pair of braces { ... } * Indented a pair of ... tokens + * InCase a case of a match + * SingleLineLambda the rest of a line following a `:` */ abstract class Region(val closedBy: Token): @@ -1667,6 +1671,7 @@ object Scanners { case _: InBraces => "}" case _: InCase => "=>" case _: Indented => "UNDENT" + case _: SingleLineLambda => "end of single-line lambda" /** Show open regions as list of lines with decreasing indentations */ def visualize: String = @@ -1680,6 +1685,7 @@ object Scanners { case class InParens(prefix: Token, outer: Region) extends Region(prefix + 1) case class InBraces(outer: Region) extends Region(RBRACE) case class InCase(outer: Region) extends Region(OUTDENT) + case class SingleLineLambda(outer: Region) extends Region(ENDlambda) /** A class describing an indentation region. * @param width The principal indentation width diff --git a/compiler/src/dotty/tools/dotc/parsing/Tokens.scala b/compiler/src/dotty/tools/dotc/parsing/Tokens.scala index d47e6dab005f..b3728ac0f547 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Tokens.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Tokens.scala @@ -203,8 +203,10 @@ object Tokens extends TokensCommon { // A `:` recognized as starting an indentation block inline val SELFARROW = 90; enter(SELFARROW, "=>") // reclassified ARROW following self-type + inline val ENDlambda = 99; enter(ENDlambda, "end of single-line lambda") + /** XML mode */ - inline val XMLSTART = 99; enter(XMLSTART, "$XMLSTART$<") // TODO: deprecate + inline val XMLSTART = 100; enter(XMLSTART, "$XMLSTART$<") // TODO: deprecate final val alphaKeywords: TokenSet = tokenRange(IF, END) final val symbolicKeywords: TokenSet = tokenRange(USCORE, CTXARROW) @@ -267,7 +269,7 @@ object Tokens extends TokensCommon { final val canStartStatTokens3: TokenSet = canStartExprTokens3 | mustStartStatTokens | BitSet( AT, CASE, END) - final val canEndStatTokens: TokenSet = atomicExprTokens | BitSet(TYPE, GIVEN, RPAREN, RBRACE, RBRACKET, OUTDENT) + final val canEndStatTokens: TokenSet = atomicExprTokens | BitSet(TYPE, GIVEN, RPAREN, RBRACE, RBRACKET, OUTDENT, ENDlambda) /** Tokens that stop a lookahead scan search for a `<-`, `then`, or `do`. * Used for disambiguating between old and new syntax. diff --git a/docs/_docs/internals/syntax.md b/docs/_docs/internals/syntax.md index 7fd7ec1be2e1..89b180274e55 100644 --- a/docs/_docs/internals/syntax.md +++ b/docs/_docs/internals/syntax.md @@ -243,6 +243,7 @@ CapFilter ::= ‘.’ ‘as’ ‘[’ QualId ’]’ ```ebnf Expr ::= FunParams (‘=>’ | ‘?=>’) Expr Function(args, expr), Function(ValDef([implicit], id, TypeTree(), EmptyTree), expr) | TypTypeParamClause ‘=>’ Expr PolyFunction(ts, expr) + | ExprCaseClause | Expr1 BlockResult ::= FunParams (‘=>’ | ‘?=>’) Block | TypTypeParamClause ‘=>’ Block @@ -295,6 +296,9 @@ SimpleExpr ::= SimpleRef | XmlExpr -- to be dropped ColonArgument ::= colon [LambdaStart] indent (CaseClauses | Block) outdent + | colon LambdaStart expr ENDlambda -- ENDlambda is inserted for each production at next EOL + -- does not apply if enclosed in parens + | colon ExprCaseClause LambdaStart ::= FunParams (‘=>’ | ‘?=>’) | TypTypeParamClause ‘=>’ Quoted ::= ‘'’ ‘{’ Block ‘}’ diff --git a/library/src/scala/language.scala b/library/src/scala/language.scala index bacbb09ad615..685d5aed8d67 100644 --- a/library/src/scala/language.scala +++ b/library/src/scala/language.scala @@ -355,6 +355,10 @@ object language { @compileTimeOnly("`subCases` can only be used at compile time in import statements") object subCases + /** Experimental support for single-line lambdas and case clause expressions after `:` + */ + @compileTimeOnly("`relaxedLambdaSyntax` can only be used at compile time in import statements") + object relaxedLambdaSyntax } /** The deprecated object contains features that are no longer officially suypported in Scala. diff --git a/library/src/scala/runtime/stdLibPatches/language.scala b/library/src/scala/runtime/stdLibPatches/language.scala index 9d38ea4371ff..87677cb72c03 100644 --- a/library/src/scala/runtime/stdLibPatches/language.scala +++ b/library/src/scala/runtime/stdLibPatches/language.scala @@ -161,6 +161,11 @@ object language: */ @compileTimeOnly("`subCases` can only be used at compile time in import statements") object subCases + + /** Experimental support for single-line lambdas and case clause expressions after `:` + */ + @compileTimeOnly("`relaxedLambdaSyntax` can only be used at compile time in import statements") + object relaxedLambdaSyntax end experimental /** The deprecated object contains features that are no longer officially suypported in Scala. diff --git a/tests/neg/closure-args.check b/tests/neg/closure-args.check new file mode 100644 index 000000000000..e4590e9147c1 --- /dev/null +++ b/tests/neg/closure-args.check @@ -0,0 +1,59 @@ +-- [E040] Syntax Error: tests/neg/closure-args.scala:2:25 -------------------------------------------------------------- +2 |val x = List(1).map: (x: => Int) => // error + | ^^ + | an identifier expected, but '=>' found + | + | longer explanation available when compiling with `-explain` +-- Error: tests/neg/closure-args.scala:14:0 ---------------------------------------------------------------------------- +14 | y => y > 0 // error // error + |^ + |indented definitions expected, end of single-line lambda found +-- [E103] Syntax Error: tests/neg/closure-args.scala:14:4 -------------------------------------------------------------- +14 | y => y > 0 // error // error + | ^ + | Illegal start of toplevel definition + | + | longer explanation available when compiling with `-explain` +-- [E018] Syntax Error: tests/neg/closure-args.scala:18:20 ------------------------------------------------------------- +18 |val e = xs.map: y => // error + | ^ + | expression expected but end of single-line lambda found + | + | longer explanation available when compiling with `-explain` +-- [E040] Syntax Error: tests/neg/closure-args.scala:21:64 ------------------------------------------------------------- +21 |val fs: List[List[Int] => Int] = xs.map: x => case y :: ys => y case Nil => -1 // error + | ^^^^ + | end of single-line lambda expected, but 'case' found +-- [E008] Not Found Error: tests/neg/closure-args.scala:10:4 ----------------------------------------------------------- + 8 |val b: Int = xs + 9 | .map: x => x +10 | * x // error + | ^ + | value * is not a member of List[Int]. + | Note that `*` is treated as an infix operator in Scala 3. + | If you do not want that, insert a `;` or empty line in front + | or drop any spaces behind the operator. +-- [E006] Not Found Error: tests/neg/closure-args.scala:16:21 ---------------------------------------------------------- +16 |val c = List(xs.map: y => y + y) // error // error // error // error + | ^ + | Not found: type y + | + | longer explanation available when compiling with `-explain` +-- [E006] Not Found Error: tests/neg/closure-args.scala:16:28 ---------------------------------------------------------- +16 |val c = List(xs.map: y => y + y) // error // error // error // error + | ^ + | Not found: type + + | + | longer explanation available when compiling with `-explain` +-- [E006] Not Found Error: tests/neg/closure-args.scala:16:26 ---------------------------------------------------------- +16 |val c = List(xs.map: y => y + y) // error // error // error // error + | ^ + | Not found: type y + | + | longer explanation available when compiling with `-explain` +-- [E006] Not Found Error: tests/neg/closure-args.scala:16:30 ---------------------------------------------------------- +16 |val c = List(xs.map: y => y + y) // error // error // error // error + | ^ + | Not found: type y + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg/closure-args.scala b/tests/neg/closure-args.scala index 76e590ad28b9..2544b1be3a66 100644 --- a/tests/neg/closure-args.scala +++ b/tests/neg/closure-args.scala @@ -1,23 +1,21 @@ -import language.`3.3` - +import language.experimental.relaxedLambdaSyntax val x = List(1).map: (x: => Int) => // error ??? val z = List(1).map: + => // ok ??? val xs = List(1) -val b: Int = xs // error - .map: x => x * x // error - .filter: y => y > 0 // error - (0) -val d = xs // error +val b: Int = xs + .map: x => x + * x // error + +val d = xs .map: x => x.toString + xs.dropWhile: - y => y > 0 + y => y > 0 // error // error val c = List(xs.map: y => y + y) // error // error // error // error -val d2: String = xs // error - .map: x => x.toString + xs.dropWhile: y => y > 0 // error // error - .filter: z => !z.isEmpty // error - (0) -val fs: List[List[Int] => Int] = xs.map: x => case y :: ys => y case Nil => -1 // error // error +val e = xs.map: y => // error +y + 1 + +val fs: List[List[Int] => Int] = xs.map: x => case y :: ys => y case Nil => -1 // error diff --git a/tests/neg/i22193.check b/tests/neg/i22193.check new file mode 100644 index 000000000000..5a51a272c217 --- /dev/null +++ b/tests/neg/i22193.check @@ -0,0 +1,34 @@ +-- [E018] Syntax Error: tests/neg/i22193.scala:15:68 ------------------------------------------------------------------- +15 | arg2 = "the quick brown fox jumped over the lazy dog"): env => // error + | ^ + | expression expected but end of single-line lambda found + | + | longer explanation available when compiling with `-explain` +-- Error: tests/neg/i22193.scala:22:2 ---------------------------------------------------------------------------------- +22 | env => // error indented definitions expected, identifier env found + | ^^^ + | indented definitions expected, identifier env found +-- Error: tests/neg/i22193.scala:31:2 ---------------------------------------------------------------------------------- +31 | val x = "Hello" // error + | ^^^ + | indented definitions expected, val found +-- [E006] Not Found Error: tests/neg/i22193.scala:16:10 ---------------------------------------------------------------- +16 | val x = env // error + | ^^^ + | Not found: env + | + | longer explanation available when compiling with `-explain` +-- [E178] Type Error: tests/neg/i22193.scala:28:2 ---------------------------------------------------------------------- +28 | fn3( // error missing argument list for value of type (=> Unit) => Unit + | ^ + | missing argument list for value of type (=> Unit) => Unit +29 | arg = "blue sleeps faster than tuesday", +30 | arg2 = "the quick brown fox jumped over the lazy dog"): + | + | longer explanation available when compiling with `-explain` +-- [E006] Not Found Error: tests/neg/i22193.scala:32:10 ---------------------------------------------------------------- +32 | println(x) // error + | ^ + | Not found: x + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg/i22193.scala b/tests/neg/i22193.scala index f7ee5b1cf5e1..638e8e7e4b03 100644 --- a/tests/neg/i22193.scala +++ b/tests/neg/i22193.scala @@ -1,4 +1,4 @@ - +import language.experimental.relaxedLambdaSyntax def fn2(arg: String, arg2: String)(f: String => Unit): Unit = f(arg) def fn3(arg: String, arg2: String)(f: => Unit): Unit = f @@ -10,9 +10,9 @@ def test1() = val x = env println(x) - fn2( // error not a legal formal parameter for a function literal + fn2( arg = "blue sleeps faster than tuesday", - arg2 = "the quick brown fox jumped over the lazy dog"): env => + arg2 = "the quick brown fox jumped over the lazy dog"): env => // error val x = env // error println(x) diff --git a/tests/neg/i22906.check b/tests/neg/i22906.check index 118f9f4fa069..c2ff40929a39 100644 --- a/tests/neg/i22906.check +++ b/tests/neg/i22906.check @@ -1,6 +1,11 @@ Flag -indent set repeatedly --- Error: tests/neg/i22906.scala:6:15 ---------------------------------------------------------------------------------- -6 | {`1`: Int => 5} // error - | ^ - | parentheses are required around the parameter of a lambda - | This construct can be rewritten automatically under -rewrite -source 3.0-migration. +-- [E040] Syntax Error: tests/neg/i22906.scala:6:20 -------------------------------------------------------------------- +6 | {`1`: Int => 5} // error // error + | ^ + | end of single-line lambda expected, but '}' found +-- [E006] Not Found Error: tests/neg/i22906.scala:6:5 ------------------------------------------------------------------ +6 | {`1`: Int => 5} // error // error + | ^^^ + | Not found: 1 + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg/i22906.scala b/tests/neg/i22906.scala index ca464e99bd48..065da159e2a8 100644 --- a/tests/neg/i22906.scala +++ b/tests/neg/i22906.scala @@ -1,6 +1,6 @@ //> using options -rewrite -indent //> nominally using scala 3.7.0-RC1 // does not reproduce under "vulpix" test rig, which enforces certain flag sets? - +import language.experimental.relaxedLambdaSyntax def program: Int => Int = - {`1`: Int => 5} // error + {`1`: Int => 5} // error // error diff --git a/tests/pos/change-lambda.scala b/tests/pos/change-lambda.scala new file mode 100644 index 000000000000..b5abf24dfd4c --- /dev/null +++ b/tests/pos/change-lambda.scala @@ -0,0 +1,9 @@ +import language.experimental.relaxedLambdaSyntax + +def foo(x: Any) = ??? + +def test(xs: List[Int]) = + xs.map: x => x + foo: + xs.map: x => x + 1 + diff --git a/tests/pos/closure-args.scala b/tests/pos/closure-args.scala index 9d7778e2e5e0..6ff1d6c40619 100644 --- a/tests/pos/closure-args.scala +++ b/tests/pos/closure-args.scala @@ -1,36 +1,21 @@ -import language.`3.3` +import language.experimental.relaxedLambdaSyntax -object Test1: - val xs = List(1, 2, 3) - val ys = xs.map: x => - x + 1 - val ys1 = List(1) map: x => - x + 1 - val x = ys.foldLeft(0): (x, y) => - x + y - val y = ys.foldLeft(0): (x: Int, y: Int) => - val z = x + y - z * z - val a: Int = xs - .map: x => - x * x - .filter: (y: Int) => - y > 0 - (0) - val e = xs.map: - case 1 => 2 - case 2 => 3 - case x => x - .filter: - x => x > 0 +val z = List(1).map: + => // ok + ??? - extension (xs: List[Int]) def foo(f: [X] => X => X) = () +val xs = List(1) +val b: Int = xs + .map: x => x * x + .filter: y => y > 0 + (0) - val p = xs.foo: - [X] => (x: X) => x +val d2: String = xs + .map: x => x.toString + xs.dropWhile: y => y > 0 + .filter: z => !z.isEmpty + (0) - val q = (x: String => String) => x - - val r = x < 0 && locally: - y > 0 +val d3: String = xs + .map: x => x.toString + xs.collect: case y if y > 0 => y + .filter: z => !z.isEmpty + (0) diff --git a/tests/run/single-case-expr.scala b/tests/run/single-case-expr.scala new file mode 100644 index 000000000000..efc963b68e01 --- /dev/null +++ b/tests/run/single-case-expr.scala @@ -0,0 +1,32 @@ +import language.experimental.relaxedLambdaSyntax + +case class Foo(x: Int, y: Int) +@main def Test = + val f: List[Int] => Int = case y :: ys => y + val xs = List(1, 2, 3) + assert(f(xs) == 1) + + val g: Foo => Int = identity(case Foo(a, b) => a) + val foo = Foo(1, 2) + assert(g(foo) == 1) + + val h1: Foo => Int = identity: case Foo(a, b) => a + val h2: Foo => Int = identity: case Foo(a, b) => + a + + val a1 = Seq((1, 2), (3, 4)).collect(case (a, b) if b > 2 => a) + assert(a1 == Seq(3)) + + var a2 = Seq((1, 2), (3, 4)).collect( + case (a, b) => + println(b) + a + ) + assert(a2 == Seq(1, 3)) + + val a3 = Seq((1, 2), (3, 4)).collect: case (a, b) if b > 2 => a + assert(a3 == Seq(3)) + + val partial: PartialFunction[(Int, Int), Int] = case (a, b) if b > 2 => a + +