diff options
| author | Vexu <git@vexu.eu> | 2020-10-12 12:20:32 +0300 |
|---|---|---|
| committer | Vexu <git@vexu.eu> | 2020-10-30 15:58:11 +0200 |
| commit | 27d233cef76f9ae3086a6ec5e33c347e2af64796 (patch) | |
| tree | 07b5b2279f50eb8c1e65951e6962b5d8c078bed1 /src | |
| parent | ad32e46bceb03c0b0d67fe73e0de0e308f0675e6 (diff) | |
| download | zig-27d233cef76f9ae3086a6ec5e33c347e2af64796.tar.gz zig-27d233cef76f9ae3086a6ec5e33c347e2af64796.zip | |
stage2: basic switch validation
Diffstat (limited to 'src')
| -rw-r--r-- | src/astgen.zig | 18 | ||||
| -rw-r--r-- | src/value.zig | 4 | ||||
| -rw-r--r-- | src/zir.zig | 8 | ||||
| -rw-r--r-- | src/zir_sema.zig | 123 |
4 files changed, 140 insertions, 13 deletions
diff --git a/src/astgen.zig b/src/astgen.zig index 19d7b87eeb..0b74886729 100644 --- a/src/astgen.zig +++ b/src/astgen.zig @@ -1592,7 +1592,7 @@ fn switchExpr(mod: *Module, scope: *Scope, rl: ResultLoc, switch_node: *ast.Node kw_args.special_case = .@"else"; else_src = case_src; cases[cases.len - 1] = .{ - .values = &[_]*zir.Inst{}, + .items = &[0]*zir.Inst{}, .body = undefined, // filled below }; continue; @@ -1606,7 +1606,7 @@ fn switchExpr(mod: *Module, scope: *Scope, rl: ResultLoc, switch_node: *ast.Node kw_args.special_case = .underscore; underscore_src = case_src; cases[cases.len - 1] = .{ - .values = &[_]*zir.Inst{}, + .items = &[0]*zir.Inst{}, .body = undefined, // filled below }; continue; @@ -1620,26 +1620,26 @@ fn switchExpr(mod: *Module, scope: *Scope, rl: ResultLoc, switch_node: *ast.Node } } - // Regular case, we need to fill `values`. - const values = try block_scope.arena.alloc(*zir.Inst, case.items_len); + // Regular case, we need to fill `items`. + const items = try block_scope.arena.alloc(*zir.Inst, case.items_len); for (case.items()) |item, i| { if (item.castTag(.Range)) |range| { - values[i] = try switchRange(mod, &block_scope.base, range); + items[i] = try switchRange(mod, &block_scope.base, range); if (kw_args.support_range == null) - kw_args.support_range = values[i]; + kw_args.support_range = items[i]; } else { - values[i] = try expr(mod, &block_scope.base, .none, item); + items[i] = try expr(mod, &block_scope.base, .none, item); } } cases[case_index] = .{ - .values = values, + .items = items, .body = undefined, // filled below }; case_index += 1; } // Then we add the switch instruction to finish the block. - _ = try addZIRInst(mod, scope, switch_src, zir.Inst.Switch, .{ + _ = try addZIRInst(mod, &block_scope.base, switch_src, zir.Inst.Switch, .{ .target_ptr = target_ptr, .cases = cases, }, kw_args); diff --git a/src/value.zig b/src/value.zig index e8fe848d5d..a7c3afbd6b 100644 --- a/src/value.zig +++ b/src/value.zig @@ -1242,6 +1242,10 @@ pub const Value = extern union { return compare(a, .eq, b); } + pub fn hash(a: Value) u64 { + @panic("TODO Value.hash"); + } + /// Asserts the value is a pointer and dereferences it. /// Returns error.AnalysisFail if the pointer points to a Decl that failed semantic analysis. pub fn pointerDeref(self: Value, allocator: *Allocator) error{ AnalysisFail, OutOfMemory }!Value { diff --git a/src/zir.zig b/src/zir.zig index d2a22cdf52..5dc7a2eee6 100644 --- a/src/zir.zig +++ b/src/zir.zig @@ -275,6 +275,8 @@ pub const Inst = struct { /// A switch expression. @"switch", /// A range in a switch case, `lhs...rhs`. + /// Only checks that `lhs >= rhs` if they are ints or floats, everything else is + /// validated by the .switch instruction. switch_range, pub fn Type(tag: Tag) type { @@ -1018,7 +1020,7 @@ pub const Inst = struct { }, pub const Case = struct { - values: []*Inst, + items: []*Inst, body: Module.Body, }; }; @@ -1284,7 +1286,7 @@ const Writer = struct { try stream.writeAll(",\n"); } try stream.writeByteNTimes(' ', self.indent); - try self.writeParamToStream(stream, &case.values); + try self.writeParamToStream(stream, &case.items); try stream.writeAll(" => "); try self.writeParamToStream(stream, &case.body); } @@ -1714,7 +1716,7 @@ const Parser = struct { while (true) { const cur = try cases.addOne(); skipSpace(self); - cur.values = try self.parseParameterGeneric([]*Inst, body_ctx); + cur.items = try self.parseParameterGeneric([]*Inst, body_ctx); skipSpace(self); try requireEatBytes(self, "=>"); cur.body = try self.parseBody(body_ctx); diff --git a/src/zir_sema.zig b/src/zir_sema.zig index cc9e8182bb..26b2714ec8 100644 --- a/src/zir_sema.zig +++ b/src/zir_sema.zig @@ -135,7 +135,8 @@ pub fn analyzeInst(mod: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError! .slice => return analyzeInstSlice(mod, scope, old_inst.castTag(.slice).?), .slice_start => return analyzeInstSliceStart(mod, scope, old_inst.castTag(.slice_start).?), .import => return analyzeInstImport(mod, scope, old_inst.castTag(.import).?), - .@"switch", .switch_range => @panic("TODO switch sema"), + .@"switch" => return analyzeInstSwitch(mod, scope, old_inst.castTag(.@"switch").?), + .switch_range => return analyzeInstSwitchRange(mod, scope, old_inst.castTag(.switch_range).?), } } @@ -1205,6 +1206,126 @@ fn analyzeInstSliceStart(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) Inn return mod.analyzeSlice(scope, inst.base.src, array_ptr, start, null, null); } +fn analyzeInstSwitchRange(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*Inst { + const start = try resolveInst(mod, scope, inst.positionals.lhs); + const end = try resolveInst(mod, scope, inst.positionals.rhs); + + switch (start.ty.zigTypeTag()) { + .Int, .ComptimeInt, .Float, .ComptimeFloat => {}, + else => return mod.constVoid(scope, inst.base.src), + } + switch (end.ty.zigTypeTag()) { + .Int, .ComptimeInt, .Float, .ComptimeFloat => {}, + else => return mod.constVoid(scope, inst.base.src), + } + if (start.value()) |start_val| { + if (end.value()) |end_val| { + if (start_val.compare(.gte, end_val)) { + return mod.fail(scope, inst.base.src, "range start value is greater than the end value", .{}); + } + } + } + return mod.constVoid(scope, inst.base.src); +} + +fn analyzeInstSwitch(mod: *Module, scope: *Scope, inst: *zir.Inst.Switch) InnerError!*Inst { + const target_ptr = try resolveInst(mod, scope, inst.positionals.target_ptr); + const target = try mod.analyzeDeref(scope, inst.base.src, target_ptr, inst.positionals.target_ptr.src); + try validateSwitch(mod, scope, target, inst); + + return mod.fail(scope, inst.base.src, "TODO analyzeInstSwitch", .{}); +} + +fn validateSwitch(mod: *Module, scope: *Scope, target: *Inst, inst: *zir.Inst.Switch) InnerError!void { + // validate usage of '_' prongs + if (inst.kw_args.special_case == .underscore and target.ty.zigTypeTag() != .Enum) { + return mod.fail(scope, inst.base.src, "'_' prong only allowed when switching on non-exhaustive enums", .{}); + // TODO notes "'_' prong here" inst.positionals.cases[last].src + } + + // check that target type supports ranges + if (inst.kw_args.support_range) |some| { + switch (target.ty.zigTypeTag()) { + .Int, .ComptimeInt, .Float, .ComptimeFloat => {}, + else => { + return mod.fail(scope, target.src, "ranges not allowed when switching on type {}", .{target.ty}); + // TODO notes "range used here" some.src + }, + } + } + + // validate for duplicate items/missing else prong + switch (target.ty.zigTypeTag()) { + .Int, .ComptimeInt => return mod.fail(scope, inst.base.src, "TODO validateSwitch .Int, .ComptimeInt", .{}), + .Float, .ComptimeFloat => return mod.fail(scope, inst.base.src, "TODO validateSwitch .Float, .ComptimeFloat", .{}), + .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", .{}), + .Bool => { + var true_count: u8 = 0; + var false_count: u8 = 0; + for (inst.positionals.cases) |case| { + for (case.items) |item| { + const resolved = try resolveInst(mod, scope, item); + const casted = try mod.coerce(scope, Type.initTag(.bool), resolved); + if ((try mod.resolveConstValue(scope, casted)).toBool()) { + true_count += 1; + } else { + false_count += 1; + } + + if (true_count > 1 or false_count > 1) { + return mod.fail(scope, item.src, "duplicate switch value", .{}); + } + } + } + if ((true_count == 0 or false_count == 0) and inst.kw_args.special_case != .@"else") { + return mod.fail(scope, inst.base.src, "switch must handle all possibilities", .{}); + } + if ((true_count == 1 and false_count == 1) and inst.kw_args.special_case == .@"else") { + return mod.fail(scope, inst.base.src, "unreachable else prong, all cases already handled", .{}); + } + }, + .EnumLiteral, .Void, .Fn, .Pointer, .Type => { + if (inst.kw_args.special_case != .@"else") { + return mod.fail(scope, inst.base.src, "else prong required when switching on type '{}'", .{target.ty}); + } + + var seen_values = std.HashMap(Value, usize, Value.hash, Value.eql, std.hash_map.DefaultMaxLoadPercentage).init(mod.gpa); + defer seen_values.deinit(); + + for (inst.positionals.cases) |case| { + for (case.items) |item| { + const resolved = try resolveInst(mod, scope, item); + const casted = try mod.coerce(scope, target.ty, resolved); + const val = try mod.resolveConstValue(scope, casted); + + if (try seen_values.fetchPut(val, item.src)) |prev| { + return mod.fail(scope, item.src, "duplicate switch value", .{}); + // TODO notes "previous value here" prev.value + } + } + } + }, + + .ErrorUnion, + .NoReturn, + .Array, + .Struct, + .Undefined, + .Null, + .Optional, + .BoundFn, + .Opaque, + .Vector, + .Frame, + .AnyFrame, + => { + return mod.fail(scope, target.src, "invalid switch target type '{}'", .{target.ty}); + }, + } +} + fn analyzeInstImport(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst { const operand = try resolveConstString(mod, scope, inst.positionals.operand); |
