Skip to content
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

Design Meeting Notes, 6/21/2019 #32158

Closed
DanielRosenwasser opened this issue Jun 28, 2019 · 25 comments
Closed

Design Meeting Notes, 6/21/2019 #32158

DanielRosenwasser opened this issue Jun 28, 2019 · 25 comments
Labels
Design Notes Notes from our design meetings

Comments

@DanielRosenwasser
Copy link
Member

Optional chaining update and feedback

https://github.com/tc39/proposal-optional-chaining/
tc39/proposal-optional-chaining#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

#31156
#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
@DanielRosenwasser DanielRosenwasser added the Design Notes Notes from our design meetings label Jun 28, 2019
@hax
Copy link

hax commented Jun 28, 2019

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

@fatcerberus
Copy link

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! 😄

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

@DanielRosenwasser
Copy link
Member Author

DanielRosenwasser commented Jun 28, 2019

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

@poseidonCore
Copy link

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.

@DanielRosenwasser
Copy link
Member Author

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.

@fatcerberus
Copy link

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’ve found. You would think the existence of intersection types (implying that overlap is possible) would clue people in, but alas...

@fatcerberus
Copy link

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?

@RyanCavanaugh
Copy link
Member

@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'.

@fatcerberus
Copy link

fatcerberus commented Jul 1, 2019

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.

@andrewbranch
Copy link
Member

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’m not sure if we want that kind of logical branching—maybe it’s better just to keep it in its current slightly wrong but consistent state.

@michaelficarra
Copy link

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.

@DanielRosenwasser
Copy link
Member Author

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.

@ppjjzz
Copy link

ppjjzz commented Jul 26, 2019

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

@DavidBabel
Copy link

@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.

@noru
Copy link

noru commented Jul 29, 2019

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.

@DavidBabel
Copy link

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.

@noru
Copy link

noru commented Jul 31, 2019

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.

@hax
Copy link

hax commented Jul 31, 2019

@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
Copy link

noru commented Jul 31, 2019

@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.

@hax
Copy link

hax commented Jul 31, 2019

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.

@noru
Copy link

noru commented Jul 31, 2019

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...

@FrankFang
Copy link

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

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

@w0rp
Copy link

w0rp commented Aug 5, 2019

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.

@fr0
Copy link

fr0 commented Aug 9, 2019

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?

@weswigham
Copy link
Member

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Design Notes Notes from our design meetings
Projects
None yet
Development

No branches or pull requests