Commit 79ef0cdf30

Veikka Tuominen <git@vexu.eu>
2022-07-19 19:20:57
Sema: better function parameter source location
1 parent 83b2d2c
src/Module.zig
@@ -2482,6 +2482,41 @@ pub const SrcLoc = struct {
                 };
                 return nodeToSpan(tree, full.ast.return_type);
             },
+            .node_offset_param => |node_off| {
+                const tree = try src_loc.file_scope.getTree(gpa);
+                const token_tags = tree.tokens.items(.tag);
+                const node = src_loc.declRelativeToNodeIndex(node_off);
+
+                var first_tok = tree.firstToken(node);
+                while (true) switch (token_tags[first_tok - 1]) {
+                    .colon, .identifier, .keyword_comptime, .keyword_noalias => first_tok -= 1,
+                    else => break,
+                };
+                return tokensToSpan(
+                    tree,
+                    first_tok,
+                    tree.lastToken(node),
+                    first_tok,
+                );
+            },
+            .token_offset_param => |token_off| {
+                const tree = try src_loc.file_scope.getTree(gpa);
+                const token_tags = tree.tokens.items(.tag);
+                const main_token = tree.nodes.items(.main_token)[src_loc.parent_decl_node];
+                const tok_index = @bitCast(Ast.TokenIndex, token_off + @bitCast(i32, main_token));
+
+                var first_tok = tok_index;
+                while (true) switch (token_tags[first_tok - 1]) {
+                    .colon, .identifier, .keyword_comptime, .keyword_noalias => first_tok -= 1,
+                    else => break,
+                };
+                return tokensToSpan(
+                    tree,
+                    first_tok,
+                    tok_index,
+                    first_tok,
+                );
+            },
 
             .node_offset_anyframe_type => |node_off| {
                 const tree = try src_loc.file_scope.getTree(gpa);
@@ -2930,6 +2965,8 @@ pub const LazySrcLoc = union(enum) {
     /// the return type node.
     /// The Decl is determined contextually.
     node_offset_fn_type_ret_ty: i32,
+    node_offset_param: i32,
+    token_offset_param: i32,
     /// The source location points to the type expression of an `anyframe->T`
     /// expression, found by taking this AST node index offset from the containing
     /// Decl AST node, which points to a `anyframe->T` expression AST node. Next, navigate
@@ -3046,6 +3083,8 @@ pub const LazySrcLoc = union(enum) {
             .node_offset_fn_type_section,
             .node_offset_fn_type_cc,
             .node_offset_fn_type_ret_ty,
+            .node_offset_param,
+            .token_offset_param,
             .node_offset_anyframe_type,
             .node_offset_lib_name,
             .node_offset_array_type_len,
@@ -5826,6 +5865,52 @@ fn queryFieldSrc(
     unreachable;
 }
 
+pub fn paramSrc(
+    func_node_offset: i32,
+    gpa: Allocator,
+    decl: *Decl,
+    param_i: usize,
+) LazySrcLoc {
+    @setCold(true);
+    const tree = decl.getFileScope().getTree(gpa) catch |err| {
+        // In this case we emit a warning + a less precise source location.
+        log.warn("unable to load {s}: {s}", .{
+            decl.getFileScope().sub_file_path, @errorName(err),
+        });
+        return LazySrcLoc.nodeOffset(0);
+    };
+    const node_datas = tree.nodes.items(.data);
+    const node_tags = tree.nodes.items(.tag);
+    const node = decl.relativeToNodeIndex(func_node_offset);
+    var params: [1]Ast.Node.Index = undefined;
+    const full = switch (node_tags[node]) {
+        .fn_proto_simple => tree.fnProtoSimple(&params, node),
+        .fn_proto_multi => tree.fnProtoMulti(node),
+        .fn_proto_one => tree.fnProtoOne(&params, node),
+        .fn_proto => tree.fnProto(node),
+        .fn_decl => switch (node_tags[node_datas[node].lhs]) {
+            .fn_proto_simple => tree.fnProtoSimple(&params, node_datas[node].lhs),
+            .fn_proto_multi => tree.fnProtoMulti(node_datas[node].lhs),
+            .fn_proto_one => tree.fnProtoOne(&params, node_datas[node].lhs),
+            .fn_proto => tree.fnProto(node_datas[node].lhs),
+            else => unreachable,
+        },
+        else => unreachable,
+    };
+    var it = full.iterate(tree);
+    while (true) {
+        if (it.param_i == param_i) {
+            const param = it.next().?;
+            if (param.anytype_ellipsis3) |some| {
+                const main_token = tree.nodes.items(.main_token)[decl.src_node];
+                return .{ .token_offset_param = @bitCast(i32, some) - @bitCast(i32, main_token) };
+            }
+            return .{ .node_offset_param = decl.nodeIndexToRelative(param.type_expr) };
+        }
+        _ = it.next();
+    }
+}
+
 /// Called from `performAllTheWork`, after all AstGen workers have finished,
 /// and before the main semantic analysis loop begins.
 pub fn processOutdatedAndDeletedDecls(mod: *Module) !void {
src/Sema.zig
@@ -7233,6 +7233,7 @@ fn funcCommon(
     opt_lib_name: ?[]const u8,
     noalias_bits: u32,
 ) CompileError!Air.Inst.Ref {
+    const fn_src = LazySrcLoc.nodeOffset(src_node_offset);
     const ret_ty_src: LazySrcLoc = .{ .node_offset_fn_type_ret_ty = src_node_offset };
     const cc_src: LazySrcLoc = .{ .node_offset_fn_type_cc = src_node_offset };
 
@@ -7241,10 +7242,6 @@ fn funcCommon(
         address_space == null or
         section == .generic or
         cc == null;
-    // Check for generic params.
-    for (block.params.items) |param| {
-        if (param.ty.tag() == .generic_poison) is_generic = true;
-    }
 
     var destroy_fn_on_error = false;
     const new_func: *Module.Fn = new_func: {
@@ -7304,55 +7301,35 @@ fn funcCommon(
         const param_types = try sema.arena.alloc(Type, block.params.items.len);
         const comptime_params = try sema.arena.alloc(bool, block.params.items.len);
         for (block.params.items) |param, i| {
-            const param_src = LazySrcLoc.nodeOffset(src_node_offset); // TODO better soruce location
             param_types[i] = param.ty;
-            const requires_comptime = try sema.typeRequiresComptime(block, param_src, param.ty);
-            comptime_params[i] = param.is_comptime or requires_comptime;
-            is_generic = is_generic or comptime_params[i] or param.ty.tag() == .generic_poison;
-            if (is_extern and is_generic) {
-                // TODO add note: function is generic because of this parameter
-                return sema.fail(block, param_src, "extern function cannot be generic", .{});
-            }
-            if (!param.ty.isValidParamType()) {
-                const opaque_str = if (param.ty.zigTypeTag() == .Opaque) "opaque " else "";
-                const msg = msg: {
-                    const msg = try sema.errMsg(block, param_src, "parameter of {s}type '{}' not allowed", .{
-                        opaque_str, param.ty.fmt(sema.mod),
-                    });
-                    errdefer msg.destroy(sema.gpa);
-
-                    try sema.addDeclaredHereNote(msg, param.ty);
-                    break :msg msg;
-                };
-                return sema.failWithOwnedErrorMsg(block, msg);
-            }
-            if (!Type.fnCallingConventionAllowsZigTypes(cc_workaround) and !(try sema.validateExternType(param.ty, .param_ty))) {
-                const msg = msg: {
-                    const msg = try sema.errMsg(block, param_src, "parameter of type '{}' not allowed in function with calling convention '{s}'", .{
-                        param.ty.fmt(sema.mod), @tagName(cc_workaround),
-                    });
-                    errdefer msg.destroy(sema.gpa);
-
-                    const src_decl = sema.mod.declPtr(block.src_decl);
-                    try sema.explainWhyTypeIsNotExtern(block, param_src, msg, param_src.toSrcLoc(src_decl), param.ty, .param_ty);
-
-                    try sema.addDeclaredHereNote(msg, param.ty);
-                    break :msg msg;
-                };
-                return sema.failWithOwnedErrorMsg(block, msg);
-            }
-            if (requires_comptime and !param.is_comptime) {
-                const msg = msg: {
-                    const msg = try sema.errMsg(block, param_src, "parametter of type '{}' must be declared comptime", .{
-                        param.ty.fmt(sema.mod),
-                    });
-                    errdefer msg.destroy(sema.gpa);
-
-                    try sema.addDeclaredHereNote(msg, param.ty);
-                    break :msg msg;
-                };
-                return sema.failWithOwnedErrorMsg(block, msg);
-            }
+            sema.analyzeParameter(
+                block,
+                fn_src,
+                .unneeded,
+                param,
+                comptime_params,
+                i,
+                &is_generic,
+                is_extern,
+                cc_workaround,
+            ) catch |err| switch (err) {
+                error.NeededSourceLocation => {
+                    const decl = sema.mod.declPtr(block.src_decl);
+                    try sema.analyzeParameter(
+                        block,
+                        fn_src,
+                        Module.paramSrc(src_node_offset, sema.gpa, decl, i),
+                        param,
+                        comptime_params,
+                        i,
+                        &is_generic,
+                        is_extern,
+                        cc_workaround,
+                    );
+                    return error.AnalysisFail;
+                },
+                else => |e| return e,
+            };
         }
 
         const ret_poison = if (!is_generic) rp: {
@@ -7542,6 +7519,79 @@ fn funcCommon(
     return sema.addConstant(fn_ty, Value.initPayload(&fn_payload.base));
 }
 
+fn analyzeParameter(
+    sema: *Sema,
+    block: *Block,
+    func_src: LazySrcLoc,
+    param_src: LazySrcLoc,
+    param: Block.Param,
+    comptime_params: []bool,
+    i: usize,
+    is_generic: *bool,
+    is_extern: bool,
+    cc: std.builtin.CallingConvention,
+) !void {
+    const requires_comptime = try sema.typeRequiresComptime(block, param_src, param.ty);
+    comptime_params[i] = param.is_comptime or requires_comptime;
+    const this_generic = comptime_params[i] or param.ty.tag() == .generic_poison;
+    is_generic.* = is_generic.* or this_generic;
+    if (is_extern and this_generic) {
+        // TODO this check should exist somewhere for notes.
+        if (param_src == .unneeded) return error.NeededSourceLocation;
+        const msg = msg: {
+            const msg = try sema.errMsg(block, func_src, "extern function cannot be generic", .{});
+            errdefer msg.destroy(sema.gpa);
+
+            try sema.errNote(block, param_src, msg, "function is generic because of this parameter", .{});
+            break :msg msg;
+        };
+        return sema.failWithOwnedErrorMsg(block, msg);
+    }
+    if (this_generic and !Type.fnCallingConventionAllowsZigTypes(cc)) {
+        return sema.fail(block, param_src, "generic parameters not allowed in function with calling convention '{s}'", .{@tagName(cc)});
+    }
+    if (!param.ty.isValidParamType()) {
+        const opaque_str = if (param.ty.zigTypeTag() == .Opaque) "opaque " else "";
+        const msg = msg: {
+            const msg = try sema.errMsg(block, param_src, "parameter of {s}type '{}' not allowed", .{
+                opaque_str, param.ty.fmt(sema.mod),
+            });
+            errdefer msg.destroy(sema.gpa);
+
+            try sema.addDeclaredHereNote(msg, param.ty);
+            break :msg msg;
+        };
+        return sema.failWithOwnedErrorMsg(block, msg);
+    }
+    if (!Type.fnCallingConventionAllowsZigTypes(cc) and !(try sema.validateExternType(param.ty, .param_ty))) {
+        const msg = msg: {
+            const msg = try sema.errMsg(block, param_src, "parameter of type '{}' not allowed in function with calling convention '{s}'", .{
+                param.ty.fmt(sema.mod), @tagName(cc),
+            });
+            errdefer msg.destroy(sema.gpa);
+
+            const src_decl = sema.mod.declPtr(block.src_decl);
+            try sema.explainWhyTypeIsNotExtern(block, param_src, msg, param_src.toSrcLoc(src_decl), param.ty, .param_ty);
+
+            try sema.addDeclaredHereNote(msg, param.ty);
+            break :msg msg;
+        };
+        return sema.failWithOwnedErrorMsg(block, msg);
+    }
+    if (requires_comptime and !param.is_comptime) {
+        const msg = msg: {
+            const msg = try sema.errMsg(block, param_src, "parametter of type '{}' must be declared comptime", .{
+                param.ty.fmt(sema.mod),
+            });
+            errdefer msg.destroy(sema.gpa);
+
+            try sema.addDeclaredHereNote(msg, param.ty);
+            break :msg msg;
+        };
+        return sema.failWithOwnedErrorMsg(block, msg);
+    }
+}
+
 fn zirParam(
     sema: *Sema,
     block: *Block,
@@ -18570,9 +18620,9 @@ fn explainWhyTypeIsNotExtern(
         .Union => try mod.errNoteNonLazy(src_loc, msg, "only unions with packed or extern layout are extern compatible", .{}),
         .Array => {
             if (position == .ret_ty) {
-                try mod.errNoteNonLazy(src_loc, msg, "arrays are not allowed as a return type", .{});
+                return mod.errNoteNonLazy(src_loc, msg, "arrays are not allowed as a return type", .{});
             } else if (position == .param_ty) {
-                try mod.errNoteNonLazy(src_loc, msg, "arrays are not allowed as a parameter type", .{});
+                return mod.errNoteNonLazy(src_loc, msg, "arrays are not allowed as a parameter type", .{});
             }
             try sema.explainWhyTypeIsNotExtern(block, src, msg, src_loc, ty.elemType2(), position);
         },
test/cases/compile_errors/stage1/obj/array_in_c_exported_function.zig
@@ -1,14 +0,0 @@
-export fn zig_array(x: [10]u8) void {
-    try std.testing.expect(std.mem.eql(u8, &x, "1234567890"));
-}
-const std = @import("std");
-export fn zig_return_array() [10]u8 {
-    return "1234567890".*;
-}
-
-// error
-// backend=stage1
-// target=native
-//
-// tmp.zig:1:24: error: parameter of type '[10]u8' not allowed in function with calling convention 'C'
-// tmp.zig:5:30: error: return type '[10]u8' not allowed in function with calling convention 'C'
test/cases/compile_errors/stage1/obj/export_function_with_comptime_parameter.zig
@@ -1,9 +0,0 @@
-export fn foo(comptime x: i32, y: i32) i32{
-    return x + y;
-}
-
-// error
-// backend=stage1
-// target=native
-//
-// tmp.zig:1:15: error: comptime parameter not allowed in function with calling convention 'C'
test/cases/compile_errors/stage1/obj/export_generic_function.zig
@@ -1,10 +0,0 @@
-export fn foo(num: anytype) i32 {
-    _ = num;
-    return 0;
-}
-
-// error
-// backend=stage1
-// target=native
-//
-// tmp.zig:1:15: error: parameter of type 'anytype' not allowed in function with calling convention 'C'
test/cases/compile_errors/stage1/obj/extern_function_with_comptime_parameter.zig
@@ -1,11 +0,0 @@
-extern fn foo(comptime x: i32, y: i32) i32;
-fn f() i32 {
-    return foo(1, 2);
-}
-export fn entry() usize { return @sizeOf(@TypeOf(f)); }
-
-// error
-// backend=stage1
-// target=native
-//
-// tmp.zig:1:15: error: comptime parameter not allowed in function with calling convention 'C'
test/cases/compile_errors/array_in_c_exported_function.zig
@@ -0,0 +1,16 @@
+export fn zig_array(x: [10]u8) void {
+    try std.testing.expect(std.mem.eql(u8, &x, "1234567890"));
+}
+const std = @import("std");
+export fn zig_return_array() [10]u8 {
+    return "1234567890".*;
+}
+
+// error
+// backend=stage2
+// target=native
+//
+// :1:21: error: parameter of type '[10]u8' not allowed in function with calling convention 'C'
+// :1:21: note: arrays are not allowed as a parameter type
+// :5:30: error: return type '[10]u8' not allowed in function with calling convention 'C'
+// :5:30: note: arrays are not allowed as a return type
test/cases/compile_errors/export_function_with_comptime_parameter.zig
@@ -0,0 +1,9 @@
+export fn foo(comptime x: anytype, y: i32) i32{
+    return x + y;
+}
+
+// error
+// backend=stage2
+// target=native
+//
+// :1:15: error: generic parameters not allowed in function with calling convention 'C'
test/cases/compile_errors/export_generic_function.zig
@@ -0,0 +1,10 @@
+export fn foo(num: anytype) i32 {
+    _ = num;
+    return 0;
+}
+
+// error
+// backend=stage2
+// target=native
+//
+// :1:15: error: generic parameters not allowed in function with calling convention 'C'
test/cases/compile_errors/extern_function_with_comptime_parameter.zig
@@ -0,0 +1,20 @@
+extern fn foo(comptime x: i32, y: i32) i32;
+fn f() i32 {
+    return foo(1, 2);
+}
+pub extern fn entry1(b: u32, comptime a: [2]u8, c: i32) void;
+pub extern fn entry2(b: u32, noalias a: anytype, i43) void;
+comptime { _ = f; }
+comptime { _ = entry1; }
+comptime { _ = entry2; }
+
+// error
+// backend=stage2
+// target=native
+//
+// :5:12: error: extern function cannot be generic
+// :5:30: note: function is generic because of this parameter
+// :6:12: error: extern function cannot be generic
+// :6:30: note: function is generic because of this parameter
+// :1:8: error: extern function cannot be generic
+// :1:15: note: function is generic because of this parameter
test/cases/compile_errors/function_parameter_is_opaque.zig
@@ -23,8 +23,9 @@ export fn entry4() void {
 // backend=stage2
 // target=native
 //
-// :3:24: error: parameter of opaque type 'tmp.FooType' not allowed
+// :3:28: error: parameter of opaque type 'tmp.FooType' not allowed
 // :1:17: note: opaque declared here
-// :8:24: error: parameter of type '@TypeOf(null)' not allowed
-// :12:1: error: parameter of opaque type 'tmp.FooType' not allowed
-// :17:1: error: parameter of type '@TypeOf(null)' not allowed
+// :8:28: error: parameter of type '@TypeOf(null)' not allowed
+// :12:8: error: parameter of opaque type 'tmp.FooType' not allowed
+// :1:17: note: opaque declared here
+// :17:8: error: parameter of type '@TypeOf(null)' not allowed
test/cases/compile_errors/function_with_non-extern_non-packed_enum_parameter.zig
@@ -5,7 +5,7 @@ export fn entry(foo: Foo) void { _ = foo; }
 // backend=stage2
 // target=native
 //
-// :2:8: error: parameter of type 'tmp.Foo' not allowed in function with calling convention 'C'
-// :2:8: note: enum tag type 'u2' is not extern compatible
-// :2:8: note: only integers with power of two bits are extern compatible
+// :2:17: error: parameter of type 'tmp.Foo' not allowed in function with calling convention 'C'
+// :2:17: note: enum tag type 'u2' is not extern compatible
+// :2:17: note: only integers with power of two bits are extern compatible
 // :1:13: note: enum declared here
test/cases/compile_errors/function_with_non-extern_non-packed_struct_parameter.zig
@@ -9,6 +9,6 @@ export fn entry(foo: Foo) void { _ = foo; }
 // backend=stage2
 // target=native
 //
-// :6:8: error: parameter of type 'tmp.Foo' not allowed in function with calling convention 'C'
-// :6:8: note: only structs with packed or extern layout are extern compatible
+// :6:17: error: parameter of type 'tmp.Foo' not allowed in function with calling convention 'C'
+// :6:17: note: only structs with packed or extern layout are extern compatible
 // :1:13: note: struct declared here
test/cases/compile_errors/function_with_non-extern_non-packed_union_parameter.zig
@@ -9,6 +9,6 @@ export fn entry(foo: Foo) void { _ = foo; }
 // backend=stage2
 // target=native
 //
-// :6:8: error: parameter of type 'tmp.Foo' not allowed in function with calling convention 'C'
-// :6:8: note: only unions with packed or extern layout are extern compatible
+// :6:17: error: parameter of type 'tmp.Foo' not allowed in function with calling convention 'C'
+// :6:17: note: only unions with packed or extern layout are extern compatible
 // :1:13: note: union declared here