Skip to content

Commit c30eafa

Browse files
committed
Make then optional at the end of line
`then` is treated like `;`: It is inferred at the end of a line, if - a new line would otherwise be inferred - the next line is indented Disambiguation with Scala-2 syntax is as follows: The question is whether to classify if (A) B C as a condition A followed by a statement B (old-style), or as a condition (A) B followed by a then part C, and an inferred `then` in-between (new-style) (new-style) is chosen if B cannot start a statement, or starts with a leading infix operator. Otherwise put, (new-style) is chosen if in ``` (A) B ``` no newline would be inserted. (old-style) is chosen otherwise. This means that some otherwise legal conditions still need a `then` at the end of a line. If (old-style) is eventually deprecated and removed, we can drop this restriction.
1 parent 0d34bfb commit c30eafa

File tree

7 files changed

+133
-83
lines changed

7 files changed

+133
-83
lines changed

compiler/src/dotty/tools/backend/jvm/BCodeHelpers.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@ trait BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
220220
if (sym == NothingClass) RT_NOTHING
221221
else if (sym == NullClass) RT_NULL
222222
else {
223-
val r = classBTypeFromSymbol(sym)
223+
val r = classBTypeFromSymbol(sym)
224224
if (r.isNestedClass) innerClassBufferASM += r
225225
r
226226
}

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

