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
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
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

View File

@ -0,0 +1,46 @@
tests/cases/compiler/mappedTypeNotMistakenlyHomomorphic.ts(33,1): error TS2741: Property 'a' is missing in type 'Gen2<ABC.B>' but required in type 'Gen2<ABC.A>'.
tests/cases/compiler/mappedTypeNotMistakenlyHomomorphic.ts(34,1): error TS2741: Property 'b' is missing in type 'Gen2<ABC.A>' but required in type 'Gen2<ABC.B>'.
==== tests/cases/compiler/mappedTypeNotMistakenlyHomomorphic.ts (2 errors) ====
enum ABC { A, B }
type Gen<T extends ABC> = { v: T; } & (
{
v: ABC.A,
a: string,
} | {
v: ABC.B,
b: string,
}
)
// Quick info: ???
//
// type Gen2<T extends ABC> = {
// v: string;
// }
//
type Gen2<T extends ABC> = {
[Property in keyof Gen<T>]: string;
};
// 'a' and 'b' properties required !?!?
const gen2TypeA: Gen2<ABC.A> = { v: "I am A", a: "" };
const gen2TypeB: Gen2<ABC.B> = { v: "I am B", b: "" };
// 'v' ???
type K = keyof Gen2<ABC.A>;
// :(
declare let a: Gen2<ABC.A>;
declare let b: Gen2<ABC.B>;
a = b;
~
!!! error TS2741: Property 'a' is missing in type 'Gen2<ABC.B>' but required in type 'Gen2<ABC.A>'.
!!! related TS2728 tests/cases/compiler/mappedTypeNotMistakenlyHomomorphic.ts:6:5: 'a' is declared here.
b = a;
~
!!! error TS2741: Property 'b' is missing in type 'Gen2<ABC.A>' but required in type 'Gen2<ABC.B>'.
!!! related TS2728 tests/cases/compiler/mappedTypeNotMistakenlyHomomorphic.ts:9:5: 'b' is declared here.

View File

@ -0,0 +1,48 @@
//// [mappedTypeNotMistakenlyHomomorphic.ts]
enum ABC { A, B }
type Gen<T extends ABC> = { v: T; } & (
{
v: ABC.A,
a: string,
} | {
v: ABC.B,
b: string,
}
)
// Quick info: ???
//
// type Gen2<T extends ABC> = {
// v: string;
// }
//
type Gen2<T extends ABC> = {
[Property in keyof Gen<T>]: string;
};
// 'a' and 'b' properties required !?!?
const gen2TypeA: Gen2<ABC.A> = { v: "I am A", a: "" };
const gen2TypeB: Gen2<ABC.B> = { v: "I am B", b: "" };
// 'v' ???
type K = keyof Gen2<ABC.A>;
// :(
declare let a: Gen2<ABC.A>;
declare let b: Gen2<ABC.B>;
a = b;
b = a;
//// [mappedTypeNotMistakenlyHomomorphic.js]
var ABC;
(function (ABC) {
ABC[ABC["A"] = 0] = "A";
ABC[ABC["B"] = 1] = "B";
})(ABC || (ABC = {}));
// 'a' and 'b' properties required !?!?
var gen2TypeA = { v: "I am A", a: "" };
var gen2TypeB = { v: "I am B", b: "" };
a = b;
b = a;

View File

