Skip to content

Commit 23992ba

Browse files
authored
Merge pull request #12640 from Microsoft/mappedTypesSecondaryInferences
Classify mapped type inferences as secondary
2 parents f61a224 + 970c4aa commit 23992ba

File tree

8 files changed

+178
-4
lines changed

8 files changed

+178
-4
lines changed

src/compiler/checker.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8706,12 +8706,15 @@ namespace ts {
87068706
if (constraintType.flags & TypeFlags.Index) {
87078707
// We're inferring from some source type S to a homomorphic mapped type { [P in keyof T]: X },
87088708
// where T is a type variable. Use inferTypeForHomomorphicMappedType to infer a suitable source
8709-
// type and then infer from that type to T.
8709+
// type and then make a secondary inference from that type to T. We make a secondary inference
8710+
// such that direct inferences to T get priority over inferences to Partial<T>, for example.
87108711
const index = indexOf(typeVariables, (<IndexType>constraintType).type);
87118712
if (index >= 0 && !typeInferences[index].isFixed) {
87128713
const inferredType = inferTypeForHomomorphicMappedType(source, <MappedType>target);
87138714
if (inferredType) {
8715+
inferiority++;
87148716
inferFromTypes(inferredType, typeVariables[index]);
8717+
inferiority--;
87158718
}
87168719
}
87178720
return;

tests/baselines/reference/isomorphicMappedTypeInference.js

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,14 @@ var g1 = applySpec({
143143
});
144144

145145
// Infers g2: (...args: any[]) => { foo: { bar: { baz: boolean } } }
146-
var g2 = applySpec({ foo: { bar: { baz: (x: any) => true } } });
146+
var g2 = applySpec({ foo: { bar: { baz: (x: any) => true } } });
147+
148+
// Repro from #12633
149+
150+
const foo = <T>(object: T, partial: Partial<T>) => object;
151+
let o = {a: 5, b: 7};
152+
foo(o, {b: 9});
153+
o = foo(o, {b: 9});
147154

148155
//// [isomorphicMappedTypeInference.js]
149156
function box(x) {
@@ -244,6 +251,11 @@ var g1 = applySpec({
244251
});
245252
// Infers g2: (...args: any[]) => { foo: { bar: { baz: boolean } } }
246253
var g2 = applySpec({ foo: { bar: { baz: function (x) { return true; } } } });
254+
// Repro from #12633
255+
var foo = function (object, partial) { return object; };
256+
var o = { a: 5, b: 7 };
257+
foo(o, { b: 9 });
258+
o = foo(o, { b: 9 });
247259

248260

249261
//// [isomorphicMappedTypeInference.d.ts]
@@ -311,3 +323,8 @@ declare var g2: (...args: any[]) => {
311323
};
312324
};
313325
};
326+
declare const foo: <T>(object: T, partial: Partial<T>) => T;
327+
declare let o: {
328+
a: number;
329+
b: number;
330+
};

tests/baselines/reference/isomorphicMappedTypeInference.symbols

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -459,3 +459,31 @@ var g2 = applySpec({ foo: { bar: { baz: (x: any) => true } } });
459459
>baz : Symbol(baz, Decl(isomorphicMappedTypeInference.ts, 144, 34))
460460
>x : Symbol(x, Decl(isomorphicMappedTypeInference.ts, 144, 41))
461461

462+
// Repro from #12633
463+
464+
const foo = <T>(object: T, partial: Partial<T>) => object;
465+
>foo : Symbol(foo, Decl(isomorphicMappedTypeInference.ts, 148, 5))
466+
>T : Symbol(T, Decl(isomorphicMappedTypeInference.ts, 148, 13))
467+
>object : Symbol(object, Decl(isomorphicMappedTypeInference.ts, 148, 16))
468+
>T : Symbol(T, Decl(isomorphicMappedTypeInference.ts, 148, 13))
469+
>partial : Symbol(partial, Decl(isomorphicMappedTypeInference.ts, 148, 26))
470+
>Partial : Symbol(Partial, Decl(lib.d.ts, --, --))
471+
>T : Symbol(T, Decl(isomorphicMappedTypeInference.ts, 148, 13))
472+
>object : Symbol(object, Decl(isomorphicMappedTypeInference.ts, 148, 16))
473+
474+
let o = {a: 5, b: 7};
475+
>o : Symbol(o, Decl(isomorphicMappedTypeInference.ts, 149, 3))
476+
>a : Symbol(a, Decl(isomorphicMappedTypeInference.ts, 149, 9))
477+
>b : Symbol(b, Decl(isomorphicMappedTypeInference.ts, 149, 14))
478+
479+
foo(o, {b: 9});
480+
>foo : Symbol(foo, Decl(isomorphicMappedTypeInference.ts, 148, 5))
481+
>o : Symbol(o, Decl(isomorphicMappedTypeInference.ts, 149, 3))
482+
>b : Symbol(b, Decl(isomorphicMappedTypeInference.ts, 150, 8))
483+
484+
o = foo(o, {b: 9});
485+
>o : Symbol(o, Decl(isomorphicMappedTypeInference.ts, 149, 3))
486+
>foo : Symbol(foo, Decl(isomorphicMappedTypeInference.ts, 148, 5))
487+
>o : Symbol(o, Decl(isomorphicMappedTypeInference.ts, 149, 3))
488+
>b : Symbol(b, Decl(isomorphicMappedTypeInference.ts, 151, 12))
489+

tests/baselines/reference/isomorphicMappedTypeInference.types

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -546,3 +546,42 @@ var g2 = applySpec({ foo: { bar: { baz: (x: any) => true } } });
546546
>x : any
547547
>true : true
548548

549+
// Repro from #12633
550+
551+
const foo = <T>(object: T, partial: Partial<T>) => object;
552+
>foo : <T>(object: T, partial: Partial<T>) => T
553+
><T>(object: T, partial: Partial<T>) => object : <T>(object: T, partial: Partial<T>) => T
554+
>T : T
555+
>object : T
556+
>T : T
557+
>partial : Partial<T>
558+
>Partial : Partial<T>
559+
>T : T
560+
>object : T
561+
562+
let o = {a: 5, b: 7};
563+
>o : { a: number; b: number; }
564+
>{a: 5, b: 7} : { a: number; b: number; }
565+
>a : number
566+
>5 : 5
567+
>b : number
568+
>7 : 7
569+
570+
foo(o, {b: 9});
571+
>foo(o, {b: 9}) : { a: number; b: number; }
572+
>foo : <T>(object: T, partial: Partial<T>) => T
573+
>o : { a: number; b: number; }
574+
>{b: 9} : { b: number; }
575+
>b : number
576+
>9 : 9
577+
578+
o = foo(o, {b: 9});
579+
>o = foo(o, {b: 9}) : { a: number; b: number; }
580+
>o : { a: number; b: number; }
581+
>foo(o, {b: 9}) : { a: number; b: number; }
582+
>foo : <T>(object: T, partial: Partial<T>) => T
583+
>o : { a: number; b: number; }
584+
>{b: 9} : { b: number; }
585+
>b : number
586+
>9 : 9
587+

tests/baselines/reference/mappedTypeErrors.errors.txt

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,15 @@ tests/cases/conformance/types/mapped/mappedTypeErrors.ts(60,9): error TS2403: Su
2020
tests/cases/conformance/types/mapped/mappedTypeErrors.ts(61,9): error TS2403: Subsequent variable declarations must have the same type. Variable 'x' must be of type '{ [P in keyof T]: T[P]; }', but here has type '{ readonly [P in keyof T]: T[P]; }'.
2121
tests/cases/conformance/types/mapped/mappedTypeErrors.ts(62,9): error TS2403: Subsequent variable declarations must have the same type. Variable 'x' must be of type '{ [P in keyof T]: T[P]; }', but here has type '{ readonly [P in keyof T]?: T[P] | undefined; }'.
2222
tests/cases/conformance/types/mapped/mappedTypeErrors.ts(67,9): error TS2403: Subsequent variable declarations must have the same type. Variable 'x' must be of type '{ [P in keyof T]: T[P]; }', but here has type '{ [P in keyof T]: T[P][]; }'.
23+
tests/cases/conformance/types/mapped/mappedTypeErrors.ts(76,45): error TS2345: Argument of type '{ x: number; }' is not assignable to parameter of type 'Readonly<{ x: number; y: number; }>'.
24+
Property 'y' is missing in type '{ x: number; }'.
25+
tests/cases/conformance/types/mapped/mappedTypeErrors.ts(78,59): error TS2345: Argument of type '{ x: number; y: number; z: number; }' is not assignable to parameter of type 'Readonly<{ x: number; y: number; }>'.
26+
Object literal may only specify known properties, and 'z' does not exist in type 'Readonly<{ x: number; y: number; }>'.
27+
tests/cases/conformance/types/mapped/mappedTypeErrors.ts(84,58): error TS2345: Argument of type '{ x: number; y: number; z: number; }' is not assignable to parameter of type 'Partial<{ x: number; y: number; }>'.
28+
Object literal may only specify known properties, and 'z' does not exist in type 'Partial<{ x: number; y: number; }>'.
2329

2430

25-
==== tests/cases/conformance/types/mapped/mappedTypeErrors.ts (14 errors) ====
31+
==== tests/cases/conformance/types/mapped/mappedTypeErrors.ts (17 errors) ====
2632

2733
interface Shape {
2834
name: string;
@@ -126,4 +132,30 @@ tests/cases/conformance/types/mapped/mappedTypeErrors.ts(67,9): error TS2403: Su
126132
var x: { [P in keyof T]: T[P][] }; // Error
127133
~
128134
!!! error TS2403: Subsequent variable declarations must have the same type. Variable 'x' must be of type '{ [P in keyof T]: T[P]; }', but here has type '{ [P in keyof T]: T[P][]; }'.
135+
}
136+
137+
// Check that inferences to mapped types are secondary
138+
139+
declare function objAndReadonly<T>(primary: T, secondary: Readonly<T>): T;
140+
declare function objAndPartial<T>(primary: T, secondary: Partial<T>): T;
141+
142+
function f20() {
143+
let x1 = objAndReadonly({ x: 0, y: 0 }, { x: 1 }); // Error
144+
~~~~~~~~
145+
!!! error TS2345: Argument of type '{ x: number; }' is not assignable to parameter of type 'Readonly<{ x: number; y: number; }>'.
146+
!!! error TS2345: Property 'y' is missing in type '{ x: number; }'.
147+
let x2 = objAndReadonly({ x: 0, y: 0 }, { x: 1, y: 1 });
148+
let x3 = objAndReadonly({ x: 0, y: 0 }, { x: 1, y: 1, z: 1 }); // Error
149+
~~~~
150+
!!! error TS2345: Argument of type '{ x: number; y: number; z: number; }' is not assignable to parameter of type 'Readonly<{ x: number; y: number; }>'.
151+
!!! error TS2345: Object literal may only specify known properties, and 'z' does not exist in type 'Readonly<{ x: number; y: number; }>'.
152+
}
153+
154+
function f21() {
155+
let x1 = objAndPartial({ x: 0, y: 0 }, { x: 1 });
156+
let x2 = objAndPartial({ x: 0, y: 0 }, { x: 1, y: 1 });
157+
let x3 = objAndPartial({ x: 0, y: 0 }, { x: 1, y: 1, z: 1 }); // Error
158+
~~~~
159+
!!! error TS2345: Argument of type '{ x: number; y: number; z: number; }' is not assignable to parameter of type 'Partial<{ x: number; y: number; }>'.
160+
!!! error TS2345: Object literal may only specify known properties, and 'z' does not exist in type 'Partial<{ x: number; y: number; }>'.
129161
}

tests/baselines/reference/mappedTypeErrors.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,23 @@ function f11<T>() {
6666
function f12<T>() {
6767
var x: { [P in keyof T]: T[P] };
6868
var x: { [P in keyof T]: T[P][] }; // Error
69+
}
70+
71+
// Check that inferences to mapped types are secondary
72+
73+
declare function objAndReadonly<T>(primary: T, secondary: Readonly<T>): T;
74+
declare function objAndPartial<T>(primary: T, secondary: Partial<T>): T;
75+
76+
function f20() {
77+
let x1 = objAndReadonly({ x: 0, y: 0 }, { x: 1 }); // Error
78+
let x2 = objAndReadonly({ x: 0, y: 0 }, { x: 1, y: 1 });
79+
let x3 = objAndReadonly({ x: 0, y: 0 }, { x: 1, y: 1, z: 1 }); // Error
80+
}
81+
82+
function f21() {
83+
let x1 = objAndPartial({ x: 0, y: 0 }, { x: 1 });
84+
let x2 = objAndPartial({ x: 0, y: 0 }, { x: 1, y: 1 });
85+
let x3 = objAndPartial({ x: 0, y: 0 }, { x: 1, y: 1, z: 1 }); // Error
6986
}
7087

7188
//// [mappedTypeErrors.js]
@@ -97,6 +114,16 @@ function f12() {
97114
var x;
98115
var x; // Error
99116
}
117+
function f20() {
118+
var x1 = objAndReadonly({ x: 0, y: 0 }, { x: 1 }); // Error
119+
var x2 = objAndReadonly({ x: 0, y: 0 }, { x: 1, y: 1 });
120+
var x3 = objAndReadonly({ x: 0, y: 0 }, { x: 1, y: 1, z: 1 }); // Error
121+
}
122+
function f21() {
123+
var x1 = objAndPartial({ x: 0, y: 0 }, { x: 1 });
124+
var x2 = objAndPartial({ x: 0, y: 0 }, { x: 1, y: 1 });
125+
var x3 = objAndPartial({ x: 0, y: 0 }, { x: 1, y: 1, z: 1 }); // Error
126+
}
100127

101128

102129
//// [mappedTypeErrors.d.ts]
@@ -137,3 +164,7 @@ declare function f4<T extends keyof Named>(x: T): void;
137164
declare function f10<T>(): void;
138165
declare function f11<T>(): void;
139166
declare function f12<T>(): void;
167+
declare function objAndReadonly<T>(primary: T, secondary: Readonly<T>): T;
168+
declare function objAndPartial<T>(primary: T, secondary: Partial<T>): T;
169+
declare function f20(): void;
170+
declare function f21(): void;

tests/cases/conformance/types/mapped/isomorphicMappedTypeInference.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,4 +145,11 @@ var g1 = applySpec({
145145
});
146146

147147
// Infers g2: (...args: any[]) => { foo: { bar: { baz: boolean } } }
148-
var g2 = applySpec({ foo: { bar: { baz: (x: any) => true } } });
148+
var g2 = applySpec({ foo: { bar: { baz: (x: any) => true } } });
149+
150+
// Repro from #12633
151+
152+
const foo = <T>(object: T, partial: Partial<T>) => object;
153+
let o = {a: 5, b: 7};
154+
foo(o, {b: 9});
155+
o = foo(o, {b: 9});

tests/cases/conformance/types/mapped/mappedTypeErrors.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,4 +67,21 @@ function f11<T>() {
6767
function f12<T>() {
6868
var x: { [P in keyof T]: T[P] };
6969
var x: { [P in keyof T]: T[P][] }; // Error
70+
}
71+
72+
// Check that inferences to mapped types are secondary
73+
74+
declare function objAndReadonly<T>(primary: T, secondary: Readonly<T>): T;
75+
declare function objAndPartial<T>(primary: T, secondary: Partial<T>): T;
76+
77+
function f20() {
78+
let x1 = objAndReadonly({ x: 0, y: 0 }, { x: 1 }); // Error
79+
let x2 = objAndReadonly({ x: 0, y: 0 }, { x: 1, y: 1 });
80+
let x3 = objAndReadonly({ x: 0, y: 0 }, { x: 1, y: 1, z: 1 }); // Error
81+
}
82+
83+
function f21() {
84+
let x1 = objAndPartial({ x: 0, y: 0 }, { x: 1 });
85+
let x2 = objAndPartial({ x: 0, y: 0 }, { x: 1, y: 1 });
86+
let x3 = objAndPartial({ x: 0, y: 0 }, { x: 1, y: 1, z: 1 }); // Error
7087
}

0 commit comments

Comments
 (0)