Skip to content

Commit

Permalink
Classify an id as infix operator only if following can start an operand
Browse files Browse the repository at this point in the history
Also, detect and report spread operators in illegal positions
  • Loading branch information
odersky committed Oct 29, 2023
1 parent 38559d7 commit ffb2ab7
Show file tree
Hide file tree
Showing 15 changed files with 78 additions and 66 deletions.
24 changes: 20 additions & 4 deletions compiler/src/dotty/tools/dotc/parsing/Parsers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -970,6 +970,15 @@ object Parsers {
isArrowIndent()
else false

/** Can the next lookahead token start an operand as defined by
* leadingOperandTokens, or is postfix ops enabled?
* This is used to decide whether the current token can be an infix operator.
*/
def nextCanFollowOperator(leadingOperandTokens: BitSet): Boolean =
leadingOperandTokens.contains(in.lookahead.token)
|| in.postfixOpsEnabled
|| in.lookahead.token == COLONop

/* --------- OPERAND/OPERATOR STACK --------------------------------------- */

var opStack: List[OpInfo] = Nil
Expand Down Expand Up @@ -1050,7 +1059,11 @@ object Parsers {
then recur(top)
else top

recur(first)
val res = recur(first)
if isIdent(nme.raw.STAR) && !followingIsVararg() then
syntaxError(em"spread operator `*` not allowed here; must come last in a parameter list")
in.nextToken()
res
end infixOps

/* -------- IDENTIFIERS AND LITERALS ------------------------------------------- */
Expand Down Expand Up @@ -1671,7 +1684,8 @@ object Parsers {

def infixTypeRest(t: Tree): Tree =
infixOps(t, canStartInfixTypeTokens, refinedTypeFn, Location.ElseWhere, ParseKind.Type,
isOperator = !followingIsVararg() && !isPureArrow)
isOperator = !followingIsVararg() && !isPureArrow
&& nextCanFollowOperator(canStartInfixTypeTokens))

/** RefinedType ::= WithType {[nl] Refinement} [`^` CaptureSet]
*/
Expand Down Expand Up @@ -2444,7 +2458,8 @@ object Parsers {

def postfixExprRest(t: Tree, location: Location): Tree =
infixOps(t, in.canStartExprTokens, prefixExpr, location, ParseKind.Expr,
isOperator = !(location.inArgs && followingIsVararg()))
isOperator = !(location.inArgs && followingIsVararg())
&& nextCanFollowOperator(canStartInfixExprTokens))

/** PrefixExpr ::= [PrefixOperator'] SimpleExpr
* PrefixOperator ::= ‘-’ | ‘+’ | ‘~’ | ‘!’ (if not backquoted)
Expand Down Expand Up @@ -2947,7 +2962,8 @@ object Parsers {
def infixPattern(): Tree =
infixOps(
simplePattern(), in.canStartExprTokens, simplePatternFn, Location.InPattern, ParseKind.Pattern,
isOperator = in.name != nme.raw.BAR && !followingIsVararg())
isOperator = in.name != nme.raw.BAR && !followingIsVararg()
&& nextCanFollowOperator(canStartPatternTokens))

/** SimplePattern ::= PatVar
* | Literal
Expand Down
11 changes: 8 additions & 3 deletions compiler/src/dotty/tools/dotc/parsing/Tokens.scala
Original file line number Diff line number Diff line change
Expand Up @@ -221,10 +221,13 @@ object Tokens extends TokensCommon {

final val openParensTokens = BitSet(LBRACE, LPAREN, LBRACKET)

final val canStartExprTokens3: TokenSet =
atomicExprTokens
final val canStartInfixExprTokens =
atomicExprTokens
| openParensTokens
| BitSet(INDENT, QUOTE, IF, WHILE, FOR, NEW, TRY, THROW)
| BitSet(QUOTE, NEW)

final val canStartExprTokens3: TokenSet =
canStartInfixExprTokens | BitSet(INDENT, IF, WHILE, FOR, TRY, THROW)

final val canStartExprTokens2: TokenSet = canStartExprTokens3 | BitSet(DO)

Expand All @@ -233,6 +236,8 @@ object Tokens extends TokensCommon {

final val canStartTypeTokens: TokenSet = canStartInfixTypeTokens | BitSet(LBRACE)

final val canStartPatternTokens = atomicExprTokens | openParensTokens | BitSet(USCORE, QUOTE)

final val templateIntroTokens: TokenSet = BitSet(CLASS, TRAIT, OBJECT, ENUM, CASECLASS, CASEOBJECT)

final val dclIntroTokens: TokenSet = BitSet(DEF, VAL, VAR, TYPE, GIVEN)
Expand Down
4 changes: 2 additions & 2 deletions tests/neg/cc-only-defs.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@ trait Test {

val b: ImpureFunction1[Int, Int] // now OK

val a: {z} String // error
} // error
val a: {z} String // error // error
}
21 changes: 4 additions & 17 deletions tests/neg/i14564.check
Original file line number Diff line number Diff line change
@@ -1,17 +1,4 @@
-- [E018] Syntax Error: tests/neg/i14564.scala:5:28 --------------------------------------------------------------------
5 |def test = sum"${ List(42)* }" // error // error
| ^
| expression expected but '}' found
|
| longer explanation available when compiling with `-explain`
-- [E008] Not Found Error: tests/neg/i14564.scala:5:26 -----------------------------------------------------------------
5 |def test = sum"${ List(42)* }" // error // error
| ^^^^^^^^^
| value * is not a member of List[Int], but could be made available as an extension method.
|
| One of the following imports might make progress towards fixing the problem:
|
| import scala.math.Fractional.Implicits.infixFractionalOps
| import scala.math.Integral.Implicits.infixIntegralOps
| import scala.math.Numeric.Implicits.infixNumericOps
|
-- Error: tests/neg/i14564.scala:5:26 ----------------------------------------------------------------------------------
5 |def test = sum"${ List(42)* }" // error
| ^
| spread operator `*` not allowed here; must come last in a parameter list
2 changes: 1 addition & 1 deletion tests/neg/i14564.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@ import language.postfixOps as _

extension (sc: StringContext) def sum(xs: Int*): String = xs.sum.toString

def test = sum"${ List(42)* }" // error // error
def test = sum"${ List(42)* }" // error

2 changes: 1 addition & 1 deletion tests/neg/i18020.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ def foo3 =
val _root_ = "abc" // error

def foo1: Unit =
val _root_: String = "abc" // error // error
val _root_: String = "abc" // error
// _root_: is, technically, a legal name
// so then it tries to construct the infix op pattern
// "_root_ String .." and then throws in a null when it fails
Expand Down
4 changes: 2 additions & 2 deletions tests/neg/i4453.scala
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
class x0 { var x0 == _ * // error: _* can be used only for last argument // error: == cannot be used as an extractor in a pattern because it lacks an unapply or unapplySeq method
// error '=' expected, but eof found
class x0 { var x0 == _ * // error spread operator `*` not allowed here // error '=' expected
// error: == cannot be used as an extractor in a pattern because it lacks an unapply or unapplySeq method
26 changes: 19 additions & 7 deletions tests/neg/i5498-postfixOps.check
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,25 @@
| expression expected but end of statement found
|
| longer explanation available when compiling with `-explain`
-- [E018] Syntax Error: tests/neg/i5498-postfixOps.scala:6:37 ----------------------------------------------------------
6 | Seq(1, 2).filter(List(1,2) contains) // error: usage of postfix operator // error
| ^
| expression expected but ')' found
|
| longer explanation available when compiling with `-explain`
-- [E040] Syntax Error: tests/neg/i5498-postfixOps.scala:6:29 ----------------------------------------------------------
6 | Seq(1, 2).filter(List(1,2) contains) // error: usage of postfix operator // error // error (type error) // error (type error)
| ^^^^^^^^
| ')' expected, but identifier found
-- [E172] Type Error: tests/neg/i5498-postfixOps.scala:6:0 -------------------------------------------------------------
6 | Seq(1, 2).filter(List(1,2) contains) // error: usage of postfix operator // error
6 | Seq(1, 2).filter(List(1,2) contains) // error: usage of postfix operator // error // error (type error) // error (type error)
|^
|No given instance of type scala.concurrent.duration.DurationConversions.Classifier[Null] was found for parameter ev of method second in trait DurationConversions
-- [E007] Type Mismatch Error: tests/neg/i5498-postfixOps.scala:6:24 ---------------------------------------------------
6 | Seq(1, 2).filter(List(1,2) contains) // error: usage of postfix operator // error // error (type error) // error (type error)
| ^
| Found: (1 : Int)
| Required: Boolean
|
| longer explanation available when compiling with `-explain`
-- [E007] Type Mismatch Error: tests/neg/i5498-postfixOps.scala:6:26 ---------------------------------------------------
6 | Seq(1, 2).filter(List(1,2) contains) // error: usage of postfix operator // error // error (type error) // error (type error)
| ^
| Found: (2 : Int)
| Required: Boolean
|
| longer explanation available when compiling with `-explain`
2 changes: 1 addition & 1 deletion tests/neg/i5498-postfixOps.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@ import scala.concurrent.duration.*
def test() = {
1 second // error: usage of postfix operator

Seq(1, 2).filter(List(1,2) contains) // error: usage of postfix operator // error
Seq(1, 2).filter(List(1,2) contains) // error: usage of postfix operator // error // error (type error) // error (type error)
}
2 changes: 1 addition & 1 deletion tests/neg/i6059.scala
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
def I0(I1: Int ) = I1
val I1 = I0(I0 i2) => // error
val I1 = I0(I0 i2) => // error // error
true
8 changes: 3 additions & 5 deletions tests/neg/t11900.check
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,7 @@
52 | println("b"), // error: weird comma
| ^
| end of statement expected but ',' found
-- [E032] Syntax Error: tests/neg/t11900.scala:64:8 --------------------------------------------------------------------
-- Error: tests/neg/t11900.scala:64:7 ----------------------------------------------------------------------------------
64 | _*, // error
| ^
| pattern expected
|
| longer explanation available when compiling with `-explain`
| ^
| spread operator `*` not allowed here; must come last in a parameter list
12 changes: 6 additions & 6 deletions tests/neg/t1625.check
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
-- [E040] Syntax Error: tests/neg/t1625.scala:2:20 ---------------------------------------------------------------------
-- Error: tests/neg/t1625.scala:2:19 -----------------------------------------------------------------------------------
2 | def foo(x: String*, y: String*, c: String*): Int // error: an identifier expected, but ',' found // error: an identifier expected, but ',' found
| ^
| an identifier expected, but ',' found
-- [E040] Syntax Error: tests/neg/t1625.scala:2:32 ---------------------------------------------------------------------
| ^
| spread operator `*` not allowed here; must come last in a parameter list
-- Error: tests/neg/t1625.scala:2:31 -----------------------------------------------------------------------------------
2 | def foo(x: String*, y: String*, c: String*): Int // error: an identifier expected, but ',' found // error: an identifier expected, but ',' found
| ^
| an identifier expected, but ',' found
| ^
| spread operator `*` not allowed here; must come last in a parameter list
2 changes: 1 addition & 1 deletion tests/neg/t1625b.scala
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
object T5 {
case class Abc(x: String*, c: String*) // error: identifier expected but `,` found
case class Abc(x: String*, c: String*) // error: varargs parameter must come last
}
16 changes: 6 additions & 10 deletions tests/neg/t5702-neg-bad-and-wild.check
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,10 @@
13 | case List(1, _*3:) => // error // error
| ^
| an identifier expected, but ')' found
-- [E032] Syntax Error: tests/neg/t5702-neg-bad-and-wild.scala:15:18 ---------------------------------------------------
-- Error: tests/neg/t5702-neg-bad-and-wild.scala:15:17 -----------------------------------------------------------------
15 | case List(x*, 1) => // error: pattern expected
| ^
| pattern expected
|
| longer explanation available when compiling with `-explain`
| ^
| spread operator `*` not allowed here; must come last in a parameter list
-- Error: tests/neg/t5702-neg-bad-and-wild.scala:16:16 -----------------------------------------------------------------
16 | case (1, x*) => // error: bad use of *
| ^
Expand All @@ -30,12 +28,10 @@
| * can be used only for last argument
|
| longer explanation available when compiling with `-explain`
-- [E032] Syntax Error: tests/neg/t5702-neg-bad-and-wild.scala:23:17 ---------------------------------------------------
-- Error: tests/neg/t5702-neg-bad-and-wild.scala:23:16 -----------------------------------------------------------------
23 | val K(ns @ _*, xx) = k // error: pattern expected
| ^
| pattern expected
|
| longer explanation available when compiling with `-explain`
| ^
| spread operator `*` not allowed here; must come last in a parameter list
-- Error: tests/neg/t5702-neg-bad-and-wild.scala:25:14 -----------------------------------------------------------------
25 | val (b, _ * ) = (5,6) // error: bad use of `*`
| ^
Expand Down
8 changes: 3 additions & 5 deletions tests/neg/t5702-neg-bad-brace.check
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
-- [E032] Syntax Error: tests/neg/t5702-neg-bad-brace.scala:8:21 -------------------------------------------------------
-- Error: tests/neg/t5702-neg-bad-brace.scala:8:20 ---------------------------------------------------------------------
8 | case List(1, _*} => // error: pattern expected
| ^
| pattern expected
|
| longer explanation available when compiling with `-explain`
| ^
| spread operator `*` not allowed here; must come last in a parameter list
-- [E040] Syntax Error: tests/neg/t5702-neg-bad-brace.scala:11:0 -------------------------------------------------------
11 |} // error: eof expected, but '}' found
|^
Expand Down

0 comments on commit ffb2ab7

Please sign in to comment.