@ -0,0 +1,95 @@
=== tests/cases/compiler/mappedTypeNotMistakenlyHomomorphic.ts ===
enum ABC { A, B }
>ABC : Symbol(ABC, Decl(mappedTypeNotMistakenlyHomomorphic.ts, 0, 0))
>A : Symbol(ABC.A, Decl(mappedTypeNotMistakenlyHomomorphic.ts, 0, 10))
>B : Symbol(ABC.B, Decl(mappedTypeNotMistakenlyHomomorphic.ts, 0, 13))
type Gen<T extends ABC> = { v: T; } & (
>Gen : Symbol(Gen, Decl(mappedTypeNotMistakenlyHomomorphic.ts, 0, 17))
>T : Symbol(T, Decl(mappedTypeNotMistakenlyHomomorphic.ts, 2, 9))
>ABC : Symbol(ABC, Decl(mappedTypeNotMistakenlyHomomorphic.ts, 0, 0))
>v : Symbol(v, Decl(mappedTypeNotMistakenlyHomomorphic.ts, 2, 27))
>T : Symbol(T, Decl(mappedTypeNotMistakenlyHomomorphic.ts, 2, 9))
{
v: ABC.A,
>v : Symbol(v, Decl(mappedTypeNotMistakenlyHomomorphic.ts, 3, 3))
>ABC : Symbol(ABC, Decl(mappedTypeNotMistakenlyHomomorphic.ts, 0, 0))
>A : Symbol(ABC.A, Decl(mappedTypeNotMistakenlyHomomorphic.ts, 0, 10))
a: string,
>a : Symbol(a, Decl(mappedTypeNotMistakenlyHomomorphic.ts, 4, 13))
} | {
v: ABC.B,
>v : Symbol(v, Decl(mappedTypeNotMistakenlyHomomorphic.ts, 6, 7))
>ABC : Symbol(ABC, Decl(mappedTypeNotMistakenlyHomomorphic.ts, 0, 0))
>B : Symbol(ABC.B, Decl(mappedTypeNotMistakenlyHomomorphic.ts, 0, 13))
b: string,
>b : Symbol(b, Decl(mappedTypeNotMistakenlyHomomorphic.ts, 7, 13))
}
)
// Quick info: ???
//
// type Gen2<T extends ABC> = {
// v: string;
// }
//
type Gen2<T extends ABC> = {
>Gen2 : Symbol(Gen2, Decl(mappedTypeNotMistakenlyHomomorphic.ts, 10, 1))
>T : Symbol(T, Decl(mappedTypeNotMistakenlyHomomorphic.ts, 18, 10))
>ABC : Symbol(ABC, Decl(mappedTypeNotMistakenlyHomomorphic.ts, 0, 0))
[Property in keyof Gen<T>]: string;
>Property : Symbol(Property, Decl(mappedTypeNotMistakenlyHomomorphic.ts, 19, 3))
>Gen : Symbol(Gen, Decl(mappedTypeNotMistakenlyHomomorphic.ts, 0, 17))
>T : Symbol(T, Decl(mappedTypeNotMistakenlyHomomorphic.ts, 18, 10))
};
// 'a' and 'b' properties required !?!?
const gen2TypeA: Gen2<ABC.A> = { v: "I am A", a: "" };
>gen2TypeA : Symbol(gen2TypeA, Decl(mappedTypeNotMistakenlyHomomorphic.ts, 23, 5))
>Gen2 : Symbol(Gen2, Decl(mappedTypeNotMistakenlyHomomorphic.ts, 10, 1))
>ABC : Symbol(ABC, Decl(mappedTypeNotMistakenlyHomomorphic.ts, 0, 0))
>A : Symbol(ABC.A, Decl(mappedTypeNotMistakenlyHomomorphic.ts, 0, 10))
>v : Symbol(v, Decl(mappedTypeNotMistakenlyHomomorphic.ts, 23, 32))
>a : Symbol(a, Decl(mappedTypeNotMistakenlyHomomorphic.ts, 23, 46))
const gen2TypeB: Gen2<ABC.B> = { v: "I am B", b: "" };
>gen2TypeB : Symbol(gen2TypeB, Decl(mappedTypeNotMistakenlyHomomorphic.ts, 24, 5))
>Gen2 : Symbol(Gen2, Decl(mappedTypeNotMistakenlyHomomorphic.ts, 10, 1))
>ABC : Symbol(ABC, Decl(mappedTypeNotMistakenlyHomomorphic.ts, 0, 0))
>B : Symbol(ABC.B, Decl(mappedTypeNotMistakenlyHomomorphic.ts, 0, 13))
>v : Symbol(v, Decl(mappedTypeNotMistakenlyHomomorphic.ts, 24, 32))
>b : Symbol(b, Decl(mappedTypeNotMistakenlyHomomorphic.ts, 24, 46))
// 'v' ???
type K = keyof Gen2<ABC.A>;
>K : Symbol(K, Decl(mappedTypeNotMistakenlyHomomorphic.ts, 24, 55))
>Gen2 : Symbol(Gen2, Decl(mappedTypeNotMistakenlyHomomorphic.ts, 10, 1))
>ABC : Symbol(ABC, Decl(mappedTypeNotMistakenlyHomomorphic.ts, 0, 0))
>A : Symbol(ABC.A, Decl(mappedTypeNotMistakenlyHomomorphic.ts, 0, 10))
// :(
declare let a: Gen2<ABC.A>;
>a : Symbol(a, Decl(mappedTypeNotMistakenlyHomomorphic.ts, 30, 11))
>Gen2 : Symbol(Gen2, Decl(mappedTypeNotMistakenlyHomomorphic.ts, 10, 1))
>ABC : Symbol(ABC, Decl(mappedTypeNotMistakenlyHomomorphic.ts, 0, 0))
>A : Symbol(ABC.A, Decl(mappedTypeNotMistakenlyHomomorphic.ts, 0, 10))
declare let b: Gen2<ABC.B>;
>b : Symbol(b, Decl(mappedTypeNotMistakenlyHomomorphic.ts, 31, 11))
>Gen2 : Symbol(Gen2, Decl(mappedTypeNotMistakenlyHomomorphic.ts, 10, 1))
>ABC : Symbol(ABC, Decl(mappedTypeNotMistakenlyHomomorphic.ts, 0, 0))
>B : Symbol(ABC.B, Decl(mappedTypeNotMistakenlyHomomorphic.ts, 0, 13))
a = b;
>a : Symbol(a, Decl(mappedTypeNotMistakenlyHomomorphic.ts, 30, 11))
>b : Symbol(b, Decl(mappedTypeNotMistakenlyHomomorphic.ts, 31, 11))
b = a;
>b : Symbol(b, Decl(mappedTypeNotMistakenlyHomomorphic.ts, 31, 11))
>a : Symbol(a, Decl(mappedTypeNotMistakenlyHomomorphic.ts, 30, 11))

