diff --git a/compiler/src/dotty/tools/dotc/config/Feature.scala b/compiler/src/dotty/tools/dotc/config/Feature.scala index 6eaa4d5c98a3..5aa7ed84f72d 100644 --- a/compiler/src/dotty/tools/dotc/config/Feature.scala +++ b/compiler/src/dotty/tools/dotc/config/Feature.scala @@ -40,6 +40,7 @@ object Feature: val packageObjectValues = experimental("packageObjectValues") val multiSpreads = experimental("multiSpreads") 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 64eb442c239a..147a650370ac 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -1090,27 +1090,56 @@ 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 - } - lookahead.nextToken() - if lookahead.isIdent || lookahead.token == USCORE then + if lookahead.token == INDENT || lookahead.token == EOF then + Some(() => expr(Location.InColonArg)) + else if in.featureEnabled(Feature.relaxedLambdaSyntax) then + isParamsAndArrow() match + case success @ Some(_) => success + case _ if !in.currentRegion.isInstanceOf[InParens] => + Some: () => + val t = inSepRegion(SingleLineLambda(_)): + expr(Location.InColonArg) + accept(ENDlambda) + t + case _ => None + else None + else None + def isParamsAndArrow(): Option[() => Tree] = lookahead.nextToken() - isArrowIndent() - else if lookahead.token == LPAREN || lookahead.token == LBRACKET then - lookahead.skipParens() - isArrowIndent() - else false + if lookahead.isIdent || lookahead.token == USCORE then + lookahead.nextToken() + isArrowIndent() + else if lookahead.token == LPAREN || lookahead.token == LBRACKET then + lookahead.skipParens() + isArrowIndent() + else if lookahead.token == CASE && in.featureEnabled(Feature.relaxedLambdaSyntax) then + Some(() => singleCaseMatch()) + else + None + isParamsAndArrow() + end followingIsLambdaAfterColon /** Can the next lookahead token start an operand as defined by * leadingOperandTokens, or is postfix ops enabled? @@ -1175,12 +1204,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], * @@ -1204,17 +1240,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)) @@ -2358,6 +2396,7 @@ object Parsers { /** Expr ::= [`implicit'] FunParams (‘=>’ | ‘?=>’) Expr * | TypTypeParamClause ‘=>’ Expr + * | ExprCaseClause -- under experimental.relaxedLambdaSyntax * | Expr1 * FunParams ::= Bindings * | id @@ -2409,6 +2448,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 @@ -2472,9 +2513,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 { @@ -2769,8 +2809,10 @@ object Parsers { * | SimpleExpr (TypeArgs | NamedTypeArgs) * | SimpleExpr1 ArgumentExprs * | SimpleExpr1 ColonArgument - * ColonArgument ::= colon [LambdaStart] + * ColonArgument ::= colon {LambdaStart} * indent (CaseClauses | Block) outdent + * | colon LambdaStart {LambdaStart} expr ENDlambda -- under experimental.relaxedLambdaSyntax + * | colon ExprCaseClause -- under experimental.relaxedLambdaSyntax * LambdaStart ::= FunParams (‘=>’ | ‘?=>’) * | TypTypeParamClause ‘=>’ * ColonArgBody ::= indent (CaseClauses | Block) outdent @@ -2853,12 +2895,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] @@ -3165,9 +3209,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() @@ -3184,6 +3228,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 686d0551e0f6..d1a5be895022 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 @@ -293,8 +294,11 @@ SimpleExpr ::= SimpleRef | SimpleExpr ColonArgument -- under language.experimental.fewerBraces | SimpleExpr ‘_’ PostfixOp(expr, _) (to be dropped) | XmlExpr -- to be dropped -ColonArgument ::= colon [LambdaStart] +ColonArgument ::= colon {LambdaStart} indent (CaseClauses | Block) outdent + | colon LambdaStart {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/docs/_docs/reference/experimental/relaxed-lambdas.md b/docs/_docs/reference/experimental/relaxed-lambdas.md new file mode 100644 index 000000000000..ecedda82d89a --- /dev/null +++ b/docs/_docs/reference/experimental/relaxed-lambdas.md @@ -0,0 +1,130 @@ +--- +layout: doc-page +title: "Relaxed Lambda Syntax" +nightlyOf: https://docs.scala-lang.org/scala3/reference/experimental/relaxed-lambdas.html +--- + +# Relaxed Lambda Syntax + +This experimental addition combines several small improvements to write function literals in more flexible ways. These improvements are specified in +[SIP 74](https://github.com/scala/improvement-proposals/pull/113) and +[SIP 75](https://github.com/scala/improvement-proposals/pull/118). +They are enabled by the experimental language import +```scala +import language.experimental.relaxedLambdas +``` + +## Single Line Lambdas + +Lambda expression following a `:` on the same line are now supported. Previously, +we needed a newline and indent after the arrow, e.g. +```scala +xs.map: x => + x + 1 +``` +We now also allow to write the lambda on a single line: +```scala +xs.map: x => x + 1 +``` +The lambda extends in this case to the end of the line. + +The syntax works for all kinds of function literals. They can start with one or more parameters, or with type parameters, or they can be partial functions starting +with `case`. + +```scala +Seq((1, 2), (3, 4)).map: (a, b) => a + b + +Seq((1, 2), (3, 4)).map: (a: Int, b: Int) => a + b + +Seq((1, 2), (3, 4)).collect: case (a, b) if b > 2 = a + +(1, true).map: [T] => (x: T) => List(x) +``` + +The syntax does not work for function values that do not contain a `=>` or `?=>`. For instance the following are illegal. + +```scala +Seq((1, 2), (3, 4)).map: _ + _ // error + +Seq(1, 2, 3).map: plus1 // error +``` + +Single-line lambdas can be nested, as in: +```scala + xs.map: x => x.toString + xs.dropWhile: y => y > 0 +``` + +### Detailed Spec + +A `:` means application if its is followed by one of the following: + + 1. a line end and an indented block, + 2. a parameter section, followed by `=>` or `?=>`, a line end and an indented block, + 3. a parameter section, followed by `=>` or `?=>` and an expression on a single line, + 4. a case clause, representing a single-case partial function. + +(1) and (2) is the status quo, (3) and (4) are new. + +**Restriction:** (3) and (4) do not apply in code that is immediately enclosed in parentheses (without being more closely enclosed in braces or indentation). This is to avoid an ambiguity with type ascription. For instance, +```scala +( + x: Int => Int +) +``` +still means type ascription, no interpretation as function application is attempted. + +## Curried Multi-Line Lambdas + +Previously, we admitted only a single parameter section and an arrow before +an indented block. We now also admit multiple such sections. So the following +is now legal: + +```scala +def fun(f: Int => Int => Int): Int = f(1)(2) + +fun: (x: Int) => y => + x + y +``` + +In the detailed spec above, point (2) is modified as follows: + +2. _one or more_ parameter sections, _each_ followed by `=>` or `?=>`, and finally a line end and an indented block. + +## Case Expressions + +Previously, case clauses making up a partial function had to be written in +braces or an indented block. If there is only a single case clause, we now allow it to be written also inside parentheses or as a top-level expression. + +Examples: + +```scala +case class Pair(x: Int, y: Int) + +Seq(Pair(1, 2), Pair(3, 4)).collect(case Pair(a, b) if b > 2 => a) + +Seq(Pair(1, 2), Pair(3, 4)).collect( + case (a, b) => + println(b) + a +) + +val partial: PartialFunction[(Int, Int), Int] = case (a, b) if b > 2 => a +``` + +## Syntax Changes + +``` +Expr ::= ... + | ExprCaseClause + +ColonArgument ::= colon {LambdaStart} indent (CaseClauses | Block) outdent + | colon LambdaStart {LambdaStart} expr ENDlambda + | colon ExprCaseClause +``` +Here, ENDlambda is a token synthesized at the next end of line following the +token that starts the production. + +`ExprCaseClause` already exists in the grammar. It is defined as follows: +``` +ExprCaseClause ::= ‘case’ Pattern [Guard] ‘=>’ Expr +``` diff --git a/docs/sidebar.yml b/docs/sidebar.yml index 276491cfd0a8..b55a8087b0b3 100644 --- a/docs/sidebar.yml +++ b/docs/sidebar.yml @@ -184,6 +184,7 @@ subsection: - page: reference/experimental/unrolled-defs.md - page: reference/experimental/package-object-values.md - page: reference/experimental/quoted-patterns-with-polymorphic-functions.md + - page: reference/experimental/relaxed-lambdas.md - page: reference/syntax.md - title: Language Versions index: reference/language-versions/language-versions.md diff --git a/library/src/scala/language.scala b/library/src/scala/language.scala index ffc9ad2fdca1..5f39ce2017e4 100644 --- a/library/src/scala/language.scala +++ b/library/src/scala/language.scala @@ -367,6 +367,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 } /** 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 abd2a34efa77..34227259de0d 100644 --- a/library/src/scala/runtime/stdLibPatches/language.scala +++ b/library/src/scala/runtime/stdLibPatches/language.scala @@ -174,6 +174,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/project/resources/referenceReplacements/sidebar.yml b/project/resources/referenceReplacements/sidebar.yml index b343f2644a9b..d3ca00297c2f 100644 --- a/project/resources/referenceReplacements/sidebar.yml +++ b/project/resources/referenceReplacements/sidebar.yml @@ -165,6 +165,7 @@ subsection: - page: reference/experimental/capture-checking/how-to-use.md - page: reference/experimental/capture-checking/internals.md - page: reference/experimental/tupled-function.md + - page: reference/experimental/relaxed-lambdas.md - page: reference/syntax.md - title: Language Versions index: reference/language-versions/language-versions.md diff --git a/tests/neg/closure-args.check b/tests/neg/closure-args.check new file mode 100644 index 000000000000..0ce4a2e60eb6 --- /dev/null +++ b/tests/neg/closure-args.check @@ -0,0 +1,66 @@ +-- [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:41 ------------------------------------------------------------- +21 |val fs: List[List[Int] => Int] = xs.map: x => case y :: ys => y case Nil => -1 // error // error + | ^ + | 'case' expected, but identifier 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` +-- [E007] Type Mismatch Error: tests/neg/closure-args.scala:21:97 ------------------------------------------------------ +21 |val fs: List[List[Int] => Int] = xs.map: x => case y :: ys => y case Nil => -1 // error // error + | ^ + | Found: Unit + | Required: List[Int] => Int + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg/closure-args.scala b/tests/neg/closure-args.scala index 76e590ad28b9..28c472644cf1 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 e = xs.map: y => // error +y + 1 val fs: List[List[Int] => Int] = xs.map: x => case y :: ys => y case Nil => -1 // error // 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/pos/curried-colon-lambda.scala b/tests/pos/curried-colon-lambda.scala new file mode 100644 index 000000000000..1be884ce3c1b --- /dev/null +++ b/tests/pos/curried-colon-lambda.scala @@ -0,0 +1,12 @@ +import language.experimental.relaxedLambdaSyntax + +def fun(f: Int => Int => Int): Int = f(1)(2) + +val a = fun: (x: Int) => + (y: Int) => x + y + +val b = fun: (x: Int) => (y: Int) => x + y + +val c = fun: (x: Int) => (y: Int) => + x + y + diff --git a/tests/run/single-case-expr.scala b/tests/run/single-case-expr.scala new file mode 100644 index 000000000000..098bd519c636 --- /dev/null +++ b/tests/run/single-case-expr.scala @@ -0,0 +1,39 @@ +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 a4 = Seq((1, 2), (3, 4)).collect: case (a, b) if b > 2 => + a + + val partial: PartialFunction[(Int, Int), Int] = case (a, b) if b > 2 => a + + val mtup = (1, true).map: [T] => (x: T) => List(x) + val _: (List[Int], List[Boolean]) = mtup + + +