Conversation
| const simplified = getSimplifiedType(type.checkType); | ||
| const constraint = simplified === type.checkType ? getConstraintOfType(simplified) : simplified; | ||
| if (constraint) { | ||
| if (constraint && constraint !== type.checkType) { |
There was a problem hiding this comment.
This particular change fixes an issue where a circularity resulted from the constraint of a conditional type being the conditional type itself. This can happen when a distributive conditional type is applied to an intersection of mapped types. The issue was uncovered because we now correctly defer resolution of conditional types involving generic mapped types.
| return wildcardType; | ||
| } | ||
| const checkTypeInstantiable = maybeTypeOfKind(checkType, TypeFlags.Instantiable); | ||
| const checkTypeInstantiable = maybeTypeOfKind(checkType, TypeFlags.Instantiable | TypeFlags.GenericMappedType); |
There was a problem hiding this comment.
We previously didn't defer resolution when the check or extends type was a generic mapped type. That definitely was not correct.
|
@typescript-bot test this ❤️ |
|
Heya @weswigham, I've started to run the extended test suite on this PR at 0c1c97e. You can monitor the build here. It should now contribute to this PR's status checks. |
| wildcardInstantiation?: Type; // Instantiation with type parameters mapped to wildcard type | ||
| permissiveInstantiation?: Type; // Instantiation with type parameters mapped to wildcard type | ||
| /* @internal */ | ||
| restrictiveInstantiation?: Type; // Instantiation with type parameters mapped to unconstrained form |
There was a problem hiding this comment.
Should permissiveInstantiations and restrictiveInstantiations have pointers back to the original type, this way when we attempt to get them, if we already have one of them we can avoid bothering instantiating a new type (and often creating a new type identity)?
| } | ||
|
|
||
| function getRestrictiveTypeParameter(tp: TypeParameter) { | ||
| return !tp.constraint ? tp : tp.restrictiveInstantiation || (tp.restrictiveInstantiation = createTypeParameter(tp.symbol)); |
There was a problem hiding this comment.
This should be
function getRestrictiveTypeParameter(tp: TypeParameter) {
return tp.constraint === noConstraintType ? tp : tp.restrictiveInstantiation || (
tp.restrictiveInstantiation = createTypeParameter(tp.symbol),
(tp.restrictiveInstantiation as TypeParameter).constraint = noConstraintType,
tp.restrictiveInstantiation
);
}to align with the original intent - as is, since constraint is a lazily calculated field (and is calculated from the symbol), all this is doing right now is cloning the type parameter (constraint and all!) if it's constraint has been calculated already, or returning it as-is if it hasn't been calculated yet (in both cases you still end up with a potentially constrained type parameter).
Fixing this, however, is not without warts. For example, take this type:
type IsDefinitelyDefined<T extends unknown> = [T] extends [{}] ? true : false;when compared with an "unconstrained" T it's always true (we have a rule stating that unconstrained type params are assignable to {} for back compat), but the unknown constraint makes it false. Maybe that's a good indicator that the constraint for the restrictiveInstantiation should be set as unknownType instead of noConstraintType?
There was a problem hiding this comment.
(I ran into this while tracking down why I couldn't simplify conditional types without causing a stack overflow, and this was a contributor to why one of my possible fixes to that had unintended behavioral changes)
There was a problem hiding this comment.
Agreed. The restrictive instantiation should be a type parameter with an unknown constraint.
This PR improves our logic for determining when to resolve conditional types. We previously used a special "definitely assignable" relation which ignored type parameter constraints, but that didn't correctly distinguish between type parameters referenced in the conditional type vs. type parameters local to contained members such as generic methods. The PR gets rid of this special relation and instead uses the regular assignable relation on a special restrictive form of each type parameter that has no constraint. Now, for a conditional type
T extends U ? X : Y, the algorithm we use to determine whether to defer resolution of a conditional type is:YwhenTis not assignable toUconsidering all type parameters referenced inTandUrelated (i.e.Tis definitely not assignable toU),XwhenTis assignable toUconsidering all type parameters referenced inTandUunrelated (i.e.Tis definitely assignable toU),Fixes #23843.