Typescript: Suggestion: "safe navigation operator", i.e. x?.y

Created on 15 Jul 2014  ·  205Comments  ·  Source: microsoft/TypeScript

Current Status

  • The TC39 proposal is now at stage 3 (🎉🎉🎉🎉🎉)
  • Implementation is in progress
  • You can expect this feature in TypeScript 3.7
  • We'll update here when it's available in a nightly build
  • Holding off on Optional Call until its semantics are clarified at committee

Open questions

  • What special-casing, if any, should document.all get?

C# and other languages have syntax sugar for accessing property chains where null (or in our case, undefined) might be encountered at any point in the object hierarchy.

var x = { y: { z: null, q: undefined } };
console.log(x?.y?.z?.foo); // Should print 'null'
console.log(x?.baz); // Still an error
console.log(x.y.q?.bar); // Should print 'undefined'

Need proposal on what exactly we should codegen, keeping in mind side effects of accessors.


Edit by @DanielRosenwasser February 27, 2018: This proposal is also called the "null propagation" operator.

Committed ES Next Suggestion Update Docs on Next Release

Most helpful comment

Optional Chaining is Stage 3

Briefly unlocking this one only for celebratory purposes

All 205 comments

So in the first example, we might emit it like the following:

x && x.y && x.y.z && x.y.z.foo

But then we'd have to somehow make x, y, z, and foo each evaluate at most once.

You also really can't do && in many cases because truthiness becomes a bit of a problem for primitives.

For example:

"     "?.trim()?.indexOf("hello")

gives "".

So you need to do some explicit comparisons to null using == for the general case, unless we leverage the type system (which would be fairly cool to see us do).

We could possibly emit a monadic-bind function (not pretty for the JS output), or use some transformation on ternary operators (closer to typical JS equivalent). I'm clearly a little biased towards the latter.

:+1:

Ideally we'd have ES7 (or ES8 or ES9 or ...) implement this first since there'd probably be some disagreement about the exact semantics about whether or not to actually use 0/"" as falsy primitives for the purposes of any operator here.

:+1: I'd like to see TypeScript gets this in first without having to wait for ESxx.

The fact that simple and insanely useful null-safety operators like "?." and "?:" AREN'T in the ES6 spec means the people putting together the ES6 spec should be hanging their heads in shame. This is such a simple and obvious thing that to not incorporate it would frankly be insane. There's a reason most modern languages support these: they're indispensable.

I realize this would be a deviation from the current spec (since the current spec is so short-sighted as to omit this). But it's so ridiculously useful that I think this single deviation would be justified. The vast (VAST) majority of TS developers wouldn't be affected by minor changes to the implementation, if or when this finally gets added to an ES specification. The huge benefits this would offer is worth the potential future impact to a tiny fraction of developers. And given the laughably slow ES spec process, this wouldn't even matter at all for several years (at minimum).

I totally agree with brain428

@brian428 the problem here is that that operator maybe implemented in ES7 so if typescript go with a specification that ends up differing from the final ES7 one, nobody will be happy.

the problem here is that that operator maybe implemented in ES7 so if typescript go with a specification that ends up differing from the final ES7 one, nobody will be happy.

I think it is a more positive approach for TypeScript to implement features that may _potentially_ (or may not) make it into a future ES version, because it will be a useful testbed for influencing ES direction.

Here is an example of ES discussion being influenced by TypeScript:

The TypeScript... option to declare and initialize via a private prefix on one of constructor's parameters would be helpful to many developers

Furthermore, it's certainly possible for ES to adopt a feature that is already present in TypeScript, but with different semantics (for example, around how modules work).

it's certainly possible for ES to adopt a feature that is already present in TypeScript, but with different semantics

I should note that we broadly consider this to be a worst-case scenario. We really wanted modules in ES6 to be finalized before we declared TypeScript 1.0, but the committee's schedule delays prevented that. This is something to be avoided, not repeated. We'd really like to hit features that have either a ~0% chance of making it into ES7+ (e.g. type annotations), or have a ~100% chance of making it in with easily-defined semantics (e.g. where fat arrow was two years ago). New operators are likely to fall in the awkward middle.

In the worst case, if ES7 does differ, could a compiler flag support the legacy TS implementation, thus offering a grace period? This coupled with clear migration documentation should offer developers a straightforward route to any new standard.

Ultimately, use of any such feature—although insanely useful—isn't essential by developers. TS should make potential future implications of it's usage abundantly clear from day one. Don't like the idea of a potential managed refactor, don't use it. Perhaps an opt-in compiler flag to enforce this message?

TS shouldn't go wild with wanting to influence ES, but in small isolated cases such as this, it'd be a shame if TS were to completely shy away.

Maybe we could put together a strawman proposal for this and then have a reference implementation behind a --harmony flag (or something like that). That way we can drive ES7 development of this feature.

To prevent side-effects due to repeated look-ups, the compiler will either have to output temporary variables:

($tmp0 = x, $tmp0 === void 0 ? void 0 : 
    ($tmp1=$tmp0.y,  $tmp1 === void 0 ? void 0 : 
        ($tmp2 = $tmp1.z,  $tmp2 === void 0 ? void 0 : $tmp2)))

or use a memoizing membrane based on Proxy.

From a categorical point of view, this is just the maybe monad applied to property lookup, so it's a very natural feature for a language where all property lookups may return undefined. I'd be surprised if ES7 adopted any semantics other than the one described by the code above.

The codeplex issue had quite a number of votes (61)

I really badly need this to ease the pain of using atom for atom-typescript.

It is very idiomatic in coffescript code (although I would like it not to be as popular as determinism is better than a fudgy ?). Open any coffescript file, especially one that works with the DOM directly like space-pen (where functions can run after the view is destroyed or before the view is attached) and you will find a gazillion ? usages. e.g. this file has 16 https://github.com/atom-community/autocomplete-plus/blob/f17659ad4fecbd69855dfaf00c11856572ad26e7/lib/suggestion-list-element.coffee

Again I don't like that I need this, but its the state of JavaScript, and I'd rather ? than a million if( && fest ) { then }

But I really really need it to keep my code readable. Its also very common to _need_ this when you are waiting for an XHR to complete and angular runs its digest loop.

Okay now I have read the thread and see why we are waiting. I understand _sigh_.

we'd have to somehow make x, y, z, and foo each evaluate at most once.

coffeescript does do some optimizations e.g. stores intermediate access results:

typeof foo !== "undefined" && foo !== null ? (ref = foo.bar) != null ? ref.baz() : void 0 : void 0;

(I strongly feel the undefined check is unnecessary for typescript : as we should have a var init typechecked by typescript)

+1

In news today, Dart is getting official support for it : https://github.com/gbracha/nullAwareOperators/blob/master/proposal.md

Very important feature.

Possibly an off-the-wall idea but the codegen for this feature could be done very easily without side effects if everyone decided that it would be OK to handle the feature with keyed property access:

if (aaa?.bbb?.ccc) {}

Could compile to

if (__chain(aaa, "bbb", "ccc")) {}

A __chain function would have to be emitted similar to __extends. The __chain function could just iterate through the arguments array, returning null when the upcoming member is not instanceof Object or does not contain the member name. Function calls could be handled by passing in an array as a parameter (and using .apply() under the covers), so ...

