This issue is to discuss and resolve any parsing ambiguities associated with non-nullable type syntax.
Known potential ambiguities:
a is int ? - 3 : 3;cc @lrhn @munificent @eernstg @danrubel
My understanding is that the code above should be interpreted the same in both situations. It is the parser's responsibility to perform arbitrary lookahead in this situation to determine that the ? is part of a conditional expression and not part of the named type.
With set/map literals, {a as int?-3:3} is either a set literal with a conditional expression or a map literal with 3 as value and a as int?-3 as key ... except that we probably wouldn't allow the - operations on a nullable type. Not sure that helps us when parsing, but it might suggest which disambiguation to choose.
Also, I feared that a is int?.toString() would be valid syntax, but we seem to not allow . or ?. after the is-check (we do allow .. though, so if we want to have ?.. as well, it might become a problem).
Maybe we could change the syntax for is and as so they delimit the type: x.is<int>, x.as<int>, with the extra benefit of working better in chains of computations.
The cheapest parsing algorithm would always combine ? with a previous potential type, but that would also prevent us from allowing int? as a type literal, because then someVar ? foo : bar wouldn't parse (someVar? is a type expression, followed by foo, which is just wrong).
The next simpler algorithm would never combine a type with ? if it can possibly be parsed any other way, so a is int?.toString() would "work". That may require arbitrary look-ahead.
I'm sure there are variants in-between, where the parsing depends on the position in the code (is a type expected here or not, with x is ... ? ... as the problem case.
This is a gnarly one. Let's say we take:
{ a as int ? - 3 : 3 }
and default to interpreting it as:
{ (a as int) ? -3 : 3}
If that's not what you want, how do you get the other interpretation where ? is part of the type? We don't currently allow you to parenthesize a type annotation. So one option is to allow that, then require:
{ a as (int?) -3 : 3}
That feels a little weird to me because in typical formatting, the ? already looks like it binds tighter as a type (no space before the ?) than as a conditional operator.
The other option is to default the other way. Treat the ? as part of the type. If you want it to not be, you have to explicitly parenthesize:
{ (a as int) ? -3 : 3}
I think we could probably do this by shuffling around the grammar so that a conditional expression's conditional prohibits a testTest or testCast expression. You'd have to get through it by going through primary and parenthesizing.
This is a breaking change and, unfortunately, I think a fairly common one.
This is a gnarly one. Let's say we take:
{ a as int ? - 3 : 3 }and default to interpreting it as:
{ (a as int) ? -3 : 3}If that's _not_ what you want, how do you get the other interpretation where
?is part of the type?
Seems to me that asking whether a is int? is probably something you essentially never want to do. We currently don't provide any way to do this (if you ask a is int in current Dart, it returns false for null).
Perhaps we should just disallow T? in is checks, or else allow it but prefer the (a is int) ? e1 : e2 parsing? If you really want to do an is check on a nullable type then you can use a type alias (once we have general type aliases).
When would one want A ? to mean nullable A? I think A? (i.e. no space between A and ?) is the only reasonable way to state it. The same goes for the conditional operator. There has to be whitespace preceding ?.
This is a gnarly one. Let's say we take:
{ a as int ? - 3 : 3 }and default to interpreting it as:
{ (a as int) ? -3 : 3}If that's not what you want, how do you get the other interpretation where
?is part of the type?
Assuming I needed to write that, I'd want to write it as { (a as int?) - 3 : 3}.
Seems to me that asking whether a is
int?is probably something you essentially never want to do.
I suppose so, but it feels weird to prevent a semantically meaningful operator for syntactic reasons. If we add other kinds of type annotations, we'll probably run into this problem again.
I can also imagine code that might want to do this. Consider some API that accepts int, null, or String, and forwards the first two to some other API that accepts int?.
Assuming I needed to write that, I'd want to write it as
{ (a as int?) - 3 : 3}.
Good point. :)
Ok, so for the conditional expression ambiguity, it sounds like we can resolve by first trying to parse as a conditional expression, and so prefer the parse (a is int) ? -3 : 3 over the parse (a is int?) -3 : 3.
That is certainly my understanding of this situation. I'm making parser
mods to that end unless I hear otherwise.
On Wed, Dec 19, 2018 at 1:55 PM Leaf Petersen notifications@github.com
wrote:
Ok, so for the conditional expression ambiguity, it sounds like we can
resolve by first trying to parse as a conditional expression, and so prefer
the parse (a is int) ? -3 : 3 over the parse (a is int?) -3 : 3.—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/dart-lang/language/issues/144#issuecomment-448705651,
or mute the thread
https://github.com/notifications/unsubscribe-auth/ACwgcHb7Q0HCI_abyVZs04NR-2IKsWXmks5u6ouxgaJpZM4ZXS7C
.
A note on grammar: the grammar for types will only allow a single ?. That is:
type' ::= functionType
| qualified typeArguments?
type ::= type' `?`?
So
int ? ? x = 3;
is a parse error.
Also, int??String parses as (int) ?? (String).
Agreed. That is the case and I've just added parser tests to ensure as much.
The grammar has now been defined, including the ambiguity introduced by null-aware index operators.
Most helpful comment
When would one want
A ?to mean nullableA? I thinkA?(i.e. no space betweenAand?) is the only reasonable way to state it. The same goes for the conditional operator. There has to be whitespace preceding?.