authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-03-31 15:06:03-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-03-31 15:06:03-07:00
loge272c29c163538159eb81f60cb5da3d7ebe099f9
tree443eb48eb399e2f16ba76743d55207cbe78e8a9f
parentc7b09be8de946d18c2f1afb532beb1b2426fab18

Sema: implement switch validation for ranges


4 files changed, 228 insertions(+), 38 deletions(-)

src/AstGen.zig+96-8
...@@ -2538,18 +2538,106 @@ fn forExpr(...@@ -2538,18 +2538,106 @@ fn forExpr(
2538fn getRangeNode(2538fn getRangeNode(
2539 node_tags: []const ast.Node.Tag,2539 node_tags: []const ast.Node.Tag,
2540 node_datas: []const ast.Node.Data,2540 node_datas: []const ast.Node.Data,
2541 start_node: ast.Node.Index,2541 node: ast.Node.Index,
2542) ?ast.Node.Index {2542) ?ast.Node.Index {
2543 var node = start_node;2543 switch (node_tags[node]) {
2544 while (true) {2544 .switch_range => return node,
2545 switch (node_tags[node]) {2545 .grouped_expression => unreachable,
2546 .switch_range => return node,2546 else => return null,
2547 .grouped_expression => node = node_datas[node].lhs,
2548 else => return null,
2549 }
2550 }2547 }
2551}2548}
25522549
2550pub const SwitchProngSrc = union(enum) {
2551 scalar: u32,
2552 multi: Multi,
2553 range: Multi,
2554
2555 pub const Multi = struct {
2556 prong: u32,
2557 item: u32,
2558 };
2559
2560 /// This function is intended to be called only when it is certain that we need
2561 /// the LazySrcLoc in order to emit a compile error.
2562 pub fn resolve(
2563 prong_src: SwitchProngSrc,
2564 decl: *Decl,
2565 switch_node_offset: i32,
2566 range_expand: enum { none, first, last },
2567 ) LazySrcLoc {
2568 @setCold(true);
2569 const switch_node = decl.relativeToNodeIndex(switch_node_offset);
2570 const tree = decl.container.file_scope.base.tree();
2571 const main_tokens = tree.nodes.items(.main_token);
2572 const node_datas = tree.nodes.items(.data);
2573 const node_tags = tree.nodes.items(.tag);
2574 const extra = tree.extraData(node_datas[switch_node].rhs, ast.Node.SubRange);
2575 const case_nodes = tree.extra_data[extra.start..extra.end];
2576
2577 var multi_i: u32 = 0;
2578 var scalar_i: u32 = 0;
2579 for (case_nodes) |case_node| {
2580 const case = switch (node_tags[case_node]) {
2581 .switch_case_one => tree.switchCaseOne(case_node),
2582 .switch_case => tree.switchCase(case_node),
2583 else => unreachable,
2584 };
2585 if (case.ast.values.len == 0)
2586 continue;
2587 if (case.ast.values.len == 1 and
2588 node_tags[case.ast.values[0]] == .identifier and
2589 mem.eql(u8, tree.tokenSlice(main_tokens[case.ast.values[0]]), "_"))
2590 {
2591 continue;
2592 }
2593 const is_multi = case.ast.values.len != 1 or
2594 getRangeNode(node_tags, node_datas, case.ast.values[0]) != null;
2595
2596 switch (prong_src) {
2597 .scalar => |i| if (!is_multi and i == scalar_i) return LazySrcLoc{
2598 .node_offset = decl.nodeIndexToRelative(case.ast.values[0]),
2599 },
2600 .multi => |s| if (is_multi and s.prong == multi_i) {
2601 var item_i: u32 = 0;
2602 for (case.ast.values) |item_node| {
2603 if (getRangeNode(node_tags, node_datas, item_node) != null)
2604 continue;
2605
2606 if (item_i == s.item) return LazySrcLoc{
2607 .node_offset = decl.nodeIndexToRelative(item_node),
2608 };
2609 item_i += 1;
2610 } else unreachable;
2611 },
2612 .range => |s| if (is_multi and s.prong == multi_i) {
2613 var range_i: u32 = 0;
2614 for (case.ast.values) |item_node| {
2615 const range = getRangeNode(node_tags, node_datas, item_node) orelse continue;
2616
2617 if (range_i == s.item) switch (range_expand) {
2618 .none => return LazySrcLoc{
2619 .node_offset = decl.nodeIndexToRelative(item_node),
2620 },
2621 .first => return LazySrcLoc{
2622 .node_offset = decl.nodeIndexToRelative(node_datas[range].lhs),
2623 },
2624 .last => return LazySrcLoc{
2625 .node_offset = decl.nodeIndexToRelative(node_datas[range].rhs),
2626 },
2627 };
2628 range_i += 1;
2629 } else unreachable;
2630 },
2631 }
2632 if (is_multi) {
2633 multi_i += 1;
2634 } else {
2635 scalar_i += 1;
2636 }
2637 } else unreachable;
2638 }
2639};
2640
2553fn switchExpr(2641fn switchExpr(
2554 parent_gz: *GenZir,2642 parent_gz: *GenZir,
2555 scope: *Scope,2643 scope: *Scope,
src/RangeSet.zig+3-2
...@@ -2,13 +2,14 @@ const std = @import("std");...@@ -2,13 +2,14 @@ const std = @import("std");
2const Order = std.math.Order;2const Order = std.math.Order;
3const Value = @import("value.zig").Value;3const Value = @import("value.zig").Value;
4const RangeSet = @This();4const RangeSet = @This();
5const SwitchProngSrc = @import("AstGen.zig").SwitchProngSrc;
56
6ranges: std.ArrayList(Range),7ranges: std.ArrayList(Range),
78
8pub const Range = struct {9pub const Range = struct {
9 start: Value,10 start: Value,
10 end: Value,11 end: Value,
11 src: usize,12 src: SwitchProngSrc,
12};13};
1314
14pub fn init(allocator: *std.mem.Allocator) RangeSet {15pub fn init(allocator: *std.mem.Allocator) RangeSet {
...@@ -21,7 +22,7 @@ pub fn deinit(self: *RangeSet) void {...@@ -21,7 +22,7 @@ pub fn deinit(self: *RangeSet) void {
21 self.ranges.deinit();22 self.ranges.deinit();
22}23}
2324
24pub fn add(self: *RangeSet, start: Value, end: Value, src: usize) !?usize {25pub fn add(self: *RangeSet, start: Value, end: Value, src: SwitchProngSrc) !?SwitchProngSrc {
25 for (self.ranges.items) |range| {26 for (self.ranges.items) |range| {
26 if ((start.compare(.gte, range.start) and start.compare(.lte, range.end)) or27 if ((start.compare(.gte, range.start) and start.compare(.lte, range.end)) or
27 (end.compare(.gte, range.start) and end.compare(.lte, range.end)))28 (end.compare(.gte, range.start) and end.compare(.lte, range.end)))
src/Sema.zig+125-27
...@@ -60,6 +60,7 @@ const InnerError = Module.InnerError;...@@ -60,6 +60,7 @@ const InnerError = Module.InnerError;
60const Decl = Module.Decl;60const Decl = Module.Decl;
61const LazySrcLoc = Module.LazySrcLoc;61const LazySrcLoc = Module.LazySrcLoc;
62const RangeSet = @import("RangeSet.zig");62const RangeSet = @import("RangeSet.zig");
63const AstGen = @import("AstGen.zig");
6364
64const ValueSrcMap = std.HashMap(Value, LazySrcLoc, Value.hash, Value.eql, std.hash_map.DefaultMaxLoadPercentage);65const ValueSrcMap = std.HashMap(Value, LazySrcLoc, Value.hash, Value.eql, std.hash_map.DefaultMaxLoadPercentage);
6566
...@@ -419,19 +420,27 @@ fn resolveType(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, zir_ref: zir.I...@@ -419,19 +420,27 @@ fn resolveType(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, zir_ref: zir.I
419420
420fn resolveConstValue(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, base: *ir.Inst) !Value {421fn resolveConstValue(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, base: *ir.Inst) !Value {
421 return (try sema.resolveDefinedValue(block, src, base)) orelse422 return (try sema.resolveDefinedValue(block, src, base)) orelse
422 return sema.mod.fail(&block.base, src, "unable to resolve comptime value", .{});423 return sema.failWithNeededComptime(block, src);
423}424}
424425
425fn resolveDefinedValue(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, base: *ir.Inst) !?Value {426fn resolveDefinedValue(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, base: *ir.Inst) !?Value {
426 if (base.value()) |val| {427 if (base.value()) |val| {
427 if (val.isUndef()) {428 if (val.isUndef()) {
428 return sema.mod.fail(&block.base, src, "use of undefined value here causes undefined behavior", .{});429 return sema.failWithUseOfUndef(block, src);
429 }430 }
430 return val;431 return val;
431 }432 }
432 return null;433 return null;
433}434}
434435
436fn failWithNeededComptime(sema: *Sema, block: *Scope.Block, src: LazySrcLoc) InnerError {
437 return sema.mod.fail(&block.base, src, "unable to resolve comptime value", .{});
438}
439
440fn failWithUseOfUndef(sema: *Sema, block: *Scope.Block, src: LazySrcLoc) InnerError {
441 return sema.mod.fail(&block.base, src, "use of undefined value here causes undefined behavior", .{});
442}
443
435/// Appropriate to call when the coercion has already been done by result444/// Appropriate to call when the coercion has already been done by result
436/// location semantics. Asserts the value fits in the provided `Int` type.445/// location semantics. Asserts the value fits in the provided `Int` type.
437/// Only supports `Int` types 64 bits or less.446/// Only supports `Int` types 64 bits or less.
...@@ -2368,7 +2377,7 @@ fn analyzeSwitch(...@@ -2368,7 +2377,7 @@ fn analyzeSwitch(
23682377
2369 var extra_index: usize = special.end;2378 var extra_index: usize = special.end;
2370 {2379 {
2371 var scalar_i: usize = 0;2380 var scalar_i: u32 = 0;
2372 while (scalar_i < scalar_cases_len) : (scalar_i += 1) {2381 while (scalar_i < scalar_cases_len) : (scalar_i += 1) {
2373 const item_ref = @intToEnum(zir.Inst.Ref, sema.code.extra[extra_index]);2382 const item_ref = @intToEnum(zir.Inst.Ref, sema.code.extra[extra_index]);
2374 extra_index += 1;2383 extra_index += 1;
...@@ -2382,11 +2391,12 @@ fn analyzeSwitch(...@@ -2382,11 +2391,12 @@ fn analyzeSwitch(
2382 &range_set,2391 &range_set,
2383 item_ref,2392 item_ref,
2384 src_node_offset,2393 src_node_offset,
2394 .{ .scalar = scalar_i },
2385 );2395 );
2386 }2396 }
2387 }2397 }
2388 {2398 {
2389 var multi_i: usize = 0;2399 var multi_i: u32 = 0;
2390 while (multi_i < multi_cases_len) : (multi_i += 1) {2400 while (multi_i < multi_cases_len) : (multi_i += 1) {
2391 const items_len = sema.code.extra[extra_index];2401 const items_len = sema.code.extra[extra_index];
2392 extra_index += 1;2402 extra_index += 1;
...@@ -2397,16 +2407,17 @@ fn analyzeSwitch(...@@ -2397,16 +2407,17 @@ fn analyzeSwitch(
2397 const items = sema.code.refSlice(extra_index, items_len);2407 const items = sema.code.refSlice(extra_index, items_len);
2398 extra_index += items_len;2408 extra_index += items_len;
23992409
2400 for (items) |item_ref| {2410 for (items) |item_ref, item_i| {
2401 try sema.validateSwitchItem(2411 try sema.validateSwitchItem(
2402 block,2412 block,
2403 &range_set,2413 &range_set,
2404 item_ref,2414 item_ref,
2405 src_node_offset,2415 src_node_offset,
2416 .{ .multi = .{ .prong = multi_i, .item = @intCast(u32, item_i) } },
2406 );2417 );
2407 }2418 }
24082419
2409 var range_i: usize = 0;2420 var range_i: u32 = 0;
2410 while (range_i < ranges_len) : (range_i += 1) {2421 while (range_i < ranges_len) : (range_i += 1) {
2411 const item_first = @intToEnum(zir.Inst.Ref, sema.code.extra[extra_index]);2422 const item_first = @intToEnum(zir.Inst.Ref, sema.code.extra[extra_index]);
2412 extra_index += 1;2423 extra_index += 1;
...@@ -2419,6 +2430,7 @@ fn analyzeSwitch(...@@ -2419,6 +2430,7 @@ fn analyzeSwitch(
2419 item_first,2430 item_first,
2420 item_last,2431 item_last,
2421 src_node_offset,2432 src_node_offset,
2433 .{ .range = .{ .prong = multi_i, .item = range_i } },
2422 );2434 );
2423 }2435 }
24242436
...@@ -2723,8 +2735,9 @@ fn analyzeSwitch(...@@ -2723,8 +2735,9 @@ fn analyzeSwitch(
2723 extra_index += body_len;2735 extra_index += body_len;
27242736
2725 case_block.instructions.shrinkRetainingCapacity(0);2737 case_block.instructions.shrinkRetainingCapacity(0);
2726 const item = try sema.resolveInst(item_ref);2738 // We validate these above; these two calls are guaranteed to succeed.
2727 const item_val = try sema.resolveConstValue(&case_block, item.src, item);2739 const item = sema.resolveInst(item_ref) catch unreachable;
2740 const item_val = sema.resolveConstValue(&case_block, .unneeded, item) catch unreachable;
27282741
2729 _ = try sema.analyzeBody(&case_block, body);2742 _ = try sema.analyzeBody(&case_block, body);
27302743
...@@ -2836,48 +2849,133 @@ fn analyzeSwitch(...@@ -2836,48 +2849,133 @@ fn analyzeSwitch(
2836 prev_condbr = new_condbr;2849 prev_condbr = new_condbr;
2837 }2850 }
28382851
2839 case_block.instructions.shrinkRetainingCapacity(0);2852 const final_else_body: Body = blk: {
2840 _ = try sema.analyzeBody(&case_block, special.body);2853 if (special.body.len != 0) {
2841 const else_body: Body = .{2854 case_block.instructions.shrinkRetainingCapacity(0);
2842 .instructions = try sema.arena.dupe(*Inst, case_block.instructions.items),2855 _ = try sema.analyzeBody(&case_block, special.body);
2843 };2856 const else_body: Body = .{
2844 first_condbr.else_body = else_body;2857 .instructions = try sema.arena.dupe(*Inst, case_block.instructions.items),
28452858 };
2846 const final_else_body: Body = .{2859 if (prev_condbr != null) {
2847 .instructions = try sema.arena.dupe(*Inst, &[1]*Inst{&first_condbr.base}),2860 first_condbr.else_body = else_body;
2861 break :blk .{
2862 .instructions = try sema.arena.dupe(*Inst, &[1]*Inst{&first_condbr.base}),
2863 };
2864 } else {
2865 break :blk else_body;
2866 }
2867 } else {
2868 break :blk .{ .instructions = &.{} };
2869 }
2848 };2870 };
28492871
2850 _ = try child_block.addSwitchBr(src, operand, cases, final_else_body);2872 _ = try child_block.addSwitchBr(src, operand, cases, final_else_body);
2851 return sema.analyzeBlockBody(block, &child_block, merges);2873 return sema.analyzeBlockBody(block, &child_block, merges);
2852}2874}
28532875
2876fn validateSwitchRange(
2877 sema: *Sema,
2878 block: *Scope.Block,
2879 range_set: *RangeSet,
2880 first_ref: zir.Inst.Ref,
2881 last_ref: zir.Inst.Ref,
2882 src_node_offset: i32,
2883 switch_prong_src: AstGen.SwitchProngSrc,
2884) InnerError!void {
2885 const first = try sema.resolveInst(first_ref);
2886 const last = try sema.resolveInst(last_ref);
2887 // We have to avoid the helper functions here because we cannot construct a LazySrcLoc
2888 // because we only have the switch AST node. Only if we know for sure we need to report
2889 // a compile error do we resolve the full source locations.
2890 const first_val = val: {
2891 if (last.value()) |val| {
2892 if (val.isUndef()) {
2893 const src = switch_prong_src.resolve(block.src_decl, src_node_offset, .first);
2894 return sema.failWithUseOfUndef(block, src);
2895 }
2896 break :val val;
2897 }
2898 const src = switch_prong_src.resolve(block.src_decl, src_node_offset, .first);
2899 return sema.failWithNeededComptime(block, src);
2900 };
2901 const last_val = val: {
2902 if (first.value()) |val| {
2903 if (val.isUndef()) {
2904 const src = switch_prong_src.resolve(block.src_decl, src_node_offset, .last);
2905 return sema.failWithUseOfUndef(block, src);
2906 }
2907 break :val val;
2908 }
2909 const src = switch_prong_src.resolve(block.src_decl, src_node_offset, .last);
2910 return sema.failWithNeededComptime(block, src);
2911 };
2912 const maybe_prev_src = try range_set.add(first_val, last_val, switch_prong_src);
2913 return sema.validateSwitchDupe(block, maybe_prev_src, switch_prong_src, src_node_offset);
2914}
2915
2854fn validateSwitchItem(2916fn validateSwitchItem(
2855 sema: *Sema,2917 sema: *Sema,
2856 block: *Scope.Block,2918 block: *Scope.Block,
2857 range_set: *RangeSet,2919 range_set: *RangeSet,
2858 item_ref: zir.Inst.Ref,2920 item_ref: zir.Inst.Ref,
2859 src_node_offset: i32,2921 src_node_offset: i32,
2922 switch_prong_src: AstGen.SwitchProngSrc,
2860) InnerError!void {2923) InnerError!void {
2861 @panic("TODO");2924 const item = try sema.resolveInst(item_ref);
2925 // We have to avoid the helper functions here because we cannot construct a LazySrcLoc
2926 // because we only have the switch AST node. Only if we know for sure we need to report
2927 // a compile error do we resolve the full source locations.
2928 const value = val: {
2929 if (item.value()) |val| {
2930 if (val.isUndef()) {
2931 const src = switch_prong_src.resolve(block.src_decl, src_node_offset, .none);
2932 return sema.failWithUseOfUndef(block, src);
2933 }
2934 break :val val;
2935 }
2936 const src = switch_prong_src.resolve(block.src_decl, src_node_offset, .none);
2937 return sema.failWithNeededComptime(block, src);
2938 };
2939 const maybe_prev_src = try range_set.add(value, value, switch_prong_src);
2940 return sema.validateSwitchDupe(block, maybe_prev_src, switch_prong_src, src_node_offset);
2862}2941}
28632942
2864fn validateSwitchItemBool(2943fn validateSwitchDupe(
2865 sema: *Sema,2944 sema: *Sema,
2866 block: *Scope.Block,2945 block: *Scope.Block,
2867 true_count: *u8,2946 maybe_prev_src: ?AstGen.SwitchProngSrc,
2868 false_count: *u8,2947 switch_prong_src: AstGen.SwitchProngSrc,
2869 item_ref: zir.Inst.Ref,
2870 src_node_offset: i32,2948 src_node_offset: i32,
2871) InnerError!void {2949) InnerError!void {
2872 @panic("TODO");2950 const prev_prong_src = maybe_prev_src orelse return;
2951 const src = switch_prong_src.resolve(block.src_decl, src_node_offset, .none);
2952 const prev_src = prev_prong_src.resolve(block.src_decl, src_node_offset, .none);
2953 const msg = msg: {
2954 const msg = try sema.mod.errMsg(
2955 &block.base,
2956 src,
2957 "duplicate switch value",
2958 .{},
2959 );
2960 errdefer msg.destroy(sema.gpa);
2961 try sema.mod.errNote(
2962 &block.base,
2963 prev_src,
2964 msg,
2965 "previous value here",
2966 .{},
2967 );
2968 break :msg msg;
2969 };
2970 return sema.mod.failWithOwnedErrorMsg(&block.base, msg);
2873}2971}
28742972
2875fn validateSwitchRange(2973fn validateSwitchItemBool(
2876 sema: *Sema,2974 sema: *Sema,
2877 block: *Scope.Block,2975 block: *Scope.Block,
2878 range_set: *RangeSet,2976 true_count: *u8,
2879 item_first: zir.Inst.Ref,2977 false_count: *u8,
2880 item_last: zir.Inst.Ref,2978 item_ref: zir.Inst.Ref,
2881 src_node_offset: i32,2979 src_node_offset: i32,
2882) InnerError!void {2980) InnerError!void {
2883 @panic("TODO");2981 @panic("TODO");
src/codegen.zig+4-1
...@@ -3994,7 +3994,10 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {...@@ -3994,7 +3994,10 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
3994 fn fail(self: *Self, src: LazySrcLoc, comptime format: []const u8, args: anytype) InnerError {3994 fn fail(self: *Self, src: LazySrcLoc, comptime format: []const u8, args: anytype) InnerError {
3995 @setCold(true);3995 @setCold(true);
3996 assert(self.err_msg == null);3996 assert(self.err_msg == null);
3997 const src_loc = src.toSrcLocWithDecl(self.mod_fn.owner_decl);3997 const src_loc = if (src != .unneeded)
3998 src.toSrcLocWithDecl(self.mod_fn.owner_decl)
3999 else
4000 self.src_loc;
3998 self.err_msg = try ErrorMsg.create(self.bin_file.allocator, src_loc, format, args);4001 self.err_msg = try ErrorMsg.create(self.bin_file.allocator, src_loc, format, args);
3999 return error.CodegenFail;4002 return error.CodegenFail;
4000 }4003 }