Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve apparent type of mapped types #57122

Merged
merged 9 commits into from Feb 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
40 changes: 24 additions & 16 deletions src/compiler/checker.ts
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 @@ -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<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 @@ -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
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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
Expand All @@ -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;
Expand Down
@@ -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"];
}

@@ -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
@@ -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"]
}

@@ -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))

},
],
},
});