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

An initialized binding element should be optional even if its parent is initialized too #2469

Closed
JsonFreeman opened this issue Mar 23, 2015 · 7 comments
Assignees
Labels
Bug A bug in TypeScript Fixed A PR has been merged for this issue Spec Issues related to the TypeScript language specification

Comments

@JsonFreeman
Copy link
Contributor

function foo({ x, y = {}} = { x: 0, y: {}}) {

}

foo(); // Fine
foo({ x: 0, y: 0 }); // Fine
foo({ x: 0 }); // Error

The last line should not be an error because the y property has a default initializer. The error goes away if I remove the default initializer { x: 0, y: {}} on the parent.

I think the rule would be that even if we get the type of a parameter / variable / binding element from an initializer, we still have to traverse the binding pattern to determine whether binding properties are optional.

@JsonFreeman JsonFreeman added Bug A bug in TypeScript Spec Issues related to the TypeScript language specification labels Mar 23, 2015
@mhegazy mhegazy added this to the TypeScript 1.6 milestone Mar 23, 2015
@JsonFreeman
Copy link
Contributor Author

Note that you can observe this in another way too. If you remove the y from the parameter initializer, then you get an error on the binding element instead of the call site. But it's really the same issue:

function foo({ x, y = {}} = { x: 0 }) { // Error that { x: number } doesn't have a y property

}

@mhegazy mhegazy removed the Bug A bug in TypeScript label May 27, 2015
@ahejlsberg
Copy link
Member

@mhegazy Curious why you removed the bug label?

@DanielRosenwasser DanielRosenwasser added the Bug A bug in TypeScript label Jul 23, 2015
@ahejlsberg
Copy link
Member

@JsonFreeman I don't think this is a bug. The order of precedence for determining the type of a variable or parameter is:

  1. Type annotation
  2. Initializer
  3. Type implied by binding pattern (if applicable)

Consider this example:

interface Foo { x: number, y: string }
function f({ x, y = "hello" }: Foo) {
}

It would be odd indeed if the type of f's parameter isn't Foo as specified in the type annotation, but rather { x: number, y?: string } because y is magically made optional. I think the same logic applies to this example:

interface Foo { x: number, y: string }
var foo: Foo = { x: 0, y: "test" };
function f({ x, y = "hello" } = foo) {
}

And, by extension, to this example:

function f({ x, y = "hello" } = { x: 0, y: "test" }) {
}

Furthermore, if a parameter has a type annotation or an initializer, it's type shouldn't be impacted by whether the parameter itself is specified as a simple identifier or a binding pattern. The two should be interchangeable with no effect on the type. (Indeed, refactorings and/or the declaration file generator might depend on this invariant.)

That said, we could potentially consider making properties optional in object literals if they are optional in the contextual type (which would apply here). I'm not really sure it's worth it, though.

@JsonFreeman
Copy link
Contributor Author

It's funny. Reading your three examples, I actually think the first one should just be Foo, as you say. But in the second and third, y feels like it should be optional. The way I am thinking of it, a type annotation is a firm indication of the type. But an initializer, while it does specify the types of the binding elements, it doesn't tell you the whole story about optionality. I feel like it's not as strong an indicator of the type. And so when there is an intializer, but no type annotation, I feel like we do want to keep descending into the binding pattern to "optionalize" the elements that have their own initializers.

Furthermore, if a parameter has a type annotation or an initializer, it's type shouldn't be impacted by whether the parameter itself is specified as a simple identifier or a binding pattern.

Again, for a type annotation, I agree. For an initializer, I agree only if you converted the identifier to a binding pattern without adding initializers inside.

As a side note, converting an identifier to a binding pattern may indeed affect the type because of tuples:

(x = [0, 1, 2]) => { } // Array type
([x, y, z] = [0, 1, 2]) => { } // Tuple type

@DanielRosenwasser
Copy link
Member

The order of precedence for determining the type of a variable or parameter is:

  1. Type annotation
  2. Initializer
  3. Type implied by binding pattern (if applicable)

@ahejlsberg Is that actually the case? In the following example, param is contextually typed as a string, and the local variable x has the type (s: string) => number.

var { x = (str: string) => +str } = { x: param => param.length };

But if we used the initializer first, wouldn't we acquire the type of x from the type of x from the RHS of the top-level initializer?

I feel like it's not as strong an indicator of the type.

Yes, I see what @JsonFreeman is saying - it would be kind of ideal if the properties "zipped up" with respect to optionality when determining assignability. I can definitely get behind a very simple rule, but I get that there's also something to be said about flexibility here.

@JsonFreeman
Copy link
Contributor Author

@DanielRosenwasser the contextual typing you are talking about is that the RHS gets contextually typed by the type implied by the binding pattern. However, the final type of x is determined by the outer initializer (which got contextually typed using the inner initializer).

@ahejlsberg We could just do the contextual typing rule for optional properties like you mentioned. It would only work when the initializer is a literal though.

@ahejlsberg
Copy link
Member

@JsonFreeman Fix now available in #4598.

@ahejlsberg ahejlsberg added the Fixed A PR has been merged for this issue label Sep 11, 2015
@microsoft microsoft locked and limited conversation to collaborators Jun 18, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Bug A bug in TypeScript Fixed A PR has been merged for this issue Spec Issues related to the TypeScript language specification
Projects
None yet
Development

No branches or pull requests

4 participants