Skip to content

Commit a25d06b

Browse files
committed
feat(types): Allow assign union type to non union type when all types are assignable, also check assignability in reverse for function parameters
1 parent 43ae479 commit a25d06b

21 files changed

Lines changed: 1081 additions & 29 deletions

‎packages/tal-eval/src/typecheck/TypeChecker.ts‎

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1204,16 +1204,25 @@ function typeIsAssignableTo(
12041204
};
12051205
}
12061206

1207-
// A union type is not assignable to a non union type
1207+
// A union type is assignable to a non union type if all the members of the union are assignable
12081208
if (isUnion(type2)) {
1209-
return {
1210-
result: false,
1211-
reasons: [
1212-
`Type ${typeToString(type2)} cannot be assigned to ${typeToString(
1213-
type1
1214-
)}`,
1215-
],
1216-
};
1209+
for (let type2Item of type2.types) {
1210+
const result = typeIsAssignableTo(type1, type2Item, symbolTable);
1211+
if (!result.result) {
1212+
return {
1213+
result: false,
1214+
reasons: [
1215+
`Type ${typeToString(
1216+
type2Item
1217+
)} cannot be assigned to ${typeToString(
1218+
type1
1219+
)}: ${result.reasons.join(', ')}`,
1220+
],
1221+
};
1222+
}
1223+
}
1224+
1225+
return AssignableResult_TRUE;
12171226
}
12181227

12191228
if (type1.kind == 'dict') {
@@ -1409,8 +1418,8 @@ function typeIsAssignableTo(
14091418
};
14101419
}
14111420
const result = typeIsAssignableTo(
1412-
type1ParamsByName[param.name] ?? typeNull(),
14131421
param.type,
1422+
type1ParamsByName[param.name] ?? typeNull(),
14141423
symbolTable
14151424
);
14161425
if (!result.result) {
@@ -1426,9 +1435,10 @@ function typeIsAssignableTo(
14261435
};
14271436
}
14281437
}
1438+
1439+
return AssignableResult_TRUE;
14291440
}
14301441

