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
No common supertype between {optional, optional} and {required, absent} #1446
We made the change that an object type with a missing property cannot be a subtype of an object with an optional property. This made subtype choose better results in some cases, because it fixed cases of nonantisymmetry in the subtype relation. But now there are cases where you get an error in type argument inference because a pair of type has no common supertype.
interfaceI{a?: string;b?: string;}vari: I;functionfoo<T>(p1: T,p2: T){}foo(i,{a: ""});// Error because neither type is a supertype of the other
One interpretation is that in this case I should be the common supertype. To make this happen, we would say that instead of a missing property being a supertype of an optional, it is a subtype. In this view, missing and required are both specific cases of optional, and optional is a supertype of both.
Another fix is a change in type argument inference. If finding a common supertype does not yield a result, we can have another round where we use assignability. In the above example, this would not produce an error, since the object literal is assignable to I.
The issue here is that it may lead to results that depend on the order of the arguments. Both would select the first type in the following case:
Jason Freeman (@JsonFreeman) The thing that gets us in trouble here is that we regard an object literal as having a complete type when in reality we only know about the properties that were actually specified.
Another way to think of it is that an object literal's type has a known set of required properties (those that were specified) and an infinite set of optional properties with any spelling and the value undefined. In that world view, the object literal { a: "" } has a type that is a subtype of { a?: string; b?: string; } because it has an phantom optional property b with the value undefined. An object literal's phantom optional properties would go away during widening (the same process that converts undefined and null types to the any type), so assigning { a: "" } to a variable would produce an inferred type of { a: string }.
This same process could be used as an alternative to #1774. We wouldn't need to copy missing properties from the contextual type as we could just rely on the phantom optional properties.
If I understand you correctly, your point could be alternatively stated like the following. Given two types S and T, if p is an optional property in T that is missing in S, then:
If S originated as an object literal, then S is a subtype of T (from the perspective of p)
If S did not originate as an object literal, then T is a subtype of S (from the perspective of p)
The observation is that an object literal cannot express whether a property is optional or required, only if it is present or absent. So as long as it is present with the right type, or absent, it is fit to be a subtype of the optional.
The one thing I am concerned about is that in this scheme: Whether or not the type originated in an object literal can completely flip the relation. So consider the following:
interfaceWithOptional{x?: string;}varwithOpt: WithOptional={};varwithoutOpt1: {}={};varwithoutOpt2={};functionfoo<T>(p1: T,p2: T){}foo(withOpt,withoutOpt1).x;// errorfoo(withOpt,withoutOpt2).x;// no error
withoutOpt1 has no properties, but withoutOpt2 has infinitely many optional properties of type undefined.
Actually, reading your explanation again, I neglected the part about widening. So withoutOpt1 and withoutOpt2 actually would have the same type.
So the key difference between this and #1774 is that #1774 only happens when you contextually type, whereas this proposal happens when you don't widen. The latter happens in strictly more places I believe.
One thing that occurs to me, is that the case where there are no inference candidates would give a different result with the new scheme:
interfaceWithOptional<T>{x?: T;}declarefunctionfoo<T>(p1: WithOptional<T>): T;foo({});// Used to infer {}, now we would infer anyinterfaceWithOptionalAndUnion<T>{x?: T// primaryy: string|T;// secondary}declarefunctionbar<T>(p1: WithOptionalAndUnion<T>): T;bar({y: ""});// Used to infer string, now we would infer any
Intuitively, this would infer { }. However, it sounds like your scheme would actually make this an error, because neither argument is a subtype of the other. This is because the object literal { } would actually have a type like { a?: undefined }. So it is not a supertype of { a: string }, because undefined is not a supertype of string. But it's not a subtype either because an optional cannot be a subtype of a required property.
Jason Freeman (@JsonFreeman) No, your example ends up inferring {} as you would expect. Forget about the "phantom" properties and think of it this way instead: An object literal type is a subtype of of a type T even if the object literal type is missing some of the optional properties in T, but once the object literal type is widened to a regular type this is no longer the case. In your example. It is effectively as if object literal types play by the old rules (pre-#919) before widening.
Activity
ahejlsberg commentedon Jan 23, 2015
Jason Freeman (@JsonFreeman) The thing that gets us in trouble here is that we regard an object literal as having a complete type when in reality we only know about the properties that were actually specified.
Another way to think of it is that an object literal's type has a known set of required properties (those that were specified) and an infinite set of optional properties with any spelling and the value undefined. In that world view, the object literal
{ a: "" }has a type that is a subtype of{ a?: string; b?: string; }because it has an phantom optional propertybwith the value undefined. An object literal's phantom optional properties would go away during widening (the same process that converts undefined and null types to the any type), so assigning{ a: "" }to a variable would produce an inferred type of{ a: string }.This same process could be used as an alternative to #1774. We wouldn't need to copy missing properties from the contextual type as we could just rely on the phantom optional properties.
JsonFreeman commentedon Jan 23, 2015
If I understand you correctly, your point could be alternatively stated like the following. Given two types S and T, if p is an optional property in T that is missing in S, then:
The observation is that an object literal cannot express whether a property is optional or required, only if it is present or absent. So as long as it is present with the right type, or absent, it is fit to be a subtype of the optional.
The one thing I am concerned about is that in this scheme: Whether or not the type originated in an object literal can completely flip the relation. So consider the following:
withoutOpt1 has no properties, but withoutOpt2 has infinitely many optional properties of type undefined.
JsonFreeman commentedon Jan 23, 2015
Actually, reading your explanation again, I neglected the part about widening. So withoutOpt1 and withoutOpt2 actually would have the same type.
So the key difference between this and #1774 is that #1774 only happens when you contextually type, whereas this proposal happens when you don't widen. The latter happens in strictly more places I believe.
JsonFreeman commentedon Jan 23, 2015
One thing that occurs to me, is that the case where there are no inference candidates would give a different result with the new scheme:
JsonFreeman commentedon Jan 23, 2015
But all in all, I do think this is a good idea.
JsonFreeman commentedon Jan 23, 2015
There is a case I can think of that the contextual typing idea #1774 fixes, but this proposal (by itself) does not:
JsonFreeman commentedon Jan 24, 2015
I thought about this some more, and discovered something very strange about this idea:
Intuitively, this would infer
{ }. However, it sounds like your scheme would actually make this an error, because neither argument is a subtype of the other. This is because the object literal{ }would actually have a type like{ a?: undefined }. So it is not a supertype of{ a: string }, because undefined is not a supertype of string. But it's not a subtype either because an optional cannot be a subtype of a required property.ahejlsberg commentedon Jan 24, 2015
Jason Freeman (@JsonFreeman) No, your example ends up inferring
{}as you would expect. Forget about the "phantom" properties and think of it this way instead: An object literal type is a subtype of of a type T even if the object literal type is missing some of the optional properties in T, but once the object literal type is widened to a regular type this is no longer the case. In your example. It is effectively as if object literal types play by the old rules (pre-#919) before widening.ahejlsberg commentedon Jan 24, 2015
Jason Freeman (@JsonFreeman) I've got a fix for this in #1795.