https://gcc.gnu.org/g:eb5eee065b3e5fe189ea1f51dc88eae7dc4c25d6
commit r15-9439-geb5eee065b3e5fe189ea1f51dc88eae7dc4c25d6 Author: Arthur Cohen <arthur.co...@embecosm.com> Date: Thu Apr 3 16:22:10 2025 +0200 gccrs: typecheck: Properly select methods when dealing with specialization gcc/rust/ChangeLog: * typecheck/rust-hir-type-check-expr.cc (is_default_fn): New. (emit_ambiguous_resolution_error): New. (handle_multiple_candidates): Properly handle multiple candidates in the case of specialization. (TypeCheckExpr::visit): Call `handle_multiple_candidates`. gcc/testsuite/ChangeLog: * rust/execute/torture/min_specialization2.rs: New test. * rust/execute/torture/min_specialization3.rs: New test. Diff: --- gcc/rust/typecheck/rust-hir-type-check-expr.cc | 129 +++++++++++++++++---- .../rust/execute/torture/min_specialization2.rs | 31 +++++ .../rust/execute/torture/min_specialization3.rs | 36 ++++++ 3 files changed, 172 insertions(+), 24 deletions(-) diff --git a/gcc/rust/typecheck/rust-hir-type-check-expr.cc b/gcc/rust/typecheck/rust-hir-type-check-expr.cc index 791795f3b0e0..b2bcac065eb2 100644 --- a/gcc/rust/typecheck/rust-hir-type-check-expr.cc +++ b/gcc/rust/typecheck/rust-hir-type-check-expr.cc @@ -16,6 +16,8 @@ // along with GCC; see the file COPYING3. If not see // <http://www.gnu.org/licenses/>. +#include "optional.h" +#include "rust-hir-expr.h" #include "rust-system.h" #include "rust-tyty-call.h" #include "rust-hir-type-check-struct-field.h" @@ -1154,6 +1156,94 @@ TypeCheckExpr::visit (HIR::FieldAccessExpr &expr) infered = lookup->get_field_type (); } +bool +is_default_fn (const MethodCandidate &candidate) +{ + if (candidate.candidate.is_impl_candidate ()) + { + auto *item = candidate.candidate.item.impl.impl_item; + + if (item->get_impl_item_type () == HIR::ImplItem::FUNCTION) + { + auto &fn = static_cast<HIR::Function &> (*item); + + return fn.is_default (); + } + } + + return false; +} + +void +emit_ambiguous_resolution_error (HIR::MethodCallExpr &expr, + std::set<MethodCandidate> &candidates) +{ + rich_location r (line_table, expr.get_method_name ().get_locus ()); + std::string rich_msg = "multiple " + + expr.get_method_name ().get_segment ().as_string () + + " found"; + + // We have to filter out default candidates + for (auto &c : candidates) + if (!is_default_fn (c)) + r.add_range (c.candidate.locus); + + r.add_fixit_replace (rich_msg.c_str ()); + + rust_error_at (r, ErrorCode::E0592, "duplicate definitions with name %qs", + expr.get_method_name ().get_segment ().as_string ().c_str ()); +} + +// We are allowed to have multiple candidates if they are all specializable +// functions or if all of them except one are specializable functions. +// In the later case, we just return a valid candidate without erroring out +// about ambiguity. If there are two or more specialized functions, then we +// error out. +// +// FIXME: The first case is not handled at the moment, so we error out +tl::optional<const MethodCandidate &> +handle_multiple_candidates (HIR::MethodCallExpr &expr, + std::set<MethodCandidate> &candidates) +{ + auto all_default = true; + tl::optional<const MethodCandidate &> found = tl::nullopt; + + for (auto &c : candidates) + { + if (!is_default_fn (c)) + { + all_default = false; + + // We haven't found a final candidate yet, so we can select + // this one. However, if we already have a candidate, then + // that means there are multiple non-default candidates - we + // must error out + if (!found) + { + found = c; + } + else + { + emit_ambiguous_resolution_error (expr, candidates); + return tl::nullopt; + } + } + } + + // None of the candidates were a non-default (specialized) function, so we + // error out + if (all_default) + { + rust_sorry_at (expr.get_locus (), + "cannot resolve method calls to non-specialized methods " + "(all function candidates are %qs)", + "default"); + return tl::nullopt; + } + + return found; +} + void TypeCheckExpr::visit (HIR::MethodCallExpr &expr) { @@ -1181,34 +1271,25 @@ TypeCheckExpr::visit (HIR::MethodCallExpr &expr) return; } - if (candidates.size () > 1) - { - rich_location r (line_table, expr.get_method_name ().get_locus ()); - std::string rich_msg - = "multiple " + expr.get_method_name ().get_segment ().as_string () - + " found"; + tl::optional<const MethodCandidate &> candidate = *candidates.begin (); - for (auto &c : candidates) - r.add_range (c.candidate.locus); + if (candidates.size () > 1) + candidate = handle_multiple_candidates (expr, candidates); - r.add_fixit_replace (rich_msg.c_str ()); + if (!candidate) + return; - rust_error_at ( - r, ErrorCode::E0592, "duplicate definitions with name %qs", - expr.get_method_name ().get_segment ().as_string ().c_str ()); - return; - } + auto found_candidate = *candidate; - auto candidate = *candidates.begin (); rust_debug_loc (expr.get_method_name ().get_locus (), "resolved method to: {%u} {%s} with [%lu] adjustments", - candidate.candidate.ty->get_ref (), - candidate.candidate.ty->debug_str ().c_str (), - (unsigned long) candidate.adjustments.size ()); + found_candidate.candidate.ty->get_ref (), + found_candidate.candidate.ty->debug_str ().c_str (), + (unsigned long) found_candidate.adjustments.size ()); // Get the adjusted self Adjuster adj (receiver_tyty); - TyTy::BaseType *adjusted_self = adj.adjust_type (candidate.adjustments); + TyTy::BaseType *adjusted_self = adj.adjust_type (found_candidate.adjustments); rust_debug ("receiver: %s adjusted self %s", receiver_tyty->debug_str ().c_str (), adjusted_self->debug_str ().c_str ()); @@ -1219,10 +1300,10 @@ TypeCheckExpr::visit (HIR::MethodCallExpr &expr) HirId autoderef_mappings_id = expr.get_receiver ().get_mappings ().get_hirid (); context->insert_autoderef_mappings (autoderef_mappings_id, - std::move (candidate.adjustments)); + std::move (found_candidate.adjustments)); - PathProbeCandidate &resolved_candidate = candidate.candidate; - TyTy::BaseType *lookup_tyty = candidate.candidate.ty; + PathProbeCandidate &resolved_candidate = found_candidate.candidate; + TyTy::BaseType *lookup_tyty = found_candidate.candidate.ty; NodeId resolved_node_id = resolved_candidate.is_impl_candidate () ? resolved_candidate.item.impl.impl_item->get_impl_mappings () @@ -1249,8 +1330,8 @@ TypeCheckExpr::visit (HIR::MethodCallExpr &expr) fn->prepare_higher_ranked_bounds (); rust_debug_loc (expr.get_locus (), "resolved method call to: {%u} {%s}", - candidate.candidate.ty->get_ref (), - candidate.candidate.ty->debug_str ().c_str ()); + found_candidate.candidate.ty->get_ref (), + found_candidate.candidate.ty->debug_str ().c_str ()); if (resolved_candidate.is_impl_candidate ()) { diff --git a/gcc/testsuite/rust/execute/torture/min_specialization2.rs b/gcc/testsuite/rust/execute/torture/min_specialization2.rs new file mode 100644 index 000000000000..d3239eea4705 --- /dev/null +++ b/gcc/testsuite/rust/execute/torture/min_specialization2.rs @@ -0,0 +1,31 @@ +#![feature(min_specialization)] + +#[lang = "sized"] +trait Sized {} + +trait Foo { + fn foo(&self) -> i32; +} + +impl<T> Foo for T { + default fn foo(&self) -> i32 { // { dg-warning "unused" } + 15 + } +} + +impl Foo for bool { + fn foo(&self) -> i32 { + if *self { + 1 + } else { + 0 + } + } +} + +fn main() -> i32 { + let a = 1.foo() - 15; + let b = true.foo() - 1; + + a + b +} diff --git a/gcc/testsuite/rust/execute/torture/min_specialization3.rs b/gcc/testsuite/rust/execute/torture/min_specialization3.rs new file mode 100644 index 000000000000..9eccd974dae7 --- /dev/null +++ b/gcc/testsuite/rust/execute/torture/min_specialization3.rs @@ -0,0 +1,36 @@ +#![feature(min_specialization)] + +#[lang = "sized"] +trait Sized {} + +trait Foo { + fn foo(&self) -> i32; +} + +struct Wrap<T>(T); + +impl<T> Foo for T { + default fn foo(&self) -> i32 { + 15 + } +} + +impl<T> Foo for Wrap<T> { + default fn foo(&self) -> i32 { + 16 + } +} + +impl Foo for Wrap<bool> { + fn foo(&self) -> i32 { + if self.0 { + 1 + } else { + 0 + } + } +} + +fn main() -> i32 { + Wrap(true).foo() - 1 +}