diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index dbe53a978c6ed..68a98e40b4137 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -15097,14 +15097,14 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // mapped type because we protect again circular constraints in getTypeFromMappedTypeNode. const modifiersType = getModifiersTypeFromMappedType(type); const baseConstraint = isGenericMappedType(modifiersType) ? getApparentTypeOfMappedType(modifiersType) : getBaseConstraintOfType(modifiersType); - if (baseConstraint && everyType(baseConstraint, t => isArrayOrTupleType(t) || isArrayOrTupleOrIntersection(t))) { + if (baseConstraint && everyType(baseConstraint, t => isArrayOrTupleType(t) || isArrayOrTupleIntersection(t))) { return instantiateType(target, prependTypeMapping(typeVariable, baseConstraint, type.mapper)); } } return type; } - function isArrayOrTupleOrIntersection(type: Type) { + function isArrayOrTupleIntersection(type: Type) { return !!(type.flags & TypeFlags.Intersection) && every((type as IntersectionType).types, isArrayOrTupleType); } @@ -16875,8 +16875,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (getTypeParameterFromMappedType(mappedType) === getActualTypeVariable(type)) { const typeParameter = getHomomorphicTypeVariable(mappedType); if (typeParameter) { - const constraint = getConstraintOfTypeParameter(typeParameter); - if (constraint && everyType(constraint, isArrayOrTupleType)) { + const constraint = getConstraintOfType(getConditionalFlowTypeOfType(typeParameter, node)); + if (constraint && everyType(constraint, t => isArrayOrTupleType(t) || isArrayOrTupleIntersection(t))) { constraints = append(constraints, getUnionType([numberType, numericStringType])); } } @@ -20493,7 +20493,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (isTupleType(t)) { return instantiateMappedTupleType(t, type, typeVariable!, mapper); } - if (isArrayOrTupleOrIntersection(t)) { + if (isArrayOrTupleIntersection(t)) { return getIntersectionType(map((t as IntersectionType).types, instantiateConstituent)); } } diff --git a/tests/baselines/reference/deepComparisons.types b/tests/baselines/reference/deepComparisons.types index ce329516adba8..d803925f0834d 100644 --- a/tests/baselines/reference/deepComparisons.types +++ b/tests/baselines/reference/deepComparisons.types @@ -1,7 +1,7 @@ //// [tests/cases/compiler/deepComparisons.ts] //// === Performance Stats === -Type Count: 2,500 +Type Count: 1,000 Instantiation count: 2,500 === deepComparisons.ts === diff --git a/tests/baselines/reference/mappedArrayTupleIntersections.errors.txt b/tests/baselines/reference/mappedArrayTupleIntersections.errors.txt new file mode 100644 index 0000000000000..0033732d0ef59 --- /dev/null +++ b/tests/baselines/reference/mappedArrayTupleIntersections.errors.txt @@ -0,0 +1,50 @@ +mappedArrayTupleIntersections.ts(33,47): error TS2344: Type 'T[I]' does not satisfy the constraint '{ foo: unknown; }'. + Type 'T[keyof T]' is not assignable to type '{ foo: unknown; }'. + Type 'T[string] | T[number] | T[symbol]' is not assignable to type '{ foo: unknown; }'. + Type 'T[string]' is not assignable to type '{ foo: unknown; }'. + + +==== mappedArrayTupleIntersections.ts (1 errors) ==== + type Box = { value: T }; + type Boxify = { [K in keyof T]: Box }; + + type T1 = Boxify; + type T2 = Boxify<[string, string]>; + type T3 = Boxify; + type T4 = Boxify; + type T5 = Boxify; + + // https://github.com/microsoft/TypeScript/issues/57744 + + type MustBeArray = T; + + type Hmm = T extends number[] ? + MustBeArray<{ [I in keyof T]: 1 }> : + never; + + type X = Hmm<[3, 4, 5]>; + + type MustHaveFooBar = T; + + type Hmm2 = T extends { foo: string }[] + ? T extends { bar: number }[] + ? MustBeArray<{ [I in keyof T]: MustHaveFooBar }> + : never + : never; + + type Y = Hmm2<[{ foo: string; bar: number }]>; + + type MustHaveFoo = T; + + type Hmm3 = T extends { bar: string } + ? MustBeArray<{ [I in keyof T]: MustHaveFoo }> + ~~~~ +!!! error TS2344: Type 'T[I]' does not satisfy the constraint '{ foo: unknown; }'. +!!! error TS2344: Type 'T[keyof T]' is not assignable to type '{ foo: unknown; }'. +!!! error TS2344: Type 'T[string] | T[number] | T[symbol]' is not assignable to type '{ foo: unknown; }'. +!!! error TS2344: Type 'T[string]' is not assignable to type '{ foo: unknown; }'. + : never; + + type Z1 = Hmm3<[{ foo: string }]>; + type Z2 = Hmm3<[{ foo: string }] & { bar: string }>; + \ No newline at end of file diff --git a/tests/baselines/reference/mappedArrayTupleIntersections.symbols b/tests/baselines/reference/mappedArrayTupleIntersections.symbols index 354bdb031d901..e5b4f5d82c621 100644 --- a/tests/baselines/reference/mappedArrayTupleIntersections.symbols +++ b/tests/baselines/reference/mappedArrayTupleIntersections.symbols @@ -60,3 +60,71 @@ type X = Hmm<[3, 4, 5]>; >X : Symbol(X, Decl(mappedArrayTupleIntersections.ts, 15, 10)) >Hmm : Symbol(Hmm, Decl(mappedArrayTupleIntersections.ts, 11, 38)) +type MustHaveFooBar = T; +>MustHaveFooBar : Symbol(MustHaveFooBar, Decl(mappedArrayTupleIntersections.ts, 17, 24)) +>T : Symbol(T, Decl(mappedArrayTupleIntersections.ts, 19, 20)) +>foo : Symbol(foo, Decl(mappedArrayTupleIntersections.ts, 19, 31)) +>bar : Symbol(bar, Decl(mappedArrayTupleIntersections.ts, 19, 45)) +>T : Symbol(T, Decl(mappedArrayTupleIntersections.ts, 19, 20)) + +type Hmm2 = T extends { foo: string }[] +>Hmm2 : Symbol(Hmm2, Decl(mappedArrayTupleIntersections.ts, 19, 66)) +>T : Symbol(T, Decl(mappedArrayTupleIntersections.ts, 21, 10)) +>T : Symbol(T, Decl(mappedArrayTupleIntersections.ts, 21, 10)) +>foo : Symbol(foo, Decl(mappedArrayTupleIntersections.ts, 21, 26)) + + ? T extends { bar: number }[] +>T : Symbol(T, Decl(mappedArrayTupleIntersections.ts, 21, 10)) +>bar : Symbol(bar, Decl(mappedArrayTupleIntersections.ts, 22, 15)) + + ? MustBeArray<{ [I in keyof T]: MustHaveFooBar }> +>MustBeArray : Symbol(MustBeArray, Decl(mappedArrayTupleIntersections.ts, 7, 43)) +>I : Symbol(I, Decl(mappedArrayTupleIntersections.ts, 23, 21)) +>T : Symbol(T, Decl(mappedArrayTupleIntersections.ts, 21, 10)) +>MustHaveFooBar : Symbol(MustHaveFooBar, Decl(mappedArrayTupleIntersections.ts, 17, 24)) +>T : Symbol(T, Decl(mappedArrayTupleIntersections.ts, 21, 10)) +>I : Symbol(I, Decl(mappedArrayTupleIntersections.ts, 23, 21)) + + : never + : never; + +type Y = Hmm2<[{ foo: string; bar: number }]>; +>Y : Symbol(Y, Decl(mappedArrayTupleIntersections.ts, 25, 10)) +>Hmm2 : Symbol(Hmm2, Decl(mappedArrayTupleIntersections.ts, 19, 66)) +>foo : Symbol(foo, Decl(mappedArrayTupleIntersections.ts, 27, 16)) +>bar : Symbol(bar, Decl(mappedArrayTupleIntersections.ts, 27, 29)) + +type MustHaveFoo = T; +>MustHaveFoo : Symbol(MustHaveFoo, Decl(mappedArrayTupleIntersections.ts, 27, 46)) +>T : Symbol(T, Decl(mappedArrayTupleIntersections.ts, 29, 17)) +>foo : Symbol(foo, Decl(mappedArrayTupleIntersections.ts, 29, 28)) +>T : Symbol(T, Decl(mappedArrayTupleIntersections.ts, 29, 17)) + +type Hmm3 = T extends { bar: string } +>Hmm3 : Symbol(Hmm3, Decl(mappedArrayTupleIntersections.ts, 29, 49)) +>T : Symbol(T, Decl(mappedArrayTupleIntersections.ts, 31, 10)) +>foo : Symbol(foo, Decl(mappedArrayTupleIntersections.ts, 31, 21)) +>T : Symbol(T, Decl(mappedArrayTupleIntersections.ts, 31, 10)) +>bar : Symbol(bar, Decl(mappedArrayTupleIntersections.ts, 31, 52)) + + ? MustBeArray<{ [I in keyof T]: MustHaveFoo }> +>MustBeArray : Symbol(MustBeArray, Decl(mappedArrayTupleIntersections.ts, 7, 43)) +>I : Symbol(I, Decl(mappedArrayTupleIntersections.ts, 32, 19)) +>T : Symbol(T, Decl(mappedArrayTupleIntersections.ts, 31, 10)) +>MustHaveFoo : Symbol(MustHaveFoo, Decl(mappedArrayTupleIntersections.ts, 27, 46)) +>T : Symbol(T, Decl(mappedArrayTupleIntersections.ts, 31, 10)) +>I : Symbol(I, Decl(mappedArrayTupleIntersections.ts, 32, 19)) + + : never; + +type Z1 = Hmm3<[{ foo: string }]>; +>Z1 : Symbol(Z1, Decl(mappedArrayTupleIntersections.ts, 33, 10)) +>Hmm3 : Symbol(Hmm3, Decl(mappedArrayTupleIntersections.ts, 29, 49)) +>foo : Symbol(foo, Decl(mappedArrayTupleIntersections.ts, 35, 17)) + +type Z2 = Hmm3<[{ foo: string }] & { bar: string }>; +>Z2 : Symbol(Z2, Decl(mappedArrayTupleIntersections.ts, 35, 34)) +>Hmm3 : Symbol(Hmm3, Decl(mappedArrayTupleIntersections.ts, 29, 49)) +>foo : Symbol(foo, Decl(mappedArrayTupleIntersections.ts, 36, 17)) +>bar : Symbol(bar, Decl(mappedArrayTupleIntersections.ts, 36, 36)) + diff --git a/tests/baselines/reference/mappedArrayTupleIntersections.types b/tests/baselines/reference/mappedArrayTupleIntersections.types index d5e5354d64da2..84bb92164e628 100644 --- a/tests/baselines/reference/mappedArrayTupleIntersections.types +++ b/tests/baselines/reference/mappedArrayTupleIntersections.types @@ -1,5 +1,9 @@ //// [tests/cases/compiler/mappedArrayTupleIntersections.ts] //// +=== Performance Stats === +Type Count: 1,000 +Instantiation count: 1,000 -> 2,500 + === mappedArrayTupleIntersections.ts === type Box = { value: T }; >Box : Box @@ -50,3 +54,64 @@ type X = Hmm<[3, 4, 5]>; >X : [1, 1, 1] > : ^^^^^^^^^ +type MustHaveFooBar = T; +>MustHaveFooBar : T +> : ^ +>foo : unknown +> : ^^^^^^^ +>bar : unknown +> : ^^^^^^^ + +type Hmm2 = T extends { foo: string }[] +>Hmm2 : Hmm2 +> : ^^^^^^^ +>foo : string +> : ^^^^^^ + + ? T extends { bar: number }[] +>bar : number +> : ^^^^^^ + + ? MustBeArray<{ [I in keyof T]: MustHaveFooBar }> + : never + : never; + +type Y = Hmm2<[{ foo: string; bar: number }]>; +>Y : [{ foo: string; bar: number; }] +> : ^^^^^^^^ ^^^^^^^ ^^^^ +>foo : string +> : ^^^^^^ +>bar : number +> : ^^^^^^ + +type MustHaveFoo = T; +>MustHaveFoo : T +> : ^ +>foo : unknown +> : ^^^^^^^ + +type Hmm3 = T extends { bar: string } +>Hmm3 : Hmm3 +> : ^^^^^^^ +>foo : string +> : ^^^^^^ +>bar : string +> : ^^^^^^ + + ? MustBeArray<{ [I in keyof T]: MustHaveFoo }> + : never; + +type Z1 = Hmm3<[{ foo: string }]>; +>Z1 : never +> : ^^^^^ +>foo : string +> : ^^^^^^ + +type Z2 = Hmm3<[{ foo: string }] & { bar: string }>; +>Z2 : { [x: number]: { foo: string; }; 0: { foo: string; }; length: 1; toString: () => string; toLocaleString: () => string; pop: () => { foo: string; } | undefined; push: (...items: { foo: string; }[]) => number; concat: { (...items: ConcatArray<{ foo: string; }>[]): { foo: string; }[]; (...items: ({ foo: string; } | ConcatArray<{ foo: string; }>)[]): { foo: string; }[]; }; join: (separator?: string) => string; reverse: () => { foo: string; }[]; shift: () => { foo: string; } | undefined; slice: (start?: number, end?: number) => { foo: string; }[]; sort: (compareFn?: ((a: { foo: string; }, b: { foo: string; }) => number) | undefined) => [{ foo: string; }] & { bar: string; }; splice: { (start: number, deleteCount?: number): { foo: string; }[]; (start: number, deleteCount: number, ...items: { foo: string; }[]): { foo: string; }[]; }; unshift: (...items: { foo: string; }[]) => number; indexOf: (searchElement: { foo: string; }, fromIndex?: number) => number; lastIndexOf: (searchElement: { foo: string; }, fromIndex?: number) => number; every: { (predicate: (value: { foo: string; }, index: number, array: { foo: string; }[]) => value is S, thisArg?: any): this is S[]; (predicate: (value: { foo: string; }, index: number, array: { foo: string; }[]) => unknown, thisArg?: any): boolean; }; some: (predicate: (value: { foo: string; }, index: number, array: { foo: string; }[]) => unknown, thisArg?: any) => boolean; forEach: (callbackfn: (value: { foo: string; }, index: number, array: { foo: string; }[]) => void, thisArg?: any) => void; map: (callbackfn: (value: { foo: string; }, index: number, array: { foo: string; }[]) => U, thisArg?: any) => U[]; filter: { (predicate: (value: { foo: string; }, index: number, array: { foo: string; }[]) => value is S, thisArg?: any): S[]; (predicate: (value: { foo: string; }, index: number, array: { foo: string; }[]) => unknown, thisArg?: any): { foo: string; }[]; }; reduce: { (callbackfn: (previousValue: { foo: string; }, currentValue: { foo: string; }, currentIndex: number, array: { foo: string; }[]) => { foo: string; }): { foo: string; }; (callbackfn: (previousValue: { foo: string; }, currentValue: { foo: string; }, currentIndex: number, array: { foo: string; }[]) => { foo: string; }, initialValue: { foo: string; }): { foo: string; }; (callbackfn: (previousValue: U, currentValue: { foo: string; }, currentIndex: number, array: { foo: string; }[]) => U, initialValue: U): U; }; reduceRight: { (callbackfn: (previousValue: { foo: string; }, currentValue: { foo: string; }, currentIndex: number, array: { foo: string; }[]) => { foo: string; }): { foo: string; }; (callbackfn: (previousValue: { foo: string; }, currentValue: { foo: string; }, currentIndex: number, array: { foo: string; }[]) => { foo: string; }, initialValue: { foo: string; }): { foo: string; }; (callbackfn: (previousValue: U, currentValue: { foo: string; }, currentIndex: number, array: { foo: string; }[]) => U, initialValue: U): U; }; bar: string; } +> : ^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^ ^^^^^^^^^^ ^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^ ^^^^^^^^^^^ ^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^ ^^^ ^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^ ^^ ^^^ ^^^^^^^^^^^^ ^^^^^^^^^^^^^^ ^^^^^ ^^^^^^^^^ ^^^^^ ^^^^^^^^^ ^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^ ^^ ^^ ^^^ ^^^^^^^^^^ ^^^^^^^^ ^^ ^^ ^^ ^^^^^ ^^^^^^^^^ ^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^ ^^^^^^^^^^ ^^^^^^^^^^^^ ^^^^^^^^^ ^^^^^ ^^^ ^^^^^ ^^^^^^^^^^^^^^^^ ^^^^^^^^^ ^^^^^ ^^^ ^^^^^ ^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^ ^^^^^ ^^^ ^^^^^^^^^ ^^^^^ ^^ ^^ ^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^ ^^^ ^^^ ^ ^^^ ^^^ ^^^^^^^^^ ^^^^^ ^^ ^^ ^^^^^^^^^ ^^^^^^^^^^ ^^ ^^^ ^^^ ^^^^^^^^^^^^ ^^^ ^^^^^^^^^ ^^^^^ ^^ ^^ ^^^^^^^^^ ^^^^^^^^^^ ^^ ^^^ ^^^^^ ^^^^^^^^^^^^ ^^^ ^^^^^^^^^ ^^^^^ ^^ ^^ ^^^^^^^^^ ^^^^^^^^^^ ^^ ^^^ ^^^^^ ^^^^^^^^ ^^ ^^^ ^^^^^^^^^ ^^^^^ ^^ ^^ ^^^^^^^^^ ^^^^^^^^^^^^^ ^^^ ^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^ ^^^^^ ^^^ ^^^^^^^^^ ^^^^^ ^^ ^^ ^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^ ^^^ ^^^^^^^^^ ^^^ ^^^^^^^^^ ^^^^^ ^^ ^^ ^^^^^^^^^ ^^^^^^^^^^ ^^ ^^^ ^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^ ^^^ ^^^^^^^^^ ^^^^^ ^^^^^^^^^ ^^^^^ ^^ ^^ ^^^^^^^^^ ^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^ ^^^^^^ ^^^ ^^^^^^^^^ ^^^^^ ^^^^^^^^^ ^^^^^ ^^ ^^ ^^^^^^^^^ ^^^^^^^^^^^^^^^^^ ^^^^^ ^^^^^^^^^ ^^^^^^^^^^^^^ ^^^^^^ ^^ ^^^ ^^^^^ ^^^^^^^^^ ^^^^^ ^^ ^^ ^^^^^^^^^ ^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^ ^^^^^^^^^ ^^^^^ ^^^^^^^^^ ^^^^^ ^^ ^^ ^^^^^^^^^ ^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^ ^^^^^^ ^^^ ^^^^^^^^^ ^^^^^ ^^^^^^^^^ ^^^^^ ^^ ^^ ^^^^^^^^^ ^^^^^^^^^^^^^^^^^ ^^^^^ ^^^^^^^^^ ^^^^^^^^^^^^^ ^^^^^^ ^^ ^^^ ^^^^^ ^^^^^^^^^ ^^^^^ ^^ ^^ ^^^^^^^^^ ^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^ ^^^ +>foo : string +> : ^^^^^^ +>bar : string +> : ^^^^^^ + diff --git a/tests/cases/compiler/mappedArrayTupleIntersections.ts b/tests/cases/compiler/mappedArrayTupleIntersections.ts index 4947e71a5d39c..b6570712f9c81 100644 --- a/tests/cases/compiler/mappedArrayTupleIntersections.ts +++ b/tests/cases/compiler/mappedArrayTupleIntersections.ts @@ -19,3 +19,22 @@ type Hmm = T extends number[] ? never; type X = Hmm<[3, 4, 5]>; + +type MustHaveFooBar = T; + +type Hmm2 = T extends { foo: string }[] + ? T extends { bar: number }[] + ? MustBeArray<{ [I in keyof T]: MustHaveFooBar }> + : never + : never; + +type Y = Hmm2<[{ foo: string; bar: number }]>; + +type MustHaveFoo = T; + +type Hmm3 = T extends { bar: string } + ? MustBeArray<{ [I in keyof T]: MustHaveFoo }> + : never; + +type Z1 = Hmm3<[{ foo: string }]>; +type Z2 = Hmm3<[{ foo: string }] & { bar: string }>;