Commit ff72b8a819

Veikka Tuominen <git@vexu.eu>
2022-02-26 11:38:25
stage2: evaluate TypeOf arguments in a separate scope
1 parent db82c1b
Changed files (6)
src/AstGen.zig
@@ -2159,6 +2159,7 @@ fn unusedResultExpr(gz: *GenZir, scope: *Scope, statement: Ast.Node.Index) Inner
             .negate,
             .negate_wrap,
             .typeof,
+            .typeof_builtin,
             .xor,
             .optional_type,
             .optional_payload_safe,
@@ -6875,29 +6876,54 @@ fn typeOf(
     scope: *Scope,
     rl: ResultLoc,
     node: Ast.Node.Index,
-    params: []const Ast.Node.Index,
+    args: []const Ast.Node.Index,
 ) InnerError!Zir.Inst.Ref {
-    if (params.len < 1) {
+    if (args.len < 1) {
         return gz.astgen.failNode(node, "expected at least 1 argument, found 0", .{});
     }
-    if (params.len == 1) {
-        const expr_result = try reachableExpr(gz, scope, .none, params[0], node);
-        const result = try gz.addUnNode(.typeof, expr_result, node);
-        return rvalue(gz, rl, result, node);
+    const gpa = gz.astgen.gpa;
+    if (args.len == 1) {
+        const typeof_inst = try gz.makeBlockInst(.typeof_builtin, node);
+
+        var typeof_scope = gz.makeSubBlock(scope);
+        typeof_scope.force_comptime = false;
+        defer typeof_scope.unstack();
+
+        const ty_expr = try reachableExpr(&typeof_scope, &typeof_scope.base, .none, args[0], node);
+        if (!gz.refIsNoReturn(ty_expr)) {
+            _ = try typeof_scope.addBreak(.break_inline, typeof_inst, ty_expr);
+        }
+        try typeof_scope.setBlockBody(typeof_inst);
+
+        // typeof_scope unstacked now, can add new instructions to gz
+        try gz.instructions.append(gpa, typeof_inst);
+        return rvalue(gz, rl, indexToRef(typeof_inst), node);
     }
+    const payload_size: u32 = std.meta.fields(Zir.Inst.TypeOfPeer).len;
+    const payload_index = try reserveExtra(gz.astgen, payload_size + args.len);
+    var args_index = payload_index + payload_size;
+
+    const typeof_inst = try gz.addExtendedMultiOpPayloadIndex(.typeof_peer, payload_index, args.len);
 
-    const payload_index = try addExtra(gz.astgen, Zir.Inst.NodeMultiOp{
+    var typeof_scope = gz.makeSubBlock(scope);
+    typeof_scope.force_comptime = false;
+
+    for (args) |arg, i| {
+        const param_ref = try reachableExpr(&typeof_scope, &typeof_scope.base, .none, arg, node);
+        gz.astgen.extra.items[args_index + i] = @enumToInt(param_ref);
+    }
+    _ = try typeof_scope.addBreak(.break_inline, refToIndex(typeof_inst).?, .void_value);
+
+    const body = typeof_scope.instructionsSlice();
+    gz.astgen.setExtra(payload_index, Zir.Inst.TypeOfPeer{
+        .body_len = @intCast(u32, body.len),
+        .body_index = @intCast(u32, gz.astgen.extra.items.len),
         .src_node = gz.nodeIndexToRelative(node),
     });
-    var extra_index = try reserveExtra(gz.astgen, params.len);
-    for (params) |param| {
-        const param_ref = try reachableExpr(gz, scope, .none, param, node);
-        gz.astgen.extra.items[extra_index] = @enumToInt(param_ref);
-        extra_index += 1;
-    }
+    try gz.astgen.extra.appendSlice(gpa, body);
+    typeof_scope.unstack();
 
-    const result = try gz.addExtendedMultiOpPayloadIndex(.typeof_peer, payload_index, params.len);
-    return rvalue(gz, rl, result, node);
+    return rvalue(gz, rl, typeof_inst, node);
 }
 
 fn builtinCall(
src/print_zir.zig
@@ -374,6 +374,7 @@ const Writer = struct {
             .validate_array_init,
             .validate_array_init_comptime,
             .c_import,
+            .typeof_builtin,
             => try self.writePlNodeBlock(stream, inst),
 
             .condbr,
@@ -458,9 +459,8 @@ const Writer = struct {
             .variable => try self.writeVarExtended(stream, extended),
             .alloc => try self.writeAllocExtended(stream, extended),
 
-            .compile_log,
-            .typeof_peer,
-            => try self.writeNodeMultiOp(stream, extended),
+            .compile_log => try self.writeNodeMultiOp(stream, extended),
+            .typeof_peer => try self.writeTypeofPeer(stream, extended),
 
             .add_with_overflow,
             .sub_with_overflow,
@@ -1966,6 +1966,19 @@ const Writer = struct {
         try self.writeSrc(stream, src);
     }
 
+    fn writeTypeofPeer(self: *Writer, stream: anytype, extended: Zir.Inst.Extended.InstData) !void {
+        const extra = self.code.extraData(Zir.Inst.TypeOfPeer, extended.operand);
+        const body = self.code.extra[extra.data.body_index..][0..extra.data.body_len];
+        try self.writeBracedBody(stream, body);
+        try stream.writeAll(",[");
+        const args = self.code.refSlice(extra.end, extended.small);
+        for (args) |arg, i| {
+            if (i != 0) try stream.writeAll(", ");
+            try self.writeInstRef(stream, arg);
+        }
+        try stream.writeAll("])");
+    }
+
     fn writeBoolBr(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void {
         const inst_data = self.code.instructions.items(.data)[inst].bool_br;
         const extra = self.code.extraData(Zir.Inst.Block, inst_data.payload_index);
src/Sema.zig
@@ -124,6 +124,7 @@ pub const Block = struct {
     runtime_index: u32 = 0,
 
     is_comptime: bool,
+    is_typeof: bool = false,
 
     /// when null, it is determined by build mode, changed by @setRuntimeSafety
     want_safety: ?bool = null,
@@ -181,6 +182,7 @@ pub const Block = struct {
             .label = null,
             .inlining = parent.inlining,
             .is_comptime = parent.is_comptime,
+            .is_typeof = parent.is_typeof,
             .runtime_cond = parent.runtime_cond,
             .runtime_loop = parent.runtime_loop,
             .runtime_index = parent.runtime_index,
@@ -682,6 +684,7 @@ fn analyzeBodyInner(
             .size_of                      => try sema.zirSizeOf(block, inst),
             .bit_size_of                  => try sema.zirBitSizeOf(block, inst),
             .typeof                       => try sema.zirTypeof(block, inst),
+            .typeof_builtin               => try sema.zirTypeofBuiltin(block, inst),
             .log2_int_type                => try sema.zirLog2IntType(block, inst),
             .typeof_log2_int_type         => try sema.zirTypeofLog2IntType(block, inst),
             .xor                          => try sema.zirBitwise(block, inst, .xor),
@@ -10574,6 +10577,29 @@ fn zirTypeof(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.
     return sema.addType(operand_ty);
 }
 
+fn zirTypeofBuiltin(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
+    const pl_node = sema.code.instructions.items(.data)[inst].pl_node;
+    const extra = sema.code.extraData(Zir.Inst.Block, pl_node.payload_index);
+    const body = sema.code.extra[extra.end..][0..extra.data.body_len];
+
+    var child_block: Block = .{
+        .parent = block,
+        .sema = sema,
+        .src_decl = block.src_decl,
+        .namespace = block.namespace,
+        .wip_capture_scope = block.wip_capture_scope,
+        .instructions = .{},
+        .inlining = block.inlining,
+        .is_comptime = false,
+        .is_typeof = true,
+    };
+    defer child_block.instructions.deinit(sema.gpa);
+
+    const operand = try sema.resolveBody(&child_block, body, inst);
+    const operand_ty = sema.typeOf(operand);
+    return sema.addType(operand_ty);
+}
+
 fn zirTypeofLog2IntType(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
     const inst_data = sema.code.instructions.items(.data)[inst].un_node;
     const src = inst_data.src();
@@ -10624,8 +10650,24 @@ fn zirTypeofPeer(
     const tracy = trace(@src());
     defer tracy.end();
 
-    const extra = sema.code.extraData(Zir.Inst.NodeMultiOp, extended.operand);
+    const extra = sema.code.extraData(Zir.Inst.TypeOfPeer, extended.operand);
     const src: LazySrcLoc = .{ .node_offset = extra.data.src_node };
+    const body = sema.code.extra[extra.data.body_index..][0..extra.data.body_len];
+
+    var child_block: Block = .{
+        .parent = block,
+        .sema = sema,
+        .src_decl = block.src_decl,
+        .namespace = block.namespace,
+        .wip_capture_scope = block.wip_capture_scope,
+        .instructions = .{},
+        .inlining = block.inlining,
+        .is_comptime = false,
+        .is_typeof = true,
+    };
+    defer child_block.instructions.deinit(sema.gpa);
+    _ = try sema.analyzeBody(&child_block, body);
+
     const args = sema.code.refSlice(extra.end, extended.small);
 
     const inst_list = try sema.gpa.alloc(Air.Inst.Ref, args.len);
src/Zir.zig
@@ -547,6 +547,9 @@ pub const Inst = struct {
         /// Returns the type of a value.
         /// Uses the `un_node` field.
         typeof,
+        /// Implements `@TypeOf` for one operand.
+        /// Uses the `pl_node` field.
+        typeof_builtin,
         /// Given a value, look at the type of it, which must be an integer type.
         /// Returns the integer type for the RHS of a shift operation.
         /// Uses the `un_node` field.
@@ -1067,6 +1070,7 @@ pub const Inst = struct {
                 .negate,
                 .negate_wrap,
                 .typeof,
+                .typeof_builtin,
                 .xor,
                 .optional_type,
                 .optional_payload_safe,
@@ -1429,6 +1433,7 @@ pub const Inst = struct {
                 .ptr_cast = .pl_node,
                 .truncate = .pl_node,
                 .align_cast = .pl_node,
+                .typeof_builtin = .pl_node,
 
                 .has_decl = .pl_node,
                 .has_field = .pl_node,
@@ -2391,6 +2396,12 @@ pub const Inst = struct {
         };
     };
 
+    pub const TypeOfPeer = struct {
+        src_node: i32,
+        body_len: u32,
+        body_index: u32,
+    };
+
     pub const BuiltinCall = struct {
         options: Ref,
         callee: Ref,
test/behavior/bugs/5474.zig
@@ -1,4 +1,5 @@
 const std = @import("std");
+const builtin = @import("builtin");
 
 // baseline (control) struct with array of scalar
 const Box0 = struct {
@@ -25,33 +26,36 @@ const Box2 = struct {
     };
 };
 
-fn doTest() !void {
-    // var
-    {
-        var box0: Box0 = .{ .items = undefined };
-        try std.testing.expect(@typeInfo(@TypeOf(box0.items[0..])).Pointer.is_const == false);
+fn mutable() !void {
+    var box0: Box0 = .{ .items = undefined };
+    try std.testing.expect(@typeInfo(@TypeOf(box0.items[0..])).Pointer.is_const == false);
 
-        var box1: Box1 = .{ .items = undefined };
-        try std.testing.expect(@typeInfo(@TypeOf(box1.items[0..])).Pointer.is_const == false);
+    var box1: Box1 = .{ .items = undefined };
+    try std.testing.expect(@typeInfo(@TypeOf(box1.items[0..])).Pointer.is_const == false);
 
-        var box2: Box2 = .{ .items = undefined };
-        try std.testing.expect(@typeInfo(@TypeOf(box2.items[0..])).Pointer.is_const == false);
-    }
+    var box2: Box2 = .{ .items = undefined };
+    try std.testing.expect(@typeInfo(@TypeOf(box2.items[0..])).Pointer.is_const == false);
+}
+
+fn constant() !void {
+    const box0: Box0 = .{ .items = undefined };
+    try std.testing.expect(@typeInfo(@TypeOf(box0.items[0..])).Pointer.is_const == true);
 
-    // const
-    {
-        const box0: Box0 = .{ .items = undefined };
-        try std.testing.expect(@typeInfo(@TypeOf(box0.items[0..])).Pointer.is_const == true);
+    const box1: Box1 = .{ .items = undefined };
+    try std.testing.expect(@typeInfo(@TypeOf(box1.items[0..])).Pointer.is_const == true);
 
-        const box1: Box1 = .{ .items = undefined };
-        try std.testing.expect(@typeInfo(@TypeOf(box1.items[0..])).Pointer.is_const == true);
+    const box2: Box2 = .{ .items = undefined };
+    try std.testing.expect(@typeInfo(@TypeOf(box2.items[0..])).Pointer.is_const == true);
+}
 
-        const box2: Box2 = .{ .items = undefined };
-        try std.testing.expect(@typeInfo(@TypeOf(box2.items[0..])).Pointer.is_const == true);
-    }
+test "pointer-to-array constness for zero-size elements, var" {
+    try mutable();
+    comptime try mutable();
 }
 
-test "pointer-to-array constness for zero-size elements" {
-    try doTest();
-    comptime try doTest();
+test "pointer-to-array constness for zero-size elements, const" {
+    if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
+
+    try constant();
+    comptime try constant();
 }
test/behavior.zig
@@ -102,6 +102,7 @@ test {
         builtin.zig_backend != .stage2_wasm)
     {
         // Tests that pass for stage1, llvm backend, C backend
+        _ = @import("behavior/bugs/5474.zig");
         _ = @import("behavior/bugs/9584.zig");
         _ = @import("behavior/bugs/10970.zig");
         _ = @import("behavior/cast_int.zig");
@@ -152,7 +153,6 @@ test {
                 _ = @import("behavior/bugs/4328.zig");
                 _ = @import("behavior/bugs/5398.zig");
                 _ = @import("behavior/bugs/5413.zig");
-                _ = @import("behavior/bugs/5474.zig");
                 _ = @import("behavior/bugs/5487.zig");
                 _ = @import("behavior/bugs/6456.zig");
                 _ = @import("behavior/bugs/6781.zig");