Typescript: Design Meeting Notes, 6/21/2019

Created on 28 Jun 2019  路  25Comments  路  Source: microsoft/TypeScript

Optional chaining update and feedback

https://github.com/tc39/proposal-optional-chaining/
https://github.com/tc39/proposal-optional-chaining/issues/59

  • [[Review of feature, short-circuiting behavior, use-cases for optional call, nullish vs. uncallable behavior for optional call]]

Issues with narrowing unions by union types

https://github.com/microsoft/TypeScript/issues/31156
https://github.com/microsoft/TypeScript/pull/31206

  • Basically if you have a union type, narrowing down doesn't work if you are narrowing from unions with unions where some constituents of a union are subtypes of the other union.

    • For example, narrowing string | number to "hello" | number.

    • You end up with intersections today.

  • Cat & Mortgage seems unlikely, but it's indistinguishable from MixinA & MixinB.

    • Is that really common?

    • It happens in the compiler.



      • Whenever you have a non-discriminated union, things go wrong.



  • hasType exhibits some issues here, and the new behavior catches this.
  • But the new behavior explodes in types, is usually not the intent for many cases, and creates impossible branded literal types.
  • Why do we even use intersection types - can we do somethig more precise from the original example?

    • Necessary to work with object types.

  • A lot of user intent seems to indicate people think of types as closed.

    • Keeps coming up.

  • On a meta level, we need to think about this at a more holistic level, see what the interplay would be between negated types, closed types, etc.
  • Conclusion: we don't think we have any idea of how to cleanly solve the current problem. No action chosen. We may
Design Notes

Most helpful comment

Optional Chaining is Stage 3 now.
Is TS team going to implement optional chaining soon?

All 25 comments

Is TS team going to implement optional chaining soon? (Maybe here is the wrong place to ask, but related issues have been locked :-)

Conclusion: we don't think we have any idea of how to cleanly solve the current problem. No action chosen. We may

Well? You may... what? Don't leave us hanging here! :smile:

@hax Optional chaining is Stage 2. TS typically doesn't implement proposed syntax until it hits Stage 3.

Ugh, it's been a week but I assume it's probably just "revisit if we come up with other ideas." @andrewbranch?

Actually, I thought the trailing "We may" was very Yoda of you.

But the new behavior explodes in types, is usually not the intent for many cases, and creates impossible branded literal types.

I'm wondering for the sake of performance, whether it is useful to implement some overflow types in these situations such that they widen automatically as aliases of related basic types (eg string or any) if the explosion passes certain limits, thereby allowing them to collapse back to simpler type expressions.

At least then we might avoid performance issues and the programmer can see the underlying problem rather than a huge jumble of type combinations.

It's possible, but it could lead to a lot of unexpected results with behavior that's hard to predict. Also, we often don't know that an operation will cause the creation of many types until you perform the operation itself.

A lot of user intent seems to indicate people think of types as closed.

Excess property checking in object literals is a big contributor to this misconception, I鈥檝e found. You would think the existence of intersection types (implying that overlap is possible) would clue people in, but alas...

To expand on the above point re: closed types, I think the error message for the excess property check is misleading:

interface FooBar {
  foo: string;
  bar: string;
}

let x: FooBar = {
  foo: "pig",
  bar: "cow",
  baz: "ape",  // error here
};

let tmp = {
  foo: "pig",
  bar: "cow",
  baz: "ape",
}
let y: FooBar = tmp;  // this is fine

The error message for the assignment to x is:

Type '{ foo: string; bar: string; baz: string; }' is not assignable to type 'FooBar'.
  Object literal may only specify known properties, and 'baz' does not exist in type 'FooBar'.

"Not assignable" sounds like a blanket statement, but clearly it is assignable, since assigning the exact same type through tmp works; we've just made an exception for direct object literals in order to catch typos. To be fair, it does say "Object literal may only specify known properties", but the position of that text makes it appear like a footnote and easy to miss. Could we improve this error message to make it clearer that this is a special case and not the general statement about object assignability it looks like?

@fatcerberus in a technical sense, the error message is correct - the fresh type '{ foo: string; bar: string; baz: string; }' is not assignable to type 'FooBar'.. But we don't show freshness in type printback, so it does seem wrong.

We could perhaps say something like

Type of object literal '{ foo: string; bar: string; baz: string; }' is not assignable to type 'FooBar'.
  Object literal may only specify known properties, and 'baz' does not exist in type 'FooBar'.

