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

Align leading infix operator with Scala 3 improvements #9567

Merged
merged 6 commits into from May 13, 2021
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
8 changes: 6 additions & 2 deletions src/compiler/scala/tools/nsc/Reporting.scala
Expand Up @@ -246,8 +246,12 @@ trait Reporting extends internal.Reporting { self: ast.Positions with Compilatio
&& parentFileName(pos.source).getOrElse("") == "xsbt"
&& Thread.currentThread.getStackTrace.exists(_.getClassName.startsWith("sbt."))
)
if (required && !isSbtCompat) reporter.error(pos, msg)
else warning(pos, msg, featureCategory(featureTrait.nameString), site)
// on postfix error, include interesting infix warning
def isXfix = featureName == "postfixOps" && suspendedMessages.get(pos.source).map(_.exists(w => pos.includes(w.pos))).getOrElse(false)
if (required && !isSbtCompat) {
val amended = if (isXfix) s"$msg\n${suspendedMessages(pos.source).filter(pos includes _.pos).map(_.msg).mkString("\n")}" else msg
reporter.error(pos, amended)
} else warning(pos, msg, featureCategory(featureTrait.nameString), site)
}

// Used in the optimizer where we don't have no symbols, the site string is created from the class internal name and method name.
Expand Down
37 changes: 29 additions & 8 deletions src/compiler/scala/tools/nsc/ast/parser/Scanners.scala
Expand Up @@ -13,6 +13,7 @@
package scala.tools.nsc
package ast.parser

import scala.tools.nsc.settings.ScalaVersion
import scala.tools.nsc.util.{CharArrayReader, CharArrayReaderData}
import scala.reflect.internal.util._
import scala.reflect.internal.Chars._
Expand Down Expand Up @@ -403,6 +404,9 @@ trait Scanners extends ScannersCommon {
sepRegions = sepRegions.tail
}

/** True to warn about migration change in infix syntax. */
private val infixMigration = settings.Xmigration.value <= ScalaVersion("2.13.2")

/** Produce next token, filling TokenData fields of Scanner.
*/
def nextToken(): Unit = {
Expand Down Expand Up @@ -442,18 +446,35 @@ trait Scanners extends ScannersCommon {
token = nl
}

def isOperator: Boolean = token == BACKQUOTED_IDENT || token == IDENTIFIER && isOperatorPart(name.charAt(name.length - 1))

/* A leading infix operator must be followed by a lexically suitable expression.
* Usually any simple expr will do. However, a backquoted identifier may serve as
* either an op or a reference. So the additional constraint is that the following
* token can't be an assignment operator. (Dotty disallows binary ops, hence the
* test for unary.) See run/multiLineOps.scala for 42 + `x` on 3 lines, where +
* is not leading infix because backquoted x is non-unary op.
*/
def followedByInfixRHS: Boolean = {
//def isCandidateInfixRHS: Boolean = isSimpleExprIntroToken(token) && (!isOperator || nme.raw.isUnary(name) || token == BACKQUOTED_IDENT)
def isAssignmentOperator: Boolean =
name.endsWith('=') && !name.startsWith('=') && isOperatorPart(name.startChar) &&
(name.length != 2 || (name.startChar match { case '!' | '<' | '>' => false case _ => true }))
def isCandidateInfixRHS: Boolean = isSimpleExprIntroToken(token) && (!isOperator || token == BACKQUOTED_IDENT || !isAssignmentOperator)
lookingAhead {
isCandidateInfixRHS || token == NEWLINE && { nextToken() ; isCandidateInfixRHS }
}
}

/* 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)
}
isOperator &&
(isWhitespace(ch) || ch == LF) &&
followedByInfixRHS

/* Insert NEWLINE or NEWLINES if
* - we are after a newline
Expand All @@ -469,8 +490,8 @@ trait Scanners extends ScannersCommon {
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")
|add an explicit `;`, add an empty line, or remove spaces after the operator."""
if (infixMigration) deprecationWarning(msg.stripMargin, "2.13.2")
insertNL(NEWLINE)
}
}
Expand Down
2 changes: 1 addition & 1 deletion test/files/neg/multiLineOps.check
@@ -1,5 +1,5 @@
multiLineOps.scala:6: warning: a pure expression does nothing in statement position; multiline expressions may require enclosing parentheses
+3 // error: Expected a toplevel definition
+3 // warning: a pure expression does nothing in statement position
^
error: No warnings can be incurred under -Werror.
1 warning
Expand Down
4 changes: 2 additions & 2 deletions test/files/neg/multiLineOps.scala
@@ -1,7 +1,7 @@
// scalac: -Werror -Xsource:3
// scalac: -Werror -Xlint -Xsource:3

class Test {
val x = 1
+ 2
+3 // error: Expected a toplevel definition
+3 // warning: a pure expression does nothing in statement position
}
14 changes: 1 addition & 13 deletions test/files/neg/stmt-expr-discard.check
@@ -1,21 +1,9 @@
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.
4 warnings
2 warnings
1 error
41 changes: 41 additions & 0 deletions test/files/neg/t12071.check
@@ -0,0 +1,41 @@
t12071.scala:15: error: not found: value c c
`c c` i
^
t12071.scala:15: error: postfix operator i needs to be enabled
by making the implicit value scala.language.postfixOps visible.
This can be achieved by adding the import clause 'import scala.language.postfixOps'
or by setting the compiler option -language:postfixOps.
See the Scaladoc for value scala.language.postfixOps for a discussion
why the feature needs to be explicitly enabled.
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.
`c c` i
^
t12071.scala:20: 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
^
t12071.scala:25: 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
^
t12071.scala:28: 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.
`test-1` + `test-2`
^
t12071.scala:31: 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.
`compareTo` (2 - 1)
^
4 warnings
2 errors
51 changes: 51 additions & 0 deletions test/files/neg/t12071.scala
@@ -0,0 +1,51 @@
// scalac: -Werror -Xlint -Xmigration:2.13

class C {
def `c c`(n: Int): Int = n + 1
}

// backticked operator is candidate for multiline infix,
// but backticked value is an innocent bystander.
//
class t12071 {
def c: C = ???
def i: Int = 42
def `n n`: Int = 17
def f = c
`c c` i
def g = i +
`n n`
def basic =
1
+ 2
}

object C {
def x = 42
+ 1

def y = 1 +
`test-1` + `test-2`

def z = 2
`compareTo` (2 - 1)

def `test-1`: Int = 23
def `test-2`: Int = 42
def compareTo(x: Int) = println("lol")

def yy = 1
/* fails in scala 3
+
`test-1`
+
`test-2`
*/
}

