Skip to content

Commit 43ea0cc

Browse files
committed
Permit new with trivial end
1 parent fb8fddc commit 43ea0cc

File tree

3 files changed

+77
-23
lines changed

3 files changed

+77
-23
lines changed

compiler/src/dotty/tools/dotc/ast/Trees.scala

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,8 @@ object Trees {
323323

324324
import WithEndMarker.*
325325

326+
/** The span of the name in the end marker, or `NoSpan` if none.
327+
*/
326328
final def endSpan(using Context): Span =
327329
if hasEndMarker then
328330
val realName = srcName.stripModuleClassSuffix.lastPart
@@ -333,8 +335,8 @@ object Trees {
333335
/** The name in source code that represents this construct,
334336
* and is the name that the user must write to create a valid
335337
* end marker.
336-
* e.g. a constructor definition is terminated in the source
337-
* code by `end this`, so it's `srcName` should return `this`.
338+
* E.g., a constructor definition is terminated in the source
339+
* code by `end this`, so its `srcName` should return `this`.
338340
*/
339341
protected def srcName(using Context): Name
340342

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

Lines changed: 42 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -349,43 +349,45 @@ object Parsers {
349349
if in.isNewLine then in.nextToken() else accept(SEMI)
350350

351351
/** Parse statement separators and end markers. Ensure that there is at least
352-
* one statement separator unless the next token terminates a statement´sequence.
353-
* @param stats the statements parsed to far
352+
* one statement separator unless the next token terminates a statement sequence.
353+
* @param stats the statements parsed so far
354354
* @param noPrevStat true if there was no immediately preceding statement parsed
355355
* @param what a string indicating what kind of statement is parsed
356356
* @param altEnd a token that is also considered as a terminator of the statement
357-
* sequence (the default `EOF` already assumes to terminate a statement
357+
* sequence (the default `EOF` is already assumed to terminate a statement
358358
* sequence).
359359
* @return true if the statement sequence continues, false if it terminates.
360360
*/
361361
def statSepOrEnd[T <: Tree](stats: ListBuffer[T], noPrevStat: Boolean = false, what: String = "statement", altEnd: Token = EOF): Boolean =
362+
inline def stopping = false
363+
inline def continuing = true
362364
def recur(sepSeen: Boolean, endSeen: Boolean): Boolean =
363365
if isStatSep then
364366
in.nextToken()
365-
recur(true, endSeen)
367+
recur(sepSeen = true, endSeen)
366368
else if in.token == END then
367369
if endSeen then syntaxError(em"duplicate end marker")
368370
checkEndMarker(stats)
369371
recur(sepSeen, endSeen = true)
370372
else if isStatSeqEnd || in.token == altEnd then
371-
false
373+
stopping
372374
else if sepSeen || endSeen then
373-
true
375+
continuing
374376
else
375377
val found = in.token
376378
val statFollows = mustStartStatTokens.contains(found)
377379
syntaxError(
378380
if noPrevStat then IllegalStartOfStatement(what, isModifier, statFollows)
379381
else em"end of $what expected but ${showToken(found)} found")
380-
if mustStartStatTokens.contains(found) then
381-
false // it's a statement that might be legal in an outer context
382+
if statFollows then
383+
stopping // it's a statement that might be legal in an outer context
382384
else
383385
in.nextToken() // needed to ensure progress; otherwise we might cycle forever
384386
skip()
385-
true
387+
continuing
386388

387389
in.observeOutdented()
388-
recur(false, false)
390+
recur(sepSeen = false, endSeen = false)
389391
end statSepOrEnd
390392

391393
def rewriteNotice(version: SourceVersion = `3.0-migration`, additionalOption: String = "") =
@@ -1566,15 +1568,23 @@ object Parsers {
15661568
if MigrationVersion.Scala2to3.needsPatch then
15671569
patch(source, Span(in.offset), " ")
15681570

1569-
def possibleTemplateStart(isNew: Boolean = false): Unit =
1571+
def possibleTemplateStart(): Unit =
1572+
possiblyNewTemplateStart(): Unit
1573+
1574+
/** Return true on trivial end */
1575+
def possiblyNewTemplateStart(): Boolean =
15701576
in.observeColonEOL(inTemplate = true)
15711577
if in.token == COLONeol then
1572-
if in.lookahead.token == END then in.token = NEWLINE
1578+
if in.lookahead.token == END then
1579+
in.token = NEWLINE
1580+
true
15731581
else
15741582
in.nextToken()
15751583
if in.token != LBRACE then acceptIndent()
1584+
false
15761585
else
15771586
newLineOptWhenFollowedBy(LBRACE)
1587+
false
15781588

15791589
def checkEndMarker[T <: Tree](stats: ListBuffer[T]): Unit =
15801590

@@ -1599,6 +1609,10 @@ object Parsers {
15991609
case _: ParsedTry => in.token == TRY
16001610
case _: Match => in.token == MATCH
16011611
case _: New => in.token == NEW
1612+
case _: Apply =>
1613+
methPart(stat) match
1614+
case Select(_: New, nme.CONSTRUCTOR) => in.token == NEW
1615+
case _ => false
16021616
case _: (ForYield | ForDo) => in.token == FOR
16031617
case _ => false
16041618

@@ -2915,15 +2929,22 @@ object Parsers {
29152929
val parents =
29162930
if in.isNestedStart then Nil
29172931
else constrApps(exclude = COMMA)
2918-
possibleTemplateStart(isNew = true)
2919-
parents match {
2920-
case parent :: Nil if !in.isNestedStart =>
2921-
reposition(if (parent.isType) ensureApplied(wrapNew(parent)) else parent)
2922-
case tkn if in.token == INDENT =>
2923-
New(templateBodyOpt(emptyConstructor, parents, Nil))
2924-
case _ =>
2925-
New(reposition(templateBodyOpt(emptyConstructor, parents, Nil)))
2926-
}
2932+
val colonized = possiblyNewTemplateStart()
2933+
parents match
2934+
case parent :: Nil if !in.isNestedStart =>
2935+
reposition:
2936+
if colonized then New(Template(emptyConstructor, parents, derived = Nil, self = EmptyValDef, body = Nil))
2937+
else if parent.isType then ensureApplied(wrapNew(parent))
2938+
else parent
2939+
case parents =>
2940+
// With brace syntax, the last token consumed by a parser is }, but with indent syntax,
2941+
// the last token consumed by a parser is OUTDENT, which causes mismatching spans, so don't reposition.
2942+
val indented = in.token == INDENT
2943+
val body =
2944+
val bo = templateBodyOpt(emptyConstructor, parents, derived = Nil)
2945+
if !indented then reposition(bo) else bo
2946+
New(body)
2947+
end newExpr
29272948

29282949
/** ExprsInParens ::= ExprInParens {`,' ExprInParens}
29292950
* | NamedExprInParens {‘,’ NamedExprInParens}

tests/pos/i24250.scala

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
2+
trait Foo
3+
val foo =
4+
new Foo:
5+
// comment
6+
end new
7+
val foo2 =
8+
new Foo:
9+
end new
10+
val foo3 =
11+
new Foo {
12+
}
13+
end new
14+
15+
class C
16+
val c =
17+
new C:
18+
end new
19+
val cc =
20+
new C
21+
end new
22+
val ccc =
23+
new C {
24+
}
25+
26+
class D:
27+
end D
28+
val d =
29+
new D:
30+
def more = ???
31+
end new

0 commit comments

Comments
 (0)