// 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
// .
#include "rust-early-name-resolver-2.0.h"
#include "rust-ast-full.h"
#include "rust-diagnostics.h"
#include "rust-toplevel-name-resolver-2.0.h"
#include "rust-attributes.h"
#include "rust-finalize-imports-2.0.h"
namespace Rust {
namespace Resolver2_0 {
Early::Early (NameResolutionContext &ctx) : DefaultResolver (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
auto toplevel = TopLevel (ctx);
toplevel.go (crate);
// We start with resolving the list of imports that `TopLevel` has built for
// us
for (auto &&import : toplevel.get_imports_to_resolve ())
build_import_mapping (std::move (import));
// Once this is done, we finalize their resolution
FinalizeImports (std::move (import_mappings), toplevel, ctx).go (crate);
dirty = toplevel.is_dirty ();
// We now proceed with resolving macros, which can be nested in almost any
// items
textual_scope.push ();
for (auto &item : crate.items)
item->accept_vis (*this);
textual_scope.pop ();
}
bool
Early::resolve_glob_import (NodeId use_dec_id, TopLevel::ImportKind &&glob)
{
auto resolved = ctx.types.resolve_path (glob.to_resolve.get_segments ());
if (!resolved.has_value ())
return false;
auto result
= Analysis::Mappings::get ().lookup_ast_module (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> &&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;
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)
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
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)
{
textual_scope.push ();
DefaultResolver::visit (module);
textual_scope.pop ();
}
void
Early::visit (AST::MacroInvocation &invoc)
{
auto path = invoc.get_invoc_data ().get_path ();
// 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 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.macros.resolve_path (path.get_segments ());
// if the definition still does not have a value, then it's an error
if (!definition.has_value ())
{
collect_error (Error (invoc.get_locus (), ErrorCode::E0433,
"could not resolve macro invocation"));
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 &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.macros.resolve_path (trait.get ().get_segments ());
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 ());
rust_assert (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.macros.resolve_path (attr.get_path ().get_segments ());
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 ());
rust_assert (pm_def.has_value ());
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);
}
} // namespace Resolver2_0
} // namespace Rust