From 198e3c369c307f07031c26cf4e110eb4b68b228d Mon Sep 17 00:00:00 2001 From: Christoffer Lerno Date: Wed, 21 Jul 2021 19:28:02 +0200 Subject: [PATCH] Extension methods added. Some initial work on virtual. --- src/compiler/ast.c | 14 +++ src/compiler/compiler.c | 1 + src/compiler/compiler_internal.h | 6 +- src/compiler/context.c | 1 + src/compiler/copying.c | 2 + src/compiler/enums.h | 3 +- src/compiler/parse_global.c | 4 +- src/compiler/sema_casts.c | 40 ++++++- src/compiler/sema_decls.c | 113 +++++++++++------- src/compiler/sema_expr.c | 11 ++ src/compiler/sema_name_resolution.c | 66 ++++++++++ src/compiler/sema_types.c | 1 + .../macro_methods_defined_twice.c3 | 31 +++++ test/test_suite/methods/extension_method.c3t | 37 ++++++ .../methods/extension_method_already_exist.c3 | 30 +++++ .../methods/methods_defined_twice.c3 | 31 +++++ 16 files changed, 339 insertions(+), 52 deletions(-) create mode 100644 test/test_suite/macro_methods/macro_methods_defined_twice.c3 create mode 100644 test/test_suite/methods/extension_method.c3t create mode 100644 test/test_suite/methods/extension_method_already_exist.c3 create mode 100644 test/test_suite/methods/methods_defined_twice.c3 diff --git a/src/compiler/ast.c b/src/compiler/ast.c index 4b74e1ad2..eb4b5016a 100644 --- a/src/compiler/ast.c +++ b/src/compiler/ast.c @@ -87,6 +87,8 @@ const char *decl_to_name(Decl *decl) return "function"; case DECL_GENERIC: return "generic"; + case DECL_GENFUNC: + TODO case DECL_INTERFACE: return "interface"; case DECL_MACRO: @@ -178,6 +180,7 @@ Decl *decl_new_with_type(TokenId name, DeclKind decl_type, Visibility visibility case DECL_ARRAY_VALUE: case DECL_IMPORT: case DECL_MACRO: + case DECL_GENFUNC: case DECL_GENERIC: case DECL_CT_IF: case DECL_CT_ELSE: @@ -864,6 +867,7 @@ void fprint_decl_recursive(Context *context, FILE *file, Decl *decl, int indent) { case DECL_INTERFACE: DUMPF("(interface %s", decl->name); + DUMPDECLS(decl->interface_decl.members); DUMPDECLS(decl->interface_decl.functions); DUMPEND(); case DECL_VAR: @@ -910,6 +914,16 @@ void fprint_decl_recursive(Context *context, FILE *file, Decl *decl, int indent) indent--; DUMPAST(decl->macro_decl.body); DUMPEND(); + case DECL_GENFUNC: + DUMPF("(macro %s", decl->name); + DUMPTI(decl->macro_decl.rtype); + indent++; + DUMP("(params"); + DUMPDECLS(decl->macro_decl.parameters); + DUMPE(); + indent--; + DUMPAST(decl->macro_decl.body); + DUMPEND(); case DECL_FUNC: DUMPF("(func %s", decl->name); if (decl->func_decl.type_parent) diff --git a/src/compiler/compiler.c b/src/compiler/compiler.c index 44fef6dfa..5b4d8b3b4 100644 --- a/src/compiler/compiler.c +++ b/src/compiler/compiler.c @@ -183,6 +183,7 @@ static void register_generic_decls(Module *module, Decl **decls) case DECL_DISTINCT: case DECL_ENUM: case DECL_GENERIC: + case DECL_GENFUNC: case DECL_INTERFACE: case DECL_ERR: case DECL_FUNC: diff --git a/src/compiler/compiler_internal.h b/src/compiler/compiler_internal.h index e4ecd9461..1acbe4298 100644 --- a/src/compiler/compiler_internal.h +++ b/src/compiler/compiler_internal.h @@ -411,6 +411,7 @@ typedef struct typedef struct { Decl **functions; + Decl **members; } InterfaceDecl; typedef struct @@ -1234,7 +1235,7 @@ typedef struct Module_ Ast **files; // Asts - Decl** functions; + Decl** method_extensions; STable symbols; STable public_symbols; struct Context_ **contexts; @@ -1762,6 +1763,7 @@ const char *resolve_status_to_string(ResolveStatus status); #define SEMA_TOKID_ERROR(_tok_id, ...) sema_error_range(source_span_from_token_id(_tok_id), __VA_ARGS__) #define SEMA_ERROR(_node, ...) sema_error_range((_node)->span, __VA_ARGS__) #define SEMA_PREV(_node, ...) sema_prev_at_range3((_node)->span, __VA_ARGS__) +#define SEMA_TOKID_PREV(_tok_id, ...) sema_prev_at_range3(source_span_from_token_id(_tok_id), __VA_ARGS__) void sema_analysis_pass_process_imports(Module *module); void sema_analysis_pass_register_globals(Module *module); @@ -1786,6 +1788,8 @@ bool sema_expr_analyse_assign_right_side(Context *context, Expr *expr, Type *lef Decl *sema_resolve_symbol_in_current_dynamic_scope(Context *context, const char *symbol); Decl *sema_resolve_parameterized_symbol(Context *context, TokenId symbol, Path *path); +Decl *sema_resolve_method(Context *context, Decl *type, const char *method_name, Decl **ambiguous_ref, Decl **private_ref); +Decl *sema_find_extension_method_in_module(Module *module, Type *type, const char *method_name); Decl *sema_resolve_normal_symbol(Context *context, TokenId symbol, Path *path, bool handle_error); Decl *sema_resolve_string_symbol(Context *context, const char *symbol, SourceSpan span, Path *path); diff --git a/src/compiler/context.c b/src/compiler/context.c index 573b20f61..cc41f95e4 100644 --- a/src/compiler/context.c +++ b/src/compiler/context.c @@ -113,6 +113,7 @@ void context_register_global_decl(Context *context, Decl *decl) vec_add(context->interfaces, decl); decl_set_external_name(decl); break; + case DECL_GENFUNC: case DECL_GENERIC: vec_add(context->generics, decl); decl_set_external_name(decl); diff --git a/src/compiler/copying.c b/src/compiler/copying.c index d4662d5e9..0fffa0100 100644 --- a/src/compiler/copying.c +++ b/src/compiler/copying.c @@ -498,6 +498,7 @@ Decl *copy_decl(Decl *decl) case DECL_INTERFACE: copy_decl_type(copy); MACRO_COPY_DECL_LIST(copy->interface_decl.functions); + MACRO_COPY_DECL_LIST(copy->interface_decl.members); break; case DECL_FUNC: MACRO_COPY_TYPE(copy->func_decl.type_parent); @@ -560,6 +561,7 @@ Decl *copy_decl(Decl *decl) break; case DECL_ARRAY_VALUE: TODO + case DECL_GENFUNC: case DECL_MACRO: MACRO_COPY_TYPE(decl->macro_decl.type_parent); MACRO_COPY_DECL_LIST(decl->macro_decl.parameters); diff --git a/src/compiler/enums.h b/src/compiler/enums.h index 367509b67..9f41e5a53 100644 --- a/src/compiler/enums.h +++ b/src/compiler/enums.h @@ -149,6 +149,7 @@ typedef enum DECL_INTERFACE, DECL_LABEL, DECL_MACRO, + DECL_GENFUNC, DECL_STRUCT, DECL_TYPEDEF, DECL_UNION, @@ -158,7 +159,7 @@ typedef enum #define NON_TYPE_DECLS DECL_ARRAY_VALUE: case DECL_IMPORT: case DECL_MACRO: \ case DECL_GENERIC: case DECL_CT_IF: case DECL_CT_ELSE: case DECL_CT_ELIF: \ case DECL_CT_SWITCH: case DECL_CT_CASE: case DECL_ATTRIBUTE: case DECL_LABEL: \ - case DECL_DEFINE: case DECL_CT_ASSERT + case DECL_DEFINE: case DECL_CT_ASSERT: case DECL_GENFUNC typedef enum { diff --git a/src/compiler/parse_global.c b/src/compiler/parse_global.c index 82b0908b2..df50d4ede 100644 --- a/src/compiler/parse_global.c +++ b/src/compiler/parse_global.c @@ -48,7 +48,7 @@ static bool context_next_is_type_and_not_ident(Context *context) { if (context->tok.type == TOKEN_IDENT) { - if (context->next_tok.type != TOKEN_COLON) return false; + if (context->next_tok.type != TOKEN_SCOPE) return false; return context_next_is_type_with_path_prefix(context); } return token_is_any_type(context->tok.type); @@ -1948,7 +1948,7 @@ static inline Decl *parse_interface_declaration(Context *context, Visibility vis return poisoned_decl; } Decl *function = TRY_DECL_OR(parse_func_definition(context, visibility, true), poisoned_decl); - vec_add(decl->interface_decl.functions, function); + vec_add(decl->methods, function); } CONSUME_OR(TOKEN_RBRACE, poisoned_decl); diff --git a/src/compiler/sema_casts.c b/src/compiler/sema_casts.c index 5865e3d9e..5de0f799e 100644 --- a/src/compiler/sema_casts.c +++ b/src/compiler/sema_casts.c @@ -464,6 +464,43 @@ bool cast_may_explicit(Type *from_type, Type *to_type) UNREACHABLE } +static bool may_cast_to_virtual(Type *virtual, Type *from) +{ + assert(from->canonical == from); + + // 1. We need a pointer, we can't cast from a non pointer. + if (from->type_kind != TYPE_POINTER) return false; + + // 2. Virtual* converts to anything, including ints + if (virtual->type_kind == TYPE_VIRTUAL_ANY) return true; + + // 3. Get the data. + Decl *virtual_decl = virtual->decl; + Decl **methods = virtual_decl->interface_decl.functions; + + // 4. No variables nor members? Then this is essentially a virtual* + if (!vec_size(methods) && !vec_size(virtual_decl->strukt.members)) return true; + + // 5. Look at the pointer. + Type *pointee = from->pointer; + + // 6. Is this an array, if so it doesn't have any functions, + // so we implicitly lower to the first element. + if (pointee->type_kind == TYPE_ARRAY) + { + pointee = pointee->array.base; + } + + // Do this: create a function that returns a matching interface method. + // store this decl. + // Same with looking at members -> store the Decl. + // Later, generating the table we provide the decl backend ref and the offset. + // Note that matching types should take into account the first element. + // Also go recursively into substructs structs + // Note that this resolution cannot be cached completely due to the module import lookup + + TODO; +} /** * Can the conversion occur implicitly? */ @@ -581,8 +618,7 @@ bool cast_may_implicit(Type *from_type, Type *to_type) // 10. Virtual cast if (to->type_kind == TYPE_VIRTUAL) { - TODO -// + return may_cast_to_virtual(to, from); } // 11. Substruct cast, if the first member is inline, see if we can cast to this member. diff --git a/src/compiler/sema_decls.c b/src/compiler/sema_decls.c index 7f86b6ca9..b64d68172 100644 --- a/src/compiler/sema_decls.c +++ b/src/compiler/sema_decls.c @@ -602,7 +602,67 @@ static inline bool sema_analyse_enum(Context *context, Decl *decl) return success; } +static inline const char *name_by_decl(Decl *method_like) +{ + switch (method_like->decl_kind) + { + case DECL_MACRO: + return "macro method"; + case DECL_FUNC: + return "method"; + case DECL_GENFUNC: + return "generic method"; + default: + UNREACHABLE + } +} +static inline bool sema_add_method_like(Context *context, Type *parent_type, Decl *method_like) +{ + assert(parent_type->canonical == parent_type); + Decl *parent = parent_type->decl; + const char *name = method_like->name; + Decl *method = sema_find_extension_method_in_module(context->module, parent_type, name); + if (method) + { + SEMA_TOKID_ERROR(method_like->name_token, "This %s is already defined in this module.", name_by_decl(method_like)); + SEMA_TOKID_PREV(method->name_token, "The previous definition was here."); + return false; + } + Decl *ambiguous = NULL; + Decl *private = NULL; + method = sema_resolve_method(context, parent, name, &ambiguous, &private); + if (method) + { + SEMA_TOKID_ERROR(method_like->name_token, "This %s is already defined for '%s'.", name_by_decl(method_like), parent_type->name); + SEMA_TOKID_PREV(method->name_token, "The previous definition was here."); + return false; + } + scratch_buffer_clear(); + if (method_like->visibility <= VISIBLE_MODULE) + { + scratch_buffer_append(parent->name); + scratch_buffer_append_char('.'); + scratch_buffer_append(method_like->name); + } + else + { + scratch_buffer_append(parent->external_name); + scratch_buffer_append("__"); + scratch_buffer_append(method_like->name); + } + method_like->external_name = scratch_buffer_interned(); + DEBUG_LOG("Method-like '%s.%s' analysed.", parent->name, method_like->name); + if (parent->module == context->module) + { + vec_add(parent->methods, method_like); + } + else + { + vec_add(context->module->method_extensions, method_like); + } + return true; +} static inline bool sema_analyse_method(Context *context, Decl *decl) { @@ -611,39 +671,12 @@ static inline bool sema_analyse_method(Context *context, Decl *decl) if (!type_may_have_sub_elements(parent_type->type)) { SEMA_ERROR(decl, - "Methods can not be associated with '%s'", - type_to_error_string(decl->func_decl.type_parent->type)); + "Methods can not be associated with '%s'", + type_to_error_string(decl->func_decl.type_parent->type)); return false; } - Decl *parent = parent_type->type->decl; - VECEACH(parent->methods, i) - { - Decl *function = parent->methods[i]; - if (function->name == decl->name) - { - SEMA_ERROR(decl, "Duplicate name '%s' for method.", function->name); - SEMA_PREV(function, "Previous definition here."); - return false; - } - } - scratch_buffer_clear(); - if (decl->visibility <= VISIBLE_MODULE) - { - scratch_buffer_append(parent->name); - scratch_buffer_append_char('.'); - scratch_buffer_append(decl->name); - } - else - { - scratch_buffer_append(parent->external_name); - scratch_buffer_append("__"); - scratch_buffer_append(decl->name); - } - decl->external_name = scratch_buffer_interned(); - DEBUG_LOG("Method '%s.%s' analysed.", parent->name, decl->name); - vec_add(parent->methods, decl); - - return true; + Type *type = parent_type->type->canonical; + return sema_add_method_like(context, type, decl); } static inline AttributeType attribute_by_name(Attr *attr) @@ -971,21 +1004,7 @@ static bool sema_analyse_macro_method(Context *context, Decl *decl) SEMA_ERROR(first_param, "The first parameter must be a regular value parameter."); return false; } - Decl *parent = parent_type->decl; - VECEACH(parent->methods, i) - { - Decl *function = parent->methods[i]; - if (function->name == decl->name) - { - SEMA_ERROR(decl, "Duplicate name '%s' for macro method.", function->name); - SEMA_PREV(function, "Previous definition here."); - return false; - } - } - DEBUG_LOG("Macro method '%s.%s' analysed.", parent->name, decl->name); - vec_add(parent->methods, decl); - - return true; + return sema_add_method_like(context, parent_type, decl); } static inline bool sema_analyse_macro(Context *context, Decl *decl) @@ -1439,6 +1458,8 @@ bool sema_analyse_decl(Context *context, Decl *decl) case DECL_FUNC: if (!sema_analyse_func(context, decl)) return decl_poison(decl); break; + case DECL_GENFUNC: + TODO case DECL_MACRO: if (!sema_analyse_macro(context, decl)) return decl_poison(decl); break; diff --git a/src/compiler/sema_expr.c b/src/compiler/sema_expr.c index 8fb877910..0f4e1cd4f 100644 --- a/src/compiler/sema_expr.c +++ b/src/compiler/sema_expr.c @@ -224,6 +224,7 @@ static inline bool sema_cast_ident_rvalue(Context *context, Type *to, Expr *expr UNREACHABLE case DECL_IMPORT: case DECL_GENERIC: + case DECL_GENFUNC: case DECL_CT_IF: case DECL_CT_ELSE: case DECL_CT_ELIF: @@ -2380,6 +2381,16 @@ CHECK_DEEPER: member = sema_resolve_symbol_in_current_dynamic_scope(context, kw); SCOPE_END; + if (!member) + { + Decl *ambiguous = NULL; + Decl *private = NULL; + member = sema_resolve_method(context, decl, kw, &ambiguous, &private); + if (member) + { + context_register_external_symbol(context, member); + } + } // 11. If we didn't find a match... if (!member) { diff --git a/src/compiler/sema_name_resolution.c b/src/compiler/sema_name_resolution.c index 6b31eb81b..9f6d23ccc 100644 --- a/src/compiler/sema_name_resolution.c +++ b/src/compiler/sema_name_resolution.c @@ -231,6 +231,72 @@ static Decl *sema_resolve_symbol(Context *context, const char *symbol_str, Sourc return decl; } +Decl *sema_find_extension_method_in_module(Module *module, Type *type, const char *method_name) +{ + Decl **extensions = module->method_extensions; + VECEACH(extensions, i) + { + Decl *extension = extensions[i]; + if (extension->name != method_name) continue; + switch (extension->decl_kind) + { + case DECL_FUNC: + if (extension->func_decl.type_parent->type == type) return extension; + break; + case DECL_MACRO: + case DECL_GENFUNC: + if (extension->macro_decl.type_parent->type == type) return extension; + break; + default: + UNREACHABLE + } + } + return NULL; +} + +Decl *sema_resolve_method(Context *context, Decl *type, const char *method_name, Decl **ambiguous_ref, Decl **private_ref) +{ + // 1. Look at the previously defined ones. + VECEACH(type->methods, i) + { + Decl *func = type->methods[i]; + if (method_name == func->name) return func; + } + // 2. Make a module lookup + Decl *previously_found = NULL; + Type *actual_type = type->type; + Decl *private_type = NULL; + Decl *result = NULL; + VECEACH(context->imports, i) + { + Decl *import = context->imports[i]; + + if (import->module->is_generic) continue; + + Decl *found = sema_find_extension_method_in_module(import->module, actual_type, method_name); + if (!found) continue; + if (found->visibility <= VISIBLE_MODULE && !import->import.private) + { + private_type = found; + continue; + } + + if (result) + { + *ambiguous_ref = previously_found; + *private_ref = NULL; + return NULL; + } + result = found; + } + + if (result && private_type) + { + private_type = NULL; + } + return result; +} + Decl *sema_resolve_parameterized_symbol(Context *context, TokenId symbol, Path *path) { Decl *ambiguous_other_decl = NULL; diff --git a/src/compiler/sema_types.c b/src/compiler/sema_types.c index b4d73aace..bfb00a2c9 100644 --- a/src/compiler/sema_types.c +++ b/src/compiler/sema_types.c @@ -119,6 +119,7 @@ static bool sema_resolve_type_identifier(Context *context, TypeInfo *type_info) case DECL_ARRAY_VALUE: case DECL_IMPORT: case DECL_MACRO: + case DECL_GENFUNC: case DECL_GENERIC: case DECL_LABEL: SEMA_TOKID_ERROR(type_info->unresolved.name_loc, "This is not a type."); diff --git a/test/test_suite/macro_methods/macro_methods_defined_twice.c3 b/test/test_suite/macro_methods/macro_methods_defined_twice.c3 new file mode 100644 index 000000000..6b6517cf0 --- /dev/null +++ b/test/test_suite/macro_methods/macro_methods_defined_twice.c3 @@ -0,0 +1,31 @@ +module foo; + +struct Bar +{ + int x; +} + +module baz; +import foo; +import std::io; + +macro void foo::Bar.test(Bar *bar) +{ + io::println("Inside of baz::Bar.test"); +} + +macro void Bar.test(Bar *bar) // #error: This macro method is already defined in this module +{ + io::println("Inside of baz::Bar.test"); +} + +module abc; +import foo; +import baz; + +func void main() +{ + Bar bar; + bar.test(); +} + diff --git a/test/test_suite/methods/extension_method.c3t b/test/test_suite/methods/extension_method.c3t new file mode 100644 index 000000000..2e8922da0 --- /dev/null +++ b/test/test_suite/methods/extension_method.c3t @@ -0,0 +1,37 @@ +module foo; + +struct Bar +{ + int x; +} + +module baz; +import foo; +import std::io; + +func void foo::Bar.test(Bar *bar) +{ + io::println("Inside of baz::Bar.test"); +} + +module abc; +import foo; +import baz; + +func void main() +{ + Bar bar; + bar.test(); +} + +// #expect: abc.ll + +declare void @foo.Bar__test(%Bar*) + +define void @main() +entry: + %bar = alloca %Bar, align 4 + %0 = bitcast %Bar* %bar to i8* + call void @llvm.memset.p0i8.i64(i8* align 4 %0, i8 0, i64 4, i1 false) + call void @foo.Bar__test(%Bar* %bar) + ret void diff --git a/test/test_suite/methods/extension_method_already_exist.c3 b/test/test_suite/methods/extension_method_already_exist.c3 new file mode 100644 index 000000000..574e7a965 --- /dev/null +++ b/test/test_suite/methods/extension_method_already_exist.c3 @@ -0,0 +1,30 @@ +module foo; + +func void Bar.test(Bar *bar) +{ + io::println("Inside of baz::Bar.test"); +} + +struct Bar +{ + int x; +} + +module baz; +import foo; +import std::io; + +func void foo::Bar.test(Bar *bar) // #error: This method is already defined for 'Bar' +{ + io::println("Inside of baz::Bar.test"); +} + +module abc; +import foo; +import baz; + +func void main() +{ + Bar bar; + bar.test(); +} \ No newline at end of file diff --git a/test/test_suite/methods/methods_defined_twice.c3 b/test/test_suite/methods/methods_defined_twice.c3 new file mode 100644 index 000000000..67adb4b0e --- /dev/null +++ b/test/test_suite/methods/methods_defined_twice.c3 @@ -0,0 +1,31 @@ +module foo; + +struct Bar +{ + int x; +} + +module baz; +import foo; +import std::io; + +func void foo::Bar.test(Bar *bar) +{ + io::println("Inside of baz::Bar.test"); +} + +func void Bar.test(Bar *bar) // #error: This method is already defined in this module +{ + io::println("Inside of baz::Bar.test"); +} + +module abc; +import foo; +import baz; + +func void main() +{ + Bar bar; + bar.test(); +} +