Commit 948926c513

mlugg <mlugg@mlugg.co.uk>
2023-03-11 15:26:56
Sema: improve error message when calling non-member function as method
Resolves: #14880
1 parent a097779
src/Sema.zig
@@ -23605,10 +23605,13 @@ fn fieldCallBind(
     }
 
     // If we get here, we need to look for a decl in the struct type instead.
-    switch (concrete_ty.zigTypeTag()) {
-        .Struct, .Opaque, .Union, .Enum => {
+    const found_decl = switch (concrete_ty.zigTypeTag()) {
+        .Struct, .Opaque, .Union, .Enum => found_decl: {
             if (concrete_ty.getNamespace()) |namespace| {
-                if (try sema.namespaceLookupRef(block, src, namespace, field_name)) |inst| {
+                if (try sema.namespaceLookup(block, src, namespace, field_name)) |decl_idx| {
+                    try sema.addReferencedBy(block, src, decl_idx);
+                    const inst = try sema.analyzeDeclRef(decl_idx);
+
                     const decl_val = try sema.analyzeLoad(block, src, inst, src);
                     const decl_type = sema.typeOf(decl_val);
                     if (decl_type.zigTypeTag() == .Fn and
@@ -23625,7 +23628,7 @@ fn fieldCallBind(
                                 first_param_type.ptrSize() == .C) and
                                 first_param_type.childType().eql(concrete_ty, sema.mod)))
                         {
-                            // zig fmt: on
+                        // zig fmt: on
                             // TODO: bound fn calls on rvalues should probably
                             // generate a by-value argument somehow.
                             const ty = Type.Tag.bound_fn.init();
@@ -23664,16 +23667,22 @@ fn fieldCallBind(
                             return sema.addConstant(ty, value);
                         }
                     }
+                    break :found_decl decl_idx;
                 }
             }
+            break :found_decl null;
         },
-        else => {},
-    }
+        else => null,
+    };
 
     const msg = msg: {
         const msg = try sema.errMsg(block, src, "no field or member function named '{s}' in '{}'", .{ field_name, concrete_ty.fmt(sema.mod) });
         errdefer msg.destroy(sema.gpa);
         try sema.addDeclaredHereNote(msg, concrete_ty);
+        if (found_decl) |decl_idx| {
+            const decl = sema.mod.declPtr(decl_idx);
+            try sema.mod.errNoteNonLazy(decl.srcLoc(), msg, "'{s}' is not a member function", .{field_name});
+        }
         break :msg msg;
     };
     return sema.failWithOwnedErrorMsg(msg);
test/cases/compile_errors/method_call_with_first_arg_type_primitive.zig
@@ -2,7 +2,7 @@ const Foo = struct {
     x: i32,
 
     fn init(x: i32) Foo {
-        return Foo {
+        return Foo{
             .x = x,
         };
     }
@@ -20,3 +20,4 @@ export fn f() void {
 //
 // :14:9: error: no field or member function named 'init' in 'tmp.Foo'
 // :1:13: note: struct declared here
+// :4:5: note: 'init' is not a member function
test/cases/compile_errors/method_call_with_first_arg_type_wrong_container.zig
@@ -29,3 +29,4 @@ export fn foo() void {
 //
 // :23:6: error: no field or member function named 'init' in 'tmp.List'
 // :1:18: note: struct declared here
+// :5:9: note: 'init' is not a member function