Skip to content

Commit d04e348

Browse files
authored
Improve apparent type of mapped types (#57122)
1 parent 86a1663 commit d04e348

8 files changed

+441
-16
lines changed

Diff for: src/compiler/checker.ts

+24-16
Original file line numberDiff line numberDiff line change
@@ -14551,11 +14551,15 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1455114551
}
1455214552

1455314553
function getResolvedApparentTypeOfMappedType(type: MappedType) {
14554-
const typeVariable = getHomomorphicTypeVariable(type);
14555-
if (typeVariable && !type.declaration.nameType) {
14556-
const constraint = getConstraintOfTypeParameter(typeVariable);
14557-
if (constraint && everyType(constraint, isArrayOrTupleType)) {
14558-
return instantiateType(type, prependTypeMapping(typeVariable, constraint, type.mapper));
14554+
const target = (type.target ?? type) as MappedType;
14555+
const typeVariable = getHomomorphicTypeVariable(target);
14556+
if (typeVariable && !target.declaration.nameType) {
14557+
const constraint = getConstraintTypeFromMappedType(type);
14558+
if (constraint.flags & TypeFlags.Index) {
14559+
const baseConstraint = getBaseConstraintOfType((constraint as IndexType).type);
14560+
if (baseConstraint && everyType(baseConstraint, isArrayOrTupleType)) {
14561+
return instantiateType(target, prependTypeMapping(typeVariable, baseConstraint, type.mapper));
14562+
}
1455914563
}
1456014564
}
1456114565
return type;
@@ -20820,8 +20824,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2082020824
const restIndex = sourceRestType || targetRestType ? paramCount - 1 : -1;
2082120825

2082220826
for (let i = 0; i < paramCount; i++) {
20823-
const sourceType = i === restIndex ? getRestTypeAtPosition(source, i) : tryGetTypeAtPosition(source, i);
20824-
const targetType = i === restIndex ? getRestTypeAtPosition(target, i) : tryGetTypeAtPosition(target, i);
20827+
const sourceType = i === restIndex ? getRestOrAnyTypeAtPosition(source, i) : tryGetTypeAtPosition(source, i);
20828+
const targetType = i === restIndex ? getRestOrAnyTypeAtPosition(target, i) : tryGetTypeAtPosition(target, i);
2082520829
if (sourceType && targetType) {
2082620830
// In order to ensure that any generic type Foo<T> is at least co-variant with respect to T no matter
2082720831
// how Foo uses T, we need to relate parameters bi-variantly (given that parameters are input positions,
@@ -36447,6 +36451,15 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3644736451
return createTupleType(types, flags, readonly, names);
3644836452
}
3644936453

36454+
// Return the rest type at the given position, transforming `any[]` into just `any`. We do this because
36455+
// in signatures we want `any[]` in a rest position to be compatible with anything, but `any[]` isn't
36456+
// assignable to tuple types with required elements.
36457+
function getRestOrAnyTypeAtPosition(source: Signature, pos: number): Type {
36458+
const restType = getRestTypeAtPosition(source, pos);
36459+
const elementType = restType && getElementTypeOfArrayType(restType);
36460+
return elementType && isTypeAny(elementType) ? anyType : restType;
36461+
}
36462+
3645036463
// Return the number of parameters in a signature. The rest parameter, if present, counts as one
3645136464
// parameter. For example, the parameter count of (x: number, y: number, ...z: string[]) is 3 and
3645236465
// the parameter count of (x: number, ...args: [number, ...string[], boolean])) is also 3. In the
@@ -36510,7 +36523,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3651036523
if (signatureHasRestParameter(signature)) {
3651136524
const restType = getTypeOfSymbol(signature.parameters[signature.parameters.length - 1]);
3651236525
if (!isTupleType(restType)) {
36513-
return restType;
36526+
return isTypeAny(restType) ? anyArrayType : restType;
3651436527
}
3651536528
if (restType.target.hasRestElement) {
3651636529
return sliceTupleType(restType, restType.target.fixedLength);
@@ -40510,7 +40523,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
4051040523
const objectIndexType = isGenericMappedType(objectType) && getMappedTypeNameTypeKind(objectType) === MappedTypeNameTypeKind.Remapping
4051140524
? getIndexTypeForMappedType(objectType, IndexFlags.None)
4051240525
: getIndexType(objectType, IndexFlags.None);
40513-
if (isTypeAssignableTo(indexType, objectIndexType)) {
40526+
const hasNumberIndexInfo = !!getIndexInfoOfType(objectType, numberType);
40527+
if (everyType(indexType, t => isTypeAssignableTo(t, objectIndexType) || hasNumberIndexInfo && isApplicableIndexType(t, numberType))) {
4051440528
if (
4051540529
accessNode.kind === SyntaxKind.ElementAccessExpression && isAssignmentTarget(accessNode) &&
4051640530
getObjectFlags(objectType) & ObjectFlags.Mapped && getMappedTypeModifiers(objectType as MappedType) & MappedTypeModifiers.IncludeReadonly
@@ -40519,16 +40533,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
4051940533
}
4052040534
return type;
4052140535
}
40522-
// Check if we're indexing with a numeric type and if either object or index types
40523-
// is a generic type with a constraint that has a numeric index signature.
40524-
const apparentObjectType = getApparentType(objectType);
40525-
if (getIndexInfoOfType(apparentObjectType, numberType) && isTypeAssignableToKind(indexType, TypeFlags.NumberLike)) {
40526-
return type;
40527-
}
4052840536
if (isGenericObjectType(objectType)) {
4052940537
const propertyName = getPropertyNameFromIndex(indexType, accessNode);
4053040538
if (propertyName) {
40531-
const propertySymbol = forEachType(apparentObjectType, t => getPropertyOfType(t, propertyName));
40539+
const propertySymbol = forEachType(getApparentType(objectType), t => getPropertyOfType(t, propertyName));
4053240540
if (propertySymbol && getDeclarationModifierFlagsFromSymbol(propertySymbol) & ModifierFlags.NonPublicAccessibilityModifier) {
4053340541
error(accessNode, Diagnostics.Private_or_protected_member_0_cannot_be_accessed_on_a_type_parameter, unescapeLeadingUnderscores(propertyName));
4053440542
return errorType;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
assignmentToAnyArrayRestParameters.ts(15,25): error TS2339: Property '0.0' does not exist on type 'string[]'.
2+
assignmentToAnyArrayRestParameters.ts(18,16): error TS2536: Type '"0.0"' cannot be used to index type 'T'.
3+
4+
5+
==== assignmentToAnyArrayRestParameters.ts (2 errors) ====
6+
// Repros from #57122
7+
8+
function foo<T extends string[]>(
9+
fa: (s: string, ...args: string[]) => string,
10+
fb: (s: string, ...args: T) => string
11+
) {
12+
const f1: (...args: any) => string = fa;
13+
const f2: (...args: any[]) => string = fa;
14+
const f3: (...args: any) => string = fb;
15+
const f4: (...args: any[]) => string = fb;
16+
}
17+
18+
function bar<T extends string[], K extends number>() {
19+
type T00 = string[]["0"];
20+
type T01 = string[]["0.0"]; // Error
21+
~~~~~
22+
!!! error TS2339: Property '0.0' does not exist on type 'string[]'.
23+
type T02 = string[][K | "0"];
24+
type T10 = T["0"];
25+
type T11 = T["0.0"]; // Error
26+
~~~~~~~~
27+
!!! error TS2536: Type '"0.0"' cannot be used to index type 'T'.
28+
type T12 = T[K | "0"];
29+
}
30+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
//// [tests/cases/compiler/assignmentToAnyArrayRestParameters.ts] ////
2+
3+
=== assignmentToAnyArrayRestParameters.ts ===
4+
// Repros from #57122
5+
6+
function foo<T extends string[]>(
7+
>foo : Symbol(foo, Decl(assignmentToAnyArrayRestParameters.ts, 0, 0))
8+
>T : Symbol(T, Decl(assignmentToAnyArrayRestParameters.ts, 2, 13))
9+
10+
fa: (s: string, ...args: string[]) => string,
11+
>fa : Symbol(fa, Decl(assignmentToAnyArrayRestParameters.ts, 2, 33))
12+
>s : Symbol(s, Decl(assignmentToAnyArrayRestParameters.ts, 3, 9))
13+
>args : Symbol(args, Decl(assignmentToAnyArrayRestParameters.ts, 3, 19))
14+
15+
fb: (s: string, ...args: T) => string
16+
>fb : Symbol(fb, Decl(assignmentToAnyArrayRestParameters.ts, 3, 49))
17+
>s : Symbol(s, Decl(assignmentToAnyArrayRestParameters.ts, 4, 9))
18+
>args : Symbol(args, Decl(assignmentToAnyArrayRestParameters.ts, 4, 19))
19+
>T : Symbol(T, Decl(assignmentToAnyArrayRestParameters.ts, 2, 13))
20+
21+
) {
22+
const f1: (...args: any) => string = fa;
23+
>f1 : Symbol(f1, Decl(assignmentToAnyArrayRestParameters.ts, 6, 9))
24+
>args : Symbol(args, Decl(assignmentToAnyArrayRestParameters.ts, 6, 15))
25+
>fa : Symbol(fa, Decl(assignmentToAnyArrayRestParameters.ts, 2, 33))
26+
27+
const f2: (...args: any[]) => string = fa;
28+
>f2 : Symbol(f2, Decl(assignmentToAnyArrayRestParameters.ts, 7, 9))
29+
>args : Symbol(args, Decl(assignmentToAnyArrayRestParameters.ts, 7, 15))
30+
>fa : Symbol(fa, Decl(assignmentToAnyArrayRestParameters.ts, 2, 33))
31+
32+
const f3: (...args: any) => string = fb;
33+
>f3 : Symbol(f3, Decl(assignmentToAnyArrayRestParameters.ts, 8, 9))
34+
>args : Symbol(args, Decl(assignmentToAnyArrayRestParameters.ts, 8, 15))
35+
>fb : Symbol(fb, Decl(assignmentToAnyArrayRestParameters.ts, 3, 49))
36+
37+
const f4: (...args: any[]) => string = fb;
38+
>f4 : Symbol(f4, Decl(assignmentToAnyArrayRestParameters.ts, 9, 9))
39+
>args : Symbol(args, Decl(assignmentToAnyArrayRestParameters.ts, 9, 15))
40+
>fb : Symbol(fb, Decl(assignmentToAnyArrayRestParameters.ts, 3, 49))
41+
}
42+
43+
function bar<T extends string[], K extends number>() {
44+
>bar : Symbol(bar, Decl(assignmentToAnyArrayRestParameters.ts, 10, 1))
45+
>T : Symbol(T, Decl(assignmentToAnyArrayRestParameters.ts, 12, 13))
46+
>K : Symbol(K, Decl(assignmentToAnyArrayRestParameters.ts, 12, 32))
47+
48+
type T00 = string[]["0"];
49+
>T00 : Symbol(T00, Decl(assignmentToAnyArrayRestParameters.ts, 12, 54))
50+
51+
type T01 = string[]["0.0"]; // Error
52+
>T01 : Symbol(T01, Decl(assignmentToAnyArrayRestParameters.ts, 13, 29))
53+
54+
type T02 = string[][K | "0"];
55+
>T02 : Symbol(T02, Decl(assignmentToAnyArrayRestParameters.ts, 14, 31))
56+
>K : Symbol(K, Decl(assignmentToAnyArrayRestParameters.ts, 12, 32))
57+
58+
type T10 = T["0"];
59+
>T10 : Symbol(T10, Decl(assignmentToAnyArrayRestParameters.ts, 15, 33))
60+
>T : Symbol(T, Decl(assignmentToAnyArrayRestParameters.ts, 12, 13))
61+
62+
type T11 = T["0.0"]; // Error
63+
>T11 : Symbol(T11, Decl(assignmentToAnyArrayRestParameters.ts, 16, 22))
64+
>T : Symbol(T, Decl(assignmentToAnyArrayRestParameters.ts, 12, 13))
65+
66+
type T12 = T[K | "0"];
67+
>T12 : Symbol(T12, Decl(assignmentToAnyArrayRestParameters.ts, 17, 24))
68+
>T : Symbol(T, Decl(assignmentToAnyArrayRestParameters.ts, 12, 13))
69+
>K : Symbol(K, Decl(assignmentToAnyArrayRestParameters.ts, 12, 32))
70+
}
71+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
//// [tests/cases/compiler/assignmentToAnyArrayRestParameters.ts] ////
2+
3+
=== assignmentToAnyArrayRestParameters.ts ===
4+
// Repros from #57122
5+
6+
function foo<T extends string[]>(
7+
>foo : <T extends string[]>(fa: (s: string, ...args: string[]) => string, fb: (s: string, ...args: T) => string) => void
8+
9+
fa: (s: string, ...args: string[]) => string,
10+
>fa : (s: string, ...args: string[]) => string
11+
>s : string
12+
>args : string[]
13+
14+
fb: (s: string, ...args: T) => string
15+
>fb : (s: string, ...args: T) => string
16+
>s : string
17+
>args : T
18+
19+
) {
20+
const f1: (...args: any) => string = fa;
21+
>f1 : (...args: any) => string
22+
>args : any
23+
>fa : (s: string, ...args: string[]) => string
24+
25+
const f2: (...args: any[]) => string = fa;
26+
>f2 : (...args: any[]) => string
27+
>args : any[]
28+
>fa : (s: string, ...args: string[]) => string
29+
30+
const f3: (...args: any) => string = fb;
31+
>f3 : (...args: any) => string
32+
>args : any
33+
>fb : (s: string, ...args: T) => string
34+
35+
const f4: (...args: any[]) => string = fb;
36+
>f4 : (...args: any[]) => string
37+
>args : any[]
38+
>fb : (s: string, ...args: T) => string
39+
}
40+
41+
function bar<T extends string[], K extends number>() {
42+
>bar : <T extends string[], K extends number>() => void
43+
44+
type T00 = string[]["0"];
45+
>T00 : string
46+
47+
type T01 = string[]["0.0"]; // Error
48+
>T01 : any
49+
50+
type T02 = string[][K | "0"];
51+
>T02 : string[][K | "0"]
52+
53+
type T10 = T["0"];
54+
>T10 : T["0"]
55+
56+
type T11 = T["0.0"]; // Error
57+
>T11 : T["0.0"]
58+
59+
type T12 = T[K | "0"];
60+
>T12 : T[K | "0"]
61+
}
62+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
//// [tests/cases/compiler/homomorphicMappedTypeWithNonHomomorphicInstantiationSpreadable1.ts] ////
2+
3+
=== homomorphicMappedTypeWithNonHomomorphicInstantiationSpreadable1.ts ===
4+
type HandleOptions<O> = {
5+
>HandleOptions : Symbol(HandleOptions, Decl(homomorphicMappedTypeWithNonHomomorphicInstantiationSpreadable1.ts, 0, 0))
6+
>O : Symbol(O, Decl(homomorphicMappedTypeWithNonHomomorphicInstantiationSpreadable1.ts, 0, 19))
7+
8+
[I in keyof O]: {
9+
>I : Symbol(I, Decl(homomorphicMappedTypeWithNonHomomorphicInstantiationSpreadable1.ts, 1, 5))
10+
>O : Symbol(O, Decl(homomorphicMappedTypeWithNonHomomorphicInstantiationSpreadable1.ts, 0, 19))
11+
12+
value: O[I];
13+
>value : Symbol(value, Decl(homomorphicMappedTypeWithNonHomomorphicInstantiationSpreadable1.ts, 1, 21))
14+
>O : Symbol(O, Decl(homomorphicMappedTypeWithNonHomomorphicInstantiationSpreadable1.ts, 0, 19))
15+
>I : Symbol(I, Decl(homomorphicMappedTypeWithNonHomomorphicInstantiationSpreadable1.ts, 1, 5))
16+
17+
};
18+
};
19+
20+
declare function func1<
21+
>func1 : Symbol(func1, Decl(homomorphicMappedTypeWithNonHomomorphicInstantiationSpreadable1.ts, 4, 2))
22+
23+
T extends Record<PropertyKey, readonly any[]>,
24+
>T : Symbol(T, Decl(homomorphicMappedTypeWithNonHomomorphicInstantiationSpreadable1.ts, 6, 23))
25+
>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --))
26+
>PropertyKey : Symbol(PropertyKey, Decl(lib.es5.d.ts, --, --))
27+
28+
>(fields: {
29+
>fields : Symbol(fields, Decl(homomorphicMappedTypeWithNonHomomorphicInstantiationSpreadable1.ts, 8, 2))
30+
31+
[K in keyof T]: {
32+
>K : Symbol(K, Decl(homomorphicMappedTypeWithNonHomomorphicInstantiationSpreadable1.ts, 9, 5))
33+
>T : Symbol(T, Decl(homomorphicMappedTypeWithNonHomomorphicInstantiationSpreadable1.ts, 6, 23))
34+
35+
label: string;
36+
>label : Symbol(label, Decl(homomorphicMappedTypeWithNonHomomorphicInstantiationSpreadable1.ts, 9, 21))
37+
38+
options: [...HandleOptions<T[K]>];
39+
>options : Symbol(options, Decl(homomorphicMappedTypeWithNonHomomorphicInstantiationSpreadable1.ts, 10, 22))
40+
>HandleOptions : Symbol(HandleOptions, Decl(homomorphicMappedTypeWithNonHomomorphicInstantiationSpreadable1.ts, 0, 0))
41+
>T : Symbol(T, Decl(homomorphicMappedTypeWithNonHomomorphicInstantiationSpreadable1.ts, 6, 23))
42+
>K : Symbol(K, Decl(homomorphicMappedTypeWithNonHomomorphicInstantiationSpreadable1.ts, 9, 5))
43+
44+
};
45+
}): T;
46+
>T : Symbol(T, Decl(homomorphicMappedTypeWithNonHomomorphicInstantiationSpreadable1.ts, 6, 23))
47+
48+
const result = func1({
49+
>result : Symbol(result, Decl(homomorphicMappedTypeWithNonHomomorphicInstantiationSpreadable1.ts, 15, 5))
50+
>func1 : Symbol(func1, Decl(homomorphicMappedTypeWithNonHomomorphicInstantiationSpreadable1.ts, 4, 2))
51+
52+
prop: {
53+
>prop : Symbol(prop, Decl(homomorphicMappedTypeWithNonHomomorphicInstantiationSpreadable1.ts, 15, 22))
54+
55+
label: "first",
56+
>label : Symbol(label, Decl(homomorphicMappedTypeWithNonHomomorphicInstantiationSpreadable1.ts, 16, 11))
57+
58+
options: [
59+
>options : Symbol(options, Decl(homomorphicMappedTypeWithNonHomomorphicInstantiationSpreadable1.ts, 17, 23))
60+
{
61+
value: 123,
62+
>value : Symbol(value, Decl(homomorphicMappedTypeWithNonHomomorphicInstantiationSpreadable1.ts, 19, 13))
63+
64+
},
65+
{
66+
value: "foo",
67+
>value : Symbol(value, Decl(homomorphicMappedTypeWithNonHomomorphicInstantiationSpreadable1.ts, 22, 13))
68+
69+
},
70+
],
71+
},
72+
other: {
73+
>other : Symbol(other, Decl(homomorphicMappedTypeWithNonHomomorphicInstantiationSpreadable1.ts, 26, 6))
74+
75+
label: "second",
76+
>label : Symbol(label, Decl(homomorphicMappedTypeWithNonHomomorphicInstantiationSpreadable1.ts, 27, 12))
77+
78+
options: [
79+
>options : Symbol(options, Decl(homomorphicMappedTypeWithNonHomomorphicInstantiationSpreadable1.ts, 28, 24))
80+
{
81+
value: "bar",
82+
>value : Symbol(value, Decl(homomorphicMappedTypeWithNonHomomorphicInstantiationSpreadable1.ts, 30, 13))
83+
84+
},
85+
{
86+
value: true,
87+
>value : Symbol(value, Decl(homomorphicMappedTypeWithNonHomomorphicInstantiationSpreadable1.ts, 33, 13))
88+
89+
},
90+
],
91+
},
92+
});
93+

0 commit comments

Comments
 (0)