Skip to content

Commit 67476f1

Browse files
committed
Merge pull request #1774 from Microsoft/objectLiteralSubtyping
Include missing optional properties in contextually typed object literals
2 parents 0ff051f + ccffc9f commit 67476f1

25 files changed

+212
-53
lines changed

src/compiler/checker.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5455,6 +5455,17 @@ module ts {
54555455
}
54565456
}
54575457

5458+
// If object literal is contextually (but not inferentially) typed, copy missing optional properties from
5459+
// the contextual type such that the resulting type becomes a subtype in cases where only optional properties
5460+
// were omitted. There is no need to create new property objects as nothing in them needs to change.
5461+
if (contextualType && !isInferentialContext(contextualMapper)) {
5462+
forEach(getPropertiesOfObjectType(contextualType), p => {
5463+
if (p.flags & SymbolFlags.Optional && !hasProperty(properties, p.name)) {
5464+
properties[p.name] = p;
5465+
}
5466+
});
5467+
}
5468+
54585469
var stringIndexType = getIndexType(IndexKind.String);
54595470
var numberIndexType = getIndexType(IndexKind.Number);
54605471
var result = createAnonymousType(node.symbol, properties, emptyArray, emptyArray, stringIndexType, numberIndexType);

tests/baselines/reference/assignmentCompatBug2.errors.txt

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@ tests/cases/compiler/assignmentCompatBug2.ts(1,5): error TS2322: Type '{ a: numb
22
Property 'b' is missing in type '{ a: number; }'.
33
tests/cases/compiler/assignmentCompatBug2.ts(3,1): error TS2322: Type '{ a: number; }' is not assignable to type '{ b: number; }'.
44
Property 'b' is missing in type '{ a: number; }'.
5-
tests/cases/compiler/assignmentCompatBug2.ts(15,1): error TS2322: Type '{ f: (n: number) => number; g: (s: string) => number; }' is not assignable to type '{ f(n: number): number; g(s: string): number; m: number; n?: number; k?(a: any): any; }'.
6-
Property 'm' is missing in type '{ f: (n: number) => number; g: (s: string) => number; }'.
7-
tests/cases/compiler/assignmentCompatBug2.ts(20,1): error TS2322: Type '{ f: (n: number) => number; m: number; }' is not assignable to type '{ f(n: number): number; g(s: string): number; m: number; n?: number; k?(a: any): any; }'.
8-
Property 'g' is missing in type '{ f: (n: number) => number; m: number; }'.
5+
tests/cases/compiler/assignmentCompatBug2.ts(15,1): error TS2322: Type '{ f: (n: number) => number; g: (s: string) => number; n?: number; k?(a: any): any; }' is not assignable to type '{ f(n: number): number; g(s: string): number; m: number; n?: number; k?(a: any): any; }'.
6+
Property 'm' is missing in type '{ f: (n: number) => number; g: (s: string) => number; n?: number; k?(a: any): any; }'.
7+
tests/cases/compiler/assignmentCompatBug2.ts(20,1): error TS2322: Type '{ f: (n: number) => number; m: number; n?: number; k?(a: any): any; }' is not assignable to type '{ f(n: number): number; g(s: string): number; m: number; n?: number; k?(a: any): any; }'.
8+
Property 'g' is missing in type '{ f: (n: number) => number; m: number; n?: number; k?(a: any): any; }'.
99
tests/cases/compiler/assignmentCompatBug2.ts(33,1): error TS2322: Type '{ f: (n: number) => number; g: (s: string) => number; n: number; k: (a: any) => any; }' is not assignable to type '{ f(n: number): number; g(s: string): number; m: number; n?: number; k?(a: any): any; }'.
1010
Property 'm' is missing in type '{ f: (n: number) => number; g: (s: string) => number; n: number; k: (a: any) => any; }'.
1111

@@ -33,16 +33,16 @@ tests/cases/compiler/assignmentCompatBug2.ts(33,1): error TS2322: Type '{ f: (n:
3333

3434
b3 = {
3535
~~
36-
!!! error TS2322: Type '{ f: (n: number) => number; g: (s: string) => number; }' is not assignable to type '{ f(n: number): number; g(s: string): number; m: number; n?: number; k?(a: any): any; }'.
37-
!!! error TS2322: Property 'm' is missing in type '{ f: (n: number) => number; g: (s: string) => number; }'.
36+
!!! error TS2322: Type '{ f: (n: number) => number; g: (s: string) => number; n?: number; k?(a: any): any; }' is not assignable to type '{ f(n: number): number; g(s: string): number; m: number; n?: number; k?(a: any): any; }'.
37+
!!! error TS2322: Property 'm' is missing in type '{ f: (n: number) => number; g: (s: string) => number; n?: number; k?(a: any): any; }'.
3838
f: (n) => { return 0; },
3939
g: (s) => { return 0; },
4040
}; // error
4141