if (aaa?.bbb?.ccc?(1, 2, 3)) {}

Could compile to

if (__chain(aaa, "bbb", "ccc", [1, 2, 3])) {}

This would also keep the generated JS reasonably idiomatic, even for long chains.

Needs refinement obviously ... but maybe there is something here?

If aaa?.bbb?.ccc? returns the value of a.b.c if all props exist and it happens to be a function, then couldn't

if (aaa?.bbb?.ccc?(1, 2, 3)) {}

compile to

if (__chain(aaa, "bbb", "ccc")(1, 2, 3)) {}

?

@metaweta: Your solution is only checking for void 0 ... doesn't that kind of defeat the purpose of the feature?

What about this:

var result = one?.two?.three;

Generates:

var $a, $b, $c;
var result = $a = one, $b = $a ? $a.two : void 0, $b ? $b.three : void 0;

I'm pretty sure this handles all the cases. Function calls would probably need an instanceof Function check.

(Minor downside here of unexpected local variables being emitted ... could be wrapped in an IIFE maybe)

@kevinb7: What happens if ccc isn't a function? With the code you've described, __chain would always have to return a valid function, or a TypeError would be emitted.

@Back-io When a property is absent, a lookup returns undefined === void 0. Your solution fails for looking up properties of falsey values like empty string and zero.

@metaweta: No it doesn't: http://jsfiddle.net/25LppbL6/

Also, I would imagine the TS team isn't crazy about using loose equality due to the fact that some people's linters warn against it.

@Back-io Yes it does: http://jsfiddle.net/25LppbL6/2/

@Back-io Regarding null, I'm not sure what the intended semantics of a?.b is. If it's "If the property b is defined then use it", then my code is almost correct. The only way you'd get null is if it's assigned to be null, because lookups of nonexistent properties return undefined. It does fail to catch the case where the property exists but is set to undefined. To be completely correct, it would check with Object.hasOwnProperty() instead of comparing to void 0 === undefined.

If the semantics are "if the property b is truthy then use it", your code is fine and to some extent matches JS idiom.

Umm ... unless I'm missing something ... the changes you made to the fiddle just further prove me right ... var result is still undefined in all 3 cases. And I'm not sure what case you're trying to bring forward by extending the primitive prototypes ...

I'm fairly certain that the behavior of the feature would be to short circuit with void 0 instead of generating an error. (I like your idea above that it should always return void 0 in the case of a missing member).

What is the intended output of ''?.toString?

@kevinb7: What happens if ccc isn't a function? With the code you've described, __chain would always have to return a valid function, or a TypeError would be emitted.

Good point.

@metaweta: I would imagine most people would expect a reference to the toString function, so I see where your point, It does break down somewhat in the case where you're accessing the prototype members of 0, false, or ''.

:+1:
Angular 2 added Elvis Operator to their template syntax

@metaweta :

What is the intended output of ''?.toString?

If you meant ''?.toString() it would be :

if ('' != null) {
  ''.toString();
}

Sample

Maybe we could put together a strawman proposal for this

@kevinb7 it already exists : http://wiki.ecmascript.org/doku.php?id=strawman:existential_operator

I did some quick tests to implement this on top of PropertyAccessExpression as its special case but didn't work out well as we really need ?. to be right associative (instead of left associative like .) otherwise the emitter becomes needlessly complex.

There is comment by BrendenEich here that reflects this as well.

I did some quick tests to implement this on top of PropertyAccessExpression as its special case but didn't work out well as we really need ?. to be right associative (instead of left associative like .) otherwise the emitter becomes needlessly complex.

@basarat can you please elaborate on that? An example of the difference between right associative and left associative ?. would be very helpful to me.

@zlumer

An example of the difference between right associative and left associative ?. would be very helpful to me.

. is left associative so the the expression foo?.bar?.baz becomes in AST (if we treat ?. the same):

                    // foo.bar.baz = PropertyAccessExpression
                    //   .expr foo.bar =  PropertyAccessExpression
                    //     .expr foo = Identifier
                    //     .name bar = Identifier
                    //   .name baz = Identifier

The JavaScript emit needed is

foo != null ? (ref_1 = foo.bar) != null ? ref_1.baz() : void 0 : void 0;

Its just easier to do this emit (especially _recursively_) if we had the following in the AST:

                    // foo.bar.baz = PropertySafeAccessExpression
                    //   .name foo =  Identifier
                    //   .expr bar.baz = PropertySafeAccessExpression
                    //      .expr bar = Identifier
                    //      .name baz = Identifier

Just think of _how would you convert the first AST to JavaScript_ and the complexities would be clearer. Hope this helps :rose:

@zlumer, to add to what @basarat said, I'll just put out the example 1 + 2 + 3.

When parsing, we have to decide which of these operations will happen first.

If + is left-associative, this will be interpreted as ((1 + 2) + 3).

If + is right-associative, this will be interpreted as (1 + (2 + 3)).

You might question whether this would actually make a difference. In JavaScript it would! Consider the example "hello" + 2 + 3.

  • Left-associative: (("hello" + 2) + 3) => ("hello2" + 3) => "hello23"
  • Right-associative: ("hello" + (2 + 3)) => ("hello" + 5) => "hello5"

(For the record, JavaScript/TypeScript use left-associativity for the + operator.)

@basarat, my understanding of what @BrendanEich said (and he can correct me if I'm wrong - sorry for the ping!) is _not_ that ?. is right-associative, but that CoffeeScript special cases property accesses on the right of the ?. to be right-associative. For example, it will parse

o.p?.q.r.s

as

((o . p) ?. (q . (r . s))) # or something close to this

instead of

((((o . p) ?. q) . r) . s)

because it's easier to emit.

Our AST needs to lend well to sane semantic analysis, so our emitting procedure can afford to be slightly more complex to suit this need.

@basarat @DanielRosenwasser thank you for the explanations. So far I understand it, but I'm still not certain about one thing.
The left-associative ?. is pretty obvious and expected:

foo?.bar?.baz

Becomes (approx.):

var ref = ((ref = foo) == null) ? null : ((ref = ref.bar) == null) ? null : ref.baz;

But I don't understand at all how would a right-associative ?. work. Can you please provide an example?

But I don't understand at all how would a right-associative ?. work. Can you please provide an example

@zlumer The runtime behaviour will be left associative. I was just talking about the AST as DanielRosenwasser also clarified: is not that ?. is right-associative, but that CoffeeScript special cases property accesses on the right of the ?. to be right-associative.

Our AST needs to lend well to sane semantic analysis, so our emitting procedure can afford to be slightly more complex to suit this need.

@DanielRosenwasser thanks for the feedback :rose:

@basarat thank you, suddenly it all became clear :smiley:

Similar to @basarat's Feb comment, I.... _sigh_...

However, if you think about it, 99% of the use cases of this will be to check against a null pointer to an object. Frankly speaking, who does x?.b?.c when x is a number? There just aren't many real-life use cases for _long_ chains when we are _not_ talking about an object (with the possible exception of string). For short chains, I think we can live with x && x.b or x === 0 ? null : x.b.

So, can we, sorta, say ?. only works on object types? Any other type throws a syntax error. And disallow function calls in side the chain.

Then the whole thing transcribes to a && a.b && a.b.c.

@schungx What if a member in the chain is an "any" type? Disallow completely? Or just let it through and hope for the best?

Well, my suggestion? Disallow completely. Inelegant as hell, I know... :-)

