Skip to content

Commit ae3d1d4

Browse files
authored
Merge pull request #31221 from microsoft/improveReverseMappedTypes
Improve reverse mapped types
2 parents bca2808 + c104aa1 commit ae3d1d4

10 files changed

+873
-26
lines changed

src/compiler/checker.ts

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14921,10 +14921,19 @@ namespace ts {
1492114921
return type;
1492214922
}
1492314923

14924+
// We consider a type to be partially inferable if it isn't marked non-inferable or if it is
14925+
// an object literal type with at least one property of an inferable type. For example, an object
14926+
// literal { a: 123, b: x => true } is marked non-inferable because it contains a context sensitive
14927+
// arrow function, but is considered partially inferable because property 'a' has an inferable type.
14928+
function isPartiallyInferableType(type: Type): boolean {
14929+
return !(getObjectFlags(type) & ObjectFlags.NonInferrableType) ||
14930+
isObjectLiteralType(type) && some(getPropertiesOfType(type), prop => isPartiallyInferableType(getTypeOfSymbol(prop)));
14931+
}
14932+
1492414933
function createReverseMappedType(source: Type, target: MappedType, constraint: IndexType) {
14925-
// If any property contains context sensitive functions that have been skipped, the source type
14926-
// is incomplete and we can't infer a meaningful input type.
14927-
if (getObjectFlags(source) & ObjectFlags.NonInferrableType || getPropertiesOfType(source).length === 0 && !getIndexInfoOfType(source, IndexKind.String)) {
14934+
// We consider a source type reverse mappable if it has a string index signature or if
14935+
// it has one or more properties and is of a partially inferable type.
14936+
if (!(getIndexInfoOfType(source, IndexKind.String) || getPropertiesOfType(source).length !== 0 && isPartiallyInferableType(source))) {
1492814937
return undefined;
1492914938
}
1493014939
// For arrays and tuples we infer new arrays and tuples where the reverse mapping has been
@@ -15309,7 +15318,11 @@ namespace ts {
1530915318
const inferredType = inferTypeForHomomorphicMappedType(source, target, <IndexType>constraintType);
1531015319
if (inferredType) {
1531115320
const savePriority = priority;
15312-
priority |= InferencePriority.HomomorphicMappedType;
15321+
// We assign a lower priority to inferences made from types containing non-inferrable
15322+
// types because we may only have a partial result (i.e. we may have failed to make
15323+
// reverse inferences for some properties).
15324+
priority |= getObjectFlags(source) & ObjectFlags.NonInferrableType ?
15325+
InferencePriority.PartialHomomorphicMappedType : InferencePriority.HomomorphicMappedType;
1531315326
inferFromTypes(inferredType, inference.typeParameter);
1531415327
priority = savePriority;
1531515328
}

src/compiler/types.ts

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4424,15 +4424,16 @@ namespace ts {
44244424
export type TypeMapper = (t: TypeParameter) => Type;
44254425

44264426
export const enum InferencePriority {
4427-
NakedTypeVariable = 1 << 0, // Naked type variable in union or intersection type
4428-
HomomorphicMappedType = 1 << 1, // Reverse inference for homomorphic mapped type
4429-
MappedTypeConstraint = 1 << 2, // Reverse inference for mapped type
4430-
ReturnType = 1 << 3, // Inference made from return type of generic function
4431-
LiteralKeyof = 1 << 4, // Inference made from a string literal to a keyof T
4432-
NoConstraints = 1 << 5, // Don't infer from constraints of instantiable types
4433-
AlwaysStrict = 1 << 6, // Always use strict rules for contravariant inferences
4434-
4435-
PriorityImpliesCombination = ReturnType | MappedTypeConstraint | LiteralKeyof, // These priorities imply that the resulting type should be a combination of all candidates
4427+
NakedTypeVariable = 1 << 0, // Naked type variable in union or intersection type
4428+
HomomorphicMappedType = 1 << 1, // Reverse inference for homomorphic mapped type
4429+
PartialHomomorphicMappedType = 1 << 2, // Partial reverse inference for homomorphic mapped type
4430+
MappedTypeConstraint = 1 << 3, // Reverse inference for mapped type
4431+
ReturnType = 1 << 4, // Inference made from return type of generic function
4432+
LiteralKeyof = 1 << 5, // Inference made from a string literal to a keyof T
4433+
NoConstraints = 1 << 6, // Don't infer from constraints of instantiable types
4434+
AlwaysStrict = 1 << 7, // Always use strict rules for contravariant inferences
4435+
4436+
PriorityImpliesCombination = ReturnType | MappedTypeConstraint | LiteralKeyof, // These priorities imply that the resulting type should be a combination of all candidates
44364437
}
44374438

44384439
/* @internal */

tests/baselines/reference/api/tsserverlibrary.d.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2418,12 +2418,13 @@ declare namespace ts {
24182418
enum InferencePriority {
24192419
NakedTypeVariable = 1,
24202420
HomomorphicMappedType = 2,
2421-
MappedTypeConstraint = 4,
2422-
ReturnType = 8,
2423-
LiteralKeyof = 16,
2424-
NoConstraints = 32,
2425-
AlwaysStrict = 64,
2426-
PriorityImpliesCombination = 28
2421+
PartialHomomorphicMappedType = 4,
2422+
MappedTypeConstraint = 8,
2423+
ReturnType = 16,
2424+
LiteralKeyof = 32,
2425+
NoConstraints = 64,
2426+
AlwaysStrict = 128,
2427+
PriorityImpliesCombination = 56
24272428
}
24282429
/** @deprecated Use FileExtensionInfo instead. */
24292430
type JsFileExtensionInfo = FileExtensionInfo;

tests/baselines/reference/api/typescript.d.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2418,12 +2418,13 @@ declare namespace ts {
24182418
enum InferencePriority {
24192419
NakedTypeVariable = 1,
24202420
HomomorphicMappedType = 2,
2421-
MappedTypeConstraint = 4,
2422-
ReturnType = 8,
2423-
LiteralKeyof = 16,
2424-
NoConstraints = 32,
2425-
AlwaysStrict = 64,
2426-
PriorityImpliesCombination = 28
2421+
PartialHomomorphicMappedType = 4,
2422+
MappedTypeConstraint = 8,
2423+
ReturnType = 16,
2424+
LiteralKeyof = 32,
2425+
NoConstraints = 64,
2426+
AlwaysStrict = 128,
2427+
PriorityImpliesCombination = 56
24272428
}
24282429
/** @deprecated Use FileExtensionInfo instead. */
24292430
type JsFileExtensionInfo = FileExtensionInfo;

tests/baselines/reference/mappedTypeInferenceErrors.errors.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ tests/cases/conformance/types/mapped/mappedTypeInferenceErrors.ts(16,9): error T
2020
baz: 42
2121
~~~
2222
!!! error TS2322: Type 'number' is not assignable to type '() => unknown'.
23-
!!! related TS6500 tests/cases/conformance/types/mapped/mappedTypeInferenceErrors.ts:16:9: The expected type comes from property 'baz' which is declared here on type 'ComputedOf<{ bar: number; baz: unknown; }>'
23+
!!! related TS6500 tests/cases/conformance/types/mapped/mappedTypeInferenceErrors.ts:16:9: The expected type comes from property 'baz' which is declared here on type 'ComputedOf<{ bar: unknown; baz: unknown; }>'
2424
}
2525
});
2626

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
tests/cases/compiler/reverseMappedPartiallyInferableTypes.ts(91,20): error TS2571: Object is of type 'unknown'.
2+
3+
4+
==== tests/cases/compiler/reverseMappedPartiallyInferableTypes.ts (1 errors) ====
5+
// Repro from #30505
6+
7+
export type Prop<T> = { (): T }
8+
export type PropType<T> = Prop<T>;
9+
export type PropDefaultValue<T> = T;
10+
11+
12+
export type PropValidatorFunction<T> = (value: T) => boolean;
13+
export type PropValidator<T> = PropOptions<T>;
14+
15+
16+
export type PropOptions<T> = {
17+
type: PropType<T>;
18+
19+
value?: PropDefaultValue<T>,
20+
required?: boolean;
21+
validator?: PropValidatorFunction<T>;
22+
}
23+
24+
export type RecordPropsDefinition<T> = {
25+
[K in keyof T]: PropValidator<T[K]>
26+
}
27+
export type PropsDefinition<T> = RecordPropsDefinition<T>;
28+
29+
30+
declare function extend<T>({ props }: { props: PropsDefinition<T> }): PropsDefinition<T>;
31+
32+
interface MyType {
33+
valid: boolean;
34+
}
35+
36+
const r = extend({
37+
props: {
38+
notResolved: {
39+
type: Object as PropType<MyType>,
40+
validator: x => {
41+
return x.valid;
42+
}
43+
},
44+
explicit: {
45+
type: Object as PropType<MyType>,
46+
validator: (x: MyType) => {
47+
return x.valid;
48+
}
49+
}
50+
}
51+
})
52+
53+
r.explicit
54+
r.notResolved
55+
r.explicit.required
56+
r.notResolved.required
57+
58+
// Modified repro from #30505
59+
60+
type Box<T> = {
61+
contents?: T;
62+
contains?(content: T): boolean;
63+
};
64+
65+
type Mapped<T> = {
66+
[K in keyof T]: Box<T[K]>;
67+
}
68+
69+
declare function id<T>(arg: Mapped<T>): Mapped<T>;
70+
71+
// All properties have inferable types
72+
73+
const obj1 = id({
74+
foo: {
75+
contents: ""
76+
}
77+
});
78+
79+
// Some properties have inferable types
80+
81+
const obj2 = id({
82+
foo: {
83+
contents: "",
84+
contains(k) {
85+
return k.length > 0;
86+
}
87+
}
88+
});
89+
90+
// No properties have inferable types
91+
92+
const obj3 = id({
93+
foo: {
94+
contains(k) {
95+
return k.length > 0;
96+
~
97+
!!! error TS2571: Object is of type 'unknown'.
98+
}
99+
}
100+
});
101+
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
//// [reverseMappedPartiallyInferableTypes.ts]
2+
// Repro from #30505
3+
4+
export type Prop<T> = { (): T }
5+
export type PropType<T> = Prop<T>;
6+
export type PropDefaultValue<T> = T;
7+
8+
9+
export type PropValidatorFunction<T> = (value: T) => boolean;
10+
export type PropValidator<T> = PropOptions<T>;
11+
12+
13+
export type PropOptions<T> = {
14+
type: PropType<T>;
15+
16+
value?: PropDefaultValue<T>,
17+
required?: boolean;
18+
validator?: PropValidatorFunction<T>;
19+
}
20+
21+
export type RecordPropsDefinition<T> = {
22+
[K in keyof T]: PropValidator<T[K]>
23+
}
24+
export type PropsDefinition<T> = RecordPropsDefinition<T>;
25+
26+
27+
declare function extend<T>({ props }: { props: PropsDefinition<T> }): PropsDefinition<T>;
28+
29+
interface MyType {
30+
valid: boolean;
31+
}
32+
33+
const r = extend({
34+
props: {
35+
notResolved: {
36+
type: Object as PropType<MyType>,
37+
validator: x => {
38+
return x.valid;
39+
}
40+
},
41+
explicit: {
42+
type: Object as PropType<MyType>,
43+
validator: (x: MyType) => {
44+
return x.valid;
45+
}
46+
}
47+
}
48+
})
49+
50+
r.explicit
51+
r.notResolved
52+
r.explicit.required
53+
r.notResolved.required
54+
55+
// Modified repro from #30505
56+
57+
type Box<T> = {
58+
contents?: T;
59+
contains?(content: T): boolean;
60+
};
61+
62+
type Mapped<T> = {
63+
[K in keyof T]: Box<T[K]>;
64+
}
65+
66+
declare function id<T>(arg: Mapped<T>): Mapped<T>;
67+
68+
// All properties have inferable types
69+
70+
const obj1 = id({
71+
foo: {
72+
contents: ""
73+
}
74+
});
75+
76+
// Some properties have inferable types
77+
78+
const obj2 = id({
79+
foo: {
80+
contents: "",
81+
contains(k) {
82+
return k.length > 0;
83+
}
84+
}
85+
});
86+
87+
// No properties have inferable types
88+
89+
const obj3 = id({
90+
foo: {
91+
contains(k) {
92+
return k.length > 0;
93+
}
94+
}
95+
});
96+
97+
98+
//// [reverseMappedPartiallyInferableTypes.js]
99+
"use strict";
100+
// Repro from #30505
101+
exports.__esModule = true;
102+
var r = extend({
103+
props: {
104+
notResolved: {
105+
type: Object,
106+
validator: function (x) {
107+
return x.valid;
108+
}
109+
},
110+
explicit: {
111+
type: Object,
112+
validator: function (x) {
113+
return x.valid;
114+
}
115+
}
116+
}
117+
});
118+
r.explicit;
119+
r.notResolved;
120+
r.explicit.required;
121+
r.notResolved.required;
122+
// All properties have inferable types
123+
var obj1 = id({
124+
foo: {
125+
contents: ""
126+
}
127+
});
128+
// Some properties have inferable types
129+
var obj2 = id({
130+
foo: {
131+
contents: "",
132+
contains: function (k) {
133+
return k.length > 0;
134+
}
135+
}
136+
});
137+
// No properties have inferable types
138+
var obj3 = id({
139+
foo: {
140+
contains: function (k) {
141+
return k.length > 0;
142+
}
143+
}
144+
});

0 commit comments

Comments
 (0)