diff --git a/compiler/src/dotty/tools/dotc/parsing/Scanners.scala b/compiler/src/dotty/tools/dotc/parsing/Scanners.scala index 1412b5a04f47..15b3f6e63134 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Scanners.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Scanners.scala @@ -369,7 +369,7 @@ object Scanners { * 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) = + def isLeadingInfixOperator(nextWidth: IndentWidth = indentWidth(offset), inConditional: Boolean = true) = allowLeadingInfixOperators && isOperator && (isWhitespace(ch) || ch == LF) @@ -397,6 +397,20 @@ object Scanners { assumeStartsExpr(lookahead) || lookahead.token == NEWLINE && assumeStartsExpr(lookahead.next) } + && { + currentRegion match + case r: Indented => + r.width <= nextWidth + || { + r.outer match + case null => true + case Indented(outerWidth, others, _, _) => + outerWidth < nextWidth && !others.contains(nextWidth) + case outer => + outer.indentWidth < nextWidth + } + case _ => true + } && { if migrateTo3 then val (what, previous) = @@ -512,7 +526,7 @@ object Scanners { if newlineIsSeparating && canEndStatTokens.contains(lastToken) && canStartStatTokens.contains(token) - && !isLeadingInfixOperator() + && !isLeadingInfixOperator(nextWidth) && !(lastWidth < nextWidth && isContinuing(lastToken)) then insert(if (pastBlankLine) NEWLINES else NEWLINE, lineOffset) @@ -521,7 +535,7 @@ object Scanners { || nextWidth == lastWidth && (indentPrefix == MATCH || indentPrefix == CATCH) && token != CASE then if currentRegion.isOutermost then if nextWidth < lastWidth then currentRegion = topLevelRegion(nextWidth) - else if !isLeadingInfixOperator() && !statCtdTokens.contains(lastToken) then + else if !isLeadingInfixOperator(nextWidth) && !statCtdTokens.contains(lastToken) then currentRegion match case r: Indented => currentRegion = r.enclosing @@ -1105,7 +1119,7 @@ object Scanners { putChar(ch) nextRawChar() getStringPart(multiLine) - } + } else if (ch == '$') { nextRawChar() if (ch == '$' || ch == '"') { diff --git a/docs/docs/reference/other-new-features/indentation.md b/docs/docs/reference/other-new-features/indentation.md index 7d28c821cbf3..4b1b78a9bf4e 100644 --- a/docs/docs/reference/other-new-features/indentation.md +++ b/docs/docs/reference/other-new-features/indentation.md @@ -80,8 +80,11 @@ There are two rules: ``` then else do catch finally yield match ``` - - the first token on the next line is not a + - if the first token on the next line is a [leading infix operator](../changed-features/operators.md). + then its indentation width is less then the current indentation width, + and it either matches a previous indentation width or is also less + than the enclosing indentation width. If an `` is inserted, the top element is popped from `IW`. If the indentation width of the token on the next line is still less than the new current indentation width, step (2) repeats. Therefore, several `` tokens @@ -105,6 +108,24 @@ if x < 0 then Indentation tokens are only inserted in regions where newline statement separators are also inferred: at the top-level, inside braces `{...}`, but not inside parentheses `(...)`, patterns or types. +**Note:** The rules for leading infix operators above are there to make sure that +```scala + one + + two.match + case 1 => b + case 2 => c + + three +``` +is parsed as `one + (two.match ...) + three`. Also, that +```scala +if x then + a + + b + + c +else d +``` +is parsed as `if x then a + b + c else d`. + ### Optional Braces Around Template Bodies The Scala grammar uses the term _template body_ for the definitions of a class, trait, or object that are normally enclosed in braces. The braces around a template body can also be omitted by means of the following rule. diff --git a/tests/run/indent.scala b/tests/run/indent.scala new file mode 100644 index 000000000000..24cb22a5c354 --- /dev/null +++ b/tests/run/indent.scala @@ -0,0 +1,12 @@ +@main def Test = + val x = false + val y = 1 + val result = + x + || y.match + case 1 => false + case 3 => false + case _ => true + || !x + assert(result) +