Commit 4b68224c60

Andrew Kelley <superjoe30@gmail.com>
2016-09-26 06:40:09
add error message for method call on non method
closes #199
1 parent 01e13de
Changed files (3)
src/all_types.hpp
@@ -452,7 +452,6 @@ struct AstNodeFieldAccessExpr {
     TypeEnumField *type_enum_field;
     Expr resolved_expr;
     StructValExprCodeGen resolved_struct_val_expr; // for enum values
-    bool is_fn_call;
     TypeTableEntry *bare_container_type;
     bool is_member_fn;
     AstNode *container_init_expr_node;
src/analyze.cpp
@@ -2798,11 +2798,11 @@ static void resolve_container_type(CodeGen *g, TypeTableEntry *type_entry) {
     }
 }
 
-static TypeTableEntry *analyze_container_member_access_inner(CodeGen *g, bool wrapped_in_fn_call,
+static TypeTableEntry *analyze_container_member_access_inner(CodeGen *g,
     TypeTableEntry *bare_struct_type, Buf *field_name, AstNode *node, TypeTableEntry *struct_type)
 {
     assert(node->type == NodeTypeFieldAccessExpr);
-    if (wrapped_in_fn_call && !is_slice(bare_struct_type)) {
+    if (!is_slice(bare_struct_type)) {
         BlockContext *container_block_context = get_container_block_context(bare_struct_type);
         assert(container_block_context);
         auto entry = container_block_context->decl_table.maybe_get(field_name);
@@ -2821,19 +2821,14 @@ static TypeTableEntry *analyze_container_member_access_inner(CodeGen *g, bool wr
             } else {
                 return resolve_expr_const_val_as_fn(g, node, fn_entry, false);
             }
-        } else {
-            add_node_error(g, node, buf_sprintf("no function named '%s' in '%s'",
-                buf_ptr(field_name), buf_ptr(&bare_struct_type->name)));
-            return g->builtin_types.entry_invalid;
         }
-    } else {
-        add_node_error(g, node,
-            buf_sprintf("no member named '%s' in '%s'", buf_ptr(field_name), buf_ptr(&struct_type->name)));
-        return g->builtin_types.entry_invalid;
     }
+    add_node_error(g, node,
+        buf_sprintf("no member named '%s' in '%s'", buf_ptr(field_name), buf_ptr(&bare_struct_type->name)));
+    return g->builtin_types.entry_invalid;
 }
 
-static TypeTableEntry *analyze_container_member_access(CodeGen *g, bool wrapped_in_fn_call,
+static TypeTableEntry *analyze_container_member_access(CodeGen *g,
         Buf *field_name, AstNode *node, TypeTableEntry *struct_type)
 {
     TypeTableEntry *bare_type = container_ref_type(struct_type);
@@ -2848,7 +2843,7 @@ static TypeTableEntry *analyze_container_member_access(CodeGen *g, bool wrapped_
         if (node->data.field_access_expr.type_struct_field) {
             return node->data.field_access_expr.type_struct_field->type_entry;
         } else {
-            return analyze_container_member_access_inner(g, wrapped_in_fn_call, bare_type, field_name,
+            return analyze_container_member_access_inner(g, bare_type, field_name,
                 node, struct_type);
         }
     } else if (bare_type->id == TypeTableEntryIdEnum) {
@@ -2856,7 +2851,7 @@ static TypeTableEntry *analyze_container_member_access(CodeGen *g, bool wrapped_
         if (node->data.field_access_expr.type_enum_field) {
             return node->data.field_access_expr.type_enum_field->type_entry;
         } else {
-            return analyze_container_member_access_inner(g, wrapped_in_fn_call, bare_type, field_name,
+            return analyze_container_member_access_inner(g, bare_type, field_name,
                 node, struct_type);
         }
     } else if (bare_type->id == TypeTableEntryIdUnion) {
@@ -2875,12 +2870,10 @@ static TypeTableEntry *analyze_field_access_expr(CodeGen *g, ImportTableEntry *i
     TypeTableEntry *struct_type = analyze_expression(g, import, context, nullptr, *struct_expr_node);
     Buf *field_name = node->data.field_access_expr.field_name;
 
-    bool wrapped_in_fn_call = node->data.field_access_expr.is_fn_call;
-
     if (struct_type->id == TypeTableEntryIdInvalid) {
         return struct_type;
     } else if (is_container_ref(struct_type)) {
-        return analyze_container_member_access(g, wrapped_in_fn_call, field_name, node, struct_type);
+        return analyze_container_member_access(g, field_name, node, struct_type);
     } else if (struct_type->id == TypeTableEntryIdArray) {
         if (buf_eql_str(field_name, "len")) {
             return resolve_expr_const_val_as_unsigned_num_lit(g, node, expected_type,
@@ -2949,8 +2942,6 @@ static TypeTableEntry *analyze_field_access_expr(CodeGen *g, ImportTableEntry *i
                         buf_ptr(&child_type->name), buf_ptr(field_name)));
                 return g->builtin_types.entry_invalid;
             }
-        } else if (wrapped_in_fn_call) { // this branch should go last, before the error in the else case
-            return resolve_expr_const_val_as_type(g, node, child_type, false);
         } else {
             add_node_error(g, node,
                 buf_sprintf("type '%s' does not support field access", buf_ptr(&struct_type->name)));
@@ -5581,6 +5572,19 @@ static TypeTableEntry *analyze_builtin_fn_call_expr(CodeGen *g, ImportTableEntry
     zig_unreachable();
 }
 
+static TypeTableEntry *bad_method_call(CodeGen *g, AstNode *node, TypeTableEntry *container_type,
+        TypeTableEntry *expected_param_type, FnTableEntry *fn_table_entry)
+{
+    ErrorMsg *msg = add_node_error(g, node,
+        buf_sprintf("function called as method of '%s', but first parameter is of type '%s'",
+            buf_ptr(&container_type->name),
+            buf_ptr(&expected_param_type->name)));
+    if (fn_table_entry) {
+        add_error_note(g, msg, fn_table_entry->proto_node, buf_sprintf("function declared here"));
+    }
+    return g->builtin_types.entry_invalid;
+}
+
 // Before calling this function, set node->data.fn_call_expr.fn_table_entry if the function is known
 // at compile time. Otherwise this is a function pointer call.
 static TypeTableEntry *analyze_fn_call_ptr(CodeGen *g, ImportTableEntry *import, BlockContext *context,
@@ -5623,10 +5627,23 @@ static TypeTableEntry *analyze_fn_call_ptr(CodeGen *g, ImportTableEntry *import,
     bool all_args_const_expr = true;
 
     if (struct_node) {
-        ConstExprValue *struct_const_val = &get_resolved_expr(struct_node)->const_val;
+        Expr *struct_expr = get_resolved_expr(struct_node);
+        ConstExprValue *struct_const_val = &struct_expr->const_val;
         if (!struct_const_val->ok) {
             all_args_const_expr = false;
         }
+
+        FnTypeParamInfo *param_info = &fn_type->data.fn.fn_type_id.param_info[0];
+        TypeTableEntry *expected_param_type = param_info->type;
+        TypeTableEntry *container_bare_type = container_ref_type(struct_expr->type_entry);
+        if (is_container_ref(expected_param_type)) {
+            TypeTableEntry *param_bare_type = container_ref_type(expected_param_type);
+            if (param_bare_type != container_bare_type) {
+                return bad_method_call(g, node, container_bare_type, expected_param_type, fn_table_entry);
+            }
+        } else {
+            return bad_method_call(g, node, container_bare_type, expected_param_type, fn_table_entry);
+        }
     }
 
     // analyze each parameter. in the case of a method, we already analyzed the
@@ -5925,10 +5942,6 @@ static TypeTableEntry *analyze_fn_call_expr(CodeGen *g, ImportTableEntry *import
         return analyze_builtin_fn_call_expr(g, import, context, expected_type, node);
     }
 
-    if (fn_ref_expr->type == NodeTypeFieldAccessExpr) {
-        fn_ref_expr->data.field_access_expr.is_fn_call = true;
-    }
-
     TypeTableEntry *invoke_type_entry = analyze_expression(g, import, context, nullptr, fn_ref_expr);
     if (invoke_type_entry->id == TypeTableEntryIdInvalid) {
         return g->builtin_types.entry_invalid;
test/run_tests.cpp
@@ -1533,7 +1533,56 @@ fn foo() {
 }
     )SOURCE", 2,
         ".tmp_source.zig:6:16: error: use of undeclared identifier 'JsonList'",
-        ".tmp_source.zig:27:8: error: no function named 'init' in 'JsonNode'");
+        ".tmp_source.zig:27:8: error: no member named 'init' in 'JsonNode'");
+
+    add_compile_fail_case("method call with first arg type primitive", R"SOURCE(
+struct Foo {
+    x: i32,
+
+    fn init(x: i32) -> Foo {
+        Foo {
+            .x = x,
+        }
+    }
+}
+
+fn f() {
+    const derp = Foo.init(3);
+
+    derp.init();
+}
+    )SOURCE", 2,
+        ".tmp_source.zig:15:14: error: function called as method of 'Foo', but first parameter is of type 'i32'",
+        ".tmp_source.zig:5:5: note: function declared here");
+
+    add_compile_fail_case("method call with first arg type wrong container", R"SOURCE(
+pub struct List {
+    len: usize,
+    allocator: &Allocator,
+
+    pub fn init(allocator: &Allocator) -> List {
+        List {
+            .len = 0,
+            .allocator = allocator,
+        }
+    }
+}
+
+pub var global_allocator = Allocator {
+    .field = 1234,
+};
+
+pub struct Allocator {
+    field: i32,
+}
+
+fn foo() {
+    var x = List.init(&global_allocator);
+    x.init();
+}
+    )SOURCE", 2,
+        ".tmp_source.zig:24:11: error: function called as method of 'List', but first parameter is of type '&Allocator'",
+        ".tmp_source.zig:6:9: note: function declared here");
 }
 
 //////////////////////////////////////////////////////////////////////////////