Skip to content

Commit 40a29fe

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
1 parent 25af394 commit 40a29fe

File tree

7 files changed

+154
-84
lines changed

7 files changed

+154
-84
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: 90 additions & 38 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) {
@@ -164,9 +164,9 @@ object Parsers {
164164
}
165165
}
166166

167-
class Parser(source: SourceFile)(implicit ctx: Context) extends ParserCommon(source) {
167+
class Parser(source: SourceFile, startFrom: Offset = 0)(implicit ctx: Context) extends ParserCommon(source) {
168168

169-
val in: Scanner = new Scanner(source)
169+
val in: Scanner = new Scanner(source, startFrom)
170170

171171
val openParens: ParensCounters = new ParensCounters
172172

@@ -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 =
@@ -1251,7 +1251,7 @@ object Parsers {
12511251
}
12521252

12531253
def possibleTemplateStart(): Unit = {
1254-
in.observeIndented(noIndentTemplateTokens, nme.derives)
1254+
in.observeIndented(unless = noIndentTemplateTokens, unlessSoftKW = nme.derives)
12551255
newLineOptWhenFollowedBy(LBRACE)
12561256
}
12571257

@@ -1650,35 +1650,87 @@ object Parsers {
16501650

16511651
/* ----------- EXPRESSIONS ------------------------------------------------ */
16521652

1653+
def condExprRest(t: Tree) =
1654+
inSepRegion(LBRACE, RBRACE) {
1655+
newLineOpt()
1656+
expr1Rest(postfixExprRest(simpleExprRest(t)), Location.ElseWhere)
1657+
}
1658+
1659+
/** A then part can start after `)` if the next token can start a statement
1660+
* and is not a leading infix operator.
1661+
*/
1662+
def canStartThenPart =
1663+
canStartStatTokens.contains(in.token)
1664+
&& !in.isLeadingInfixOperator(inConditional = true)
1665+
1666+
/** Does the current conditional expression continue after
1667+
* the initially parsed (...) region?
1668+
*/
1669+
def toBeContinued(t: Tree, altToken: Token): Boolean =
1670+
if in.token == altToken || in.isNewLine then
1671+
false // a newline token means the expression is finished
1672+
else if !canStartThenPart then
1673+
true // since it can't be a then-part continue on the asumption that the expression continues
1674+
else if !canFollowParensTokens.contains(in.token) then
1675+
false // the next token cannot follow `)` in an expression
1676+
else
1677+
val curIndentWidth = in.currentRegion.indentWidth
1678+
1679+
// Try a complete the expression in a separate parser
1680+
object lookahead extends Parser(source, in.offset)
1681+
private var hasErrors = false
1682+
override def syntaxError(msg: => Message, span: Span): Unit =
1683+
hasErrors = true
1684+
1685+
def isCondSeparator =
1686+
in.token == altToken
1687+
|| altToken == THEN
1688+
&& in.isNewLine
1689+
&& curIndentWidth < in.indentWidth(in.next.offset)
1690+
1691+
def isCondSuffix(): Boolean =
1692+
while in.token == LPAREN || in.token == LBRACE || in.token == LBRACKET do
1693+
in.skipParens()
1694+
if isCondSeparator then
1695+
true
1696+
else if in.isNewLine then
1697+
false // expression is finished, but no altToken was found
1698+
else // try to complete expression
1699+
condExprRest(t)
1700+
!hasErrors && isCondSeparator
1701+
end lookahead
1702+
lookahead.isCondSuffix()
1703+
end toBeContinued
1704+
16531705
def condExpr(altToken: Token): Tree =
1654-
if (in.token == LPAREN) {
1706+
if in.token == LPAREN then
16551707
var t: Tree = atSpan(in.offset) { Parens(inParens(exprInParens())) }
1656-
if (in.token != altToken && followedByToken(altToken))
1657-
t = inSepRegion(LPAREN, RPAREN) {
1658-
newLineOpt()
1659-
expr1Rest(postfixExprRest(simpleExprRest(t)), Location.ElseWhere)
1660-
}
1661-
if (in.token == altToken) {
1662-
if (rewriteToOldSyntax()) revertToParens(t)
1708+
val newSyntax = toBeContinued(t, altToken)
1709+
if newSyntax then condExprRest(t)
1710+
if in.token == altToken then
1711+
if rewriteToOldSyntax() then revertToParens(t)
16631712
in.nextToken()
1664-
}
1665-
else {
1666-
in.observeIndented(noIndentAfterConditionTokens)
1713+
else
1714+
if (altToken == THEN || !newSyntax) && in.isNewLine then
1715+
in.observeIndented()
1716+
if newSyntax && in.token != INDENT then accept(altToken)
16671717
if (rewriteToNewSyntax(t.span))
16681718
dropParensOrBraces(t.span.start, s"${tokenString(altToken)}")
1669-
}
16701719
t
1671-
}
1672-
else {
1720+
else
16731721
val t =
1674-
if (in.isNestedStart)
1722+
if in.isNestedStart then
16751723
try expr() finally newLinesOpt()
16761724
else
1677-
inSepRegion(LPAREN, RPAREN)(expr())
1678-
if (rewriteToOldSyntax(t.span.startPos)) revertToParens(t)
1679-
accept(altToken)
1725+
inSepRegion(LBRACE, RBRACE)(expr())
1726+
if rewriteToOldSyntax(t.span.startPos) then
1727+
revertToParens(t)
1728+
if altToken == THEN && in.isNewLine then
1729+
// don't require a `then` at the end of a line
1730+
in.observeIndented()
1731+
if in.token != INDENT then accept(altToken)
16801732
t
1681-
}
1733+
end condExpr
16821734

16831735
/** Expr ::= [`implicit'] FunParams =>' Expr
16841736
* | Expr1
@@ -2327,7 +2379,7 @@ object Parsers {
23272379
dropParensOrBraces(start, if (in.token == YIELD || in.token == DO) "" else "do")
23282380
}
23292381
}
2330-
in.observeIndented(noIndentAfterEnumeratorTokens)
2382+
in.observeIndented(unless = noIndentAfterEnumeratorTokens)
23312383
res
23322384
}
23332385
else {

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

Lines changed: 33 additions & 29 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

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,11 @@ object Tokens extends TokensCommon {
253253
final val canEndStatTokens: TokenSet = atomicExprTokens | BitSet(
254254
TYPE, GIVEN, RPAREN, RBRACE, RBRACKET, OUTDENT)
255255

256+
/** The tokens that can follow a leading `(...)` in an expression */
257+
final val canFollowParensTokens: TokenSet = BitSet(
258+
DOT, LBRACKET, LPAREN, LBRACE, USCORE, IDENTIFIER, BACKQUOTED_IDENT, EQUALS, COLON, MATCH
259+
)
260+
256261
/** Tokens that stop a lookahead scan search for a `<-`, `then`, or `do`.
257262
* Used for disambiguating between old and new syntax.
258263
*/

0 commit comments

Comments
 (0)