[ 
https://issues.apache.org/jira/browse/GROOVY-12695?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18138403#comment-18138403
 ] 

ASF GitHub Bot commented on GROOVY-12695:
-----------------------------------------

codeconsole opened a new pull request, #3216:
URL: https://github.com/apache/groovy/pull/3216

   Declaring a variable named after a primitive type keyword is rejected, but 
the diagnostic depends on which declaration keyword was used and neither 
variant says what is wrong. `var long = 1` and `val long = 1` report 
ResolveVisitor's "you tried to assign a value to the class 'long'": `var` and 
`val` are soft keywords, so once the declaration fails to parse the parser 
accepts the statement as the command expression `var(long = 1)`, AstBuilder 
builds an assignment to the built-in type name, and the resolve phase complains 
about assigning to a class. `def long = 1` and `final long = 1` report 
"Unexpected input: 'def long ='" with the caret on the `=`: `def` and `final` 
are reserved, the parser commits to a declaration, consumes `long` as the 
declared type and fails one token past the mistake. The same two shapes cover 
the other primitive names and `void`, typed declarations such as `String long = 
"x"` or a field `int long = 1`, and a bare `long = 1`.
   
   All of these now report `'long' is a keyword and cannot be used as a 
variable name` with the caret on the keyword.
   
   `AstBuilder.visitAssignmentExprAlt` rejects an assignment whose left-hand 
side is a built-in type name (the `IS_BUILT_IN_TYPE` variable expression that 
`builtInType` produces) before the resolve phase sees it. That covers 
`var`/`val`, typed declarations in scripts and blocks, declarator lists such as 
`var a = 1, long = 2`, and bare or compound assignments to the keyword. The 
input was always an error, so no valid program changes; `var long` without an 
initializer still parses as a call to a method named `var`.
   
   `AbstractFriendlyErrorStrategy` gets `keywordAsVariableName`, an 
error-path-only lookup that runs after `MissingDelimiterDiagnostic` and before 
the sentence-only refinements, and that, like the delimiter diagnostic, moves 
the caret: back onto the keyword when ANTLR consumed it as the declared type 
and failed on the `=`, `,`, `;`, `)` or `}` after it (the keyword must follow a 
variable modifier, or the parser must have been inside `identifier`, which is 
the shape of the failure inside a block and in `def (a, long) = ...`), or on 
the keyword itself when it is the offender after a primitive type or an array 
dimension (`int long = 1` in a class body) or after a `,` where only a name was 
accepted (`def a, long = 2`). A keyword followed by `(` is a method name and 
keeps the generic sentence, an enum constant list keeps the `';' expected after 
the last enum constant` sentence (its expected set also accepts a type), and 
`def long` at end of input stays "Unexpected end of input". The lookup reads 
the token after the offender through `TokenStream.LT`, because prediction stops 
at the offender and the buffer may end there; the scan is bounded and wrapped 
in the same defensive try as the other refinements. No grammar change, so 
successful parses are unaffected (GROOVY-9588).
   
   Tests: `ParserNegativeSyntaxTest` pins the exact message and caret for the 
four reported forms and checks the sibling forms (other primitives, `void`, 
bare and compound assignment, declarator lists, block, field, multi-assignment 
pairs) plus the cases that must keep their own diagnostic (`int long()`, enum 
constant list, `import long`, `def long` at EOF); `ErrorStrategyTest` 
unit-tests the lookup over real token streams, including the stream-failure and 
start-of-stream guards. `ParserNegativeSyntaxTest` was run red before the fix 
(5 failures) and green after; `ErrorStrategyTest`, `ParserNegativeSyntaxTest` 
and the rest of `org.apache.groovy.parser.antlr4` pass with the fix; 
`checkstyleMain` reports nothing new for the changed sources.
   
   Suggested backport: GROOVY_6_0_X (the behaviour is the same on 6.0.0, 
6.0.1-SNAPSHOT and master).
   




> Primitive type keyword used as a variable name gets inconsistent and 
> misleading diagnostics
> -------------------------------------------------------------------------------------------
>
>                 Key: GROOVY-12695
>                 URL: https://issues.apache.org/jira/browse/GROOVY-12695
>             Project: Groovy
>          Issue Type: Bug
>          Components: parser
>    Affects Versions: 6.0.0
>            Reporter: Scott Murphy Heiberg
>            Priority: Major
>
> *Environment:* macOS 26.5, BellSoft Liberica JDK 21.0.12; reproduced with the 
> {{groovy}} command of each distribution
> h3. Problem
> Declaring a local variable named after a primitive type keyword is rejected 
> (as it should be), but the message depends on which declaration keyword was 
> used, and neither message says what is wrong.
> {code}
> var long = 1
> val long = 1
> def long = 1
> final long = 1
> {code}
> h3. Actual
> {{var}} and {{val}} report a resolve-phase message that talks about assigning 
> to a class, with the caret on the keyword:
> {noformat}
> var_long.groovy: 1: you tried to assign a value to the class 'long'
>  @ line 1, column 5.
>    var long = 1
>        ^
> {noformat}
> {noformat}
> val_long.groovy: 1: you tried to assign a value to the class 'long'
>  @ line 1, column 5.
>    val long = 1
>        ^
> {noformat}
> {{def}} and {{final}} report a generic syntax error with the caret on the 
> {{=}}, one token past the mistake:
> {noformat}
> def_long.groovy: 1: Unexpected input: 'def long =' @ line 1, column 10.
>    def long = 1
>             ^
> {noformat}
> {noformat}
> final_long.groovy: 1: Unexpected input: 'final long =' @ line 1, column 12.
>    final long = 1
>               ^
> {noformat}
> Other primitive names and {{void}} behave the same way ({{var int = 1}} 
> reports {{you tried to assign a value to the class 'int'}}, {{def int = 1}} 
> reports {{Unexpected input: 'def int ='}}). Inside a method body {{def long = 
> 1}} reports {{Unexpected input: '='}} on the {{=}}. The same two shapes also 
> appear for a typed declaration ({{String long = "x"}} and {{int long = 1}} 
> report the class message, a field {{class C { int long = 1 }}} reports 
> {{Unexpected input: 'int long'}}) and for a bare {{long = 1}}.
> h3. Cause
> {{var}} and {{val}} are soft keywords, so when {{var long = 1}} fails to 
> parse as a declaration the parser accepts it as the command expression 
> {{var(long = 1)}}; {{AstBuilder}} builds an assignment whose left-hand side 
> is the built-in type name, and {{ResolveVisitor}} later reports it as an 
> assignment to a class. ({{var long}} without an initializer actually runs, as 
> a call to a method named {{var}}.) {{def}} and {{final}} are reserved, so the 
> parser commits to a declaration, consumes {{long}} as the declared type and 
> fails on the {{=}} with the generic {{Unexpected input}} sentence.
> h3. Expected
> One diagnostic for every form, naming the keyword, with the caret on the 
> keyword:
> {noformat}
> 'long' is a keyword and cannot be used as a variable name @ line 1, column 5.
>    var long = 1
>        ^
> {noformat}
> This is in line with the other developer-facing parser sentences added in 
> GROOVY-12353 and GROOVY-12401.
> A pull request with regression tests follows.
> h3. Versions
> Reproduced on 6.0.0, 6.0.1-SNAPSHOT (GROOVY_6_0_X at 7c0dcf17dd) and master 
> (d86ea7eb85).



--
This message was sent by Atlassian Jira
(v10.3.26#10030026)

Reply via email to