But my rationale:

  1. This is a short-hand, so if somebody is using any, just use long-hand.
  2. If somebody is using TypeScript, most likely he/she is using it for the typing support, so hopefully he/she won't have many any's around!
  3. any really should be handled with care. Allowing usage of such short-hands with a flexible type like any is really asking for bugs to happen. In my opinion, any should be as limited as possible. It is sort of like C's (void *) -- the fact that you're handed a nuke doesn't mean you must trigger it just because you can!

This would be amazing operator!! Especially for ES6/ES7/TypeScript

var error = a.b.c.d; //this would fail with error if a, b or c are null or undefined.
var current = a && a.b && a.b.c && a.b.c.d; // the current messy way to handle this
var currentBrackets = a && a['b'] && a['b']['c'] && a['b']['c']['d']; //the current messy way to handle this
var typeScript = a?.b?.c?.d; // The typescript way of handling the above mess with no errors
var typeScriptBrackets = a?['b']?['c']?['d']; //The typescript of handling the above mess with no errors

However I propose a more clear one - as not to confuse ? from the a ? b : c statements with a?.b statements:

var doubleDots = a..b..c..d; //this would be ideal to understand that you assume that if any of a, b, c is null or undefined the result will be null or undefined.
var doubleDotsWithBrackets = a..['b']..['c']..['d'];

For the bracket notation, I recommend two dots instead of a single one as it's consistent with the others when non brackets are used. Hence only the property name is static or dynamic via brackets.

Two dots, means if its null or undefined stop processing further and assume the result of expression is null or undefined. (as d would be null or undefined).

Two dots make it more clear, more visible and more space-wise so you understand what's going on.

This is not messing with numbers too - as is not the same case e.g.

1..toString(); // works returning '1'
var x = {};
x.1 = {y: 'test' }; //fails currently
x[1] = {y: 'test' }; //works currently 
var current = x[1].y; //works
var missing= x[2].y; //throws exception
var assume= x && x[2] && x[2].y; // works but very messy

About numbers two options: Your call which one can be adopted, but I recommend first one for compatibility with existing rules!

  1. Should fail as it does now (x.1.y == runtime error)
var err = x..1..y; // should fail as well, since 1 is not a good property name, nor a number to call a method, since it's after x object.
  1. Should work since it understands that is not a number calling a property from Number.prototype
var err = x..1..y; // should work as well, resulting 'test' in this case
var err = x..2..y; // should work as well, resulting undefined in this case

With dynamic names:

var correct1 = x..[1]..y; //would work returning 'test'
var correct2 = x..[2]..y; //would work returning undefined;

What do you think folks?

P.S. foo?.bar and foo?['bar'] syntax would work too.

However the using both current ? : operator and ?. might be very confusing on the same line.

e.g. using ?. and ?['prop']

var a = { x: { y: 1 } };
var b = condition ? a?.x.?y : a?.y?.z;
var c = condition ? a?['x']?['y'] : a?['y']?['z'];

as opposed to double dots .. and ..['prop']

var a = { x: { y: 1 } };
var b = condition ? a..x..y : a..y..z;
var c = condition ? a..['x']..['y'] : a..['y']..['z'];
Which one does look more clear to you?

Very interesting. :+1:

IMHO, two dots will be more confusing. There are languages where two dots represent a range (e.g. 1..4) and TypeScript may add this feature in the future.

A question mark also has the semantic meaning of uncertainty or conditional and two dots do not convey the same meaning.

@schungx Fair enough, but it would help for the weird possibilities like this : a?['b'] or this a?().

+1 ?

a?['b'] and a?() behave nicely in coffeescript, and they look good to me.

But then again, I might just be coffeescript-blind.

+1 Ruby just implemented the existential operator https://twitter.com/mikepack_/status/657229703443451904. Typescript needs this too, regardless of the specific syntax.

This would be make code cleaner 👍

+1 Need this!

Please, implenent ?. operator, as C# does

+1 I've been missing this for a long time

+1
so ugly to write: someVariable && someVariable.someMember
when you could write: someVariable?.someMember

+1, this would be great.... (most wanted feature for me)

+1, it's a good idea!
Only, if the object is complicated, the expression will be full of ?. after each property (var result=myValue?.a?.b?.c?.d?.e;) when I need to recieve a value of the last one (var result=?myValue.a.b.c.d.e;).

+1 - This is arguably one of the greatest features of CoffeeScript and is by far my team's most desired TypeScript feature after converting much of our code from CS to TS.

+1 however, this is too complex:

var x = { y: { z: null, q: undefined } };
var z: x|y|z = x?.y?.z;

I like this:

var x = { y: { z: null, q: undefined } };
var z: z|void = x?.y?.z;

x?.y?.z's type is always the type of the z field. Of course the type must be nullable and the actual value may be null. If it is non-null, then it must be of the type of the z field.

+1 This would go well with Typescript's vision of easing development of large scale complex JS Projects.

Any updates on this? Is this a case of the community voting for this feature for it to be considered? Or has it been considered but there are some engineering challenges?

No updates so far because introducing new expression-level syntax is dangerous without some sort of proposal from the ECMAScript committee.

See https://github.com/Microsoft/TypeScript/issues/16#issuecomment-57645069.

