mirror of
https://codeberg.org/ziglang/zig.git
synced 2025-12-06 13:54:21 +00:00
stage2: implement switch validation for integers
This commit is contained in:
parent
4155d2ae24
commit
12e4c648cc
3 changed files with 196 additions and 2 deletions
76
src/RangeSet.zig
Normal file
76
src/RangeSet.zig
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
const std = @import("std");
|
||||
const Order = std.math.Order;
|
||||
const Value = @import("value.zig").Value;
|
||||
const RangeSet = @This();
|
||||
|
||||
ranges: std.ArrayList(Range),
|
||||
|
||||
pub const Range = struct {
|
||||
start: Value,
|
||||
end: Value,
|
||||
src: usize,
|
||||
};
|
||||
|
||||
pub fn init(allocator: *std.mem.Allocator) RangeSet {
|
||||
return .{
|
||||
.ranges = std.ArrayList(Range).init(allocator),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *RangeSet) void {
|
||||
self.ranges.deinit();
|
||||
}
|
||||
|
||||
pub fn add(self: *RangeSet, start: Value, end: Value, src: usize) !?usize {
|
||||
for (self.ranges.items) |range| {
|
||||
if ((start.compare(.gte, range.start) and start.compare(.lte, range.end)) or
|
||||
(end.compare(.gte, range.start) and end.compare(.lte, range.end)))
|
||||
{
|
||||
// ranges overlap
|
||||
return range.src;
|
||||
}
|
||||
}
|
||||
try self.ranges.append(.{
|
||||
.start = start,
|
||||
.end = end,
|
||||
.src = src,
|
||||
});
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Assumes a and b do not overlap
|
||||
fn lessThan(_: void, a: Range, b: Range) bool {
|
||||
return a.start.compare(.lt, b.start);
|
||||
}
|
||||
|
||||
pub fn spans(self: *RangeSet, start: Value, end: Value) !bool {
|
||||
std.sort.sort(Range, self.ranges.items, {}, lessThan);
|
||||
|
||||
if (!self.ranges.items[0].start.eql(start) or
|
||||
!self.ranges.items[self.ranges.items.len - 1].end.eql(end))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var space: Value.BigIntSpace = undefined;
|
||||
|
||||
var counter = try std.math.big.int.Managed.init(self.ranges.allocator);
|
||||
defer counter.deinit();
|
||||
|
||||
// look for gaps
|
||||
for (self.ranges.items[1..]) |cur, i| {
|
||||
// i starts counting from the second item.
|
||||
const prev = self.ranges.items[i];
|
||||
|
||||
// prev.end + 1 == cur.start
|
||||
try counter.copy(prev.end.toBigInt(&space));
|
||||
try counter.addScalar(counter.toConst(), 1);
|
||||
|
||||
const cur_start_int = cur.start.toBigInt(&space);
|
||||
if (!cur_start_int.eq(counter.toConst())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
72
src/type.zig
72
src/type.zig
|
|
@ -2863,6 +2863,78 @@ pub const Type = extern union {
|
|||
};
|
||||
}
|
||||
|
||||
/// Asserts that self.zigTypeTag() == .Int.
|
||||
pub fn minInt(self: Type, arena: *std.heap.ArenaAllocator, target: Target) !Value {
|
||||
assert(self.zigTypeTag() == .Int);
|
||||
const info = self.intInfo(target);
|
||||
|
||||
if (!info.signed) {
|
||||
return Value.initTag(.zero);
|
||||
}
|
||||
|
||||
if ((info.bits - 1) <= std.math.maxInt(u6)) {
|
||||
const payload = try arena.allocator.create(Value.Payload.Int_i64);
|
||||
payload.* = .{
|
||||
.int = -(@as(i64, 1) << @truncate(u6, info.bits - 1)),
|
||||
};
|
||||
return Value.initPayload(&payload.base);
|
||||
}
|
||||
|
||||
var res = try std.math.big.int.Managed.initSet(&arena.allocator, 1);
|
||||
try res.shiftLeft(res, info.bits - 1);
|
||||
res.negate();
|
||||
|
||||
const res_const = res.toConst();
|
||||
if (res_const.positive) {
|
||||
const val_payload = try arena.allocator.create(Value.Payload.IntBigPositive);
|
||||
val_payload.* = .{ .limbs = res_const.limbs };
|
||||
return Value.initPayload(&val_payload.base);
|
||||
} else {
|
||||
const val_payload = try arena.allocator.create(Value.Payload.IntBigNegative);
|
||||
val_payload.* = .{ .limbs = res_const.limbs };
|
||||
return Value.initPayload(&val_payload.base);
|
||||
}
|
||||
}
|
||||
|
||||
/// Asserts that self.zigTypeTag() == .Int.
|
||||
pub fn maxInt(self: Type, arena: *std.heap.ArenaAllocator, target: Target) !Value {
|
||||
assert(self.zigTypeTag() == .Int);
|
||||
const info = self.intInfo(target);
|
||||
|
||||
if (info.signed and (info.bits - 1) <= std.math.maxInt(u6)) {
|
||||
const payload = try arena.allocator.create(Value.Payload.Int_i64);
|
||||
payload.* = .{
|
||||
.int = (@as(i64, 1) << @truncate(u6, info.bits - 1)) - 1,
|
||||
};
|
||||
return Value.initPayload(&payload.base);
|
||||
} else if (!info.signed and info.bits <= std.math.maxInt(u6)) {
|
||||
const payload = try arena.allocator.create(Value.Payload.Int_u64);
|
||||
payload.* = .{
|
||||
.int = (@as(u64, 1) << @truncate(u6, info.bits)) - 1,
|
||||
};
|
||||
return Value.initPayload(&payload.base);
|
||||
}
|
||||
|
||||
var res = try std.math.big.int.Managed.initSet(&arena.allocator, 1);
|
||||
try res.shiftLeft(res, info.bits - @boolToInt(info.signed));
|
||||
const one = std.math.big.int.Const{
|
||||
.limbs = &[_]std.math.big.Limb{1},
|
||||
.positive = true,
|
||||
};
|
||||
res.sub(res.toConst(), one) catch unreachable;
|
||||
|
||||
const res_const = res.toConst();
|
||||
if (res_const.positive) {
|
||||
const val_payload = try arena.allocator.create(Value.Payload.IntBigPositive);
|
||||
val_payload.* = .{ .limbs = res_const.limbs };
|
||||
return Value.initPayload(&val_payload.base);
|
||||
} else {
|
||||
const val_payload = try arena.allocator.create(Value.Payload.IntBigNegative);
|
||||
val_payload.* = .{ .limbs = res_const.limbs };
|
||||
return Value.initPayload(&val_payload.base);
|
||||
}
|
||||
}
|
||||
|
||||
/// This enum does not directly correspond to `std.builtin.TypeId` because
|
||||
/// it has extra enum tags in it, as a way of using less memory. For example,
|
||||
/// even though Zig recognizes `*align(10) i32` and `*i32` both as Pointer types
|
||||
|
|
|
|||
|
|
@ -1268,7 +1268,7 @@ fn analyzeInstSwitchBr(mod: *Module, scope: *Scope, inst: *zir.Inst.SwitchBr) In
|
|||
.body = .{ .instructions = try parent_block.arena.dupe(*Inst, case_block.instructions.items) },
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
return mod.addSwitchBr(parent_block, inst.base.src, target_ptr, cases);
|
||||
}
|
||||
|
||||
|
|
@ -1292,10 +1292,56 @@ fn validateSwitch(mod: *Module, scope: *Scope, target: *Inst, inst: *zir.Inst.Sw
|
|||
|
||||
// validate for duplicate items/missing else prong
|
||||
switch (target.ty.zigTypeTag()) {
|
||||
.Int, .ComptimeInt => return mod.fail(scope, inst.base.src, "TODO validateSwitch .Int, .ComptimeInt", .{}),
|
||||
.Enum => return mod.fail(scope, inst.base.src, "TODO validateSwitch .Enum", .{}),
|
||||
.ErrorSet => return mod.fail(scope, inst.base.src, "TODO validateSwitch .ErrorSet", .{}),
|
||||
.Union => return mod.fail(scope, inst.base.src, "TODO validateSwitch .Union", .{}),
|
||||
.Int, .ComptimeInt => {
|
||||
var range_set = @import("RangeSet.zig").init(mod.gpa);
|
||||
defer range_set.deinit();
|
||||
|
||||
for (inst.positionals.items) |item| {
|
||||
const maybe_src = if (item.castTag(.switch_range)) |range| blk: {
|
||||
const start_resolved = try resolveInst(mod, scope, range.positionals.lhs);
|
||||
const start_casted = try mod.coerce(scope, target.ty, start_resolved);
|
||||
const end_resolved = try resolveInst(mod, scope, range.positionals.rhs);
|
||||
const end_casted = try mod.coerce(scope, target.ty, end_resolved);
|
||||
|
||||
break :blk try range_set.add(
|
||||
try mod.resolveConstValue(scope, start_casted),
|
||||
try mod.resolveConstValue(scope, end_casted),
|
||||
item.src,
|
||||
);
|
||||
} else blk: {
|
||||
const resolved = try resolveInst(mod, scope, item);
|
||||
const casted = try mod.coerce(scope, target.ty, resolved);
|
||||
const value = try mod.resolveConstValue(scope, casted);
|
||||
break :blk try range_set.add(value, value, item.src);
|
||||
};
|
||||
|
||||
if (maybe_src) |previous_src| {
|
||||
return mod.fail(scope, item.src, "duplicate switch value", .{});
|
||||
// TODO notes "previous value is here" previous_src
|
||||
}
|
||||
}
|
||||
|
||||
if (target.ty.zigTypeTag() == .Int) {
|
||||
var arena = std.heap.ArenaAllocator.init(mod.gpa);
|
||||
defer arena.deinit();
|
||||
|
||||
const start = try target.ty.minInt(&arena, mod.getTarget());
|
||||
const end = try target.ty.maxInt(&arena, mod.getTarget());
|
||||
if (try range_set.spans(start, end)) {
|
||||
if (inst.kw_args.special_prong == .@"else") {
|
||||
return mod.fail(scope, inst.base.src, "unreachable else prong, all cases already handled", .{});
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (inst.kw_args.special_prong != .@"else") {
|
||||
return mod.fail(scope, inst.base.src, "switch must handle all possibilities", .{});
|
||||
}
|
||||
},
|
||||
.Bool => {
|
||||
var true_count: u8 = 0;
|
||||
var false_count: u8 = 0;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue