diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 0d12a7f74fdb4..a92962dd9f046 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -14551,11 +14551,15 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } function getResolvedApparentTypeOfMappedType(type: MappedType) { - const typeVariable = getHomomorphicTypeVariable(type); - if (typeVariable && !type.declaration.nameType) { - const constraint = getConstraintOfTypeParameter(typeVariable); - if (constraint && everyType(constraint, isArrayOrTupleType)) { - return instantiateType(type, prependTypeMapping(typeVariable, constraint, type.mapper)); + const target = (type.target ?? type) as MappedType; + const typeVariable = getHomomorphicTypeVariable(target); + if (typeVariable && !target.declaration.nameType) { + const constraint = getConstraintTypeFromMappedType(type); + if (constraint.flags & TypeFlags.Index) { + const baseConstraint = getBaseConstraintOfType((constraint as IndexType).type); + if (baseConstraint && everyType(baseConstraint, isArrayOrTupleType)) { + return instantiateType(target, prependTypeMapping(typeVariable, baseConstraint, type.mapper)); + } } } return type; @@ -20833,8 +20837,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const restIndex = sourceRestType || targetRestType ? paramCount - 1 : -1; for (let i = 0; i < paramCount; i++) { - const sourceType = i === restIndex ? getRestTypeAtPosition(source, i) : tryGetTypeAtPosition(source, i); - const targetType = i === restIndex ? getRestTypeAtPosition(target, i) : tryGetTypeAtPosition(target, i); + const sourceType = i === restIndex ? getRestOrAnyTypeAtPosition(source, i) : tryGetTypeAtPosition(source, i); + const targetType = i === restIndex ? getRestOrAnyTypeAtPosition(target, i) : tryGetTypeAtPosition(target, i); if (sourceType && targetType) { // In order to ensure that any generic type Foo is at least co-variant with respect to T no matter // how Foo uses T, we need to relate parameters bi-variantly (given that parameters are input positions, @@ -36460,6 +36464,15 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return createTupleType(types, flags, readonly, names); } + // Return the rest type at the given position, transforming `any[]` into just `any`. We do this because + // in signatures we want `any[]` in a rest position to be compatible with anything, but `any[]` isn't + // assignable to tuple types with required elements. + function getRestOrAnyTypeAtPosition(source: Signature, pos: number): Type { + const restType = getRestTypeAtPosition(source, pos); + const elementType = restType && getElementTypeOfArrayType(restType); + return elementType && isTypeAny(elementType) ? anyType : restType; + } + // Return the number of parameters in a signature. The rest parameter, if present, counts as one // parameter. For example, the parameter count of (x: number, y: number, ...z: string[]) is 3 and // the parameter count of (x: number, ...args: [number, ...string[], boolean])) is also 3. In the @@ -36523,7 +36536,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (signatureHasRestParameter(signature)) { const restType = getTypeOfSymbol(signature.parameters[signature.parameters.length - 1]); if (!isTupleType(restType)) { - return restType; + return isTypeAny(restType) ? anyArrayType : restType; } if (restType.target.hasRestElement) { return sliceTupleType(restType, restType.target.fixedLength); @@ -40523,7 +40536,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const objectIndexType = isGenericMappedType(objectType) && getMappedTypeNameTypeKind(objectType) === MappedTypeNameTypeKind.Remapping ? getIndexTypeForMappedType(objectType, IndexFlags.None) : getIndexType(objectType, IndexFlags.None); - if (isTypeAssignableTo(indexType, objectIndexType)) { + const hasNumberIndexInfo = !!getIndexInfoOfType(objectType, numberType); + if (everyType(indexType, t => isTypeAssignableTo(t, objectIndexType) || hasNumberIndexInfo && isApplicableIndexType(t, numberType))) { if ( accessNode.kind === SyntaxKind.ElementAccessExpression && isAssignmentTarget(accessNode) && getObjectFlags(objectType) & ObjectFlags.Mapped && getMappedTypeModifiers(objectType as MappedType) & MappedTypeModifiers.IncludeReadonly @@ -40532,16 +40546,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } return type; } - // Check if we're indexing with a numeric type and if either object or index types - // is a generic type with a constraint that has a numeric index signature. - const apparentObjectType = getApparentType(objectType); - if (getIndexInfoOfType(apparentObjectType, numberType) && isTypeAssignableToKind(indexType, TypeFlags.NumberLike)) { - return type; - } if (isGenericObjectType(objectType)) { const propertyName = getPropertyNameFromIndex(indexType, accessNode); if (propertyName) { - const propertySymbol = forEachType(apparentObjectType, t => getPropertyOfType(t, propertyName)); + const propertySymbol = forEachType(getApparentType(objectType), t => getPropertyOfType(t, propertyName)); if (propertySymbol && getDeclarationModifierFlagsFromSymbol(propertySymbol) & ModifierFlags.NonPublicAccessibilityModifier) { error(accessNode, Diagnostics.Private_or_protected_member_0_cannot_be_accessed_on_a_type_parameter, unescapeLeadingUnderscores(propertyName)); return errorType; diff --git a/tests/baselines/reference/assignmentToAnyArrayRestParameters.errors.txt b/tests/baselines/reference/assignmentToAnyArrayRestParameters.errors.txt new file mode 100644 index 0000000000000..5644d888d42b9 --- /dev/null +++ b/tests/baselines/reference/assignmentToAnyArrayRestParameters.errors.txt @@ -0,0 +1,30 @@ +assignmentToAnyArrayRestParameters.ts(15,25): error TS2339: Property '0.0' does not exist on type 'string[]'. +assignmentToAnyArrayRestParameters.ts(18,16): error TS2536: Type '"0.0"' cannot be used to index type 'T'. + + +==== assignmentToAnyArrayRestParameters.ts (2 errors) ==== + // Repros from #57122 + + function foo( + fa: (s: string, ...args: string[]) => string, + fb: (s: string, ...args: T) => string + ) { + const f1: (...args: any) => string = fa; + const f2: (...args: any[]) => string = fa; + const f3: (...args: any) => string = fb; + const f4: (...args: any[]) => string = fb; + } + + function bar() { + type T00 = string[]["0"]; + type T01 = string[]["0.0"]; // Error + ~~~~~ +!!! error TS2339: Property '0.0' does not exist on type 'string[]'. + type T02 = string[][K | "0"]; + type T10 = T["0"]; + type T11 = T["0.0"]; // Error + ~~~~~~~~ +!!! error TS2536: Type '"0.0"' cannot be used to index type 'T'. + type T12 = T[K | "0"]; + } + \ No newline at end of file diff --git a/tests/baselines/reference/assignmentToAnyArrayRestParameters.symbols b/tests/baselines/reference/assignmentToAnyArrayRestParameters.symbols new file mode 100644 index 0000000000000..a5c7496cdb8b7 --- /dev/null +++ b/tests/baselines/reference/assignmentToAnyArrayRestParameters.symbols @@ -0,0 +1,71 @@ +//// [tests/cases/compiler/assignmentToAnyArrayRestParameters.ts] //// + +=== assignmentToAnyArrayRestParameters.ts === +// Repros from #57122 + +function foo( +>foo : Symbol(foo, Decl(assignmentToAnyArrayRestParameters.ts, 0, 0)) +>T : Symbol(T, Decl(assignmentToAnyArrayRestParameters.ts, 2, 13)) + + fa: (s: string, ...args: string[]) => string, +>fa : Symbol(fa, Decl(assignmentToAnyArrayRestParameters.ts, 2, 33)) +>s : Symbol(s, Decl(assignmentToAnyArrayRestParameters.ts, 3, 9)) +>args : Symbol(args, Decl(assignmentToAnyArrayRestParameters.ts, 3, 19)) + + fb: (s: string, ...args: T) => string +>fb : Symbol(fb, Decl(assignmentToAnyArrayRestParameters.ts, 3, 49)) +>s : Symbol(s, Decl(assignmentToAnyArrayRestParameters.ts, 4, 9)) +>args : Symbol(args, Decl(assignmentToAnyArrayRestParameters.ts, 4, 19)) +>T : Symbol(T, Decl(assignmentToAnyArrayRestParameters.ts, 2, 13)) + +) { + const f1: (...args: any) => string = fa; +>f1 : Symbol(f1, Decl(assignmentToAnyArrayRestParameters.ts, 6, 9)) +>args : Symbol(args, Decl(assignmentToAnyArrayRestParameters.ts, 6, 15)) +>fa : Symbol(fa, Decl(assignmentToAnyArrayRestParameters.ts, 2, 33)) + + const f2: (...args: any[]) => string = fa; +>f2 : Symbol(f2, Decl(assignmentToAnyArrayRestParameters.ts, 7, 9)) +>args : Symbol(args, Decl(assignmentToAnyArrayRestParameters.ts, 7, 15)) +>fa : Symbol(fa, Decl(assignmentToAnyArrayRestParameters.ts, 2, 33)) + + const f3: (...args: any) => string = fb; +>f3 : Symbol(f3, Decl(assignmentToAnyArrayRestParameters.ts, 8, 9)) +>args : Symbol(args, Decl(assignmentToAnyArrayRestParameters.ts, 8, 15)) +>fb : Symbol(fb, Decl(assignmentToAnyArrayRestParameters.ts, 3, 49)) + + const f4: (...args: any[]) => string = fb; +>f4 : Symbol(f4, Decl(assignmentToAnyArrayRestParameters.ts, 9, 9)) +>args : Symbol(args, Decl(assignmentToAnyArrayRestParameters.ts, 9, 15)) +>fb : Symbol(fb, Decl(assignmentToAnyArrayRestParameters.ts, 3, 49)) +} + +function bar() { +>bar : Symbol(bar, Decl(assignmentToAnyArrayRestParameters.ts, 10, 1)) +>T : Symbol(T, Decl(assignmentToAnyArrayRestParameters.ts, 12, 13)) +>K : Symbol(K, Decl(assignmentToAnyArrayRestParameters.ts, 12, 32)) + + type T00 = string[]["0"]; +>T00 : Symbol(T00, Decl(assignmentToAnyArrayRestParameters.ts, 12, 54)) + + type T01 = string[]["0.0"]; // Error +>T01 : Symbol(T01, Decl(assignmentToAnyArrayRestParameters.ts, 13, 29)) + + type T02 = string[][K | "0"]; +>T02 : Symbol(T02, Decl(assignmentToAnyArrayRestParameters.ts, 14, 31)) +>K : Symbol(K, Decl(assignmentToAnyArrayRestParameters.ts, 12, 32)) + + type T10 = T["0"]; +>T10 : Symbol(T10, Decl(assignmentToAnyArrayRestParameters.ts, 15, 33)) +>T : Symbol(T, Decl(assignmentToAnyArrayRestParameters.ts, 12, 13)) + + type T11 = T["0.0"]; // Error +>T11 : Symbol(T11, Decl(assignmentToAnyArrayRestParameters.ts, 16, 22)) +>T : Symbol(T, Decl(assignmentToAnyArrayRestParameters.ts, 12, 13)) + + type T12 = T[K | "0"]; +>T12 : Symbol(T12, Decl(assignmentToAnyArrayRestParameters.ts, 17, 24)) +>T : Symbol(T, Decl(assignmentToAnyArrayRestParameters.ts, 12, 13)) +>K : Symbol(K, Decl(assignmentToAnyArrayRestParameters.ts, 12, 32)) +} + diff --git a/tests/baselines/reference/assignmentToAnyArrayRestParameters.types b/tests/baselines/reference/assignmentToAnyArrayRestParameters.types new file mode 100644 index 0000000000000..c4bb45b3c662e --- /dev/null +++ b/tests/baselines/reference/assignmentToAnyArrayRestParameters.types @@ -0,0 +1,62 @@ +//// [tests/cases/compiler/assignmentToAnyArrayRestParameters.ts] //// + +=== assignmentToAnyArrayRestParameters.ts === +// Repros from #57122 + +function foo( +>foo : (fa: (s: string, ...args: string[]) => string, fb: (s: string, ...args: T) => string) => void + + fa: (s: string, ...args: string[]) => string, +>fa : (s: string, ...args: string[]) => string +>s : string +>args : string[] + + fb: (s: string, ...args: T) => string +>fb : (s: string, ...args: T) => string +>s : string +>args : T + +) { + const f1: (...args: any) => string = fa; +>f1 : (...args: any) => string +>args : any +>fa : (s: string, ...args: string[]) => string + + const f2: (...args: any[]) => string = fa; +>f2 : (...args: any[]) => string +>args : any[] +>fa : (s: string, ...args: string[]) => string + + const f3: (...args: any) => string = fb; +>f3 : (...args: any) => string +>args : any +>fb : (s: string, ...args: T) => string + + const f4: (...args: any[]) => string = fb; +>f4 : (...args: any[]) => string +>args : any[] +>fb : (s: string, ...args: T) => string +} + +function bar() { +>bar : () => void + + type T00 = string[]["0"]; +>T00 : string + + type T01 = string[]["0.0"]; // Error +>T01 : any + + type T02 = string[][K | "0"]; +>T02 : string[][K | "0"] + + type T10 = T["0"]; +>T10 : T["0"] + + type T11 = T["0.0"]; // Error +>T11 : T["0.0"] + + type T12 = T[K | "0"]; +>T12 : T[K | "0"] +} + diff --git a/tests/baselines/reference/homomorphicMappedTypeWithNonHomomorphicInstantiationSpreadable1.symbols b/tests/baselines/reference/homomorphicMappedTypeWithNonHomomorphicInstantiationSpreadable1.symbols new file mode 100644 index 0000000000000..f643150cf97a1 --- /dev/null +++ b/tests/baselines/reference/homomorphicMappedTypeWithNonHomomorphicInstantiationSpreadable1.symbols @@ -0,0 +1,93 @@ +//// [tests/cases/compiler/homomorphicMappedTypeWithNonHomomorphicInstantiationSpreadable1.ts] //// + +=== homomorphicMappedTypeWithNonHomomorphicInstantiationSpreadable1.ts === +type HandleOptions = { +>HandleOptions : Symbol(HandleOptions, Decl(homomorphicMappedTypeWithNonHomomorphicInstantiationSpreadable1.ts, 0, 0)) +>O : Symbol(O, Decl(homomorphicMappedTypeWithNonHomomorphicInstantiationSpreadable1.ts, 0, 19)) + + [I in keyof O]: { +>I : Symbol(I, Decl(homomorphicMappedTypeWithNonHomomorphicInstantiationSpreadable1.ts, 1, 5)) +>O : Symbol(O, Decl(homomorphicMappedTypeWithNonHomomorphicInstantiationSpreadable1.ts, 0, 19)) + + value: O[I]; +>value : Symbol(value, Decl(homomorphicMappedTypeWithNonHomomorphicInstantiationSpreadable1.ts, 1, 21)) +>O : Symbol(O, Decl(homomorphicMappedTypeWithNonHomomorphicInstantiationSpreadable1.ts, 0, 19)) +>I : Symbol(I, Decl(homomorphicMappedTypeWithNonHomomorphicInstantiationSpreadable1.ts, 1, 5)) + + }; +}; + +declare function func1< +>func1 : Symbol(func1, Decl(homomorphicMappedTypeWithNonHomomorphicInstantiationSpreadable1.ts, 4, 2)) + + T extends Record, +>T : Symbol(T, Decl(homomorphicMappedTypeWithNonHomomorphicInstantiationSpreadable1.ts, 6, 23)) +>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --)) +>PropertyKey : Symbol(PropertyKey, Decl(lib.es5.d.ts, --, --)) + +>(fields: { +>fields : Symbol(fields, Decl(homomorphicMappedTypeWithNonHomomorphicInstantiationSpreadable1.ts, 8, 2)) + + [K in keyof T]: { +>K : Symbol(K, Decl(homomorphicMappedTypeWithNonHomomorphicInstantiationSpreadable1.ts, 9, 5)) +>T : Symbol(T, Decl(homomorphicMappedTypeWithNonHomomorphicInstantiationSpreadable1.ts, 6, 23)) + + label: string; +>label : Symbol(label, Decl(homomorphicMappedTypeWithNonHomomorphicInstantiationSpreadable1.ts, 9, 21)) + + options: [...HandleOptions]; +>options : Symbol(options, Decl(homomorphicMappedTypeWithNonHomomorphicInstantiationSpreadable1.ts, 10, 22)) +>HandleOptions : Symbol(HandleOptions, Decl(homomorphicMappedTypeWithNonHomomorphicInstantiationSpreadable1.ts, 0, 0)) +>T : Symbol(T, Decl(homomorphicMappedTypeWithNonHomomorphicInstantiationSpreadable1.ts, 6, 23)) +>K : Symbol(K, Decl(homomorphicMappedTypeWithNonHomomorphicInstantiationSpreadable1.ts, 9, 5)) + + }; +}): T; +>T : Symbol(T, Decl(homomorphicMappedTypeWithNonHomomorphicInstantiationSpreadable1.ts, 6, 23)) + +const result = func1({ +>result : Symbol(result, Decl(homomorphicMappedTypeWithNonHomomorphicInstantiationSpreadable1.ts, 15, 5)) +>func1 : Symbol(func1, Decl(homomorphicMappedTypeWithNonHomomorphicInstantiationSpreadable1.ts, 4, 2)) + + prop: { +>prop : Symbol(prop, Decl(homomorphicMappedTypeWithNonHomomorphicInstantiationSpreadable1.ts, 15, 22)) + + label: "first", +>label : Symbol(label, Decl(homomorphicMappedTypeWithNonHomomorphicInstantiationSpreadable1.ts, 16, 11)) + + options: [ +>options : Symbol(options, Decl(homomorphicMappedTypeWithNonHomomorphicInstantiationSpreadable1.ts, 17, 23)) + { + value: 123, +>value : Symbol(value, Decl(homomorphicMappedTypeWithNonHomomorphicInstantiationSpreadable1.ts, 19, 13)) + + }, + { + value: "foo", +>value : Symbol(value, Decl(homomorphicMappedTypeWithNonHomomorphicInstantiationSpreadable1.ts, 22, 13)) + + }, + ], + }, + other: { +>other : Symbol(other, Decl(homomorphicMappedTypeWithNonHomomorphicInstantiationSpreadable1.ts, 26, 6)) + + label: "second", +>label : Symbol(label, Decl(homomorphicMappedTypeWithNonHomomorphicInstantiationSpreadable1.ts, 27, 12)) + + options: [ +>options : Symbol(options, Decl(homomorphicMappedTypeWithNonHomomorphicInstantiationSpreadable1.ts, 28, 24)) + { + value: "bar", +>value : Symbol(value, Decl(homomorphicMappedTypeWithNonHomomorphicInstantiationSpreadable1.ts, 30, 13)) + + }, + { + value: true, +>value : Symbol(value, Decl(homomorphicMappedTypeWithNonHomomorphicInstantiationSpreadable1.ts, 33, 13)) + + }, + ], + }, +}); + diff --git a/tests/baselines/reference/homomorphicMappedTypeWithNonHomomorphicInstantiationSpreadable1.types b/tests/baselines/reference/homomorphicMappedTypeWithNonHomomorphicInstantiationSpreadable1.types new file mode 100644 index 0000000000000..d50293e983f5f --- /dev/null +++ b/tests/baselines/reference/homomorphicMappedTypeWithNonHomomorphicInstantiationSpreadable1.types @@ -0,0 +1,96 @@ +//// [tests/cases/compiler/homomorphicMappedTypeWithNonHomomorphicInstantiationSpreadable1.ts] //// + +=== homomorphicMappedTypeWithNonHomomorphicInstantiationSpreadable1.ts === +type HandleOptions = { +>HandleOptions : HandleOptions + + [I in keyof O]: { + value: O[I]; +>value : O[I] + + }; +}; + +declare function func1< +>func1 : >(fields: { [K in keyof T]: { label: string; options: [...HandleOptions]; }; }) => T + + T extends Record, +>(fields: { +>fields : { [K in keyof T]: { label: string; options: [...HandleOptions]; }; } + + [K in keyof T]: { + label: string; +>label : string + + options: [...HandleOptions]; +>options : [...HandleOptions] + + }; +}): T; + +const result = func1({ +>result : { prop: [number, string]; other: [string, boolean]; } +>func1({ prop: { label: "first", options: [ { value: 123, }, { value: "foo", }, ], }, other: { label: "second", options: [ { value: "bar", }, { value: true, }, ], },}) : { prop: [number, string]; other: [string, boolean]; } +>func1 : >(fields: { [K in keyof T]: { label: string; options: [...HandleOptions]; }; }) => T +>{ prop: { label: "first", options: [ { value: 123, }, { value: "foo", }, ], }, other: { label: "second", options: [ { value: "bar", }, { value: true, }, ], },} : { prop: { label: string; options: [{ value: number; }, { value: string; }]; }; other: { label: string; options: [{ value: string; }, { value: true; }]; }; } + + prop: { +>prop : { label: string; options: [{ value: number; }, { value: string; }]; } +>{ label: "first", options: [ { value: 123, }, { value: "foo", }, ], } : { label: string; options: [{ value: number; }, { value: string; }]; } + + label: "first", +>label : string +>"first" : "first" + + options: [ +>options : [{ value: number; }, { value: string; }] +>[ { value: 123, }, { value: "foo", }, ] : [{ value: number; }, { value: string; }] + { +>{ value: 123, } : { value: number; } + + value: 123, +>value : number +>123 : 123 + + }, + { +>{ value: "foo", } : { value: string; } + + value: "foo", +>value : string +>"foo" : "foo" + + }, + ], + }, + other: { +>other : { label: string; options: [{ value: string; }, { value: true; }]; } +>{ label: "second", options: [ { value: "bar", }, { value: true, }, ], } : { label: string; options: [{ value: string; }, { value: true; }]; } + + label: "second", +>label : string +>"second" : "second" + + options: [ +>options : [{ value: string; }, { value: true; }] +>[ { value: "bar", }, { value: true, }, ] : [{ value: string; }, { value: true; }] + { +>{ value: "bar", } : { value: string; } + + value: "bar", +>value : string +>"bar" : "bar" + + }, + { +>{ value: true, } : { value: true; } + + value: true, +>value : true +>true : true + + }, + ], + }, +}); + diff --git a/tests/cases/compiler/assignmentToAnyArrayRestParameters.ts b/tests/cases/compiler/assignmentToAnyArrayRestParameters.ts new file mode 100644 index 0000000000000..97b665da22d3c --- /dev/null +++ b/tests/cases/compiler/assignmentToAnyArrayRestParameters.ts @@ -0,0 +1,23 @@ +// @strict: true +// @noEmit: true + +// Repros from #57122 + +function foo( + fa: (s: string, ...args: string[]) => string, + fb: (s: string, ...args: T) => string +) { + const f1: (...args: any) => string = fa; + const f2: (...args: any[]) => string = fa; + const f3: (...args: any) => string = fb; + const f4: (...args: any[]) => string = fb; +} + +function bar() { + type T00 = string[]["0"]; + type T01 = string[]["0.0"]; // Error + type T02 = string[][K | "0"]; + type T10 = T["0"]; + type T11 = T["0.0"]; // Error + type T12 = T[K | "0"]; +} diff --git a/tests/cases/compiler/homomorphicMappedTypeWithNonHomomorphicInstantiationSpreadable1.ts b/tests/cases/compiler/homomorphicMappedTypeWithNonHomomorphicInstantiationSpreadable1.ts new file mode 100644 index 0000000000000..08990dc86aec7 --- /dev/null +++ b/tests/cases/compiler/homomorphicMappedTypeWithNonHomomorphicInstantiationSpreadable1.ts @@ -0,0 +1,42 @@ +// @strict: true +// @noEmit: true + +type HandleOptions = { + [I in keyof O]: { + value: O[I]; + }; +}; + +declare function func1< + T extends Record, +>(fields: { + [K in keyof T]: { + label: string; + options: [...HandleOptions]; + }; +}): T; + +const result = func1({ + prop: { + label: "first", + options: [ + { + value: 123, + }, + { + value: "foo", + }, + ], + }, + other: { + label: "second", + options: [ + { + value: "bar", + }, + { + value: true, + }, + ], + }, +});