var yield = 5;

yield is lexed as an identifier, so this is a valid variable declaration

 var res = switch(yield) {

yield is lexed as an identifier, so this is a valid switch operand


    default -> yield + yield;

RHS of a single-consequence case is expression | block statement | throw.  We're not in a YieldStatement production, so this is an expression.

Like lambdas, we can think of this as shorthand for

    default -> {
        yield yield + yield;
    }

which parses as a yield statement whose operand is the expression yield+yield.


// are we
returning result of binary plus (10) or yielding result of unary plus
(5)? Seems the first one, yet confusing.

Tagir.

On Fri, May 24, 2019 at 4:30 AM Dan Smith <daniel.sm...@oracle.com> wrote:
On May 22, 2019, at 9:45 AM, Brian Goetz <brian.go...@oracle.com> wrote:

The “compromise” strategy is like the smart strategy, except that it trades 
fixed lookahead for missing a few more method invocation cases.  Here, we look 
at the tokens that follow the identifier yield, and use those to determine 
whether to classify yield as a keyword or identifier.  (We’d choose identifier 
if it is an assignment op (=, +=, etc), left-bracket, dot, and a few others, 
plus a few two-token sequences (e.g., ++ and then semicolon), which is 
lookahead(2).
The compromise strategy misses some cases we could parse unambiguously, but 
also offers a simpler user model: always qualify invocations of methods called 
yield when used as expression statements.  And it offers the better lookup 
behavior, which will make life easier for IDEs.
There's still some space for different design choices within the compromise 
strategy: what happens to names in contexts *other than* the start of a 
statement?

I think it's really helpful to split the question into three parts: variable 
names, type names, and method names.

1) Variable names: we've established that, with a fixed lookahead, every legal 
use of the variable name 'yield' can be properly interpreted. Great.

2) Type names: 'yield' might be used as the name of a class, type of a method 
parameter, type of a field, array component type, type of a 'final' local 
variable etc. Or we can prohibit it entirely as a type name.

We went through this when designing 'var', and settled on the more restrictive 
position: you can't declare classes/interfaces/type vars or make reference to 
types with name 'var', regardless of context. That way, there's no risk of 
confusion between subtly different programs—wherever you see 'var' used as a 
type, you know it can only mean the keyword.

I think it's best to treat 'yield' like 'var' in this case.

3) Method names: 'yield(' at the start of a statement means YieldStatement, but 
what about other contexts in which method invocations can appear?

Example:
var v = switch (x) {
     case 1 -> yield(x); // method call?
     default -> { yield(x); } // no-op, produces x (oops!)
};

Fortunately, the different normal-completion behavior of a method call and a 
yield statement will probably catch most errors of this form—when I type the 
braces above, I'll probably also try adding a statement after the attempted 
'yield' call, and the compiler will complain that the statement is unreachable. 
But it's all very subtle (not to mention painful for IDEs).

Taking inspiration from the treatment of type names, my preference here is to 
make a blanket restriction that's easy to visualize: an *unqualified* method 
invocation must not use the name 'yield'. Context is irrelevant. The workaround 
is always to add a qualifier.

(If, in the future, we introduce local methods or something similar that can't 
be qualified, we should not allow such methods to be named 'yield'.)

---

Are people generally good with my preferred restrictions, or do you think it's 
better to be more permissive?


Reply via email to