Yield as contextual keyword (was: Call for bikeshed -- break replacement in expression switch)
Manoj Palat
manoj.palat at in.ibm.com
Mon May 20 14:47:57 UTC 2019
I would vote for option E - a real keyword : break-with.
Regards,
Manoj
From: Guy Steele <guy.steele at oracle.com>
To: Brian Goetz <brian.goetz at oracle.com>
Cc: amber-spec-experts <amber-spec-experts at openjdk.java.net>
Date: 05/18/2019 12:11 AM
Subject: [EXTERNAL] Re: Yield as contextual keyword (was: Call for
bikeshed -- break replacement in expression switch)
Sent by: "amber-spec-experts"
<amber-spec-experts-bounces at openjdk.java.net>
I (somewhat reluctantly, but with an appreciation for the pragmatics of the
situation) support option B.
—Guy
On May 17, 2019, at 12:57 PM, Brian Goetz <brian.goetz at oracle.com>
wrote:
As was pointed out in Keyword Management for the Java Language (
https://openjdk.java.net/jeps/8223002), contextual keywords are a
compromise, and their compromises vary by lexical position. Let’s
take a more organized look at the costs and options for doing `yield`
as a contextual keyword.
But, before we do, let’s put this in context (heh): methods called
yield() are rare (there’s only one in the JDK), and blocks on the RHS
of an arrow-switch are rare, so we’re talking about the interaction
of two corner cases.
Let’s take the following example.
class C {
/* 1 */ void yield(int x) { }
void m(int y) {
/* 2 */ yield (1);
/* 3 */ yield 1;
int z = switch (y) {
case 0 -> {
/* 4 */ yield (1);
}
case 1 -> {
/* 5 */ yield 1;
}
default -> 42;
}
}
}
First, requirements:
For usage (1), this has to be a valid method declaration.
For usage (2), this has to be a method invocation.
For usage (3), this has to be some sort of compilation error.
For usage (4), there is some discussion to be had.
For usage (5), this has to be a yield statement.
(1) is not problematic, as the yield-statement production is not in
play at all when parsing method declarations.
(3) is not problematic, as there is no ambiguity between
method-invocation and yield-statement, and yield-statement is not
allowed here. (Even if the operand were an identifier, not a numeric
literal, it would not be ambiguous with a local variable declaration,
because `yield` will not be permitted as a type identifier.).
(5) is not problematic, as there is no ambiguity between method
invocation and yield-statement.
Let’s talk about (2) and (4).
Let’s assume the parser production only allows yield statement inside
of a block on the RHS of an arrow-switch (and maybe some other
contexts in the future, but not all blocks). Let’s call these
“switchy blocks” for clarity. That means that (2) is similarly
unambiguous to (3), and will be parsed as a method invocation. So
this is really all about (4).
OPTION A: DISALLOW YIELD (E)
----------------------------
In this option, we disallow yield statements whose argument is a
parenthesized expression, instead parsing them as method invocations.
Most such invocations will fail as there is unlikely to be a yield()
method in scope.
From a parser perspective, this is straightforward enough; we need an
alternate Expression production which omits “parenthesized
expression.”
From a user perspective, I think this is likely to be a sharp edge,
as I would expect it to be more common to want to use a parenthesized
operand than there will be a yield method in scope.
OPTION B: DISALLOW UNQUALIFIED INVOCATION
-----------------------------------------
From a parser perspective, this is similarly straightforward: inside
a switchy block, give the rule `yield <expr>` a higher priority than
method invocation. The compiler can warn on this ambiguity, if we
like.
From a user perspective, users wanting to invoke yield() methods
inside switchy blocks will need to qualify the receiver (Foo.yield(),
this.yield(), etc).
The cost is that a statement “yield (e)” parses to different things
in different contexts; in a switchy block, it is a yield statement,
the rest of the time, it is a method invocation.
I think this is much less likely to cause user distress than Option
A, because it is rare that there is an unqualified yield(x) method in
scope. (And, given every yield() method I can think of, you’d likely
never call one from a switchy block anyway, as they are
side-effectful and blocking.). And in the case of collision, there is
a clear workaround if the user really wanted a method invocation, and
the compiler can deliver a warning when there is actual ambiguity.
OPTION C: SYMBOL-DRIVEN PARSING
-------------------------------
In this option, the context-sensitivity of parsing includes a check
for whether a `yield()` method is in scope. I think we can rule this
out as overly heroic; constraining parsing to be aware of the symbol
table is asking a lot of compilers.
OPTION D: BOTH WAYS
-------------------
In this option, we proceed as with Option A, but when we get to
symbol analysis, if we are in a switchy block and there is no yield()
method in scope, we rewrite the tree to be a yield statement instead.
OPTION E: A REAL KEYWORD
------------------------
The pain above is an artifact of choosing a contextual keyword; on
the scale of contextual pain, this rates a “mild”, largely because
true collisions are likely to be quite rare, and there is no backward
compatibility concern. So while choosing a real keyword (break-with)
would be cleaner, I don’t think the users will like it as much.
My opinions: I think C is pretty much a non-starter, and IMO B is
measurably more attractive than A. Option D is not as terrible as C
but seems overly heroic, as we try to avoid tree-rewriting in
attribution. I don’t think the pain of either A or B merits grabbing
for E.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.java.net/pipermail/amber-spec-experts/attachments/20190520/fd825526/attachment.html>
More information about the amber-spec-experts
mailing list