Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions compiler/src/dotty/tools/dotc/ast/Trees.scala
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,8 @@ object Trees {

import WithEndMarker.*

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

Expand Down
59 changes: 38 additions & 21 deletions compiler/src/dotty/tools/dotc/parsing/Parsers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -349,43 +349,45 @@ object Parsers {
if in.isNewLine then in.nextToken() else accept(SEMI)

/** Parse statement separators and end markers. Ensure that there is at least
* one statement separator unless the next token terminates a statement´sequence.
* @param stats the statements parsed to far
* one statement separator unless the next token terminates a statement sequence.
* @param stats the statements parsed so far
* @param noPrevStat true if there was no immediately preceding statement parsed
* @param what a string indicating what kind of statement is parsed
* @param altEnd a token that is also considered as a terminator of the statement
* sequence (the default `EOF` already assumes to terminate a statement
* sequence (the default `EOF` is already assumed to terminate a statement
* sequence).
* @return true if the statement sequence continues, false if it terminates.
*/
def statSepOrEnd[T <: Tree](stats: ListBuffer[T], noPrevStat: Boolean = false, what: String = "statement", altEnd: Token = EOF): Boolean =
inline def stopping = false
inline def continuing = true
def recur(sepSeen: Boolean, endSeen: Boolean): Boolean =
if isStatSep then
in.nextToken()
recur(true, endSeen)
recur(sepSeen = true, endSeen)
else if in.token == END then
if endSeen then syntaxError(em"duplicate end marker")
checkEndMarker(stats)
recur(sepSeen, endSeen = true)
else if isStatSeqEnd || in.token == altEnd then
false
stopping
else if sepSeen || endSeen then
true
continuing
else
val found = in.token
val statFollows = mustStartStatTokens.contains(found)
syntaxError(
if noPrevStat then IllegalStartOfStatement(what, isModifier, statFollows)
else em"end of $what expected but ${showToken(found)} found")
if mustStartStatTokens.contains(found) then
false // it's a statement that might be legal in an outer context
if statFollows then
stopping // it's a statement that might be legal in an outer context
else
in.nextToken() // needed to ensure progress; otherwise we might cycle forever
skip()
true
continuing

in.observeOutdented()
recur(false, false)
recur(sepSeen = false, endSeen = false)
end statSepOrEnd

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

def possibleTemplateStart(isNew: Boolean = false): Unit =
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isNew was never used

def possibleTemplateStart(): Unit =
possiblyNewTemplateStart(): Unit
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ignores the boolean value


/** Return true on trivial end */
def possiblyNewTemplateStart(): Boolean =
in.observeColonEOL(inTemplate = true)
if in.token == COLONeol then
if in.lookahead.token == END then in.token = NEWLINE
if in.lookahead.token == END then
in.token = NEWLINE
true
else
in.nextToken()
if in.token != LBRACE then acceptIndent()
false
else
newLineOptWhenFollowedBy(LBRACE)
false

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

Expand Down Expand Up @@ -2915,15 +2925,22 @@ object Parsers {
val parents =
if in.isNestedStart then Nil
else constrApps(exclude = COMMA)
possibleTemplateStart(isNew = true)
parents match {
case parent :: Nil if !in.isNestedStart =>
reposition(if (parent.isType) ensureApplied(wrapNew(parent)) else parent)
case tkn if in.token == INDENT =>
New(templateBodyOpt(emptyConstructor, parents, Nil))
case _ =>
New(reposition(templateBodyOpt(emptyConstructor, parents, Nil)))
}
val colonized = possiblyNewTemplateStart()
parents match
case parent :: Nil if !in.isNestedStart =>
reposition:
if colonized then New(Template(emptyConstructor, parents, derived = Nil, self = EmptyValDef, body = Nil))
else if parent.isType then ensureApplied(wrapNew(parent))
else parent
case parents =>
// With brace syntax, the last token consumed by a parser is }, but with indent syntax,
// the last token consumed by a parser is OUTDENT, which causes mismatching spans, so don't reposition.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comment inlined from the previous PR

val indented = in.token == INDENT
val body =
val bo = templateBodyOpt(emptyConstructor, parents, derived = Nil)
if !indented then reposition(bo) else bo
New(body)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

preserves existing behavior, but what is the span of the result?

end newExpr

/** ExprsInParens ::= ExprInParens {`,' ExprInParens}
* | NamedExprInParens {‘,’ NamedExprInParens}
Expand Down
5 changes: 5 additions & 0 deletions tests/neg/i24250.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to break the habit of inserting an initial blank line in test sources?

class C
val cc =
new C
end new // error
28 changes: 28 additions & 0 deletions tests/pos/i24250.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@

trait Foo
val foo =
new Foo:
// comment
end new
val foo2 =
new Foo:
end new
val foo3 =
new Foo {
}
end new

class C
val c =
new C:
end new
val c2 =
new C {
}

class D:
end D
val d =
new D:
def more = ???
end new
Loading