authorgravatar for git@vexu.euVeikka Tuominen <git@vexu.eu> 2020-10-12 12:20:32+03:00
committergravatar for git@vexu.euVeikka Tuominen <git@vexu.eu> 2020-10-30 15:58:11+02:00
log27d233cef76f9ae3086a6ec5e33c347e2af64796
tree07b5b2279f50eb8c1e65951e6962b5d8c078bed1
parentad32e46bceb03c0b0d67fe73e0de0e308f0675e6
signaturelock-open Commit is signed but in an unrecognized format.

stage2: basic switch validation


4 files changed, 140 insertions(+), 13 deletions(-)

src/astgen.zig+9-9
...@@ -1592,7 +1592,7 @@ fn switchExpr(mod: *Module, scope: *Scope, rl: ResultLoc, switch_node: *ast.Node...@@ -1592,7 +1592,7 @@ fn switchExpr(mod: *Module, scope: *Scope, rl: ResultLoc, switch_node: *ast.Node
1592 kw_args.special_case = .@"else";1592 kw_args.special_case = .@"else";
1593 else_src = case_src;1593 else_src = case_src;
1594 cases[cases.len - 1] = .{1594 cases[cases.len - 1] = .{
1595 .values = &[_]*zir.Inst{},1595 .items = &[0]*zir.Inst{},
1596 .body = undefined, // filled below1596 .body = undefined, // filled below
1597 };1597 };
1598 continue;1598 continue;
...@@ -1606,7 +1606,7 @@ fn switchExpr(mod: *Module, scope: *Scope, rl: ResultLoc, switch_node: *ast.Node...@@ -1606,7 +1606,7 @@ fn switchExpr(mod: *Module, scope: *Scope, rl: ResultLoc, switch_node: *ast.Node
1606 kw_args.special_case = .underscore;1606 kw_args.special_case = .underscore;
1607 underscore_src = case_src;1607 underscore_src = case_src;
1608 cases[cases.len - 1] = .{1608 cases[cases.len - 1] = .{
1609 .values = &[_]*zir.Inst{},1609 .items = &[0]*zir.Inst{},
1610 .body = undefined, // filled below1610 .body = undefined, // filled below
1611 };1611 };
1612 continue;1612 continue;
...@@ -1620,26 +1620,26 @@ fn switchExpr(mod: *Module, scope: *Scope, rl: ResultLoc, switch_node: *ast.Node...@@ -1620,26 +1620,26 @@ fn switchExpr(mod: *Module, scope: *Scope, rl: ResultLoc, switch_node: *ast.Node
1620 }1620 }
1621 }1621 }
16221622
1623 // Regular case, we need to fill `values`.1623 // Regular case, we need to fill `items`.
1624 const values = try block_scope.arena.alloc(*zir.Inst, case.items_len);1624 const items = try block_scope.arena.alloc(*zir.Inst, case.items_len);
1625 for (case.items()) |item, i| {1625 for (case.items()) |item, i| {
1626 if (item.castTag(.Range)) |range| {1626 if (item.castTag(.Range)) |range| {
1627 values[i] = try switchRange(mod, &block_scope.base, range);1627 items[i] = try switchRange(mod, &block_scope.base, range);
1628 if (kw_args.support_range == null)1628 if (kw_args.support_range == null)
1629 kw_args.support_range = values[i];1629 kw_args.support_range = items[i];
1630 } else {1630 } else {
1631 values[i] = try expr(mod, &block_scope.base, .none, item);1631 items[i] = try expr(mod, &block_scope.base, .none, item);
1632 }1632 }
1633 }1633 }
1634 cases[case_index] = .{1634 cases[case_index] = .{
1635 .values = values,1635 .items = items,
1636 .body = undefined, // filled below1636 .body = undefined, // filled below
1637 };1637 };
1638 case_index += 1;1638 case_index += 1;
1639 }1639 }
16401640
1641 // Then we add the switch instruction to finish the block.1641 // Then we add the switch instruction to finish the block.
1642 _ = try addZIRInst(mod, scope, switch_src, zir.Inst.Switch, .{1642 _ = try addZIRInst(mod, &block_scope.base, switch_src, zir.Inst.Switch, .{
1643 .target_ptr = target_ptr,1643 .target_ptr = target_ptr,
1644 .cases = cases,1644 .cases = cases,
1645 }, kw_args);1645 }, kw_args);
src/value.zig+4
...@@ -1242,6 +1242,10 @@ pub const Value = extern union {...@@ -1242,6 +1242,10 @@ pub const Value = extern union {
1242 return compare(a, .eq, b);1242 return compare(a, .eq, b);
1243 }1243 }
12441244
1245 pub fn hash(a: Value) u64 {
1246 @panic("TODO Value.hash");
1247 }
1248
1245 /// Asserts the value is a pointer and dereferences it.1249 /// Asserts the value is a pointer and dereferences it.
1246 /// Returns error.AnalysisFail if the pointer points to a Decl that failed semantic analysis.1250 /// Returns error.AnalysisFail if the pointer points to a Decl that failed semantic analysis.
1247 pub fn pointerDeref(self: Value, allocator: *Allocator) error{ AnalysisFail, OutOfMemory }!Value {1251 pub fn pointerDeref(self: Value, allocator: *Allocator) error{ AnalysisFail, OutOfMemory }!Value {
src/zir.zig+5-3
...@@ -275,6 +275,8 @@ pub const Inst = struct {...@@ -275,6 +275,8 @@ pub const Inst = struct {
275 /// A switch expression.275 /// A switch expression.
276 @"switch",276 @"switch",
277 /// A range in a switch case, `lhs...rhs`.277 /// A range in a switch case, `lhs...rhs`.
278 /// Only checks that `lhs >= rhs` if they are ints or floats, everything else is
279 /// validated by the .switch instruction.
278 switch_range,280 switch_range,
279281
280 pub fn Type(tag: Tag) type {282 pub fn Type(tag: Tag) type {
...@@ -1018,7 +1020,7 @@ pub const Inst = struct {...@@ -1018,7 +1020,7 @@ pub const Inst = struct {
1018 },1020 },
10191021
1020 pub const Case = struct {1022 pub const Case = struct {
1021 values: []*Inst,1023 items: []*Inst,
1022 body: Module.Body,1024 body: Module.Body,
1023 };1025 };
1024 };1026 };
...@@ -1284,7 +1286,7 @@ const Writer = struct {...@@ -1284,7 +1286,7 @@ const Writer = struct {
1284 try stream.writeAll(",\n");1286 try stream.writeAll(",\n");
1285 }1287 }
1286 try stream.writeByteNTimes(' ', self.indent);1288 try stream.writeByteNTimes(' ', self.indent);
1287 try self.writeParamToStream(stream, &case.values);1289 try self.writeParamToStream(stream, &case.items);
1288 try stream.writeAll(" => ");1290 try stream.writeAll(" => ");
1289 try self.writeParamToStream(stream, &case.body);1291 try self.writeParamToStream(stream, &case.body);
1290 }1292 }
...@@ -1714,7 +1716,7 @@ const Parser = struct {...@@ -1714,7 +1716,7 @@ const Parser = struct {
1714 while (true) {1716 while (true) {
1715 const cur = try cases.addOne();1717 const cur = try cases.addOne();
1716 skipSpace(self);1718 skipSpace(self);
1717 cur.values = try self.parseParameterGeneric([]*Inst, body_ctx);1719 cur.items = try self.parseParameterGeneric([]*Inst, body_ctx);
1718 skipSpace(self);1720 skipSpace(self);
1719 try requireEatBytes(self, "=>");1721 try requireEatBytes(self, "=>");
1720 cur.body = try self.parseBody(body_ctx);1722 cur.body = try self.parseBody(body_ctx);
src/zir_sema.zig+122-1
...@@ -135,7 +135,8 @@ pub fn analyzeInst(mod: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!...@@ -135,7 +135,8 @@ pub fn analyzeInst(mod: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!
135 .slice => return analyzeInstSlice(mod, scope, old_inst.castTag(.slice).?),135 .slice => return analyzeInstSlice(mod, scope, old_inst.castTag(.slice).?),
136 .slice_start => return analyzeInstSliceStart(mod, scope, old_inst.castTag(.slice_start).?),136 .slice_start => return analyzeInstSliceStart(mod, scope, old_inst.castTag(.slice_start).?),
137 .import => return analyzeInstImport(mod, scope, old_inst.castTag(.import).?),137 .import => return analyzeInstImport(mod, scope, old_inst.castTag(.import).?),
138 .@"switch", .switch_range => @panic("TODO switch sema"),138 .@"switch" => return analyzeInstSwitch(mod, scope, old_inst.castTag(.@"switch").?),
139 .switch_range => return analyzeInstSwitchRange(mod, scope, old_inst.castTag(.switch_range).?),
139 }140 }
140}141}
141142
...@@ -1205,6 +1206,126 @@ fn analyzeInstSliceStart(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) Inn...@@ -1205,6 +1206,126 @@ fn analyzeInstSliceStart(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) Inn
1205 return mod.analyzeSlice(scope, inst.base.src, array_ptr, start, null, null);1206 return mod.analyzeSlice(scope, inst.base.src, array_ptr, start, null, null);
1206}1207}
12071208
1209fn analyzeInstSwitchRange(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*Inst {
1210 const start = try resolveInst(mod, scope, inst.positionals.lhs);
1211 const end = try resolveInst(mod, scope, inst.positionals.rhs);
1212
1213 switch (start.ty.zigTypeTag()) {
1214 .Int, .ComptimeInt, .Float, .ComptimeFloat => {},
1215 else => return mod.constVoid(scope, inst.base.src),
1216 }
1217 switch (end.ty.zigTypeTag()) {
1218 .Int, .ComptimeInt, .Float, .ComptimeFloat => {},
1219 else => return mod.constVoid(scope, inst.base.src),
1220 }
1221 if (start.value()) |start_val| {
1222 if (end.value()) |end_val| {
1223 if (start_val.compare(.gte, end_val)) {
1224 return mod.fail(scope, inst.base.src, "range start value is greater than the end value", .{});
1225 }
1226 }
1227 }
1228 return mod.constVoid(scope, inst.base.src);
1229}
1230
1231fn analyzeInstSwitch(mod: *Module, scope: *Scope, inst: *zir.Inst.Switch) InnerError!*Inst {
1232 const target_ptr = try resolveInst(mod, scope, inst.positionals.target_ptr);
1233 const target = try mod.analyzeDeref(scope, inst.base.src, target_ptr, inst.positionals.target_ptr.src);
1234 try validateSwitch(mod, scope, target, inst);
1235
1236 return mod.fail(scope, inst.base.src, "TODO analyzeInstSwitch", .{});
1237}
1238
1239fn validateSwitch(mod: *Module, scope: *Scope, target: *Inst, inst: *zir.Inst.Switch) InnerError!void {
1240 // validate usage of '_' prongs
1241 if (inst.kw_args.special_case == .underscore and target.ty.zigTypeTag() != .Enum) {
1242 return mod.fail(scope, inst.base.src, "'_' prong only allowed when switching on non-exhaustive enums", .{});
1243 // TODO notes "'_' prong here" inst.positionals.cases[last].src
1244 }
1245
1246 // check that target type supports ranges
1247 if (inst.kw_args.support_range) |some| {
1248 switch (target.ty.zigTypeTag()) {
1249 .Int, .ComptimeInt, .Float, .ComptimeFloat => {},
1250 else => {
1251 return mod.fail(scope, target.src, "ranges not allowed when switching on type {}", .{target.ty});
1252 // TODO notes "range used here" some.src
1253 },
1254 }
1255 }
1256
1257 // validate for duplicate items/missing else prong
1258 switch (target.ty.zigTypeTag()) {
1259 .Int, .ComptimeInt => return mod.fail(scope, inst.base.src, "TODO validateSwitch .Int, .ComptimeInt", .{}),
1260 .Float, .ComptimeFloat => return mod.fail(scope, inst.base.src, "TODO validateSwitch .Float, .ComptimeFloat", .{}),
1261 .Enum => return mod.fail(scope, inst.base.src, "TODO validateSwitch .Enum", .{}),
1262 .ErrorSet => return mod.fail(scope, inst.base.src, "TODO validateSwitch .ErrorSet", .{}),
1263 .Union => return mod.fail(scope, inst.base.src, "TODO validateSwitch .Union", .{}),
1264 .Bool => {
1265 var true_count: u8 = 0;
1266 var false_count: u8 = 0;
1267 for (inst.positionals.cases) |case| {
1268 for (case.items) |item| {
1269 const resolved = try resolveInst(mod, scope, item);
1270 const casted = try mod.coerce(scope, Type.initTag(.bool), resolved);
1271 if ((try mod.resolveConstValue(scope, casted)).toBool()) {
1272 true_count += 1;
1273 } else {
1274 false_count += 1;
1275 }
1276
1277 if (true_count > 1 or false_count > 1) {
1278 return mod.fail(scope, item.src, "duplicate switch value", .{});
1279 }
1280 }
1281 }
1282 if ((true_count == 0 or false_count == 0) and inst.kw_args.special_case != .@"else") {
1283 return mod.fail(scope, inst.base.src, "switch must handle all possibilities", .{});
1284 }
1285 if ((true_count == 1 and false_count == 1) and inst.kw_args.special_case == .@"else") {
1286 return mod.fail(scope, inst.base.src, "unreachable else prong, all cases already handled", .{});
1287 }
1288 },
1289 .EnumLiteral, .Void, .Fn, .Pointer, .Type => {
1290 if (inst.kw_args.special_case != .@"else") {
1291 return mod.fail(scope, inst.base.src, "else prong required when switching on type '{}'", .{target.ty});
1292 }
1293
1294 var seen_values = std.HashMap(Value, usize, Value.hash, Value.eql, std.hash_map.DefaultMaxLoadPercentage).init(mod.gpa);
1295 defer seen_values.deinit();
1296
1297 for (inst.positionals.cases) |case| {
1298 for (case.items) |item| {
1299 const resolved = try resolveInst(mod, scope, item);
1300 const casted = try mod.coerce(scope, target.ty, resolved);
1301 const val = try mod.resolveConstValue(scope, casted);
1302
1303 if (try seen_values.fetchPut(val, item.src)) |prev| {
1304 return mod.fail(scope, item.src, "duplicate switch value", .{});
1305 // TODO notes "previous value here" prev.value
1306 }
1307 }
1308 }
1309 },
1310
1311 .ErrorUnion,
1312 .NoReturn,
1313 .Array,
1314 .Struct,
1315 .Undefined,
1316 .Null,
1317 .Optional,
1318 .BoundFn,
1319 .Opaque,
1320 .Vector,
1321 .Frame,
1322 .AnyFrame,
1323 => {
1324 return mod.fail(scope, target.src, "invalid switch target type '{}'", .{target.ty});
1325 },
1326 }
1327}
1328
1208fn analyzeInstImport(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst {1329fn analyzeInstImport(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst {
1209 const operand = try resolveConstString(mod, scope, inst.positionals.operand);1330 const operand = try resolveConstString(mod, scope, inst.positionals.operand);
12101331