Skip to content

Commit 12a9190

Browse files
committed
Issue warnings if code is indented too far to the right
1 parent b419803 commit 12a9190

File tree

7 files changed

+96
-46
lines changed

7 files changed

+96
-46
lines changed

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

Lines changed: 54 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -585,10 +585,44 @@ object Parsers {
585585
try op finally in.adjustSepRegions(closing)
586586
}
587587

588+
/** Parse `body` while checking (under -noindent) that a `{` is not missing before it.
589+
* This is done as follows:
590+
* If the next token S is indented relative to the current region,
591+
* and the end of `body` is followed by a new line and another statement,
592+
* check that that other statement is indented less than S
593+
*/
594+
def subPart[T](body: () => T): T = in.currentRegion match
595+
case r: InBraces if in.isAfterLineEnd =>
596+
val startIndentWidth = in.indentWidth(in.offset)
597+
if r.indentWidth < startIndentWidth then
598+
// Note: we can get here only if indentation is not significant
599+
// If indentation is significant, we would see an <indent> as current token
600+
// and the indent region would be Indented instead of InBraces.
601+
//
602+
// If indentation would be significant, an <indent> would be inserted here.
603+
val t = body()
604+
// Therefore, make sure there would be a matching <outdent>
605+
def nextIndentWidth = in.indentWidth(in.next.offset)
606+
if (in.token == NEWLINE || in.token == NEWLINES)
607+
&& !(nextIndentWidth < startIndentWidth)
608+
then
609+
syntaxError(
610+
if startIndentWidth <= nextIndentWidth then
611+
i"""Line is indented too far to the right, or a `{' is missing before:
612+
|
613+
|$t"""
614+
else
615+
in.spaceTabMismatchMsg(startIndentWidth, nextIndentWidth),
616+
in.next.offset
617+
)
618+
t
619+
else body()
620+
case _ => body()
621+
588622
/* -------- REWRITES ----------------------------------------------------------- */
589623

590624
/** The last offset where a colon at the end of line would be required if a subsequent { ... }
591-
* block would be converted to an indentation region.
625+
* block would be converted to an indentation reg`ion.
592626
*/
593627
var possibleColonOffset: Int = -1
594628

