diff --git a/src/AstGen.zig b/src/AstGen.zig index b77285139e..28413bb424 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -1415,6 +1415,7 @@ fn varDecl( const type_inst = try typeExpr(gz, &init_scope.base, var_decl.ast.type_node); opt_type_inst = type_inst; init_scope.rl_ptr = try init_scope.addUnNode(.alloc, type_inst, node); + init_scope.rl_ty_inst = type_inst; } else { const alloc = try init_scope.addUnNode(.alloc_inferred, undefined, node); resolve_inferred_alloc = alloc; @@ -1441,20 +1442,13 @@ fn varDecl( parent_zir.appendAssumeCapacity(src_inst); } assert(parent_zir.items.len == expected_len); - const casted_init = if (opt_type_inst != .none) - try gz.addPlNode(.as_node, var_decl.ast.type_node, zir.Inst.As{ - .dest_type = opt_type_inst, - .operand = init_inst, - }) - else - init_inst; const sub_scope = try block_arena.create(Scope.LocalVal); sub_scope.* = .{ .parent = scope, .gen_zir = gz, .name = ident_name, - .inst = casted_init, + .inst = init_inst, .src = name_src, }; return &sub_scope.base; @@ -3029,25 +3023,48 @@ fn switchExpr( // all prongs, except for prongs that ended with a noreturn instruction. // Elide all the `store_to_block_ptr` instructions. + // The break instructions need to have their operands coerced if the + // switch's result location is a `ty`. In this case we overwrite the + // `store_to_block_ptr` instruction with an `as` instruction and repurpose + // it as the break operand. + var extra_index: usize = 0; extra_index += 2; extra_index += @boolToInt(multi_cases_len != 0); - if (special_prong != .none) { + if (special_prong != .none) special_prong: { const body_len_index = extra_index; const body_len = scalar_cases_payload.items[extra_index]; extra_index += 1; + if (body_len < 2) { + extra_index += body_len; + astgen.extra.appendSliceAssumeCapacity(scalar_cases_payload.items[0..extra_index]); + break :special_prong; + } extra_index += body_len - 2; const store_inst = scalar_cases_payload.items[extra_index]; - if (zir_tags[store_inst] == .store_to_block_ptr) { - assert(zir_datas[store_inst].bin.lhs == block_scope.rl_ptr); + if (zir_tags[store_inst] != .store_to_block_ptr) { + extra_index += 2; + astgen.extra.appendSliceAssumeCapacity(scalar_cases_payload.items[0..extra_index]); + break :special_prong; + } + assert(zir_datas[store_inst].bin.lhs == block_scope.rl_ptr); + if (block_scope.rl_ty_inst != .none) { + extra_index += 1; + const break_inst = scalar_cases_payload.items[extra_index]; + extra_index += 1; + astgen.extra.appendSliceAssumeCapacity(scalar_cases_payload.items[0..extra_index]); + zir_tags[store_inst] = .as; + zir_datas[store_inst].bin = .{ + .lhs = block_scope.rl_ty_inst, + .rhs = zir_datas[break_inst].@"break".operand, + }; + zir_datas[break_inst].@"break".operand = astgen.indexToRef(store_inst); + } else { scalar_cases_payload.items[body_len_index] -= 1; astgen.extra.appendSliceAssumeCapacity(scalar_cases_payload.items[0..extra_index]); extra_index += 1; astgen.extra.appendAssumeCapacity(scalar_cases_payload.items[extra_index]); extra_index += 1; - } else { - extra_index += 2; - astgen.extra.appendSliceAssumeCapacity(scalar_cases_payload.items[0..extra_index]); } } else { astgen.extra.appendSliceAssumeCapacity(scalar_cases_payload.items[0..extra_index]); @@ -3066,16 +3083,29 @@ fn switchExpr( } extra_index += body_len - 2; const store_inst = scalar_cases_payload.items[extra_index]; - if (zir_tags[store_inst] == .store_to_block_ptr) { + if (zir_tags[store_inst] != .store_to_block_ptr) { + extra_index += 2; + astgen.extra.appendSliceAssumeCapacity(scalar_cases_payload.items[start_index..extra_index]); + continue; + } + if (block_scope.rl_ty_inst != .none) { + extra_index += 1; + const break_inst = scalar_cases_payload.items[extra_index]; + extra_index += 1; + astgen.extra.appendSliceAssumeCapacity(scalar_cases_payload.items[start_index..extra_index]); + zir_tags[store_inst] = .as; + zir_datas[store_inst].bin = .{ + .lhs = block_scope.rl_ty_inst, + .rhs = zir_datas[break_inst].@"break".operand, + }; + zir_datas[break_inst].@"break".operand = astgen.indexToRef(store_inst); + } else { assert(zir_datas[store_inst].bin.lhs == block_scope.rl_ptr); scalar_cases_payload.items[body_len_index] -= 1; astgen.extra.appendSliceAssumeCapacity(scalar_cases_payload.items[start_index..extra_index]); extra_index += 1; astgen.extra.appendAssumeCapacity(scalar_cases_payload.items[extra_index]); extra_index += 1; - } else { - extra_index += 2; - astgen.extra.appendSliceAssumeCapacity(scalar_cases_payload.items[start_index..extra_index]); } } extra_index = 0; @@ -3098,16 +3128,29 @@ fn switchExpr( } extra_index += body_len - 2; const store_inst = multi_cases_payload.items[extra_index]; - if (zir_tags[store_inst] == .store_to_block_ptr) { + if (zir_tags[store_inst] != .store_to_block_ptr) { + extra_index += 2; + astgen.extra.appendSliceAssumeCapacity(multi_cases_payload.items[start_index..extra_index]); + continue; + } + if (block_scope.rl_ty_inst != .none) { + extra_index += 1; + const break_inst = multi_cases_payload.items[extra_index]; + extra_index += 1; + astgen.extra.appendSliceAssumeCapacity(multi_cases_payload.items[start_index..extra_index]); + zir_tags[store_inst] = .as; + zir_datas[store_inst].bin = .{ + .lhs = block_scope.rl_ty_inst, + .rhs = zir_datas[break_inst].@"break".operand, + }; + zir_datas[break_inst].@"break".operand = astgen.indexToRef(store_inst); + } else { assert(zir_datas[store_inst].bin.lhs == block_scope.rl_ptr); multi_cases_payload.items[body_len_index] -= 1; astgen.extra.appendSliceAssumeCapacity(multi_cases_payload.items[start_index..extra_index]); extra_index += 1; astgen.extra.appendAssumeCapacity(multi_cases_payload.items[extra_index]); extra_index += 1; - } else { - extra_index += 2; - astgen.extra.appendSliceAssumeCapacity(multi_cases_payload.items[start_index..extra_index]); } } diff --git a/src/Module.zig b/src/Module.zig index 91790aa1b6..bceaf43425 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -942,6 +942,8 @@ pub const Scope = struct { break_result_loc: AstGen.ResultLoc = undefined, /// When a block has a pointer result location, here it is. rl_ptr: zir.Inst.Ref = .none, + /// When a block has a type result location, here it is. + rl_ty_inst: zir.Inst.Ref = .none, /// Keeps track of how many branches of a block did not actually /// consume the result location. astgen uses this to figure out /// whether to rely on break instructions or writing to the result @@ -1001,7 +1003,11 @@ pub const Scope = struct { // we emit ZIR for the block break instructions to have the result values, // and then rvalue() on that to pass the value to the result location. switch (parent_rl) { - .discard, .none, .ty, .ptr, .ref => { + .ty => |ty_inst| { + gz.rl_ty_inst = ty_inst; + gz.break_result_loc = parent_rl; + }, + .discard, .none, .ptr, .ref => { gz.break_result_loc = parent_rl; }, @@ -1016,6 +1022,7 @@ pub const Scope = struct { }, .block_ptr => |parent_block_scope| { + gz.rl_ty_inst = parent_block_scope.rl_ty_inst; gz.rl_ptr = parent_block_scope.rl_ptr; gz.break_result_loc = .{ .block_ptr = gz }; }, diff --git a/src/Sema.zig b/src/Sema.zig index 5ea1836fbe..504edfe97b 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -4104,102 +4104,108 @@ fn coerce( } assert(inst.ty.zigTypeTag() != .Undefined); - // null to ?T - if (dest_type.zigTypeTag() == .Optional and inst.ty.zigTypeTag() == .Null) { - return sema.mod.constInst(sema.arena, inst_src, .{ .ty = dest_type, .val = Value.initTag(.null_value) }); - } - - // T to ?T - if (dest_type.zigTypeTag() == .Optional) { - var buf: Type.Payload.ElemType = undefined; - const child_type = dest_type.optionalChild(&buf); - if (child_type.eql(inst.ty)) { - return sema.wrapOptional(block, dest_type, inst); - } else if (try sema.coerceNum(block, child_type, inst)) |some| { - return sema.wrapOptional(block, dest_type, some); - } - } - // T to E!T or E to E!T if (dest_type.tag() == .error_union) { return try sema.wrapErrorUnion(block, dest_type, inst); } - // Coercions where the source is a single pointer to an array. - src_array_ptr: { - if (!inst.ty.isSinglePointer()) break :src_array_ptr; - const array_type = inst.ty.elemType(); - if (array_type.zigTypeTag() != .Array) break :src_array_ptr; - const array_elem_type = array_type.elemType(); - if (inst.ty.isConstPtr() and !dest_type.isConstPtr()) break :src_array_ptr; - if (inst.ty.isVolatilePtr() and !dest_type.isVolatilePtr()) break :src_array_ptr; - - const dst_elem_type = dest_type.elemType(); - switch (coerceInMemoryAllowed(dst_elem_type, array_elem_type)) { - .ok => {}, - .no_match => break :src_array_ptr, - } - - switch (dest_type.ptrSize()) { - .Slice => { - // *[N]T to []T - return sema.coerceArrayPtrToSlice(block, dest_type, inst); - }, - .C => { - // *[N]T to [*c]T - return sema.coerceArrayPtrToMany(block, dest_type, inst); - }, - .Many => { - // *[N]T to [*]T - // *[N:s]T to [*:s]T - const src_sentinel = array_type.sentinel(); - const dst_sentinel = dest_type.sentinel(); - if (src_sentinel == null and dst_sentinel == null) - return sema.coerceArrayPtrToMany(block, dest_type, inst); - - if (src_sentinel) |src_s| { - if (dst_sentinel) |dst_s| { - if (src_s.eql(dst_s)) { - return sema.coerceArrayPtrToMany(block, dest_type, inst); - } - } - } - }, - .One => {}, - } - } - // comptime known number to other number if (try sema.coerceNum(block, dest_type, inst)) |some| return some; const target = sema.mod.getTarget(); - // integer widening - if (inst.ty.zigTypeTag() == .Int and dest_type.zigTypeTag() == .Int) { - assert(inst.value() == null); // handled above + switch (dest_type.zigTypeTag()) { + .Optional => { + // null to ?T + if (inst.ty.zigTypeTag() == .Null) { + return sema.mod.constInst(sema.arena, inst_src, .{ .ty = dest_type, .val = Value.initTag(.null_value) }); + } - const src_info = inst.ty.intInfo(target); - const dst_info = dest_type.intInfo(target); - if ((src_info.signedness == dst_info.signedness and dst_info.bits >= src_info.bits) or - // small enough unsigned ints can get casted to large enough signed ints - (src_info.signedness == .signed and dst_info.signedness == .unsigned and dst_info.bits > src_info.bits)) - { - try sema.requireRuntimeBlock(block, inst_src); - return block.addUnOp(inst_src, dest_type, .intcast, inst); - } - } + // T to ?T + var buf: Type.Payload.ElemType = undefined; + const child_type = dest_type.optionalChild(&buf); + if (child_type.eql(inst.ty)) { + return sema.wrapOptional(block, dest_type, inst); + } else if (try sema.coerceNum(block, child_type, inst)) |some| { + return sema.wrapOptional(block, dest_type, some); + } + }, + .Pointer => { + // Coercions where the source is a single pointer to an array. + src_array_ptr: { + if (!inst.ty.isSinglePointer()) break :src_array_ptr; + const array_type = inst.ty.elemType(); + if (array_type.zigTypeTag() != .Array) break :src_array_ptr; + const array_elem_type = array_type.elemType(); + if (inst.ty.isConstPtr() and !dest_type.isConstPtr()) break :src_array_ptr; + if (inst.ty.isVolatilePtr() and !dest_type.isVolatilePtr()) break :src_array_ptr; - // float widening - if (inst.ty.zigTypeTag() == .Float and dest_type.zigTypeTag() == .Float) { - assert(inst.value() == null); // handled above + const dst_elem_type = dest_type.elemType(); + switch (coerceInMemoryAllowed(dst_elem_type, array_elem_type)) { + .ok => {}, + .no_match => break :src_array_ptr, + } - const src_bits = inst.ty.floatBits(target); - const dst_bits = dest_type.floatBits(target); - if (dst_bits >= src_bits) { - try sema.requireRuntimeBlock(block, inst_src); - return block.addUnOp(inst_src, dest_type, .floatcast, inst); - } + switch (dest_type.ptrSize()) { + .Slice => { + // *[N]T to []T + return sema.coerceArrayPtrToSlice(block, dest_type, inst); + }, + .C => { + // *[N]T to [*c]T + return sema.coerceArrayPtrToMany(block, dest_type, inst); + }, + .Many => { + // *[N]T to [*]T + // *[N:s]T to [*:s]T + const src_sentinel = array_type.sentinel(); + const dst_sentinel = dest_type.sentinel(); + if (src_sentinel == null and dst_sentinel == null) + return sema.coerceArrayPtrToMany(block, dest_type, inst); + + if (src_sentinel) |src_s| { + if (dst_sentinel) |dst_s| { + if (src_s.eql(dst_s)) { + return sema.coerceArrayPtrToMany(block, dest_type, inst); + } + } + } + }, + .One => {}, + } + } + }, + .Int => { + // integer widening + if (inst.ty.zigTypeTag() == .Int) { + assert(inst.value() == null); // handled above + + const dst_info = dest_type.intInfo(target); + const src_info = inst.ty.intInfo(target); + if ((src_info.signedness == dst_info.signedness and dst_info.bits >= src_info.bits) or + // small enough unsigned ints can get casted to large enough signed ints + (src_info.signedness == .signed and dst_info.signedness == .unsigned and dst_info.bits > src_info.bits)) + { + try sema.requireRuntimeBlock(block, inst_src); + return block.addUnOp(inst_src, dest_type, .intcast, inst); + } + } + }, + .Float => { + // float widening + if (inst.ty.zigTypeTag() == .Float) { + assert(inst.value() == null); // handled above + + const src_bits = inst.ty.floatBits(target); + const dst_bits = dest_type.floatBits(target); + if (dst_bits >= src_bits) { + try sema.requireRuntimeBlock(block, inst_src); + return block.addUnOp(inst_src, dest_type, .floatcast, inst); + } + } + }, + else => {}, } return sema.mod.fail(&block.base, inst_src, "expected {}, found {}", .{ dest_type, inst.ty }); diff --git a/test/stage2/cbe.zig b/test/stage2/cbe.zig index 991e0f54c6..b79f448e57 100644 --- a/test/stage2/cbe.zig +++ b/test/stage2/cbe.zig @@ -279,6 +279,72 @@ pub fn addCases(ctx: *TestContext) !void { \\ return a - 4; \\} , ""); + + // Switch expression missing else case. + case.addError( + \\export fn main() c_int { + \\ var cond: c_int = 0; + \\ const a: c_int = switch (cond) { + \\ 1 => 1, + \\ 2 => 2, + \\ 3 => 3, + \\ 4 => 4, + \\ }; + \\ return a - 4; + \\} + , &.{":3:22: error: switch must handle all possibilities"}); + + // Switch expression, has an unreachable prong. + case.addCompareOutput( + \\export fn main() c_int { + \\ var cond: c_int = 0; + \\ const a: c_int = switch (cond) { + \\ 1 => 1, + \\ 2 => 2, + \\ 99...300, 12 => 3, + \\ 0 => 4, + \\ 13 => unreachable, + \\ else => 5, + \\ }; + \\ return a - 4; + \\} + , ""); + + // Switch expression, has an unreachable prong and prongs write + // to result locations. + case.addCompareOutput( + \\export fn main() c_int { + \\ var cond: c_int = 0; + \\ var a: c_int = switch (cond) { + \\ 1 => 1, + \\ 2 => 2, + \\ 99...300, 12 => 3, + \\ 0 => 4, + \\ 13 => unreachable, + \\ else => 5, + \\ }; + \\ return a - 4; + \\} + , ""); + + // Switch expression has duplicate case value. + case.addError( + \\export fn main() c_int { + \\ var cond: c_int = 0; + \\ const a: c_int = switch (cond) { + \\ 1 => 1, + \\ 2 => 2, + \\ 96, 11...13, 97 => 3, + \\ 0 => 4, + \\ 90, 12 => 100, + \\ else => 5, + \\ }; + \\ return a - 4; + \\} + , &.{ + ":8:13: error: duplicate switch value", + ":6:15: note: previous value here", + }); } //{ // var case = ctx.exeFromCompiledC("optionals", .{});