Skip to content

Allow destructured object entries with default parameters not to be optional #51179

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

Closed
4 of 5 tasks
skondrashov opened this issue Oct 14, 2022 · 10 comments
Closed
4 of 5 tasks
Labels
Declined The issue was declined as something which matches the TypeScript vision Suggestion An idea for TypeScript

Comments

@skondrashov
Copy link

skondrashov commented Oct 14, 2022

Suggestion

πŸ” Search Terms

default value optional object destructure

βœ… Viability Checklist

My suggestion meets these guidelines:

  • This wouldn't be a breaking change in existing TypeScript/JavaScript code
  • This wouldn't change the runtime behavior of existing JavaScript code
  • This could be implemented without emitting different JS based on the types of the expressions
  • This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, new syntax sugar for JS, etc.)
  • This feature would agree with the rest of TypeScript's Design Goals.

⭐ Suggestion

If a default value is set on a required field in an object, I wish Typescript allowed an undefined value to be passed in without throwing an error.

I don't think this would be a breaking change because it will only affect situations that are currently errors.

I feel like there's probably something fundamentally weird about the way I'm thinking about it, but I wanted to at least create an issue so that other people could find it, as I wasn't able to find anything on this.

πŸ“ƒ Motivating Example

const {a = 1, b}: {a: number; b: number} = {b: 1};

This throws an error, but I believe that this is readable, and as far as I understand it Javascript could never allow the value of a to be undefined in such cases.

πŸ’» Use Cases

The primary use case is for React prop interfaces:

interface Props {
  a: number;
  b: number;
}

const Component: React.FC<Props> = ({a = 1, b}) => {
  return <>{a + b}</>;
};

<Component b={1} />;

This shows an error unless a is made optional, but it doesn't seem like there's anything wrong or confusing about this to me.

@RyanCavanaugh RyanCavanaugh added Suggestion An idea for TypeScript Declined The issue was declined as something which matches the TypeScript vision labels Oct 14, 2022
@RyanCavanaugh
Copy link
Member

Previously discussed at #30488.

By design, type annotations are always the last word on anything. This is important for predictability and for performance.

@RyanCavanaugh RyanCavanaugh closed this as not planned Won't fix, can't repro, duplicate, stale Oct 14, 2022
@skondrashov
Copy link
Author

skondrashov commented Oct 14, 2022

For those unable to follow, like me without a lot of head-scratching:

@RyanCavanaugh seems to be saying that the following two lines should both throw the same error:

const {a = 1}: {a: number} = {a: undefined};
const {a = 1}: {a: number} = {};

I respectfully disagree, I don't think either of them should throw an error, since it is promised that a will be a number and so that is what we get. My way of thinking about it seems quite reasonable unless we're allowed to put type hints in the destructuring syntax (which I was able to find the issues for, I don't know how I didn't come up with the one you linked). Then there would be a way to distinguish between these cases, though I still don't understand why one would mind if the first one was allowed (maybe a case where you reuse the assignment operator in a statement to chain it in a weird way?):

const {a = 1}: {a: number} = {}; // error cause we're expecting an object that contains a as a number, which {} is not
const {a: number = 1} = {}; // fine cause we're implicitly expecting any object, and we know that a is guaranteed to be a number

Personally I'd want to see a case where it's ambiguous what the author intended and the transpilation is more complicated than just stripping out everything after the colon, which is generally what I think of as the defining feature of typescript. I don't know about performance but it seems unlikely that you need a whole bunch of overhead just to remember whether the destructuring had a default parameter or not when you check if an undefined value was passed in.

@RyanCavanaugh
Copy link
Member

RyanCavanaugh commented Oct 14, 2022

Personally I'd want to see a case where it's ambiguous what the author intended

I think the intent of this line is extremely clear:

const {a = 1}: {a: number} = {};
  1. I want to initialize a binding with an object
  2. The type of the value that must initialize the binding is { a: number }
  3. If the type doesn't match, I have made a mistake and should see an error
  4. After that, if the value for a is undefined, set it to 1

Step 3 is unambiguous and what happens at step 4 is immaterial. Further proof of this intent is counterfactual: had I wanted a to be optional, I could have written { a?: number } instead, but I didn't.

@skondrashov
Copy link
Author

skondrashov commented Oct 14, 2022

I think the disconnect is that as a maintainer, you see the behavior of a type annotation as saying something about what objects can enter the zone it's responsible for, but as a user, I am thinking of its purpose as giving me guarantees about the code that follows. So to me as long as there's no earthly way for me to interfere in the middle of an expression, I want the annotation to tell me what types the expression gives me, not what I can pass into it.

This might be the wrong way of thinking about it but it's the only use typescript has to me if I'm coding, I don't really care about initializing bindings with an object I just want the stuff in the object.

@skondrashov
Copy link
Author

const {a = 1}: {a: number} = {};

self-evidently and unambiguously (:p) says that we are destructuring an object and that what we'll have after is a number called "a", which is true

@RyanCavanaugh
Copy link
Member

It depends on what you think destructuring is a sugar for. If you wrote

const obj: {a: number} = {};
const a = obj.a === undefined ? 1 : obj.a;

then the type error is still there.

TS is consistent about this view of what destructuring means:

const {a = 1}: Partial<{a: number}> = {};
a // correctly number, not number | undefined

Obviously there are multiple ways to interpret the situation, but both have trade-offs and your view of this being an incompleteness is someone else's view of a possible lack of error here being an unsoundness.

@skondrashov
Copy link
Author

skondrashov commented Oct 14, 2022

Yeah that's what I'm trying to get at, you said this was "discussed" but in both cases it was just a post by you that's basically impenetrable to users. I understand the need for maintainers to have a consistent philosophy, I just think it's helpful to have it written out what the disagreement is because users rarely have to think of it that way.

const {a = 1}: {a?: number} = {};

The fact that this does not result in a number | undefined reads strangely.

@RyanCavanaugh
Copy link
Member

I'm not sure what you want me to do?

@skondrashov
Copy link
Author

I wanted you to make that comment that explains it! Thank you, unironically!

@RyanCavanaugh
Copy link
Member

Happy to help πŸ˜…

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Declined The issue was declined as something which matches the TypeScript vision Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests

2 participants