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
Generic literal marker not obeyed for unknown and {} #32169
The exact rules we use for widening inference candidates are a bit more complex (see getCovariantInference function in the checker for implementation). We widen inferred literal types if:
all inferences were made to top-level occurrences of the type parameter, and
the type parameter has no constraint or its constraint includes no primitive or literal types, and
the type parameter was fixed during inference or does not occur at top-level in the return type.
Primitive types are string, number, bigint, boolean, symbol, void, undefined, null, and all literal and unique symbol types. The intent of the rules is to not widen when there is some indication in the constraint that a literal type is wanted. For example, a constraint number is a pretty clear indication that we want something that is a subtype of number, i.e. a numeric literal type.
I'm not sure I see a compelling reason to change these rules, in particular since it would likely break code that depends on the current behavior.
Anders Hejlsberg (@ahejlsberg) why isn't e.g. widening to number gated on the number primitive type (or a numeric literal type) appearing in the union?
At a minimum, I would expect the non-values (null, undefined, void) to not have any influence on literal widening.
This function has the important property that it can be inserted around any expression to get the same result type out:
enumE{A,B}constx=assert(E.A);// type E.A
However, you cannot pass unknown to this function as written, and changing the param to T extends unknown or plain T means it now widens.
(You might wonder why there's any code that ever assert()s on an unknown, because the return type isn't useful. But it still is useful because it throws on null.)
This came up in particular in TS3.5, because we are seeing a lot more code that ends up passing around unknowns, due to the default constraint change (#30637).
functionf<T>(x: T): T{returnx;}enumE{A,B}letx=f(E.A);// x has type E, not E.A
the reason for the widened type is that x is a mutable location. If you declare with const we don't widen. The reason we don't widen is because T occurs at top-level in the return type, as per the rules above.
However, when the example is changed to
declarefunctionf<T>(x: T): NonNullable<T>;
we start widening because we don't consider the occurrence of T in the return type to be top-level. From #10676, the original PR for literal types
An occurrence of a type X within a type S is said to be top-level if S is X or if S is a union or intersection type and X occurs at top-level within S.
We never updated this rule to account for conditional types. I think we should augment the definition to
An occurrence of a type X within a type S is said to be top-level if S is X or if S is a union or intersection type and X occurs at top-level within S, or if S is a conditional type and X occurs at top-level within the true or false branch of S.
That should fix the specific code that is breaking (because the type parameter constraint doesn't even matter then).
why isn't e.g. widening to number gated on the number primitive type (or a numeric literal type) appearing in the union?
In general, if we're going to keep literal types we should keep them for all types, not just some types. And if the constraint is number it doesn't really matter if we keep literal strings because they'll fail the constraint check anyways.
You could argue that we shouldn't consider null or undefined in the constraint a reason to keep literal types (i.e. to not widen), but I'm not super eager to mess with these rules and have something break because of it, and it is unrelated to the real fix for the problem in this issue.
Changing this to be a bug, the bug being that the check for top-level occurrences of a type parameter should be augmented to handle conditional types as I describe above.
Activity
ahejlsberg commentedon Jul 3, 2019
The exact rules we use for widening inference candidates are a bit more complex (see
getCovariantInferencefunction in the checker for implementation). We widen inferred literal types if:Primitive types are
string,number,bigint,boolean,symbol,void,undefined,null, and all literal and unique symbol types. The intent of the rules is to not widen when there is some indication in the constraint that a literal type is wanted. For example, a constraintnumberis a pretty clear indication that we want something that is a subtype ofnumber, i.e. a numeric literal type.I'm not sure I see a compelling reason to change these rules, in particular since it would likely break code that depends on the current behavior.
RyanCavanaugh commentedon Jul 3, 2019
Anders Hejlsberg (@ahejlsberg) why isn't e.g. widening to
numbergated on thenumberprimitive type (or a numeric literal type) appearing in the union?At a minimum, I would expect the non-values (
null,undefined,void) to not have any influence on literal widening.evmar commentedon Jul 3, 2019
The specific code this breaks for us is:
This function has the important property that it can be inserted around any expression to get the same result type out:
However, you cannot pass
unknownto this function as written, and changing the param toT extends unknownor plainTmeans it now widens.(You might wonder why there's any code that ever assert()s on an unknown, because the return type isn't useful. But it still is useful because it throws on null.)
This came up in particular in TS3.5, because we are seeing a lot more code that ends up passing around unknowns, due to the default constraint change (#30637).
ahejlsberg commentedon Jul 4, 2019
So, first of all, in the original example
the reason for the widened type is that
xis a mutable location. If you declare withconstwe don't widen. The reason we don't widen is becauseToccurs at top-level in the return type, as per the rules above.However, when the example is changed to
we start widening because we don't consider the occurrence of
Tin the return type to be top-level. From #10676, the original PR for literal typesWe never updated this rule to account for conditional types. I think we should augment the definition to
That should fix the specific code that is breaking (because the type parameter constraint doesn't even matter then).
ahejlsberg commentedon Jul 4, 2019
In general, if we're going to keep literal types we should keep them for all types, not just some types. And if the constraint is
numberit doesn't really matter if we keep literal strings because they'll fail the constraint check anyways.You could argue that we shouldn't consider
nullorundefinedin the constraint a reason to keep literal types (i.e. to not widen), but I'm not super eager to mess with these rules and have something break because of it, and it is unrelated to the real fix for the problem in this issue.ahejlsberg commentedon Jul 5, 2019
Changing this to be a bug, the bug being that the check for top-level occurrences of a type parameter should be augmented to handle conditional types as I describe above.