Lines changed: 53 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ object Parsers {
6969
* if not, the AST will be supplemented.
7070
*/
7171
def parser(source: SourceFile)(implicit ctx: Context): Parser =
72-
if (source.isSelfContained) new ScriptParser(source)
72+
if source.isSelfContained then new ScriptParser(source)
7373
else new Parser(source)
7474

7575
abstract class ParserCommon(val source: SourceFile)(implicit ctx: Context) {
@@ -354,7 +354,7 @@ object Parsers {
354354
case NEWLINE | NEWLINES => in.nextToken()
355355
case SEMI => in.nextToken()
356356
case _ =>
357-
syntaxError(i"end of statement expected but $in found")
357+
syntaxError(i"end of statement expected but ${showToken(in.token)} found")
358358
in.nextToken() // needed to ensure progress; otherwise we might cycle forever
359359
accept(SEMI)
360360
}
@@ -813,20 +813,20 @@ object Parsers {
813813
else span
814814
}
815815

816-
/** Drop current token, which is assumed to be `then` or `do`. */
817-
def dropTerminator(): Unit = {
818-
var startOffset = in.offset
819-
var endOffset = in.lastCharOffset
820-
if (in.isAfterLineEnd) {
821-
if (testChar(endOffset, ' '))
822-
endOffset += 1
823-
}
824-
else
825-
if (testChar(startOffset - 1, ' ') &&
826-
!overlapsPatch(source, Span(startOffset - 1, endOffset)))
827-
startOffset -= 1
828-
patch(source, widenIfWholeLine(Span(startOffset, endOffset)), "")
829-
}
816+
/** Drop current token, if it is a `then` or `do`. */
817+
def dropTerminator(): Unit =
818+
if in.token == THEN || in.token == DO then
819+
var startOffset = in.offset
820+
var endOffset = in.lastCharOffset
821+
if (in.isAfterLineEnd) {
822+
if (testChar(endOffset, ' '))
823+
endOffset += 1
824+
}
825+
else
826+
if (testChar(startOffset - 1, ' ') &&
827+
!overlapsPatch(source, Span(startOffset - 1, endOffset)))
828+
startOffset -= 1
829+
patch(source, widenIfWholeLine(Span(startOffset, endOffset)), "")
830830

831831
/** rewrite code with (...) around the source code of `t` */
832832
def revertToParens(t: Tree): Unit =
@@ -841,7 +841,8 @@ object Parsers {
841841
/** In the tokens following the current one, does `query` precede any of the tokens that
842842
* - must start a statement, or
843843
* - separate two statements, or
844-
* - continue a statement (e.g. `else`, catch`)?
844+
* - continue a statement (e.g. `else`, catch`), or
845+
* - terminate the current scope?
845846
*/
846847
def followedByToken(query: Token): Boolean = {
847848
val lookahead = in.LookaheadScanner()
@@ -1251,7 +1252,7 @@ object Parsers {
12511252
}
12521253

12531254
def possibleTemplateStart(): Unit = {
1254-
in.observeIndented(noIndentTemplateTokens, nme.derives)
1255+
in.observeIndented(unless = noIndentTemplateTokens, unlessSoftKW = nme.derives)
12551256
newLineOptWhenFollowedBy(LBRACE)
12561257
}
12571258

@@ -1650,35 +1651,51 @@ object Parsers {
16501651

16511652
/* ----------- EXPRESSIONS ------------------------------------------------ */
16521653

1654+
/** Does the current conditional expression continue after
1655+
* the initially parsed (...) region?
1656+
*/
1657+
def toBeContinued(altToken: Token): Boolean =
1658+
if in.token == altToken || in.isNewLine || in.isScala2Mode then
1659+
false // a newline token means the expression is finished
1660+
else if !canStartStatTokens.contains(in.token)
1661+
|| in.isLeadingInfixOperator(inConditional = true)
1662+
then
1663+
true
1664+
else
1665+
followedByToken(altToken) // scan ahead to see whether we find a `then` or `do`
1666+
16531667
def condExpr(altToken: Token): Tree =
1654-
if (in.token == LPAREN) {
1668+
if in.token == LPAREN then
16551669
var t: Tree = atSpan(in.offset) { Parens(inParens(exprInParens())) }
1656-
if (in.token != altToken && followedByToken(altToken))
1657-
t = inSepRegion(LPAREN, RPAREN) {
1658-
newLineOpt()
1670+
val newSyntax = toBeContinued(altToken)
1671+
if newSyntax then
1672+
t = inSepRegion(LBRACE, RBRACE) {
16591673
expr1Rest(postfixExprRest(simpleExprRest(t)), Location.ElseWhere)
16601674
}
1661-
if (in.token == altToken) {
1662-
if (rewriteToOldSyntax()) revertToParens(t)
1675+
if in.token == altToken then
1676+
if rewriteToOldSyntax() then revertToParens(t)
16631677
in.nextToken()
1664-
}
1665-
else {
1666-
in.observeIndented(noIndentAfterConditionTokens)
1678+
else
1679+
if (altToken == THEN || !newSyntax) && in.isNewLine then
1680+
in.observeIndented()
1681+
if newSyntax && in.token != INDENT then accept(altToken)
16671682
if (rewriteToNewSyntax(t.span))
16681683
dropParensOrBraces(t.span.start, s"${tokenString(altToken)}")
1669-
}
16701684
t
1671-
}
1672-
else {
1685+
else
16731686
val t =
1674-
if (in.isNestedStart)
1687+
if in.isNestedStart then
16751688
try expr() finally newLinesOpt()
16761689
else
1677-
inSepRegion(LPAREN, RPAREN)(expr())
1678-
if (rewriteToOldSyntax(t.span.startPos)) revertToParens(t)
1679-
accept(altToken)
1690+
inSepRegion(LBRACE, RBRACE)(expr())
1691+
if rewriteToOldSyntax(t.span.startPos) then
1692+
revertToParens(t)
1693+
if altToken == THEN && in.isNewLine then
1694+
// don't require a `then` at the end of a line
1695+
in.observeIndented()
1696+
if in.token != INDENT then accept(altToken)
16801697
t
1681-
}
1698+
end condExpr
16821699

16831700
/** Expr ::= [`implicit'] FunParams =>' Expr
16841701
* | Expr1
@@ -2327,7 +2344,7 @@ object Parsers {
23272344
dropParensOrBraces(start, if (in.token == YIELD || in.token == DO) "" else "do")
23282345
}
23292346
}
2330-
in.observeIndented(noIndentAfterEnumeratorTokens)
2347+
in.observeIndented(unless = noIndentAfterEnumeratorTokens)
23312348
res
23322349
}
23332350
else {

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

Lines changed: 35 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -374,8 +374,8 @@ object Scanners {
374374
* If a leading infix operator is found and -language:Scala2 or -old-syntax is set,
375375
* emit a change warning.
376376
*/
377-
def isLeadingInfixOperator() = (
378-
allowLeadingInfixOperators
377+
def isLeadingInfixOperator(inConditional: Boolean = true) = (
378+
allowLeadingInfixOperators
379379
&& ( token == BACKQUOTED_IDENT
380380
|| token == IDENTIFIER && isOperatorPart(name(name.length - 1)))
381381
&& ch == ' '
@@ -388,9 +388,12 @@ object Scanners {
388388
canStartExpressionTokens.contains(lookahead.token)
389389
}
390390
&& {
391-
if (isScala2Mode || oldSyntax && !rewrite)
392-
ctx.warning(em"""Line starts with an operator;
393-
|it is now treated as a continuation of the expression on the previous line,
391+
if isScala2Mode || oldSyntax && !rewrite then
392+
val (what, previous) =
393+
if inConditional then ("Rest of line", "previous expression in parentheses")
394+
else ("Line", "expression on the previous line")
395+
ctx.warning(em"""$what starts with an operator;
396+
|it is now treated as a continuation of the $previous,
394397
|not as a separate statement.""",
395398
source.atSpan(Span(offset)))
396399
true
@@ -538,11 +541,13 @@ object Scanners {
538541
|Previous indent : $lastWidth
539542
|Latest indent : $nextWidth"""
540543

541-
def observeIndented(unless: BitSet, unlessSoftKW: TermName = EmptyTermName): Unit =
544+
def observeIndented(
545+
unless: BitSet = BitSet.empty,
546+
unlessSoftKW: TermName = EmptyTermName): Unit
547+
=
542548
if (indentSyntax && isAfterLineEnd && token != INDENT) {
543-
val newLineInserted = token == NEWLINE || token == NEWLINES
544-
val nextOffset = if (newLineInserted) next.offset else offset
545-
val nextToken = if (newLineInserted) next.token else token
549+
val nextOffset = if (isNewLine) next.offset else offset
550+
val nextToken = if (isNewLine) next.token else token
546551
val nextWidth = indentWidth(nextOffset)
547552
val lastWidth = currentRegion match {
548553
case r: Indented => r.width
@@ -554,7 +559,7 @@ object Scanners {
554559
&& !unless.contains(nextToken)
555560
&& (unlessSoftKW.isEmpty || token != IDENTIFIER || name != unlessSoftKW)) {
556561
currentRegion = Indented(nextWidth, Set(), COLONEOL, currentRegion)
557-
if (!newLineInserted) next.copyFrom(this)
562+
if (!isNewLine) next.copyFrom(this)
558563
offset = nextOffset
559564
token = INDENT
560565
}
@@ -880,38 +885,35 @@ object Scanners {
880885
print("la:")
881886
super.printState()
882887
}
883-
884-
/** Skip matching pairs of `(...)` or `[...]` parentheses.
885-
* @pre The current token is `(` or `[`
886-
*/
887-
final def skipParens(): Unit = {
888-
val opening = token
889-
nextToken()
890-
while token != EOF && token != opening + 1 do
891-
if token == opening then skipParens() else nextToken()
892-
nextToken()
893-
}
894888
}
895889

890+
/** Skip matching pairs of `(...)` or `[...]` parentheses.
891+
* @pre The current token is `(` or `[`
892+
*/
893+
final def skipParens(multiple: Boolean = true): Unit =
894+
val opening = token
895+
nextToken()
896+
while token != EOF && token != opening + 1 do
897+
if token == opening && multiple then skipParens() else nextToken()
898+
nextToken()
899+
896900
/** Is the token following the current one in `tokens`? */
897901
def lookaheadIn(tokens: BitSet): Boolean = {
898902
val lookahead = LookaheadScanner()
899-
while ({
903+
while
900904
lookahead.nextToken()
901-
lookahead.token == NEWLINE || lookahead.token == NEWLINES
902-
})
903-
()
905+
lookahead.isNewLine
906+
do ()
904907
tokens.contains(lookahead.token)
905908
}
906909

907910
/** Is the current token in a position where a modifier is allowed? */
908911
def inModifierPosition(): Boolean = {
909912
val lookahead = LookaheadScanner()
910-
while ({
913+
while
911914
lookahead.nextToken()
912-
lookahead.token == NEWLINE || lookahead.token == NEWLINES || lookahead.isSoftModifier
913-
})
914-
()
915+
lookahead.isNewLine || lookahead.isSoftModifier
916+
do ()
915917
modifierFollowers.contains(lookahead.token)
916918
}
917919

@@ -1003,6 +1005,8 @@ object Scanners {
10031005
def isSoftModifierInParamModifierPosition: Boolean =
10041006
isSoftModifier && !lookaheadIn(BitSet(COLON))
10051007

1008+
def isNewLine = token == NEWLINE || token == NEWLINES
1009+
10061010
def isNestedStart = token == LBRACE || token == INDENT
10071011
def isNestedEnd = token == RBRACE || token == OUTDENT
10081012

@@ -1273,7 +1277,8 @@ object Scanners {
12731277

12741278
override def toString: String =
12751279
showTokenDetailed(token) + {
1276-
if ((identifierTokens contains token) || (literalTokens contains token)) " " + name
1280+
if identifierTokens.contains(token) then name
1281+
else if literalTokens.contains(token) then strVal
12771282
else ""
12781283
}
12791284

language-server/src/dotty/tools/languageserver/DottyLanguageServer.scala

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ class DottyLanguageServer extends LanguageServer
6666
private[this] var myDependentProjects: mutable.Map[ProjectConfig, mutable.Set[ProjectConfig]] = _
6767

6868
def drivers: Map[ProjectConfig, InteractiveDriver] = thisServer.synchronized {
69-
if (myDrivers == null) {
69+
if myDrivers == null
7070
assert(rootUri != null, "`drivers` cannot be called before `initialize`")
7171
val configFile = new File(new URI(rootUri + '/' + IDE_CONFIG_FILE))
7272
val configs: List[ProjectConfig] = (new ObjectMapper).readValue(configFile, classOf[Array[ProjectConfig]]).toList
@@ -78,7 +78,7 @@ class DottyLanguageServer extends LanguageServer
7878
implicit class updateDeco(ss: List[String]) {
7979
def update(pathKind: String, pathInfo: String) = {
8080
val idx = ss.indexOf(pathKind)
81-
val ss1 = if (idx >= 0) ss.take(idx) ++ ss.drop(idx + 2) else ss
81+
val ss1 = if idx >= 0 then ss.take(idx) ++ ss.drop(idx + 2) else ss
8282
ss1 ++ List(pathKind, pathInfo)
8383
}
8484
}
@@ -91,7 +91,6 @@ class DottyLanguageServer extends LanguageServer
9191
"-scansource"
9292
myDrivers(config) = new InteractiveDriver(settings)
9393
}
94-
}
9594
myDrivers
9695
}
9796

@@ -105,12 +104,13 @@ class DottyLanguageServer extends LanguageServer
105104
System.gc()
106105
for ((_, driver, opened) <- driverConfigs; (uri, source) <- opened)
107106
driver.run(uri, source)
108-
if (Memory.isCritical())
107+
if Memory.isCritical()
109108
println(s"WARNING: Insufficient memory to run Scala language server on these projects.")
110109
}
111110

112111
private def checkMemory() =
113-
if (Memory.isCritical()) CompletableFutures.computeAsync { _ => restart() }
112+
if Memory.isCritical()
113+
CompletableFutures.computeAsync { _ => restart() }
114114

115115
/** The configuration of the project that owns `uri`. */
116116
def configFor(uri: URI): ProjectConfig = thisServer.synchronized {
@@ -138,7 +138,7 @@ class DottyLanguageServer extends LanguageServer
138138
implicit class updateDeco(ss: List[String]) {
139139
def update(pathKind: String, pathInfo: String) = {
140140
val idx = ss.indexOf(pathKind)
141-
val ss1 = if (idx >= 0) ss.take(idx) ++ ss.drop(idx + 2) else ss
141+
val ss1 = if idx >= 0 then ss.take(idx) ++ ss.drop(idx + 2) else ss
142142
ss1 ++ List(pathKind, pathInfo)
143143
}
144144
}
@@ -151,7 +151,7 @@ class DottyLanguageServer extends LanguageServer
151151

152152
/** A mapping from project `p` to the set of projects that transitively depend on `p`. */
153153
def dependentProjects: Map[ProjectConfig, Set[ProjectConfig]] = thisServer.synchronized {
154-
if (myDependentProjects == null) {
154+
if myDependentProjects == null
155155
val idToConfig = drivers.keys.map(k => k.id -> k).toMap
156156
val allProjects = drivers.keySet
157157

@@ -165,7 +165,6 @@ class DottyLanguageServer extends LanguageServer
165165
dependency <- transitiveDependencies(project) } {
166166
myDependentProjects(dependency) += project
167167
}
168-
}
169168
myDependentProjects
170169
}
171170

@@ -195,7 +194,7 @@ class DottyLanguageServer extends LanguageServer
195194
throw ex
196195
}
197196
}
198-
if (synchronize)
197+
if synchronize
199198
thisServer.synchronized { computation() }
200199
else
201200
computation()
@@ -814,15 +813,15 @@ object DottyLanguageServer {
814813
def completionItemKind(sym: Symbol)(implicit ctx: Context): lsp4j.CompletionItemKind = {
815814
import lsp4j.{CompletionItemKind => CIK}
816815

817-
if (sym.is(Package) || sym.is(Module))
816+
if sym.is(Package) || sym.is(Module)
818817
CIK.Module // No CompletionItemKind.Package (https://github.com/Microsoft/language-server-protocol/issues/155)
819-
else if (sym.isConstructor)
818+
else if sym.isConstructor
820819
CIK.Constructor
821-
else if (sym.isClass)
820+
else if sym.isClass
822821
CIK.Class
823-
else if (sym.is(Mutable))
822+
else if sym.is(Mutable)
824823
CIK.Variable
825-
else if (sym.is(Method))
824+
else if sym.is(Method)
826825
CIK.Method
827826
else
828827
CIK.Field
@@ -846,7 +845,8 @@ object DottyLanguageServer {
846845
}
847846

848847
def markupContent(content: String): lsp4j.MarkupContent = {
849-
if (content.isEmpty) null
848+
if content.isEmpty
849+
null
850850
else {
851851
val markup = new lsp4j.MarkupContent
852852
markup.setKind("markdown")

0 commit comments

Comments
 (0)