Here is a more current discussion on existential operators (lots of thoughts, but doesn't look like a lot of action):

where should I write "+1" to help bring it to ES?

@msklvsk ESDiscuss

Closing this for now. Since there's not really anything TypeScript-specific that would require this at expression level, this kind of big operator change should happen at the ES spec committee rather than here.

The general tripwires for re-evaluating this would be a concrete ES proposal reaching the next stage, or a general consensus from the ES committee that this feature wouldn't happen for a long time (so that we could define our own semantics and be reasonably sure that they would "win").

life sucks

Deleting standalone :+1:s. Please use the GitHub reactions feature or send flowers and candy to your nearest TC39 representative.

Once I got used to it in coffeescript and Swift, there's no going back. TS is dead to me as it currently stands.

@algesten I can agree about swift if we consider the language itself, not sure about long term coffescript support (I use CoffeScript in production projects). We may agree to be sure of language analytics trends as for June 2016 like this where we can clearly see what's going on to coffescript recently, while TypeScript has seen the fastest growth in the past years:

TypeScript: Outside of Go or Swift, the fastest growing language we’ve observed in recent years is TypeScript. The Microsoft-backed JavaScript superset and Angular 2 foundation has made significant gains for the second consecutive quarter, jumping from #31 to #26. That was the biggest single change in any Top 30 language, and the second largest jump overall (Standard ML, 7 spots). At #26, in fact, TypeScript is now tied with Erlang, one spot behind Powershell and four behind CoffeeScript, which is just outside the Top 20. The question facing the language isn’t whether it can grow, but whether it has the momentum to crack the Top 20 in the next two to three quarters, leapfrogging the likes of CoffeeScript and Lua in the process.

Until 2014 the coffescript trends was more than positive as you can look here, that is when the decline started.

With the new typescript 2.0 strict null (undefined) checking, it is a must, because otherwise you cannot use function chaining!

This should be in the next version of ECMAScript. Considering this would be even more useful in vanilla JavaScript than TypeScript and considering that the implementation would naturally be a check for undefined or null as opposed to a truthy check, it should be trivial for TC39 to add.

The simple implementation and one which would be idiomatic in JavaScript, would simply be to short circuit when encountering either null or undefined, and return undefined.

@bterlson does this make sense? What are the odds of such a proposal being accepted?

One of the great benefits of typescript is "the future is given to you today". This is one of the features that I was surprised that are not already there.

I hope that it will be added soon as function chaining is popular idiom and will strict null checking it is no longer work unless the ?. operator is added.

TS 2 wonderful null checking made it from a nice to have to must have!

Reading through this thread I'm kind of surprised how this doesn't get more attention from the maintainers.

In an ideal world TypeScript should lead the way for ES and not reversely. Seriously, where would be TypeScript now if the TS team would always have waited for ESx to propose and finalize a feature or a syntax?

This syntax is really a "must have", as others have pointed it out. It even received some good proposals in this thread so far. I think the implementation should match these expectations.

  • In general an expression a?.b should be valid at compile-time if and only if a.b is valid.
  • It should evaluate each expression in the chain only once.
  • It should be short-circuit.
  • If execution reaches a mid-expression with null or undefined value then that value should be the return value.

What do you think the parts are which can lead to disagreements when ES will specify it?

For a?.b I do not see any conflicts with existing (and future?) syntax. If the parser finds the token ?. it can treat it as 'safe navigation operator' (with expectations as described by @cervengoc).

Syntax conflicts arise only when allowing a?(b) and a?[b], since these could also be interpreted as the start of a ternary ?: operator expression. But for starters I think these could be put aside and supporting just the a?.b syntax would already make a lot of developers happy!

In an ideal world TypeScript should lead the way for ES and not reversely.

In terms of expression-level syntax, we absolutely disagree. There's a committee driving a standard for a reason -- not so that one-off actions by one player can unilaterally decide the future of JavaScript.

This syntax is really a "must have", as others have pointed it out.

If this is a must-have for TypeScript, it's a must-have for JavaScript as well! Again, take your concerns to the ECMAScript committee.

What do you think the parts are which can lead to disagreements when ES will specify it?

At a minimum, I think there will be disagreement about whether the syntax short-circuits to null or undefined when encountering those values, or always short-circuits to undefined. There is also going to be some contention around whether or not some form of bracketed syntax is supported. There's also the question of what the behavior of a?.b.c is. There's also the question of ?. vs .? vs a.b?. There's the question of what the effect of this on the delete operator is.

The ES DIscuss thread on this is over 100 comments long. There's no lack of ambiguity! It's easy to look at one example in isolation and think there can't be tons of corner cases. There are. That's probably why no one has championed this at TC39 yet and _also_ why we're not rushing to add a feature that has lots of ambiguity around how it should behave.

I apologize, I didn't read through the mentioned thread, I will definitely have a look at it to see more.

We see this a bit differently. About the committee, in my honest opinion this is one of the biggest reasons why JavaScript will never be _good_. Just for an example, many of the most successful software what I've seen (like Total Commander or IrfanView) are successful because they are maintained and designed by ONE person, and not by a *committee". Of course this is not a completely correct example. But I'm almost sure, that if for example you alone would have designed the complete ES6, then the world would be a better place now.

Also, the ambiguities which you've mentioned are in 99% _theoretical_ things and kind of irrelevant from the developer's side. Who would care about what it returns, null or undefined? Just pick one, and we will use it like that.

All in all, you and that committee are on a different side than most of us, and things from that side are usually more complex than they really are. And this can lead to some contra-productivity to say the least. Don't take it personally, but according to my experience in general, some people would be better come out of the conference room more often and have a look at some code.

Of course no offense, don't take anything personally, I have a huge respect to you and all the TS team, because you did revolutionize many developers' client-side development including myself, and thank you for all your work. We're just a bit disappointed about this specific one I guess.

One last thought. I've ran through the mentioned ES thread, and one thing is completely sure: they are overcomplicating it. They want to design something which is good for every scenario.

I personally would be completely satisfied and happy with a conditional member access operator. Don't need conditional invocation, conditional index signature, don't need to support every fancy scenario which is valid JS code. That's all. But instead, they will likely keep sitting there and discuss how to do everything at once, which is a great plan, but we will have nothing at the end of the day.

All in all, you and that committee are on a different side than most of us, and things from that side are usually more complex than they really are.

I don't think you are reflecting the true status of Ryan or TC39 accurately. Ryan, and the TypeScript team, have set out very clear design goals for TypeScript. One of the original and still very current goals is that TypeScript is a superset of JavaScript. Not the language people would like it to be (e.g. Dart, Haxe). When it come to syntax, the TypeScript team have learned the hard way the cost of pre-inventing it (e.g. modules). We are headed headlong into a challenge with private members of classes too, where the proposed ES syntax is totally incompatible with the syntax TypeScript uses. Why? Because what may look uncomplicated on the surface is impossible to achieve given the run-time challenges of a language.

TC39 has "saved" JavaScript in my opinion. ES4 was abandoned, not because it lacked good, innovative ideas, but because it would break the internet. TC39 got themselves into shape, they have shared and been totally open in their discussions and how they make decisions and delivered us ES2015, which is a lot like ES4, but didn't break the internet. It is amazing that we essentially have JavaScript run-times that run code from 10 years ago just fine, but support many significant improvements to the language. ES2016 was the lull before the storm. ES2017 has a "reasonable" amount of functionality and change and a clear governing process that heads the right direction.

So being on the "different side" of things clearly has worked, in my opinion. Which beats expediency of "must have" features.

@kitsonk I didn't mean "different side" negatively, and especially I didn't mean to degrade the work what was put into TypeScript or ES6. What's more, the best thing in TypeScript IMO is that it really had and has a clear design goal, and it's well protected against becoming a havoc like many other opensource stuff.

I just wanted to tell that this very feature is a clear example of where a group of genius people will end up overthinking and overcomplicating things, instead of just going the easy simple way, and accept some limitations like not supporting invocations or index signatures, etc. Someone in that forum even suggested using this syntax in assignments, which is kind of crazy. I still think that this phenomenon is contra-productive in this sense.

I understand that on your side it's a pain that for example private members became incompatible with the final ES6 concept. But on the other hand, we HAD it. Way before ES6. And that's the main point from our side. Roughly speaking, we don't care about how you manage to emit the appropriate code for it, we're just happily using it. Same with modules, and everything. We (or at least I) didn't see those pains what you're talking about it, we were always happy with private members or modules.

This particular feature is in CoffeScript as I read about it. Why do we, simple developers always have to make compromises when choosing a platform/library/plugin, etc.? I mean always. This is kind of annoying. Here we have a great language, which has great potential, which completely leaves every other participants behind (including ES!), and which has successfully revolutionized a huge part of client-side development, and when it comes to this "simple" feature (I mean the member access part at least), we hear that it won't be implemented until ES commits to this.

I wanted to give folks a quick heads up that this feature moved from stage 0 to stage 1 at today's TC39 meeting.

Relevant commit: https://github.com/tc39/proposals/commit/cb447642290a55398d483f5b55fb7f973273c75d
Meeting agenda: https://github.com/tc39/agendas/blob/master/2017/01.md

wow! that's huge!

Some "surprises" I see here (not saying I disagree, just things that we probably would have done differently if we had done this earlier):

  • null is not produced from an a?.b expression: when a is null this produces undefined instead
  • ~Propagation in chained dots: a?.b.c.d will not throw if b and c properties are undefined~ Ryan can't read
  • ~Propagation in presence of parens: even (a?.b).c will not throw if b is undefined~ Ryan can't read
  • ~Propagation even occurs on method calls: a?.b.c().d will return undefined if the c invocation returns null~ Ryan can't read
  • delete operator is supported
  • Bracketing syntax a?.[x] is supported
  • Function call syntax func?.(...args) is supported, even for non-method calls (!)

I'd expect to see change in those areas between now and stage 2.

I think coffeescript got it right.

a?.b.c throws if b is undefined.

a?() and a?[0] are both good.

  • Propagation in chained dots: a?.b.c.d will not throw if b and c properties are undefined
  • Propagation in presence of parens: even (a?.b).c will not throw if b is undefined
  • Propagation even occurs on method calls: a?.b.c().d will return undefined if the c invocation returns null

Those points don't seem accurate to me. From the proposal:

a?.b.c().d      // undefined if a is null/undefined, a.b.c().d otherwise.
                // NB: If a is not null/undefined, and a.b is nevertheless undefined,
                //     short-circuiting does *not* apply

Wow, I totally misread that. You're right. Updating

@algesten from original proposal:

a?.()

b?.[0]

sweet. the operator can sort of be though of as ?. then.

There's some additional conversation happening over here: https://github.com/estree/estree/issues/146

A quote that could apply well: «Make simple things easy, and difficult things possible». Thus perhaps, support the most common cases well, while skip (at least initially) the complicated/rare cases, while they are still allowed to be "done manually" with longer (existing) syntax.

Just my two cents

let a = b?.c?.d?.e;

to:

let a;
try{
   a = b.c.d.e;
}catch(e){
   a = undefined;
}

@cedvdb totally different semantics -- exceptions thrown in getters should not cause coalescing

@RyanCavanaugh yeah.. I didn't think this through.

Is this on the radar for implementation now, or will the TS team wait for the ES proposal to move further along?

It is on our short list; we still have a few questions/concerns but you should see movement on this in the comming month.

Not sure where @mhegazy 's comment came from - the number of open questions on the TC39 proposal is too large for us to do meaningful work here. Specifically questions around how null and undefined interact and which syntax is actually supported need to be addressed first. Stage 2 is an absolute bare minimum and we'd prefer stage 3 given the impact on runtime behavior.

does this simply code works?

a == undefined ? expression : undefined

expression means a.x, a[x], a(x), delete could also generated here

then a?.b?.[c]?.(d) will generate

a == undefined ? (a.b == undefined ? (a.b[c] == undefined ? a.b[c](d) : undefined) : undefined) : undefined

seems will pass through all RyanCavanaugh's rule


if hate == operator, it also could be a === undefined || a === null

@zh99998 you _have_ to hate == because '' and 0 are also equated. It almost is argued that the runtime behaviour should be some sort of check on (typeof value === 'object' || typeof value === 'function' || typeof value === 'symbol') && value !== null, which is now getting to be rather complex.

As @RyanCavanaugh said, it is unlikely to progress until the TC39 Proposal for it progresses to at least stage 2 or 3.

I see == only equals null and undefined as
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Equality_comparisons_and_sameness

and test passed in chrome console:

'' == undefined
false
0 == undefined
false

@kitsonk undefined only coerce to null and vice versa. No other value coerces to undefined or null.

You seem to be confused with falsy values. 0 and "" are indeed falsy, but will never coerce to null nor undefined.

== implies coercion. null == 0 is false because nothing except undefined can coerce to null. Same goes for undefined == 0.

Another example could be

    if(!NaN) console.log("NaN is falsy") // NaN is falsy
    if(false == NaN) console.log("NaN coerces to false")
   else console.log("NaN doesn't coerce to false");// NaN doesn't coerce

You get the picture.

I see a lot of attempts using ternary operators, but any implementation that refers to the same property multiple times could result in unexpected side effects.

Fortunately, JavaScript has IIFE's, so you can store the result of an accessor in a function parameter, refer to it as much as you want and never evaluate it more than once. In my example below, I create a coalesce lambda that can be called multiple times in place of an expression containing ?. operators.

One thing the language implementors would need to concern themselves with are array and function calls in the middle of an expression. I think the compiler could handle detecting those situations and simply append those expressions onto the end of the coalesce call.

const coalesce = (x: any, y: string) => x == null ? null : x[y];

const x = {
    y: {
        z: "Hello, World!!!"
    },
    f: () => "Foo!",
    a: ["Array!"]
};

// x?.y?.z
const value1 = coalesce(coalesce(x, 'y'), 'z');

// x?.f()
const value2 = coalesce(x, 'f')()

// x?.a[0]
const value3 = coalesce(x, 'a')[0]

Note there's no need for an actual global named "coalesce". This code could be directly inlined into any expression. It might cut down on bloat giving it a name, though.

I am not too concerned with the syntax used for the operator or if the language implementors want to wait for a standard. Just thought I would show off another approach.

This also brings up the need for a ?? operator. In C#, this replaces any null expression with whatever is to the right.

string x = null ?? "Hello";
````

In JavaScript, it is more idiomatic to use `||` to replace "falsey" values with the value on the right. 

```javascript
var x = null || "Hello";

Unfortunately, truthiness captures too many edge cases (0, false, etc.). Working with a null coalesce (?.), operator, you'd want something specific to null-ness.

const x = { y: "" };
const result1 = x?.y || "default";  // I'd expect "default"
const result2 = x?.y ?? "default";  // I'd expect "" 

@jehugaleahsa, in your example with coalesce there would have to be a way to prevent function calls and member access from happening if the check before returned null. In the x?.f() example, f shouldn't be called if x is null.

@bschlenk I don't think I agree. I think it should fail with a message like null is not a function, but that's not up to me, I guess.

The recent surge in comments detailing possible ways to implement the operator is a bit weird.
The implementation is probably a solved problem.

If you are interested, there's the idx library, which closely resembles the behavior of the ?. operator, although it also ignores lots of details of the planned operator. Anyway, their specs for the compile output might be of interest for anyone wondering about the ways this stuff could be implemented.

TS could output something like that or it could output something completely different, but I don't think that's what we are waiting here. It has been said many times here that TS won't get the operator until the ES proposal moves to one direction or to the other.

It's the semantics that still has some unknowns, listed here and here.

Rest assured we'll be implementing this correctly and don't need another 100 comments here to figure out what || and ? ... : ... do. As @noppa noted we're just waiting for the ES spec to finalize.

https://github.com/babel/babel/pull/5813 (accompanying babylon PR) was just merged into Babel's preset-stage-1. The spec is still at stage 1, of course, but this will help move it forward.

Maybe I'm wrong, but I didn't see any obvious link to the tc39 proposal in this thread, so here it is for the future readers : https://github.com/tc39/proposal-optional-chaining

FYI optional chaining will be at TC39 next week https://github.com/tc39/agendas/blob/master/2017/07.md

@jehugaleahsa I think you're right and I'll be presenting ?? at TC39 next week. You can follow the proposal here: https://github.com/gisenberg/proposal-nullary-coalescing

I see that optional chaining was covered at the TC39... What was the verdict?
Hopefully it was enough to move this forward in Typescript 😉

@markwhitfeld From the notes summary:
Optional Chaining Operators: remains on Stage 1, will come back later with clearer definitions for various options and answers to feedback

Full notes here: https://github.com/rwaldron/tc39-notes/blob/master/es8/2017-07/jul-27.md#13iia-optional-chaining-operator

There are still open questions around how the operator should behave, so seems like it's not yet in a state where it can be added to TypeScript.

From those notes, it looks like the committee has no idea how the options presented work, I would've thought they'd have taken to learn about the proposals they were planning on discussing, before discussing them. I guess its going to be a while longer until I can enable strict null checks.

I very much hope they go with option 2/4 (which is the current state of the proposal anyway).

15 Jul 2014 - 4 Sept 2017, nothing yet

@frankfvb you have clearly not read the issue.

There has been a lot of discussion which has lead the core team to believe that it would be imprudent to implement at this juncture until there is further progress on the ECMAScript proposal which would directly impact the functionality of this feature in TypeScript.

As of the last meeting of the ECMAScript standards committee, the proposal is staying at Stage 1 as it has some very fundamental questions about how it would be implemented. While not a hard and fast rule, TypeScript will only implement Stage 3 proposals. It sometimes implements Stage 2 proposals if they believe it is of critical importance and potentially usage in TypeScript drives the evolution of the standard.

I am not sure how any more clear people can be about that.

As I said earlier this is on our short list. we are waiting on TC39 to reach some sort of consensus on the semantics of the operator. we would hate to put it out and then break users.

This is not the thread to rehash the TC39 discussion

If you want to weigh on in the proposal, go comment on it at the appropriate place. I have opinions too but dropping them in this thread isn't going to do anything.

I created something simple that meets my current needs. It will only work on a chain where each link is a property name, so accessing an element in an array (for example) isn't supported.

Implementing a really simple Elvis Operator in TypeScript

Also if you have lodash/underscore, you can already use _.get(Book, 'author.name.firstName') which will do what this wants

Edit: Appearently this is bad advice because of type issues with _.get() method. See comment below

@tolgaek , _.get has bad typings, even with this better typings (not yet merged, because of authors) typescript can definitely deduce type of result only if depth of object is 1, in all other cases it's any and must be checked in runtime

On other hand with elvis operator typescript may be able to infer type of result in objects with any depth, this is why Im looking forward to elvis operator

oh I see, I didn't know there was typings issue. Thanks @BjornMelgaard

@mhegazy can't this feature be implemented first and mark as experimental feature? I think people should not have problem if there is spec changed in experimental feature.

Elvis is not amused waiting this long.

It has landed in Babel7. Typescript, we are running slow.. Someone please make this happen.
:)

@gs-akhan the Babel plugin implements an old version of the proposal from a few months ago. There have been changes to the proposal since then (including a significant change to how the operator is parsed), and there likely will be more changes before the feature reaches stage 2 (let alone stage 3), so any code written with the current babel plugin could break when the actual feature is released. Babel intentionally implements proposed features before they are stable so that the spec authors and other interested parties can try out the feature as proposed. Just because Babel has implemented a feature doesn't mean it can be implemented in a way that won't require breaking changes in the future.

@alangpierce That makes sense. Thanks

I get that this is a really, really nice operator, but having it be available before its rules have been ironed out is a footgun and we're not going to do that. The runtime behavior of the operator is still in flux; if you write code today it might stop working tomorrow in ways that aren't immediately apparent - maybe rare crashes, maybe data corruption, who knows? A little patience now will save you a lot of pain a few months down the road.

Starting to think this ticket should be locked (not closed) until the spec is ready.

When is the spec going to be ready?

@oliverjanik The current draft specification can be found here. There's an agenda item to advance the proposal to stage 2 at the next TC39 meeting (9/26-9/28). I'll be presenting these slides at that time. For folks who'd like to review early and provide feedback, please file issues on the proposal repo.

Thank you so much @gisenberg for championing this issue for us! I was thinking about putting together a summary slide deck to help clear up the options around the operator that could be used at the TC39 meeting as to avoid confusion, but you have done this already. Awesome work!
Maybe just one other thing that could be beneficial to the TC39 conversation (and slide deck) is to look at the semantics and syntax of the operator in other languages. Although other languages should not necessarily dictate how it should work in Javascript, it would be helpful to keep the operator similar to that of other languages as to avoid confusion.
Good luck for next week!!!

Sorry for going a bit off topic once again, but I figured some folks here could find it interesting that in Flow it's now possible to add somewhat working type definitions for safe getter functions like _.get.

Example: flowtype.org/try

Not the prettiest piece of code ever and it doesn't correctly distinguish between null and undefined, but other than that it seems to work quite nicely.

AFAIK the only thing that's missing from TS for it to do the same is something like that $NonMaybeType.
Not that it would remove the need for this operator, of course, I just thought that was cool.

This did not reach Stage 2 at the latest TC39 meeting due to concerns about syntactic consistency regarding bracket vs dot vs call access and semantic behavior (predictability around side effects of expressions to the right of an undefined/null-evaluated operand) as well as open questions about what kind of expressions are allowed to be no-op'd (e.g. x.?b() when x.b is a number)

(vote 🎉 on this comment to throw rotten fruit)

Thanks for letting us know. What a bummer. Maybe the scope needs to be narrowed so it's simpler but still useful?

Maybe the scope needs to be narrowed so it's simpler but still useful?

This is the challenge that TC39 faces, which, while sucky, I have to admit I am glad people are going through this. It is really hard for them to introduce a fairly complex level syntax and in fact, in this case the weak typing of the language is actually causing a significant amount of edge cases that have to be addressed, or you get code that goes 💥 which isn't good for anyone. I don't think if you introduce an operator like this you can actually narrow its scope. It would be easier for the implementors to cover 90% of the cases, but I don't think ¯\_(ツ)_/¯ is valid code for the remaining 10%.

Guys, 3+ years later, I'd say it's time to say "fuck you" to the committee and do it our own way, whatever is idiomatic to TypeScript. This feature cannot work properly without static typing anyway.

Make a TypeScript implementation with a syntax that is explicitely incompatible with the TC39 proposition. Once ES gets a safe-nav operator, TypeScript will have two options: one with shitty semantics that's compatible with ES, and one that is tied to the type system. This means that you cannot use the TS safe-nav operator with the any "type", and that's just fine.

@notsnotso Typescript is not supposed to break JS, that's what Coffeescript is for.

Maybe good solution is to:

  • implement very simple ? operator for impatient devs, as experimental feature (like decorator), with warning - it WILL break your code in future
  • wait another 3 years when standart will be written, implement as not-experimental feature. Write tutorial, what could be broken. Even, with static typing, it's possible to write warnings when people will compile code with new operator implementation.
    "do it our own way, whatever is idiomatic to TypeScript" is not a case, because in 3 years people will face with problem, that ts elvis doesn't work same way as in js.

I am not surprised this did not go though, to be honest. In my original
post, I pointed out the ambiguity regarding indexing, function calls, etc.
Honestly, the more I think about ?., the more it feels like it doesn't
belong in JavaScript, since it has undefined.

I would much rather a more general-purpose utility that solved this problem
and more. One thought I had was something like an inline try/catch
expression (let x = try a.b.c else 0) in conjuction with an operator that
checked for "nully " (e.g., x ?? 1) rather than "falsey" (e.g., x || 1).
So, you'd combine them like this: try a.b.c ?? 0 else 0. It's wordy, yes,
but it basically says: Try to evaluate a.b.c and if the result is null or
undefined, return 0. If a or b is undefined and an exception is thrown,
catch it and return a 0.

An alternative would be to make the else clause optional, defaulting to
undefined. Then you could write the expression as: let x= (try a.b.c) ?? 0. That's pretty darn compact, avoids the ambiguity and provides a more
general-purpose solution that can solve for other problems.

I'm not saying we should push for this instead; all I am saying is there
are alternatives to ?. and we should really explore our options until we
find one that fits well with the JavaScript language.

On Thu, Oct 5, 2017 at 7:51 AM, Dmitry Radkovskiy notifications@github.com
wrote:

@notsnotso Typescript is not supposed to break JS, that's what
Coffeescript is for.


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/Microsoft/TypeScript/issues/16#issuecomment-334441781,
or mute the thread
https://github.com/notifications/unsubscribe-auth/ABTgPilbZfuKc2egdBrYfdTHHeDl3F6Sks5spMLLgaJpZM4CNapf
.

@zlumer nice joke. That said, a TS specific feature would not break Javascript.

@jehugaleahsa I like your proposal, and it's found in other languages. I think it would work just fine for TS.

I will say I don't really understand the strong need to wait until ECMAScript accepts the operator. TypeScript added classes, modules, lambdas, all long before they found their way into ECMAScript. In fact one of the stated goals of TypeScript was/is piloting experimental JS features. TypeScript providing its own implementation would no doubt help inform these discussions at the committee.

You guys have been willing to take breaking changes before when the committee eventually goes a different direction, I don't really see why this feature is so different.

async/await was also introduced to TypeScript at an early stage.

That said, a TS specific feature would not break Javascript.

There won't be something like "TS specific feature", please check TypeScript Design Goals for more information.

TypeScript arrives before ES going to a bright path, there could be some existing ones,
they're just kept for compatibility.

TypeScript added classes, modules, lambdas, all long before they found their way into ECMAScript.

And modules in particular are a huge debacle that still cause no end to confusion. That is an example that the TypeScript team have used repeatedly as an example of a _mistake_ and jumping the gun too early. We also have private fields looming now, which again, I have been thankful for private for the past 5 years but it is going to cause no end to confusion.

Again, decorators have been available under the flag, but now that decorators are actually getting to Stage 3, it will require a re-implementation and code breakage for TypeScript. That is why these things _cannot_ be taken likely.

async/await was also introduced to TypeScript at an early stage.

Once the ECMAScript proposal reached Stage 3.

No one's misbehaving but if this conversation can't go anywhere but in circles (what if there were a flag? Yes we are aware of flags) or off-topic (what if TS stops being JS? No five years later we are not changing our mind on that), we don't need to be having it. We've said for about 3 years now that we will implement this exactly when the ES committee locks down its semantics.

Once again, the proposal repo is https://github.com/tc39/proposal-optional-chaining and you can follow its progress there. We'll be working offline to try to improve the proposal's chances at the next TC39 meeting because we really want this to go through too.

Update: We reached stage 2 this afternoon at TC39!!!

Optional Chaining is Stage 3

Briefly unlocking this one only for celebratory purposes

Hooray!

Don't spam...

You can send an emoji expressing your feelings

Don't spam...

You can send an emoji expressing your feelings

Plus, it's fun watching the emoji count climb like this. I've never seen an issue comment gain so much popularity so fast!

I recorded a small video of the real-time updates https://youtu.be/JLBrgPjeGhc

Can somebody unsubscribe me from this thing?

@DanielRosenwasser In case you're not joking, or for anyone else who wants to unsubscribe and doesn't know how, you're looking for this button in the right sidebar:

image

Not to mention there's a link in the email:

image

It was a joke, I'm working on the proposal.

@RyanCavanaugh after unlocking this issue:
martian

Really excited to see this finally land! 🎈 🎉

Can't wait for the matching VSCode quick fix 😆

image

I am going to @ @RyanCavanaugh because he is probably unsubscribed to this thread and I want to be rude! (and @DanielRosenwasser for good measure)

@kitsonk Don't be an ass. People are free to unsubscribe and not be harassed.

@kitsonk, Why would RyanCavanaugh or DanielRosenwasser be unsubscribed to this thread? Ryan unlocked this issue and Daniel responded three comments above you.

Even if they were unsubscribed there is no need to cause more notification fatigue by spamming them.

Apparently GitHub is a terrible place for sarcastic humor, gee...

Big thanks to our champions at TC39 for figuring out the nasty details of this new language feature!

thanks

I think @kitsonk was just joking around, just as I sort of was. While we're being a little silly around the excitement, this is a gentle reminder to keep things civil as per the CoC.

@DanielRosenwasser
Could I have to take a try on this? Or @rbuckton has yet another existed branch for it 🙋🏻‍♂️


Ok,i got it https://github.com/microsoft/TypeScript/commits/optionalChainingStage1 🤦🏻‍♂️

Yeah, that's fairly out of date, unfortunately.

I've just realized this issue is opened 5 years ago. :astonished:

@rbuckton

Yeah, that's fairly out of date, unfortunately.

Could I have a try on this?

Sorry @jhpratt @MatthiasKunnen I forgot we don't all share the same context on GitHub. I've been bouncing around here for a long while and have spent time with Ryan and Daniel IRL and briefly participated in the recent event that gave rise to my misunderstood inside joke. Apologies.

This whole issue though shows an interesting archeology of the design principles of TypeScript. Ryan raised it at a time where TypeScript _might_ have actually considered syntax that pre-dated serious consideration in ECMAScript. It was around that time though where TypeScript internally was learning some lessons of predicting ES2015 syntax that were having serious impacts on the language. The team decided to not consider inclusion until there was a TC39 Stage 3 proposal.

While @RyanCavanaugh can address it, and I know he has been close to what has happened with the proposal, I suspect it would have been unlikely what the team would have implemented back in 2014 would have been 100% compatible with the current Stage 3 proposal. So while it certainly is a celebration, also be thankful that we don't have 5 years of TypeScript code out that with a safe navigation operator that would not be entirely consistent with the behaviour in the proposal.

🙏

westwing

Is this the moment to remove Waiting for TC39 label? 🤔

Is this the moment to remove Waiting for TC39 label? 🤔

And add ship-it

wowowow

Finally. Literally was asking around about optional chaining y'day, wondering when will it be stage 3, and bam! Thanks everyone who were working to push it through!

How to mute this thread? :)

@opnksyn If you don't care about the spammy excitement, there is a feature to get notified when this issue is closed. This will prevent all the comments from showing up in notifications like the one you just made 😄

image

image

Any emit solution already defined?
Something like this could be interesting:

function __chain<T extends object, U>(value: T|null|undefined, callback: (value: T) => U): U|undefined {
    if (value !== null && value !== undefined) {
        return callback(value);
    }

    return undefined;
}

type Foo = { x?: { y?: { z?: () => number } } }

const foo: Foo|null = { }

// foo?.x?.y?.z?()
const value = __chain(foo, _a => __chain(_a.x, _b => __chain(_b.y, _c => __chain(_c.z, _d => _d()))));

I prefer what Babel does as it avoids creating unnecessary function scopes:

var _foo, _foo$x, _foo$x$y, _foo$x$y$z;

// foo?.x?.y?.z?.()
(_foo = foo) === null || _foo === void 0 ? void 0
    : (_foo$x = _foo.x) === null || _foo$x === void 0 ? void 0
    : (_foo$x$y = _foo$x.y) === null || _foo$x$y === void 0 ? void 0
    : (_foo$x$y$z = _foo$x$y.z) === null || _foo$x$y$z === void 0 ? void 0
    : _foo$x$y$z.call(_foo$x$y);

I prefer what Babel does as it avoids creating unnecessary function scopes:

var _foo, _foo$x, _foo$x$y, _foo$x$y$z;

// foo?.x?.y?.z?.()
(_foo = foo) === null || _foo === void 0 ? void 0
  : (_foo$x = _foo.x) === null || _foo$x === void 0 ? void 0
  : (_foo$x$y = _foo$x.y) === null || _foo$x$y === void 0 ? void 0
  : (_foo$x$y$z = _foo$x$y.z) === null || _foo$x$y$z === void 0 ? void 0
  : _foo$x$y$z.call(_foo$x$y);

I agree, @ExE-Boss. I feel that avoiding creating unnecessary function scopes is ideal (even if it makes the compiled code a bit ugly)

Performance and adherence to the ES specification should definitely win out over readability when we’re talking about compiled code.

Is there any reason the code compiled by Babel compares to both null and void 0 with triple equals instead of a simple == null?

Is there any reason the code compiled by Babel compares to both null and void 0 with triple equals instead of a simple == null?

@proteria I just took a quick look at the optional chaining code for __Babel__; it seems that if you pass in the loose option and set it to a truthy value, it doesn’t produce the strict equality checks

That's because of document.all (or to be pedantic, the [[IsHTMLDDA]] internal slot), a quirk that gets special treatment in the language for backwards compatibility.

document.all == null // true
document.all === null || document.all === undefined // false

In the optional chaining proposal

document.all?.foo === document.all.foo

but document.all == null ? void 0 : document.all.foo would incorrectly return void 0. In loose mode, this detail of the spec is dismissed for simplicity/performance/generated code size as most people don't have to deal with document.all anyway.

Surely the document.all case could be special? It shouldn't require much extra code, just a few lines to check the object and property.

Except that document.all can be assigned to a variable, and tracking where it’s used requires a type system, which is why Babel outputs by default:

(_prop = prop) === null || _prop === void 0 ? void 0 : _prop./* do stuff */;

I'm aware of that. Babel doesn't have a type system, TypeScript does. Perhaps it's not as simple as I made it sound, but I imagine there's already code for certain situations that is capable of tracking usage.

Actually, you don't need a type system to track document.all variables, as the special behaviour is actually on HTMLAllCollection, not document.all.

So you should just be able to do an instanceof HTMLAllCollection check, and you'll be golden.

Yeah but... why'd you do instanceof when you can just do === null || === void 0? Surely that's more simple.

For sure - I was just pointing out you don't need a type system to track document.all :)

Personally I'm tempted to say just break it and see who complains, but it's in the spec, so easiest to just stick with that.

@noppa It can be performed at compile time. If foo instanceof HTMLAllCollection is true, emit foo === null || foo === void 0, otherwiise we can _safely_ emit foo == null.

@G-Rath Like it or not, deprecated doesn't mean it shouldn't work. TypeScript should remain compatible with JavaScript.

@jhpratt But that currently goes against the TypeScript Design Non‑Goals.

Also, you’d still have to do foo === null || foo === void 0 for anything to which HTMLAllCollection could be assigned, eg. any or object, so I don’t think it’s really worth it.

I presume you're referring to non-goal (5)

Add or rely on run-time type information in programs, or emit different code based on the results of the type system. Instead, encourage programming patterns that do not require run-time metadata.

Though I agree this would certainly emit different code based on the type, it's only to reduce code size. Though as you've pointed out, it's not quite as simple as checking for HTMLAllCollection.

To be fair, TS _has_ rejected a potential minifier that uses type information, and this is (sort of) related — the primary reason to emit == null is to reduce code size based on type information.

If this _isn't_ implemented, it would be great if the lang team adds an option to tsconfig similar to Babel's "loose" option.

After quickly checking, terser automatically converts foo === null || foo === undefined to foo == null, which isn't safe due to this edge case.

If this isn't implemented, it would be great if the lang team adds an option to tsconfig similar to Babel's "loose" option.

Relatedly, many of us use TypeScript for build tools, and for mobile applications, none of which have to worry about browser constraints!

In fact, we use both TS & Babel together, so maybe one of these options should be to passthrough the operator to Babel/underlying runtime!

@fbartho

In fact, we use both TS & Babel together, so maybe one of these options should be to passthrough the operator to Babel/underlying runtime!

I don't understand this comment. You don't need any extra options to passthrough the operator to Babel; if you have TypeScript set up for Babel, you already have noEmit: true which already passes _everything_ through to Babel.

@Zarel Babel’s TypeScript implementation is missing several features that our codebase was already relying on, including namespaces and const enums. We’re using TSC with emit enabled, and applying Babel as a second transformation. (We’re working on getting rid of the namespaces, but it’s unclear if we’ll ever be able to get rid of all of the mismatched features)

People coming to this thread should start at the earlier stage 3 announcement and read the comments starting there (blame GitHub for hiding tons of user content with no straightforward way to load everything)

Great feature - "optional chaining" / "safe navigation". Especially in TypeScript strict-mode. Awesome to hear that this will be implemented soon. ❤️

This brought me here and I hope it will be supported. Just an use case:

Expected in TypeScript 3.7.

document.querySelector('html')?.setAttribute('lang', 'en');

VS

Currently in TypeScript 3.5.

const htmlElement = document.querySelector('html');
if (htmlElement) {
  htmlElement.setAttribute('lang', 'en');
}

Will this work without any errors? Or is this still a TypeError: Cannot read property 'setAttribute' of null.? The ? op. should be cancel further chains after null / undefined.

class Test {
  it() {
    console.log('One');
    document.querySelector('html')?.setAttribute('lang', 'en');
    console.log('Two');
  }
}
new Test().it();

I expect following:
If html element does not exist (null). The console should logged One and Two, and the setAttribute method is not tried to invoked. (No errors).
Did I understand that correctly?

@domske FYI, this isn't strictly a TS feature; it's a JS feature.

According to the TC39 proposal the syntax will be:

document.querySelector('html')?.setAttribute?.('lang', 'en');

The discussion has started to go circular again so we're back to locked state.

I truly beg anyone tempted to leave a comment in a 100+-comment long GitHub thread to really commit to reading all the prior comments first. Probably your question, and the answer to it, will be found there!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

weswigham picture weswigham  ·  3Comments

Roam-Cooper picture Roam-Cooper  ·  3Comments

jbondc picture jbondc  ·  3Comments

CyrusNajmabadi picture CyrusNajmabadi  ·  3Comments

manekinekko picture manekinekko  ·  3Comments