object Test extends App {
println(C.x)
println(C.y)
println(C.z)
println(C.yy)
}
14 changes: 1 addition & 13 deletions test/files/neg/t9847.check
@@ -1,15 +1,3 @@
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 @@ -47,5 +35,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.
14 warnings
12 warnings
1 error
21 changes: 21 additions & 0 deletions test/files/pos/i11371.scala
@@ -0,0 +1,21 @@
// scalac: -Xsource:3
//
object HelloWorld {
def whileLoop: Int = {
var i = 0
var acc = 0
while (i < 3) {
var `i'` = 0
while (`i'` < 4) {
acc += (i * `i'`)
`i'` += 1
}
i += 1
}
acc
}

def main(args: Array[String]): Unit = {
println(s"hello world: ${whileLoop}")
}
}
19 changes: 19 additions & 0 deletions test/files/pos/leading-infix-op.scala
@@ -0,0 +1,19 @@

// scalac: -Xsource:3

trait T {
def f(x: Int): Boolean =
x < 0
||
x > 0
&&
x != 3

def g(x: Option[Int]) = x match {
case Some(err) =>
println("hi")
???
case None =>
???
}
}
11 changes: 7 additions & 4 deletions test/files/run/multiLineOps.scala
@@ -1,12 +1,15 @@
// scalac: -Xsource:3
//
// without backticks, "not found: value +"
// was: without backticks, "not found: value +" (but parsed here as +a * 6, where backticks fool the lexer)
// now: + is taken as "solo" infix op
//
object Test extends App {
val a = 7
val x = 1
+ //
`a` * 6
+
`a`
*
6

assert(x == 1)
assert(x == 1 + 7 * 6, x) // was: 1, now: successor(42)
}
28 changes: 28 additions & 0 deletions test/files/run/t12071.scala
@@ -0,0 +1,28 @@
// scalac: -Werror -Xlint -Xsource:3

class C {
def `c c`(n: Int): Int = n + 1
}

// backticked operator is candidate for multiline infix,
// but backticked value is an innocent bystander.
//
class t12071 {
def c: C = new C
def i: Int = 42
def `n n`: Int = 27
def f = c
`c c` i
def g = i +
`n n`
def basic =
1
+ 2
}

object Test extends App {
val t = new t12071
assert(t.f == 43)
assert(t.g == 69)
assert(t.basic == 3)
}