Skip to content

Commit baf8f57

Browse files
FnControlOptionTechatrix
authored andcommitted
Resolve type of loop expressions (#1310)
Also improve block type resolution
1 parent cfdb9f2 commit baf8f57

File tree

1 file changed

+105
-15
lines changed

1 file changed

+105
-15
lines changed

src/analysis.zig

Lines changed: 105 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -722,6 +722,56 @@ pub fn isTypeIdent(text: []const u8) bool {
722722
return true;
723723
}
724724

725+
const FindBreaks = struct {
726+
const Error = error{OutOfMemory};
727+
728+
label: ?[]const u8,
729+
allow_unlabeled: bool,
730+
allocator: std.mem.Allocator,
731+
break_operands: std.ArrayListUnmanaged(Ast.Node.Index) = .{},
732+
733+
fn deinit(context: *FindBreaks) void {
734+
context.break_operands.deinit(context.allocator);
735+
}
736+
737+
fn findBreakOperands(context: *FindBreaks, tree: Ast, node: Ast.Node.Index) Error!void {
738+
if (node == 0)
739+
return;
740+
741+
const allow_unlabeled = context.allow_unlabeled;
742+
const node_tags = tree.nodes.items(.tag);
743+
const datas = tree.nodes.items(.data);
744+
745+
switch (node_tags[node]) {
746+
.@"break" => {
747+
const label_token = datas[node].lhs;
748+
const operand = datas[node].rhs;
749+
if (allow_unlabeled and label_token == 0) {
750+
try context.break_operands.append(context.allocator, operand);
751+
} else if (context.label) |label| {
752+
if (label_token != 0 and std.mem.eql(u8, label, tree.tokenSlice(label_token)))
753+
try context.break_operands.append(context.allocator, operand);
754+
}
755+
},
756+
757+
.@"while",
758+
.while_simple,
759+
.while_cont,
760+
.@"for",
761+
.for_simple,
762+
=> {
763+
context.allow_unlabeled = false;
764+
try ast.iterateChildren(tree, node, context, Error, findBreakOperands);
765+
context.allow_unlabeled = allow_unlabeled;
766+
},
767+
768+
else => {
769+
try ast.iterateChildren(tree, node, context, Error, findBreakOperands);
770+
},
771+
}
772+
}
773+
};
774+
725775
/// Resolves the type of a node
726776
fn resolveTypeOfNodeInternal(analyser: *Analyser, node_handle: NodeWithHandle) error{OutOfMemory}!?TypeWithHandle {
727777
const node_with_uri = NodeWithUri{
@@ -1187,6 +1237,50 @@ fn resolveTypeOfNodeUncached(analyser: *Analyser, node_handle: NodeWithHandle) e
11871237

11881238
return TypeWithHandle.fromEither(analyser.gpa, try either.toOwnedSlice(analyser.arena.allocator()), handle);
11891239
},
1240+
.@"while",
1241+
.while_simple,
1242+
.while_cont,
1243+
.@"for",
1244+
.for_simple,
1245+
=> {
1246+
const loop: struct {
1247+
label_token: ?Ast.TokenIndex,
1248+
then_expr: Ast.Node.Index,
1249+
else_expr: Ast.Node.Index,
1250+
} = if (tree.fullWhile(node)) |while_node|
1251+
.{
1252+
.label_token = while_node.label_token,
1253+
.then_expr = while_node.ast.then_expr,
1254+
.else_expr = while_node.ast.else_expr,
1255+
}
1256+
else if (tree.fullFor(node)) |for_node|
1257+
.{
1258+
.label_token = for_node.label_token,
1259+
.then_expr = for_node.ast.then_expr,
1260+
.else_expr = for_node.ast.else_expr,
1261+
}
1262+
else
1263+
unreachable;
1264+
1265+
if (loop.else_expr == 0)
1266+
return null;
1267+
1268+
// TODO: peer type resolution based on `else` and all `break` statements
1269+
if (try analyser.resolveTypeOfNodeInternal(.{ .node = loop.else_expr, .handle = handle })) |else_type|
1270+
return else_type;
1271+
1272+
var context = FindBreaks{
1273+
.label = if (loop.label_token) |token| tree.tokenSlice(token) else null,
1274+
.allow_unlabeled = true,
1275+
.allocator = analyser.gpa,
1276+
};
1277+
defer context.deinit();
1278+
try context.findBreakOperands(tree, loop.then_expr);
1279+
for (context.break_operands.items) |operand| {
1280+
if (try analyser.resolveTypeOfNodeInternal(.{ .node = operand, .handle = handle })) |operand_type|
1281+
return operand_type;
1282+
}
1283+
},
11901284
.block,
11911285
.block_semicolon,
11921286
.block_two,
@@ -1197,21 +1291,17 @@ fn resolveTypeOfNodeUncached(analyser: *Analyser, node_handle: NodeWithHandle) e
11971291

11981292
const block_label = tree.tokenSlice(first_token);
11991293

1200-
var buffer: [2]Ast.Node.Index = undefined;
1201-
const statements = ast.blockStatements(tree, node, &buffer).?;
1202-
1203-
for (statements) |child_idx| {
1204-
// TODO: Recursively find matching `break :label` (e.g. inside `if`)
1205-
if (node_tags[child_idx] == .@"break") {
1206-
if (datas[child_idx].lhs == 0) continue;
1207-
if (datas[child_idx].rhs == 0) continue;
1208-
1209-
const break_label = tree.tokenSlice(datas[child_idx].lhs);
1210-
if (!std.mem.eql(u8, block_label, break_label)) continue;
1211-
1212-
const operand = .{ .node = datas[child_idx].rhs, .handle = handle };
1213-
return try analyser.resolveTypeOfNodeInternal(operand);
1214-
}
1294+
// TODO: peer type resolution based on all `break` statements
1295+
var context = FindBreaks{
1296+
.label = block_label,
1297+
.allow_unlabeled = false,
1298+
.allocator = analyser.gpa,
1299+
};
1300+
defer context.deinit();
1301+
try context.findBreakOperands(tree, node);
1302+
for (context.break_operands.items) |operand| {
1303+
if (try analyser.resolveTypeOfNodeInternal(.{ .node = operand, .handle = handle })) |operand_type|
1304+
return operand_type;
12151305
}
12161306
},
12171307
else => {},

0 commit comments

Comments
 (0)