Skip to content

Commit e19ad6a

Browse files
committed
Allow nested end marker scopes at same indentation width
Example in pos/end-nested.scala ```scala def f[T](x: Option[T]) = x match case Some(y) => case None => end f ``` Here, we should not give a "misaligned endmarker" error, which means we have to skip the nested `match` scope.
1 parent ca6b1c6 commit e19ad6a

File tree

5 files changed

+66
-36
lines changed

5 files changed

+66
-36
lines changed

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

Lines changed: 16 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1278,15 +1278,8 @@ object Parsers {
12781278
if silentTemplateIndent && !isNew then in.observeIndented()
12791279
newLineOptWhenFollowedBy(LBRACE)
12801280

1281-
def indentRegion[T](tag: EndMarkerTag)(op: => T): T = {
1282-
val iw = in.currentRegion.indentWidth
1283-
val t = op
1284-
in.consumeEndMarker(tag, iw)
1285-
t
1286-
}
1287-
1288-
def indentRegion[T](pid: Tree)(op: => T): T = pid match {
1289-
case pid: RefTree => indentRegion(pid.name.toTermName)(op)
1281+
def endMarkerScope[T](pid: Tree)(op: => T): T = pid match {
1282+
case pid: RefTree => in.endMarkerScope(pid.name.toTermName)(op)
12901283
case _ => op
12911284
}
12921285

@@ -1789,9 +1782,9 @@ object Parsers {
17891782

17901783
def expr1(location: Location.Value = Location.ElseWhere): Tree = in.token match {
17911784
case IF =>
1792-
indentRegion(IF) { ifExpr(in.offset, If) }
1785+
in.endMarkerScope(IF) { ifExpr(in.offset, If) }
17931786
case WHILE =>
1794-
indentRegion(WHILE) {
1787+
in.endMarkerScope(WHILE) {
17951788
atSpan(in.skipToken()) {
17961789
val cond = condExpr(DO)
17971790
newLinesOpt()
@@ -1826,7 +1819,7 @@ object Parsers {
18261819
WhileDo(Block(body, cond), Literal(Constant(())))
18271820
}
18281821
case TRY =>
1829-
indentRegion(TRY) {
1822+
in.endMarkerScope(TRY) {
18301823
val tryOffset = in.offset
18311824
atSpan(in.skipToken()) {
18321825
val body = expr()
@@ -1961,7 +1954,7 @@ object Parsers {
19611954
/** `match' (`{' CaseClauses `}' | CaseClause)
19621955
*/
19631956
def matchExpr(t: Tree, start: Offset, mkMatch: (Tree, List[CaseDef]) => Match) =
1964-
indentRegion(MATCH) {
1957+
in.endMarkerScope(MATCH) {
19651958
atSpan(start, in.skipToken()) {
19661959
mkMatch(t, casesExpr(caseClause))
19671960
}
@@ -2157,7 +2150,7 @@ object Parsers {
21572150
* | ‘new’ TemplateBody
21582151
*/
21592152
def newExpr(): Tree =
2160-
indentRegion(NEW) {
2153+
in.endMarkerScope(NEW) {
21612154
val start = in.skipToken()
21622155
def reposition(t: Tree) = t.withSpan(Span(start, in.lastOffset))
21632156
possibleBracesStart()
@@ -2323,7 +2316,7 @@ object Parsers {
23232316
* {nl} [`yield'] Expr
23242317
* | `for' Enumerators (`do' Expr | `yield' Expr)
23252318
*/
2326-
def forExpr(): Tree = indentRegion(FOR) {
2319+
def forExpr(): Tree = in.endMarkerScope(FOR) {
23272320
atSpan(in.skipToken()) {
23282321
var wrappedEnums = true
23292322
val start = in.offset
@@ -3070,7 +3063,7 @@ object Parsers {
30703063
else emptyType
30713064
val rhs =
30723065
if (tpt.isEmpty || in.token == EQUALS)
3073-
indentRegion(first) {
3066+
endMarkerScope(first) {
30743067
accept(EQUALS)
30753068
if (in.token == USCORE && !tpt.isEmpty && mods.is(Mutable) &&
30763069
(lhs.toList forall (_.isInstanceOf[Ident])))
@@ -3162,7 +3155,7 @@ object Parsers {
31623155
if (in.isScala2CompatMode) newLineOptWhenFollowedBy(LBRACE)
31633156
val rhs =
31643157
if (in.token == EQUALS)
3165-
indentRegion(name) {
3158+
in.endMarkerScope(name) {
31663159
in.nextToken()
31673160
subExpr()
31683161
}
@@ -3294,7 +3287,7 @@ object Parsers {
32943287
}
32953288

32963289
def classDefRest(start: Offset, mods: Modifiers, name: TypeName): TypeDef =
3297-
indentRegion(name.toTermName) {
3290+
in.endMarkerScope(name.toTermName) {
32983291
val constr = classConstr(isCaseClass = mods.is(Case))
32993292
val templ = templateOpt(constr)
33003293
finalizeDef(TypeDef(name, templ), mods, start)
@@ -3318,7 +3311,7 @@ object Parsers {
33183311
*/
33193312
def objectDef(start: Offset, mods: Modifiers): ModuleDef = atSpan(start, nameStart) {
33203313
val name = ident()
3321-
indentRegion(name) {
3314+
in.endMarkerScope(name) {
33223315
val templ = templateOpt(emptyConstructor)
33233316
finalizeDef(ModuleDef(name, templ), mods, start)
33243317
}
@@ -3328,7 +3321,7 @@ object Parsers {
33283321
*/
33293322
def enumDef(start: Offset, mods: Modifiers): TypeDef = atSpan(start, nameStart) {
33303323
val modulName = ident()
3331-
indentRegion(modulName) {
3324+
in.endMarkerScope(modulName) {
33323325
val clsName = modulName.toTypeName
33333326
val constr = classConstr()
33343327
val templ = template(constr, isEnum = true)
@@ -3403,7 +3396,7 @@ object Parsers {
34033396
var mods1 = addMod(mods, instanceMod)
34043397
val hasGivenSig = followingIsGivenSig()
34053398
val name = if isIdent && hasGivenSig then ident() else EmptyTermName
3406-
indentRegion(name) {
3399+
in.endMarkerScope(name) {
34073400
var tparams: List[TypeDef] = Nil
34083401
var vparamss: List[List[ValDef]] = Nil
34093402
var hasExtensionParams = false
@@ -3578,7 +3571,7 @@ object Parsers {
35783571
*/
35793572
def packaging(start: Int): Tree = {
35803573
val pkg = qualId()
3581-
indentRegion(pkg) {
3574+
endMarkerScope(pkg) {
35823575
possibleTemplateStart()
35833576
val stats = inDefScopeBraces(topStatSeq())
35843577
makePackaging(start, pkg, stats)
@@ -3766,7 +3759,7 @@ object Parsers {
37663759
else
37673760
val pkg = qualId()
37683761
var continue = false
3769-
indentRegion(pkg) {
3762+
endMarkerScope(pkg) {
37703763
possibleTemplateStart()
37713764
if in.token == EOF then
37723765
ts += makePackaging(start, pkg, List())

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

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,10 @@ object Scanners {
226226
/** The current region. This is initially an Indented region with indentation width. */
227227
var currentRegion: Region = Indented(IndentWidth.Zero, Set(), EMPTY, null)
228228

229-
/** The end marker that was skipped last */
229+
/** The number of open end marker scopes */
230+
var endMarkerScopeCount: Int = 0
231+
232+
/** The end markers that are encountered but not yet consumed */
230233
val endMarkers = new mutable.ListBuffer[EndMarker]
231234

232235
// Scala 2 compatibility
@@ -350,22 +353,29 @@ object Scanners {
350353
}
351354
}
352355

356+
/** Establish a scope for a passible end-marker with given tag, parsed by `op` */
357+
def endMarkerScope[T](tag: EndMarkerTag)(op: => T): T =
358+
endMarkerScopeCount += 1
359+
val iw = currentRegion.indentWidth
360+
try op
361+
finally
362+
endMarkerScopeCount -= 1
363+
consumeEndMarker(tag, iw)
364+
353365
/** Consume and cancel the head of the end markers queue if it has the given `tag` and width.
354366
* Flag end markers with higher indent widths as errors.
355367
*/
356-
def consumeEndMarker(tag: EndMarkerTag, width: IndentWidth): Unit = {
368+
def consumeEndMarker(tag: EndMarkerTag, width: IndentWidth): Unit =
357369
lexical.println(i"consume end marker $tag $width")
358-
if (endMarkers.nonEmpty) {
370+
if endMarkers.nonEmpty then
359371
val em = endMarkers.head
360-
if (width <= em.width) {
361-
if (em.tag != tag || em.width != width) {
362-
lexical.println(i"misaligned end marker ${em.tag}, ${em.width} at ${width}")
363-
errorButContinue("misaligned end marker", em.offset)
364-
}
372+
if width == em.width && tag == em.tag then
365373
endMarkers.trimStart(1)
366-
}
367-
}
368-
}
374+
else if width < em.width || endMarkerScopeCount == 0 then
375+
lexical.println(i"misaligned end marker ${em.tag}, $tag, ${em.width} at ${width}, $endMarkers")
376+
errorButContinue("misaligned end marker", em.offset)
377+
endMarkers.trimStart(1)
378+
end consumeEndMarker
369379

370380
/** A leading symbolic or backquoted identifier is treated as an infix operator if
371381
* - it does not follow a blank line, and

tests/neg/endmarkers.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ object Test
2424

2525
val z = 22
2626
x + y + z
27-
end f
27+
end f // error: misaligned end marker (follow on)
2828

2929
def g = "!"
3030

@@ -54,7 +54,7 @@ class Test2
5454
} // error: ';' expected, but '}' found
5555
def bar = 2
5656
end Test2 // error: misaligned end marker
57-
end Test2
57+
end Test2 // error: misaligned end marker (follow on)
5858

5959
class Test3
6060
self =>

tests/neg/endmarkers1.scala

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
2+
def f7[T](x: Option[T]) = x match
3+
case Some(y) =>
4+
case None =>
5+
end if // error: misaligned end marker
6+
7+
object Test4 with
8+
def f[T](x: Option[T]) = x match
9+
case Some(y) =>
10+
case None =>
11+
end if // error: misaligned end marker
12+
end Test
13+

tests/pos/end-nested.scala

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
def f[T](x: Option[T]) = x match
2+
case Some(y) =>
3+
case None =>
4+
end f
5+
6+
object Test with
7+
try List(1, 2, 3) match
8+
case x :: xs => println(x)
9+
case Nil => println("Nil")
10+
catch
11+
case ex: java.io.IOException => println(ex)
12+
case ex: Throwable => throw ex
13+
end try
14+

0 commit comments

Comments
 (0)