[patterns] Scoping

Tagir Valeev amaembo at gmail.com
Fri Aug 4 02:46:38 UTC 2017


I was thinking about scoping problem of the variable declared inside
pattern. It seems that the problem could be simplified if we think of it as
two separate operations: declaration of uninitialized variable and variable
assignment. Why it's simpler? Because, first, assignment already could be a
part of expression, so nothing new here. And second, javac already performs
a control-flow analysis to check whether a variable was initialized at
particular use location.

We can rewrite any "x matches Type t" expression in current Java via "(x
instanceof Type && _true(t = (Type)x)" where _true is a helper method:

static boolean _true(Object x) {return true;}

And the only question to discuss is where to place an uninitialized
declaration of t. I see two possible options:
Opt1. Place it right before the current statement like
Type t;
if((x instanceof Type && _true(t = (Type)x)) {...}

Opt2. Place it right before the current statement, but enclosing into the
new scope (so it's not declared after current statement):
  Type t;
  if((x instanceof Type && _true(t = (Type)x)) {...}

In any case you don't need complex rules about && or || or ?: or whatever.
They are already covered by javac behavior. Some examples follow.

// Test interfaces
    interface Node {}
    interface IntNode extends Node {int value();}

// && example
IntNode x;
if((n instanceof IntNode && _true(x = (IntNode) n)) &&
    x.value() > 5) { // ok
    System.out.println(x); // ok
} else {
    System.out.println(x); // error -- not initialized

// ! and || example
IntNode x;
if(!(n instanceof IntNode && _true(x = (IntNode) n)) ||
    x.value() > 5) { // negated pattern with || -- ok
    System.out.println(x); // error -- not initialized
} else {
    System.out.println(x); // ok

// ?: example
IntNode x;
if(!(n instanceof IntNode && _true(x = (IntNode) n)) ?
        x.value() > 5 : // error -- not initialized
        x.value() < 0) { // ok


// return example
IntNode x;
if(!(n instanceof IntNode && _true(x = (IntNode) n))) return;
System.out.println(x.value()); // ok if Opt1 is selected; cannot find
symbol if Opt2 is selected

// for example:
IntNode x;
for(int i = x.value(); // error -- not initialized
     (n instanceof IntNode && _true(x = (IntNode) n));
     i+=x.value()){ // ok
    System.out.println(x.value()); // ok
    n = next();
System.out.println(x.value()); // error -- not initialized if Opt1, cannot
find symbol if Opt2

This solution does not add non-contiguous scopes: scope is always
contiguous, though variable is not initialized in some places. Note that
this also fixes problem with shadowing quite reasonably: in both Opt1 and
Opt2 cases if we have if(n matches IntNode x) {...} else {...} we cannot
use x in else branch even if there's a field with the same name in the
scope. The question is only whether we can continue using x after current
statement, but to my opinion either solution could be good. Opt1 is less
restrictive (see return example), but Opt2 is closer to what we have in
Java now (e.g. when variable is declared in for initializer, it's not
visible after for). Finally if we want to reuse the variable in several
places within single statement, fine then. As long as type is the same we
reuse the declaration and assign it in several points.

What do you think?

With best regards,
Tagir Valeev

More information about the amber-dev mailing list