-
Notifications
You must be signed in to change notification settings - Fork 214
Do null aware operators use promotion semantics? #290
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
We need to define exactly how far the null-detection reaches. I suggest using the same limits as a cascade: a sequence of selectors, potentially trailed by an assignment. That is Using the same extent as a cascade makes it easier for users to learn and reason about. We already have the syntax in the grammar, but we might have to tweak it a little because The new That means that We should consider introducing I'm fine with nothing working for |
I'm not sure I follow this. I don't exactly what you mean by "limits as a cascade", but cascaded setters certainly make that a fuzzy concept: a..b = c..d = e; This (for better or worse) is a single series of cascaded selectors. But the corresponding null-aware chain means something entirely different: a?.b = c?.d = e;
That's putting it lightly. main() {
print(true ? [1] : [2]..add(3)); // "[1, 3]".
}
SGTM. Though I would describe it more as a "high precedence postfix operator" than a "selector", though I guess the latter is just the grammar's name for the former.
This feels like a weird syntactic restriction to me. My mental model is that you have a primary expression followed by any number of "call-ish" postfix things: I'm digging around in the grammar and I find it pretty confusing, though that's somewhat to be expected because of cascades. I see:
If I'm reading that right, it would disallow: super.x.y = 1; Since only a single unconditionalAssignableSelector can follow In my mind, the grammar with
But I'm probably missing some subtlety. Either way, we should be able to slot |
PS: This CL updates the spec parser to handle ui-as-code collections. This would make grammar experiments easier to sanity check. |
On Thu, Mar 28, 2019 at 1:38 AM Bob Nystrom ***@***.***> wrote:
I suggest using the same limits as a cascade: a sequence of selectors,
potentially trailed by an assignment.
I'm not sure I follow this. I don't exactly what you mean by "limits as a
cascade", but cascaded setters certainly make that a fuzzy concept:
a..b = c..d = e;
This (for better or worse) is a single series of cascaded selectors. But
the corresponding null-aware chain means something entirely different:
a?.b = c?.d = e;
we might have to tweak it a little because ?. does not have the same
"precedence" as ...
That's putting it lightly. .. is all the way on the other end of the
precedence table.
True. It's somewhat confusion if
a?.b = c?.d = e
means
(a?.b =c)?.d = e
but that is what I was proposing, because I didn't have a *better* idea.
You'd have to look at `?.` like you look at `..` today.
If we can make it
a?.b = (c?.d = e)
then all the much better. It's not a cascade, We won't have independent
x..foo..bar..baz following, so we don't need `expressionWithoutNullAware`
for the assignment expression.
So, yes, I think that's doable.
main() {
print(true ? [1] : [2]..add(3)); // "[1, 3]".
}
Yeah, that sucks. The problem with cascades is that the "precedence" is
different on the left and on the right, which is why we have
"expressionNoCascadade".
Cascades *look* like they bind as strongly as `.`, but they really don't.
Maybe they should (but changing it would be breaking, people are using
ridiculous things like this, we'd need a migration tool to add parentheses).
The new ! suffix operator should be a selector as well, so x?.foo!.bar is
valid.
SGTM. Though I would describe it more as a "high precedence postfix
operator" than a "selector", though I guess the latter is just the
grammar's name for the former.
The "selector" name is just a name for a high-precedence postfix operator
that works in a cascade, so I just meant to say that it should work in
cascades.
That means that x?.foo! + bar would still not be allowed, you have to
write (x?.foo)! + bar to override the nullable type.
This feels like a weird syntactic restriction to me. My mental model is
that you have a primary expression followed by any number of "call-ish"
postfix things: (...) for function/method calls, identifier for getters,
[...] for index operators, etc. We'd just add ! to that list.
Yes. The problem is that if ?. works like .., then (x?.foo! + bar) means
(x?.foo!) + bar, and the `!` is elided along with the `.foo` when `x` is
null.
So, you write (potentiallyNullExpression) + bar, which is not allowed.
I'm digging around in the grammar and I find it pretty confusing, though
that's somewhat to be expected because of cascades. I see:
expression:
assignableExpression assignmentOperator expression |
conditionalExpression cascadeSection* |
throwExpression
assignableExpression:
primary (argumentPart* assignableSelector)+ |
"super" unconditionalAssignableSelector |
identifier
unconditionalAssignableSelector:
"[" expression "]" |
"." identifier
If I'm reading that right, it would disallow:
super.x.y = 1;
I think I can find a derivation
expression -> assignableExpression assignmentOperator expression
assignableExpression -> primary (argumentPart* assignableSelector)+
primary -> "super" unconditationalAssignableSelector
unconditionallyAssignableSelector -> "." identifer
identifer -> x
(argumentPart* assignableSelector)+ -> argumentPart* assignableSelector
argumentPart ->
asignableSelector -> '.' identifier
identifier -> y
assignmentOperator -> '='
expression -> ... 1
Since only a single *unconditionalAssignableSelector* can follow super.
But that's clearly not right. :-/
No, primary can be `super.id` as well.
In my mind, the grammar with ! would be something like:
expression:
assignableExpression assignmentOperator expression |
cascadeExpression |
throwExpression
fieldInitializer:
("this" ".")? identifier = cascadeExpression
cascadeExpression:
conditionalExpression ( cascadeSection+ | invocationSection* )
cascadeSection:
".." cascadeSelector invocationSection*
(assignmentOperator expressionWithoutCascade)?
cascadeSelector:
"[" expression "]" |
identifier argumentPart?
invocationSection:
"?." identifier |
unconditionalInvocationSection
unconditionalInvocationSection:
argumentPart |
"." identifier |
"[" expression "]" |
"!"
postfixExpression:
invocation postfixOperator?
invocation:
primary invocationSection*
assignableExpression:
invocation
"super" unconditionalInvocationSection
identifier
But I'm probably missing some subtlety. Either way, we should be able to
slot
! into the grammar such that it can follow both a primary expression and
an invocation chain if that's what we want.
It's not `!` I'm worried about, it's how to delimit the `?.` reach, and
avoid being ambiguous.
It's probably possible. The x?.b = c?.d example shows where we have to be
cautious. It's probably best to let the `c?.d` be its own expression,
nested inside the outer `?.`.
If we can do that, then yey.
…--
Lasse R.H. Nielsen - [email protected]
'Faith without judgement merely degrades the spirit divine'
Google Denmark ApS - Frederiksborggade 20B, 1 sal - 1360 København K
- Denmark - CVR nr. 28 86 69 84
|
Ah, thanks for walking through the derivation.
Yes, definitely. |
Decided, yes. |
We have decided that null aware operators shall short-circuit. That is, the following is valid code:
Notionally, we treat
x?.foz.isEven
as de-sugaring(x == null) ? null : x.foz.isEven
. Note that this interpretation relies on promotion ofx
toint
to make the de-sugaring produce the appropriate errors (or lack thereof).If we take this interpretation as normative then the following should be an error:
This is an error since no promotion rule applies to
x
, and so it remains potentially nullable. Alternatively, we can simply specify the errors and warnings for this directly, thereby permitting this code.cc @lrhn @eernstg @munificent
The text was updated successfully, but these errors were encountered: