Skip to content

Commit 236c117

Browse files
committed
Patch indentation when removing braces (essentials)
If the first indentation of the region is greater than the indentation of the enclosing region, we use it to indent the whole region. Otherwise we use the incremented indentation of the enclosing region. ```scala def foo = { x // we replicate indentation of x downward in region y } ``` ```scala def foo = { x // indentation of x is incorrect, we increment enclosing indentation y } ``` A bigger indentation than the required one is permitted except just after a closing brace. ```scala def bar = { x .toString // permitted indentation def foo = { } bar // must be unindented, to not fall into the body of foo } ``` And other bug fixes (see lampepfl#17522)
1 parent e674eaa commit 236c117

File tree

4 files changed

+41
-30
lines changed

4 files changed

+41
-30
lines changed

compiler/src/dotty/tools/dotc/parsing/Parsers.scala

Lines changed: 35 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -533,11 +533,11 @@ object Parsers:
533533
def inBrackets[T](body: => T): T = enclosed(LBRACKET, body)
534534

535535
def inBracesOrIndented[T](body: => T, inStatSeq: Boolean = false, rewriteWithColon: Boolean = false): T =
536-
val followsArrow = in.last.token == ARROW
537536
if in.token == INDENT then
538537
// braces are always optional after `=>` so none should be inserted
539-
val rewriteToBraces = in.rewriteNoIndent && !followsArrow
540-
val rewriteToIndent = in.rewriteToIndent && !followsArrow
538+
val afterArrow = testChars(in.lastOffset - 3, " =>")
539+
val rewriteToBraces = in.rewriteNoIndent && !afterArrow
540+
val rewriteToIndent = in.rewriteToIndent && !afterArrow
541541
if rewriteToBraces then indentedToBraces(body)
542542
else if rewriteToIndent then enclosed(INDENT, toIndentedRegion(body))
543543
else enclosed(INDENT, body)
@@ -614,6 +614,11 @@ object Parsers:
614614
/* -------- REWRITES ----------------------------------------------------------- */
615615

616616
object IndentRewriteState:
617+
assert(in.rewriteToIndent)
618+
619+
/** A copy of the previous token */
620+
var prev: TokenData = Scanners.newTokenData
621+
617622
/** The last offset where a colon at the end of line would be required if a subsequent { ... }
618623
* block would be converted to an indentation region. */
619624
var possibleColonOffset: Int = -1
@@ -705,8 +710,8 @@ object Parsers:
705710
val (start, end) = blankLinesAround(offset, offset + 1)
706711
if testChar(end, Chars.LF) then
707712
if testChar(start - 1, Chars.LF) then (start, end + 1) // skip the whole line
708-
else (start, end) // skip the end of line
709-
else (offset, end) // skip from last to end of token
713+
else (start, end) // skip from previous char to end of line
714+
else (offset, end) // skip from token to next char
710715

711716
/** Expand the current span to its surrounding blank space */
712717
def blankLinesAround(start: Offset, end: Offset): (Offset, Offset) =
@@ -720,18 +725,18 @@ object Parsers:
720725
* 4. there is at least one token between the braces
721726
* 5. the closing brace is also at the end of the line, or it is followed by one of
722727
* `then`, `else`, `do`, `catch`, `finally`, `yield`, or `match`.
723-
* 6. the opening brace does not follow a closing `}`
728+
* 6. the opening brace does not follow a closing brace
724729
* 7. last token is not a leading operator
725730
* 8. not a block in a sequence of statements
726-
* 9. cannot rewrite to colon after a NEWLINE, e.g.
731+
* 9. cannot rewrite if colon required after a NEWLINE, e.g.
727732
* true ||
728-
* { // NEWLINE inserted between || and {
733+
* {
729734
* false
730735
* }
731736
*/
732737
def bracesToIndented[T](body: => T, inStatSeq: Boolean, rewriteWithColon: Boolean): T =
733738
import IndentRewriteState.*
734-
val lastSaved = in.last.saveCopy
739+
val prevSaved = prev.saveCopy
735740
val lastOffsetSaved = in.lastOffset
736741
val underColonSyntax = possibleColonOffset == in.lastOffset
737742
val colonRequired = rewriteWithColon || underColonSyntax
@@ -741,9 +746,10 @@ object Parsers:
741746
case r: InBraces => true
742747
case _ => false
743748
var canRewrite = isBracesOrIndented(in.currentRegion) && // test (1)
744-
lastSaved.token != RBRACE && // test (6)
745-
!(lastSaved.isOperator && lastSaved.isAfterLineEnd) && // test (7)
746-
!inStatSeq // test (8)
749+
prevSaved.token != RBRACE && // test (6)
750+
!(prevSaved.isOperator && prevSaved.isAfterLineEnd) && // test (7)
751+
!inStatSeq && // test (8)
752+
(!colonRequired || !in.isAfterLineEnd) // test (9)
747753
val t = enclosed(LBRACE, {
748754
if in.isAfterLineEnd && in.token != RBRACE then // test (2)(4)
749755
toIndentedRegion:
@@ -753,17 +759,16 @@ object Parsers:
753759
canRewrite = false
754760
body
755761
})
756-
canRewrite &= (in.isAfterLineEnd || in.token == EOF || statCtdTokens.contains(in.token)) && // test (5)
757-
(!colonRequired || !lastSaved.isNewLine) // test (9)
762+
canRewrite &= (in.isAfterLineEnd || in.token == EOF || statCtdTokens.contains(in.token)) // test (5)
758763
if canRewrite && (!underColonSyntax || Feature.fewerBracesEnabled) then
759-
val (startClosing, endClosing) = elimRegion(in.last.offset)
764+
val (startClosing, endClosing) = elimRegion(in.lastOffset - 1)
760765
// patch over the added indentation to remove braces
761766
patchOver(source, Span(startOpening, endOpening), "")
762767
patchOver(source, Span(startClosing, endClosing), "")
763768
if colonRequired then
764-
if lastSaved.token == IDENTIFIER && lastSaved.isOperator then
765-
patch(Span(lastSaved.offset, lastSaved.offset + lastSaved.name.length), s"`${lastSaved.name}`:")
766-
else if lastSaved.token == IDENTIFIER && lastSaved.name.last == '_' then
769+
if prevSaved.token == IDENTIFIER && prevSaved.isOperator then
770+
patch(Span(prevSaved.offset, lastOffsetSaved), s"`${prevSaved.name}`:")
771+
else if prevSaved.token == IDENTIFIER && prevSaved.name.last == '_' then
767772
patch(Span(lastOffsetSaved), " :")
768773
else patch(Span(lastOffsetSaved), ":")
769774
else
@@ -816,7 +821,9 @@ object Parsers:
816821
maximumIndent = None
817822

818823
def nextToken(): Unit =
819-
if in.rewriteToIndent then patchIndent()
824+
if in.rewriteToIndent then
825+
IndentRewriteState.prev = in.saveCopy
826+
patchIndent()
820827
in.nextToken()
821828

822829
def skipToken(): Offset =
@@ -838,7 +845,7 @@ object Parsers:
838845
val preFill = if (closingStartsLine || endStr.isEmpty) "" else " "
839846
val postFill = if (in.lastOffset == in.offset) " " else ""
840847
val (startClosing, endClosing) =
841-
if (closingStartsLine && endStr.isEmpty) elimRegion(in.last.offset)
848+
if (closingStartsLine && endStr.isEmpty) elimRegion(in.lastOffset - 1)
842849
else (in.lastOffset - 1, in.lastOffset)
843850
patch(source, Span(startClosing, endClosing), s"$preFill$endStr$postFill")
844851

@@ -1336,7 +1343,8 @@ object Parsers:
13361343
syntaxErrorOrIncomplete(em"indented definitions expected, ${in} found")
13371344

13381345
def colonAtEOLOpt(): Unit =
1339-
IndentRewriteState.possibleColonOffset = in.lastOffset
1346+
if in.rewriteToIndent then
1347+
IndentRewriteState.possibleColonOffset = in.lastOffset
13401348
in.observeColonEOL(inTemplate = false)
13411349
if in.token == COLONeol then
13421350
nextToken()
@@ -2062,7 +2070,7 @@ object Parsers:
20622070

20632071
def subExpr() = subPart(expr)
20642072

2065-
def expr(location: Location, inStatSeq: Boolean = false): Tree =
2073+
def expr(location: Location, inStatSeq: Boolean = false): Tree = {
20662074
val start = in.offset
20672075
in.token match
20682076
case IMPLICIT =>
@@ -2385,7 +2393,7 @@ object Parsers:
23852393
* Quoted ::= ‘'’ ‘{’ Block ‘}’
23862394
* | ‘'’ ‘[’ Type ‘]’
23872395
*/
2388-
def simpleExpr(location: Location, inStatSeq: Boolean = false): Tree =
2396+
def simpleExpr(location: Location, inStatSeq: Boolean = false): Tree = {
23892397
var canApply = true
23902398
val t = in.token match
23912399
case XMLSTART =>
@@ -2557,12 +2565,13 @@ object Parsers:
25572565

25582566
/** BlockExpr ::= <<< (CaseClauses | Block) >>>
25592567
*/
2560-
def blockExpr(inStatSeq: Boolean = false): Tree = atSpan(in.offset):
2568+
def blockExpr(inStatSeq: Boolean = false): Tree = atSpan(in.offset) {
25612569
val simplify = in.token == INDENT
25622570
inDefScopeBraces({
25632571
if (in.token == CASE) Match(EmptyTree, caseClauses(() => caseClause()))
25642572
else block(simplify)
25652573
}, inStatSeq = inStatSeq)
2574+
}
25662575

25672576
/** Block ::= BlockStatSeq
25682577
* @note Return tree does not have a defined span.
@@ -2731,7 +2740,7 @@ object Parsers:
27312740
rejectWildcardType(infixType())
27322741
CaseDef(pat, EmptyTree, atSpan(accept(ARROW)) {
27332742
val t = indentedRegionAfterArrow(rejectWildcardType(typ()), inCaseDef = true)
2734-
if in.token == SEMI then nextToken()
2743+
if in.token == SEMI then in.nextToken()
27352744
newLinesOptWhenFollowedBy(CASE)
27362745
t
27372746
})
@@ -4181,7 +4190,7 @@ object Parsers:
41814190
def skipBracesHook(): Option[Tree] =
41824191
if (in.token == XMLSTART) Some(xmlLiteral()) else None
41834192

4184-
override def blockExpr(inStatSeq: Boolean): Tree =
4193+
override def blockExpr(inStatSeq: Boolean): Tree = {
41854194
skipBraces()
41864195
EmptyTree
41874196

compiler/src/dotty/tools/dotc/parsing/Scanners.scala

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ object Scanners:
9595

9696
def newTokenData: TokenData = new TokenData {}
9797

98-
abstract class ScannerCommon(source: SourceFile)(using Context) extends CharArrayReader with TokenData:
98+
abstract class ScannerCommon(source: SourceFile)(using Context) extends CharArrayReader with TokenData {
9999
val buf: Array[Char] = source.content
100100
def nextToken(): Unit
101101

@@ -472,7 +472,7 @@ object Scanners:
472472
else if (nextChar == ' ' || nextChar == '\t')
473473
if (nextChar == ch)
474474
recur(idx - 1, ch, n + 1, k)
475-
else
475+
else {
476476
val k1: IndentWidth => IndentWidth = if (n == 0) k else iw => k(Conc(iw, Run(ch, n)))
477477
recur(idx - 1, nextChar, 1, k1)
478478
else recur(idx - 1, ' ', 0, identity)
@@ -744,6 +744,8 @@ object Scanners:
744744
if endMarkerTokens.contains(lookahead.token)
745745
&& source.offsetToLine(lookahead.offset) == endLine
746746
then
747+
if rewriteToIndent && lookahead.token == MATCH then
748+
patch(Span(offset, offset + 3), "`end`")
747749
lookahead.nextToken()
748750
if lookahead.token == EOF
749751
|| source.offsetToLine(lookahead.offset) > endLine

compiler/src/dotty/tools/dotc/parsing/xml/MarkupParsers.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -364,7 +364,7 @@ object MarkupParsers:
364364

365365
def escapeToScala[A](op: => A, kind: String): A =
366366
xEmbeddedBlock = false
367-
val res = saving(parser.in.currentRegion, parser.in.currentRegion = _):
367+
val res = saving(parser.in.currentRegion, parser.in.currentRegion = _) {
368368
val lbrace = Scanners.newTokenData
369369
lbrace.token = LBRACE
370370
lbrace.offset = parser.in.charOffset - 1

compiler/src/dotty/tools/dotc/rewrites/Rewrites.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ object Rewrites:
2828
pbuf.indices.reverse.find(i => span.contains(pbuf(i).span)).foreach(pbuf.remove)
2929
pbuf += Patch(span, replacement)
3030

31-
def apply(cs: Array[Char]): Array[Char] =
31+
def apply(cs: Array[Char]): Array[Char] = {
3232
val delta = pbuf.map(_.delta).sum
3333
val patches = pbuf.toList.sortBy(p => (p.span.start, p.span.end))
3434
if (patches.nonEmpty)

0 commit comments

Comments
 (0)