From 9749b66c1e21aa3646b5e89427f1b177ea7d15b9 Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Sun, 23 Jun 2024 01:00:41 +0200 Subject: [PATCH 1/3] Classify an id as infix operator only if following can start an operand Also, detect and report spread operators in illegal positions [Cherry-picked ffb2ab753506bef930820f01757a5e18a1ca59bb][modified] --- .../dotty/tools/dotc/parsing/Parsers.scala | 24 ++++++++++++++--- .../src/dotty/tools/dotc/parsing/Tokens.scala | 11 +++++--- tests/neg/cc-only-defs.scala | 4 +-- tests/neg/i14564.check | 21 +++------------ tests/neg/i14564.scala | 2 +- tests/neg/i4453.scala | 4 +-- tests/neg/i5498-postfixOps.check | 26 ++++++++++++++----- tests/neg/i5498-postfixOps.scala | 2 +- tests/neg/i6059.scala | 2 +- tests/neg/t11900.check | 8 +++--- tests/neg/t1625.check | 12 ++++----- tests/neg/t1625b.scala | 2 +- tests/neg/t5702-neg-bad-and-wild.check | 16 +++++------- tests/neg/t5702-neg-bad-brace.check | 8 +++--- 14 files changed, 77 insertions(+), 65 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index d197f40f889a..d4afd493a4f5 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -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 @@ -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 ------------------------------------------- */ @@ -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] */ @@ -2413,7 +2427,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) @@ -2920,7 +2935,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 diff --git a/compiler/src/dotty/tools/dotc/parsing/Tokens.scala b/compiler/src/dotty/tools/dotc/parsing/Tokens.scala index dba0ad3fa2ee..8a20009f1927 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Tokens.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Tokens.scala @@ -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) @@ -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) diff --git a/tests/neg/cc-only-defs.scala b/tests/neg/cc-only-defs.scala index 43ac025f203a..a87978b41881 100644 --- a/tests/neg/cc-only-defs.scala +++ b/tests/neg/cc-only-defs.scala @@ -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 +} diff --git a/tests/neg/i14564.check b/tests/neg/i14564.check index 2fb320761e1a..01b222361653 100644 --- a/tests/neg/i14564.check +++ b/tests/neg/i14564.check @@ -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 diff --git a/tests/neg/i14564.scala b/tests/neg/i14564.scala index c8d8c369575e..cdc989554254 100644 --- a/tests/neg/i14564.scala +++ b/tests/neg/i14564.scala @@ -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 diff --git a/tests/neg/i4453.scala b/tests/neg/i4453.scala index cc339f83b35b..41f7dab62e1a 100644 --- a/tests/neg/i4453.scala +++ b/tests/neg/i4453.scala @@ -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 \ No newline at end of file +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 \ No newline at end of file diff --git a/tests/neg/i5498-postfixOps.check b/tests/neg/i5498-postfixOps.check index d41862364270..d43e5ff9f491 100644 --- a/tests/neg/i5498-postfixOps.check +++ b/tests/neg/i5498-postfixOps.check @@ -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` diff --git a/tests/neg/i5498-postfixOps.scala b/tests/neg/i5498-postfixOps.scala index 6dd89517bf52..3b95603f7961 100644 --- a/tests/neg/i5498-postfixOps.scala +++ b/tests/neg/i5498-postfixOps.scala @@ -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) } diff --git a/tests/neg/i6059.scala b/tests/neg/i6059.scala index 375c99fb6ba0..d6f28b0c2574 100644 --- a/tests/neg/i6059.scala +++ b/tests/neg/i6059.scala @@ -1,3 +1,3 @@ def I0(I1: Int ) = I1 -val I1 = I0(I0 i2) => // error +val I1 = I0(I0 i2) => // error // error true diff --git a/tests/neg/t11900.check b/tests/neg/t11900.check index 531a1b8417fd..a18cb16c4ddb 100644 --- a/tests/neg/t11900.check +++ b/tests/neg/t11900.check @@ -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` \ No newline at end of file + | ^ + | spread operator `*` not allowed here; must come last in a parameter list diff --git a/tests/neg/t1625.check b/tests/neg/t1625.check index 7e31f49f3729..05ef10b8780d 100644 --- a/tests/neg/t1625.check +++ b/tests/neg/t1625.check @@ -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 diff --git a/tests/neg/t1625b.scala b/tests/neg/t1625b.scala index 4c4633c62d23..5382156f812f 100644 --- a/tests/neg/t1625b.scala +++ b/tests/neg/t1625b.scala @@ -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 } diff --git a/tests/neg/t5702-neg-bad-and-wild.check b/tests/neg/t5702-neg-bad-and-wild.check index 876a3a39b308..261b2a9e44d1 100644 --- a/tests/neg/t5702-neg-bad-and-wild.check +++ b/tests/neg/t5702-neg-bad-and-wild.check @@ -14,24 +14,20 @@ 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 -- [E031] Syntax Error: tests/neg/t5702-neg-bad-and-wild.scala:17:18 --------------------------------------------------- 17 | case (1, x: _*) => // error: bad use of _* (sequence pattern not allowed) | ^ | * 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 -- [E161] Naming Error: tests/neg/t5702-neg-bad-and-wild.scala:24:10 --------------------------------------------------- 24 | val K(x) = k // error: x is already defined as value x | ^^^^^^^^^^^^ diff --git a/tests/neg/t5702-neg-bad-brace.check b/tests/neg/t5702-neg-bad-brace.check index 92e9fe912a92..d305e9272a6e 100644 --- a/tests/neg/t5702-neg-bad-brace.check +++ b/tests/neg/t5702-neg-bad-brace.check @@ -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 |^ From 30cd1f2588b9ab421bcb54ac7cd09334456b22a3 Mon Sep 17 00:00:00 2001 From: odersky Date: Sun, 29 Oct 2023 15:35:22 +0100 Subject: [PATCH 2/3] Mention `,` in addition to `)` in error messages [Cherry-picked 45fc82a50fb34dc90795da7b13b476cb913f5fb4] --- .../dotty/tools/dotc/parsing/Parsers.scala | 56 +++++++++++++------ .../dotty/tools/dotc/reporting/messages.scala | 4 +- tests/neg/i18734.check | 28 ++++++++++ tests/neg/i18734.scala | 25 +++++++++ tests/neg/i5498-postfixOps.check | 2 +- tests/neg/syntax-error-recovery.check | 4 +- 6 files changed, 96 insertions(+), 23 deletions(-) create mode 100644 tests/neg/i18734.check create mode 100644 tests/neg/i18734.scala diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index d4afd493a4f5..d9d40e2b843c 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -555,10 +555,29 @@ object Parsers { accept(tok) try body finally accept(tok + 1) + /** Same as enclosed, but if closing token is missing, add `,` to the expected tokens + * in the error message provided the next token could have followed a `,`. + */ + def enclosedWithCommas[T](tok: Token, body: => T): T = + accept(tok) + val closing = tok + 1 + val isEmpty = in.token == closing + val ts = body + if in.token != closing then + val followComma = + if tok == LPAREN then canStartExprTokens3 else canStartTypeTokens + val prefix = if !isEmpty && followComma.contains(in.token) then "',' or " else "" + syntaxErrorOrIncomplete(ExpectedTokenButFound(closing, in.token, prefix)) + if in.token == closing then in.nextToken() + ts + def inParens[T](body: => T): T = enclosed(LPAREN, body) def inBraces[T](body: => T): T = enclosed(LBRACE, body) def inBrackets[T](body: => T): T = enclosed(LBRACKET, body) + def inParensWithCommas[T](body: => T): T = enclosedWithCommas(LPAREN, body) + def inBracketsWithCommas[T](body: => T): T = enclosedWithCommas(LBRACKET, body) + def inBracesOrIndented[T](body: => T, rewriteWithColon: Boolean = false): T = if in.token == INDENT then val rewriteToBraces = in.rewriteNoIndent @@ -1672,7 +1691,7 @@ object Parsers { /** FunParamClause ::= ‘(’ TypedFunParam {‘,’ TypedFunParam } ‘)’ */ def funParamClause(): List[ValDef] = - inParens(commaSeparated(() => typedFunParam(in.offset, ident()))) + inParensWithCommas(commaSeparated(() => typedFunParam(in.offset, ident()))) def funParamClauses(): List[List[ValDef]] = if in.token == LPAREN then funParamClause() :: funParamClauses() else Nil @@ -1821,7 +1840,7 @@ object Parsers { else def singletonArgs(t: Tree): Tree = if in.token == LPAREN && in.featureEnabled(Feature.dependent) - then singletonArgs(AppliedTypeTree(t, inParens(commaSeparated(singleton)))) + then singletonArgs(AppliedTypeTree(t, inParensWithCommas(commaSeparated(singleton)))) else t singletonArgs(simpleType1()) @@ -1837,7 +1856,7 @@ object Parsers { def simpleType1() = simpleTypeRest { if in.token == LPAREN then atSpan(in.offset) { - makeTupleOrParens(inParens(argTypes(namedOK = false, wildOK = true))) + makeTupleOrParens(inParensWithCommas(argTypes(namedOK = false, wildOK = true))) } else if in.token == LBRACE then atSpan(in.offset) { RefinedTypeTree(EmptyTree, refinement(indentOK = false)) } @@ -1990,7 +2009,8 @@ object Parsers { /** TypeArgs ::= `[' Type {`,' Type} `]' * NamedTypeArgs ::= `[' NamedTypeArg {`,' NamedTypeArg} `]' */ - def typeArgs(namedOK: Boolean, wildOK: Boolean): List[Tree] = inBrackets(argTypes(namedOK, wildOK)) + def typeArgs(namedOK: Boolean, wildOK: Boolean): List[Tree] = + inBracketsWithCommas(argTypes(namedOK, wildOK)) /** Refinement ::= `{' RefineStatSeq `}' */ @@ -2487,7 +2507,7 @@ object Parsers { placeholderParams = param :: placeholderParams atSpan(start) { Ident(pname) } case LPAREN => - atSpan(in.offset) { makeTupleOrParens(inParens(exprsInParensOrBindings())) } + atSpan(in.offset) { makeTupleOrParens(inParensWithCommas(exprsInParensOrBindings())) } case LBRACE | INDENT => canApply = false blockExpr() @@ -2592,15 +2612,15 @@ object Parsers { /** ParArgumentExprs ::= `(' [‘using’] [ExprsInParens] `)' * | `(' [ExprsInParens `,'] PostfixExpr `*' ')' */ - def parArgumentExprs(): (List[Tree], Boolean) = inParens { - if in.token == RPAREN then - (Nil, false) - else if isIdent(nme.using) then - in.nextToken() - (commaSeparated(argumentExpr), true) - else - (commaSeparated(argumentExpr), false) - } + def parArgumentExprs(): (List[Tree], Boolean) = + inParensWithCommas: + if in.token == RPAREN then + (Nil, false) + else if isIdent(nme.using) then + in.nextToken() + (commaSeparated(argumentExpr), true) + else + (commaSeparated(argumentExpr), false) /** ArgumentExprs ::= ParArgumentExprs * | [nl] BlockExpr @@ -2957,7 +2977,7 @@ object Parsers { case USCORE => wildcardIdent() case LPAREN => - atSpan(in.offset) { makeTupleOrParens(inParens(patternsOpt())) } + atSpan(in.offset) { makeTupleOrParens(inParensWithCommas(patternsOpt())) } case QUOTE => simpleExpr(Location.InPattern) case XMLSTART => @@ -3003,7 +3023,7 @@ object Parsers { * | ‘(’ [Patterns ‘,’] PatVar ‘*’ ‘)’ */ def argumentPatterns(): List[Tree] = - inParens(patternsOpt(Location.InPatternArgs)) + inParensWithCommas(patternsOpt(Location.InPatternArgs)) /* -------- MODIFIERS and ANNOTATIONS ------------------------------------------- */ @@ -3192,7 +3212,7 @@ object Parsers { * HkTypeParamClause ::= ‘[’ HkTypeParam {‘,’ HkTypeParam} ‘]’ * HkTypeParam ::= {Annotation} [‘+’ | ‘-’] (id [HkTypePamClause] | ‘_’) TypeBounds */ - def typeParamClause(ownerKind: ParamOwner): List[TypeDef] = inBrackets { + def typeParamClause(ownerKind: ParamOwner): List[TypeDef] = inBracketsWithCommas { def checkVarianceOK(): Boolean = val ok = ownerKind != ParamOwner.Def && ownerKind != ParamOwner.TypeParam @@ -3331,7 +3351,7 @@ object Parsers { } // begin termParamClause - inParens { + inParensWithCommas { if in.token == RPAREN && !prefix && !impliedMods.is(Given) then Nil else val clause = diff --git a/compiler/src/dotty/tools/dotc/reporting/messages.scala b/compiler/src/dotty/tools/dotc/reporting/messages.scala index 8ea18f7bdfd0..2543a3aee904 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -1169,7 +1169,7 @@ extends ReferenceMsg(ForwardReferenceExtendsOverDefinitionID) { |""" } -class ExpectedTokenButFound(expected: Token, found: Token)(using Context) +class ExpectedTokenButFound(expected: Token, found: Token, prefix: String = "")(using Context) extends SyntaxMsg(ExpectedTokenButFoundID) { private def foundText = Tokens.showToken(found) @@ -1178,7 +1178,7 @@ extends SyntaxMsg(ExpectedTokenButFoundID) { val expectedText = if (Tokens.isIdentifier(expected)) "an identifier" else Tokens.showToken(expected) - i"""${expectedText} expected, but ${foundText} found""" + i"""$prefix$expectedText expected, but $foundText found""" def explain(using Context) = if (Tokens.isIdentifier(expected) && Tokens.isKeyword(found)) diff --git a/tests/neg/i18734.check b/tests/neg/i18734.check new file mode 100644 index 000000000000..bb348025cf73 --- /dev/null +++ b/tests/neg/i18734.check @@ -0,0 +1,28 @@ +-- [E040] Syntax Error: tests/neg/i18734.scala:7:8 --------------------------------------------------------------------- +7 | Foo(1 2) // error + | ^ + | ',' or ')' expected, but integer literal found +-- [E040] Syntax Error: tests/neg/i18734.scala:9:8 --------------------------------------------------------------------- +9 | Foo(x y) // error + | ^ + | ',' or ')' expected, but identifier found +-- [E040] Syntax Error: tests/neg/i18734.scala:11:8 -------------------------------------------------------------------- +11 | Foo(1 b = 2) // error + | ^ + | ',' or ')' expected, but identifier found +-- [E040] Syntax Error: tests/neg/i18734.scala:16:4 -------------------------------------------------------------------- +16 | b = 2 // error + | ^ + | ',' or ')' expected, but identifier found +-- [E040] Syntax Error: tests/neg/i18734.scala:19:32 ------------------------------------------------------------------- +19 | val f: (Int, Int) => Int = (x y) => x + y // error + | ^ + | ',' or ')' expected, but identifier found +-- [E040] Syntax Error: tests/neg/i18734.scala:23:10 ------------------------------------------------------------------- +23 | bar[Int String](1 2) // error // error + | ^^^^^^ + | ',' or ']' expected, but identifier found +-- [E040] Syntax Error: tests/neg/i18734.scala:23:20 ------------------------------------------------------------------- +23 | bar[Int String](1 2) // error // error + | ^ + | ',' or ')' expected, but integer literal found diff --git a/tests/neg/i18734.scala b/tests/neg/i18734.scala new file mode 100644 index 000000000000..b658ef9f94d3 --- /dev/null +++ b/tests/neg/i18734.scala @@ -0,0 +1,25 @@ +case class Foo(a: Int, b: Int) + +object Bar: + val x = 1 + val y = 2 + + Foo(1 2) // error + + Foo(x y) // error + + Foo(1 b = 2) // error + + // Or + Foo( + a = 1 + b = 2 // error + ) + + val f: (Int, Int) => Int = (x y) => x + y // error + + def bar[X, Y](x: X, y: Y) = ??? + + bar[Int String](1 2) // error // error + + diff --git a/tests/neg/i5498-postfixOps.check b/tests/neg/i5498-postfixOps.check index d43e5ff9f491..21c8d33380ed 100644 --- a/tests/neg/i5498-postfixOps.check +++ b/tests/neg/i5498-postfixOps.check @@ -7,7 +7,7 @@ -- [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 + | ',' or ')' 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 // error (type error) // error (type error) |^ diff --git a/tests/neg/syntax-error-recovery.check b/tests/neg/syntax-error-recovery.check index 8034aeb556c7..0cf550c74d09 100644 --- a/tests/neg/syntax-error-recovery.check +++ b/tests/neg/syntax-error-recovery.check @@ -9,7 +9,7 @@ -- [E040] Syntax Error: tests/neg/syntax-error-recovery.scala:19:4 ----------------------------------------------------- 19 | if x == 0 then println(bar) // error | ^^ - | ')' expected, but 'if' found + | ',' or ')' expected, but 'if' found -- [E040] Syntax Error: tests/neg/syntax-error-recovery.scala:23:12 ---------------------------------------------------- 23 | if x < 0) then // error | ^ @@ -25,7 +25,7 @@ -- [E040] Syntax Error: tests/neg/syntax-error-recovery.scala:48:4 ----------------------------------------------------- 48 | if x == 0 then println(bar) // error | ^^ - | ')' expected, but 'if' found + | ',' or ')' expected, but 'if' found -- [E040] Syntax Error: tests/neg/syntax-error-recovery.scala:52:12 ---------------------------------------------------- 52 | if x < 0) then // error | ^ From a96e975aedc23db513bedf985b528eb2334c2b13 Mon Sep 17 00:00:00 2001 From: odersky Date: Sun, 29 Oct 2023 17:29:40 +0100 Subject: [PATCH 3/3] Override operator detection logic in interactive mode In interactive mode the token after an operator might not yet exist, so we should disregard that info when deciding whether we see an infix operator or not. [Cherry-picked fca7d0611058f2e9368ee688c4b7fe084b913e64] --- compiler/src/dotty/tools/dotc/parsing/Parsers.scala | 2 ++ 1 file changed, 2 insertions(+) diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index d9d40e2b843c..cfc1368f5758 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -997,6 +997,8 @@ object Parsers { leadingOperandTokens.contains(in.lookahead.token) || in.postfixOpsEnabled || in.lookahead.token == COLONop + || in.lookahead.token == EOF // important for REPL completions + || ctx.mode.is(Mode.Interactive) // in interactive mode the next tokens might be missing /* --------- OPERAND/OPERATOR STACK --------------------------------------- */