4242
b3 = {
4343
~~
44-
!!! error TS2322: Type '{ f: (n: number) => number; m: number; }' is not assignable to type '{ f(n: number): number; g(s: string): number; m: number; n?: number; k?(a: any): any; }'.
45-
!!! error TS2322: Property 'g' is missing in type '{ f: (n: number) => number; m: number; }'.
44+
!!! error TS2322: Type '{ f: (n: number) => number; m: number; n?: number; k?(a: any): any; }' is not assignable to type '{ f(n: number): number; g(s: string): number; m: number; n?: number; k?(a: any): any; }'.
45+
!!! error TS2322: Property 'g' is missing in type '{ f: (n: number) => number; m: number; n?: number; k?(a: any): any; }'.
4646
f: (n) => { return 0; },
4747
m: 0,
4848
}; // error

tests/baselines/reference/assignmentCompatWithObjectMembers3.types

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,13 +51,13 @@ var b: { foo: string; baz?: string }
5151
var a2: S2 = { foo: '' };
5252
>a2 : S2
5353
>S2 : S2
54-
>{ foo: '' } : { foo: string; }
54+
>{ foo: '' } : { foo: string; bar?: string; }
5555
>foo : string
5656

5757
var b2: T2 = { foo: '' };
5858
>b2 : T2
5959
>T2 : T2
60-
>{ foo: '' } : { foo: string; }
60+
>{ foo: '' } : { foo: string; baz?: string; }
6161
>foo : string
6262

6363
s = t;

