From 8d563eba7a71c2384524c6fe253cdbbef6f2c234 Mon Sep 17 00:00:00 2001 From: Christoffer Lerno Date: Sat, 24 May 2025 17:10:11 +0200 Subject: [PATCH] Implicit casting from struct to interface failure for inheriting interfaces #2151. Fix second bug in #2148 --- releasenotes.md | 1 + src/compiler/sema_casts.c | 19 +++-- src/compiler/sema_expr.c | 4 ++ src/compiler/sema_passes.c | 69 +++++++++++-------- .../any/interface_inheritance_fail.c3 | 13 ++++ test/test_suite/interface_multi.c3 | 29 ++++++++ .../macros/macro_body_error_2148_2.c3 | 13 ++++ 7 files changed, 116 insertions(+), 32 deletions(-) create mode 100644 test/test_suite/any/interface_inheritance_fail.c3 create mode 100644 test/test_suite/interface_multi.c3 create mode 100644 test/test_suite/macros/macro_body_error_2148_2.c3 diff --git a/releasenotes.md b/releasenotes.md index bbd46fb68..290214dcc 100644 --- a/releasenotes.md +++ b/releasenotes.md @@ -40,6 +40,7 @@ - Missing error on default values for body with default arguments #2148. - `--path` does not interact correctly with relative path arguments #2149. - Add missing `@noreturn` to `os::exit`. +- Implicit casting from struct to interface failure for inheriting interfaces #2151. ### Stdlib changes - Added `String.quick_ztr` and `String.is_zstr` diff --git a/src/compiler/sema_casts.c b/src/compiler/sema_casts.c index 67d1e2986..4ea4f6781 100644 --- a/src/compiler/sema_casts.c +++ b/src/compiler/sema_casts.c @@ -1143,14 +1143,23 @@ static bool rule_vecarr_to_infer(CastContext *cc, bool is_explicit, bool is_sile return cast_is_allowed(cc, is_explicit, is_silent); } +static inline BoolErr type_implements_interface_ignore_substruct(CastContext *cc, Decl *decl, Type *interface) +{ + FOREACH(TypeInfo *, interface_type, decl->interfaces) + { + if (!sema_resolve_type_info(cc->context, interface_type, RESOLVE_TYPE_DEFAULT)) return BOOL_ERR; + if (interface_type->type == interface) return BOOL_TRUE; + BoolErr res = type_implements_interface_ignore_substruct(cc, interface_type->type->decl, interface); + if (res != BOOL_FALSE) return res; + } + + return BOOL_FALSE; +} static inline bool type_implements_interface(CastContext *cc, Decl *decl, Type *interface) { RETRY:; - FOREACH(TypeInfo *, interface_type, decl->interfaces) - { - if (!sema_resolve_type_info(cc->context, interface_type, RESOLVE_TYPE_DEFAULT)) return false; - if (interface_type->type == interface) return true; - } + BoolErr result = type_implements_interface_ignore_substruct(cc, decl, interface); + if (result != BOOL_FALSE) return result == BOOL_TRUE; if (!decl->is_substruct) return false; Type *inner; if (decl->decl_kind == DECL_DISTINCT) diff --git a/src/compiler/sema_expr.c b/src/compiler/sema_expr.c index 01240ed4c..9db7f6a40 100644 --- a/src/compiler/sema_expr.c +++ b/src/compiler/sema_expr.c @@ -2381,6 +2381,10 @@ bool sema_expr_analyse_macro_call(SemaContext *context, Expr *call_expr, Expr *s default: UNREACHABLE } + if (body_arg->var.init_expr) + { + RETURN_SEMA_ERROR(body_arg->var.init_expr, "Macro body parameters should never have default values."); + } TypeInfo *expected_type_info = vartype(body_param); TypeInfo *type_info = vartype(body_arg); if (type_info && !sema_resolve_type_info(context, type_info, RESOLVE_TYPE_DEFAULT)) return false; diff --git a/src/compiler/sema_passes.c b/src/compiler/sema_passes.c index 03311ca26..6ac52d524 100644 --- a/src/compiler/sema_passes.c +++ b/src/compiler/sema_passes.c @@ -680,40 +680,55 @@ void sema_analysis_pass_lambda(Module *module) DEBUG_LOG("Pass finished with %d error(s).", compiler.context.errors_found); } +static bool sema_check_interface(SemaContext *context, Decl *decl, TypeInfo *interface_type, TypeInfo *original_type) +{ + Decl *interface = interface_type->type->decl; + FOREACH(Decl *, method, interface->interface_methods) + { + Decl *matching_method = sema_decl_stack_resolve_symbol(method->name); + if (!matching_method) + { + if (method->func_decl.attr_optional) continue; + if (interface_type != original_type) + { + RETURN_SEMA_ERROR(original_type, + "'%s' was not fully implemented, the required method '%s' of interface '%s' needs to be implemented, did you forget it?", + original_type->type->decl->name, method->name, interface->name); + } + RETURN_SEMA_ERROR(original_type, + "'%s' was not fully implemented, the required method '%s' needs to be implemented, did you forget it?", + interface->name, method->name); + } + if (matching_method->decl_kind != DECL_FUNC) + { + if (method->func_decl.attr_optional) continue; + RETURN_SEMA_ERROR(matching_method, "'%s' was not fully implemented, it requires '%s' to be a function marked '@dynamic'.", + interface->name, method->name); + } + if (!matching_method->func_decl.attr_dynamic) + { + SEMA_ERROR(matching_method, "'%s(...)' must be marked '@dynamic' as it matches the method '%s' in interface '%s'.", + method->name, method->name, interface->name); + SEMA_NOTE(method, "Here is the interface method to implement."); + return false; + } + } + FOREACH(TypeInfo *, parent_interface, interface->interfaces) + { + if (!sema_check_interface(context, decl, parent_interface, original_type)) return false; + } + return true; +} static inline bool sema_check_interfaces(SemaContext *context, Decl *decl) { Decl **store = sema_decl_stack_store(); FOREACH(Decl *, method, decl->methods) sema_decl_stack_push(method); FOREACH(TypeInfo *, interface_type, decl->interfaces) { - Decl *interface = interface_type->type->decl; - FOREACH(Decl *, method, interface->interface_methods) + if (!sema_check_interface(context, decl, interface_type, interface_type)) { - Decl *matching_method = sema_decl_stack_resolve_symbol(method->name); - if (!matching_method) - { - if (method->func_decl.attr_optional) continue; - SEMA_ERROR(interface_type, "'%s' was not fully implemented, required method '%s' needs to be implemented, did you forget it?", - interface->name, method->name); - sema_decl_stack_restore(store); - return false; - } - if (matching_method->decl_kind != DECL_FUNC) - { - if (method->func_decl.attr_optional) continue; - SEMA_ERROR(matching_method, "'%s' was not fully implemented, it requires '%s' to be a function marked '@dynamic'.", - interface->name, method->name); - sema_decl_stack_restore(store); - return false; - } - if (!matching_method->func_decl.attr_dynamic) - { - SEMA_ERROR(matching_method, "'%s(...)' must be marked '@dynamic' as it matches the method '%s' in interface '%s'.", - method->name, method->name, interface->name); - SEMA_NOTE(method, "Here is the interface method to implement."); - sema_decl_stack_restore(store); - return false; - } + sema_decl_stack_restore(store); + return false; } } sema_decl_stack_restore(store); diff --git a/test/test_suite/any/interface_inheritance_fail.c3 b/test/test_suite/any/interface_inheritance_fail.c3 new file mode 100644 index 000000000..7c20153a0 --- /dev/null +++ b/test/test_suite/any/interface_inheritance_fail.c3 @@ -0,0 +1,13 @@ +import std; + +interface IFoo { fn void a(); } +interface IBar { fn void b(); } + +interface IFooBar: IFoo, IBar {} + +struct Foo (IFooBar) { int _a; } // #error: was not fully implemented +struct Foo2 (IFoo, IBar) { int _a; } + +fn void Foo2.a(&s) @dynamic {} +fn void Foo2.b(&s) @dynamic {} + diff --git a/test/test_suite/interface_multi.c3 b/test/test_suite/interface_multi.c3 new file mode 100644 index 000000000..24953037a --- /dev/null +++ b/test/test_suite/interface_multi.c3 @@ -0,0 +1,29 @@ +import std; + +interface IFoo { fn void a(); } +interface IBar { fn void b(); } + +interface IFooBar: IFoo, IBar {} + +struct Foo (IFooBar) { int _a; } + +fn void Foo.a(&s) @dynamic {} +fn void Foo.b(&s) @dynamic {} + +fn void foo(IFoo o) => o.a(); +fn void bar(IBar o) => o.b(); + +fn void foobar(IFooBar o) +{ + o.a(); + o.b(); +} + +fn void main() +{ + Foo f; + + foo(&f); + bar(&f); + foobar(&f); +} diff --git a/test/test_suite/macros/macro_body_error_2148_2.c3 b/test/test_suite/macros/macro_body_error_2148_2.c3 new file mode 100644 index 000000000..d45050bf1 --- /dev/null +++ b/test/test_suite/macros/macro_body_error_2148_2.c3 @@ -0,0 +1,13 @@ +fn int main() +{ + @foo(;int x = 2) // #error: Macro body parameters should + { + + }; + return 0; +} + +macro @foo(; @body(int x)) +{ + +}