Skip to content

Better handling of leading infix operators in indented code #11959

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 2, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 18 additions & 4 deletions compiler/src/dotty/tools/dotc/parsing/Scanners.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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) =
Expand Down Expand Up @@ -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)
Expand All @@ -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
Expand Down Expand Up @@ -1105,7 +1119,7 @@ object Scanners {
putChar(ch)
nextRawChar()
getStringPart(multiLine)
}
}
else if (ch == '$') {
nextRawChar()
if (ch == '$' || ch == '"') {
Expand Down
23 changes: 22 additions & 1 deletion docs/docs/reference/other-new-features/indentation.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 `<outdent>` 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 `<outdent>` tokens
Expand All @@ -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`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The following code compiles previously:

class Test:
   if
     5 < 6
   + 3
   then
    println("hello")

But I guess it's fine to reject it after the change.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's OK, in particular since there is no legacy code, since leading infix operators are a Scala 3 thing.


### 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.
Expand Down
12 changes: 12 additions & 0 deletions tests/run/indent.scala
Original file line number Diff line number Diff line change
@@ -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)