tests/baselines/reference/assignmentCompatability1.types

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ module __test1__ {
1212
>U : U
1313
>obj4 : interfaceWithPublicAndOptional<number, string>
1414
>interfaceWithPublicAndOptional : interfaceWithPublicAndOptional<T, U>
15-
>{ one: 1 } : { one: number; }
15+
>{ one: 1 } : { one: number; two?: string; }
1616
>one : number
1717

1818
export var __val__obj4 = obj4;

tests/baselines/reference/assignmentCompatability2.types

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ module __test1__ {
1212
>U : U
1313
>obj4 : interfaceWithPublicAndOptional<number, string>
1414
>interfaceWithPublicAndOptional : interfaceWithPublicAndOptional<T, U>
15-
>{ one: 1 } : { one: number; }
15+
>{ one: 1 } : { one: number; two?: string; }
1616
>one : number
1717

1818
export var __val__obj4 = obj4;

tests/baselines/reference/assignmentCompatability3.types

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ module __test1__ {
1212
>U : U
1313
>obj4 : interfaceWithPublicAndOptional<number, string>
1414
>interfaceWithPublicAndOptional : interfaceWithPublicAndOptional<T, U>
15-
>{ one: 1 } : { one: number; }
15+
>{ one: 1 } : { one: number; two?: string; }
1616
>one : number
1717

1818
export var __val__obj4 = obj4;

tests/baselines/reference/assignmentCompatability4.types

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ module __test1__ {
1212
>U : U
1313
>obj4 : interfaceWithPublicAndOptional<number, string>
1414
>interfaceWithPublicAndOptional : interfaceWithPublicAndOptional<T, U>
15-
>{ one: 1 } : { one: number; }
15+
>{ one: 1 } : { one: number; two?: string; }
1616
>one : number
1717

1818
export var __val__obj4 = obj4;

tests/baselines/reference/assignmentCompatability5.types

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ module __test1__ {
1212
>U : U
1313
>obj4 : interfaceWithPublicAndOptional<number, string>
1414
>interfaceWithPublicAndOptional : interfaceWithPublicAndOptional<T, U>
15-
>{ one: 1 } : { one: number; }
15+
>{ one: 1 } : { one: number; two?: string; }
1616
>one : number
1717

1818
export var __val__obj4 = obj4;

tests/baselines/reference/assignmentCompatability6.types

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ module __test1__ {
1212
>U : U
1313
>obj4 : interfaceWithPublicAndOptional<number, string>
1414
>interfaceWithPublicAndOptional : interfaceWithPublicAndOptional<T, U>
15-
>{ one: 1 } : { one: number; }
15+
>{ one: 1 } : { one: number; two?: string; }
1616
>one : number
1717

1818
export var __val__obj4 = obj4;
@@ -29,7 +29,7 @@ module __test2__ {
2929
>T : T
3030
>obj3 : interfaceWithOptional<number>
3131
>interfaceWithOptional : interfaceWithOptional<T>
32-
>{ } : {}
32+
>{ } : { one?: number; }
3333

3434
export var __val__obj3 = obj3;
3535
>__val__obj3 : interfaceWithOptional<number>

tests/baselines/reference/assignmentCompatability7.types

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ module __test1__ {
1212
>U : U
1313
>obj4 : interfaceWithPublicAndOptional<number, string>
1414
>interfaceWithPublicAndOptional : interfaceWithPublicAndOptional<T, U>
15-
>{ one: 1 } : { one: number; }
15+
>{ one: 1 } : { one: number; two?: string; }
1616
>one : number
1717

1818
export var __val__obj4 = obj4;
@@ -32,7 +32,7 @@ module __test2__ {
3232
>U : U
3333
>obj4 : interfaceWithPublicAndOptional<number, string>
3434
>interfaceWithPublicAndOptional : interfaceWithPublicAndOptional<T, U>
35-
>{ one: 1 } : { one: number; }
35+
>{ one: 1 } : { one: number; two?: string; }
3636
>one : number
3737

3838
export var __val__obj4 = obj4;

tests/baselines/reference/assignmentCompatability8.types

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ module __test1__ {
1212
>U : U
1313
>obj4 : interfaceWithPublicAndOptional<number, string>
1414
>interfaceWithPublicAndOptional : interfaceWithPublicAndOptional<T, U>
15-
>{ one: 1 } : { one: number; }
15+
>{ one: 1 } : { one: number; two?: string; }
1616
>one : number
1717

1818
export var __val__obj4 = obj4;

tests/baselines/reference/assignmentCompatability9.types

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ module __test1__ {
1212
>U : U
1313
>obj4 : interfaceWithPublicAndOptional<number, string>
1414
>interfaceWithPublicAndOptional : interfaceWithPublicAndOptional<T, U>
15-
>{ one: 1 } : { one: number; }
15+
>{ one: 1 } : { one: number; two?: string; }
1616
>one : number
1717

1818
export var __val__obj4 = obj4;

tests/baselines/reference/getterSetterNonAccessor.types

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ Object.defineProperty({}, "0", <PropertyDescriptor>({
1414
>{} : {}
1515
><PropertyDescriptor>({ get: getFunc, set: setFunc, configurable: true }) : PropertyDescriptor
1616
>PropertyDescriptor : PropertyDescriptor
17-
>({ get: getFunc, set: setFunc, configurable: true }) : { get: () => any; set: (v: any) => void; configurable: boolean; }
18-
>{ get: getFunc, set: setFunc, configurable: true } : { get: () => any; set: (v: any) => void; configurable: boolean; }
17+
>({ get: getFunc, set: setFunc, configurable: true }) : { get: () => any; set: (v: any) => void; configurable: boolean; enumerable?: boolean; value?: any; writable?: boolean; }
18+
>{ get: getFunc, set: setFunc, configurable: true } : { get: () => any; set: (v: any) => void; configurable: boolean; enumerable?: boolean; value?: any; writable?: boolean; }
1919

2020
get: getFunc,
2121
>get : () => any

tests/baselines/reference/intTypeCheck.errors.txt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@ tests/cases/compiler/intTypeCheck.ts(146,5): error TS2322: Type 'boolean' is not
3939
tests/cases/compiler/intTypeCheck.ts(146,21): error TS1109: Expression expected.
4040
tests/cases/compiler/intTypeCheck.ts(146,22): error TS2304: Cannot find name 'i4'.
4141
tests/cases/compiler/intTypeCheck.ts(147,17): error TS2351: Cannot use 'new' with an expression whose type lacks a call or construct signature.
42-
tests/cases/compiler/intTypeCheck.ts(152,5): error TS2322: Type '{}' is not assignable to type 'i5'.
43-
Property 'p' is missing in type '{}'.
42+
tests/cases/compiler/intTypeCheck.ts(152,5): error TS2322: Type '{ p1?: any; p2?: string; p4?(): any; p5?(): void; p7?(pa1: any, pa2: any): void; }' is not assignable to type 'i5'.
43+
Property 'p' is missing in type '{ p1?: any; p2?: string; p4?(): any; p5?(): void; p7?(pa1: any, pa2: any): void; }'.
4444
tests/cases/compiler/intTypeCheck.ts(153,5): error TS2322: Type 'Object' is not assignable to type 'i5'.
4545
Property 'p' is missing in type 'Object'.
4646
tests/cases/compiler/intTypeCheck.ts(154,17): error TS2351: Cannot use 'new' with an expression whose type lacks a call or construct signature.
@@ -313,8 +313,8 @@ tests/cases/compiler/intTypeCheck.ts(203,17): error TS2351: Cannot use 'new' wit
313313
var obj44: i5;
314314
var obj45: i5 = {};
315315
~~~~~
316-
!!! error TS2322: Type '{}' is not assignable to type 'i5'.
317-
!!! error TS2322: Property 'p' is missing in type '{}'.
316+
!!! error TS2322: Type '{ p1?: any; p2?: string; p4?(): any; p5?(): void; p7?(pa1: any, pa2: any): void; }' is not assignable to type 'i5'.
317+
!!! error TS2322: Property 'p' is missing in type '{ p1?: any; p2?: string; p4?(): any; p5?(): void; p7?(pa1: any, pa2: any): void; }'.
318318
var obj46: i5 = new Object();
319319
~~~~~
320320
!!! error TS2322: Type 'Object' is not assignable to type 'i5'.

tests/baselines/reference/interfaceContextualType.types

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,27 +34,27 @@ class Bug {
3434
>{} : { [x: string]: undefined; }
3535

3636
this.values['comments'] = { italic: true };
37-
>this.values['comments'] = { italic: true } : { italic: boolean; }
37+
>this.values['comments'] = { italic: true } : { italic: boolean; bold?: boolean; }
3838
>this.values['comments'] : IOptions
3939
>this.values : IMap
4040
>this : Bug
4141
>values : IMap
42-
>{ italic: true } : { italic: boolean; }
42+
>{ italic: true } : { italic: boolean; bold?: boolean; }
4343
>italic : boolean
4444
}
4545
shouldBeOK() {
4646
>shouldBeOK : () => void
4747

4848
this.values = {
49-
>this.values = { comments: { italic: true } } : { [x: string]: { italic: boolean; }; comments: { italic: boolean; }; }
49+
>this.values = { comments: { italic: true } } : { [x: string]: { italic: boolean; bold?: boolean; }; comments: { italic: boolean; bold?: boolean; }; }
5050
>this.values : IMap
5151
>this : Bug
5252
>values : IMap
53-
>{ comments: { italic: true } } : { [x: string]: { italic: boolean; }; comments: { italic: boolean; }; }
53+
>{ comments: { italic: true } } : { [x: string]: { italic: boolean; bold?: boolean; }; comments: { italic: boolean; bold?: boolean; }; }
5454

5555
comments: { italic: true }
56-
>comments : { italic: boolean; }
57-
>{ italic: true } : { italic: boolean; }
56+
>comments : { italic: boolean; bold?: boolean; }
57+
>{ italic: true } : { italic: boolean; bold?: boolean; }
5858
>italic : boolean
5959

6060
};

tests/baselines/reference/objectLitGetterSetter.types

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@
1111
>obj : {}
1212
><PropertyDescriptor>({ get: function () { eval("public = 1;"); return 11; }, set: function (v) { } }) : PropertyDescriptor
1313
>PropertyDescriptor : PropertyDescriptor
14-
>({ get: function () { eval("public = 1;"); return 11; }, set: function (v) { } }) : { get: () => number; set: (v: any) => void; }
15-
>{ get: function () { eval("public = 1;"); return 11; }, set: function (v) { } } : { get: () => number; set: (v: any) => void; }
14+
>({ get: function () { eval("public = 1;"); return 11; }, set: function (v) { } }) : { get: () => number; set: (v: any) => void; configurable?: boolean; enumerable?: boolean; value?: any; writable?: boolean; }
15+
>{ get: function () { eval("public = 1;"); return 11; }, set: function (v) { } } : { get: () => number; set: (v: any) => void; configurable?: boolean; enumerable?: boolean; value?: any; writable?: boolean; }
1616

1717
get: function () {
1818
>get : () => number
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
//// [objectLiteralContextualTyping.ts]
2+
// Tests related to #1774
3+
4+
interface Item {
5+
name: string;
6+
description?: string;
7+
}
8+
9+
declare function foo(item: Item): string;
10+
declare function foo(item: any): number;
11+
12+
var x = foo({ name: "Sprocket" });
13+
var x: string;
14+
15+
var y = foo({ name: "Sprocket", description: "Bumpy wheel" });
16+
var y: string;
17+
18+
var z = foo({ name: "Sprocket", description: false });
19+
var z: number;
20+
21+
var w = foo({ a: 10 });
22+
var w: number;
23+
24+
declare function bar<T>(param: { x?: T }): T;
25+
26+
var b = bar({});
27+
var b: {};
28+
29+
30+
//// [objectLiteralContextualTyping.js]
31+
// Tests related to #1774
32+
var x = foo({ name: "Sprocket" });
33+
var x;
34+
var y = foo({ name: "Sprocket", description: "Bumpy wheel" });
35+
var y;
36+
var z = foo({ name: "Sprocket", description: false });
37+
var z;
38+
var w = foo({ a: 10 });
39+
var w;
40+
var b = bar({});
41+
var b;
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
=== tests/cases/conformance/expressions/contextualTyping/objectLiteralContextualTyping.ts ===
2+
// Tests related to #1774
3+
4+
interface Item {
5+
>Item : Item
6+
7+
name: string;
8+
>name : string
9+
10+
description?: string;
11+
>description : string
12+
}
13+
14+
declare function foo(item: Item): string;
15+
>foo : { (item: Item): string; (item: any): number; }
16+
>item : Item
17+
>Item : Item
18+
19+
declare function foo(item: any): number;
20+
>foo : { (item: Item): string; (item: any): number; }
21+
>item : any
22+
23+
var x = foo({ name: "Sprocket" });
24+
>x : string
25+
>foo({ name: "Sprocket" }) : string
26+
>foo : { (item: Item): string; (item: any): number; }
27+
>{ name: "Sprocket" } : { name: string; description?: string; }
28+
>name : string
29+
30+
var x: string;
31+
>x : string
32+
33+
var y = foo({ name: "Sprocket", description: "Bumpy wheel" });
34+
>y : string
35+
>foo({ name: "Sprocket", description: "Bumpy wheel" }) : string
36+
>foo : { (item: Item): string; (item: any): number; }
37+
>{ name: "Sprocket", description: "Bumpy wheel" } : { name: string; description: string; }
38+
>name : string
39+
>description : string
40+
41+
var y: string;
42+
>y : string
43+
44+
var z = foo({ name: "Sprocket", description: false });
45+
>z : number
46+
>foo({ name: "Sprocket", description: false }) : number
47+
>foo : { (item: Item): string; (item: any): number; }
48+
>{ name: "Sprocket", description: false } : { name: string; description: boolean; }
49+
>name : string
50+
>description : boolean
51+
52+
var z: number;
53+
>z : number
54+
55+
var w = foo({ a: 10 });
56+
>w : number
57+
>foo({ a: 10 }) : number
58+
>foo : { (item: Item): string; (item: any): number; }
59+
>{ a: 10 } : { a: number; }
60+
>a : number
61+
62+
var w: number;
63+
>w : number
64+
65+
declare function bar<T>(param: { x?: T }): T;
66+
>bar : <T>(param: { x?: T; }) => T
67+
>T : T
68+
>param : { x?: T; }
69+
>x : T
70+
>T : T
71+
>T : T
72+
73+
var b = bar({});
74+
>b : {}
75+
>bar({}) : {}
76+
>bar : <T>(param: { x?: T; }) => T
77+
>{} : { x?: {}; }
78+
79+
var b: {};
80+
>b : {}
81+

tests/baselines/reference/optionalAccessorsInInterface1.types

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ defineMyProperty({}, "name", { get: function () { return 5; } });
2121
>defineMyProperty({}, "name", { get: function () { return 5; } }) : any
2222
>defineMyProperty : (o: any, p: string, attributes: MyPropertyDescriptor) => any
2323
>{} : {}
24-
>{ get: function () { return 5; } } : { get: () => number; }
24+
>{ get: function () { return 5; } } : { get: () => number; set?(v: any): void; }
2525
>get : () => number
2626
>function () { return 5; } : () => number
2727

@@ -47,7 +47,7 @@ defineMyProperty2({}, "name", { get: function () { return 5; } });
4747
>defineMyProperty2({}, "name", { get: function () { return 5; } }) : any
4848
>defineMyProperty2 : (o: any, p: string, attributes: MyPropertyDescriptor2) => any
4949
>{} : {}
50-
>{ get: function () { return 5; } } : { get: () => number; }
50+
>{ get: function () { return 5; } } : { get: () => number; set?: (v: any) => void; }
5151
>get : () => number
5252
>function () { return 5; } : () => number
5353

0 commit comments

Comments
 (0)