diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 3d4f288f1293b..75f7accff57b7 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -13689,7 +13689,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const modifiers = getMappedTypeModifiers(type.mappedType); const readonlyMask = modifiers & MappedTypeModifiers.IncludeReadonly ? false : true; const optionalMask = modifiers & MappedTypeModifiers.IncludeOptional ? 0 : SymbolFlags.Optional; - const indexInfos = indexInfo ? [createIndexInfo(stringType, inferReverseMappedType(indexInfo.type, type.mappedType, type.constraintType), readonlyMask && indexInfo.isReadonly)] : emptyArray; + const indexInfos = indexInfo ? [createIndexInfo(stringType, inferReverseMappedType(indexInfo.keyType, indexInfo.type, type.mappedType, type.constraintType, type.inferenceMapper), readonlyMask && indexInfo.isReadonly)] : emptyArray; const members = createSymbolTable(); const limitedConstraint = getLimitedConstraint(type); for (const prop of getPropertiesOfType(type.source)) { @@ -13724,6 +13724,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { inferredProp.links.mappedType = type.mappedType; inferredProp.links.constraintType = type.constraintType; } + if (type.inferenceMapper) { + inferredProp.links.inferenceMapper = type.inferenceMapper; + } members.set(prop.escapedName, inferredProp); } setStructuredTypeMembers(type, members, emptyArray, emptyArray, indexInfos); @@ -13993,13 +13996,13 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { else if ((type as ObjectType).objectFlags & ObjectFlags.ClassOrInterface) { resolveClassOrInterfaceMembers(type as InterfaceType); } - else if ((type as ReverseMappedType).objectFlags & ObjectFlags.ReverseMapped) { + else if ((type as ObjectType).objectFlags & ObjectFlags.ReverseMapped) { resolveReverseMappedTypeMembers(type as ReverseMappedType); } else if ((type as ObjectType).objectFlags & ObjectFlags.Anonymous) { resolveAnonymousTypeMembers(type as AnonymousType); } - else if ((type as MappedType).objectFlags & ObjectFlags.Mapped) { + else if ((type as ObjectType).objectFlags & ObjectFlags.Mapped) { resolveMappedTypeMembers(type as MappedType); } else { @@ -25065,10 +25068,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // For arrays and tuples we infer new arrays and tuples where the reverse mapping has been // applied to the element type(s). if (isArrayType(source)) { - return createArrayType(inferReverseMappedType(getTypeArguments(source)[0], target, constraint), isReadonlyArrayType(source)); + return createArrayType(inferReverseMappedType(numberType, getTypeArguments(source)[0], target, constraint), isReadonlyArrayType(source)); } if (isTupleType(source)) { - const elementTypes = map(getElementTypes(source), t => inferReverseMappedType(t, target, constraint)); + const elementTypes = map(getElementTypes(source), (t, i) => inferReverseMappedType(getStringLiteralType("" + i), t, target, constraint)); const elementFlags = getMappedTypeModifiers(target) & MappedTypeModifiers.IncludeOptional ? sameMap(source.target.elementFlags, f => f & ElementFlags.Optional ? ElementFlags.Required : f) : source.target.elementFlags; @@ -25086,17 +25089,26 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { function getTypeOfReverseMappedSymbol(symbol: ReverseMappedSymbol) { const links = getSymbolLinks(symbol); if (!links.type) { - links.type = inferReverseMappedType(symbol.links.propertyType, symbol.links.mappedType, symbol.links.constraintType); + const propertyNameType = getStringLiteralType(unescapeLeadingUnderscores(symbol.escapedName)); + links.type = inferReverseMappedType(propertyNameType, symbol.links.propertyType, symbol.links.mappedType, symbol.links.constraintType, symbol.links.inferenceMapper); } return links.type; } - function inferReverseMappedType(sourceType: Type, target: MappedType, constraint: IndexType): Type { + function inferReverseMappedType(propertyNameType: Type, sourceType: Type, target: MappedType, constraint: IndexType, inferenceMapper?: TypeMapper): Type { const typeParameter = getIndexedAccessType(constraint.type, getTypeParameterFromMappedType(target)) as TypeParameter; const templateType = getTemplateTypeFromMappedType(target); const inference = createInferenceInfo(typeParameter); inferTypes([inference], sourceType, templateType); - return getTypeFromInference(inference) || unknownType; + const inferredType = getTypeFromInference(inference); + if (inferredType) { + return inferredType; + } + if (!inferenceMapper) { + return getBaseConstraintOfType(typeParameter) || unknownType; + } + + return instantiateType(getConstraintOfType(getIndexedAccessType(constraint.type, propertyNameType)), inferenceMapper) || unknownType; } function* getUnmatchedProperties(source: Type, target: Type, requireOptionalProperties: boolean, matchDiscriminantProperties: boolean): IterableIterator { @@ -26185,10 +26197,17 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const constraint = getConstraintOfTypeParameter(inference.typeParameter); if (constraint) { const instantiatedConstraint = instantiateType(constraint, context.nonFixingMapper); + // TODO: decide what to do about fallback type + if (inferredType && inferredType.flags & TypeFlags.Object && (inferredType as ObjectType).objectFlags & ObjectFlags.ReverseMapped) { + (inferredType as ReverseMappedType).inferenceMapper = context.nonFixingMapper; + } if (!inferredType || !context.compareTypes(inferredType, getTypeWithThisArgument(instantiatedConstraint, inferredType))) { // If the fallback type satisfies the constraint, we pick it. Otherwise, we pick the constraint. inference.inferredType = fallbackType && context.compareTypes(fallbackType, getTypeWithThisArgument(instantiatedConstraint, fallbackType)) ? fallbackType : instantiatedConstraint; } + if (inferredType && inferredType.flags & TypeFlags.Object && (inferredType as ObjectType).objectFlags & ObjectFlags.ReverseMapped) { + (inferredType as ReverseMappedType).inferenceMapper = undefined; + } } } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index e56bba5ab4859..14c896a02813e 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -5945,6 +5945,7 @@ export interface ReverseMappedSymbolLinks extends TransientSymbolLinks { propertyType: Type; mappedType: MappedType; constraintType: IndexType; + inferenceMapper?: TypeMapper; } /** @internal */ @@ -6535,6 +6536,7 @@ export interface ReverseMappedType extends ObjectType { source: Type; mappedType: MappedType; constraintType: IndexType; + inferenceMapper?: TypeMapper; } /** @internal */ diff --git a/tests/baselines/reference/reverseMappedDefaultInferenceToConstraint.symbols b/tests/baselines/reference/reverseMappedDefaultInferenceToConstraint.symbols new file mode 100644 index 0000000000000..3fbd5c76df940 --- /dev/null +++ b/tests/baselines/reference/reverseMappedDefaultInferenceToConstraint.symbols @@ -0,0 +1,154 @@ +//// [tests/cases/compiler/reverseMappedDefaultInferenceToConstraint.ts] //// + +=== reverseMappedDefaultInferenceToConstraint.ts === +// https://github.com/microsoft/TypeScript/issues/56241 + +interface ParameterizedObject { +>ParameterizedObject : Symbol(ParameterizedObject, Decl(reverseMappedDefaultInferenceToConstraint.ts, 0, 0)) + + type: string; +>type : Symbol(ParameterizedObject.type, Decl(reverseMappedDefaultInferenceToConstraint.ts, 2, 31)) + + params?: Record; +>params : Symbol(ParameterizedObject.params, Decl(reverseMappedDefaultInferenceToConstraint.ts, 3, 15)) +>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --)) +} + +declare function setup< +>setup : Symbol(setup, Decl(reverseMappedDefaultInferenceToConstraint.ts, 5, 1)) + + TContext, +>TContext : Symbol(TContext, Decl(reverseMappedDefaultInferenceToConstraint.ts, 7, 23)) + + TGuards extends Record, +>TGuards : Symbol(TGuards, Decl(reverseMappedDefaultInferenceToConstraint.ts, 8, 11)) +>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --)) +>ParameterizedObject : Symbol(ParameterizedObject, Decl(reverseMappedDefaultInferenceToConstraint.ts, 0, 0)) + +>(_: { +>_ : Symbol(_, Decl(reverseMappedDefaultInferenceToConstraint.ts, 10, 2)) + + types: { +>types : Symbol(types, Decl(reverseMappedDefaultInferenceToConstraint.ts, 10, 6)) + + context: TContext; +>context : Symbol(context, Decl(reverseMappedDefaultInferenceToConstraint.ts, 11, 10)) +>TContext : Symbol(TContext, Decl(reverseMappedDefaultInferenceToConstraint.ts, 7, 23)) + + }; + guards: { +>guards : Symbol(guards, Decl(reverseMappedDefaultInferenceToConstraint.ts, 13, 4)) + + [K in keyof TGuards]: (context: TContext, params: TGuards[K]) => void; +>K : Symbol(K, Decl(reverseMappedDefaultInferenceToConstraint.ts, 15, 5)) +>TGuards : Symbol(TGuards, Decl(reverseMappedDefaultInferenceToConstraint.ts, 8, 11)) +>context : Symbol(context, Decl(reverseMappedDefaultInferenceToConstraint.ts, 15, 27)) +>TContext : Symbol(TContext, Decl(reverseMappedDefaultInferenceToConstraint.ts, 7, 23)) +>params : Symbol(params, Decl(reverseMappedDefaultInferenceToConstraint.ts, 15, 45)) +>TGuards : Symbol(TGuards, Decl(reverseMappedDefaultInferenceToConstraint.ts, 8, 11)) +>K : Symbol(K, Decl(reverseMappedDefaultInferenceToConstraint.ts, 15, 5)) + + }; +}): TGuards; +>TGuards : Symbol(TGuards, Decl(reverseMappedDefaultInferenceToConstraint.ts, 8, 11)) + +const result = setup({ +>result : Symbol(result, Decl(reverseMappedDefaultInferenceToConstraint.ts, 19, 5)) +>setup : Symbol(setup, Decl(reverseMappedDefaultInferenceToConstraint.ts, 5, 1)) + + types: { +>types : Symbol(types, Decl(reverseMappedDefaultInferenceToConstraint.ts, 19, 22)) + + context: { +>context : Symbol(context, Decl(reverseMappedDefaultInferenceToConstraint.ts, 20, 10)) + + count: 100, +>count : Symbol(count, Decl(reverseMappedDefaultInferenceToConstraint.ts, 21, 14)) + + }, + }, + guards: { +>guards : Symbol(guards, Decl(reverseMappedDefaultInferenceToConstraint.ts, 24, 4)) + + checkFoo: (_, { foo }: { foo: string }) => foo === "foo", +>checkFoo : Symbol(checkFoo, Decl(reverseMappedDefaultInferenceToConstraint.ts, 25, 11)) +>_ : Symbol(_, Decl(reverseMappedDefaultInferenceToConstraint.ts, 26, 15)) +>foo : Symbol(foo, Decl(reverseMappedDefaultInferenceToConstraint.ts, 26, 19)) +>foo : Symbol(foo, Decl(reverseMappedDefaultInferenceToConstraint.ts, 26, 28)) +>foo : Symbol(foo, Decl(reverseMappedDefaultInferenceToConstraint.ts, 26, 19)) + + alwaysTrue: (_) => true, +>alwaysTrue : Symbol(alwaysTrue, Decl(reverseMappedDefaultInferenceToConstraint.ts, 26, 61)) +>_ : Symbol(_, Decl(reverseMappedDefaultInferenceToConstraint.ts, 27, 17)) + + }, +}); + +declare function foo< +>foo : Symbol(foo, Decl(reverseMappedDefaultInferenceToConstraint.ts, 29, 3)) + + T extends Record, +>T : Symbol(T, Decl(reverseMappedDefaultInferenceToConstraint.ts, 31, 21)) +>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --)) +>PropertyKey : Symbol(PropertyKey, Decl(lib.es5.d.ts, --, --)) +>U : Symbol(U, Decl(reverseMappedDefaultInferenceToConstraint.ts, 32, 35)) + + U extends number | boolean, +>U : Symbol(U, Decl(reverseMappedDefaultInferenceToConstraint.ts, 32, 35)) + +>( + a: { +>a : Symbol(a, Decl(reverseMappedDefaultInferenceToConstraint.ts, 34, 2)) + + [K in keyof T]: (arg: T[K]) => void; +>K : Symbol(K, Decl(reverseMappedDefaultInferenceToConstraint.ts, 36, 5)) +>T : Symbol(T, Decl(reverseMappedDefaultInferenceToConstraint.ts, 31, 21)) +>arg : Symbol(arg, Decl(reverseMappedDefaultInferenceToConstraint.ts, 36, 21)) +>T : Symbol(T, Decl(reverseMappedDefaultInferenceToConstraint.ts, 31, 21)) +>K : Symbol(K, Decl(reverseMappedDefaultInferenceToConstraint.ts, 36, 5)) + + }, + b: U, +>b : Symbol(b, Decl(reverseMappedDefaultInferenceToConstraint.ts, 37, 4)) +>U : Symbol(U, Decl(reverseMappedDefaultInferenceToConstraint.ts, 32, 35)) + +): T; +>T : Symbol(T, Decl(reverseMappedDefaultInferenceToConstraint.ts, 31, 21)) + +declare const num: number; +>num : Symbol(num, Decl(reverseMappedDefaultInferenceToConstraint.ts, 41, 13)) + +const result1 = foo( +>result1 : Symbol(result1, Decl(reverseMappedDefaultInferenceToConstraint.ts, 43, 5)) +>foo : Symbol(foo, Decl(reverseMappedDefaultInferenceToConstraint.ts, 29, 3)) + { + a: (arg) => {}, +>a : Symbol(a, Decl(reverseMappedDefaultInferenceToConstraint.ts, 44, 3)) +>arg : Symbol(arg, Decl(reverseMappedDefaultInferenceToConstraint.ts, 45, 8)) + + b: () => {}, +>b : Symbol(b, Decl(reverseMappedDefaultInferenceToConstraint.ts, 45, 19)) + + }, + num, +>num : Symbol(num, Decl(reverseMappedDefaultInferenceToConstraint.ts, 41, 13)) + +); + +const result2 = foo( +>result2 : Symbol(result2, Decl(reverseMappedDefaultInferenceToConstraint.ts, 51, 5)) +>foo : Symbol(foo, Decl(reverseMappedDefaultInferenceToConstraint.ts, 29, 3)) + { + a: (arg: 100) => {}, +>a : Symbol(a, Decl(reverseMappedDefaultInferenceToConstraint.ts, 52, 3)) +>arg : Symbol(arg, Decl(reverseMappedDefaultInferenceToConstraint.ts, 53, 8)) + + b: () => {}, +>b : Symbol(b, Decl(reverseMappedDefaultInferenceToConstraint.ts, 53, 24)) + + }, + num, +>num : Symbol(num, Decl(reverseMappedDefaultInferenceToConstraint.ts, 41, 13)) + +); + diff --git a/tests/baselines/reference/reverseMappedDefaultInferenceToConstraint.types b/tests/baselines/reference/reverseMappedDefaultInferenceToConstraint.types new file mode 100644 index 0000000000000..1739f49c2470d --- /dev/null +++ b/tests/baselines/reference/reverseMappedDefaultInferenceToConstraint.types @@ -0,0 +1,146 @@ +//// [tests/cases/compiler/reverseMappedDefaultInferenceToConstraint.ts] //// + +=== reverseMappedDefaultInferenceToConstraint.ts === +// https://github.com/microsoft/TypeScript/issues/56241 + +interface ParameterizedObject { + type: string; +>type : string + + params?: Record; +>params : Record | undefined +} + +declare function setup< +>setup : | undefined>>(_: { types: { context: TContext;}; guards: { [K in keyof TGuards]: (context: TContext, params: TGuards[K]) => void; }; }) => TGuards + + TContext, + TGuards extends Record, +>(_: { +>_ : { types: { context: TContext;}; guards: { [K in keyof TGuards]: (context: TContext, params: TGuards[K]) => void; }; } + + types: { +>types : { context: TContext; } + + context: TContext; +>context : TContext + + }; + guards: { +>guards : { [K in keyof TGuards]: (context: TContext, params: TGuards[K]) => void; } + + [K in keyof TGuards]: (context: TContext, params: TGuards[K]) => void; +>context : TContext +>params : TGuards[K] + + }; +}): TGuards; + +const result = setup({ +>result : { checkFoo: { foo: string; }; alwaysTrue: Record | undefined; } +>setup({ types: { context: { count: 100, }, }, guards: { checkFoo: (_, { foo }: { foo: string }) => foo === "foo", alwaysTrue: (_) => true, },}) : { checkFoo: { foo: string; }; alwaysTrue: Record | undefined; } +>setup : | undefined>>(_: { types: { context: TContext; }; guards: { [K in keyof TGuards]: (context: TContext, params: TGuards[K]) => void; }; }) => TGuards +>{ types: { context: { count: 100, }, }, guards: { checkFoo: (_, { foo }: { foo: string }) => foo === "foo", alwaysTrue: (_) => true, },} : { types: { context: { count: number; }; }; guards: { checkFoo: (_: { count: number; }, { foo }: { foo: string; }) => boolean; alwaysTrue: (_: { count: number; }) => boolean; }; } + + types: { +>types : { context: { count: number; }; } +>{ context: { count: 100, }, } : { context: { count: number; }; } + + context: { +>context : { count: number; } +>{ count: 100, } : { count: number; } + + count: 100, +>count : number +>100 : 100 + + }, + }, + guards: { +>guards : { checkFoo: (_: { count: number; }, { foo }: { foo: string; }) => boolean; alwaysTrue: (_: { count: number; }) => boolean; } +>{ checkFoo: (_, { foo }: { foo: string }) => foo === "foo", alwaysTrue: (_) => true, } : { checkFoo: (_: { count: number; }, { foo }: { foo: string; }) => boolean; alwaysTrue: (_: { count: number; }) => boolean; } + + checkFoo: (_, { foo }: { foo: string }) => foo === "foo", +>checkFoo : (_: { count: number; }, { foo }: { foo: string; }) => boolean +>(_, { foo }: { foo: string }) => foo === "foo" : (_: { count: number; }, { foo }: { foo: string; }) => boolean +>_ : { count: number; } +>foo : string +>foo : string +>foo === "foo" : boolean +>foo : string +>"foo" : "foo" + + alwaysTrue: (_) => true, +>alwaysTrue : (_: { count: number; }) => boolean +>(_) => true : (_: { count: number; }) => boolean +>_ : { count: number; } +>true : true + + }, +}); + +declare function foo< +>foo : , U extends number | boolean>(a: { [K in keyof T]: (arg: T[K]) => void; }, b: U) => T + + T extends Record, + U extends number | boolean, +>( + a: { +>a : { [K in keyof T]: (arg: T[K]) => void; } + + [K in keyof T]: (arg: T[K]) => void; +>arg : T[K] + + }, + b: U, +>b : U + +): T; + +declare const num: number; +>num : number + +const result1 = foo( +>result1 : { a: number; b: number; } +>foo( { a: (arg) => {}, b: () => {}, }, num,) : { a: number; b: number; } +>foo : , U extends number | boolean>(a: { [K in keyof T]: (arg: T[K]) => void; }, b: U) => T + { +>{ a: (arg) => {}, b: () => {}, } : { a: (arg: number) => void; b: () => void; } + + a: (arg) => {}, +>a : (arg: number) => void +>(arg) => {} : (arg: number) => void +>arg : number + + b: () => {}, +>b : () => void +>() => {} : () => void + + }, + num, +>num : number + +); + +const result2 = foo( +>result2 : { a: 100; b: number; } +>foo( { a: (arg: 100) => {}, b: () => {}, }, num,) : { a: 100; b: number; } +>foo : , U extends number | boolean>(a: { [K in keyof T]: (arg: T[K]) => void; }, b: U) => T + { +>{ a: (arg: 100) => {}, b: () => {}, } : { a: (arg: 100) => void; b: () => void; } + + a: (arg: 100) => {}, +>a : (arg: 100) => void +>(arg: 100) => {} : (arg: 100) => void +>arg : 100 + + b: () => {}, +>b : () => void +>() => {} : () => void + + }, + num, +>num : number + +); + diff --git a/tests/cases/compiler/reverseMappedDefaultInferenceToConstraint.ts b/tests/cases/compiler/reverseMappedDefaultInferenceToConstraint.ts new file mode 100644 index 0000000000000..ba934f6f4465e --- /dev/null +++ b/tests/cases/compiler/reverseMappedDefaultInferenceToConstraint.ts @@ -0,0 +1,61 @@ +// @strict: true +// @noEmit: true + +// https://github.com/microsoft/TypeScript/issues/56241 + +interface ParameterizedObject { + type: string; + params?: Record; +} + +declare function setup< + TContext, + TGuards extends Record, +>(_: { + types: { + context: TContext; + }; + guards: { + [K in keyof TGuards]: (context: TContext, params: TGuards[K]) => void; + }; +}): TGuards; + +const result = setup({ + types: { + context: { + count: 100, + }, + }, + guards: { + checkFoo: (_, { foo }: { foo: string }) => foo === "foo", + alwaysTrue: (_) => true, + }, +}); + +declare function foo< + T extends Record, + U extends number | boolean, +>( + a: { + [K in keyof T]: (arg: T[K]) => void; + }, + b: U, +): T; + +declare const num: number; + +const result1 = foo( + { + a: (arg) => {}, + b: () => {}, + }, + num, +); + +const result2 = foo( + { + a: (arg: 100) => {}, + b: () => {}, + }, + num, +);