Skip to content

Commit 9cdf707

Browse files
authored
Add record type to typechecker (#50)
This PR parses record types and adds limited support for them in the typechecker: - Record literals are given a type now - `least_common_type` works on record types so you can append two lists of records or something - `is_type_compatible` works on record types so you can assign records to variables - I had to make `is_type_compatible` a method of `Typechecker`, since the typechecker holds information about each record type's fields - That's about it The way record types are stored brings me some sadness. The field types are stored as `Vec<(NodeId, TypeId)>`, sorted by the field name. I didn't use a hashmap, because fields need to be searched by their text, and using `NodeId`s as the keys would not have been very useful. Storing them in sorted order does, however, help a bit with iteration. Side note: it looks like `OneOf` types are stored in a hashset of type IDs, but this may not be super useful. `typecheck_match()` finds the intersection of these hashsets without looking at the underlying types.
1 parent 4543afb commit 9cdf707

12 files changed

+310
-73
lines changed

src/parser.rs

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ pub enum ParamsContext {
4242
Squares,
4343
/// Params for a closure
4444
Pipes,
45+
/// Fields for a record
46+
Angles,
4547
}
4648

4749
#[derive(Debug)]
@@ -65,6 +67,11 @@ pub enum AstNode {
6567
optional: bool,
6668
},
6769
TypeArgs(Vec<NodeId>),
70+
RecordType {
71+
/// Contains [AstNode::Params]
72+
fields: NodeId,
73+
optional: bool,
74+
},
6875
Variable,
6976

7077
// Booleans
@@ -860,6 +867,7 @@ impl Parser {
860867
match params_context {
861868
ParamsContext::Pipes => self.pipe(),
862869
ParamsContext::Squares => self.lsquare(),
870+
ParamsContext::Angles => self.less_than(),
863871
}
864872

865873
let mut output = vec![];
@@ -876,6 +884,11 @@ impl Parser {
876884
break;
877885
}
878886
}
887+
ParamsContext::Angles => {
888+
if self.is_greater_than() {
889+
break;
890+
}
891+
}
879892
}
880893

