Don't eagerly simplify reducible generic union index types (#46812)

This commit is contained in:
Wesley Wigham
2022-03-09 12:02:11 -08:00
committed by GitHub
parent ea4791d4d7
commit fc82c67357
7 changed files with 332 additions and 3 deletions

View File

@@ -814,6 +814,8 @@ namespace ts {
const restrictiveMapper: TypeMapper = makeFunctionTypeMapper(t => t.flags & TypeFlags.TypeParameter ? getRestrictiveTypeParameter(t as TypeParameter) : t);
const permissiveMapper: TypeMapper = makeFunctionTypeMapper(t => t.flags & TypeFlags.TypeParameter ? wildcardType : t);
const uniqueLiteralType = createIntrinsicType(TypeFlags.Never, "never"); // `uniqueLiteralType` is a special `never` flagged by union reduction to behave as a literal
const uniqueLiteralMapper: TypeMapper = makeFunctionTypeMapper(t => t.flags & TypeFlags.TypeParameter ? uniqueLiteralType : t); // replace all type parameters with the unique literal type (disregarding constraints)
const emptyObjectType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, emptyArray);
const emptyJsxObjectType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, emptyArray);
@@ -12367,10 +12369,10 @@ namespace ts {
else if (type !== firstType) {
checkFlags |= CheckFlags.HasNonUniformType;
}
if (isLiteralType(type) || isPatternLiteralType(type)) {
if (isLiteralType(type) || isPatternLiteralType(type) || type === uniqueLiteralType) {
checkFlags |= CheckFlags.HasLiteralType;
}
if (type.flags & TypeFlags.Never) {
if (type.flags & TypeFlags.Never && type !== uniqueLiteralType) {
checkFlags |= CheckFlags.HasNeverType;
}
propTypes.push(type);
@@ -15124,9 +15126,24 @@ namespace ts {
/*aliasSymbol*/ undefined, /*aliasTypeArguments*/ undefined, origin);
}
/**
* A union type which is reducible upon instantiation (meaning some members are removed under certain instantiations)
* must be kept generic, as that instantiation information needs to flow through the type system. By replacing all
* type parameters in the union with a special never type that is treated as a literal in `getReducedType`, we can cause the `getReducedType` logic
* to reduce the resulting type if possible (since only intersections with conflicting literal-typed properties are reducible).
*/
function isPossiblyReducibleByInstantiation(type: UnionType): boolean {
return some(type.types, t => {
const uniqueFilled = getUniqueLiteralFilledInstantiation(t);
return getReducedType(uniqueFilled) !== uniqueFilled;
});
}
function getIndexType(type: Type, stringsOnly = keyofStringsOnly, noIndexSignatures?: boolean): Type {
type = getReducedType(type);
return type.flags & TypeFlags.Union ? getIntersectionType(map((type as UnionType).types, t => getIndexType(t, stringsOnly, noIndexSignatures))) :
return type.flags & TypeFlags.Union ? isPossiblyReducibleByInstantiation(type as UnionType)
? getIndexTypeForGenericType(type as InstantiableType | UnionOrIntersectionType, stringsOnly)
: getIntersectionType(map((type as UnionType).types, t => getIndexType(t, stringsOnly, noIndexSignatures))) :
type.flags & TypeFlags.Intersection ? getUnionType(map((type as IntersectionType).types, t => getIndexType(t, stringsOnly, noIndexSignatures))) :
type.flags & TypeFlags.InstantiableNonPrimitive || isGenericTupleType(type) || isGenericMappedType(type) && !hasDistributiveNameType(type) ? getIndexTypeForGenericType(type as InstantiableType | UnionOrIntersectionType, stringsOnly) :
getObjectFlags(type) & ObjectFlags.Mapped ? getIndexTypeForMappedType(type as MappedType, stringsOnly, noIndexSignatures) :
@@ -17070,6 +17087,11 @@ namespace ts {
return type; // Nested invocation of `inferTypeForHomomorphicMappedType` or the `source` instantiated into something unmappable
}
function getUniqueLiteralFilledInstantiation(type: Type) {
return type.flags & (TypeFlags.Primitive | TypeFlags.AnyOrUnknown | TypeFlags.Never) ? type :
type.uniqueLiteralFilledInstantiation || (type.uniqueLiteralFilledInstantiation = instantiateType(type, uniqueLiteralMapper));
}
function getPermissiveInstantiation(type: Type) {
return type.flags & (TypeFlags.Primitive | TypeFlags.AnyOrUnknown | TypeFlags.Never) ? type :
type.permissiveInstantiation || (type.permissiveInstantiation = instantiateType(type, permissiveMapper));

View File

@@ -5244,6 +5244,8 @@ namespace ts {
/* @internal */
restrictiveInstantiation?: Type; // Instantiation with type parameters mapped to unconstrained form
/* @internal */
uniqueLiteralFilledInstantiation?: Type; // Instantiation with type parameters mapped to never type
/* @internal */
immediateBaseConstraint?: Type; // Immediate base constraint cache
/* @internal */
widened?: Type; // Cached widened form of the type