Skip to content
Open
42 changes: 33 additions & 9 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14647,7 +14647,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) || unknownType, readonlyMask && indexInfo.isReadonly)] : emptyArray;
const indexInfos = indexInfo ? [createIndexInfo(stringType, inferReverseMappedType(indexInfo.type, type.mappedType, type.constraintType, type.inferenceConstraintType && getIndexedAccessTypeOrUndefined(type.inferenceConstraintType, indexInfo.keyType)) ?? unknownType, readonlyMask && indexInfo.isReadonly)] : emptyArray;
const members = createSymbolTable();
const limitedConstraint = getLimitedConstraint(type);
for (const prop of getPropertiesOfType(type.source)) {
Expand Down Expand Up @@ -14682,6 +14682,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
inferredProp.links.mappedType = type.mappedType;
inferredProp.links.constraintType = type.constraintType;
}
inferredProp.links.inferenceConstraintType = type.inferenceConstraintType;
members.set(prop.escapedName, inferredProp);
}
setStructuredTypeMembers(type, members, emptyArray, emptyArray, indexInfos);
Expand Down Expand Up @@ -14961,13 +14962,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 {
Expand Down Expand Up @@ -26428,21 +26429,22 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
function getTypeOfReverseMappedSymbol(symbol: ReverseMappedSymbol): Type {
const links = getSymbolLinks(symbol);
if (!links.type) {
links.type = inferReverseMappedType(symbol.links.propertyType, symbol.links.mappedType, symbol.links.constraintType) || unknownType;
const reverseConstraint = symbol.links.inferenceConstraintType && getIndexedAccessTypeOrUndefined(symbol.links.inferenceConstraintType, getStringLiteralType(unescapeLeadingUnderscores(symbol.escapedName)));
links.type = inferReverseMappedType(symbol.links.propertyType, symbol.links.mappedType, symbol.links.constraintType, reverseConstraint) ?? unknownType;
}
return links.type;
}

function inferReverseMappedTypeWorker(sourceType: Type, target: MappedType, constraint: IndexType): Type {
function inferReverseMappedTypeWorker(sourceType: Type, target: MappedType, constraint: IndexType, reverseMappedConstraint: Type | undefined): 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;
return getTypeFromInference(inference) ?? reverseMappedConstraint ?? unknownType;
}

function inferReverseMappedType(source: Type, target: MappedType, constraint: IndexType): Type | undefined {
const cacheKey = source.id + "," + target.id + "," + constraint.id;
function inferReverseMappedType(source: Type, target: MappedType, constraint: IndexType, reverseMappedConstraint?: Type): Type | undefined {
const cacheKey = source.id + "," + target.id + "," + constraint.id + "," + (reverseMappedConstraint?.id ?? "0");
if (reverseMappedCache.has(cacheKey)) {
return reverseMappedCache.get(cacheKey) || unknownType;
}
Expand All @@ -26453,7 +26455,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
if (isDeeplyNestedType(target, reverseMappedTargetStack, reverseMappedTargetStack.length, 2)) reverseExpandingFlags |= ExpandingFlags.Target;
let type;
if (reverseExpandingFlags !== ExpandingFlags.Both) {
type = inferReverseMappedTypeWorker(source, target, constraint);
type = inferReverseMappedTypeWorker(source, target, constraint, reverseMappedConstraint);
}
reverseMappedSourceStack.pop();
reverseMappedTargetStack.pop();
Expand Down Expand Up @@ -27579,6 +27581,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
if (constraint) {
const instantiatedConstraint = instantiateType(constraint, context.nonFixingMapper);
if (inferredType) {
inferredType = cloneWithInferenceConstraintIfNeeded(inferredType, instantiatedConstraint);
const constraintWithThis = getTypeWithThisArgument(instantiatedConstraint, inferredType);
if (!context.compareTypes(inferredType, constraintWithThis)) {
// If we have a pure return type inference, we may succeed by removing constituents of the inferred type
Expand All @@ -27597,6 +27600,27 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}

return inference.inferredType;

function cloneWithInferenceConstraintIfNeeded(type: Type, inferenceConstraintType: Type) {
if (!(type.flags & TypeFlags.Object) || !((type as ObjectType).objectFlags & ObjectFlags.ReverseMapped)) {
return type;
}
const reversed = type as ReverseMappedType;
const cacheKey = reversed.source.id + "," + reversed.mappedType.id + "," + reversed.constraintType.id + "," + inferenceConstraintType.id;

if (reverseHomomorphicMappedCache.has(cacheKey)) {
return reverseHomomorphicMappedCache.get(cacheKey)!;
}

const clone = createObjectType(ObjectFlags.ReverseMapped | ObjectFlags.Anonymous, /*symbol*/ undefined) as ReverseMappedType;
clone.source = reversed.source;
clone.mappedType = reversed.mappedType;
clone.constraintType = reversed.constraintType;
clone.inferenceConstraintType = inferenceConstraintType;

reverseHomomorphicMappedCache.set(cacheKey, clone);
return clone;
}
}

function getDefaultTypeArgumentType(isInJavaScriptFile: boolean): Type {
Expand Down
2 changes: 2 additions & 0 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6156,6 +6156,7 @@ export interface ReverseMappedSymbolLinks extends TransientSymbolLinks {
propertyType: Type;
mappedType: MappedType;
constraintType: IndexType;
inferenceConstraintType?: Type;
}

/** @internal */
Expand Down Expand Up @@ -6788,6 +6789,7 @@ export interface ReverseMappedType extends ObjectType {
source: Type;
mappedType: MappedType;
constraintType: IndexType;
inferenceConstraintType?: Type;
}

/** @internal */
Expand Down
Original file line number Diff line number Diff line change
@@ -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<string, unknown>;
>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<string, ParameterizedObject["params"] | undefined>,
>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<PropertyKey, U>,
>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))

);

Loading