Skip to content

Commit

Permalink
Improve apparent type of mapped types (#57122)
Browse files Browse the repository at this point in the history
  • Loading branch information
ahejlsberg committed Feb 19, 2024
1 parent 86a1663 commit d04e348
Show file tree
Hide file tree
Showing 8 changed files with 441 additions and 16 deletions.
40 changes: 24 additions & 16 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -20820,8 +20824,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<T> 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,
Expand Down Expand Up @@ -36447,6 +36451,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
Expand Down Expand Up @@ -36510,7 +36523,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);
Expand Down Expand Up @@ -40510,7 +40523,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
Expand All @@ -40519,16 +40533,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;
Expand Down
Original file line number Diff line number Diff line change
@@ -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<T extends string[]>(
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<T extends string[], K extends number>() {
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"];
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
//// [tests/cases/compiler/assignmentToAnyArrayRestParameters.ts] ////

=== assignmentToAnyArrayRestParameters.ts ===
// Repros from #57122

function foo<T extends string[]>(
>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<T extends string[], K extends number>() {
>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))
}

62 changes: 62 additions & 0 deletions tests/baselines/reference/assignmentToAnyArrayRestParameters.types
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
//// [tests/cases/compiler/assignmentToAnyArrayRestParameters.ts] ////

=== assignmentToAnyArrayRestParameters.ts ===
// Repros from #57122

function foo<T extends string[]>(
>foo : <T extends string[]>(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<T extends string[], K extends number>() {
>bar : <T extends string[], K extends number>() => 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"]
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
//// [tests/cases/compiler/homomorphicMappedTypeWithNonHomomorphicInstantiationSpreadable1.ts] ////

=== homomorphicMappedTypeWithNonHomomorphicInstantiationSpreadable1.ts ===
type HandleOptions<O> = {
>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<PropertyKey, readonly any[]>,
>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<T[K]>];
>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))

},
],
},
});

Loading

0 comments on commit d04e348

Please sign in to comment.