diff --git a/compiler/typecheck/step1_global_symbols.jou b/compiler/typecheck/step1_global_symbols.jou index 8efeb3a5c..734b513a7 100644 --- a/compiler/typecheck/step1_global_symbols.jou +++ b/compiler/typecheck/step1_global_symbols.jou @@ -125,6 +125,7 @@ def typecheck_statement_with_symbol(stmt: AstStatement*) -> None: stmt.symbol().signature = typecheck_signature(jou_file, &stmt.method, self_class) case AstStatementKind.Class: t = create_empty_class(stmt.symbol().name) + t.classdata.definition = stmt if stmt.classdef.is_generic(): for nameptr = stmt.classdef.generic_typevar_names.ptr; nameptr < stmt.classdef.generic_typevar_names.end(); nameptr++: t.classdata.generic_params.append(create_typevar(*nameptr)) diff --git a/compiler/typecheck/step3_function_and_method_bodies.jou b/compiler/typecheck/step3_function_and_method_bodies.jou index b61e6f690..9d63b25ac 100644 --- a/compiler/typecheck/step3_function_and_method_bodies.jou +++ b/compiler/typecheck/step3_function_and_method_bodies.jou @@ -629,6 +629,49 @@ def nth(n: int) -> byte[100]: return result +def fail_with_method_not_found( + state: State*, + location: Location, + self_class: Type*, + method_name: byte*, +) -> noreturn: + assert self_class.kind == TypeKind.Class + class_ast = self_class.classdata.definition + assert class_ast != NULL + assert class_ast.kind == AstStatementKind.Class + + msg: byte[500] + + # Is the method in the class definition? + for stmt = class_ast.classdef.body.ptr; stmt < class_ast.classdef.body.end(); stmt++: + if ( + stmt.kind == AstStatementKind.MethodDef + and strcmp(stmt.method.ast_signature.name, method_name) == 0 + and strcmp(stmt.location.path, state.jou_file.path) != 0 + ): + # Method exists + if stmt.symbol().public: + # TODO: generate something that can be copy/pasted to write an import statement? + snprintf( + msg, sizeof(msg), + "to use the '%s' method of class %s, you need to import the file that defines it (%s)", + method_name, + self_class.name, + stmt.location.path, + ) + else: + snprintf( + msg, sizeof(msg), + "method '%s' of class %s is private (maybe add @public to it?)", + method_name, + self_class.name, + ) + fail(location, msg) + + snprintf(msg, sizeof(msg), "class %s has no method named '%s'", self_class.name, method_name) + fail(location, msg) + + # This is for code that looks like obj.member(). It could be a method call or a # function pointer call, because `obj` may have a field whose type is funcptr. # @@ -684,21 +727,7 @@ def typecheck_member_that_looks_like_method(state: State*, call: AstCall*) -> Ty field = self_class.find_class_field(member_name) if field == NULL: - # TODO: better error messages!!!!! - big_msg: byte* = NULL - asprintf( - &big_msg, - "method '%s' not found in class %s. The problem is ONE of the following: 1) you didn't import the file that defines class %.*s, 2) the '%s' method is private, 3) there really is no such method in class %.*s. I know that this is a bad compiler error message, and I will later split this into 3 different error messages.", - member_name, - self_class.name, - strcspn(self_class.name, "[") as int, # List[int] --> List - self_class.name, - member_name, - strcspn(self_class.name, "[") as int, # List[int] --> List - self_class.name, - ) - assert big_msg != NULL - fail(func_location, big_msg) + fail_with_method_not_found(state, func_location, self_class, member_name) # It is a function pointer call where the function is in a class field. # It looks like obj.field(). diff --git a/compiler/types.jou b/compiler/types.jou index b8364b0d4..80b07a069 100644 --- a/compiler/types.jou +++ b/compiler/types.jou @@ -21,6 +21,7 @@ class ClassField: class ClassData: generic_params: List[Type*] # this is the "int" part of List[int] fields: List[ClassField] + definition: AstStatement* @public def is_generic(self) -> bool: diff --git a/tests/404/import_private_method.jou b/tests/404/import_private_method.jou index cacf03eea..f37c82557 100644 --- a/tests/404/import_private_method.jou +++ b/tests/404/import_private_method.jou @@ -3,4 +3,4 @@ import "./imported/privates.jou" def foo() -> None: p = PublicClass{} # TODO: ideally error message would say that it exists but is private - p.private_method() # Error: method 'private_method' not found in class PublicClass. The problem is ONE of the following: 1) you didn't import the file that defines class PublicClass, 2) the 'private_method' method is private, 3) there really is no such method in class PublicClass. I know that this is a bad compiler error message, and I will later split this into 3 different error messages. + p.private_method() # Error: method 'private_method' of class PublicClass is private (maybe add @public to it?) diff --git a/tests/404/indirect_import_method.jou b/tests/404/indirect_import_method.jou index 277e51ac7..aba5d1db7 100644 --- a/tests/404/indirect_import_method.jou +++ b/tests/404/indirect_import_method.jou @@ -2,5 +2,4 @@ import "./imported/point_factory.jou" def blah() -> int: p = make_point() - # TODO: Improve this error! - return p.get_sum() # Error: method 'get_sum' not found in class Point. The problem is ONE of the following: 1) you didn't import the file that defines class Point, 2) the 'get_sum' method is private, 3) there really is no such method in class Point. I know that this is a bad compiler error message, and I will later split this into 3 different error messages. + return p.get_sum() # Error: to use the 'get_sum' method of class Point, you need to import the file that defines it (tests/404/imported/point.jou) diff --git a/tests/404/method.jou b/tests/404/method.jou index 8e850d5d0..62306f051 100644 --- a/tests/404/method.jou +++ b/tests/404/method.jou @@ -3,4 +3,4 @@ class Foo: def main() -> int: f = Foo{x=1} - f.bar() # Error: method 'bar' not found in class Foo. The problem is ONE of the following: 1) you didn't import the file that defines class Foo, 2) the 'bar' method is private, 3) there really is no such method in class Foo. I know that this is a bad compiler error message, and I will later split this into 3 different error messages. + f.bar() # Error: class Foo has no method named 'bar'