// Copyright (C) 2020-2024 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 "expected.h" #include "rust-ast.h" #include "rust-diagnostics.h" #include "rust-forever-stack.h" #include "rust-rib.h" #include "optional.h" namespace Rust { namespace Resolver2_0 { template bool ForeverStack::Node::is_root () const { return !parent.has_value (); } template bool ForeverStack::Node::is_leaf () const { return children.empty (); } template void ForeverStack::Node::insert_child (Link link, Node child) { auto res = children.insert ({link, child}); // Do we want to error if the child already exists? Probably not, right? // That's kinda the point, isn't it. So this method always succeeds, right? } template void ForeverStack::push (Rib rib, NodeId id, tl::optional path) { push_inner (rib, Link (id, path)); } template void ForeverStack::push_inner (Rib rib, Link link) { // If the link does not exist, we create it and emplace a new `Node` with the // current node as its parent. `unordered_map::emplace` returns a pair with // the iterator and a boolean. If the value already exists, the iterator // points to it. Otherwise, it points to the newly emplaced value, so we can // just update our cursor(). auto emplace = cursor ().children.emplace ( std::make_pair (link, Node (rib, link.id, cursor ()))); auto it = emplace.first; auto existed = !emplace.second; rust_debug ("inserting link: Link(%d [%s]): existed? %s", link.id, link.path.has_value () ? link.path.value ().as_string ().c_str () : "", existed ? "yes" : "no"); // We update the cursor update_cursor (it->second); } template void ForeverStack::pop () { rust_assert (!cursor ().is_root ()); rust_debug ("popping link"); for (const auto &kv : cursor ().rib.get_values ()) rust_debug ("current_rib: k: %s, v: %d", kv.first.c_str (), kv.second); if (cursor ().parent.has_value ()) for (const auto &kv : cursor ().parent.value ().rib.get_values ()) rust_debug ("new cursor: k: %s, v: %d", kv.first.c_str (), kv.second); update_cursor (cursor ().parent.value ()); } static tl::expected insert_inner (Rib &rib, std::string name, NodeId node, bool can_shadow) { return rib.insert (name, node, can_shadow); } template tl::expected ForeverStack::insert (Identifier name, NodeId node) { auto &innermost_rib = peek (); // So what do we do here - if the Rib has already been pushed in an earlier // pass, we might end up in a situation where it is okay to re-add new names. // Do we just ignore that here? Do we keep track of if the Rib is new or not? // should our cursor have info on the current node like "is it newly pushed"? return insert_inner (innermost_rib, name.as_string (), node, false); } template tl::expected ForeverStack::insert_at_root (Identifier name, NodeId node) { auto &root_rib = root.rib; // inserting in the root of the crate is never a shadowing operation, even for // macros return insert_inner (root_rib, name.as_string (), node, false); } // Specialization for Macros and Labels - where we are allowed to shadow // existing definitions template <> inline tl::expected ForeverStack::insert (Identifier name, NodeId node) { return insert_inner (peek (), name.as_string (), node, true); } template <> inline tl::expected ForeverStack::insert (Identifier name, NodeId node) { return insert_inner (peek (), name.as_string (), node, true); } template Rib & ForeverStack::peek () { return cursor ().rib; } template const Rib & ForeverStack::peek () const { return cursor ().rib; } template void ForeverStack::reverse_iter (std::function lambda) { return reverse_iter (cursor (), lambda); } template void ForeverStack::reverse_iter (Node &start, std::function lambda) { auto *tmp = &start; while (true) { auto keep_going = lambda (*tmp); if (keep_going == KeepGoing::No) return; if (tmp->is_root ()) return; tmp = &tmp->parent.value (); } } template typename ForeverStack::Node & ForeverStack::cursor () { return cursor_reference; } template const typename ForeverStack::Node & ForeverStack::cursor () const { return cursor_reference; } template void ForeverStack::update_cursor (Node &new_cursor) { cursor_reference = new_cursor; } template tl::optional ForeverStack::get (const Identifier &name) { tl::optional resolved_node = tl::nullopt; // TODO: Can we improve the API? have `reverse_iter` return an optional? reverse_iter ([&resolved_node, &name] (Node ¤t) { auto candidate = current.rib.get (name.as_string ()); return candidate.map_or ( [&resolved_node] (NodeId found) { // for most namespaces, we do not need to care about various ribs - they // are available from all contexts if defined in the current scope, or // an outermore one. so if we do have a candidate, we can return it // directly and stop iterating resolved_node = found; return KeepGoing::No; }, // if there was no candidate, we keep iterating KeepGoing::Yes); }); return resolved_node; } template <> tl::optional inline ForeverStack::get ( const Identifier &name) { tl::optional resolved_node = tl::nullopt; reverse_iter ([&resolved_node, &name] (Node ¤t) { // looking up for labels cannot go through function ribs // TODO: What other ribs? if (current.rib.kind == Rib::Kind::Function) return KeepGoing::No; auto candidate = current.rib.get (name.as_string ()); // FIXME: Factor this in a function with the generic `get` return candidate.map_or ( [&resolved_node] (NodeId found) { resolved_node = found; return KeepGoing::No; }, KeepGoing::Yes); }); return resolved_node; } /* Check if an iterator points to the last element */ template static bool is_last (const I &iterator, const C &collection) { return iterator + 1 == collection.end (); } /* Check if an iterator points to the start of the collection */ template static bool is_start (const I &iterator, const C &collection) { return iterator == collection.begin (); } template typename ForeverStack::Node & ForeverStack::find_closest_module (Node &starting_point) { auto *closest_module = &starting_point; reverse_iter (starting_point, [&closest_module] (Node ¤t) { if (current.rib.kind == Rib::Kind::Module || current.is_root ()) { closest_module = ¤t; return KeepGoing::No; } return KeepGoing::Yes; }); return *closest_module; } /* If a the given condition is met, emit an error about misused leading path * segments */ template static inline bool check_leading_kw_at_start (const S &segment, bool condition) { if (condition) rust_error_at ( segment.get_locus (), ErrorCode::E0433, "leading path segment %qs can only be used at the beginning of a path", segment.as_string ().c_str ()); return condition; } // we first need to handle the "starting" segments - `super`, `self` or // `crate`. we don't need to do anything for `self` and can just skip it. for // `crate`, we need to go back to the root of the current stack. for each // `super` segment, we go back to the cursor's parent until we reach the // correct one or the root. template template tl::optional::const_iterator> ForeverStack::find_starting_point (const std::vector &segments, Node &starting_point) { auto iterator = segments.begin (); // If we need to do path segment resolution, then we start // at the closest module. In order to resolve something like `foo::bar!()`, we // need to get back to the surrounding module, and look for a child module // named `foo`. if (segments.size () > 1) starting_point = find_closest_module (starting_point); for (; !is_last (iterator, segments); iterator++) { auto &seg = *iterator; auto is_self_or_crate = seg.is_crate_path_seg () || seg.is_lower_self_seg (); // if we're after the first path segment and meet `self` or `crate`, it's // an error - we should only be seeing `super` keywords at this point if (check_leading_kw_at_start (seg, !is_start (iterator, segments) && is_self_or_crate)) return tl::nullopt; if (seg.is_crate_path_seg ()) { starting_point = root; iterator++; break; } if (seg.is_lower_self_seg ()) { // do nothing and exit iterator++; break; } if (seg.is_super_path_seg ()) { if (starting_point.is_root ()) { rust_error_at (seg.get_locus (), ErrorCode::E0433, "too many leading % keywords"); return tl::nullopt; } starting_point = find_closest_module (starting_point.parent.value ()); continue; } // now we've gone through the allowed `crate`, `self` or leading `super` // segments. we can start resolving each segment itself. // if we do see another leading segment, then we can error out. break; } return iterator; } template template tl::optional::Node &> ForeverStack::resolve_segments ( Node &starting_point, const std::vector &segments, typename std::vector::const_iterator iterator) { auto *current_node = &starting_point; for (; !is_last (iterator, segments); iterator++) { auto &seg = *iterator; auto str = seg.as_string (); rust_debug ("[ARTHUR]: resolving segment part: %s", str.c_str ()); // check that we don't encounter *any* leading keywords afterwards if (check_leading_kw_at_start (seg, seg.is_crate_path_seg () || seg.is_super_path_seg () || seg.is_lower_self_seg ())) return tl::nullopt; tl::optional::Node &> child = tl::nullopt; for (auto &kv : current_node->children) { auto &link = kv.first; if (link.path.map_or ( [&str] (Identifier path) { auto &path_str = path.as_string (); return str == path_str; }, false)) { child = kv.second; break; } } if (!child.has_value ()) { rust_error_at (seg.get_locus (), ErrorCode::E0433, "failed to resolve path segment %qs", str.c_str ()); return tl::nullopt; } current_node = &child.value (); } return *current_node; } template template tl::optional ForeverStack::resolve_path (const std::vector &segments) { // TODO: What to do if segments.empty() ? // if there's only one segment, we just use `get` if (segments.size () == 1) return get (segments.back ().as_string ()); auto starting_point = cursor (); return find_starting_point (segments, starting_point) .and_then ([this, &segments, &starting_point] ( typename std::vector::const_iterator iterator) { return resolve_segments (starting_point, segments, iterator); }) .and_then ([&segments] (Node final_node) { return final_node.rib.get (segments.back ().as_string ()); }); } template tl::optional::Node &, std::string>> ForeverStack::dfs (ForeverStack::Node &starting_point, NodeId to_find) { auto &values = starting_point.rib.get_values (); for (auto &kv : values) if (kv.second == to_find) return {{starting_point, kv.first}}; for (auto &child : starting_point.children) { auto candidate = dfs (child.second, to_find); if (candidate.has_value ()) return candidate; } return tl::nullopt; } template tl::optional ForeverStack::to_canonical_path (NodeId id) { // find the id in the current forever stack, starting from the root, // performing either a BFS or DFS once the Node containing the ID is found, go // back up to the root (parent().parent().parent()...) accumulate link // segments reverse them that's your canonical path return dfs (root, id).map ([this, id] (std::pair tuple) { auto containing_node = tuple.first; auto name = tuple.second; auto segments = std::vector (); reverse_iter (containing_node, [&segments] (Node ¤t) { if (current.is_root ()) return KeepGoing::No; auto children = current.parent.value ().children; const Link *outer_link = nullptr; for (auto &kv : children) { auto &link = kv.first; auto &child = kv.second; if (link.id == child.id) { outer_link = &link; break; } } rust_assert (outer_link); outer_link->path.map ([&segments, outer_link] (Identifier path) { segments.emplace (segments.begin (), Resolver::CanonicalPath::new_seg (outer_link->id, path.as_string ())); }); return KeepGoing::Yes; }); auto path = Resolver::CanonicalPath::create_empty (); for (const auto &segment : segments) path = path.append (segment); // Finally, append the name path = path.append (Resolver::CanonicalPath::new_seg (id, name)); return path; }); } template tl::optional ForeverStack::dfs_rib (ForeverStack::Node &starting_point, NodeId to_find) { if (starting_point.id == to_find) return starting_point.rib; for (auto &child : starting_point.children) { auto candidate = dfs_rib (child.second, to_find); if (candidate.has_value ()) return candidate; } return tl::nullopt; } template tl::optional ForeverStack::to_rib (NodeId rib_id) { return dfs_rib (root, rib_id); } template void ForeverStack::stream_rib (std::stringstream &stream, const Rib &rib, const std::string &next, const std::string &next_next) { if (rib.get_values ().empty ()) { stream << next << "rib: {},\n"; return; } stream << next << "rib: {\n"; for (const auto &kv : rib.get_values ()) stream << next_next << kv.first << ": " << kv.second << "\n"; stream << next << "},\n"; } template void ForeverStack::stream_node (std::stringstream &stream, unsigned indentation, const ForeverStack::Node &node) { auto indent = std::string (indentation, ' '); auto next = std::string (indentation + 4, ' '); auto next_next = std::string (indentation + 8, ' '); stream << indent << "Node {\n" << next << "is_root: " << (node.is_root () ? "true" : "false") << ",\n" << next << "is_leaf: " << (node.is_leaf () ? "true" : "false") << ",\n"; stream_rib (stream, node.rib, next, next_next); stream << indent << "}\n"; for (auto &kv : node.children) { auto link = kv.first; auto child = kv.second; stream << indent << "Link (" << link.id << ", " << (link.path.has_value () ? link.path.value ().as_string () : "") << "):\n"; stream_node (stream, indentation + 4, child); stream << '\n'; } } template std::string ForeverStack::as_debug_string () { std::stringstream stream; stream_node (stream, 0, root); return stream.str (); } // FIXME: Can we add selftests? } // namespace Resolver2_0 } // namespace Rust