Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow infix operators at start of line (under -Xsource:3 only) #8419

Merged
merged 2 commits into from Feb 5, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
22 changes: 6 additions & 16 deletions src/compiler/scala/tools/nsc/ast/parser/Parsers.scala
Expand Up @@ -693,19 +693,10 @@ self =>
def isLiteralToken(token: Token) = token match {
case CHARLIT | INTLIT | LONGLIT | FLOATLIT | DOUBLELIT |
STRINGLIT | INTERPOLATIONID | SYMBOLLIT | TRUE | FALSE | NULL => true
case _ => false
case _ => false
som-snytt marked this conversation as resolved.
Show resolved Hide resolved
}
def isLiteral = isLiteralToken(in.token)

def isSimpleExprIntroToken(token: Token): Boolean = isLiteralToken(token) || (token match {
case IDENTIFIER | BACKQUOTED_IDENT |
THIS | SUPER | NEW | USCORE |
LPAREN | LBRACE | XMLSTART => true
case _ => false
})

def isSimpleExprIntro: Boolean = isExprIntroToken(in.token)

def isExprIntroToken(token: Token): Boolean = isLiteralToken(token) || (token match {
case IDENTIFIER | BACKQUOTED_IDENT |
THIS | SUPER | IF | FOR | NEW | USCORE | TRY | WHILE |
Expand Down Expand Up @@ -1707,23 +1698,22 @@ self =>
* PrefixExpr ::= [`-` | `+` | `~` | `!`] SimpleExpr
* }}}
*/
def prefixExpr(): Tree = {
if (isUnaryOp) {
def prefixExpr(): Tree =
if (isUnaryOp)
atPos(in.offset) {
if (lookingAhead(isSimpleExprIntro)) {
if (lookingAhead(isExprIntro)) {
val namePos = in.offset
val uname = nme.toUnaryName(rawIdent().toTermName)
if (uname == nme.UNARY_- && isNumericLit)
/* start at the -, not the number */
// start at the -, not the number
simpleExprRest(literal(isNegated = true, start = namePos), canApply = true)
else
Select(stripParens(simpleExpr()), uname)
}
else simpleExpr()
}
}
else simpleExpr()
}

def xmlLiteral(): Tree

/** {{{
Expand Down
55 changes: 52 additions & 3 deletions src/compiler/scala/tools/nsc/ast/parser/Scanners.scala
Expand Up @@ -159,6 +159,9 @@ trait Scanners extends ScannersCommon {
}

abstract class Scanner extends CharArrayReader with TokenData with ScannerData with ScannerCommon with DocScanner {
/** A switch whether operators at the start of lines can be infix operators. */
private var allowLeadingInfixOperators = true

private def isDigit(c: Char) = java.lang.Character isDigit c

private var openComments = 0
Expand Down Expand Up @@ -380,6 +383,45 @@ trait Scanners extends ScannersCommon {
next.token = EMPTY
}

def lookingAhead[A](body: => A): A = {
val saved = new ScannerData {} copyFrom this
val aLIO = allowLeadingInfixOperators
allowLeadingInfixOperators = false
nextToken()
try body finally {
this copyFrom saved
allowLeadingInfixOperators = aLIO
}
}

def isSimpleExprIntroToken(token: Token): Boolean = token match {
case CHARLIT | INTLIT | LONGLIT | FLOATLIT | DOUBLELIT |
STRINGLIT | INTERPOLATIONID | SYMBOLLIT | TRUE | FALSE | NULL | // literals
IDENTIFIER | BACKQUOTED_IDENT | THIS | SUPER | NEW | USCORE |
LPAREN | LBRACE | XMLSTART => true
case _ => false
}

def insertNL(nl: Token): Unit = {
next.copyFrom(this)
// todo: make offset line-end of previous line?
offset = if (lineStartOffset <= offset) lineStartOffset else lastLineStartOffset
token = nl
}

/** A leading symbolic or backquoted identifier is treated as an infix operator
* if it is followed by at least one ' ' and a token on the same line
* that can start an expression.
*/
def isLeadingInfixOperator =
allowLeadingInfixOperators &&
(token == BACKQUOTED_IDENT ||
token == IDENTIFIER && isOperatorPart(name.charAt(name.length - 1))) &&
(ch == ' ') && lookingAhead {
// force a NEWLINE after current token if it is on its own line
isSimpleExprIntroToken(token)
}

/* Insert NEWLINE or NEWLINES if
* - we are after a newline
* - we are within a { ... } or on toplevel (wrt sepRegions)
Expand All @@ -388,9 +430,16 @@ trait Scanners extends ScannersCommon {
*/
if (!applyBracePatch() && afterLineEnd() && inLastOfStat(lastToken) && inFirstOfStat(token) &&
(sepRegions.isEmpty || sepRegions.head == RBRACE)) {
next copyFrom this
offset = if (lineStartOffset <= offset) lineStartOffset else lastLineStartOffset
token = if (pastBlankLine()) NEWLINES else NEWLINE
if (pastBlankLine()) insertNL(NEWLINES)
else if (!isLeadingInfixOperator) insertNL(NEWLINE)
else if (!currentRun.isScala214) {
val msg = """|Line starts with an operator that in future
|will be taken as an infix expression continued from the previous line.
|To force the previous interpretation as a separate statement,
|add an explicit `;`, add an empty line, or remove spaces after the operator.""".stripMargin
deprecationWarning(msg, "2.13.2")
insertNL(NEWLINE)
}
}

// Join CASE + CLASS => CASECLASS, CASE + OBJECT => CASEOBJECT, SEMI + ELSE => ELSE
Expand Down
4 changes: 4 additions & 0 deletions test/files/neg/infixed.check
@@ -0,0 +1,4 @@
infixed.scala:8: error: ';' expected but integer literal found.
x 42
^
1 error
10 changes: 10 additions & 0 deletions test/files/neg/infixed.scala
@@ -0,0 +1,10 @@
// scalac: -Xsource:3

class K { def x(y: Int) = 0 }

class Test {
def bad = {
(new K)
x 42
}
}
4 changes: 4 additions & 0 deletions test/files/neg/multiLineOps-b.check
@@ -0,0 +1,4 @@
multiLineOps-b.scala:7: error: ';' expected but integer literal found.
*/*one more*/22 // error: end of statement expected
^
1 error
9 changes: 9 additions & 0 deletions test/files/neg/multiLineOps-b.scala
@@ -0,0 +1,9 @@
// scalac: -Werror -Xsource:2.14

class Test {
val b1 = {
22
* 22 // ok
*/*one more*/22 // error: end of statement expected
} // error: ';' expected, but '}' found
}
5 changes: 5 additions & 0 deletions test/files/neg/multiLineOps-c.check
@@ -0,0 +1,5 @@
multiLineOps-c.scala:7: error: value ! is not a member of Unit
possible cause: maybe a semicolon is missing before `value !`?
! "hello".isEmpty // error: value ! is not a member of Unit
^
1 error
9 changes: 9 additions & 0 deletions test/files/neg/multiLineOps-c.scala
@@ -0,0 +1,9 @@
// scalac: -Werror -Xsource:2.14

class Test {
val x = 42
val b2: Boolean = {
println(x)
! "hello".isEmpty // error: value ! is not a member of Unit
}
}
6 changes: 6 additions & 0 deletions test/files/neg/multiLineOps.check
@@ -0,0 +1,6 @@
multiLineOps.scala:6: warning: a pure expression does nothing in statement position; multiline expressions may require enclosing parentheses
+3 // error: Expected a toplevel definition
^
error: No warnings can be incurred under -Werror.
1 warning
1 error
7 changes: 7 additions & 0 deletions test/files/neg/multiLineOps.scala
@@ -0,0 +1,7 @@
// scalac: -Werror -Xsource:2.14

class Test {
val x = 1
+ 2
+3 // error: Expected a toplevel definition
som-snytt marked this conversation as resolved.
Show resolved Hide resolved
}
14 changes: 13 additions & 1 deletion test/files/neg/stmt-expr-discard.check
@@ -1,9 +1,21 @@
stmt-expr-discard.scala:5: warning: Line starts with an operator that in future
will be taken as an infix expression continued from the previous line.
To force the previous interpretation as a separate statement,
add an explicit `;`, add an empty line, or remove spaces after the operator.
+ 2
^
stmt-expr-discard.scala:6: warning: Line starts with an operator that in future
will be taken as an infix expression continued from the previous line.
To force the previous interpretation as a separate statement,
add an explicit `;`, add an empty line, or remove spaces after the operator.
- 4
^
stmt-expr-discard.scala:5: warning: a pure expression does nothing in statement position; multiline expressions may require enclosing parentheses
+ 2
^
stmt-expr-discard.scala:6: warning: a pure expression does nothing in statement position; multiline expressions may require enclosing parentheses
- 4
^
error: No warnings can be incurred under -Werror.
2 warnings
4 warnings
1 error
2 changes: 1 addition & 1 deletion test/files/neg/stmt-expr-discard.scala
@@ -1,4 +1,4 @@
// scalac: -Xfatal-warnings
// scalac: -Werror -Xsource:2.13 -Xlint:deprecation
//
class A {
def f = 1
Expand Down
14 changes: 13 additions & 1 deletion test/files/neg/t9847.check
@@ -1,3 +1,15 @@
t9847.scala:10: warning: Line starts with an operator that in future
will be taken as an infix expression continued from the previous line.
To force the previous interpretation as a separate statement,
add an explicit `;`, add an empty line, or remove spaces after the operator.
+ 1
^
t9847.scala:14: warning: Line starts with an operator that in future
will be taken as an infix expression continued from the previous line.
To force the previous interpretation as a separate statement,
add an explicit `;`, add an empty line, or remove spaces after the operator.
+ 1
^
t9847.scala:6: warning: discarded non-Unit value
def f(): Unit = 42
^
Expand Down Expand Up @@ -35,5 +47,5 @@ t9847.scala:24: warning: a pure expression does nothing in statement position; m
class D { 42 ; 17 }
^
error: No warnings can be incurred under -Werror.
12 warnings
14 warnings
1 error
2 changes: 1 addition & 1 deletion test/files/neg/t9847.scala
@@ -1,4 +1,4 @@
// scalac: -Xfatal-warnings -Ywarn-value-discard
// scalac: -Werror -Xlint:deprecation -Ywarn-value-discard
//

trait T {
Expand Down
10 changes: 10 additions & 0 deletions test/files/pos/infixed.scala
@@ -0,0 +1,10 @@
// scalac: -Xsource:3

class K { def x(y: Int) = 0 }

class Test {
def ok = {
(new K)
`x` 42
}
}
30 changes: 30 additions & 0 deletions test/files/pos/multiLineOps.scala
@@ -0,0 +1,30 @@
// scalac: -Werror -Xsource:2.14

class Channel {
def ! (msg: String): Channel = this
def send_! (msg: String): Channel = this
}

class Test {
val x = 1
+ 2
+ 3

val c = new Channel()

def send() =
c ! "hello"
! "world"
send_! "!"

val b: Boolean =
"hello".isEmpty
&& true &&
!"hello".isEmpty

val b2: Boolean = {
println(x)
!"hello".isEmpty
???
}
}
12 changes: 12 additions & 0 deletions test/files/run/multiLineOps.scala
@@ -0,0 +1,12 @@
// scalac: -Xsource:2.14
//
// without backticks, "not found: value +"
//
object Test extends App {
val a = 7
val x = 1
+ //
`a` * 6

assert(x == 1)
}