Skip to content

Commit a0d8bcc

Browse files
committed
improve per-property constraints by using other available inferences
1 parent b3cd45e commit a0d8bcc

File tree

5 files changed

+190
-8
lines changed

5 files changed

+190
-8
lines changed

src/compiler/checker.ts

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13689,7 +13689,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1368913689
const modifiers = getMappedTypeModifiers(type.mappedType);
1369013690
const readonlyMask = modifiers & MappedTypeModifiers.IncludeReadonly ? false : true;
1369113691
const optionalMask = modifiers & MappedTypeModifiers.IncludeOptional ? 0 : SymbolFlags.Optional;
13692-
const indexInfos = indexInfo ? [createIndexInfo(stringType, inferReverseMappedType(indexInfo.type, type.mappedType, type.constraintType), readonlyMask && indexInfo.isReadonly)] : emptyArray;
13692+
const indexInfos = indexInfo ? [createIndexInfo(stringType, inferReverseMappedType(indexInfo.keyType, indexInfo.type, type.mappedType, type.constraintType, type.inferenceMapper), readonlyMask && indexInfo.isReadonly)] : emptyArray;
1369313693
const members = createSymbolTable();
1369413694
const limitedConstraint = getLimitedConstraint(type);
1369513695
for (const prop of getPropertiesOfType(type.source)) {
@@ -13724,6 +13724,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1372413724
inferredProp.links.mappedType = type.mappedType;
1372513725
inferredProp.links.constraintType = type.constraintType;
1372613726
}
13727+
if (type.inferenceMapper) {
13728+
inferredProp.links.inferenceMapper = type.inferenceMapper;
13729+
}
1372713730
members.set(prop.escapedName, inferredProp);
1372813731
}
1372913732
setStructuredTypeMembers(type, members, emptyArray, emptyArray, indexInfos);
@@ -13993,13 +13996,13 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1399313996
else if ((type as ObjectType).objectFlags & ObjectFlags.ClassOrInterface) {
1399413997
resolveClassOrInterfaceMembers(type as InterfaceType);
1399513998
}
13996-
else if ((type as ReverseMappedType).objectFlags & ObjectFlags.ReverseMapped) {
13999+
else if ((type as ObjectType).objectFlags & ObjectFlags.ReverseMapped) {
1399714000
resolveReverseMappedTypeMembers(type as ReverseMappedType);
1399814001
}
1399914002
else if ((type as ObjectType).objectFlags & ObjectFlags.Anonymous) {
1400014003
resolveAnonymousTypeMembers(type as AnonymousType);
1400114004
}
14002-
else if ((type as MappedType).objectFlags & ObjectFlags.Mapped) {
14005+
else if ((type as ObjectType).objectFlags & ObjectFlags.Mapped) {
1400314006
resolveMappedTypeMembers(type as MappedType);
1400414007
}
1400514008
else {
@@ -25065,10 +25068,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2506525068
// For arrays and tuples we infer new arrays and tuples where the reverse mapping has been
2506625069
// applied to the element type(s).
2506725070
if (isArrayType(source)) {
25068-
return createArrayType(inferReverseMappedType(getTypeArguments(source)[0], target, constraint), isReadonlyArrayType(source));
25071+
return createArrayType(inferReverseMappedType(numberType, getTypeArguments(source)[0], target, constraint), isReadonlyArrayType(source));
2506925072
}
2507025073
if (isTupleType(source)) {
25071-
const elementTypes = map(getElementTypes(source), t => inferReverseMappedType(t, target, constraint));
25074+
const elementTypes = map(getElementTypes(source), (t, i) => inferReverseMappedType(getStringLiteralType("" + i), t, target, constraint));
2507225075
const elementFlags = getMappedTypeModifiers(target) & MappedTypeModifiers.IncludeOptional ?
2507325076
sameMap(source.target.elementFlags, f => f & ElementFlags.Optional ? ElementFlags.Required : f) :
2507425077
source.target.elementFlags;
@@ -25086,17 +25089,26 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2508625089
function getTypeOfReverseMappedSymbol(symbol: ReverseMappedSymbol) {
2508725090
const links = getSymbolLinks(symbol);
2508825091
if (!links.type) {
25089-
links.type = inferReverseMappedType(symbol.links.propertyType, symbol.links.mappedType, symbol.links.constraintType);
25092+
const propertyNameType = getStringLiteralType(unescapeLeadingUnderscores(symbol.escapedName));
25093+
links.type = inferReverseMappedType(propertyNameType, symbol.links.propertyType, symbol.links.mappedType, symbol.links.constraintType, symbol.links.inferenceMapper);
2509025094
}
2509125095
return links.type;
2509225096
}
2509325097

25094-
function inferReverseMappedType(sourceType: Type, target: MappedType, constraint: IndexType): Type {
25098+
function inferReverseMappedType(propertyNameType: Type, sourceType: Type, target: MappedType, constraint: IndexType, inferenceMapper?: TypeMapper): Type {
2509525099
const typeParameter = getIndexedAccessType(constraint.type, getTypeParameterFromMappedType(target)) as TypeParameter;
2509625100
const templateType = getTemplateTypeFromMappedType(target);
2509725101
const inference = createInferenceInfo(typeParameter);
2509825102
inferTypes([inference], sourceType, templateType);
25099-
return getTypeFromInference(inference) || getBaseConstraintOfType(typeParameter) || unknownType;
25103+
const inferredType = getTypeFromInference(inference);
25104+
if (inferredType) {
25105+
return inferredType;
25106+
}
25107+
if (!inferenceMapper) {
25108+
return getBaseConstraintOfType(typeParameter) || unknownType;
25109+
}
25110+
25111+
return instantiateType(getConstraintOfType(getIndexedAccessType(constraint.type, propertyNameType)), inferenceMapper) || unknownType;
2510025112
}
2510125113

2510225114
function* getUnmatchedProperties(source: Type, target: Type, requireOptionalProperties: boolean, matchDiscriminantProperties: boolean): IterableIterator<Symbol> {
@@ -26185,10 +26197,17 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2618526197
const constraint = getConstraintOfTypeParameter(inference.typeParameter);
2618626198
if (constraint) {
2618726199
const instantiatedConstraint = instantiateType(constraint, context.nonFixingMapper);
26200+
// TODO: decide what to do about fallback type
26201+
if (inferredType && inferredType.flags & TypeFlags.Object && (inferredType as ObjectType).objectFlags & ObjectFlags.ReverseMapped) {
26202+
(inferredType as ReverseMappedType).inferenceMapper = context.nonFixingMapper;
26203+
}
2618826204
if (!inferredType || !context.compareTypes(inferredType, getTypeWithThisArgument(instantiatedConstraint, inferredType))) {
2618926205
// If the fallback type satisfies the constraint, we pick it. Otherwise, we pick the constraint.
2619026206
inference.inferredType = fallbackType && context.compareTypes(fallbackType, getTypeWithThisArgument(instantiatedConstraint, fallbackType)) ? fallbackType : instantiatedConstraint;
2619126207
}
26208+
if (inferredType && inferredType.flags & TypeFlags.Object && (inferredType as ObjectType).objectFlags & ObjectFlags.ReverseMapped) {
26209+
(inferredType as ReverseMappedType).inferenceMapper = undefined;
26210+
}
2619226211
}
2619326212
}
2619426213

src/compiler/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5945,6 +5945,7 @@ export interface ReverseMappedSymbolLinks extends TransientSymbolLinks {
59455945
propertyType: Type;
59465946
mappedType: MappedType;
59475947
constraintType: IndexType;
5948+
inferenceMapper?: TypeMapper;
59485949
}
59495950

59505951
/** @internal */
@@ -6535,6 +6536,7 @@ export interface ReverseMappedType extends ObjectType {
65356536
source: Type;
65366537
mappedType: MappedType;
65376538
constraintType: IndexType;
6539+
inferenceMapper?: TypeMapper;
65386540
}
65396541

65406542
/** @internal */

tests/baselines/reference/reverseMappedDefaultInferenceToConstraint.symbols

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,3 +84,71 @@ const result = setup({
8484
},
8585
});
8686

87+
declare function foo<
88+
>foo : Symbol(foo, Decl(reverseMappedDefaultInferenceToConstraint.ts, 29, 3))
89+
90+
T extends Record<PropertyKey, U>,
91+
>T : Symbol(T, Decl(reverseMappedDefaultInferenceToConstraint.ts, 31, 21))
92+
>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --))
93+
>PropertyKey : Symbol(PropertyKey, Decl(lib.es5.d.ts, --, --))
94+
>U : Symbol(U, Decl(reverseMappedDefaultInferenceToConstraint.ts, 32, 35))
95+
96+
U extends number | boolean,
97+
>U : Symbol(U, Decl(reverseMappedDefaultInferenceToConstraint.ts, 32, 35))
98+
99+
>(
100+
a: {
101+
>a : Symbol(a, Decl(reverseMappedDefaultInferenceToConstraint.ts, 34, 2))
102+
103+
[K in keyof T]: (arg: T[K]) => void;
104+
>K : Symbol(K, Decl(reverseMappedDefaultInferenceToConstraint.ts, 36, 5))
105+
>T : Symbol(T, Decl(reverseMappedDefaultInferenceToConstraint.ts, 31, 21))
106+
>arg : Symbol(arg, Decl(reverseMappedDefaultInferenceToConstraint.ts, 36, 21))
107+
>T : Symbol(T, Decl(reverseMappedDefaultInferenceToConstraint.ts, 31, 21))
108+
>K : Symbol(K, Decl(reverseMappedDefaultInferenceToConstraint.ts, 36, 5))
109+
110+
},
111+
b: U,
112+
>b : Symbol(b, Decl(reverseMappedDefaultInferenceToConstraint.ts, 37, 4))
113+
>U : Symbol(U, Decl(reverseMappedDefaultInferenceToConstraint.ts, 32, 35))
114+
115+
): T;
116+
>T : Symbol(T, Decl(reverseMappedDefaultInferenceToConstraint.ts, 31, 21))
117+
118+
declare const num: number;
119+
>num : Symbol(num, Decl(reverseMappedDefaultInferenceToConstraint.ts, 41, 13))
120+
121+
const result1 = foo(
122+
>result1 : Symbol(result1, Decl(reverseMappedDefaultInferenceToConstraint.ts, 43, 5))
123+
>foo : Symbol(foo, Decl(reverseMappedDefaultInferenceToConstraint.ts, 29, 3))
124+
{
125+
a: (arg) => {},
126+
>a : Symbol(a, Decl(reverseMappedDefaultInferenceToConstraint.ts, 44, 3))
127+
>arg : Symbol(arg, Decl(reverseMappedDefaultInferenceToConstraint.ts, 45, 8))
128+
129+
b: () => {},
130+
>b : Symbol(b, Decl(reverseMappedDefaultInferenceToConstraint.ts, 45, 19))
131+
132+
},
133+
num,
134+
>num : Symbol(num, Decl(reverseMappedDefaultInferenceToConstraint.ts, 41, 13))
135+
136+
);
137+
138+
const result2 = foo(
139+
>result2 : Symbol(result2, Decl(reverseMappedDefaultInferenceToConstraint.ts, 51, 5))
140+
>foo : Symbol(foo, Decl(reverseMappedDefaultInferenceToConstraint.ts, 29, 3))
141+
{
142+
a: (arg: 100) => {},
143+
>a : Symbol(a, Decl(reverseMappedDefaultInferenceToConstraint.ts, 52, 3))
144+
>arg : Symbol(arg, Decl(reverseMappedDefaultInferenceToConstraint.ts, 53, 8))
145+
146+
b: () => {},
147+
>b : Symbol(b, Decl(reverseMappedDefaultInferenceToConstraint.ts, 53, 24))
148+
149+
},
150+
num,
151+
>num : Symbol(num, Decl(reverseMappedDefaultInferenceToConstraint.ts, 41, 13))
152+
153+
);
154+

tests/baselines/reference/reverseMappedDefaultInferenceToConstraint.types

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,3 +79,68 @@ const result = setup({
7979
},
8080
});
8181

82+
declare function foo<
83+
>foo : <T extends Record<PropertyKey, U>, U extends number | boolean>(a: { [K in keyof T]: (arg: T[K]) => void; }, b: U) => T
84+
85+
T extends Record<PropertyKey, U>,
86+
U extends number | boolean,
87+
>(
88+
a: {
89+
>a : { [K in keyof T]: (arg: T[K]) => void; }
90+
91+
[K in keyof T]: (arg: T[K]) => void;
92+
>arg : T[K]
93+
94+
},
95+
b: U,
96+
>b : U
97+
98+
): T;
99+
100+
declare const num: number;
101+
>num : number
102+
103+
const result1 = foo(
104+
>result1 : { a: number; b: number; }
105+
>foo( { a: (arg) => {}, b: () => {}, }, num,) : { a: number; b: number; }
106+
>foo : <T extends Record<PropertyKey, U>, U extends number | boolean>(a: { [K in keyof T]: (arg: T[K]) => void; }, b: U) => T
107+
{
108+
>{ a: (arg) => {}, b: () => {}, } : { a: (arg: number) => void; b: () => void; }
109+
110+
a: (arg) => {},
111+
>a : (arg: number) => void
112+
>(arg) => {} : (arg: number) => void
113+
>arg : number
114+
115+
b: () => {},
116+
>b : () => void
117+
>() => {} : () => void
118+
119+
},
120+
num,
121+
>num : number
122+
123+
);
124+
125+
const result2 = foo(
126+
>result2 : { a: 100; b: number; }
127+
>foo( { a: (arg: 100) => {}, b: () => {}, }, num,) : { a: 100; b: number; }
128+
>foo : <T extends Record<PropertyKey, U>, U extends number | boolean>(a: { [K in keyof T]: (arg: T[K]) => void; }, b: U) => T
129+
{
130+
>{ a: (arg: 100) => {}, b: () => {}, } : { a: (arg: 100) => void; b: () => void; }
131+
132+
a: (arg: 100) => {},
133+
>a : (arg: 100) => void
134+
>(arg: 100) => {} : (arg: 100) => void
135+
>arg : 100
136+
137+
b: () => {},
138+
>b : () => void
139+
>() => {} : () => void
140+
141+
},
142+
num,
143+
>num : number
144+
145+
);
146+

tests/cases/compiler/reverseMappedDefaultInferenceToConstraint.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,31 @@ const result = setup({
3131
alwaysTrue: (_) => true,
3232
},
3333
});
34+
35+
declare function foo<
36+
T extends Record<PropertyKey, U>,
37+
U extends number | boolean,
38+
>(
39+
a: {
40+
[K in keyof T]: (arg: T[K]) => void;
41+
},
42+
b: U,
43+
): T;
44+
45+
declare const num: number;
46+
47+
const result1 = foo(
48+
{
49+
a: (arg) => {},
50+
b: () => {},
51+
},
52+
num,
53+
);
54+
55+
const result2 = foo(
56+
{
57+
a: (arg: 100) => {},
58+
b: () => {},
59+
},
60+
num,
61+
);

0 commit comments

Comments
 (0)