| author | |
| committer | |
| log | 75acfcf0eaa306b3a8872e50cb735e1d5eb18c52 |
| tree | ff80058a12bedc0d9806a37047becd484f102faf |
| parent | 3ec5c9a3bcae09c01cbe4f0505e6ab03834bbb98 |
| signature |
4 files changed, 606 insertions(+), 5 deletions(-)
src/astgen.zig+312-1| ... | ... | @@ -309,7 +309,7 @@ pub fn expr(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node) InnerEr |
| 309 | 309 | .Catch => return catchExpr(mod, scope, rl, node.castTag(.Catch).?), |
| 310 | 310 | .Comptime => return comptimeKeyword(mod, scope, rl, node.castTag(.Comptime).?), |
| 311 | 311 | .OrElse => return orelseExpr(mod, scope, rl, node.castTag(.OrElse).?), |
| 312 | .Switch => return mod.failNode(scope, node, "TODO implement astgen.expr for .Switch", .{}), | |
| 312 | .Switch => return switchExpr(mod, scope, rl, node.castTag(.Switch).?), | |
| 313 | 313 | .ContainerDecl => return containerDecl(mod, scope, rl, node.castTag(.ContainerDecl).?), |
| 314 | 314 | |
| 315 | 315 | .Defer => return mod.failNode(scope, node, "TODO implement astgen.expr for .Defer", .{}), |
| ... | ... | @@ -2246,6 +2246,317 @@ fn forExpr( |
| 2246 | 2246 | ); |
| 2247 | 2247 | } |
| 2248 | 2248 | |
| 2249 | fn switchCaseUsesRef(node: *ast.Node.Switch) bool { | |
| 2250 | for (node.cases()) |uncasted_case| { | |
| 2251 | const case = uncasted_case.castTag(.SwitchCase).?; | |
| 2252 | const uncasted_payload = case.payload orelse continue; | |
| 2253 | const payload = uncasted_payload.castTag(.PointerPayload).?; | |
| 2254 | if (payload.ptr_token) |_| return true; | |
| 2255 | } | |
| 2256 | return false; | |
| 2257 | } | |
| 2258 | ||
| 2259 | fn getRangeNode(node: *ast.Node) ?*ast.Node.SimpleInfixOp { | |
| 2260 | var cur = node; | |
| 2261 | while (true) { | |
| 2262 | switch (cur.tag) { | |
| 2263 | .Range => return @fieldParentPtr(ast.Node.SimpleInfixOp, "base", cur), | |
| 2264 | .GroupedExpression => cur = @fieldParentPtr(ast.Node.GroupedExpression, "base", cur).expr, | |
| 2265 | else => return null, | |
| 2266 | } | |
| 2267 | } | |
| 2268 | } | |
| 2269 | ||
| 2270 | fn switchExpr(mod: *Module, scope: *Scope, rl: ResultLoc, switch_node: *ast.Node.Switch) InnerError!*zir.Inst { | |
| 2271 | const tree = scope.tree(); | |
| 2272 | const switch_src = tree.token_locs[switch_node.switch_token].start; | |
| 2273 | const use_ref = switchCaseUsesRef(switch_node); | |
| 2274 | ||
| 2275 | var block_scope: Scope.GenZIR = .{ | |
| 2276 | .parent = scope, | |
| 2277 | .decl = scope.ownerDecl().?, | |
| 2278 | .arena = scope.arena(), | |
| 2279 | .force_comptime = scope.isComptime(), | |
| 2280 | .instructions = .{}, | |
| 2281 | }; | |
| 2282 | setBlockResultLoc(&block_scope, rl); | |
| 2283 | defer block_scope.instructions.deinit(mod.gpa); | |
| 2284 | ||
| 2285 | var items = std.ArrayList(*zir.Inst).init(mod.gpa); | |
| 2286 | defer items.deinit(); | |
| 2287 | ||
| 2288 | // first we gather all the switch items and check else/'_' prongs | |
| 2289 | var else_src: ?usize = null; | |
| 2290 | var underscore_src: ?usize = null; | |
| 2291 | var first_range: ?*zir.Inst = null; | |
| 2292 | var simple_case_count: usize = 0; | |
| 2293 | for (switch_node.cases()) |uncasted_case| { | |
| 2294 | const case = uncasted_case.castTag(.SwitchCase).?; | |
| 2295 | const case_src = tree.token_locs[case.firstToken()].start; | |
| 2296 | assert(case.items_len != 0); | |
| 2297 | ||
| 2298 | // Check for else/_ prong, those are handled last. | |
| 2299 | if (case.items_len == 1 and case.items()[0].tag == .SwitchElse) { | |
| 2300 | if (else_src) |src| { | |
| 2301 | const msg = msg: { | |
| 2302 | const msg = try mod.errMsg( | |
| 2303 | scope, | |
| 2304 | case_src, | |
| 2305 | "multiple else prongs in switch expression", | |
| 2306 | .{}, | |
| 2307 | ); | |
| 2308 | errdefer msg.destroy(mod.gpa); | |
| 2309 | try mod.errNote(scope, src, msg, "previous else prong is here", .{}); | |
| 2310 | break :msg msg; | |
| 2311 | }; | |
| 2312 | return mod.failWithOwnedErrorMsg(scope, msg); | |
| 2313 | } | |
| 2314 | else_src = case_src; | |
| 2315 | continue; | |
| 2316 | } else if (case.items_len == 1 and case.items()[0].tag == .Identifier and | |
| 2317 | mem.eql(u8, tree.tokenSlice(case.items()[0].firstToken()), "_")) | |
| 2318 | { | |
| 2319 | if (underscore_src) |src| { | |
| 2320 | const msg = msg: { | |
| 2321 | const msg = try mod.errMsg( | |
| 2322 | scope, | |
| 2323 | case_src, | |
| 2324 | "multiple '_' prongs in switch expression", | |
| 2325 | .{}, | |
| 2326 | ); | |
| 2327 | errdefer msg.destroy(mod.gpa); | |
| 2328 | try mod.errNote(scope, src, msg, "previous '_' prong is here", .{}); | |
| 2329 | break :msg msg; | |
| 2330 | }; | |
| 2331 | return mod.failWithOwnedErrorMsg(scope, msg); | |
| 2332 | } | |
| 2333 | underscore_src = case_src; | |
| 2334 | continue; | |
| 2335 | } | |
| 2336 | ||
| 2337 | if (else_src) |some_else| { | |
| 2338 | if (underscore_src) |some_underscore| { | |
| 2339 | const msg = msg: { | |
| 2340 | const msg = try mod.errMsg( | |
| 2341 | scope, | |
| 2342 | switch_src, | |
| 2343 | "else and '_' prong in switch expression", | |
| 2344 | .{}, | |
| 2345 | ); | |
| 2346 | errdefer msg.destroy(mod.gpa); | |
| 2347 | try mod.errNote(scope, some_else, msg, "else prong is here", .{}); | |
| 2348 | try mod.errNote(scope, some_underscore, msg, "'_' prong is here", .{}); | |
| 2349 | break :msg msg; | |
| 2350 | }; | |
| 2351 | return mod.failWithOwnedErrorMsg(scope, msg); | |
| 2352 | } | |
| 2353 | } | |
| 2354 | ||
| 2355 | if (case.items_len == 1 and getRangeNode(case.items()[0]) == null) simple_case_count += 1; | |
| 2356 | ||
| 2357 | // generate all the switch items as comptime expressions | |
| 2358 | for (case.items()) |item| { | |
| 2359 | if (getRangeNode(item)) |range| { | |
| 2360 | const start = try comptimeExpr(mod, &block_scope.base, .none, range.lhs); | |
| 2361 | const end = try comptimeExpr(mod, &block_scope.base, .none, range.rhs); | |
| 2362 | const range_src = tree.token_locs[range.op_token].start; | |
| 2363 | const range_inst = try addZIRBinOp(mod, &block_scope.base, range_src, .switch_range, start, end); | |
| 2364 | try items.append(range_inst); | |
| 2365 | } else { | |
| 2366 | const item_inst = try comptimeExpr(mod, &block_scope.base, .none, item); | |
| 2367 | try items.append(item_inst); | |
| 2368 | } | |
| 2369 | } | |
| 2370 | } | |
| 2371 | ||
| 2372 | var special_prong: zir.Inst.SwitchBr.SpecialProng = .none; | |
| 2373 | if (else_src != null) special_prong = .@"else"; | |
| 2374 | if (underscore_src != null) special_prong = .underscore; | |
| 2375 | var cases = try block_scope.arena.alloc(zir.Inst.SwitchBr.Case, simple_case_count); | |
| 2376 | ||
| 2377 | const target_ptr = if (use_ref) try expr(mod, &block_scope.base, .ref, switch_node.expr) else null; | |
| 2378 | const target = if (target_ptr) |some| | |
| 2379 | try addZIRUnOp(mod, &block_scope.base, some.src, .deref, some) | |
| 2380 | else | |
| 2381 | try expr(mod, &block_scope.base, .none, switch_node.expr); | |
| 2382 | const switch_inst = try addZIRInst(mod, &block_scope.base, switch_src, zir.Inst.SwitchBr, .{ | |
| 2383 | .target = target, | |
| 2384 | .cases = cases, | |
| 2385 | .items = try block_scope.arena.dupe(*zir.Inst, items.items), | |
| 2386 | .else_body = undefined, // populated below | |
| 2387 | }, .{ | |
| 2388 | .range = first_range, | |
| 2389 | .special_prong = special_prong, | |
| 2390 | }); | |
| 2391 | ||
| 2392 | const block = try addZIRInstBlock(mod, scope, switch_src, .block, .{ | |
| 2393 | .instructions = try block_scope.arena.dupe(*zir.Inst, block_scope.instructions.items), | |
| 2394 | }); | |
| 2395 | ||
| 2396 | var case_scope: Scope.GenZIR = .{ | |
| 2397 | .parent = scope, | |
| 2398 | .decl = block_scope.decl, | |
| 2399 | .arena = block_scope.arena, | |
| 2400 | .force_comptime = block_scope.force_comptime, | |
| 2401 | .instructions = .{}, | |
| 2402 | }; | |
| 2403 | defer case_scope.instructions.deinit(mod.gpa); | |
| 2404 | ||
| 2405 | var else_scope: Scope.GenZIR = .{ | |
| 2406 | .parent = scope, | |
| 2407 | .decl = case_scope.decl, | |
| 2408 | .arena = case_scope.arena, | |
| 2409 | .force_comptime = case_scope.force_comptime, | |
| 2410 | .instructions = .{}, | |
| 2411 | }; | |
| 2412 | defer else_scope.instructions.deinit(mod.gpa); | |
| 2413 | ||
| 2414 | // Now generate all but the special cases | |
| 2415 | var special_case: ?*ast.Node.SwitchCase = null; | |
| 2416 | var items_index: usize = 0; | |
| 2417 | var case_index: usize = 0; | |
| 2418 | for (switch_node.cases()) |uncasted_case| { | |
| 2419 | const case = uncasted_case.castTag(.SwitchCase).?; | |
| 2420 | const case_src = tree.token_locs[case.firstToken()].start; | |
| 2421 | // reset without freeing to reduce allocations. | |
| 2422 | case_scope.instructions.items.len = 0; | |
| 2423 | ||
| 2424 | // Check for else/_ prong, those are handled last. | |
| 2425 | if (case.items_len == 1 and case.items()[0].tag == .SwitchElse) { | |
| 2426 | special_case = case; | |
| 2427 | continue; | |
| 2428 | } else if (case.items_len == 1 and case.items()[0].tag == .Identifier and | |
| 2429 | mem.eql(u8, tree.tokenSlice(case.items()[0].firstToken()), "_")) | |
| 2430 | { | |
| 2431 | special_case = case; | |
| 2432 | continue; | |
| 2433 | } | |
| 2434 | ||
| 2435 | // If this is a simple one item prong then it is handled by the switchbr. | |
| 2436 | if (case.items_len == 1 and getRangeNode(case.items()[0]) == null) { | |
| 2437 | const item = items.items[items_index]; | |
| 2438 | items_index += 1; | |
| 2439 | try switchCaseExpr(mod, &case_scope.base, block_scope.break_result_loc, block, case, target, target_ptr); | |
| 2440 | ||
| 2441 | cases[case_index] = .{ | |
| 2442 | .item = item, | |
| 2443 | .body = .{ .instructions = try scope.arena().dupe(*zir.Inst, case_scope.instructions.items) }, | |
| 2444 | }; | |
| 2445 | case_index += 1; | |
| 2446 | continue; | |
| 2447 | } | |
| 2448 | ||
| 2449 | // TODO if the case has few items and no ranges it might be better | |
| 2450 | // to just handle them as switch prongs. | |
| 2451 | ||
| 2452 | // Check if the target matches any of the items. | |
| 2453 | // 1, 2, 3..6 will result in | |
| 2454 | // target == 1 or target == 2 or (target >= 3 and target <= 6) | |
| 2455 | var any_ok: ?*zir.Inst = null; | |
| 2456 | for (case.items()) |item| { | |
| 2457 | if (getRangeNode(item)) |range| { | |
| 2458 | const range_src = tree.token_locs[range.op_token].start; | |
| 2459 | const range_inst = items.items[items_index].castTag(.switch_range).?; | |
| 2460 | items_index += 1; | |
| 2461 | ||
| 2462 | // target >= start and target <= end | |
| 2463 | const range_start_ok = try addZIRBinOp(mod, &else_scope.base, range_src, .cmp_gte, target, range_inst.positionals.lhs); | |
| 2464 | const range_end_ok = try addZIRBinOp(mod, &else_scope.base, range_src, .cmp_lte, target, range_inst.positionals.rhs); | |
| 2465 | const range_ok = try addZIRBinOp(mod, &else_scope.base, range_src, .bool_and, range_start_ok, range_end_ok); | |
| 2466 | ||
| 2467 | if (any_ok) |some| { | |
| 2468 | any_ok = try addZIRBinOp(mod, &else_scope.base, range_src, .bool_or, some, range_ok); | |
| 2469 | } else { | |
| 2470 | any_ok = range_ok; | |
| 2471 | } | |
| 2472 | continue; | |
| 2473 | } | |
| 2474 | ||
| 2475 | const item_inst = items.items[items_index]; | |
| 2476 | items_index += 1; | |
| 2477 | const cpm_ok = try addZIRBinOp(mod, &else_scope.base, item_inst.src, .cmp_eq, target, item_inst); | |
| 2478 | ||
| 2479 | if (any_ok) |some| { | |
| 2480 | any_ok = try addZIRBinOp(mod, &else_scope.base, item_inst.src, .bool_or, some, cpm_ok); | |
| 2481 | } else { | |
| 2482 | any_ok = cpm_ok; | |
| 2483 | } | |
| 2484 | } | |
| 2485 | ||
| 2486 | const condbr = try addZIRInstSpecial(mod, &case_scope.base, case_src, zir.Inst.CondBr, .{ | |
| 2487 | .condition = any_ok.?, | |
| 2488 | .then_body = undefined, // populated below | |
| 2489 | .else_body = undefined, // populated below | |
| 2490 | }, .{}); | |
| 2491 | const cond_block = try addZIRInstBlock(mod, &else_scope.base, case_src, .block, .{ | |
| 2492 | .instructions = try scope.arena().dupe(*zir.Inst, case_scope.instructions.items), | |
| 2493 | }); | |
| 2494 | ||
| 2495 | // reset cond_scope for then_body | |
| 2496 | case_scope.instructions.items.len = 0; | |
| 2497 | try switchCaseExpr(mod, &case_scope.base, block_scope.break_result_loc, block, case, target, target_ptr); | |
| 2498 | condbr.positionals.then_body = .{ | |
| 2499 | .instructions = try scope.arena().dupe(*zir.Inst, case_scope.instructions.items), | |
| 2500 | }; | |
| 2501 | ||
| 2502 | // reset cond_scope for else_body | |
| 2503 | case_scope.instructions.items.len = 0; | |
| 2504 | _ = try addZIRInst(mod, &case_scope.base, case_src, zir.Inst.BreakVoid, .{ | |
| 2505 | .block = cond_block, | |
| 2506 | }, .{}); | |
| 2507 | condbr.positionals.else_body = .{ | |
| 2508 | .instructions = try scope.arena().dupe(*zir.Inst, case_scope.instructions.items), | |
| 2509 | }; | |
| 2510 | } | |
| 2511 | ||
| 2512 | // Finally generate else block or a break. | |
| 2513 | if (special_case) |case| { | |
| 2514 | try switchCaseExpr(mod, &else_scope.base, block_scope.break_result_loc, block, case, target, target_ptr); | |
| 2515 | } else { | |
| 2516 | // Not handling all possible cases is a compile error. | |
| 2517 | _ = try addZIRNoOp(mod, &else_scope.base, switch_src, .unreachable_unsafe); | |
| 2518 | } | |
| 2519 | switch_inst.castTag(.switchbr).?.positionals.else_body = .{ | |
| 2520 | .instructions = try block_scope.arena.dupe(*zir.Inst, else_scope.instructions.items), | |
| 2521 | }; | |
| 2522 | ||
| 2523 | return &block.base; | |
| 2524 | } | |
| 2525 | ||
| 2526 | fn switchCaseExpr( | |
| 2527 | mod: *Module, | |
| 2528 | scope: *Scope, | |
| 2529 | rl: ResultLoc, | |
| 2530 | block: *zir.Inst.Block, | |
| 2531 | case: *ast.Node.SwitchCase, | |
| 2532 | target: *zir.Inst, | |
| 2533 | target_ptr: ?*zir.Inst, | |
| 2534 | ) !void { | |
| 2535 | const tree = scope.tree(); | |
| 2536 | const case_src = tree.token_locs[case.firstToken()].start; | |
| 2537 | const sub_scope = blk: { | |
| 2538 | const uncasted_payload = case.payload orelse break :blk scope; | |
| 2539 | const payload = uncasted_payload.castTag(.PointerPayload).?; | |
| 2540 | const is_ptr = payload.ptr_token != null; | |
| 2541 | const value_name = tree.tokenSlice(payload.value_symbol.firstToken()); | |
| 2542 | if (mem.eql(u8, value_name, "_")) { | |
| 2543 | if (is_ptr) { | |
| 2544 | return mod.failTok(scope, payload.ptr_token.?, "pointer modifier invalid on discard", .{}); | |
| 2545 | } | |
| 2546 | break :blk scope; | |
| 2547 | } | |
| 2548 | return mod.failNode(scope, payload.value_symbol, "TODO implement switch value payload", .{}); | |
| 2549 | }; | |
| 2550 | ||
| 2551 | const case_body = try expr(mod, sub_scope, rl, case.expr); | |
| 2552 | if (!case_body.tag.isNoReturn()) { | |
| 2553 | _ = try addZIRInst(mod, sub_scope, case_src, zir.Inst.Break, .{ | |
| 2554 | .block = block, | |
| 2555 | .operand = case_body, | |
| 2556 | }, .{}); | |
| 2557 | } | |
| 2558 | } | |
| 2559 | ||
| 2249 | 2560 | fn ret(mod: *Module, scope: *Scope, cfe: *ast.Node.ControlFlowExpression) InnerError!*zir.Inst { |
| 2250 | 2561 | const tree = scope.tree(); |
| 2251 | 2562 | const src = tree.token_locs[cfe.ltoken].start; |
src/codegen/c.zig+6-4| ... | ... | @@ -414,11 +414,13 @@ pub fn genBody(o: *Object, body: ir.Body) error{ AnalysisFail, OutOfMemory }!voi |
| 414 | 414 | .loop => try genLoop(o, inst.castTag(.loop).?), |
| 415 | 415 | .condbr => try genCondBr(o, inst.castTag(.condbr).?), |
| 416 | 416 | .br => try genBr(o, inst.castTag(.br).?), |
| 417 | .brvoid => try genBrVoid(o, inst.castTag(.brvoid).?.block), | |
| 417 | .br_void => try genBrVoid(o, inst.castTag(.br_void).?.block), | |
| 418 | 418 | .switchbr => try genSwitchBr(o, inst.castTag(.switchbr).?), |
| 419 | // booland and boolor are non-short-circuit operations | |
| 420 | .booland, .bitand => try genBinOp(o, inst.castTag(.booland).?, " & "), | |
| 421 | .boolor, .bitor => try genBinOp(o, inst.castTag(.boolor).?, " | "), | |
| 419 | // bool_and and bool_or are non-short-circuit operations | |
| 420 | .bool_and => try genBinOp(o, inst.castTag(.bool_and).?, " & "), | |
| 421 | .bool_or => try genBinOp(o, inst.castTag(.bool_or).?, " | "), | |
| 422 | .bit_and => try genBinOp(o, inst.castTag(.bit_and).?, " & "), | |
| 423 | .bit_or => try genBinOp(o, inst.castTag(.bit_or).?, " | "), | |
| 422 | 424 | .xor => try genBinOp(o, inst.castTag(.xor).?, " ^ "), |
| 423 | 425 | .not => try genUnOp(o, inst.castTag(.not).?, "!"), |
| 424 | 426 | else => |e| return o.dg.fail(o.dg.decl.src(), "TODO: C backend: implement codegen for {}", .{e}), |
src/zir.zig+60| ... | ... | @@ -338,6 +338,12 @@ pub const Inst = struct { |
| 338 | 338 | enum_type, |
| 339 | 339 | /// Does nothing; returns a void value. |
| 340 | 340 | void_value, |
| 341 | /// A switch expression. | |
| 342 | switchbr, | |
| 343 | /// A range in a switch case, `lhs...rhs`. | |
| 344 | /// Only checks that `lhs >= rhs` if they are ints, everything else is | |
| 345 | /// validated by the .switch instruction. | |
| 346 | switch_range, | |
| 341 | 347 | |
| 342 | 348 | pub fn Type(tag: Tag) type { |
| 343 | 349 | return switch (tag) { |
| ... | ... | @@ -435,6 +441,7 @@ pub const Inst = struct { |
| 435 | 441 | .error_union_type, |
| 436 | 442 | .merge_error_sets, |
| 437 | 443 | .slice_start, |
| 444 | .switch_range, | |
| 438 | 445 | => BinOp, |
| 439 | 446 | |
| 440 | 447 | .block, |
| ... | ... | @@ -478,6 +485,7 @@ pub const Inst = struct { |
| 478 | 485 | .enum_type => EnumType, |
| 479 | 486 | .union_type => UnionType, |
| 480 | 487 | .struct_type => StructType, |
| 488 | .switchbr => SwitchBr, | |
| 481 | 489 | }; |
| 482 | 490 | } |
| 483 | 491 | |
| ... | ... | @@ -605,6 +613,8 @@ pub const Inst = struct { |
| 605 | 613 | .union_type, |
| 606 | 614 | .struct_type, |
| 607 | 615 | .void_value, |
| 616 | .switch_range, | |
| 617 | .switchbr, | |
| 608 | 618 | => false, |
| 609 | 619 | |
| 610 | 620 | .@"break", |
| ... | ... | @@ -1171,6 +1181,36 @@ pub const Inst = struct { |
| 1171 | 1181 | none, |
| 1172 | 1182 | }; |
| 1173 | 1183 | }; |
| 1184 | ||
| 1185 | pub const SwitchBr = struct { | |
| 1186 | pub const base_tag = Tag.switchbr; | |
| 1187 | base: Inst, | |
| 1188 | ||
| 1189 | positionals: struct { | |
| 1190 | target: *Inst, | |
| 1191 | /// List of all individual items and ranges | |
| 1192 | items: []*Inst, | |
| 1193 | cases: []Case, | |
| 1194 | else_body: Body, | |
| 1195 | }, | |
| 1196 | kw_args: struct { | |
| 1197 | /// Pointer to first range if such exists. | |
| 1198 | range: ?*Inst = null, | |
| 1199 | special_prong: SpecialProng = .none, | |
| 1200 | }, | |
| 1201 | ||
| 1202 | // Not anonymous due to stage1 limitations | |
| 1203 | pub const SpecialProng = enum { | |
| 1204 | none, | |
| 1205 | @"else", | |
| 1206 | underscore, | |
| 1207 | }; | |
| 1208 | ||
| 1209 | pub const Case = struct { | |
| 1210 | item: *Inst, | |
| 1211 | body: Body, | |
| 1212 | }; | |
| 1213 | }; | |
| 1174 | 1214 | }; |
| 1175 | 1215 | |
| 1176 | 1216 | pub const ErrorMsg = struct { |
| ... | ... | @@ -1431,6 +1471,26 @@ const Writer = struct { |
| 1431 | 1471 | } |
| 1432 | 1472 | try stream.writeByte(']'); |
| 1433 | 1473 | }, |
| 1474 | []Inst.SwitchBr.Case => { | |
| 1475 | if (param.len == 0) { | |
| 1476 | return stream.writeAll("{}"); | |
| 1477 | } | |
| 1478 | try stream.writeAll("{\n"); | |
| 1479 | for (param) |*case, i| { | |
| 1480 | if (i != 0) { | |
| 1481 | try stream.writeAll(",\n"); | |
| 1482 | } | |
| 1483 | try stream.writeByteNTimes(' ', self.indent); | |
| 1484 | self.indent += 2; | |
| 1485 | try self.writeParamToStream(stream, &case.item); | |
| 1486 | try stream.writeAll(" => "); | |
| 1487 | try self.writeParamToStream(stream, &case.body); | |
| 1488 | self.indent -= 2; | |
| 1489 | } | |
| 1490 | try stream.writeByte('\n'); | |
| 1491 | try stream.writeByteNTimes(' ', self.indent - 2); | |
| 1492 | try stream.writeByte('}'); | |
| 1493 | }, | |
| 1434 | 1494 | else => |T| @compileError("unimplemented: rendering parameter of type " ++ @typeName(T)), |
| 1435 | 1495 | } |
| 1436 | 1496 | } |
src/zir_sema.zig+228| ... | ... | @@ -154,6 +154,8 @@ pub fn analyzeInst(mod: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError! |
| 154 | 154 | .bool_and => return zirBoolOp(mod, scope, old_inst.castTag(.bool_and).?), |
| 155 | 155 | .bool_or => return zirBoolOp(mod, scope, old_inst.castTag(.bool_or).?), |
| 156 | 156 | .void_value => return mod.constVoid(scope, old_inst.src), |
| 157 | .switchbr => return zirSwitchBr(mod, scope, old_inst.castTag(.switchbr).?), | |
| 158 | .switch_range => return zirSwitchRange(mod, scope, old_inst.castTag(.switch_range).?), | |
| 157 | 159 | |
| 158 | 160 | .container_field_named, |
| 159 | 161 | .container_field_typed, |
| ... | ... | @@ -1535,6 +1537,232 @@ fn zirSliceStart(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError! |
| 1535 | 1537 | return mod.analyzeSlice(scope, inst.base.src, array_ptr, start, null, null); |
| 1536 | 1538 | } |
| 1537 | 1539 | |
| 1540 | fn zirSwitchRange(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*Inst { | |
| 1541 | const tracy = trace(@src()); | |
| 1542 | defer tracy.end(); | |
| 1543 | const start = try resolveInst(mod, scope, inst.positionals.lhs); | |
| 1544 | const end = try resolveInst(mod, scope, inst.positionals.rhs); | |
| 1545 | ||
| 1546 | switch (start.ty.zigTypeTag()) { | |
| 1547 | .Int, .ComptimeInt => {}, | |
| 1548 | else => return mod.constVoid(scope, inst.base.src), | |
| 1549 | } | |
| 1550 | switch (end.ty.zigTypeTag()) { | |
| 1551 | .Int, .ComptimeInt => {}, | |
| 1552 | else => return mod.constVoid(scope, inst.base.src), | |
| 1553 | } | |
| 1554 | // .switch_range must be inside a comptime scope | |
| 1555 | const start_val = start.value().?; | |
| 1556 | const end_val = end.value().?; | |
| 1557 | if (start_val.compare(.gte, end_val)) { | |
| 1558 | return mod.fail(scope, inst.base.src, "range start value must be smaller than the end value", .{}); | |
| 1559 | } | |
| 1560 | return mod.constVoid(scope, inst.base.src); | |
| 1561 | } | |
| 1562 | ||
| 1563 | fn zirSwitchBr(mod: *Module, scope: *Scope, inst: *zir.Inst.SwitchBr) InnerError!*Inst { | |
| 1564 | const tracy = trace(@src()); | |
| 1565 | defer tracy.end(); | |
| 1566 | const target = try resolveInst(mod, scope, inst.positionals.target); | |
| 1567 | try validateSwitch(mod, scope, target, inst); | |
| 1568 | ||
| 1569 | if (try mod.resolveDefinedValue(scope, target)) |target_val| { | |
| 1570 | for (inst.positionals.cases) |case| { | |
| 1571 | const resolved = try resolveInst(mod, scope, case.item); | |
| 1572 | const casted = try mod.coerce(scope, target.ty, resolved); | |
| 1573 | const item = try mod.resolveConstValue(scope, casted); | |
| 1574 | ||
| 1575 | if (target_val.eql(item)) { | |
| 1576 | try analyzeBody(mod, scope.cast(Scope.Block).?, case.body); | |
| 1577 | return mod.constNoReturn(scope, inst.base.src); | |
| 1578 | } | |
| 1579 | } | |
| 1580 | try analyzeBody(mod, scope.cast(Scope.Block).?, inst.positionals.else_body); | |
| 1581 | return mod.constNoReturn(scope, inst.base.src); | |
| 1582 | } | |
| 1583 | ||
| 1584 | if (inst.positionals.cases.len == 0) { | |
| 1585 | // no cases just analyze else_branch | |
| 1586 | try analyzeBody(mod, scope.cast(Scope.Block).?, inst.positionals.else_body); | |
| 1587 | return mod.constNoReturn(scope, inst.base.src); | |
| 1588 | } | |
| 1589 | ||
| 1590 | const parent_block = try mod.requireRuntimeBlock(scope, inst.base.src); | |
| 1591 | const cases = try parent_block.arena.alloc(Inst.SwitchBr.Case, inst.positionals.cases.len); | |
| 1592 | ||
| 1593 | var case_block: Scope.Block = .{ | |
| 1594 | .parent = parent_block, | |
| 1595 | .inst_table = parent_block.inst_table, | |
| 1596 | .func = parent_block.func, | |
| 1597 | .owner_decl = parent_block.owner_decl, | |
| 1598 | .src_decl = parent_block.src_decl, | |
| 1599 | .instructions = .{}, | |
| 1600 | .arena = parent_block.arena, | |
| 1601 | .inlining = parent_block.inlining, | |
| 1602 | .is_comptime = parent_block.is_comptime, | |
| 1603 | .branch_quota = parent_block.branch_quota, | |
| 1604 | }; | |
| 1605 | defer case_block.instructions.deinit(mod.gpa); | |
| 1606 | ||
| 1607 | for (inst.positionals.cases) |case, i| { | |
| 1608 | // Reset without freeing. | |
| 1609 | case_block.instructions.items.len = 0; | |
| 1610 | ||
| 1611 | const resolved = try resolveInst(mod, scope, case.item); | |
| 1612 | const casted = try mod.coerce(scope, target.ty, resolved); | |
| 1613 | const item = try mod.resolveConstValue(scope, casted); | |
| 1614 | ||
| 1615 | try analyzeBody(mod, &case_block, case.body); | |
| 1616 | ||
| 1617 | cases[i] = .{ | |
| 1618 | .item = item, | |
| 1619 | .body = .{ .instructions = try parent_block.arena.dupe(*Inst, case_block.instructions.items) }, | |
| 1620 | }; | |
| 1621 | } | |
| 1622 | ||
| 1623 | case_block.instructions.items.len = 0; | |
| 1624 | try analyzeBody(mod, &case_block, inst.positionals.else_body); | |
| 1625 | ||
| 1626 | const else_body: ir.Body = .{ | |
| 1627 | .instructions = try parent_block.arena.dupe(*Inst, case_block.instructions.items), | |
| 1628 | }; | |
| 1629 | ||
| 1630 | return mod.addSwitchBr(parent_block, inst.base.src, target, cases, else_body); | |
| 1631 | } | |
| 1632 | ||
| 1633 | fn validateSwitch(mod: *Module, scope: *Scope, target: *Inst, inst: *zir.Inst.SwitchBr) InnerError!void { | |
| 1634 | // validate usage of '_' prongs | |
| 1635 | if (inst.kw_args.special_prong == .underscore and target.ty.zigTypeTag() != .Enum) { | |
| 1636 | return mod.fail(scope, inst.base.src, "'_' prong only allowed when switching on non-exhaustive enums", .{}); | |
| 1637 | // TODO notes "'_' prong here" inst.positionals.cases[last].src | |
| 1638 | } | |
| 1639 | ||
| 1640 | // check that target type supports ranges | |
| 1641 | if (inst.kw_args.range) |range_inst| { | |
| 1642 | switch (target.ty.zigTypeTag()) { | |
| 1643 | .Int, .ComptimeInt => {}, | |
| 1644 | else => { | |
| 1645 | return mod.fail(scope, target.src, "ranges not allowed when switching on type {}", .{target.ty}); | |
| 1646 | // TODO notes "range used here" range_inst.src | |
| 1647 | }, | |
| 1648 | } | |
| 1649 | } | |
| 1650 | ||
| 1651 | // validate for duplicate items/missing else prong | |
| 1652 | switch (target.ty.zigTypeTag()) { | |
| 1653 | .Enum => return mod.fail(scope, inst.base.src, "TODO validateSwitch .Enum", .{}), | |
| 1654 | .ErrorSet => return mod.fail(scope, inst.base.src, "TODO validateSwitch .ErrorSet", .{}), | |
| 1655 | .Union => return mod.fail(scope, inst.base.src, "TODO validateSwitch .Union", .{}), | |
| 1656 | .Int, .ComptimeInt => { | |
| 1657 | var range_set = @import("RangeSet.zig").init(mod.gpa); | |
| 1658 | defer range_set.deinit(); | |
| 1659 | ||
| 1660 | for (inst.positionals.items) |item| { | |
| 1661 | const maybe_src = if (item.castTag(.switch_range)) |range| blk: { | |
| 1662 | const start_resolved = try resolveInst(mod, scope, range.positionals.lhs); | |
| 1663 | const start_casted = try mod.coerce(scope, target.ty, start_resolved); | |
| 1664 | const end_resolved = try resolveInst(mod, scope, range.positionals.rhs); | |
| 1665 | const end_casted = try mod.coerce(scope, target.ty, end_resolved); | |
| 1666 | ||
| 1667 | break :blk try range_set.add( | |
| 1668 | try mod.resolveConstValue(scope, start_casted), | |
| 1669 | try mod.resolveConstValue(scope, end_casted), | |
| 1670 | item.src, | |
| 1671 | ); | |
| 1672 | } else blk: { | |
| 1673 | const resolved = try resolveInst(mod, scope, item); | |
| 1674 | const casted = try mod.coerce(scope, target.ty, resolved); | |
| 1675 | const value = try mod.resolveConstValue(scope, casted); | |
| 1676 | break :blk try range_set.add(value, value, item.src); | |
| 1677 | }; | |
| 1678 | ||
| 1679 | if (maybe_src) |previous_src| { | |
| 1680 | return mod.fail(scope, item.src, "duplicate switch value", .{}); | |
| 1681 | // TODO notes "previous value is here" previous_src | |
| 1682 | } | |
| 1683 | } | |
| 1684 | ||
| 1685 | if (target.ty.zigTypeTag() == .Int) { | |
| 1686 | var arena = std.heap.ArenaAllocator.init(mod.gpa); | |
| 1687 | defer arena.deinit(); | |
| 1688 | ||
| 1689 | const start = try target.ty.minInt(&arena, mod.getTarget()); | |
| 1690 | const end = try target.ty.maxInt(&arena, mod.getTarget()); | |
| 1691 | if (try range_set.spans(start, end)) { | |
| 1692 | if (inst.kw_args.special_prong == .@"else") { | |
| 1693 | return mod.fail(scope, inst.base.src, "unreachable else prong, all cases already handled", .{}); | |
| 1694 | } | |
| 1695 | return; | |
| 1696 | } | |
| 1697 | } | |
| 1698 | ||
| 1699 | if (inst.kw_args.special_prong != .@"else") { | |
| 1700 | return mod.fail(scope, inst.base.src, "switch must handle all possibilities", .{}); | |
| 1701 | } | |
| 1702 | }, | |
| 1703 | .Bool => { | |
| 1704 | var true_count: u8 = 0; | |
| 1705 | var false_count: u8 = 0; | |
| 1706 | for (inst.positionals.items) |item| { | |
| 1707 | const resolved = try resolveInst(mod, scope, item); | |
| 1708 | const casted = try mod.coerce(scope, Type.initTag(.bool), resolved); | |
| 1709 | if ((try mod.resolveConstValue(scope, casted)).toBool()) { | |
| 1710 | true_count += 1; | |
| 1711 | } else { | |
| 1712 | false_count += 1; | |
| 1713 | } | |
| 1714 | ||
| 1715 | if (true_count + false_count > 2) { | |
| 1716 | return mod.fail(scope, item.src, "duplicate switch value", .{}); | |
| 1717 | } | |
| 1718 | } | |
| 1719 | if ((true_count + false_count < 2) and inst.kw_args.special_prong != .@"else") { | |
| 1720 | return mod.fail(scope, inst.base.src, "switch must handle all possibilities", .{}); | |
| 1721 | } | |
| 1722 | if ((true_count + false_count == 2) and inst.kw_args.special_prong == .@"else") { | |
| 1723 | return mod.fail(scope, inst.base.src, "unreachable else prong, all cases already handled", .{}); | |
| 1724 | } | |
| 1725 | }, | |
| 1726 | .EnumLiteral, .Void, .Fn, .Pointer, .Type => { | |
| 1727 | if (inst.kw_args.special_prong != .@"else") { | |
| 1728 | return mod.fail(scope, inst.base.src, "else prong required when switching on type '{}'", .{target.ty}); | |
| 1729 | } | |
| 1730 | ||
| 1731 | var seen_values = std.HashMap(Value, usize, Value.hash, Value.eql, std.hash_map.DefaultMaxLoadPercentage).init(mod.gpa); | |
| 1732 | defer seen_values.deinit(); | |
| 1733 | ||
| 1734 | for (inst.positionals.items) |item| { | |
| 1735 | const resolved = try resolveInst(mod, scope, item); | |
| 1736 | const casted = try mod.coerce(scope, target.ty, resolved); | |
| 1737 | const val = try mod.resolveConstValue(scope, casted); | |
| 1738 | ||
| 1739 | if (try seen_values.fetchPut(val, item.src)) |prev| { | |
| 1740 | return mod.fail(scope, item.src, "duplicate switch value", .{}); | |
| 1741 | // TODO notes "previous value here" prev.value | |
| 1742 | } | |
| 1743 | } | |
| 1744 | }, | |
| 1745 | ||
| 1746 | .ErrorUnion, | |
| 1747 | .NoReturn, | |
| 1748 | .Array, | |
| 1749 | .Struct, | |
| 1750 | .Undefined, | |
| 1751 | .Null, | |
| 1752 | .Optional, | |
| 1753 | .BoundFn, | |
| 1754 | .Opaque, | |
| 1755 | .Vector, | |
| 1756 | .Frame, | |
| 1757 | .AnyFrame, | |
| 1758 | .ComptimeFloat, | |
| 1759 | .Float, | |
| 1760 | => { | |
| 1761 | return mod.fail(scope, target.src, "invalid switch target type '{}'", .{target.ty}); | |
| 1762 | }, | |
| 1763 | } | |
| 1764 | } | |
| 1765 | ||
| 1538 | 1766 | fn zirImport(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst { |
| 1539 | 1767 | const tracy = trace(@src()); |
| 1540 | 1768 | defer tracy.end(); |