I would probably just say Object literal '{ ... }' is not assignable to..., without mention of "type" at all. I'd want to make it very clear in the error message that this case only applies because the assignment is from a fresh object literal and is NOT a statement about structural typing in general.

We may

Ugh, it's been a week but I assume it's probably just "revisit if we come up with other ideas."

Sounds right to me 馃槃

Or possibly it was referring to what I posted in the issue?

I may look into doing the correct thing when all constituents of both the original type and the predicate type are primitives, since intersections of those are more intuitive and fall out when empty. On the other hand, I鈥檓 not sure if we want that kind of logical branching鈥攎aybe it鈥檚 better just to keep it in its current slightly wrong but consistent state.

nullish vs. uncallable behavior for optional call

@DanielRosenwasser Can you point me to discussions on this topic? My intuition would be that an optional call feature would test for callability, not nullability. But I'd love to be convinced otherwise. FYI CoffeeScript's optional call feature tests for callability.

If optional call tests for callability, then based on the semantics of optional property access, you'd be propagating out undefined when you have an uncallable value. In other words, false?.() gives you undefined. That doesn't really seem like useful behavior to anyone I've discussed the feature with.

In my opinion it makes the most sense to keep optional call consistent with optional property access in that they both only propagate undefined from null/undefined.

Optional Chaining is Stage 3 now.
Is TS team going to implement optional chaining soon?

@ppjjzz you was faster than me :)

Here is the reference : https://github.com/tc39/proposal-optional-chaining
Can't wait to see it in typescript.

It is not necessarily a good thing for TS. I don't mind TS to leave it alone (and break the "superset of ecmascript" promise). On the other hand, I'd be very upset if it were introduced in a lousy way.


I'm not saying optional chaining is bad. It is definitely good for JS due to its dynamic (unsafe) feature. That, may not the case in TS world, not without drawbacks.

As you said, TS is a superset of JavaScript, it does not have to miss futur JS features.
If it's not the best feature to come, it's still one, and people are not forced to use it.
But for me it's a great one.

As you said, TS is a superset of JavaScript, it does not have to miss futur JS features.
If it's not the best feature to come, it's still one, and people are not forced to use it.
But for me it's a great one.

I respect that. However don't you think the PL guys should consider more than 'people are not forced to use it'? Isn't any feature like that?

Here's a simple example:

interface Something{
  p1: Other
}
interface Other {
  p2: string 
}
let s: Something = ...

// are you going to let me write this? 
// Either way, you are breaking something: the typing or the superset thing 
s.p1? .p2? 

I'd believe the reality would be much more complicated, as this note says.

@noru As I understand, TS should not allow you write s.p1?.p2 because s.p1 is never nullish. It is only allowed if the interface Something is:

interface Something{
  p1?: Other
}
// or
interface Something{
  p1: Other | null
}
// or
interface Something{
  p1: Other | null | undefined
}

@noru As I understand, TS should not allow you write s.p1?.p2 because s.p1 is never nullish.

Yeah I'd like to see that too. But therefore TS goes separate way with js and drop the runtime null-safe guarantee that js provides.

drop the runtime null-safe guarantee that js provides.

I'm not sure I understand what you mean. Do you mean s.p1 could be nullish in JS runtime even TS never allow it in compile time? It's possible anyway, but I don't think it's a good reason to abuse ?. to write everything like a?.b?.c?.d?.e.

But I don't think it's a good reason to abuse ?. to write everything like a?.b?.c?.d?.e.

Again, I agree. But I don't see means to prevent such abuse. That's why I said I don't mind not having it in TS when types already solved 80% of my problems.

I think the question is, how to enable full support of optional chaining without hurt the existing types. It may be a mission impossible...

Again, I agree. But I don't see means to prevent such abuse

TypeScript could give tips for such abuse I believe. @noru

Optional chaining is going to be great, and I look forward to seeing in TypeScript. It will simplify a ton of the code I write in a team every day.

Optional chaining is going to be great, and I look forward to seeing in TypeScript. It will simplify a ton of the code I write in a team every day.

Definitely. Is it fair to ask whether it is reasonable for us to expect this in 3.6? Or is that too soon?

Or is that too soon?

Too soon. We cut the 3.6 beta 4 weeks ago, and the RC is in the next few days.

Was this page helpful?
0 / 5 - 0 ratings