diff --git a/compiler/src/dotty/tools/dotc/parsing/Scanners.scala b/compiler/src/dotty/tools/dotc/parsing/Scanners.scala index ee75a43db291..aae4aab4e588 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Scanners.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Scanners.scala @@ -337,15 +337,15 @@ object Scanners { /** A leading symbolic or backquoted identifier is treated as an infix operator if * - it does not follow a blank line, and - * - it is followed on the same line by at least one ' ' - * and a token that can start an expression. + * - it is followed by at least one whitespace character and a + * token that can start an expression. * If a leading infix operator is found and the source version is `3.0-migration`, emit a change warning. */ def isLeadingInfixOperator(inConditional: Boolean = true) = allowLeadingInfixOperators && ( token == BACKQUOTED_IDENT || token == IDENTIFIER && isOperatorPart(name(name.length - 1))) - && ch == ' ' + && (isWhitespace(ch) || ch == LF) && !pastBlankLine && { val lookahead = LookaheadScanner() @@ -353,6 +353,7 @@ object Scanners { // force a NEWLINE a after current token if it is on its own line lookahead.nextToken() canStartExprTokens.contains(lookahead.token) + || lookahead.token == NEWLINE && canStartExprTokens.contains(lookahead.next.token) } && { if migrateTo3 then @@ -462,7 +463,7 @@ object Scanners { indentPrefix = r.prefix case r => indentIsSignificant = indentSyntax - if (r.knownWidth == null) r.knownWidth = nextWidth + r.proposeKnownWidth(nextWidth, lastToken) lastWidth = r.knownWidth newlineIsSeparating = r.isInstanceOf[InBraces] @@ -1349,6 +1350,18 @@ object Scanners { /** The indentation width, Zero if not known */ final def indentWidth: IndentWidth = if knownWidth == null then IndentWidth.Zero else knownWidth + + def proposeKnownWidth(width: IndentWidth, lastToken: Token) = + if knownWidth == null then + this match + case InParens(_, _) if lastToken != LPAREN => + useOuterWidth() + case _ => + knownWidth = width + + private def useOuterWidth(): Unit = + if enclosing.knownWidth == null then enclosing.useOuterWidth() + knownWidth = enclosing.knownWidth end Region case class InString(multiLine: Boolean, outer: Region) extends Region diff --git a/docs/docs/reference/changed-features/operators.md b/docs/docs/reference/changed-features/operators.md index d98da3bed317..04fe87976e02 100644 --- a/docs/docs/reference/changed-features/operators.md +++ b/docs/docs/reference/changed-features/operators.md @@ -114,7 +114,8 @@ val str = "hello" def condition = x > 0 - || xs.exists(_ > 0) + || + xs.exists(_ > 0) || xs.isEmpty ``` Previously, those expressions would have been rejected, since the compiler's semicolon inference @@ -124,8 +125,8 @@ To make this syntax work, the rules are modified to not infer semicolons in fron A _leading infix operator_ is - a symbolic identifier such as `+`, or `approx_==`, or an identifier in backticks, - that starts a new line, - - that precedes a token on the same line that can start an expression, - - and that is immediately followed by at least one space character `' '`. + - that precedes a token on the same or the next line that can start an expression, + - and that is immediately followed by at least one whitespace character. Example: diff --git a/docs/docs/reference/other-new-features/indentation.md b/docs/docs/reference/other-new-features/indentation.md index 6949f3d75c07..2980d9d1368a 100644 --- a/docs/docs/reference/other-new-features/indentation.md +++ b/docs/docs/reference/other-new-features/indentation.md @@ -159,13 +159,37 @@ Indentation prefixes can consist of spaces and/or tabs. Indentation widths are t ### Indentation and Braces -Indentation can be mixed freely with braces. For interpreting indentation inside braces, the following rules apply. +Indentation can be mixed freely with braces `{...}`, as well as brackets `[...]` and parentheses `(...)`. For interpreting indentation inside such regions, the following rules apply. 1. The assumed indentation width of a multiline region enclosed in braces is the indentation width of the first token that starts a new line after the opening brace. - 2. On encountering a closing brace `}`, as many `` tokens as necessary are - inserted to close all open indentation regions inside the pair of braces. + 2. The assumed indentation width of a multiline region inside brackets or parentheses is: + + - if the opening bracket or parenthesis is at the end of a line, the indentation width of token following it, + - otherwise, the indentation width of the enclosing region. + + 3. On encountering a closing brace `}`, bracket `]` or parenthesis `)`, as many `` tokens as necessary are inserted to close all open nested indentation regions. + +For instance, consider: +```scala +{ + val x = f(x: Int, y => + x * ( + y + 1 + ) + + (x + + x) + ) +} +``` + - Here, the indentation width of the region enclosed by the braces is 3 (i.e. the indentation width of the +statement starting with `val`). + - The indentation width of the region in parentheses that follows `f` is also 3, since the opening + parenthesis is not at the end of a line. + - The indentation width of the region in parentheses around `y + 1` is 9 + (i.e. the indentation width of `y + 1`). + - Finally, the indentation width of the last region in parentheses starting with `(x` is 6 (i.e. the indentation width of the indented region following the `=>`. ### Special Treatment of Case Clauses diff --git a/tests/pos/indent-in-parens.scala b/tests/pos/indent-in-parens.scala index 946b7b22d832..ae01b631314e 100644 --- a/tests/pos/indent-in-parens.scala +++ b/tests/pos/indent-in-parens.scala @@ -1,3 +1,16 @@ +def g(x: Int, op: Int => Int) = op(x) + +def test1 = g(1, x => + val y = x * x + y * y + ) + +def test2 = g(1, + x => + val y = x * x + y * y + ) + def f(x: Int) = assert( if x > 0 then diff --git a/tests/pos/leading-infix-op.scala b/tests/pos/leading-infix-op.scala new file mode 100644 index 000000000000..30e18b800fa0 --- /dev/null +++ b/tests/pos/leading-infix-op.scala @@ -0,0 +1,13 @@ +def f(x: Int): Boolean = + x < 0 + || + x > 0 + && + x != 3 + +def g(x: Option[Int]) = x match + case Some(err) => + println("hi") + ??? + case None => + ??? diff --git a/tests/run/Course-2002-13.scala b/tests/run/Course-2002-13.scala index e908a557a768..8a2dd9575410 100644 --- a/tests/run/Course-2002-13.scala +++ b/tests/run/Course-2002-13.scala @@ -179,7 +179,7 @@ class Parser(s: String) { val a = token; token = it.next; Con(a, - if (token equals "(") { + if (token equals "(") { token = it.next; val ts: List[Term] = if (token equals ")") List() else rep(term); if (token equals ")") token = it.next else syntaxError("`)` expected"); diff --git a/tests/run/i7031.scala b/tests/run/i7031.scala index 0756f885d966..0bc3fe81f9e6 100644 --- a/tests/run/i7031.scala +++ b/tests/run/i7031.scala @@ -1,9 +1,10 @@ @main def Test = { val a = 5 val x = 1 + + // `a` * 6 - assert(x == 1) + assert(x == 1, x) }