From 48a51628b841a9b69db846326dcaa67239a8559f Mon Sep 17 00:00:00 2001 From: Eugen Wissner Date: Sun, 1 Nov 2026 22:10:48 +0100 Subject: Fix generic inference for a procedure call as a slice base --- boot/name_analysis.cc | 31 +++++++++++++- boot/symbol.cc | 97 +++++++++++++++++++++++++++++++++++++++++ boot/type_check.cc | 116 -------------------------------------------------- 3 files changed, 127 insertions(+), 117 deletions(-) (limited to 'boot') diff --git a/boot/name_analysis.cc b/boot/name_analysis.cc index 9305cd2..959aefc 100644 --- a/boot/name_analysis.cc +++ b/boot/name_analysis.cc @@ -968,13 +968,42 @@ namespace elna::boot void resolving_visitor::visit(procedure_call *call) { call->callable().accept(this); + visit_all(this, call->arguments); + + if (const auto *reference = call->callable().is_named(); + reference != nullptr && reference->arguments.empty()) + { + const std::shared_ptr symbol = this->bag.lookup(reference->name); + const std::shared_ptr procedure = + symbol == nullptr ? nullptr : symbol->is_procedure(); + + if (procedure != nullptr && !procedure->parameters.empty()) + { + std::vector bindings(procedure->parameters.size()); + auto argument_iterator = call->arguments.begin(); + auto declared_iterator = procedure->symbol.parameters.begin(); + + while (argument_iterator != call->arguments.end() + && declared_iterator != procedure->symbol.parameters.end()) + { + const type actual = unqualify((*argument_iterator)->type_decoration); + bind_arguments(procedure->parameters, bindings, *declared_iterator, actual); + ++argument_iterator; + ++declared_iterator; + } + if (std::ranges::none_of(bindings, &type::empty)) + { + call->callable().type_decoration = substitute( + call->callable().type_decoration, procedure->parameters, bindings); + } + } + } const type callable_type = resolve_underlying_type(call->callable().type_decoration); if (const auto procedure = callable_type.get()) { call->type_decoration = procedure->return_type; } - visit_all(this, call->arguments); this->current_type = type(); } diff --git a/boot/symbol.cc b/boot/symbol.cc index 310ae8a..dcbd460 100644 --- a/boot/symbol.cc +++ b/boot/symbol.cc @@ -700,6 +700,103 @@ namespace elna::boot return subject; } + bool bind_arguments(const std::vector& parameters, std::vector& bindings, + const type& declared, const type& actual) + { + const type left = resolve_aliases(declared); + const type right = resolve_aliases(actual); + + if (const auto parameter = left.get()) + { + for (std::size_t i = 0; i < parameters.size(); ++i) + { + if (parameters[i].get() == parameter && bindings[i].empty()) + { + // The actual type is recorded as it stands, which inside a + // generic body may itself be an abstract parameter. + bindings[i] = actual; + } + } + return true; + } + else if (const auto left_pointer = left.get()) + { + const std::shared_ptr right_pointer = right.get(); + + return right_pointer != nullptr + && bind_arguments(parameters, bindings, left_pointer->base, right_pointer->base); + } + else if (const auto left_slice = left.get()) + { + const std::shared_ptr right_slice = right.get(); + + return right_slice != nullptr + && bind_arguments(parameters, bindings, left_slice->base, right_slice->base); + } + else if (const auto left_array = left.get()) + { + const std::shared_ptr right_array = right.get(); + + return right_array != nullptr + && bind_arguments(parameters, bindings, left_array->base, right_array->base); + } + else if (const auto left_constant = left.get()) + { + const std::shared_ptr right_constant = right.get(); + const type right_unqualified = right_constant != nullptr + ? right_constant->unqualified + : right; + + return bind_arguments(parameters, bindings, left_constant->unqualified, right_unqualified); + } + else if (const auto left_instance = left.get()) + { + for (type candidate = right; !candidate.empty(); candidate = base_of(candidate)) + { + const std::shared_ptr right_instance = + resolve_aliases(candidate).get(); + + if (right_instance == nullptr + || !(left_instance->generic == right_instance->generic) + || left_instance->arguments.size() != right_instance->arguments.size()) + { + continue; + } + for (std::size_t i = 0; i < left_instance->arguments.size(); ++i) + { + if (!bind_arguments(parameters, bindings, + left_instance->arguments.at(i), right_instance->arguments.at(i))) + { + return false; + } + } + return true; + } + return false; + } + else if (const auto left_procedure = left.get()) + { + const std::shared_ptr right_procedure = right.get(); + + if (right_procedure == nullptr + || left_procedure->parameters.size() != right_procedure->parameters.size()) + { + return false; + } + for (std::size_t i = 0; i < left_procedure->parameters.size(); ++i) + { + if (!bind_arguments(parameters, bindings, + left_procedure->parameters.at(i), right_procedure->parameters.at(i))) + { + return false; + } + } + return left_procedure->return_type.empty() + || bind_arguments(parameters, bindings, left_procedure->return_type, right_procedure->return_type); + } + return true; + } + type instantiate(const std::shared_ptr& instance) { std::shared_ptr const generic = unwrap_generic(instance->generic); diff --git a/boot/type_check.cc b/boot/type_check.cc index 7adaaed..a135bcb 100644 --- a/boot/type_check.cc +++ b/boot/type_check.cc @@ -1078,112 +1078,6 @@ namespace elna::boot } } - /* - * Walks a declared parameter type and an actual argument type in parallel, - * binding a parameter the first time it is met and leaving every later - * occurrence to the ordinary assignability check. Both sides are resolved - * at every step, so a transparent instantiation is seen through and only a - * record-referent one is compared as an instantiation. - * - * Returns false when the walk fails on shape rather than on type. - */ - static bool bind_arguments(const std::vector& parameters, std::vector& bindings, - const type& declared, const type& actual) - { - const type left = resolve_aliases(declared); - const type right = resolve_aliases(actual); - - if (const auto parameter = left.get()) - { - for (std::size_t i = 0; i < parameters.size(); ++i) - { - if (parameters[i].get() == parameter && bindings[i].empty()) - { - // The actual type is recorded as it stands, which inside a - // generic body may itself be an abstract parameter. - bindings[i] = actual; - } - } - return true; - } - else if (const auto left_pointer = left.get()) - { - const std::shared_ptr right_pointer = right.get(); - - return right_pointer != nullptr - && bind_arguments(parameters, bindings, left_pointer->base, right_pointer->base); - } - else if (const auto left_slice = left.get()) - { - const std::shared_ptr right_slice = right.get(); - - return right_slice != nullptr - && bind_arguments(parameters, bindings, left_slice->base, right_slice->base); - } - else if (const auto left_array = left.get()) - { - const std::shared_ptr right_array = right.get(); - - return right_array != nullptr - && bind_arguments(parameters, bindings, left_array->base, right_array->base); - } - else if (const auto left_constant = left.get()) - { - const std::shared_ptr right_constant = right.get(); - const type right_unqualified = right_constant != nullptr - ? right_constant->unqualified - : right; - - return bind_arguments(parameters, bindings, left_constant->unqualified, right_unqualified); - } - else if (const auto left_instance = left.get()) - { - for (type candidate = right; !candidate.empty(); candidate = base_of(candidate)) - { - const std::shared_ptr right_instance = - resolve_aliases(candidate).get(); - - if (right_instance == nullptr - || !(left_instance->generic == right_instance->generic) - || left_instance->arguments.size() != right_instance->arguments.size()) - { - continue; - } - for (std::size_t i = 0; i < left_instance->arguments.size(); ++i) - { - if (!bind_arguments(parameters, bindings, - left_instance->arguments.at(i), right_instance->arguments.at(i))) - { - return false; - } - } - return true; - } - return false; - } - else if (const auto left_procedure = left.get()) - { - const std::shared_ptr right_procedure = right.get(); - - if (right_procedure == nullptr - || left_procedure->parameters.size() != right_procedure->parameters.size()) - { - return false; - } - for (std::size_t i = 0; i < left_procedure->parameters.size(); ++i) - { - if (!bind_arguments(parameters, bindings, - left_procedure->parameters.at(i), right_procedure->parameters.at(i))) - { - return false; - } - } - return left_procedure->return_type.empty() - || bind_arguments(parameters, bindings, left_procedure->return_type, right_procedure->return_type); - } - return true; - } - /* * The volatile builtins take the pointee as their type argument, so the * rule applies to what is loaded or stored rather than to a pointer. @@ -1382,19 +1276,9 @@ namespace elna::boot if (procedure != nullptr && !procedure->parameters.empty()) { - // Inference reads the argument types, so they are decorated - // before the parameter types they will be checked against exist. visit_all(this, call->arguments); inferred = procedure; callable_type = infer_call(*call, *reference, *procedure); - - // Name analysis decorated the call with the declared return - // type, which still mentions the parameters inference has - // just bound. - if (const auto substituted = callable_type.get()) - { - call->type_decoration = substituted->return_type; - } } } if (const auto procedure = callable_type.get()) -- cgit v1.2.3