881894
if self.is_comma() {
@@ -913,6 +926,7 @@ impl Parser {
913926
match params_context {
914927
ParamsContext::Pipes => self.pipe(),
915928
ParamsContext::Squares => self.rsquare(),
929+
ParamsContext::Angles => self.greater_than(),
916930
}
917931

918932
output
@@ -956,6 +970,25 @@ impl Parser {
956970
let _span = span!();
957971
if let (Token::Bareword, span) = self.tokens.peek() {
958972
let name = self.name();
973+
let name_text = self.compiler.get_span_contents(name);
974+
975+
if name_text == b"record" {
976+
let fields = self.signature_params(ParamsContext::Angles);
977+
let optional = if self.is_question_mark() {
978+
// We have an optional type
979+
self.tokens.advance();
980+
true
981+
} else {
982+
false
983+
};
984+
let span_end = self.position();
985+
return self.create_node(
986+
AstNode::RecordType { fields, optional },
987+
span.start,
988+
span_end,
989+
);
990+
}
991+
959992
let mut args = None;
960993
if self.is_less_than() {
961994
// We have generics
@@ -969,15 +1002,14 @@ impl Parser {
9691002
} else {
9701003
false
9711004
};
972-
9731005
self.create_node(
9741006
AstNode::Type {
9751007
name,
9761008
args,
9771009
optional,
9781010
},
9791011
span.start,
980-
span.end,
1012+
span.end, // FIXME: this uses the end of the name as its end
9811013
)
9821014
} else {
9831015
self.error("expect name")

src/snapshots/new_nu_parser__test__node_output@binary_ops_subtypes.nu.snap

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,25 @@ snapshot_kind: text
5858
50: Modulo (112 to 115)
5959
51: Int (116 to 117) "1"
6060
52: BinaryOp { lhs: NodeId(49), op: NodeId(50), rhs: NodeId(51) } (108 to 117)
61-
53: Block(BlockId(0)) (0 to 118)
61+
53: String (120 to 121) "b"
62+
54: Int (123 to 124) "2"
63+
55: String (126 to 127) "c"
64+
56: Int (129 to 130) "3"
65+
57: Record { pairs: [(NodeId(53), NodeId(54)), (NodeId(55), NodeId(56))] } (119 to 131)
66+
58: List([NodeId(57)]) (118 to 131)
67+
59: Append (133 to 135)
68+
60: String (138 to 139) "a"
69+
61: Int (141 to 142) "3"
70+
62: String (144 to 145) "b"
71+
63: Float (147 to 150) "1.5"
72+
64: String (152 to 153) "c"
73+
65: String (155 to 160) ""foo""
74+
66: Record { pairs: [(NodeId(60), NodeId(61)), (NodeId(62), NodeId(63)), (NodeId(64), NodeId(65))] } (137 to 161)
75+
67: List([NodeId(66)]) (136 to 161)
76+
68: BinaryOp { lhs: NodeId(58), op: NodeId(59), rhs: NodeId(67) } (118 to 161)
77+
69: Block(BlockId(0)) (0 to 163)
6278
==== SCOPE ====
63-
0: Frame Scope, node_id: NodeId(53) (empty)
79+
0: Frame Scope, node_id: NodeId(69) (empty)
6480
==== TYPES ====
6581
0: int
6682
1: forbidden
@@ -115,7 +131,23 @@ snapshot_kind: text
115131
50: forbidden
116132
51: int
117133
52: float
118-
53: float
134+
53: unknown
135+
54: int
136+
55: unknown
137+
56: int
138+
57: record<b: int, c: int>
139+
58: list<record<b: int, c: int>>
140+
59: forbidden
141+
60: unknown
142+
61: int
143+
62: unknown
144+
63: float
145+
64: unknown
146+
65: string
147+
66: record<a: int, b: float, c: string>
148+
67: list<record<a: int, b: float, c: string>>
149+
68: list<record<b: number, c: any>>
150+
69: list<record<b: number, c: any>>
119151
==== IR ====
120152
register_count: 1
121153
file_count: 0

src/snapshots/[email protected]

Lines changed: 46 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,13 @@ input_file: tests/def.nu
55
---
66
==== COMPILER ====
77
0: Name (4 to 7) "foo"
8-
1: Name (9 to 10) "x"
8+
1: Name (9 to 10) "w"
99
2: Param { name: NodeId(1), ty: None } (9 to 10)
10-
3: Name (11 to 12) "y"
10+
3: Name (11 to 12) "x"
1111
4: Name (14 to 17) "int"
1212
5: Type { name: NodeId(4), args: None, optional: false } (14 to 17)
1313
6: Param { name: NodeId(3), ty: Some(NodeId(5)) } (11 to 17)
14-
7: Name (19 to 20) "z"
14+
7: Name (19 to 20) "y"
1515
8: Name (22 to 26) "list"
1616
9: Name (27 to 31) "list"
1717
10: Name (32 to 35) "int"
@@ -21,19 +21,31 @@ input_file: tests/def.nu
2121
14: TypeArgs([NodeId(13)]) (26 to 37)
2222
15: Type { name: NodeId(8), args: Some(NodeId(14)), optional: false } (22 to 26)
2323
16: Param { name: NodeId(7), ty: Some(NodeId(15)) } (19 to 26)
24-
17: Params([NodeId(2), NodeId(6), NodeId(16)]) (8 to 39)
25-
18: Variable (44 to 46) "$x"
26-
19: Variable (47 to 49) "$y"
27-
20: Variable (51 to 53) "$z"
28-
21: List([NodeId(18), NodeId(19), NodeId(20)]) (42 to 54)
29-
22: Block(BlockId(0)) (40 to 57)
30-
23: Def { name: NodeId(0), params: NodeId(17), in_out_types: None, block: NodeId(22) } (0 to 57)
31-
24: Block(BlockId(1)) (0 to 57)
24+
17: Name (39 to 40) "z"
25+
18: Name (42 to 48) "record"
26+
19: Name (49 to 50) "a"
27+
20: Param { name: NodeId(19), ty: None } (49 to 50)
28+
21: Name (52 to 53) "b"
29+
22: Name (55 to 58) "int"
30+
23: Type { name: NodeId(22), args: None, optional: false } (55 to 58)
31+
24: Param { name: NodeId(21), ty: Some(NodeId(23)) } (52 to 58)
32+
25: Params([NodeId(20), NodeId(24)]) (48 to 59)
33+
26: RecordType { fields: NodeId(25), optional: false } (42 to 60)
34+
27: Param { name: NodeId(17), ty: Some(NodeId(26)) } (39 to 60)
35+
28: Params([NodeId(2), NodeId(6), NodeId(16), NodeId(27)]) (8 to 61)
36+
29: Variable (66 to 68) "$w"
37+
30: Variable (69 to 71) "$x"
38+
31: Variable (73 to 75) "$y"
39+
32: Variable (77 to 79) "$z"
40+
33: List([NodeId(29), NodeId(30), NodeId(31), NodeId(32)]) (64 to 80)
41+
34: Block(BlockId(0)) (62 to 83)
42+
35: Def { name: NodeId(0), params: NodeId(28), in_out_types: None, block: NodeId(34) } (0 to 83)
43+
36: Block(BlockId(1)) (0 to 83)
3244
==== SCOPE ====
33-
0: Frame Scope, node_id: NodeId(24)
45+
0: Frame Scope, node_id: NodeId(36)
3446
decls: [ foo: NodeId(0) ]
35-
1: Frame Scope, node_id: NodeId(22)
36-
variables: [ x: NodeId(1), y: NodeId(3), z: NodeId(7) ]
47+
1: Frame Scope, node_id: NodeId(34)
48+
variables: [ w: NodeId(1), x: NodeId(3), y: NodeId(7), z: NodeId(17) ]
3749
==== TYPES ====
3850
0: unknown
3951
1: unknown
@@ -52,16 +64,28 @@ input_file: tests/def.nu
5264
14: forbidden
5365
15: list<list<int>>
5466
16: list<list<int>>
55-
17: forbidden
67+
17: unknown
5668
18: unknown
57-
19: int
58-
20: list<list<int>>
59-
21: list<any>
60-
22: list<any>
61-
23: ()
62-
24: ()
69+
19: unknown
70+
20: unknown
71+
21: unknown
72+
22: unknown
73+
23: int
74+
24: unknown
75+
25: unknown
76+
26: record<a: any, b: int>
77+
27: record<a: any, b: int>
78+
28: forbidden
79+
29: unknown
80+
30: int
81+
31: list<list<int>>
82+
32: record<a: any, b: int>
83+
33: list<any>
84+
34: list<any>
85+
35: ()
86+
36: ()
6387
==== IR ====
6488
register_count: 0
6589
file_count: 0
6690
==== IR ERRORS ====
67-
Error (NodeId 23): node Def { name: NodeId(0), params: NodeId(17), in_out_types: None, block: NodeId(22) } not suported yet
91+
Error (NodeId 35): node Def { name: NodeId(0), params: NodeId(28), in_out_types: None, block: NodeId(34) } not suported yet

src/snapshots/new_nu_parser__test__node_output@let_mismatch.nu.snap

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,22 @@ input_file: tests/let_mismatch.nu
3232
25: List([NodeId(24)]) (114 to 120)
3333
26: List([NodeId(25)]) (112 to 122)
3434
27: Let { variable_name: NodeId(15), ty: Some(NodeId(23)), initializer: NodeId(26), is_mutable: false } (87 to 122)
35-
28: Block(BlockId(0)) (0 to 124)
35+
28: Variable (128 to 129) "v"
36+
29: Name (131 to 137) "record"
37+
30: Name (138 to 139) "a"
38+
31: Name (141 to 144) "int"
39+
32: Type { name: NodeId(31), args: None, optional: false } (141 to 144)
40+
33: Param { name: NodeId(30), ty: Some(NodeId(32)) } (138 to 144)
41+
34: Params([NodeId(33)]) (137 to 145)
42+
35: RecordType { fields: NodeId(34), optional: false } (131 to 146)
43+
36: String (149 to 150) "a"
44+
37: String (152 to 157) ""foo""
45+
38: Record { pairs: [(NodeId(36), NodeId(37))] } (148 to 158)
46+
39: Let { variable_name: NodeId(28), ty: Some(NodeId(35)), initializer: NodeId(38), is_mutable: false } (124 to 158)
47+
40: Block(BlockId(0)) (0 to 159)
3648
==== SCOPE ====
37-
0: Frame Scope, node_id: NodeId(28)
38-
variables: [ w: NodeId(15), x: NodeId(0), y: NodeId(5), z: NodeId(10) ]
49+
0: Frame Scope, node_id: NodeId(40)
50+
variables: [ v: NodeId(28), w: NodeId(15), x: NodeId(0), y: NodeId(5), z: NodeId(10) ]
3951
==== TYPES ====
4052
0: number
4153
1: unknown
@@ -65,10 +77,23 @@ input_file: tests/let_mismatch.nu
6577
25: list<string>
6678
26: list<list<string>>
6779
27: ()
68-
28: ()
80+
28: record<a: int>
81+
29: unknown
82+
30: unknown
83+
31: unknown
84+
32: int
85+
33: unknown
86+
34: unknown
87+
35: record<a: int>
88+
36: unknown
89+
37: string
90+
38: record<a: string>
91+
39: ()
92+
40: ()
6993
==== TYPE ERRORS ====
7094
Error (NodeId 13): initializer does not match declared type
7195
Error (NodeId 26): initializer does not match declared type
96+
Error (NodeId 38): initializer does not match declared type
7297
==== IR ====
7398
register_count: 0
7499
file_count: 0

src/snapshots/[email protected]

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,11 @@ snapshot_kind: text
1515
0: Frame Scope, node_id: NodeId(5) (empty)
1616
==== TYPES ====
1717
0: unknown
18-
1: unknown
18+
1: int
1919
2: unknown
20-
3: unknown
21-
4: unknown
22-
5: unknown
23-
==== TYPE ERRORS ====
24-
Error (NodeId 4): unsupported ast node 'Record { pairs: [(NodeId(0), NodeId(1)), (NodeId(2), NodeId(3))] }' in typechecker
20+
3: int
21+
4: record<a: int, b: int>
22+
5: record<a: int, b: int>
2523
==== IR ====
2624
register_count: 0
2725
file_count: 0

src/snapshots/[email protected]

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,11 @@ snapshot_kind: text
1515
0: Frame Scope, node_id: NodeId(5) (empty)
1616
==== TYPES ====
1717
0: unknown
18-
1: unknown
18+
1: int
1919
2: unknown
20-
3: unknown
21-
4: unknown
22-
5: unknown
23-
==== TYPE ERRORS ====
24-
Error (NodeId 4): unsupported ast node 'Record { pairs: [(NodeId(0), NodeId(1)), (NodeId(2), NodeId(3))] }' in typechecker
20+
3: int
21+
4: record<"a": int, "b": int>
22+
5: record<"a": int, "b": int>
2523
==== IR ====
2624
register_count: 0
2725
file_count: 0

src/snapshots/[email protected]

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,11 @@ snapshot_kind: text
1515
0: Frame Scope, node_id: NodeId(5) (empty)
1616
==== TYPES ====
1717
0: unknown
18-
1: unknown
18+
1: int
1919
2: unknown
20-
3: unknown
21-
4: unknown
22-
5: unknown
23-
==== TYPE ERRORS ====
24-
Error (NodeId 4): unsupported ast node 'Record { pairs: [(NodeId(0), NodeId(1)), (NodeId(2), NodeId(3))] }' in typechecker
20+
3: int
21+
4: record<a: int, b: int>
22+
5: record<a: int, b: int>
2523
==== IR ====
2624
register_count: 0
2725
file_count: 0

src/snapshots/[email protected]

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,12 @@ snapshot_kind: text
3333
5: unknown
3434
6: closure
3535
7: ()
36-
8: unknown
36+
8: record<a: string>
3737
9: unknown
38-
10: unknown
39-
11: unknown
38+
10: string
39+
11: record<a: string>
4040
12: ()
4141
13: ()
42-
==== TYPE ERRORS ====
43-
Error (NodeId 11): unsupported ast node 'Record { pairs: [(NodeId(9), NodeId(10))] }' in typechecker
4442
==== IR ====
4543
register_count: 0
4644
file_count: 0

0 commit comments

Comments
 (0)