Skip to content

Allow indentation to work inside parens #10969

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 3 commits into from
Jan 6, 2021
Merged
Show file tree
Hide file tree
Changes from 2 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
17 changes: 15 additions & 2 deletions compiler/src/dotty/tools/dotc/parsing/Scanners.scala
Original file line number Diff line number Diff line change
Expand Up @@ -345,14 +345,15 @@ object Scanners {
allowLeadingInfixOperators
&& ( token == BACKQUOTED_IDENT
|| token == IDENTIFIER && isOperatorPart(name(name.length - 1)))
&& ch == ' '
&& ch <= ' '
Copy link
Member

@smarter smarter Jan 5, 2021

Choose a reason for hiding this comment

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

If this is trying to match all whitespace characters, then it would be better to use an isSpace method for clarity (we can reuse the existing https://github.com/lampepfl/dotty/blob/ea65338e06142f41f0e68b23250c8e22b0ba8837/compiler/src/dotty/tools/dotc/parsing/xml/Utility.scala#L113-L119), this would also avoid matching non-space characters that precede ' ' in ASCII like \b.

The documentation of this method also needs to be updated: it currently states "it is followed on the same line by at least one ' ' and a token that can start an expression."

&& !pastBlankLine
&& {
val lookahead = LookaheadScanner()
lookahead.allowLeadingInfixOperators = false
// 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
Expand Down Expand Up @@ -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]

Expand Down Expand Up @@ -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
Expand Down
7 changes: 4 additions & 3 deletions docs/docs/reference/changed-features/operators.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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:

Expand Down
30 changes: 27 additions & 3 deletions docs/docs/reference/other-new-features/indentation.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 `<outdent>` 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 `<outdent>` 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

Expand Down
13 changes: 13 additions & 0 deletions tests/pos/indent-in-parens.scala
Original file line number Diff line number Diff line change
@@ -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
Expand Down
13 changes: 13 additions & 0 deletions tests/pos/leading-infix-op.scala
Original file line number Diff line number Diff line change
@@ -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 =>
???
2 changes: 1 addition & 1 deletion tests/run/Course-2002-13.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down
3 changes: 2 additions & 1 deletion tests/run/i7031.scala
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
@main def Test = {
val a = 5
val x = 1

+ //
`a` * 6

assert(x == 1)
assert(x == 1, x)
}