Skip to content

Commit 04c061e

Browse files
authored
fix(jsii-diff): correctly handle assignability of type unions (#500)
1 parent 05f5189 commit 04c061e

File tree

3 files changed

+161
-11
lines changed

3 files changed

+161
-11
lines changed

packages/jsii-diff/lib/type-analysis.ts

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { flatMap } from './util';
44
/**
55
* Check whether A is a supertype of B
66
*
7-
* Put differently: whether an value of type B would be assignable to a
7+
* Put differently: whether any value of type B would be assignable to a
88
* variable of type A.
99
*
1010
* We always check the relationship in the NEW (latest, updated) typesystem.
@@ -33,16 +33,7 @@ export function isSuperType(a: reflect.TypeReference, b: reflect.TypeReference,
3333
`${b} is not assignable to ${a}`);
3434
}
3535

36-
// Any element of A should accept all of B
37-
if (a.unionOfTypes !== undefined) {
38-
const analyses = a.unionOfTypes.map(aaa => isSuperType(aaa, b, updatedSystem));
39-
if (analyses.some(x => x.success)) { return { success: true }; }
40-
return failure(
41-
`none of ${b} are assignable to ${a}`,
42-
...flatMap(analyses, x => x.success ? [] : x.reasons)
43-
);
44-
}
45-
// All potential elements of B should go into A
36+
// Every element of B can be assigned to A
4637
if (b.unionOfTypes !== undefined) {
4738
const analyses = b.unionOfTypes.map(bbb => isSuperType(a, bbb, updatedSystem));
4839
if (analyses.every(x => x.success)) { return { success: true }; }
@@ -51,6 +42,15 @@ export function isSuperType(a: reflect.TypeReference, b: reflect.TypeReference,
5142
...flatMap(analyses, x => x.success ? [] : x.reasons)
5243
);
5344
}
45+
// There should be an element of A which can accept all of B
46+
if (a.unionOfTypes !== undefined) {
47+
const analyses = a.unionOfTypes.map(aaa => isSuperType(aaa, b, updatedSystem));
48+
if (analyses.some(x => x.success)) { return { success: true }; }
49+
return failure(
50+
`none of ${b} are assignable to ${a}`,
51+
...flatMap(analyses, x => x.success ? [] : x.reasons)
52+
);
53+
}
5454

5555
// For named types, we'll always do a nominal typing relationship.
5656
// That is, if in the updated typesystem someone were to use the type name

packages/jsii-diff/package.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,11 @@
3535
"typescript": "^3.4.3",
3636
"yargs": "^13.2.2"
3737
},
38+
"repository": {
39+
"type": "git",
40+
"url": "https://github.com/awslabs/jsii.git",
41+
"directory": "packages/jsii-diff"
42+
},
3843
"nyc": {
3944
"reporter": [
4045
"lcov",
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
import { Test } from 'nodeunit';
2+
import { expectError, expectNoError } from './util';
3+
4+
export = {
5+
6+
// ----------------------------------------------------------------------
7+
8+
async 'type unions in return structs can be the same'(test: Test) {
9+
await expectNoError(test,
10+
`
11+
export interface Henk {
12+
readonly henk: string | number;
13+
}
14+
export class Actions {
15+
returnHenk(): Henk { return { henk: 'henk' }; }
16+
}
17+
`, `
18+
export interface Henk {
19+
readonly henk: string | number;
20+
}
21+
export class Actions {
22+
returnHenk(): Henk { return { henk: 'henk' }; }
23+
}
24+
`);
25+
26+
test.done();
27+
},
28+
29+
// ----------------------------------------------------------------------
30+
31+
async 'type unions in return structs can be narrowed'(test: Test) {
32+
await expectNoError(test,
33+
`
34+
export interface Henk {
35+
readonly henk: string | number;
36+
}
37+
export class Actions {
38+
returnHenk(): Henk { return { henk: 'henk' }; }
39+
}
40+
`, `
41+
export interface Henk {
42+
readonly henk: string;
43+
}
44+
export class Actions {
45+
returnHenk(): Henk { return { henk: 'henk' }; }
46+
}
47+
`);
48+
49+
test.done();
50+
},
51+
52+
// ----------------------------------------------------------------------
53+
54+
async 'type unions in return structs can not be widened'(test: Test) {
55+
await expectError(test,
56+
/some of string \| number \| boolean are not assignable to string \| number/,
57+
`
58+
export interface Henk {
59+
readonly henk: string | number;
60+
}
61+
export class Actions {
62+
returnHenk(): Henk { return { henk: 'henk' }; }
63+
}
64+
`, `
65+
export interface Henk {
66+
readonly henk: string | number | boolean;
67+
}
68+
export class Actions {
69+
returnHenk(): Henk { return { henk: 'henk' }; }
70+
}
71+
`);
72+
73+
test.done();
74+
},
75+
76+
// ----------------------------------------------------------------------
77+
78+
async 'type unions in input structs can be the same'(test: Test) {
79+
await expectNoError(test,
80+
`
81+
export interface Henk {
82+
readonly henk: string | number;
83+
}
84+
export class Actions {
85+
takeHenk(_henk: Henk): void { }
86+
}
87+
`, `
88+
export interface Henk {
89+
readonly henk: string | number;
90+
}
91+
export class Actions {
92+
takeHenk(_henk: Henk): void { }
93+
}
94+
`);
95+
96+
test.done();
97+
},
98+
99+
// ----------------------------------------------------------------------
100+
101+
async 'type unions in input structs can be widened'(test: Test) {
102+
await expectNoError(test,
103+
`
104+
export interface Henk {
105+
readonly henk: string | number;
106+
}
107+
export class Actions {
108+
takeHenk(_henk: Henk): void { }
109+
}
110+
`, `
111+
export interface Henk {
112+
readonly henk: string | number | boolean;
113+
}
114+
export class Actions {
115+
takeHenk(_henk: Henk): void { }
116+
}
117+
`);
118+
119+
test.done();
120+
},
121+
122+
// ----------------------------------------------------------------------
123+
124+
async 'type unions in input structs can not be narrowed'(test: Test) {
125+
await expectError(test,
126+
/string \| number is not assignable to string/,
127+
`
128+
export interface Henk {
129+
readonly henk: string | number;
130+
}
131+
export class Actions {
132+
takeHenk(_henk: Henk): void { }
133+
}
134+
`, `
135+
export interface Henk {
136+
readonly henk: string;
137+
}
138+
export class Actions {
139+
takeHenk(_henk: Henk): void { }
140+
}
141+
`);
142+
143+
test.done();
144+
},
145+
};

0 commit comments

Comments
 (0)