| // Copyright (C) 2020-2025 Free Software Foundation, Inc. |
| |
| // This file is part of GCC. |
| |
| // GCC is free software; you can redistribute it and/or modify it under |
| // the terms of the GNU General Public License as published by the Free |
| // Software Foundation; either version 3, or (at your option) any later |
| // version. |
| |
| // GCC is distributed in the hope that it will be useful, but WITHOUT ANY |
| // WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| // FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| // for more details. |
| |
| // You should have received a copy of the GNU General Public License |
| // along with GCC; see the file COPYING3. If not see |
| // <http://www.gnu.org/licenses/>. |
| |
| #include "rust-diagnostics.h" |
| #include "rust-hir-map.h" |
| #include "rust-hir-path.h" |
| #include "rust-hir-type-check-expr.h" |
| #include "rust-hir-type-check-type.h" |
| #include "rust-hir-type-check-item.h" |
| #include "rust-hir-trait-resolve.h" |
| #include "rust-substitution-mapper.h" |
| #include "rust-hir-path-probe.h" |
| #include "rust-type-util.h" |
| #include "rust-hir-type-bounds.h" |
| #include "rust-hir-item.h" |
| #include "rust-session-manager.h" |
| #include "rust-immutable-name-resolution-context.h" |
| |
| namespace Rust { |
| namespace Resolver { |
| |
| void |
| TypeCheckExpr::visit (HIR::QualifiedPathInExpression &expr) |
| { |
| HIR::QualifiedPathType qual_path_type = expr.get_path_type (); |
| TyTy::BaseType *root = TypeCheckType::Resolve (qual_path_type.get_type ()); |
| if (root->get_kind () == TyTy::TypeKind::ERROR) |
| return; |
| |
| if (!qual_path_type.has_as_clause ()) |
| { |
| NodeId root_resolved_node_id = UNKNOWN_NODEID; |
| resolve_segments (root_resolved_node_id, expr.get_segments (), 0, root, |
| expr.get_mappings (), expr.get_locus ()); |
| return; |
| } |
| |
| // Resolve the trait now |
| HIR::TypePath &trait_path_ref = qual_path_type.get_trait (); |
| TraitReference *trait_ref = TraitResolver::Resolve (trait_path_ref); |
| if (trait_ref->is_error ()) |
| return; |
| |
| // does this type actually implement this type-bound? |
| if (!TypeBoundsProbe::is_bound_satisfied_for_type (root, trait_ref)) |
| return; |
| |
| // then we need to look at the next segment to create perform the correct |
| // projection type |
| if (expr.get_segments ().empty ()) |
| return; |
| |
| // get the predicate for the bound |
| auto specified_bound |
| = get_predicate_from_bound (trait_path_ref, qual_path_type.get_type ()); |
| if (specified_bound.is_error ()) |
| return; |
| |
| // inherit the bound |
| root->inherit_bounds ({specified_bound}); |
| |
| // lookup the associated item from the specified bound |
| HIR::PathExprSegment &item_seg = expr.get_segments ().at (0); |
| HIR::PathIdentSegment item_seg_identifier = item_seg.get_segment (); |
| TyTy::TypeBoundPredicateItem item |
| = specified_bound.lookup_associated_item (item_seg_identifier.as_string ()); |
| if (item.is_error ()) |
| { |
| rust_error_at (item_seg.get_locus (), "unknown associated item"); |
| return; |
| } |
| |
| // we try to look for the real impl item if possible |
| HIR::ImplItem *impl_item = nullptr; |
| |
| // lookup the associated impl trait for this if we can (it might be generic) |
| AssociatedImplTrait *associated_impl_trait |
| = lookup_associated_impl_block (specified_bound, root); |
| if (associated_impl_trait != nullptr) |
| { |
| associated_impl_trait->setup_associated_types (root, specified_bound); |
| |
| for (auto &i : |
| associated_impl_trait->get_impl_block ()->get_impl_items ()) |
| { |
| bool found = i->get_impl_item_name ().compare ( |
| item_seg_identifier.as_string ()) |
| == 0; |
| if (found) |
| { |
| impl_item = i.get (); |
| break; |
| } |
| } |
| } |
| |
| NodeId root_resolved_node_id = UNKNOWN_NODEID; |
| if (impl_item == nullptr) |
| { |
| // this may be valid as there could be a default trait implementation here |
| // and we dont need to worry if the trait item is actually implemented or |
| // not because this will have already been validated as part of the trait |
| // impl block |
| infered = item.get_tyty_for_receiver (root); |
| root_resolved_node_id |
| = item.get_raw_item ()->get_mappings ().get_nodeid (); |
| } |
| else |
| { |
| HirId impl_item_id = impl_item->get_impl_mappings ().get_hirid (); |
| bool ok = query_type (impl_item_id, &infered); |
| if (!ok) |
| { |
| // FIXME |
| // I think query_type should error if required here anyway |
| return; |
| } |
| |
| root_resolved_node_id = impl_item->get_impl_mappings ().get_nodeid (); |
| } |
| |
| // turbo-fish segment path::<ty> |
| if (item_seg.has_generic_args ()) |
| { |
| if (!infered->has_substitutions_defined ()) |
| { |
| rust_error_at (item_seg.get_locus (), |
| "substitutions not supported for %s", |
| infered->as_string ().c_str ()); |
| infered = new TyTy::ErrorType (expr.get_mappings ().get_hirid ()); |
| return; |
| } |
| std::vector<TyTy::Region> regions; |
| |
| infered = SubstMapper::Resolve (infered, expr.get_locus (), |
| &item_seg.get_generic_args (), |
| context->regions_from_generic_args ( |
| item_seg.get_generic_args ())); |
| } |
| |
| // continue on as a path-in-expression |
| bool fully_resolved = expr.get_segments ().size () <= 1; |
| if (fully_resolved) |
| { |
| auto &nr_ctx = const_cast<Resolver2_0::NameResolutionContext &> ( |
| Resolver2_0::ImmutableNameResolutionContext::get ().resolver ()); |
| |
| nr_ctx.map_usage (Resolver2_0::Usage (expr.get_mappings ().get_nodeid ()), |
| Resolver2_0::Definition (root_resolved_node_id)); |
| return; |
| } |
| |
| resolve_segments (root_resolved_node_id, expr.get_segments (), 1, infered, |
| expr.get_mappings (), expr.get_locus ()); |
| } |
| |
| void |
| TypeCheckExpr::visit (HIR::PathInExpression &expr) |
| { |
| NodeId resolved_node_id = UNKNOWN_NODEID; |
| if (expr.is_lang_item ()) |
| { |
| auto lookup |
| = Analysis::Mappings::get ().get_lang_item_node (expr.get_lang_item ()); |
| auto hir_id = mappings.lookup_node_to_hir (lookup); |
| |
| // We can type resolve the path in expression easily as it is a lang |
| // item path, but we still need to setup the various generics and |
| // substitutions |
| |
| // FIXME: We probably need to check *if* the type needs substitutions |
| // or not |
| if (LangItem::IsEnumVariant (expr.get_lang_item ())) |
| { |
| std::pair<HIR::Enum *, HIR::EnumItem *> enum_item_lookup |
| = mappings.lookup_hir_enumitem (*hir_id); |
| bool enum_item_ok = enum_item_lookup.first != nullptr |
| && enum_item_lookup.second != nullptr; |
| rust_assert (enum_item_ok); |
| |
| HirId variant_id |
| = enum_item_lookup.second->get_mappings ().get_hirid (); |
| |
| HIR::EnumItem *enum_item = enum_item_lookup.second; |
| resolved_node_id = enum_item->get_mappings ().get_nodeid (); |
| |
| // insert the id of the variant we are resolved to |
| context->insert_variant_definition (expr.get_mappings ().get_hirid (), |
| variant_id); |
| |
| query_type (variant_id, &infered); |
| infered = SubstMapper::InferSubst (infered, expr.get_locus ()); |
| } |
| else |
| { |
| TyTy::BaseType *resolved = nullptr; |
| context->lookup_type (*hir_id, &resolved); |
| |
| rust_assert (resolved); |
| |
| query_type (*hir_id, &infered); |
| |
| infered = SubstMapper::InferSubst (resolved, expr.get_locus ()); |
| } |
| |
| // FIXME: also we probably need to insert resolved types in the name |
| // resolver here |
| } |
| else |
| { |
| size_t offset = -1; |
| TyTy::BaseType *tyseg |
| = resolve_root_path (expr, &offset, &resolved_node_id); |
| if (tyseg->get_kind () == TyTy::TypeKind::ERROR) |
| return; |
| |
| bool fully_resolved = offset == expr.get_segments ().size (); |
| if (fully_resolved) |
| { |
| infered = tyseg; |
| return; |
| } |
| |
| resolve_segments (resolved_node_id, expr.get_segments (), offset, tyseg, |
| expr.get_mappings (), expr.get_locus ()); |
| } |
| } |
| |
| TyTy::BaseType * |
| TypeCheckExpr::resolve_root_path (HIR::PathInExpression &expr, size_t *offset, |
| NodeId *root_resolved_node_id) |
| { |
| TyTy::BaseType *root_tyty = nullptr; |
| *offset = 0; |
| for (size_t i = 0; i < expr.get_num_segments (); i++) |
| { |
| HIR::PathExprSegment &seg = expr.get_segments ().at (i); |
| |
| bool have_more_segments = (expr.get_num_segments () - 1 != i); |
| bool is_root = *offset == 0; |
| NodeId ast_node_id = seg.get_mappings ().get_nodeid (); |
| |
| auto &nr_ctx |
| = Resolver2_0::ImmutableNameResolutionContext::get ().resolver (); |
| |
| // lookup the reference_node_id |
| NodeId ref_node_id; |
| if (auto res = nr_ctx.lookup (ast_node_id)) |
| { |
| ref_node_id = *res; |
| } |
| else |
| { |
| if (root_tyty != nullptr && *offset > 0) |
| { |
| // then we can let the impl path probe take over now |
| return root_tyty; |
| } |
| |
| rust_error_at (seg.get_locus (), |
| "failed to type resolve root segment"); |
| return new TyTy::ErrorType (expr.get_mappings ().get_hirid ()); |
| } |
| |
| // node back to HIR |
| tl::optional<HirId> hid = mappings.lookup_node_to_hir (ref_node_id); |
| if (!hid.has_value ()) |
| { |
| rust_error_at (seg.get_locus (), "456 reverse lookup failure"); |
| rust_debug_loc (seg.get_locus (), |
| "failure with [%s] mappings [%s] ref_node_id [%u]", |
| seg.as_string ().c_str (), |
| seg.get_mappings ().as_string ().c_str (), |
| ref_node_id); |
| |
| return new TyTy::ErrorType (expr.get_mappings ().get_hirid ()); |
| } |
| auto ref = hid.value (); |
| |
| auto seg_is_module = mappings.lookup_module (ref).has_value (); |
| auto seg_is_crate = mappings.is_local_hirid_crate (ref); |
| auto seg_is_pattern = mappings.lookup_hir_pattern (ref).has_value (); |
| auto seg_is_self = is_root && !have_more_segments |
| && seg.get_segment ().as_string () == "self"; |
| if (seg_is_module || seg_is_crate) |
| { |
| // A::B::C::this_is_a_module::D::E::F |
| // ^^^^^^^^^^^^^^^^ |
| // Currently handling this. |
| if (have_more_segments) |
| { |
| (*offset)++; |
| continue; |
| } |
| |
| // In the case of : |
| // A::B::C::this_is_a_module |
| // ^^^^^^^^^^^^^^^^ |
| // This is an error, we are not expecting a module. |
| rust_error_at (seg.get_locus (), "expected value"); |
| return new TyTy::ErrorType (expr.get_mappings ().get_hirid ()); |
| } |
| |
| TyTy::BaseType *lookup = nullptr; |
| if (!query_type (ref, &lookup)) |
| { |
| if (is_root || root_tyty == nullptr) |
| { |
| rust_error_at (expr.get_locus (), ErrorCode::E0425, |
| "cannot find value %qs in this scope", |
| expr.as_simple_path ().as_string ().c_str ()); |
| |
| return new TyTy::ErrorType (expr.get_mappings ().get_hirid ()); |
| } |
| return root_tyty; |
| } |
| |
| // is it an enum item? |
| std::pair<HIR::Enum *, HIR::EnumItem *> enum_item_lookup |
| = mappings.lookup_hir_enumitem (ref); |
| bool is_enum_item = enum_item_lookup.first != nullptr |
| && enum_item_lookup.second != nullptr; |
| if (is_enum_item) |
| { |
| HirId expr_id = expr.get_mappings ().get_hirid (); |
| HirId variant_id |
| = enum_item_lookup.second->get_mappings ().get_hirid (); |
| context->insert_variant_definition (expr_id, variant_id); |
| } |
| |
| // if we have a previous segment type |
| if (root_tyty != nullptr) |
| { |
| // if this next segment needs substitution we must apply the |
| // previous type arguments |
| // |
| // such as: GenericStruct::<_>::new(123, 456) |
| if (lookup->needs_generic_substitutions ()) |
| { |
| if (!root_tyty->needs_generic_substitutions ()) |
| { |
| auto used_args_in_prev_segment |
| = GetUsedSubstArgs::From (root_tyty); |
| lookup |
| = SubstMapperInternal::Resolve (lookup, |
| used_args_in_prev_segment); |
| } |
| } |
| } |
| |
| // turbo-fish segment path::<ty> |
| if (seg.has_generic_args ()) |
| { |
| lookup = SubstMapper::Resolve (lookup, expr.get_locus (), |
| &seg.get_generic_args (), |
| context->regions_from_generic_args ( |
| seg.get_generic_args ())); |
| if (lookup->get_kind () == TyTy::TypeKind::ERROR) |
| return new TyTy::ErrorType (expr.get_mappings ().get_hirid ()); |
| } |
| else if (lookup->needs_generic_substitutions () && !seg_is_pattern |
| && !seg_is_self) |
| { |
| lookup = SubstMapper::InferSubst (lookup, expr.get_locus ()); |
| } |
| |
| *root_resolved_node_id = ref_node_id; |
| *offset = *offset + 1; |
| root_tyty = lookup; |
| } |
| |
| return root_tyty; |
| } |
| |
| void |
| TypeCheckExpr::resolve_segments (NodeId root_resolved_node_id, |
| std::vector<HIR::PathExprSegment> &segments, |
| size_t offset, TyTy::BaseType *tyseg, |
| const Analysis::NodeMapping &expr_mappings, |
| location_t expr_locus) |
| { |
| NodeId resolved_node_id = root_resolved_node_id; |
| TyTy::BaseType *prev_segment = tyseg; |
| bool receiver_is_generic = prev_segment->get_kind () == TyTy::TypeKind::PARAM; |
| bool receiver_is_dyn = prev_segment->get_kind () == TyTy::TypeKind::DYNAMIC; |
| |
| for (size_t i = offset; i < segments.size (); i++) |
| { |
| HIR::PathExprSegment &seg = segments.at (i); |
| bool probe_impls = !receiver_is_generic; |
| |
| // probe the path is done in two parts one where we search impls if no |
| // candidate is found then we search extensions from traits |
| auto candidates |
| = PathProbeType::Probe (prev_segment, seg.get_segment (), probe_impls, |
| false /*probe_bounds*/, |
| true /*ignore_mandatory_trait_items*/); |
| if (candidates.size () == 0) |
| { |
| candidates |
| = PathProbeType::Probe (prev_segment, seg.get_segment (), false, |
| true /*probe_bounds*/, |
| false /*ignore_mandatory_trait_items*/); |
| |
| if (candidates.size () == 0) |
| { |
| rust_error_at ( |
| seg.get_locus (), |
| "failed to resolve path segment using an impl Probe"); |
| return; |
| } |
| } |
| |
| if (candidates.size () > 1) |
| { |
| ReportMultipleCandidateError::Report (candidates, seg.get_segment (), |
| seg.get_locus ()); |
| return; |
| } |
| |
| auto &candidate = *candidates.begin (); |
| prev_segment = tyseg; |
| tyseg = candidate.ty; |
| |
| HIR::ImplBlock *associated_impl_block = nullptr; |
| if (candidate.is_enum_candidate ()) |
| { |
| const TyTy::VariantDef *variant = candidate.item.enum_field.variant; |
| |
| HirId variant_id = variant->get_id (); |
| std::pair<HIR::Enum *, HIR::EnumItem *> enum_item_lookup |
| = mappings.lookup_hir_enumitem (variant_id); |
| bool enum_item_ok = enum_item_lookup.first != nullptr |
| && enum_item_lookup.second != nullptr; |
| rust_assert (enum_item_ok); |
| |
| HIR::EnumItem *enum_item = enum_item_lookup.second; |
| resolved_node_id = enum_item->get_mappings ().get_nodeid (); |
| |
| // insert the id of the variant we are resolved to |
| context->insert_variant_definition (expr_mappings.get_hirid (), |
| variant_id); |
| } |
| else if (candidate.is_impl_candidate ()) |
| { |
| resolved_node_id |
| = candidate.item.impl.impl_item->get_impl_mappings ().get_nodeid (); |
| |
| associated_impl_block = candidate.item.impl.parent; |
| } |
| else |
| { |
| resolved_node_id |
| = candidate.item.trait.item_ref->get_mappings ().get_nodeid (); |
| |
| // lookup the associated-impl-trait |
| HIR::ImplBlock *impl = candidate.item.trait.impl; |
| if (impl != nullptr) |
| { |
| // get the associated impl block |
| associated_impl_block = impl; |
| } |
| } |
| |
| if (associated_impl_block != nullptr && !receiver_is_dyn) |
| { |
| // associated types |
| HirId impl_block_id |
| = associated_impl_block->get_mappings ().get_hirid (); |
| |
| AssociatedImplTrait *associated = nullptr; |
| bool found_impl_trait |
| = context->lookup_associated_trait_impl (impl_block_id, |
| &associated); |
| |
| auto mappings = TyTy::SubstitutionArgumentMappings::error (); |
| TyTy::BaseType *impl_block_ty |
| = TypeCheckItem::ResolveImplBlockSelfWithInference ( |
| *associated_impl_block, seg.get_locus (), &mappings); |
| |
| // we need to apply the arguments to the segment type so they get |
| // unified properly |
| if (!mappings.is_error ()) |
| tyseg = SubstMapperInternal::Resolve (tyseg, mappings); |
| |
| prev_segment = unify_site (seg.get_mappings ().get_hirid (), |
| TyTy::TyWithLocation (prev_segment), |
| TyTy::TyWithLocation (impl_block_ty), |
| seg.get_locus ()); |
| bool ok = prev_segment->get_kind () != TyTy::TypeKind::ERROR; |
| if (!ok) |
| return; |
| |
| if (found_impl_trait) |
| { |
| // we need to setup with apropriate bounds |
| HIR::TypePath &bound_path |
| = associated->get_impl_block ()->get_trait_ref (); |
| const auto &trait_ref = *TraitResolver::Resolve (bound_path); |
| rust_assert (!trait_ref.is_error ()); |
| |
| const auto &predicate |
| = impl_block_ty->lookup_predicate (trait_ref.get_defid ()); |
| if (!predicate.is_error ()) |
| impl_block_ty |
| = associated->setup_associated_types (prev_segment, predicate, |
| nullptr, false); |
| } |
| } |
| |
| if (seg.has_generic_args ()) |
| { |
| rust_debug_loc (seg.get_locus (), "applying segment generics: %s", |
| tyseg->as_string ().c_str ()); |
| tyseg |
| = SubstMapper::Resolve (tyseg, expr_locus, &seg.get_generic_args (), |
| context->regions_from_generic_args ( |
| seg.get_generic_args ())); |
| if (tyseg->get_kind () == TyTy::TypeKind::ERROR) |
| return; |
| } |
| else if (tyseg->needs_generic_substitutions () && !receiver_is_generic) |
| { |
| location_t locus = seg.get_locus (); |
| tyseg = SubstMapper::InferSubst (tyseg, locus); |
| if (tyseg->get_kind () == TyTy::TypeKind::ERROR) |
| return; |
| } |
| } |
| |
| rust_assert (resolved_node_id != UNKNOWN_NODEID); |
| |
| auto &nr_ctx = const_cast<Resolver2_0::NameResolutionContext &> ( |
| Resolver2_0::ImmutableNameResolutionContext::get ().resolver ()); |
| |
| nr_ctx.map_usage (Resolver2_0::Usage (expr_mappings.get_nodeid ()), |
| Resolver2_0::Definition (resolved_node_id)); |
| |
| infered = tyseg; |
| } |
| |
| } // namespace Resolver |
| } // namespace Rust |