The compiler behavior seems to be in sync with the spec:

From [1]:

"Any variable that is used but not declared in the guarding expression of a guarded pattern must either be final or effectively final (4.12.4)."

And, from [2]:

"Any variable that is used but not declared in a |when| expression must be either final or effectively final (4.12.4 <https://docs.oracle.com/javase/specs/jls/se18/html/jls-4.html#jls-4.12.4>)"

As for your question of "why doesn't it work", I think it can be decomposed into two questions:

* why is a "when" expression restricted to only mention final/effectively-final variables? * even under the constraint of final/effectively final, why isn't "when" allowed to "initialize" a final variable?

Both of these add some asymmetries when it comes to refactoring the switch into a chain of if/else.

Maurizio

[1]: https://docs.oracle.com/javase/specs/jls/se18/preview/specs/patterns-switch-jls.html#jls-6.3.3.1 [2]: http://cr.openjdk.java.net/~gbierman/PatternSwitchPlusRecordPatterns/PatternSwitchPlusRecordPatterns-20220407/specs/patterns-switch-jls.html

On 21/05/2022 12:55, Remi Forax wrote:
Not sure if it's an implementation bug (bad error message from the compiler) or 
a spec bug,
hence this message to both amber-dev and amber-spec-experts.

If i try to compile this code with Java 19 (which currently still uses && 
instead of when for a guard)

interface reverse_polish_notation {
   static Map<String, IntBinaryOperator> OPS =
       Map.of("+", (a, b) -> a + b, "*", (a, b) -> a * b);

   static int eval(List<String> expr) {
     var stack = new ArrayDeque<Integer>();
     for(var token: expr) {
       final IntBinaryOperator op;
       stack.push(switch (token) {
         case String __ && (op = OPS.get(token)) != null -> {
           var value1 = stack.pop();
           var value2 = stack.pop();
           yield op.applyAsInt(value1, value2);
         }
         default -> Integer.parseInt(token);
       });
     }
     return stack.pop();
   }

   static void main(String[] args) {
     var expr = List.of("1",  "2",  "+", "3", "*", "4");
     System.out.println(eval(expr));
   }
}

I get the following error

java --enable-preview --source 19 reverse_polish_notation.java
reverse_polish_notation.java:17: error: local variables referenced from a guard 
must be final or effectively final
         case String __ && (op = OPS.get(token)) != null -> {
                            ^
Note: reverse_polish_notation.java uses preview features of Java SE 19.
Note: Recompile with -Xlint:preview for details.
1 error
error: compilation failed
Obviously the error message is funny, IntBinaryOperator is declared final so it is effectively final.

In case of a lambda,
   final IntBinaryOperator op;
   Supplier<String> supplier = () -> op = null;

supplier.get() can be called several times so "op = null" does not compile.

But in the example above, "op" can not be assigned more than once so maybe it 
should compile.

regards,
Rémi

Reply via email to