| // 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-early-name-resolver-2.0.h" |
| #include "optional.h" |
| #include "options.h" |
| #include "rust-diagnostics.h" |
| #include "rust-hir-map.h" |
| #include "rust-item.h" |
| #include "rust-toplevel-name-resolver-2.0.h" |
| #include "rust-attributes.h" |
| #include "rust-finalize-imports-2.0.h" |
| #include "rust-attribute-values.h" |
| |
| namespace Rust { |
| namespace Resolver2_0 { |
| |
| Early::Early (NameResolutionContext &ctx) |
| : DefaultResolver (ctx), toplevel (TopLevel (ctx)), dirty (false) |
| {} |
| |
| void |
| Early::insert_once (AST::MacroInvocation &invocation, NodeId resolved) |
| { |
| // TODO: Should we use `ctx.mark_resolved()`? |
| auto definition = ctx.mappings.lookup_macro_def (resolved); |
| |
| if (!ctx.mappings.lookup_macro_invocation (invocation)) |
| ctx.mappings.insert_macro_invocation (invocation, definition.value ()); |
| } |
| |
| void |
| Early::insert_once (AST::MacroRulesDefinition &def) |
| { |
| // TODO: Should we use `ctx.mark_resolved()`? |
| if (!ctx.mappings.lookup_macro_def (def.get_node_id ())) |
| ctx.mappings.insert_macro_def (&def); |
| } |
| |
| void |
| Early::go (AST::Crate &crate) |
| { |
| // First we go through TopLevel resolution to get all our declared items |
| toplevel.go (crate); |
| |
| // We start with resolving the list of imports that `TopLevel` has built for |
| // us |
| |
| dirty = toplevel.is_dirty (); |
| // We now proceed with resolving macros, which can be nested in almost any |
| // items |
| textual_scope.push (); |
| |
| visit (crate); |
| |
| textual_scope.pop (); |
| } |
| |
| bool |
| Early::resolve_glob_import (NodeId use_dec_id, TopLevel::ImportKind &&glob) |
| { |
| auto resolved = ctx.resolve_path (glob.to_resolve, Namespace::Types); |
| if (!resolved.has_value ()) |
| return false; |
| |
| auto result = Analysis::Mappings::get ().lookup_glob_container ( |
| resolved->get_node_id ()); |
| |
| if (!result) |
| return false; |
| |
| // here, we insert the module's NodeId into the import_mappings and will look |
| // up the module proper in `FinalizeImports` |
| // The namespace does not matter here since we are dealing with a glob |
| // TODO: Ugly |
| import_mappings.insert (use_dec_id, |
| ImportPair (std::move (glob), |
| ImportData::Glob (*resolved))); |
| |
| return true; |
| } |
| |
| bool |
| Early::resolve_simple_import (NodeId use_dec_id, TopLevel::ImportKind &&import) |
| { |
| auto definitions = resolve_path_in_all_ns (import.to_resolve); |
| |
| // if we've found at least one definition, then we're good |
| if (definitions.empty ()) |
| return false; |
| |
| auto &imports = import_mappings.new_or_access (use_dec_id); |
| |
| imports.emplace_back ( |
| ImportPair (std::move (import), |
| ImportData::Simple (std::move (definitions)))); |
| |
| return true; |
| } |
| |
| bool |
| Early::resolve_rebind_import (NodeId use_dec_id, |
| TopLevel::ImportKind &&rebind_import) |
| { |
| auto definitions = resolve_path_in_all_ns (rebind_import.to_resolve); |
| |
| // if we've found at least one definition, then we're good |
| if (definitions.empty ()) |
| return false; |
| |
| auto &imports = import_mappings.new_or_access (use_dec_id); |
| |
| imports.emplace_back ( |
| ImportPair (std::move (rebind_import), |
| ImportData::Rebind (std::move (definitions)))); |
| |
| return true; |
| } |
| |
| void |
| Early::build_import_mapping ( |
| std::pair<NodeId, std::vector<TopLevel::ImportKind>> &&use_import) |
| { |
| auto found = false; |
| auto use_dec_id = use_import.first; |
| |
| for (auto &&import : use_import.second) |
| { |
| // We create a copy of the path in case of errors, since the `import` will |
| // be moved into the newly created import mappings |
| auto path = import.to_resolve; |
| |
| // used to skip the "unresolved import" error |
| // if we output other errors during resolution |
| size_t old_error_count = macro_resolve_errors.size (); |
| |
| switch (import.kind) |
| { |
| case TopLevel::ImportKind::Kind::Glob: |
| found = resolve_glob_import (use_dec_id, std::move (import)); |
| break; |
| case TopLevel::ImportKind::Kind::Simple: |
| found = resolve_simple_import (use_dec_id, std::move (import)); |
| break; |
| case TopLevel::ImportKind::Kind::Rebind: |
| found = resolve_rebind_import (use_dec_id, std::move (import)); |
| break; |
| } |
| |
| if (!found && old_error_count == macro_resolve_errors.size ()) |
| collect_error (Error (path.get_final_segment ().get_locus (), |
| ErrorCode::E0433, "unresolved import %qs", |
| path.as_string ().c_str ())); |
| } |
| } |
| |
| void |
| Early::TextualScope::push () |
| { |
| // push a new empty scope |
| scopes.emplace_back (); |
| } |
| |
| void |
| Early::TextualScope::pop () |
| { |
| rust_assert (!scopes.empty ()); |
| |
| scopes.pop_back (); |
| } |
| |
| void |
| Early::TextualScope::insert (std::string name, NodeId id) |
| { |
| rust_assert (!scopes.empty ()); |
| |
| // we can ignore the return value as we always want the latest defined macro |
| // to shadow a previous one - so if two macros have the same name and get |
| // inserted with the same key, it's not a bug |
| scopes.back ().insert ({name, id}); |
| } |
| |
| tl::optional<NodeId> |
| Early::TextualScope::get (const std::string &name) |
| { |
| for (auto iterator = scopes.rbegin (); iterator != scopes.rend (); iterator++) |
| { |
| auto scope = *iterator; |
| auto found = scope.find (name); |
| if (found != scope.end ()) |
| return found->second; |
| } |
| |
| return tl::nullopt; |
| } |
| |
| void |
| Early::visit (AST::MacroRulesDefinition &def) |
| { |
| DefaultResolver::visit (def); |
| |
| textual_scope.insert (def.get_rule_name ().as_string (), def.get_node_id ()); |
| insert_once (def); |
| } |
| |
| void |
| Early::visit (AST::BlockExpr &block) |
| { |
| textual_scope.push (); |
| |
| DefaultResolver::visit (block); |
| |
| textual_scope.pop (); |
| } |
| |
| void |
| Early::visit (AST::Module &module) |
| { |
| bool is_macro_use = false; |
| |
| for (const auto &attr : module.get_outer_attrs ()) |
| { |
| if (attr.get_path ().as_string () == Values::Attributes::MACRO_USE) |
| { |
| is_macro_use = true; |
| break; |
| } |
| } |
| |
| if (!is_macro_use) |
| textual_scope.push (); |
| |
| DefaultResolver::visit (module); |
| |
| if (!is_macro_use) |
| textual_scope.pop (); |
| } |
| |
| void |
| Early::visit (AST::MacroInvocation &invoc) |
| { |
| auto &path = invoc.get_invoc_data ().get_path (); |
| |
| // We special case the `offset_of!()` macro if the flag is here, otherwise |
| // we accept whatever `offset_of!()` definition we resolved to. |
| auto resolve_offset_of |
| = flag_assume_builtin_offset_of && (path.as_string () == "offset_of"); |
| |
| if (invoc.get_kind () == AST::MacroInvocation::InvocKind::Builtin) |
| for (auto &pending_invoc : invoc.get_pending_eager_invocations ()) |
| pending_invoc->accept_vis (*this); |
| |
| // When a macro is invoked by an unqualified identifier (not part of a |
| // multi-part path), it is first looked up in textual scoping. If this does |
| // not yield any results, then it is looked up in path-based scoping. If the |
| // macro's name is qualified with a path, then it is only looked up in |
| // path-based scoping. |
| |
| // https://doc.rust-lang.org/reference/macros-by-example.html#path-based-scope |
| |
| tl::optional<Rib::Definition> definition = tl::nullopt; |
| if (path.get_segments ().size () == 1) |
| definition |
| = textual_scope.get (path.get_final_segment ().as_string ()) |
| .map ([] (NodeId id) { return Rib::Definition::NonShadowable (id); }); |
| |
| // we won't have changed `definition` from `nullopt` if there are more |
| // than one segments in our path |
| if (!definition.has_value ()) |
| definition = ctx.resolve_path (path, Namespace::Macros); |
| |
| // if the definition still does not have a value, then it's an error - unless |
| // we should automatically resolve offset_of!() calls |
| if (!definition.has_value ()) |
| { |
| if (!resolve_offset_of) |
| collect_error (Error (invoc.get_locus (), ErrorCode::E0433, |
| "could not resolve macro invocation %qs", |
| path.as_string ().c_str ())); |
| return; |
| } |
| |
| insert_once (invoc, definition->get_node_id ()); |
| |
| // now do we need to keep mappings or something? or insert "uses" into our |
| // ForeverStack? can we do that? are mappings simpler? |
| auto &mappings = Analysis::Mappings::get (); |
| auto rules_def = mappings.lookup_macro_def (definition->get_node_id ()); |
| |
| // Macro definition not found, maybe it is not expanded yet. |
| if (!rules_def) |
| return; |
| |
| if (mappings.lookup_macro_invocation (invoc)) |
| return; |
| |
| mappings.insert_macro_invocation (invoc, rules_def.value ()); |
| } |
| |
| void |
| Early::visit_attributes (std::vector<AST::Attribute> &attrs) |
| { |
| auto &mappings = Analysis::Mappings::get (); |
| |
| for (auto &attr : attrs) |
| { |
| auto name = attr.get_path ().get_segments ().at (0).get_segment_name (); |
| |
| if (attr.is_derive ()) |
| { |
| auto traits = attr.get_traits_to_derive (); |
| for (auto &trait : traits) |
| { |
| auto definition |
| = ctx.resolve_path (trait.get (), Namespace::Macros); |
| if (!definition.has_value ()) |
| { |
| // FIXME: Change to proper error message |
| collect_error (Error (trait.get ().get_locus (), |
| "could not resolve trait %qs", |
| trait.get ().as_string ().c_str ())); |
| continue; |
| } |
| |
| auto pm_def = mappings.lookup_derive_proc_macro_def ( |
| definition->get_node_id ()); |
| |
| if (pm_def.has_value ()) |
| mappings.insert_derive_proc_macro_invocation (trait, |
| pm_def.value ()); |
| } |
| } |
| else if (Analysis::BuiltinAttributeMappings::get () |
| ->lookup_builtin (name) |
| .is_error ()) // Do not resolve builtins |
| { |
| auto definition |
| = ctx.resolve_path (attr.get_path (), Namespace::Macros); |
| if (!definition.has_value ()) |
| { |
| // FIXME: Change to proper error message |
| collect_error ( |
| Error (attr.get_locus (), |
| "could not resolve attribute macro invocation")); |
| return; |
| } |
| auto pm_def = mappings.lookup_attribute_proc_macro_def ( |
| definition->get_node_id ()); |
| |
| if (!pm_def.has_value ()) |
| return; |
| |
| mappings.insert_attribute_proc_macro_invocation (attr.get_path (), |
| pm_def.value ()); |
| } |
| } |
| } |
| |
| void |
| Early::visit (AST::Function &fn) |
| { |
| visit_attributes (fn.get_outer_attrs ()); |
| DefaultResolver::visit (fn); |
| } |
| |
| void |
| Early::visit (AST::StructStruct &s) |
| { |
| visit_attributes (s.get_outer_attrs ()); |
| DefaultResolver::visit (s); |
| } |
| |
| void |
| Early::finalize_simple_import (const Early::ImportPair &mapping) |
| { |
| // FIXME: We probably need to store namespace information |
| |
| auto locus = mapping.import_kind.to_resolve.get_locus (); |
| auto data = mapping.data; |
| auto identifier |
| = mapping.import_kind.to_resolve.get_final_segment ().get_segment_name (); |
| |
| for (auto &&definition : data.definitions ()) |
| toplevel |
| .insert_or_error_out ( |
| identifier, locus, definition.first.get_node_id (), definition.second /* TODO: This isn't clear - it would be better if it was called .ns or something */); |
| } |
| |
| void |
| Early::finalize_glob_import (NameResolutionContext &ctx, |
| const Early::ImportPair &mapping) |
| { |
| auto container = Analysis::Mappings::get ().lookup_glob_container ( |
| mapping.data.container ().get_node_id ()); |
| |
| rust_assert (container); |
| |
| GlobbingVisitor (ctx).go (container.value ()); |
| } |
| |
| void |
| Early::finalize_rebind_import (const Early::ImportPair &mapping) |
| { |
| // We can fetch the value here as `resolve_rebind` will only be called on |
| // imports of the right kind |
| auto &path = mapping.import_kind.to_resolve; |
| auto &rebind = mapping.import_kind.rebind.value (); |
| auto data = mapping.data; |
| |
| location_t locus = UNKNOWN_LOCATION; |
| std::string declared_name; |
| |
| // FIXME: This needs to be done in `FinalizeImports` |
| switch (rebind.get_new_bind_type ()) |
| { |
| case AST::UseTreeRebind::NewBindType::IDENTIFIER: |
| declared_name = rebind.get_identifier ().as_string (); |
| locus = rebind.get_identifier ().get_locus (); |
| break; |
| case AST::UseTreeRebind::NewBindType::NONE: |
| { |
| const auto &segments = path.get_segments (); |
| // We don't want to insert `self` with `use module::self` |
| if (path.get_final_segment ().is_lower_self_seg ()) |
| { |
| rust_assert (segments.size () > 1); |
| declared_name = segments[segments.size () - 2].as_string (); |
| } |
| else |
| declared_name = path.get_final_segment ().as_string (); |
| locus = path.get_final_segment ().get_locus (); |
| break; |
| } |
| case AST::UseTreeRebind::NewBindType::WILDCARD: |
| rust_unreachable (); |
| break; |
| } |
| |
| for (auto &&definition : data.definitions ()) |
| toplevel.insert_or_error_out ( |
| declared_name, locus, definition.first.get_node_id (), definition.second /* TODO: This isn't clear - it would be better if it was called .ns or something */); |
| } |
| |
| void |
| Early::visit (AST::UseDeclaration &decl) |
| { |
| auto &imports = toplevel.get_imports_to_resolve (); |
| auto current_import = imports.find (decl.get_node_id ()); |
| if (current_import != imports.end ()) |
| { |
| build_import_mapping (*current_import); |
| } |
| |
| // Once this is done, we finalize their resolution |
| for (const auto &mapping : import_mappings.get (decl.get_node_id ())) |
| switch (mapping.import_kind.kind) |
| { |
| case TopLevel::ImportKind::Kind::Glob: |
| finalize_glob_import (ctx, mapping); |
| break; |
| case TopLevel::ImportKind::Kind::Simple: |
| finalize_simple_import (mapping); |
| break; |
| case TopLevel::ImportKind::Kind::Rebind: |
| finalize_rebind_import (mapping); |
| break; |
| } |
| |
| DefaultResolver::visit (decl); |
| } |
| |
| } // namespace Resolver2_0 |
| } // namespace Rust |