ReviewOS

also looking at this

home-lang/home

fix(types): define the index variable of an enumerating for loop

#935
Open mkantautas wants to merge fix/types-for-index-variable into main
2 files +57 -0
packages/types/src/type_system.zigmodified+10-0
Changes to packages/types/src/type_system.zig
@@ -2079,6 +2079,16 @@ pub const TypeChecker = struct {
20792079 iterable_type.Array.element_type.*
20802080 else
20812081 Type.Int;
2082 // `for i, item in items` also binds `i`: for an array, string
2083 // or integer iterable it is the position, an int, defined
2084 // before the item as the interpreter does. A map binds its
2085 // value there, and the checker cannot type that yet, so other
2086 // iterables are left as they were.
2087 if (for_stmt.index) |index_name| {
2088 if (iterable_type == .Array or iterable_type == .String or isIntegerType(iterable_type)) {
2089 try loop_env.define(index_name, Type.Int);
2090 }
2091 }
20822092 try loop_env.define(for_stmt.iterator, iterator_type);
20832093
20842094 // Check body with loop scope
packages/types/tests/control_flow_checker_test.zigmodified+47-0
Changes to packages/types/tests/control_flow_checker_test.zig
@@ -130,6 +130,53 @@ test "checker accepts a correctly typed function return" {
130130 ));
131131}
132132
133fn checkSourceInArena(source: []const u8) !bool {
134 // Arena: the checker does not free for-loop scopes yet.
135 var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
136 defer arena.deinit();
137 const allocator = arena.allocator();
138 var lexer = home.lexer.Lexer.init(allocator, source);
139 const tokens = try lexer.tokenize();
140 var parser = try home.parser.Parser.init(allocator, tokens.items);
141 const program = try parser.parse();
142 var checker = home.types.TypeChecker.init(allocator, program);
143 return checker.check();
144}
145
146test "checker defines the index variable of an enumerating for loop" {
147 try std.testing.expect(try checkSourceInArena(
148 \\fn run() {
149 \\ for i, x in [10, 20] {
150 \\ let next = i + 1
151 \\ }
152 \\}
153 ));
154 // For a map the first name is bound to the value, not an int position.
155 try std.testing.expect(!try checkSourceInArena(
156 \\fn run() {
157 \\ for value, key in {"k": true} {
158 \\ let out = value + 1
159 \\ }
160 \\}
161 ));
162 try std.testing.expect(!try checkSourceInArena(
163 \\fn run() {
164 \\ let data = {"k": true} as ?Map<string, bool>
165 \\ for value, key in data {
166 \\ let out = value + 1
167 \\ }
168 \\}
169 ));
170 // Same name twice: the item is bound last, as at run time.
171 try std.testing.expect(try checkSourceInArena(
172 \\fn run() {
173 \\ for x, x in ["hello"] {
174 \\ let out = x + "!"
175 \\ }
176 \\}
177 ));
178}
179
133180test "checker recognizes never as the bottom type" {
134181 try std.testing.expect(try checkSource(
135182 \\fn halt(): never {