Skip to content

Commit 0d34bfb

Browse files
authored
Merge pull request #7270 from dotty-staging/check-indent
Issue warnings if braces are missing
2 parents ac4e8b9 + 25af394 commit 0d34bfb

38 files changed

+373
-290
lines changed

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

Lines changed: 76 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -585,6 +585,50 @@ 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+
warning(
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+
622+
/** If indentation is not significant, check that this is not the start of a
623+
* statement that's indented relative to the current region.
624+
*/
625+
def checkNextNotIndented(): Unit = in.currentRegion match
626+
case r: InBraces if in.token == NEWLINE || in.token == NEWLINES =>
627+
val nextIndentWidth = in.indentWidth(in.next.offset)
628+
if r.indentWidth < nextIndentWidth then
629+
warning(i"Line is indented too far to the right, or a `{' is missing", in.next.offset)
630+
case _ =>
631+
588632
/* -------- REWRITES ----------------------------------------------------------- */
589633

590634
/** The last offset where a colon at the end of line would be required if a subsequent { ... }
@@ -1606,13 +1650,6 @@ object Parsers {
16061650

16071651
/* ----------- EXPRESSIONS ------------------------------------------------ */
16081652

1609-
/** EqualsExpr ::= `=' Expr
1610-
*/
1611-
def equalsExpr(): Tree = {
1612-
accept(EQUALS)
1613-
expr()
1614-
}
1615-
16161653
def condExpr(altToken: Token): Tree =
16171654
if (in.token == LPAREN) {
16181655
var t: Tree = atSpan(in.offset) { Parens(inParens(exprInParens())) }
@@ -1676,7 +1713,9 @@ object Parsers {
16761713
*/
16771714
val exprInParens: () => Tree = () => expr(Location.InParens)
16781715

1679-
def expr(): Tree = expr(Location.ElseWhere)
1716+
val expr: () => Tree = () => expr(Location.ElseWhere)
1717+
1718+
def subExpr() = subPart(expr)
16801719

16811720
def expr(location: Location.Value): Tree = {
16821721
val start = in.offset
@@ -1714,7 +1753,7 @@ object Parsers {
17141753
atSpan(in.skipToken()) {
17151754
val cond = condExpr(DO)
17161755
newLinesOpt()
1717-
val body = expr()
1756+
val body = subExpr()
17181757
WhileDo(cond, body)
17191758
}
17201759
}
@@ -1753,7 +1792,7 @@ object Parsers {
17531792
if (in.token == CATCH) {
17541793
val span = in.offset
17551794
in.nextToken()
1756-
(expr(), span)
1795+
(subExpr(), span)
17571796
}
17581797
else (EmptyTree, -1)
17591798

@@ -1768,7 +1807,7 @@ object Parsers {
17681807
}
17691808

17701809
val finalizer =
1771-
if (in.token == FINALLY) { in.nextToken(); expr() }
1810+
if (in.token == FINALLY) { in.nextToken(); subExpr() }
17721811
else {
17731812
if (handler.isEmpty) warning(
17741813
EmptyCatchAndFinallyBlock(body),
@@ -1823,7 +1862,7 @@ object Parsers {
18231862
case EQUALS =>
18241863
t match {
18251864
case Ident(_) | Select(_, _) | Apply(_, _) =>
1826-
atSpan(startOffset(t), in.skipToken()) { Assign(t, expr()) }
1865+
atSpan(startOffset(t), in.skipToken()) { Assign(t, subExpr()) }
18271866
case _ =>
18281867
t
18291868
}
@@ -1870,8 +1909,8 @@ object Parsers {
18701909
atSpan(start, in.skipToken()) {
18711910
val cond = condExpr(THEN)
18721911
newLinesOpt()
1873-
val thenp = expr()
1874-
val elsep = if (in.token == ELSE) { in.nextToken(); expr() }
1912+
val thenp = subExpr()
1913+
val elsep = if (in.token == ELSE) { in.nextToken(); subExpr() }
18751914
else EmptyTree
18761915
mkIf(cond, thenp, elsep)
18771916
}
@@ -2224,7 +2263,7 @@ object Parsers {
22242263
else if (in.token == CASE) generator()
22252264
else {
22262265
val pat = pattern1()
2227-
if (in.token == EQUALS) atSpan(startOffset(pat), in.skipToken()) { GenAlias(pat, expr()) }
2266+
if (in.token == EQUALS) atSpan(startOffset(pat), in.skipToken()) { GenAlias(pat, subExpr()) }
22282267
else generatorRest(pat, casePat = false)
22292268
}
22302269

@@ -2241,7 +2280,7 @@ object Parsers {
22412280
if (casePat) GenCheckMode.FilterAlways
22422281
else if (ctx.settings.strict.value) GenCheckMode.Check
22432282
else GenCheckMode.FilterNow // filter for now, to keep backwards compat
2244-
GenFrom(pat, expr(), checkMode)
2283+
GenFrom(pat, subExpr(), checkMode)
22452284
}
22462285

22472286
/** ForExpr ::= `for' (`(' Enumerators `)' | `{' Enumerators `}')
@@ -2313,12 +2352,12 @@ object Parsers {
23132352
newLinesOpt()
23142353
if (in.token == YIELD) {
23152354
in.nextToken()
2316-
ForYield(enums, expr())
2355+
ForYield(enums, subExpr())
23172356
}
23182357
else if (in.token == DO) {
23192358
if (rewriteToOldSyntax()) dropTerminator()
23202359
in.nextToken()
2321-
ForDo(enums, expr())
2360+
ForDo(enums, subExpr())
23222361
}
23232362
else {
23242363
if (!wrappedEnums) syntaxErrorOrIncomplete(YieldOrDoExpectedInForComprehension())
@@ -2758,7 +2797,7 @@ object Parsers {
27582797
syntaxError(VarValParametersMayNotBeCallByName(name, mods.is(Mutable)))
27592798
val tpt = paramType()
27602799
val default =
2761-
if (in.token == EQUALS) { in.nextToken(); expr() }
2800+
if (in.token == EQUALS) { in.nextToken(); subExpr() }
27622801
else EmptyTree
27632802
if (impliedMods.mods.nonEmpty)
27642803
impliedMods = impliedMods.withMods(Nil) // keep only flags, so that parameter positions don't overlap
@@ -3003,7 +3042,7 @@ object Parsers {
30033042
(lhs.toList forall (_.isInstanceOf[Ident])))
30043043
wildcardIdent()
30053044
else
3006-
expr()
3045+
subExpr()
30073046
}
30083047
else EmptyTree
30093048
lhs match {
@@ -3043,7 +3082,7 @@ object Parsers {
30433082
if (in.isScala2Mode) newLineOptWhenFollowedBy(LBRACE)
30443083
val rhs = {
30453084
if (!(in.token == LBRACE && scala2ProcedureSyntax(""))) accept(EQUALS)
3046-
atSpan(in.offset) { constrExpr() }
3085+
atSpan(in.offset) { subPart(constrExpr) }
30473086
}
30483087
makeConstructor(Nil, vparamss, rhs).withMods(mods).setComment(in.getDocComment(start))
30493088
}
@@ -3076,7 +3115,7 @@ object Parsers {
30763115
if (in.token == EQUALS)
30773116
indentRegion(name) {
30783117
in.nextToken()
3079-
expr()
3118+
subExpr()
30803119
}
30813120
else if (!tpt.isEmpty)
30823121
EmptyTree
@@ -3100,7 +3139,7 @@ object Parsers {
31003139
/** ConstrExpr ::= SelfInvocation
31013140
* | `{' SelfInvocation {semi BlockStat} `}'
31023141
*/
3103-
def constrExpr(): Tree =
3142+
val constrExpr: () => Tree = () =>
31043143
if (in.isNestedStart)
31053144
atSpan(in.offset) {
31063145
inBracesOrIndented {
@@ -3351,7 +3390,7 @@ object Parsers {
33513390
if in.token == EQUALS && parents.length == 1 && parents.head.isType then
33523391
in.nextToken()
33533392
mods1 |= Final
3354-
DefDef(name, tparams, vparamss, parents.head, expr())
3393+
DefDef(name, tparams, vparamss, parents.head, subExpr())
33553394
else
33563395
//println(i"given $name $hasExtensionParams $hasGivenSig")
33573396
possibleTemplateStart()
@@ -3431,22 +3470,27 @@ object Parsers {
34313470

34323471
/** TemplateOpt = [Template]
34333472
*/
3434-
def templateOpt(constr: DefDef): Template = {
3473+
def templateOpt(constr: DefDef): Template =
34353474
possibleTemplateStart()
3436-
if (in.token == EXTENDS || isIdent(nme.derives))
3475+
if in.token == EXTENDS || isIdent(nme.derives) then
34373476
template(constr)
34383477
else
3439-
if (in.isNestedStart) template(constr)
3440-
else Template(constr, Nil, Nil, EmptyValDef, Nil)
3441-
}
3478+
if in.isNestedStart then
3479+
template(constr)
3480+
else
3481+
checkNextNotIndented()
3482+
Template(constr, Nil, Nil, EmptyValDef, Nil)
34423483

34433484
/** TemplateBody ::= [nl] `{' TemplateStatSeq `}'
34443485
*/
3445-
def templateBodyOpt(constr: DefDef, parents: List[Tree], derived: List[Tree]): Template = {
3486+
def templateBodyOpt(constr: DefDef, parents: List[Tree], derived: List[Tree]): Template =
34463487
val (self, stats) =
3447-
if (in.isNestedStart) templateBody() else (EmptyValDef, Nil)
3488+
if in.isNestedStart then
3489+
templateBody()
3490+
else
3491+
checkNextNotIndented()
3492+
(EmptyValDef, Nil)
34483493
Template(constr, parents, derived, self, stats)
3449-
}
34503494

34513495
def templateBody(): (ValDef, List[Tree]) = {
34523496
val r = inDefScopeBraces { templateStatSeq() }

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

Lines changed: 25 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -497,31 +497,29 @@ object Scanners {
497497
canStartStatTokens.contains(token) &&
498498
!isLeadingInfixOperator())
499499
insert(if (pastBlankLine) NEWLINES else NEWLINE, lineOffset)
500-
else if (indentIsSignificant)
501-
if (nextWidth < lastWidth
502-
|| nextWidth == lastWidth && (indentPrefix == MATCH || indentPrefix == CATCH) && token != CASE)
503-
currentRegion match {
504-
case r: Indented
505-
if !r.isOutermost &&
506-
!isLeadingInfixOperator() &&
507-
!statCtdTokens.contains(lastToken) =>
508-
currentRegion = r.enclosing
509-
insert(OUTDENT, offset)
510-
handleEndMarkers(nextWidth)
511-
case _ =>
512-
}
513-
else if (lastWidth < nextWidth ||
514-
lastWidth == nextWidth && (lastToken == MATCH || lastToken == CATCH) && token == CASE) {
515-
if (canStartIndentTokens.contains(lastToken)) {
500+
else if indentIsSignificant then
501+
if nextWidth < lastWidth
502+
|| nextWidth == lastWidth && (indentPrefix == MATCH || indentPrefix == CATCH) && token != CASE then
503+
if !currentRegion.isOutermost &&
504+
!isLeadingInfixOperator() &&
505+
!statCtdTokens.contains(lastToken) then
506+
currentRegion match
507+
case r: Indented =>
508+
currentRegion = r.enclosing
509+
insert(OUTDENT, offset)
510+
handleEndMarkers(nextWidth)
511+
case r: InBraces if !closingRegionTokens.contains(token) =>
512+
ctx.warning("Line is indented too far to the left, or a `}' is missing",
513+
source.atSpan(Span(offset)))
514+
case _ =>
515+
516+
else if lastWidth < nextWidth
517+
|| lastWidth == nextWidth && (lastToken == MATCH || lastToken == CATCH) && token == CASE then
518+
if canStartIndentTokens.contains(lastToken) then
516519
currentRegion = Indented(nextWidth, Set(), lastToken, currentRegion)
517520
insert(INDENT, offset)
518-
}
519-
}
520521
else if (lastWidth != nextWidth)
521-
errorButContinue(
522-
i"""Incompatible combinations of tabs and spaces in indentation prefixes.
523-
|Previous indent : $lastWidth
524-
|Latest indent : $nextWidth""")
522+
errorButContinue(spaceTabMismatchMsg(lastWidth, nextWidth))
525523
currentRegion match {
526524
case Indented(curWidth, others, prefix, outer) if curWidth < nextWidth && !others.contains(nextWidth) =>
527525
if (token == OUTDENT)
@@ -535,6 +533,11 @@ object Scanners {
535533
}
536534
}
537535

536+
def spaceTabMismatchMsg(lastWidth: IndentWidth, nextWidth: IndentWidth) =
537+
i"""Incompatible combinations of tabs and spaces in indentation prefixes.
538+
|Previous indent : $lastWidth
539+
|Latest indent : $nextWidth"""
540+
538541
def observeIndented(unless: BitSet, unlessSoftKW: TermName = EmptyTermName): Unit =
539542
if (indentSyntax && isAfterLineEnd && token != INDENT) {
540543
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 =

0 commit comments

Comments
 (0)