View File

@ -0,0 +1,82 @@
=== tests/cases/compiler/mappedTypeNotMistakenlyHomomorphic.ts ===
enum ABC { A, B }
>ABC : ABC
>A : ABC.A
>B : ABC.B
type Gen<T extends ABC> = { v: T; } & (
>Gen : Gen<T>
>v : T
{
v: ABC.A,
>v : ABC.A
>ABC : any
a: string,
>a : string
} | {
v: ABC.B,
>v : ABC.B
>ABC : any
b: string,
>b : string
}
)
// Quick info: ???
//
// type Gen2<T extends ABC> = {
// v: string;
// }
//
type Gen2<T extends ABC> = {
>Gen2 : Gen2<T>
[Property in keyof Gen<T>]: string;
};
// 'a' and 'b' properties required !?!?
const gen2TypeA: Gen2<ABC.A> = { v: "I am A", a: "" };
>gen2TypeA : Gen2<ABC.A>
>ABC : any
>{ v: "I am A", a: "" } : { v: string; a: string; }
>v : string
>"I am A" : "I am A"
>a : string
>"" : ""
const gen2TypeB: Gen2<ABC.B> = { v: "I am B", b: "" };
>gen2TypeB : Gen2<ABC.B>
>ABC : any
>{ v: "I am B", b: "" } : { v: string; b: string; }
>v : string
>"I am B" : "I am B"
>b : string
>"" : ""
// 'v' ???
type K = keyof Gen2<ABC.A>;
>K : "v" | "a"
>ABC : any
// :(
declare let a: Gen2<ABC.A>;
>a : Gen2<ABC.A>
>ABC : any
declare let b: Gen2<ABC.B>;
>b : Gen2<ABC.B>
>ABC : any
a = b;
>a = b : Gen2<ABC.B>
>a : Gen2<ABC.A>
>b : Gen2<ABC.B>
b = a;
>b = a : Gen2<ABC.A>
>b : Gen2<ABC.B>
>a : Gen2<ABC.A>

View File

@ -0,0 +1,34 @@
enum ABC { A, B }
type Gen<T extends ABC> = { v: T; } & (
{
v: ABC.A,
a: string,
} | {
v: ABC.B,
b: string,
}
)
// Quick info: ???
//
// type Gen2<T extends ABC> = {
// v: string;
// }
//
type Gen2<T extends ABC> = {
[Property in keyof Gen<T>]: string;
};
// 'a' and 'b' properties required !?!?
const gen2TypeA: Gen2<ABC.A> = { v: "I am A", a: "" };
const gen2TypeB: Gen2<ABC.B> = { v: "I am B", b: "" };
// 'v' ???
type K = keyof Gen2<ABC.A>;
// :(
declare let a: Gen2<ABC.A>;
declare let b: Gen2<ABC.B>;
a = b;
b = a;