> Before we get to the harder ones, can people share what made them 
> uncomfortable, for the ones where you marked "not comfortable with the 
> answer”?

We’re currently exploring the consequences of Doug’s query about introducing 
non-conditional locals into expressions (not necessarily because we want to 
have such a feature now, but because if we did want such a feature, we’d want 
the scoping machinery to handle it well, and if it doesn’t, that may suggest 
refinements for the scoping machinery.)  

Let’s talk about the more complicated examples in the quiz.  

Everyone seemed to do pretty well on the if/else ones (flip the condition, and 
it flips the scopes), the conditional-expression ones, and  the 
short-circuiting examples.  Let’s talk about merging.  

In this example:

```
public void test() {
    if (a instanceof String v || b instanceof String v) { 
        println(v.length());
    }
    else {
        println("foo");
    }
}
```

we have two candidate conditional variables called `v` of type `String`, and 
the flow rules ensure that at any point, at most one of them could be DA.  So 
in the `true` arm, the use of `v` is OK, and it binds to whichever of these is 
DA (both must have the same type, otherwise its an error.)  The rules sound 
complicated, but as always, our DA intuition guides us pretty well — we can’t 
get to the `true` arm unless exactly one of the `instanceof` expressions has 
matched.  

In the `&&` version of this example, people’s intuition was mostly right — that 
this code is illegal — but some were unsure: 

```
public void test() {
    if (a instanceof String v && b instanceof String v) { 
        println(v.length());
    }
    else {
        println("foo");
    }
}
```

The answer here is that because `v` is not DU in the second `instanceof`, and 
therefore might be in scope, this constitutes an illegal shadowing, and the 
compiler rejects it.

The hard ones (and the potentially controversial ones) were the last two — 
regarding use _after_ the declaring statement:

```
public void test() {
    if (!(a instanceof String v)) 
        throw new NotAStringException("a");
    if (v.length() == 0)
        throw new EmptyStringException();
        
    println(v.length());
}
```

```
public void test() {
    if (!(a instanceof String v)) 
        return;
        
    println(v.length());
}
```

In both of these cases, flow scoping allows `v` to be in scope in the last line 
— because `v` is DA at the point of use.  And this is because if the 
`insteanceof` does not match, control does not make it to the use.  In other 
words, we can use the full flow analysis — including reachability — to conclude 
the only way we could reach the use is if the binding is defined.  (If there 
was not a return or a throw, then the last use would not be in scope.)  

For some, this is uncomfortable at first, as not only does the scope bleed into 
the full `if` statement, but it bleeds out of it too.  But again, we’re driven 
by two goals:

 - Make scoping line up with DA-ness (so we can build on an existing mechanism 
rather than creating a new one)
 - Be friendly to refactoring.  

In the latter point, I’d like for

    if (E) { throw; } else { s }

to be refactorable to

    if (E) throw; s;

as it is common that users will perform precondition checking on entry to a 
method, throw if there’s a failure, and then “fall” into the main body of the 
method:

    if (!(a instanceof String s))
        throw new IllegalARgumentException(a.toString());

    // use s here

The main argument against supporting this is discomfort; it feels new and 
different.  (But, because we’re building on DA, it’s not, really.)

Reply via email to