Skip to content

Commit ddc0292

Browse files
committedJan 24, 2024
Check unassignable types and properties
1 parent a3cbd8d commit ddc0292

File tree

4 files changed

+160
-10
lines changed

4 files changed

+160
-10
lines changed
 

‎draft/object-types.md

+6-3
Original file line numberDiff line numberDiff line change
@@ -569,15 +569,18 @@ Two way type checking:
569569

570570
Check phases
571571

572-
1. Any property type mismatch should generate this error
572+
1. Any property type mismatch should generate this error. If they don't share the same type. e.g. `number` and `object`
573573

574-
- If they don't share the same type. e.g. `number` and `object`
574+
- [x] Generate error
575575

576576
```
577577
Type {X} is not assignable to {Y}
578578
```
579579

580-
2. Go through the list of properties. For each one, check if it's defined in the type definition. If it's not, it should generate the type error
580+
2. Go through the list of properties. For each one, check if it's defined in the type definition. If it's not, it should generate the type error. This error only appears if rule number one passes the checker.
581+
582+
- [x] Generate error
583+
- [ ] Generate error only if there're no rule number one errors
581584

582585
```
583586
Object literal may only specify known properties, and 'g' does not exist in type 'D'.

‎src/check.ts

+87-1
Original file line numberDiff line numberDiff line change
@@ -138,17 +138,61 @@ export function check(module: Module) {
138138

139139
const type = checkType(declaration.typename);
140140

141-
if (type !== initType && type !== errorType)
141+
handleUnassignableTypes(declaration, initType, type);
142+
143+
if (initType.id === 'object') {
144+
// Handle property type mismatch and only known property errors
145+
handlePropertyTypeMismatch(declaration, initType, type);
146+
// Handle missing properties error
147+
148+
return type;
149+
}
150+
151+
if (type !== initType && type !== errorType) {
142152
error(
143153
declaration.init.pos,
144154
`Cannot assign initialiser of type '${typeToString(
145155
initType,
146156
)}' to variable with declared type '${typeToString(type)}'.`,
147157
);
158+
}
148159

149160
return type;
150161
}
151162

163+
function handlePropertyTypeMismatch(
164+
declaration: VariableDeclaration,
165+
initType: Type,
166+
type: Type,
167+
) {
168+
let hasUnassignablePropertyTypes = false;
169+
let undefinedPropertyName;
170+
171+
for (const [propertyName, propertyType] of initType.members as TypeTable) {
172+
const typePropertyType = type.members?.get(propertyName);
173+
174+
if (typePropertyType) {
175+
hasUnassignablePropertyTypes ||= handleUnassignablePropertyTypes(
176+
declaration,
177+
propertyType,
178+
typePropertyType,
179+
propertyName,
180+
);
181+
} else {
182+
undefinedPropertyName ||= propertyName;
183+
}
184+
}
185+
186+
if (!hasUnassignablePropertyTypes && undefinedPropertyName) {
187+
error(
188+
declaration.init.pos,
189+
`Object literal may only specify known properties, and '${undefinedPropertyName}' does not exist in type '${declaration.typename?.text}'.`,
190+
);
191+
}
192+
193+
return hasUnassignablePropertyTypes;
194+
}
195+
152196
function checkVariableDeclarationType(declaration: VariableDeclaration) {
153197
return declaration.typename
154198
? checkType(declaration.typename)
@@ -220,6 +264,48 @@ export function check(module: Module) {
220264
: checkExpression(statement.init);
221265
}
222266

267+
function handleUnassignableTypes(
268+
declaration: VariableDeclaration,
269+
initType: Type,
270+
type: Type,
271+
) {
272+
if (initType.id !== type.id) {
273+
error(
274+
declaration.init.pos,
275+
`Type '${typeToString(
276+
initType,
277+
)}' is not assignable to type '${typeToString(type)}'.`,
278+
);
279+
}
280+
}
281+
282+
function handleUnassignablePropertyTypes(
283+
declaration: VariableDeclaration,
284+
initType: Type,
285+
type: Type,
286+
propertyName: string,
287+
): boolean {
288+
if (initType.id !== type.id) {
289+
error(
290+
declaration.init.pos,
291+
`Type '${typeToString(
292+
initType,
293+
)}' is not assignable to type '${typeToString(
294+
type,
295+
)}'. The expected type comes from property '${propertyName}' which is declared here on type '${
296+
declaration.typename?.text
297+
}'`,
298+
);
299+
return true;
300+
}
301+
302+
if (initType.id === type.id && initType.id === 'object') {
303+
return handlePropertyTypeMismatch(declaration, initType, type);
304+
}
305+
306+
return false;
307+
}
308+
223309
function handleSubsequentVariableDeclarationsTypes(
224310
declaration: VariableDeclaration,
225311
valueDeclarationType: Type,

‎tests/objectLiteral.ts

+8-6
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,18 @@ type D = {
1717
};
1818
};
1919

20+
var a: A = 'string';
21+
var b: B = 10;
2022
var c: C = {
21-
a: 'a',
22-
b: 1
23+
a: a,
24+
b: b
2325
};
2426

2527
var d: D = {
26-
a: 'a',
27-
b: 1,
28-
c: 'a',
29-
d: 1,
28+
a: a,
29+
b: b,
30+
c: a,
31+
d: b,
3032
e: c,
3133
f: {
3234
foo: 'string',

‎tests/unassignableObjectLiteral.ts

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
type A = string;
2+
type B = number;
3+
type C = {
4+
a: string;
5+
b: number;
6+
};
7+
8+
type D = {
9+
a: string;
10+
b: number;
11+
c: A;
12+
d: B;
13+
e: C;
14+
f: {
15+
foo: string;
16+
bar: number;
17+
};
18+
};
19+
20+
var a: D = 'string';
21+
var b: D = 1;
22+
var c: D = {
23+
a: 1
24+
}
25+
26+
var d: D = {
27+
f: {
28+
foo: 123
29+
}
30+
}
31+
32+
type E = {
33+
a: {
34+
foo: {
35+
bar: number
36+
}
37+
}
38+
}
39+
40+
var e: E = {
41+
a: {
42+
foo: {
43+
bar: '123'
44+
}
45+
}
46+
}
47+
48+
type F = {
49+
a: number
50+
}
51+
52+
var f: F = {
53+
b: 123
54+
}
55+
56+
var g: F = {
57+
a: '123',
58+
b: 123
59+
}

0 commit comments

Comments
 (0)
Please sign in to comment.