@@ -1606,13 +1640,6 @@ object Parsers {
16061640

16071641
/* ----------- EXPRESSIONS ------------------------------------------------ */
16081642

1609-
/** EqualsExpr ::= `=' Expr
1610-
*/
1611-
def equalsExpr(): Tree = {
1612-
accept(EQUALS)
1613-
expr()
1614-
}
1615-
16161643
def condExpr(altToken: Token): Tree =
16171644
if (in.token == LPAREN) {
16181645
var t: Tree = atSpan(in.offset) { Parens(inParens(exprInParens())) }
@@ -1676,7 +1703,9 @@ object Parsers {
16761703
*/
16771704
val exprInParens: () => Tree = () => expr(Location.InParens)
16781705

1679-
def expr(): Tree = expr(Location.ElseWhere)
1706+
val expr: () => Tree = () => expr(Location.ElseWhere)
1707+
1708+
def subExpr() = subPart(expr)
16801709

16811710
def expr(location: Location.Value): Tree = {
16821711
val start = in.offset
@@ -1714,7 +1743,7 @@ object Parsers {
17141743
atSpan(in.skipToken()) {
17151744
val cond = condExpr(DO)
17161745
newLinesOpt()
1717-
val body = expr()
1746+
val body = subExpr()
17181747
WhileDo(cond, body)
17191748
}
17201749
}
@@ -1753,7 +1782,7 @@ object Parsers {
17531782
if (in.token == CATCH) {
17541783
val span = in.offset
17551784
in.nextToken()
1756-
(expr(), span)
1785+
(subExpr(), span)
17571786
}
17581787
else (EmptyTree, -1)
17591788

@@ -1768,7 +1797,7 @@ object Parsers {
17681797
}
17691798

17701799
val finalizer =
1771-
if (in.token == FINALLY) { in.nextToken(); expr() }
1800+
if (in.token == FINALLY) { in.nextToken(); subExpr() }
17721801
else {
17731802
if (handler.isEmpty) warning(
17741803
EmptyCatchAndFinallyBlock(body),
@@ -1823,7 +1852,7 @@ object Parsers {
18231852
case EQUALS =>
18241853
t match {
18251854
case Ident(_) | Select(_, _) | Apply(_, _) =>
1826-
atSpan(startOffset(t), in.skipToken()) { Assign(t, expr()) }
1855+
atSpan(startOffset(t), in.skipToken()) { Assign(t, subExpr()) }
18271856
case _ =>
18281857
t
18291858
}
@@ -1870,8 +1899,8 @@ object Parsers {
18701899
atSpan(start, in.skipToken()) {
18711900
val cond = condExpr(THEN)
18721901
newLinesOpt()
1873-
val thenp = expr()
1874-
val elsep = if (in.token == ELSE) { in.nextToken(); expr() }
1902+
val thenp = subExpr()
1903+
val elsep = if (in.token == ELSE) { in.nextToken(); subExpr() }
18751904
else EmptyTree
18761905
mkIf(cond, thenp, elsep)
18771906
}
@@ -2224,7 +2253,7 @@ object Parsers {
22242253
else if (in.token == CASE) generator()
22252254
else {
22262255
val pat = pattern1()
2227-
if (in.token == EQUALS) atSpan(startOffset(pat), in.skipToken()) { GenAlias(pat, expr()) }
2256+
if (in.token == EQUALS) atSpan(startOffset(pat), in.skipToken()) { GenAlias(pat, subExpr()) }
22282257
else generatorRest(pat, casePat = false)
22292258
}
22302259

@@ -2241,7 +2270,7 @@ object Parsers {
22412270
if (casePat) GenCheckMode.FilterAlways
22422271
else if (ctx.settings.strict.value) GenCheckMode.Check
22432272
else GenCheckMode.FilterNow // filter for now, to keep backwards compat
2244-
GenFrom(pat, expr(), checkMode)
2273+
GenFrom(pat, subExpr(), checkMode)
22452274
}
22462275

22472276
/** ForExpr ::= `for' (`(' Enumerators `)' | `{' Enumerators `}')
@@ -2313,12 +2342,12 @@ object Parsers {
23132342
newLinesOpt()
23142343
if (in.token == YIELD) {
23152344
in.nextToken()
2316-
ForYield(enums, expr())
2345+
ForYield(enums, subExpr())
23172346
}
23182347
else if (in.token == DO) {
23192348
if (rewriteToOldSyntax()) dropTerminator()
23202349
in.nextToken()
2321-
ForDo(enums, expr())
2350+
ForDo(enums, subExpr())
23222351
}
23232352
else {
23242353
if (!wrappedEnums) syntaxErrorOrIncomplete(YieldOrDoExpectedInForComprehension())
@@ -2758,7 +2787,7 @@ object Parsers {
27582787
syntaxError(VarValParametersMayNotBeCallByName(name, mods.is(Mutable)))
27592788
val tpt = paramType()
27602789
val default =
2761-
if (in.token == EQUALS) { in.nextToken(); expr() }
2790+
if (in.token == EQUALS) { in.nextToken(); subExpr() }
27622791
else EmptyTree
27632792
if (impliedMods.mods.nonEmpty)
27642793
impliedMods = impliedMods.withMods(Nil) // keep only flags, so that parameter positions don't overlap
@@ -3003,7 +3032,7 @@ object Parsers {
30033032
(lhs.toList forall (_.isInstanceOf[Ident])))
30043033
wildcardIdent()
30053034
else
3006-
expr()
3035+
subExpr()
30073036
}
30083037
else EmptyTree
30093038
lhs match {
@@ -3043,7 +3072,7 @@ object Parsers {
30433072
if (in.isScala2Mode) newLineOptWhenFollowedBy(LBRACE)
30443073
val rhs = {
30453074
if (!(in.token == LBRACE && scala2ProcedureSyntax(""))) accept(EQUALS)
3046-
atSpan(in.offset) { constrExpr() }
3075+
atSpan(in.offset) { subPart(constrExpr) }
30473076
}
30483077
makeConstructor(Nil, vparamss, rhs).withMods(mods).setComment(in.getDocComment(start))
30493078
}
@@ -3076,7 +3105,7 @@ object Parsers {
30763105
if (in.token == EQUALS)
30773106
indentRegion(name) {
30783107
in.nextToken()
3079-
expr()
3108+
subExpr()
30803109
}
30813110
else if (!tpt.isEmpty)
30823111
EmptyTree
@@ -3100,7 +3129,7 @@ object Parsers {
31003129
/** ConstrExpr ::= SelfInvocation
31013130
* | `{' SelfInvocation {semi BlockStat} `}'
31023131
*/
3103-
def constrExpr(): Tree =
3132+
val constrExpr: () => Tree = () =>
31043133
if (in.isNestedStart)
31053134
atSpan(in.offset) {
31063135
inBracesOrIndented {
@@ -3351,7 +3380,7 @@ object Parsers {
33513380
if in.token == EQUALS && parents.length == 1 && parents.head.isType then
33523381
in.nextToken()
33533382
mods1 |= Final
3354-
DefDef(name, tparams, vparamss, parents.head, expr())
3383+
DefDef(name, tparams, vparamss, parents.head, subExpr())
33553384
else
33563385
//println(i"given $name $hasExtensionParams $hasGivenSig")
33573386
possibleTemplateStart()

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

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -512,8 +512,8 @@ object Scanners {
512512
currentRegion = r.enclosing
513513
insert(OUTDENT, offset)
514514
handleEndMarkers(nextWidth)
515-
case r: InBraces if token != RBRACE && !statCtdTokens.contains(token) =>
516-
ctx.warning("Line is indented too far to the left or a `}' is missing",
515+
case r: InBraces if !closingRegionTokens.contains(token) =>
516+
ctx.warning("Line is indented too far to the left, or a `}' is missing",
517517
source.atSpan(Span(offset)))
518518
case _ =>
519519

@@ -523,10 +523,7 @@ object Scanners {
523523
currentRegion = Indented(nextWidth, Set(), lastToken, currentRegion)
524524
insert(INDENT, offset)
525525
else if (lastWidth != nextWidth)
526-
errorButContinue(
527-
i"""Incompatible combinations of tabs and spaces in indentation prefixes.
528-
|Previous indent : $lastWidth
529-
|Latest indent : $nextWidth""")
526+
errorButContinue(spaceTabMismatchMsg(lastWidth, nextWidth))
530527
currentRegion match {
531528
case Indented(curWidth, others, prefix, outer) if curWidth < nextWidth && !others.contains(nextWidth) =>
532529
if (token == OUTDENT)
@@ -540,6 +537,11 @@ object Scanners {
540537
}
541538
}
542539

540+
def spaceTabMismatchMsg(lastWidth: IndentWidth, nextWidth: IndentWidth) =
541+
i"""Incompatible combinations of tabs and spaces in indentation prefixes.
542+
|Previous indent : $lastWidth
543+
|Latest indent : $nextWidth"""
544+
543545
def observeIndented(unless: BitSet, unlessSoftKW: TermName = EmptyTermName): Unit =
544546
if (indentSyntax && isAfterLineEnd && token != INDENT) {
545547
val newLineInserted = token == NEWLINE || token == NEWLINES

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,8 @@ object Tokens extends TokensCommon {
263263

264264
final val statCtdTokens: BitSet = BitSet(THEN, ELSE, DO, CATCH, FINALLY, YIELD, MATCH)
265265

266+
final val closingRegionTokens = BitSet(RBRACE, CASE) | statCtdTokens
267+
266268
final val canStartIndentTokens: BitSet =
267269
statCtdTokens | BitSet(COLONEOL, EQUALS, ARROW, LARROW, WHILE, TRY, FOR)
268270
// `if` is excluded because it often comes after `else` which makes for awkward indentation rules TODO: try to do without the exception

compiler/src/dotty/tools/dotc/plugins/Plugins.scala

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -96,12 +96,11 @@ trait Plugins {
9696

9797
private var _plugins: List[Plugin] = _
9898
def plugins(implicit ctx: Context): List[Plugin] =
99-
if (_plugins == null) {
100-
_plugins = loadPlugins
101-
_plugins
102-
}
103-
else _plugins
104-
99+
if (_plugins == null) {
100+
_plugins = loadPlugins
101+
_plugins
102+
}
103+
else _plugins
105104

106105
/** A description of all the plugins that are loaded */
107106
def pluginDescriptions: String =

compiler/test/dotty/tools/dotc/CompilationTests.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,8 @@ class CompilationTests extends ParallelTesting {
139139
compileFile("tests/neg-custom-args/i6300.scala", allowDeepSubtypes),
140140
compileFile("tests/neg-custom-args/infix.scala", defaultOptions.and("-strict", "-deprecation", "-Xfatal-warnings")),
141141
compileFile("tests/neg-custom-args/missing-alpha.scala", defaultOptions.and("-strict", "-deprecation", "-Xfatal-warnings")),
142-
compileFile("tests/neg-custom-args/wildcards.scala", defaultOptions.and("-strict", "-deprecation", "-Xfatal-warnings"))
142+
compileFile("tests/neg-custom-args/wildcards.scala", defaultOptions.and("-strict", "-deprecation", "-Xfatal-warnings")),
143+
compileFile("tests/neg-custom-args/indentRight.scala", defaultOptions.and("-noindent", "-Xfatal-warnings"))
143144
).checkExpectedErrors()
144145
}
145146

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
object Test {
2+
3+
if (true)
4+
println("ho")
5+
println("hu") // error: Line is indented too far to the right
6+
7+
if (true)
8+
{ println("ho")
9+
}
10+
println("hu") // error: Line is indented too far to the right
11+
12+
while (true)
13+
()
14+
15+
for (x <- List()) // OK
16+
{
17+
18+
}
19+
20+
if (true) // OK
21+
println("hi")
22+
}
23+
24+
println("!") // error: expected a toplevel definition
25+
}

tests/neg/indentRight.scala

Lines changed: 0 additions & 8 deletions
This file was deleted.

0 commit comments

Comments
 (0)