Effectively final local variable cannot be declared as final

Maurizio Cimadamore maurizio.cimadamore at oracle.com
Tue Sep 27 21:25:29 UTC 2022


Hi Tagir,
I believe this is a bug in how the compiler determines effective 
finality. Javac does this while computing DA/DU. This is because locals 
that are assigned "at most once" are considered effectively final (as 
per the JLS).

In the case of for loop, the DA/DU logic is tripping us up: when we see 
the initializer of "i" in the for loop, we know that "i" is DA and not 
DU. But then we scan the loop body, and there's a break. And, per JLS, 
after a break, any variable is both DA and DU. In other words, the DA/DU 
status is reset.

That means that, by the time we reach "i++", javac thinks it's an 
assignment to a variable that is still DU, so the assignment will not 
make the variable lose its effectively final status.

In a way, what the compiler does is correct - the variable is assigned 
only once, and can only have value 0, but I agree that there's an 
inconsistency with the JLS and, more broadly, with what happens if you 
add an explicit "final". There is also some code in Flow to deal with 
assignment of "final" variables in loop, for instance in cases like this:

```
final int x;
for (int i = 0; i < 10 ; i++) {
    x = 2; // error: variable might be assigned in loop
}
```

And the logic is exactly the same as the one we use for effectively 
final. But variables declared as `final` also gets an extra check in an 
earlier compiler pass (Attr), which simply looks at assignments, and if 
the target of the assignment is final, then it reports an error (and 
that's where the discrepancy comes from).

It looks like the javac logic based on DA/DU should be dialled back a 
bit to only be applied in cases when the variable doesn't have an 
initializer.

Thanks
Maurizio


On 27/09/2022 11:03, Tagir Valeev wrote:
> Hello!
>
> Consider the following code:
>
> public class Demo {
>      public static void main(String[] args) {
>          for (int i = 0; i < 10; i++) {
>              Runnable r = () -> System.out.println(i);
>              r.run();
>              break;
>          }
>      }
> }
>
> It can be compiled with javac (version 19), successfully executed and
> prints 0. As only effectively final locals are allowed to be captured
> in lambda, I conclude that `i` is effectively final. I also assumed
> that for every effectively final variable, it's possible to write an
> explicit `final` modifier. However, this is not the case. The
> following code fails with the compilation error:
>
> public class Demo {
>      public static void main(String[] args) {
>          for (final int i = 0; i < 10; i++) {
>              Runnable r = () -> System.out.println(i);
>              r.run();
>              break;
>          }
>      }
> }
> Demo.java:3: error: cannot assign a value to final variable i
>          for (final int i = 0; i < 10; i++) {
>                                        ^
> 1 error
> error: compilation failed
>
> In my opinion, either both should be accepted or both should be
> rejected. According to JLS 4.12.4, I'm inclined that both should be
> rejected:
>
> A local variable whose declarator has an initializer (§14.4.2) is
> effectively final if all of the following are true:
> - It is not declared final.
> - It never occurs as the left hand side in an assignment expression
> (§15.26). (Note that the local variable declarator containing the
> initializer is not an assignment expression.)
> - It never occurs as the operand of a prefix or postfix increment or
> decrement operator (§15.14, §15.15).
>
> Here, `i` has an initializer and also occurs as the operand of prefix
> increment, so it cannot be effectively final.
>
> What do you think?
>
> With best regards,
> Tagir Valeev.


More information about the compiler-dev mailing list