You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Removing null and undefined from a Tuple breaks equality #50753
When trying to remove nils from a Tuple it resolves to an Tuple that do not extends to the identical Tuple (as represented by the compiler)
So in the example below the reult of a type is [a: 1, b?:2] which do not extends the type [a: 1, b?:2] ??
🔎 Search Terms
Identical type - Invalid Equality
tuple optional extends
🕗 Version & Regression Information
Discovered the issue today and it's referenced on the following stackoverflow
I did test it in the playground upto version 4.05 of typescript (syntax breaks in version prior)
I don't think so. Instead I would expect UnNullify<[a: 1, b?: 2 | undefined]> to be [a: 1, b: 2].
The definition { [K in keyof T]: Exclude<T[K], null | undefined> } indicates that for any key of T it will resolve to a value which can never be null or undefined, and also note that Foo[1] resolves to 2(while [a: 1, b?: 2][1] resolves to 2 | undefined), which exactly matches the definition.
Instead I would expect UnNullify<[a: 1, b?: 2 | undefined]> to be [a: 1, b: 2].
It's a homomorphic mapped type, so it preserves the ? modifier. In any case, the result is[a: 1, b?: 2], and that result apparently does not extend [a: 1, b?: 2], despite that apparently being exactly the same type.
A bit of further investigation reveals that the type written as [a: 1, b?: 2] gets expanded to [a: 1, b?: 2 | undefined] in an error message, and this distinction appears to be why it doesn't count as extending the other type.
declareconstbar: [a: 1,b?: 2];constfoo: Foo=bar;
Type '[a: 1, b?: 2 | undefined]' is not assignable to type '[a: 1, b?: 2]'.
Type at position 1 in source is not compatible with type at position 1 in target.
Type '2 | undefined' is not assignable to type '2'.
Type 'undefined' is not assignable to type '2'.(2322)
However, when the exactOptionalPropertyTypes flag is enabled, so that writing [a: 1, b?: 2] does not expand to include undefined in the second component, the error message is different, and rather surprising:
Type '[a: 1, b?: 2]' is not assignable to type '[a: 1, b?: 2]'. Two different types with this name exist, but they are unrelated.
'number' index signatures are incompatible.
Type '1 | 2 | undefined' is not assignable to type '1 | 2'.
Type 'undefined' is not assignable to type '1 | 2'.(2719)
Anders Hejlsberg (@ahejlsberg) I think this is because when we compare source and target tuples, we just look at the type arguments and forget to re-introduce the undefined type.
Here's a test case that might be useful - it demonstrates different results between optional
object-declared properties and tuple elements.
typeUnNullify<T>={[KinkeyofT]: NonNullable<T[K]>};constx: UnNullify<[string,number?]>=['abc'];// Property '1' is still optionalconstx1=x[1];// number, but should be number | undefined
The UnNullify type removes undefined from the element type, but it doesn't remove optionality. Therefore, the 1 property is still optional (as demonstrated by the assignment to x) and it's type should still include undefined.
Notice the -? modifier that strips optionality. When this type is used in the example above, the assignment to x fails as expected, and only then is it sound for property 1 to have type number.
Reacted by whzx5byb, Joe Calzaretta, seb and Amin Pakseresht
Activity
kaya3 commentedon Sep 13, 2022
More search terms: tuple optional extends
jcalz commentedon Sep 13, 2022
In case it matters, this does look specific to optional tuple elements and not to optional properties in general:
Playground link to code
zedryas commentedon Sep 14, 2022
To expand Joe Calzaretta (@jcalz) post it is not relevant to labels and does not work either with unlablelled tuples
Playground link
whzx5byb commentedon Sep 14, 2022
I don't think so. Instead I would expect
UnNullify<[a: 1, b?: 2 | undefined]>to be[a: 1, b: 2].The definition
{ [K in keyof T]: Exclude<T[K], null | undefined> }indicates that for any key of T it will resolve to a value which can never benullorundefined, and also note thatFoo[1]resolves to2(while[a: 1, b?: 2][1]resolves to2 | undefined), which exactly matches the definition.kaya3 commentedon Sep 14, 2022
It's a homomorphic mapped type, so it preserves the
?modifier. In any case, the result is[a: 1, b?: 2], and that result apparently does not extend[a: 1, b?: 2], despite that apparently being exactly the same type.kaya3 commentedon Sep 14, 2022
A bit of further investigation reveals that the type written as
[a: 1, b?: 2]gets expanded to[a: 1, b?: 2 | undefined]in an error message, and this distinction appears to be why it doesn't count as extending the other type.Playground Link
However, when the
exactOptionalPropertyTypesflag is enabled, so that writing[a: 1, b?: 2]does not expand to includeundefinedin the second component, the error message is different, and rather surprising:DanielRosenwasser commentedon Sep 16, 2022
Anders Hejlsberg (@ahejlsberg) I think this is because when we compare source and target tuples, we just look at the type arguments and forget to re-introduce the
undefinedtype.Here's a test case that might be useful - it demonstrates different results between optional
object-declared properties and tuple elements.
ahejlsberg commentedon Sep 19, 2022
A shorter example of the issue:
The
UnNullifytype removesundefinedfrom the element type, but it doesn't remove optionality. Therefore, the1property is still optional (as demonstrated by the assignment tox) and it's type should still includeundefined.The proper way to "un-nullify" a type would be:
Notice the
-?modifier that strips optionality. When this type is used in the example above, the assignment toxfails as expected, and only then is it sound for property1to have typenumber.