Commit 2083208f19

Andrew Kelley <andrew@ziglang.org>
2021-04-20 00:03:41
AstGen: implement functions with inferred error sets
This commit also reclaims +2 ZIR instruction tags by moving the following to `extended`: * func_var_args * func_extra * func_extra_var_args The following ZIR instruction tag is added: * func_inferred
1 parent 22015c1
src/AstGen.zig
@@ -1375,9 +1375,7 @@ fn blockExprStmts(
                         .field_ptr_named,
                         .field_val_named,
                         .func,
-                        .func_var_args,
-                        .func_extra,
-                        .func_extra_var_args,
+                        .func_inferred,
                         .int,
                         .float,
                         .float128,
@@ -2129,9 +2127,8 @@ fn fnDecl(
     }
 
     const maybe_bang = tree.firstToken(fn_proto.ast.return_type) - 1;
-    if (token_tags[maybe_bang] == .bang) {
-        return astgen.failTok(maybe_bang, "TODO implement inferred error sets", .{});
-    }
+    const is_inferred_error = token_tags[maybe_bang] == .bang;
+
     const return_type_inst = try AstGen.expr(
         &decl_gz,
         &decl_gz.base,
@@ -2153,31 +2150,24 @@ fn fnDecl(
 
     const func_inst: Zir.Inst.Ref = if (body_node == 0) func: {
         if (is_extern) {
-            return astgen.failNode(fn_proto.ast.fn_token, "non-extern function has no body", .{});
+            return astgen.failTok(fn_proto.ast.fn_token, "non-extern function has no body", .{});
         }
-
-        if (cc != .none or lib_name != 0) {
-            const tag: Zir.Inst.Tag = if (is_var_args) .func_extra_var_args else .func_extra;
-            break :func try decl_gz.addFuncExtra(tag, .{
-                .src_node = fn_proto.ast.proto_node,
-                .ret_ty = return_type_inst,
-                .param_types = param_types,
-                .cc = cc,
-                .lib_name = lib_name,
-                .body = &[0]Zir.Inst.Index{},
-            });
+        if (is_inferred_error) {
+            return astgen.failTok(maybe_bang, "function prototype requires explicit error set", .{});
         }
-
-        const tag: Zir.Inst.Tag = if (is_var_args) .func_var_args else .func;
-        break :func try decl_gz.addFunc(tag, .{
+        break :func try decl_gz.addFunc(.{
             .src_node = fn_proto.ast.proto_node,
             .ret_ty = return_type_inst,
             .param_types = param_types,
             .body = &[0]Zir.Inst.Index{},
+            .cc = cc,
+            .lib_name = lib_name,
+            .is_var_args = is_var_args,
+            .is_inferred_error = false,
         });
     } else func: {
         if (is_var_args) {
-            return astgen.failNode(fn_proto.ast.fn_token, "non-extern function is variadic", .{});
+            return astgen.failTok(fn_proto.ast.fn_token, "non-extern function is variadic", .{});
         }
 
         var fn_gz: Scope.GenZir = .{
@@ -2224,30 +2214,20 @@ fn fnDecl(
         if (fn_gz.instructions.items.len == 0 or
             !astgen.instructions.items(.tag)[fn_gz.instructions.items.len - 1].isNoReturn())
         {
-            // astgen uses result location semantics to coerce return operands.
             // Since we are adding the return instruction here, we must handle the coercion.
             // We do this by using the `ret_coerce` instruction.
             _ = try fn_gz.addUnTok(.ret_coerce, .void_value, tree.lastToken(body_node));
         }
 
-        if (cc != .none or lib_name != 0) {
-            const tag: Zir.Inst.Tag = if (is_var_args) .func_extra_var_args else .func_extra;
-            break :func try decl_gz.addFuncExtra(tag, .{
-                .src_node = fn_proto.ast.proto_node,
-                .ret_ty = return_type_inst,
-                .param_types = param_types,
-                .cc = cc,
-                .lib_name = lib_name,
-                .body = fn_gz.instructions.items,
-            });
-        }
-
-        const tag: Zir.Inst.Tag = if (is_var_args) .func_var_args else .func;
-        break :func try decl_gz.addFunc(tag, .{
+        break :func try decl_gz.addFunc(.{
             .src_node = fn_proto.ast.proto_node,
             .ret_ty = return_type_inst,
             .param_types = param_types,
             .body = fn_gz.instructions.items,
+            .cc = cc,
+            .lib_name = lib_name,
+            .is_var_args = is_var_args,
+            .is_inferred_error = is_inferred_error,
         });
     };
 
src/Module.zig
@@ -1278,79 +1278,90 @@ pub const Scope = struct {
             }
         }
 
-        pub fn addFuncExtra(gz: *GenZir, tag: Zir.Inst.Tag, args: struct {
+        pub fn addFunc(gz: *GenZir, args: struct {
             src_node: ast.Node.Index,
             param_types: []const Zir.Inst.Ref,
+            body: []const Zir.Inst.Index,
             ret_ty: Zir.Inst.Ref,
             cc: Zir.Inst.Ref,
-            body: []const Zir.Inst.Index,
             lib_name: u32,
+            is_var_args: bool,
+            is_inferred_error: bool,
         }) !Zir.Inst.Ref {
             assert(args.src_node != 0);
             assert(args.ret_ty != .none);
-            assert(args.cc != .none);
-            const gpa = gz.astgen.gpa;
-            try gz.instructions.ensureCapacity(gpa, gz.instructions.items.len + 1);
-            try gz.astgen.instructions.ensureCapacity(gpa, gz.astgen.instructions.len + 1);
-            try gz.astgen.extra.ensureCapacity(gpa, gz.astgen.extra.items.len +
-                @typeInfo(Zir.Inst.FuncExtra).Struct.fields.len + args.param_types.len +
-                args.body.len);
-
-            const payload_index = gz.astgen.addExtraAssumeCapacity(Zir.Inst.FuncExtra{
-                .return_type = args.ret_ty,
-                .cc = args.cc,
-                .param_types_len = @intCast(u32, args.param_types.len),
-                .body_len = @intCast(u32, args.body.len),
-                .lib_name = args.lib_name,
-            });
-            gz.astgen.appendRefsAssumeCapacity(args.param_types);
-            gz.astgen.extra.appendSliceAssumeCapacity(args.body);
-
-            const new_index = @intCast(Zir.Inst.Index, gz.astgen.instructions.len);
-            gz.astgen.instructions.appendAssumeCapacity(.{
-                .tag = tag,
-                .data = .{ .pl_node = .{
-                    .src_node = gz.nodeIndexToRelative(args.src_node),
-                    .payload_index = payload_index,
-                } },
-            });
-            gz.instructions.appendAssumeCapacity(new_index);
-            return gz.indexToRef(new_index);
-        }
-
-        pub fn addFunc(gz: *GenZir, tag: Zir.Inst.Tag, args: struct {
-            src_node: ast.Node.Index,
-            ret_ty: Zir.Inst.Ref,
-            param_types: []const Zir.Inst.Ref,
-            body: []const Zir.Inst.Index,
-        }) !Zir.Inst.Ref {
-            assert(args.src_node != 0);
-            assert(args.ret_ty != .none);
-            const gpa = gz.astgen.gpa;
-            try gz.instructions.ensureCapacity(gpa, gz.instructions.items.len + 1);
-            try gz.astgen.instructions.ensureCapacity(gpa, gz.astgen.instructions.len + 1);
-            try gz.astgen.extra.ensureCapacity(gpa, gz.astgen.extra.items.len +
-                @typeInfo(Zir.Inst.Func).Struct.fields.len + args.param_types.len +
-                args.body.len);
-
-            const payload_index = gz.astgen.addExtraAssumeCapacity(Zir.Inst.Func{
-                .return_type = args.ret_ty,
-                .param_types_len = @intCast(u32, args.param_types.len),
-                .body_len = @intCast(u32, args.body.len),
-            });
-            gz.astgen.appendRefsAssumeCapacity(args.param_types);
-            gz.astgen.extra.appendSliceAssumeCapacity(args.body);
+            const astgen = gz.astgen;
+            const gpa = astgen.gpa;
 
-            const new_index = @intCast(Zir.Inst.Index, gz.astgen.instructions.len);
-            gz.astgen.instructions.appendAssumeCapacity(.{
-                .tag = tag,
-                .data = .{ .pl_node = .{
+            try gz.instructions.ensureUnusedCapacity(gpa, 1);
+            try astgen.instructions.ensureUnusedCapacity(gpa, 1);
+
+            if (args.cc != .none or args.lib_name != 0 or args.is_var_args) {
+                try astgen.extra.ensureUnusedCapacity(
+                    gpa,
+                    @typeInfo(Zir.Inst.ExtendedFunc).Struct.fields.len +
+                        args.param_types.len + args.body.len +
+                        @boolToInt(args.lib_name != 0) +
+                        @boolToInt(args.cc != .none),
+                );
+                const payload_index = astgen.addExtraAssumeCapacity(Zir.Inst.ExtendedFunc{
                     .src_node = gz.nodeIndexToRelative(args.src_node),
-                    .payload_index = payload_index,
-                } },
-            });
-            gz.instructions.appendAssumeCapacity(new_index);
-            return gz.indexToRef(new_index);
+                    .return_type = args.ret_ty,
+                    .param_types_len = @intCast(u32, args.param_types.len),
+                    .body_len = @intCast(u32, args.body.len),
+                });
+                if (args.cc != .none) {
+                    astgen.extra.appendAssumeCapacity(@enumToInt(args.cc));
+                }
+                if (args.lib_name != 0) {
+                    astgen.extra.appendAssumeCapacity(args.lib_name);
+                }
+                astgen.appendRefsAssumeCapacity(args.param_types);
+                astgen.extra.appendSliceAssumeCapacity(args.body);
+
+                const new_index = @intCast(Zir.Inst.Index, astgen.instructions.len);
+                astgen.instructions.appendAssumeCapacity(.{
+                    .tag = .extended,
+                    .data = .{ .extended = .{
+                        .opcode = .func,
+                        .small = @bitCast(u16, Zir.Inst.ExtendedFunc.Small{
+                            .is_var_args = args.is_var_args,
+                            .is_inferred_error = args.is_inferred_error,
+                            .has_lib_name = args.lib_name != 0,
+                            .has_cc = args.cc != .none,
+                        }),
+                        .operand = payload_index,
+                    } },
+                });
+                gz.instructions.appendAssumeCapacity(new_index);
+                return gz.indexToRef(new_index);
+            } else {
+                try gz.astgen.extra.ensureUnusedCapacity(
+                    gpa,
+                    @typeInfo(Zir.Inst.Func).Struct.fields.len +
+                        args.param_types.len + args.body.len,
+                );
+
+                const payload_index = gz.astgen.addExtraAssumeCapacity(Zir.Inst.Func{
+                    .return_type = args.ret_ty,
+                    .param_types_len = @intCast(u32, args.param_types.len),
+                    .body_len = @intCast(u32, args.body.len),
+                });
+                gz.astgen.appendRefsAssumeCapacity(args.param_types);
+                gz.astgen.extra.appendSliceAssumeCapacity(args.body);
+
+                const tag: Zir.Inst.Tag = if (args.is_inferred_error) .func_inferred else .func;
+                const new_index = @intCast(Zir.Inst.Index, gz.astgen.instructions.len);
+                gz.astgen.instructions.appendAssumeCapacity(.{
+                    .tag = tag,
+                    .data = .{ .pl_node = .{
+                        .src_node = gz.nodeIndexToRelative(args.src_node),
+                        .payload_index = payload_index,
+                    } },
+                });
+                gz.instructions.appendAssumeCapacity(new_index);
+                return gz.indexToRef(new_index);
+            }
         }
 
         pub fn addCall(
src/Sema.zig
@@ -194,9 +194,7 @@ pub fn analyzeBody(
             .field_val                    => try sema.zirFieldVal(block, inst),
             .field_val_named              => try sema.zirFieldValNamed(block, inst),
             .func                         => try sema.zirFunc(block, inst, false),
-            .func_extra                   => try sema.zirFuncExtra(block, inst, false),
-            .func_extra_var_args          => try sema.zirFuncExtra(block, inst, true),
-            .func_var_args                => try sema.zirFunc(block, inst, true),
+            .func_inferred                => try sema.zirFunc(block, inst, true),
             .import                       => try sema.zirImport(block, inst),
             .indexable_ptr_len            => try sema.zirIndexablePtrLen(block, inst),
             .int                          => try sema.zirInt(block, inst),
@@ -2646,7 +2644,12 @@ fn zirEnsureErrPayloadVoid(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Inde
     }
 }
 
-fn zirFunc(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index, var_args: bool) InnerError!*Inst {
+fn zirFunc(
+    sema: *Sema,
+    block: *Scope.Block,
+    inst: Zir.Inst.Index,
+    inferred_error_set: bool,
+) InnerError!*Inst {
     const tracy = trace(@src());
     defer tracy.end();
 
@@ -2654,40 +2657,17 @@ fn zirFunc(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index, var_args: boo
     const src = inst_data.src();
     const extra = sema.code.extraData(Zir.Inst.Func, inst_data.payload_index);
     const param_types = sema.code.refSlice(extra.end, extra.data.param_types_len);
+    const body = sema.code.extra[extra.end + param_types.len ..][0..extra.data.body_len];
 
     return sema.funcCommon(
         block,
         inst_data.src_node,
         param_types,
+        body,
         extra.data.return_type,
         .Unspecified,
-        var_args,
-    );
-}
-
-fn zirFuncExtra(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index, var_args: bool) InnerError!*Inst {
-    const tracy = trace(@src());
-    defer tracy.end();
-
-    const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
-    const src = inst_data.src();
-    const cc_src: LazySrcLoc = .{ .node_offset_fn_type_cc = inst_data.src_node };
-    const extra = sema.code.extraData(Zir.Inst.FuncExtra, inst_data.payload_index);
-    const param_types = sema.code.refSlice(extra.end, extra.data.param_types_len);
-
-    const cc_tv = try sema.resolveInstConst(block, cc_src, extra.data.cc);
-    // TODO once we're capable of importing and analyzing decls from
-    // std.builtin, this needs to change
-    const cc_str = cc_tv.val.castTag(.enum_literal).?.data;
-    const cc = std.meta.stringToEnum(std.builtin.CallingConvention, cc_str) orelse
-        return sema.mod.fail(&block.base, cc_src, "Unknown calling convention {s}", .{cc_str});
-    return sema.funcCommon(
-        block,
-        inst_data.src_node,
-        param_types,
-        extra.data.return_type,
-        cc,
-        var_args,
+        false,
+        inferred_error_set,
     );
 }
 
@@ -2696,9 +2676,11 @@ fn funcCommon(
     block: *Scope.Block,
     src_node_offset: i32,
     zir_param_types: []const Zir.Inst.Ref,
+    body: []const Zir.Inst.Index,
     zir_return_type: Zir.Inst.Ref,
     cc: std.builtin.CallingConvention,
     var_args: bool,
+    inferred_error_set: bool,
 ) InnerError!*Inst {
     const src: LazySrcLoc = .{ .node_offset = src_node_offset };
     const ret_ty_src: LazySrcLoc = .{ .node_offset_fn_type_ret_ty = src_node_offset };
@@ -5307,15 +5289,68 @@ fn zirExtended(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) InnerErro
     const extended = sema.code.instructions.items(.data)[inst].extended;
     switch (extended.opcode) {
         // zig fmt: off
-        .c_undef            => return sema.zirCUndef(           block, inst, extended),
-        .c_include          => return sema.zirCInclude(         block, inst, extended),
-        .c_define           => return sema.zirCDefine(          block, inst, extended),
-        .wasm_memory_size   => return sema.zirWasmMemorySize(   block, inst, extended),
-        .wasm_memory_grow   => return sema.zirWasmMemoryGrow(   block, inst, extended),
+        .func               => return sema.zirFuncExtended(  block, inst, extended),
+        .c_undef            => return sema.zirCUndef(        block, inst, extended),
+        .c_include          => return sema.zirCInclude(      block, inst, extended),
+        .c_define           => return sema.zirCDefine(       block, inst, extended),
+        .wasm_memory_size   => return sema.zirWasmMemorySize(block, inst, extended),
+        .wasm_memory_grow   => return sema.zirWasmMemoryGrow(block, inst, extended),
         // zig fmt: on
     }
 }
 
+fn zirFuncExtended(
+    sema: *Sema,
+    block: *Scope.Block,
+    inst: Zir.Inst.Index,
+    extended: Zir.Inst.Extended.InstData,
+) InnerError!*Inst {
+    const tracy = trace(@src());
+    defer tracy.end();
+
+    const extra = sema.code.extraData(Zir.Inst.ExtendedFunc, extended.operand);
+    const src: LazySrcLoc = .{ .node_offset = extra.data.src_node };
+    const cc_src: LazySrcLoc = .{ .node_offset_fn_type_cc = extra.data.src_node };
+    const small = @bitCast(Zir.Inst.ExtendedFunc.Small, extended.small);
+
+    var extra_index: usize = extra.end;
+    if (small.has_lib_name) {
+        const lib_name = @intToEnum(Zir.Inst.Ref, sema.code.extra[extra_index]);
+        extra_index += 1;
+        return sema.mod.fail(&block.base, src, "TODO: implement Sema func lib name", .{});
+    }
+    const cc: std.builtin.CallingConvention = if (small.has_cc) blk: {
+        const cc_ref = @intToEnum(Zir.Inst.Ref, sema.code.extra[extra_index]);
+        extra_index += 1;
+        const cc_tv = try sema.resolveInstConst(block, cc_src, cc_ref);
+        // TODO this needs to resolve other kinds of Value tags rather than
+        // assuming the tag will be .enum_field_index.
+        const cc_field_index = cc_tv.val.castTag(.enum_field_index).?.data;
+        // TODO should `@intToEnum` do this `@intCast` for you?
+        const cc = @intToEnum(
+            std.builtin.CallingConvention,
+            @intCast(@typeInfo(std.builtin.CallingConvention).Enum.tag_type, cc_field_index),
+        );
+        break :blk cc;
+    } else .Unspecified;
+
+    const param_types = sema.code.refSlice(extra_index, extra.data.param_types_len);
+    extra_index += 1;
+
+    const body = sema.code.extra[extra_index..][0..extra.data.body_len];
+
+    return sema.funcCommon(
+        block,
+        extra.data.src_node,
+        param_types,
+        body,
+        extra.data.return_type,
+        cc,
+        small.is_var_args,
+        small.is_inferred_error,
+    );
+}
+
 fn zirCUndef(
     sema: *Sema,
     block: *Scope.Block,
src/Zir.zig
@@ -371,15 +371,8 @@ pub const Inst = struct {
         /// the body_len is 0. Calling convention is auto.
         /// Uses the `pl_node` union field. `payload_index` points to a `Func`.
         func,
-        /// Same as `func` but the function is variadic.
-        func_var_args,
-        /// Same as `func` but with extra fields:
-        ///  * calling convention
-        ///  * extern lib name
-        /// Uses the `pl_node` union field. `payload_index` points to a `FuncExtra`.
-        func_extra,
-        /// Same as `func_extra` but the function is variadic.
-        func_extra_var_args,
+        /// Same as `func` but has an inferred error set.
+        func_inferred,
         /// Implements the `@import` builtin.
         /// Uses the `str_tok` field.
         import,
@@ -1010,9 +1003,7 @@ pub const Inst = struct {
                 .field_ptr_named,
                 .field_val_named,
                 .func,
-                .func_var_args,
-                .func_extra,
-                .func_extra_var_args,
+                .func_inferred,
                 .has_decl,
                 .int,
                 .float,
@@ -1211,6 +1202,11 @@ pub const Inst = struct {
     /// Rarer instructions are here; ones that do not fit in the 8-bit `Tag` enum.
     /// `noreturn` instructions may not go here; they must be part of the main `Tag` enum.
     pub const Extended = enum(u16) {
+        /// Represents a function declaration or function prototype, depending on
+        /// whether body_len is 0.
+        /// `operand` is payload index to `ExtendedFunc`.
+        /// `small` is `ExtendedFunc.Small`.
+        func,
         /// `operand` is payload index to `UnNode`.
         c_undef,
         /// `operand` is payload index to `UnNode`.
@@ -1774,15 +1770,23 @@ pub const Inst = struct {
     };
 
     /// Trailing:
-    /// 0. param_type: Ref // for each param_types_len
-    /// 1. body: Index // for each body_len
-    pub const FuncExtra = struct {
-        cc: Ref,
-        /// null terminated string index, or 0 to mean none.
-        lib_name: u32,
+    /// 0. lib_name: u32, // null terminated string index, if has_lib_name is set
+    /// 1. cc: Ref, // if has_cc is set
+    /// 2. param_type: Ref // for each param_types_len
+    /// 3. body: Index // for each body_len
+    pub const ExtendedFunc = struct {
+        src_node: i32,
         return_type: Ref,
         param_types_len: u32,
         body_len: u32,
+
+        pub const Small = packed struct {
+            is_var_args: bool,
+            is_inferred_error: bool,
+            has_lib_name: bool,
+            has_cc: bool,
+            _: u12 = undefined,
+        };
     };
 
     /// Trailing:
@@ -2459,9 +2463,7 @@ const Writer = struct {
             => try self.writeStrTok(stream, inst),
 
             .func => try self.writeFunc(stream, inst, false),
-            .func_extra => try self.writeFuncExtra(stream, inst, false),
-            .func_var_args => try self.writeFunc(stream, inst, true),
-            .func_extra_var_args => try self.writeFuncExtra(stream, inst, true),
+            .func_inferred => try self.writeFunc(stream, inst, true),
 
             .@"unreachable" => try self.writeUnreachable(stream, inst),
 
@@ -3099,7 +3101,7 @@ const Writer = struct {
         self: *Writer,
         stream: anytype,
         inst: Inst.Index,
-        var_args: bool,
+        inferred_error_set: bool,
     ) !void {
         const inst_data = self.code.instructions.items(.data)[inst].pl_node;
         const src = inst_data.src();
@@ -3110,36 +3112,14 @@ const Writer = struct {
             stream,
             param_types,
             extra.data.return_type,
-            var_args,
+            inferred_error_set,
+            false,
             .none,
             body,
             src,
         );
     }
 
-    fn writeFuncExtra(
-        self: *Writer,
-        stream: anytype,
-        inst: Inst.Index,
-        var_args: bool,
-    ) !void {
-        const inst_data = self.code.instructions.items(.data)[inst].pl_node;
-        const src = inst_data.src();
-        const extra = self.code.extraData(Inst.FuncExtra, inst_data.payload_index);
-        const param_types = self.code.refSlice(extra.end, extra.data.param_types_len);
-        const cc = extra.data.cc;
-        const body = self.code.extra[extra.end + param_types.len ..][0..extra.data.body_len];
-        return self.writeFuncCommon(
-            stream,
-            param_types,
-            extra.data.return_type,
-            var_args,
-            cc,
-            body,
-            src,
-        );
-    }
-
     fn writeBoolBr(self: *Writer, stream: anytype, inst: Inst.Index) !void {
         const inst_data = self.code.instructions.items(.data)[inst].bool_br;
         const extra = self.code.extraData(Inst.Block, inst_data.payload_index);
@@ -3184,6 +3164,7 @@ const Writer = struct {
         stream: anytype,
         param_types: []const Inst.Ref,
         ret_ty: Inst.Ref,
+        inferred_error_set: bool,
         var_args: bool,
         cc: Inst.Ref,
         body: []const Inst.Index,
@@ -3197,7 +3178,8 @@ const Writer = struct {
         try stream.writeAll("], ");
         try self.writeInstRef(stream, ret_ty);
         try self.writeOptionalInstRef(stream, ", cc=", cc);
-        try self.writeFlag(stream, ", var_args", var_args);
+        try self.writeFlag(stream, ", vargs", var_args);
+        try self.writeFlag(stream, ", inferror", inferred_error_set);
 
         try stream.writeAll(", {\n");
         self.indent += 2;
BRANCH_TODO
@@ -737,3 +737,27 @@ fn errorSetDecl(
             try mod.analyzeExport(&decl_scope.base, export_src, name, decl);
         }
     }
+
+    fn writeFuncExtra(
+        self: *Writer,
+        stream: anytype,
+        inst: Inst.Index,
+        var_args: bool,
+    ) !void {
+        const inst_data = self.code.instructions.items(.data)[inst].pl_node;
+        const src = inst_data.src();
+        const extra = self.code.extraData(Inst.FuncExtra, inst_data.payload_index);
+        const param_types = self.code.refSlice(extra.end, extra.data.param_types_len);
+        const cc = extra.data.cc;
+        const body = self.code.extra[extra.end + param_types.len ..][0..extra.data.body_len];
+        return self.writeFuncCommon(
+            stream,
+            param_types,
+            extra.data.return_type,
+            var_args,
+            cc,
+            body,
+            src,
+        );
+    }
+