diff --git a/releasenotes.md b/releasenotes.md index dea23cd30..c8b09395a 100644 --- a/releasenotes.md +++ b/releasenotes.md @@ -3,6 +3,7 @@ ## 0.7.10 Change list ### Changes / improvements +- Method resolution and `$define` now works together well unless definitions are out of order for real. ### Stdlib changes - Summarize sort macros as generic function wrappers to reduce the amount of generated code. #2831 diff --git a/src/compiler/compiler_internal.h b/src/compiler/compiler_internal.h index 98f19f27a..4229ed761 100644 --- a/src/compiler/compiler_internal.h +++ b/src/compiler/compiler_internal.h @@ -720,6 +720,7 @@ typedef struct Decl_ bool attr_structlike : 1; bool is_template : 1; bool is_templated : 1; + bool is_method_checked : 1; union { void *backend_ref; @@ -2016,6 +2017,7 @@ typedef struct HTable features; Module std_module; MethodTable method_extensions; + Type **types_with_failed_methods; Decl **method_extension_list; DeclTable symbols; PathTable path_symbols; diff --git a/src/compiler/sema_decls.c b/src/compiler/sema_decls.c index 537973b8e..605e46817 100644 --- a/src/compiler/sema_decls.c +++ b/src/compiler/sema_decls.c @@ -13,7 +13,7 @@ static inline bool sema_check_param_uniqueness_and_type(SemaContext *context, De static inline bool sema_analyse_method(SemaContext *context, Decl *decl); static inline bool sema_is_valid_method_param(SemaContext *context, Decl *param, Type *parent_type, bool is_dynamic); static inline bool sema_analyse_macro_method(SemaContext *context, Decl *decl); -static inline bool unit_add_base_extension_method(SemaContext *context, Decl *method); +static inline bool unit_add_base_extension_method(SemaContext *context, Type *type, Decl *method); static inline bool type_add_method(SemaContext *context, Type *parent_type, Decl *method); static bool sema_analyse_operator_common(SemaContext *context, Decl *method, TypeInfo **rtype_ptr, Decl ***params_ptr, uint32_t parameters); static inline bool sema_analyse_operator_element_at(SemaContext *context, Decl *method); @@ -2295,7 +2295,7 @@ INLINE SourceSpan method_find_overload_span(Decl *method) return method->attrs_resolved->overload; } -static inline bool unit_add_base_extension_method(SemaContext *context, Decl *method) +static inline bool unit_add_base_extension_method(SemaContext *context, Type *type, Decl *method) { Decl *other = declptrzero(methodtable_set(&compiler.context.method_extensions, method)); if (other) @@ -2304,6 +2304,13 @@ static inline bool unit_add_base_extension_method(SemaContext *context, Decl *me SEMA_NOTE(other, "The previous definition was here."); return false; } + FOREACH(Type *, t, compiler.context.types_with_failed_methods) + { + if (t == type) + { + RETURN_SEMA_ERROR(method, "A method was added to %s which already was checked for method availability, declarations must be reordered.", type_quoted_error_string(type)); + } + } vec_add(compiler.context.method_extension_list, method); DEBUG_LOG("Builtin type method '%s' analysed.", method->name); return true; @@ -2636,7 +2643,7 @@ static inline bool type_add_method(SemaContext *context, Type *parent_type, Decl return true; } // Is it a base extension? - if (!type_is_user_defined(parent_type)) return unit_add_base_extension_method(context, method); + if (!type_is_user_defined(parent_type)) return unit_add_base_extension_method(context, parent_type, method); // Resolve it as a user-defined type extension. Decl *parent = parent_type->decl; @@ -2653,7 +2660,10 @@ static inline bool type_add_method(SemaContext *context, Type *parent_type, Decl SEMA_NOTE(other, "The previous definition was here."); return decl_poison(method); } - + if (parent->is_method_checked) + { + RETURN_SEMA_ERROR(method, "A method was added to %s which already was checked for method availability, declarations must be reordered.", type_quoted_error_string(parent_type)); + } DEBUG_LOG("Method-like '%s.%s' analysed.", parent->name, method->name); Methods *table = parent->method_table; diff --git a/src/compiler/sema_expr.c b/src/compiler/sema_expr.c index 76e4393e6..4b88a67de 100644 --- a/src/compiler/sema_expr.c +++ b/src/compiler/sema_expr.c @@ -5059,7 +5059,11 @@ static inline bool sema_expr_analyse_type_access(SemaContext *context, Expr *exp if (!decl_ok(member)) return false; if (!member) { - if (missing_ref) goto MISSING_REF; + if (missing_ref) + { + vec_add(compiler.context.types_with_failed_methods, parent_type); + goto MISSING_REF; + } RETURN_SEMA_ERROR(expr, "'%s' does not have a property or method '%s'.", type_to_error_string(parent_type), name); } expr_resolve_ident(expr, member); @@ -5106,8 +5110,7 @@ static inline bool sema_expr_analyse_type_access(SemaContext *context, Expr *exp { if (decl->unit->module->stage < ANALYSIS_POST_REGISTER) { - bool err = SEMA_WARN_STRICT(expr, "There might be a method '%s' for %s, but methods for the type have not yet been completely registered, so a warning is issued.", name, type_quoted_error_string(parent_type)); - if (err) return false; + decl->is_method_checked = true; } goto MISSING_REF; } @@ -5205,6 +5208,8 @@ static inline bool sema_expr_analyse_member_access(SemaContext *context, Expr *e sema_create_const_membersof(expr, decl->type->canonical, parent->const_expr.member.align, parent->const_expr.member.offset); return true; case TYPE_PROPERTY_METHODSOF: + decl->is_method_checked = true; + FALLTHROUGH; case TYPE_PROPERTY_KINDOF: case TYPE_PROPERTY_SIZEOF: return sema_expr_rewrite_to_type_property(context, expr, decl->type->canonical, type_property, decl->type->canonical); @@ -5260,6 +5265,7 @@ static inline bool sema_expr_analyse_member_access(SemaContext *context, Expr *e return true; MISSING_REF: *missing_ref = true; + decl->is_method_checked = true; return false; } @@ -6551,7 +6557,11 @@ CHECK_DEEPER: Decl *method = sema_resolve_type_method(context, type, kw); if (!method) { - if (missing_ref) goto MISSING_REF; + if (missing_ref) + { + vec_add(compiler.context.types_with_failed_methods, type); + goto MISSING_REF; + } RETURN_SEMA_ERROR(expr, "There is no member or method '%s' on %s", kw, type_quoted_error_string(parent->type)); } @@ -6608,6 +6618,13 @@ CHECK_DEEPER: // 11. If we didn't find a match... if (!member) { + Type *parent_type = type_no_optional(parent->type)->canonical; + ASSERT(type_is_user_defined(parent_type)); + // Tag as maybe wrong. + if (!private && missing_ref && parent_type->decl->unit->module->stage < ANALYSIS_POST_REGISTER) + { + parent_type->decl->is_method_checked = true; + } // 11a. We have a potential embedded struct check: Expr *substruct = sema_enter_inline_member(current_parent, type); if (substruct) @@ -6625,13 +6642,7 @@ CHECK_DEEPER: if (missing_ref) goto MISSING_REF; RETURN_SEMA_ERROR(expr, "The method '%s' has private visibility.", kw); } - Type *parent_type = type_no_optional(parent->type)->canonical; - ASSERT(type_is_user_defined(parent_type)); - if (missing_ref && parent_type->decl->unit->module->stage < ANALYSIS_POST_REGISTER) - { - bool err = SEMA_WARN_STRICT(expr, "There might be a method '%s' for %s, but methods have not yet been completely registered, so analysis fails.", kw, type_quoted_error_string(parent->type)); - if (err) return false; - } + if (parent_type->type_kind == TYPE_INTERFACE) { if (missing_ref) goto MISSING_REF; @@ -6666,7 +6677,6 @@ CHECK_DEEPER: } } // 13. Copy properties. - expr->access_resolved_expr = (ExprResolvedAccess) { .parent = current_parent, .ref = member }; if (expr->expr_kind == EXPR_ACCESS_UNRESOLVED) expr->expr_kind = EXPR_ACCESS_RESOLVED; expr->type = type_add_optional(member->type, optional); diff --git a/src/compiler/sema_internal.h b/src/compiler/sema_internal.h index 9483db36c..0338b34bf 100644 --- a/src/compiler/sema_internal.h +++ b/src/compiler/sema_internal.h @@ -18,7 +18,6 @@ #define UINT20_MAX 1048575U #define SEMA_WARN(_node, ...) (sema_warn_at(context, (_node)->span, __VA_ARGS__)) -#define SEMA_WARN_STRICT(_node, ...) (sema_warn_very_strict(context, (_node)->span, __VA_ARGS__)) #define SEMA_ERROR(_node, ...) sema_error_at(context, (_node)->span, __VA_ARGS__) #define RETURN_SEMA_ERROR(_node, ...) do { sema_error_at(context, (_node)->span, __VA_ARGS__); return false; } while (0) #define RETURN_VAL_SEMA_ERROR(val__, _node, ...) do { sema_error_at(context, (_node)->span, __VA_ARGS__); return (val__); } while (0) diff --git a/test/test_suite/methods/method_resolution_order.c3 b/test/test_suite/methods/method_resolution_order.c3 index b3658427e..dd10e4856 100644 --- a/test/test_suite/methods/method_resolution_order.c3 +++ b/test/test_suite/methods/method_resolution_order.c3 @@ -1,11 +1,11 @@ module test2; import test3; -struct Bar @if($defined(Foo.b)) // warning: There might be a method 'b' for 'Foo', but methods for the type have not yet been +struct Bar @if($defined(Foo.b)) { int a; } -struct Bar2 @if($defined((Foo){}.b)) // warning: There might be a method 'b' for 'Foo', but methods have not yet been completely +struct Bar2 @if($defined((Foo){}.b)) { int a; } @@ -15,6 +15,8 @@ struct Foo { int a; } +fn void Foo.b(&self) {} // #error: A method was added to 'Foo' which already was checked for method availability + module test; import test2; @@ -22,6 +24,6 @@ import test3; fn int main() { - Bar b; // #error: 'Bar' could not be found, did you spell it right + Bar b; return 0; } \ No newline at end of file