1431-
// For non union types other than any, their kinds must be the same
14321442
return type1.kind == type2.kind
14331443
? AssignableResult_TRUE
14341444
: {
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
main()
2+
entry:
3+
Pop inBlock: false
4+
Literal "name"
5+
Literal "toto"
6+
Literal "age"
7+
Literal 32
8+
Literal 2
9+
MakeRecord
10+
DeclareLocal name: a1, mutable: false, hasInitialValue: true
11+
Pop inBlock: false
12+
Literal "name"
13+
Literal "toto"
14+
Literal 1
15+
MakeRecord
16+
DeclareLocal name: a2, mutable: false, hasInitialValue: true
17+
Pop inBlock: false
18+
FunctionRef name: a3_0
19+
DeclareLocal name: a3, mutable: true, hasInitialValue: true
20+
Pop inBlock: false
21+
FunctionRef name: a4_1
22+
DeclareLocal name: a4, mutable: true, hasInitialValue: true
23+
Pop inBlock: false
24+
FunctionRef name: a5_2
25+
DeclareLocal name: a5, mutable: true, hasInitialValue: true
26+
a3_0(row)
27+
entry:
28+
Literal null
29+
a4_1(row)
30+
entry:
31+
Literal null
32+
a5_2(row)
33+
entry:
34+
Literal null
Lines changed: 248 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,248 @@
1+
[
2+
{
3+
"kind": "TypeAlias",
4+
"name": "R",
5+
"type": {
6+
"kind": "record",
7+
"fields": {
8+
"name": {
9+
"kind": "named",
10+
"name": "string"
11+
},
12+
"age": {
13+
"kind": "named",
14+
"name": "number"
15+
}
16+
}
17+
}
18+
},
19+
{
20+
"kind": "Comment",
21+
"text": " OK\r",
22+
"node": {
23+
"kind": "DeclareLocal",
24+
"mutable": false,
25+
"name": "a1",
26+
"type": {
27+
"kind": "named",
28+
"name": "R"
29+
},
30+
"value": {
31+
"kind": "Record",
32+
"entries": [
33+
{
34+
"kind": "RecordEntry",
35+
"key": "name",
36+
"value": "toto"
37+
},
38+
{
39+
"kind": "RecordEntry",
40+
"key": "age",
41+
"value": 32
42+
}
43+
]
44+
}
45+
}
46+
},
47+
{
48+
"kind": "Comment",
49+
"text": " KO, missing field age\r",
50+
"node": {
51+
"kind": "DeclareLocal",
52+
"mutable": false,
53+
"name": "a2",
54+
"type": {
55+
"kind": "named",
56+
"name": "R"
57+
},
58+
"value": {
59+
"kind": "Record",
60+
"entries": [
61+
{
62+
"kind": "RecordEntry",
63+
"key": "name",
64+
"value": "toto"
65+
}
66+
]
67+
}
68+
}
69+
},
70+
{
71+
"kind": "Comment",
72+
"text": " OK\r",
73+
"node": {
74+
"kind": "DeclareLocal",
75+
"mutable": true,
76+
"name": "a3",
77+
"type": {
78+
"kind": "function",
79+
"parameters": [
80+
{
81+
"name": {
82+
"kind": "Identifier",
83+
"name": "row"
84+
},
85+
"type": {
86+
"kind": "record",
87+
"fields": {
88+
"name": {
89+
"kind": "named",
90+
"name": "string"
91+
},
92+
"age": {
93+
"kind": "named",
94+
"name": "number"
95+
}
96+
}
97+
}
98+
}
99+
],
100+
"returnType": {
101+
"kind": "named",
102+
"name": "null"
103+
}
104+
},
105+
"value": {
106+
"kind": "Function",
107+
"body": null,
108+
"parameters": [
109+
{
110+
"name": {
111+
"kind": "Identifier",
112+
"name": "row"
113+
},
114+
"type": {
115+
"kind": "record",
116+
"fields": {
117+
"name": {
118+
"kind": "named",
119+
"name": "string"
120+
},
121+
"age": {
122+
"kind": "named",
123+
"name": "number"
124+
}
125+
}
126+
}
127+
}
128+
]
129+
}
130+
}
131+
},
132+
{
133+
"kind": "Comment",
134+
"text": " OK\r",
135+
"node": {
136+
"kind": "DeclareLocal",
137+
"mutable": true,
138+
"name": "a4",
139+
"type": {
140+
"kind": "function",
141+
"parameters": [
142+
{
143+
"name": {
144+
"kind": "Identifier",
145+
"name": "row"
146+
},
147+
"type": {
148+
"kind": "record",
149+
"fields": {
150+
"name": {
151+
"kind": "named",
152+
"name": "string"
153+
},
154+
"age": {
155+
"kind": "named",
156+
"name": "number"
157+
}
158+
}
159+
}
160+
}
161+
],
162+
"returnType": {
163+
"kind": "named",
164+
"name": "null"
165+
}
166+
},
167+
"value": {
168+
"kind": "Function",
169+
"body": null,
170+
"parameters": [
171+
{
172+
"name": {
173+
"kind": "Identifier",
174+
"name": "row"
175+
},
176+
"type": {
177+
"kind": "record",
178+
"fields": {
179+
"name": {
180+
"kind": "named",
181+
"name": "string"
182+
}
183+
}
184+
}
185+
}
186+
]
187+
}
188+
}
189+
},
190+
{
191+
"kind": "Comment",
192+
"text": " KO, missing field age in parameter\r",
193+
"node": {
194+
"kind": "DeclareLocal",
195+
"mutable": true,
196+
"name": "a5",
197+
"type": {
198+
"kind": "function",
199+
"parameters": [
200+
{
201+
"name": {
202+
"kind": "Identifier",
203+
"name": "row"
204+
},
205+
"type": {
206+
"kind": "record",
207+
"fields": {
208+
"name": {
209+
"kind": "named",
210+
"name": "string"
211+
}
212+
}
213+
}
214+
}
215+
],
216+
"returnType": {
217+
"kind": "named",
218+
"name": "null"
219+
}
220+
},
221+
"value": {
222+
"kind": "Function",
223+
"body": null,
224+
"parameters": [
225+
{
226+
"name": {
227+
"kind": "Identifier",
228+
"name": "row"
229+
},
230+
"type": {
231+
"kind": "record",
232+
"fields": {
233+
"name": {
234+
"kind": "named",
235+
"name": "string"
236+
},
237+
"age": {
238+
"kind": "named",
239+
"name": "number"
240+
}
241+
}
242+
}
243+
}
244+
]
245+
}
246+
}
247+
}
248+
]
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
type R = {name: string, age: number}
2+
3+
// OK
4+
let a1: R = {name: "toto", age: 32}
5+
6+
// KO, missing field age
7+
let a2: R = {name: "toto"}
8+
9+
// OK
10+
fun a3(row: {name: string, age: number}) null
11+
12+
// OK
13+
fun a4(row: {name: string}) null
14+
15+
// KO, missing field age in parameter
16+
fun a5(row: {name: string, age: number}) null
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
(10:13) Incompatible initial value: Non nullable field age is missing
2+
(21:41) Incompatible initial value: Parameter row from (row: {name: string, age: number}) => null is not assignable to {name: string, age: number}: Non nullable field age is missing
3+
null

0 commit comments

Comments
 (0)