Skip to content
49 changes: 24 additions & 25 deletions src/analysis.zig
Original file line number Diff line number Diff line change
Expand Up @@ -5760,6 +5760,29 @@ pub fn resolveExpressionTypeFromAncestors(
}
},

.@"continue" => {
const opt_target, const opt_continue_expr = tree.nodeData(ancestors[0]).opt_token_and_opt_node;
const target = opt_target.unwrap() orelse return null;
const continue_expr = opt_continue_expr.unwrap() orelse return null;
if (node != continue_expr) return null;

const continue_label = tree.tokenSlice(target);

const ancestor_switch = for (ancestors[1..]) |ancestor| {
if (tree.fullSwitch(ancestor)) |switch_node| {
const switch_label_token = switch_node.label_token orelse continue;
const switch_label = tree.tokenSlice(switch_label_token);
if (std.mem.eql(u8, continue_label, switch_label)) {
break switch_node;
}
}
} else {
return null;
};

return try analyser.resolveTypeOfNode(.of(ancestor_switch.ast.condition, handle));
},

.@"break" => {
const opt_target, const opt_break_expr = tree.nodeData(ancestors[0]).opt_token_and_opt_node;
const break_expr = opt_break_expr.unwrap() orelse return null;
Expand All @@ -5770,31 +5793,7 @@ pub fn resolveExpressionTypeFromAncestors(
else
null;

const index = blk: for (1..ancestors.len) |index| {
if (ast.fullFor(tree, ancestors[index])) |for_node| {
const break_label = break_label_maybe orelse break :blk index;
const for_label = tree.tokenSlice(for_node.label_token orelse continue);
if (std.mem.eql(u8, break_label, for_label)) break :blk index;
} else if (ast.fullWhile(tree, ancestors[index])) |while_node| {
const break_label = break_label_maybe orelse break :blk index;
const while_label = tree.tokenSlice(while_node.label_token orelse continue);
if (std.mem.eql(u8, break_label, while_label)) break :blk index;
} else switch (tree.nodeTag(ancestors[index])) {
.block,
.block_semicolon,
.block_two,
.block_two_semicolon,
=> {
const break_label = break_label_maybe orelse continue;
const block_label_token = ast.blockLabel(tree, ancestors[index]) orelse continue;
const block_label = tree.tokenSlice(block_label_token);

if (std.mem.eql(u8, break_label, block_label)) break :blk index;
},

else => {},
}
} else return null;
const index = ast.indexOfBreakTarget(tree, ancestors, break_label_maybe) orelse return null;

return try analyser.resolveExpressionType(
handle,
Expand Down
36 changes: 36 additions & 0 deletions src/ast.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1772,3 +1772,39 @@ test smallestEnclosingSubrange {
children[result4.start .. result4.start + result4.len],
);
}

pub fn indexOfBreakTarget(
tree: Ast,
nodes: []const Ast.Node.Index,
break_label_maybe: ?[]const u8,
) ?usize {
for (nodes, 0..) |node, index| {
if (fullFor(tree, node)) |for_node| {
const break_label = break_label_maybe orelse return index;
const for_label = tree.tokenSlice(for_node.label_token orelse continue);
if (std.mem.eql(u8, break_label, for_label)) return index;
} else if (fullWhile(tree, node)) |while_node| {
const break_label = break_label_maybe orelse return index;
const while_label = tree.tokenSlice(while_node.label_token orelse continue);
if (std.mem.eql(u8, break_label, while_label)) return index;
} else if (tree.fullSwitch(node)) |switch_node| {
const break_label = break_label_maybe orelse continue;
const switch_label = tree.tokenSlice(switch_node.label_token orelse continue);
if (std.mem.eql(u8, break_label, switch_label)) return index;
} else switch (tree.nodeTag(node)) {
.block,
.block_semicolon,
.block_two,
.block_two_semicolon,
=> {
const break_label = break_label_maybe orelse continue;
const block_label_token = blockLabel(tree, node) orelse continue;
const block_label = tree.tokenSlice(block_label_token);

if (std.mem.eql(u8, break_label, block_label)) return index;
},
else => {},
}
}
return null;
}
71 changes: 70 additions & 1 deletion src/features/completions.zig
Original file line number Diff line number Diff line change
Expand Up @@ -666,7 +666,7 @@ fn completeDot(builder: *Builder, loc: offsets.Loc) error{OutOfMemory}!void {
if (dot_token_index < 2) return;

blk: {
const nodes = try ast.nodesOverlappingIndex(builder.arena, tree, loc.start);
const nodes = try ast.nodesOverlappingIndexIncludingParseErrors(builder.arena, tree, loc.start);
const dot_context = getEnumLiteralContext(tree, dot_token_index, nodes) orelse break :blk;
const used_members_set = try collectUsedMembersSet(builder, dot_context.likely, dot_token_index);
const containers = try collectContainerNodes(builder, builder.orig_handle, dot_context);
Expand Down Expand Up @@ -1043,6 +1043,10 @@ const EnumLiteralContext = struct {
enum_assignment,
/// `return .`
enum_return,
/// `break .` or `break :blk .`
enum_break,
/// `continue :blk .`
enum_continue,
// `==`, `!=`
enum_comparison,
/// the enum is a fn arg, eg `f(.`
Expand All @@ -1061,6 +1065,8 @@ const EnumLiteralContext = struct {
return switch (likely) {
.enum_assignment,
.enum_return,
.enum_break,
.enum_continue,
.enum_arg,
=> true,
else => false,
Expand Down Expand Up @@ -1101,6 +1107,34 @@ fn getEnumLiteralContext(
dot_context.type_info = .{ .expr_node_index = getReturnTypeNode(tree, nodes) orelse return null };
dot_context.likely = .enum_return;
},
.keyword_break => {
const i = ast.indexOfBreakTarget(tree, nodes, null) orelse return null;
dot_context = getEnumLiteralContext(tree, tree.firstToken(nodes[i]), nodes[i + 1 ..]) orelse return null;
dot_context.likely = .enum_break;
},
.identifier => {
if (tree.isTokenPrecededByTags(token_index, &.{ .keyword_break, .colon })) {
const break_label = tree.tokenSlice(token_index);
const i = ast.indexOfBreakTarget(tree, nodes, break_label) orelse return null;
dot_context = getEnumLiteralContext(tree, tree.firstToken(nodes[i]), nodes[i + 1 ..]) orelse return null;
dot_context.likely = .enum_break;
} else if (tree.isTokenPrecededByTags(token_index, &.{ .keyword_continue, .colon })) {
const continue_label = tree.tokenSlice(token_index);
const ancestor_switch = for (nodes) |node| {
if (tree.fullSwitch(node)) |switch_node| {
const switch_label_token = switch_node.label_token orelse continue;
const switch_label = tree.tokenSlice(switch_label_token);
if (std.mem.eql(u8, continue_label, switch_label)) {
break switch_node;
}
}
} else {
return null;
};
dot_context.type_info = .{ .expr_node_index = ancestor_switch.ast.condition };
dot_context.likely = .enum_continue;
}
},
.equal_equal, .bang_equal => {
token_index -= 1;
dot_context.likely = .enum_comparison;
Expand Down Expand Up @@ -1161,6 +1195,41 @@ fn getSwitchOrStructInitContext(
if (upper_index < 3) return null;
upper_index -= 1;
if (tree.tokenTag(upper_index) == .ampersand) upper_index -= 1; // `&.{.`
switch (tree.tokenTag(upper_index)) {
.keyword_break => { // `break .{.`
const i = ast.indexOfBreakTarget(tree, nodes, null) orelse return null;
upper_index = tree.firstToken(nodes[i]);
upper_index -= 1;
},
.identifier => {
if (tree.isTokenPrecededByTags(upper_index, &.{ .keyword_break, .colon })) { // `break :blk .{.`
const break_label = tree.tokenSlice(upper_index);
const i = ast.indexOfBreakTarget(tree, nodes, break_label) orelse return null;
upper_index = tree.firstToken(nodes[i]);
upper_index -= 1;
} else if (tree.isTokenPrecededByTags(upper_index, &.{ .keyword_continue, .colon })) { // `continue :blk .{.`
const continue_label = tree.tokenSlice(upper_index);
const ancestor_switch = for (nodes) |node| {
if (tree.fullSwitch(node)) |switch_node| {
const switch_label_token = switch_node.label_token orelse continue;
const switch_label = tree.tokenSlice(switch_label_token);
if (std.mem.eql(u8, continue_label, switch_label)) {
break switch_node;
}
}
} else {
return null;
};
return .{
.likely = likely,
.type_info = .{ .expr_node_index = ancestor_switch.ast.condition },
.fn_arg_index = fn_arg_index,
.need_ret_type = need_ret_type,
};
}
},
else => {},
}
if (tree.tokenTag(upper_index) == .equal) { // `= .{.`
upper_index -= 1; // eat the `=`
switch (tree.tokenTag(upper_index)) {
Expand Down
18 changes: 18 additions & 0 deletions tests/analysis/result_location_type.zig
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,17 @@ fn return_2() Error!?StructType {
// ^^^^ (u32)()
}

//
// continue
//

const continue_switch: StructType = blk: switch (some_tagged_union) {
.bar => |bar| continue :blk .{ .baz = @truncate(bar) },
// ^^^^ (u8)()
.baz => |baz| .{ .foo = baz },
// ^^^^ (u32)()
};

//
// break
//
Expand Down Expand Up @@ -224,6 +235,13 @@ const break_while_1: StructType =
// ^^^^ (u32)()
};

const break_switch: StructType = blk: switch (some_tagged_union) {
.bar => |bar| break :blk .{ .foo = bar },
// ^^^^ (u32)()
.baz => |baz| .{ .foo = baz },
// ^^^^ (u32)()
};

const break_block: StructType =
blk: {
break :blk .{ .foo = 1 };
Expand Down
Loading