Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add use declaration resolution #2742

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions gcc/rust/ast/rust-item.h
Original file line number Diff line number Diff line change
Expand Up @@ -940,6 +940,7 @@ class ExternCrate : public VisItem
class UseTree
{
location_t locus;
NodeId node_id;

public:
enum Kind
Expand Down Expand Up @@ -975,14 +976,17 @@ class UseTree
virtual Kind get_kind () const = 0;

location_t get_locus () const { return locus; }
NodeId get_node_id () const { return node_id; }

virtual void accept_vis (ASTVisitor &vis) = 0;

protected:
// Clone function implementation as pure virtual method
virtual UseTree *clone_use_tree_impl () const = 0;

UseTree (location_t locus) : locus (locus) {}
UseTree (location_t locus)
: locus (locus), node_id (Analysis::Mappings::get ()->get_next_node_id ())
{}
};

// Use tree with a glob (wildcard) operator
Expand Down Expand Up @@ -1182,7 +1186,7 @@ class UseTreeRebind : public UseTree

Kind get_kind () const override { return Rebind; }

SimplePath get_path () const
const SimplePath &get_path () const
{
rust_assert (has_path ());
return path;
Expand Down
46 changes: 30 additions & 16 deletions gcc/rust/resolve/rust-early-name-resolver-2.0.cc
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,33 @@ namespace Resolver2_0 {

Early::Early (NameResolutionContext &ctx) : DefaultResolver (ctx) {}

void
Early::insert_once (AST::MacroInvocation &invocation, NodeId resolved)
{
// TODO: Should we use `ctx.mark_resolved()`?
AST::MacroRulesDefinition *definition;
auto ok = ctx.mappings.lookup_macro_def (resolved, &definition);

rust_assert (ok);

AST::MacroRulesDefinition *existing;
auto exists = ctx.mappings.lookup_macro_invocation (invocation, &existing);

if (!exists)
ctx.mappings.insert_macro_invocation (invocation, definition);
}

void
Early::insert_once (AST::MacroRulesDefinition &def)
{
// TODO: Should we use `ctx.mark_resolved()`?
AST::MacroRulesDefinition *definition;
auto exists = ctx.mappings.lookup_macro_def (def.get_node_id (), &definition);

if (!exists)
ctx.mappings.insert_macro_def (&def);
}

void
Early::go (AST::Crate &crate)
{
Expand Down Expand Up @@ -89,6 +116,7 @@ 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
Expand Down Expand Up @@ -141,6 +169,8 @@ Early::visit (AST::MacroInvocation &invoc)
return;
}

insert_once (invoc, *definition);

// 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 ();
Expand All @@ -158,22 +188,6 @@ Early::visit (AST::MacroInvocation &invoc)
mappings->insert_macro_invocation (invoc, rules_def);
}

void
Early::visit (AST::UseDeclaration &use)
{}

void
Early::visit (AST::UseTreeRebind &use)
{}

void
Early::visit (AST::UseTreeList &use)
{}

void
Early::visit (AST::UseTreeGlob &use)
{}

void
Early::visit_attributes (std::vector<AST::Attribute> &attrs)
{
Expand Down
15 changes: 11 additions & 4 deletions gcc/rust/resolve/rust-early-name-resolver-2.0.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,16 +50,23 @@ class Early : public DefaultResolver
void visit (AST::Module &) override;

void visit (AST::MacroInvocation &) override;
void visit (AST::UseDeclaration &) override;
void visit (AST::UseTreeRebind &) override;
void visit (AST::UseTreeList &) override;
void visit (AST::UseTreeGlob &) override;

void visit (AST::Function &) override;
void visit (AST::StructStruct &) override;

private:
void visit_attributes (std::vector<AST::Attribute> &attrs);

/**
* Insert a resolved macro invocation into the mappings once, meaning that we
* can call this function each time the early name resolution pass is underway
* and it will not trigger assertions for already resolved invocations.
*/
// TODO: Rename
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure what you want to rename here ?

void insert_once (AST::MacroInvocation &invocation, NodeId resolved);
// TODO: Rename
void insert_once (AST::MacroRulesDefinition &definition);

/**
* Macros can either be resolved through textual scoping or regular path
* scoping - which this class represents. Textual scoping works similarly to a
Expand Down
195 changes: 191 additions & 4 deletions gcc/rust/resolve/rust-toplevel-name-resolver-2.0.cc
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
// <http://www.gnu.org/licenses/>.

#include "rust-toplevel-name-resolver-2.0.h"
#include "optional.h"
#include "rust-ast-full.h"
#include "rust-hir-map.h"
#include "rust-attribute-values.h"
Expand All @@ -33,17 +34,25 @@ void
TopLevel::insert_or_error_out (const Identifier &identifier, const T &node,
Namespace ns)
{
auto loc = node.get_locus ();
auto node_id = node.get_node_id ();
insert_or_error_out (identifier, node.get_locus (), node.get_node_id (), ns);
}

void
TopLevel::insert_or_error_out (const Identifier &identifier,
const location_t &locus, const NodeId &node_id,
Namespace ns)
{
// keep track of each node's location to provide useful errors
node_locations.emplace (node_id, loc);
node_locations.emplace (node_id, locus);

auto result = ctx.insert (identifier, node_id, ns);

if (!result)
{
rich_location rich_loc (line_table, loc);
// can we do something like check if the node id is the same? if it is the
// same, it's not an error, just the resolver running multiple times?

rich_location rich_loc (line_table, locus);
rich_loc.add_range (node_locations[result.error ().existing]);

rust_error_at (rich_loc, ErrorCode::E0428, "%qs defined multiple times",
Expand All @@ -54,6 +63,11 @@ TopLevel::insert_or_error_out (const Identifier &identifier, const T &node,
void
TopLevel::go (AST::Crate &crate)
{
// we do not include builtin types in the top-level definition collector, as
// they are not used until `Late`. furthermore, we run this visitor multiple
// times in a row in a fixed-point fashion, so it would make the code
// responsible for this ugly and perfom a lot of error checking.

for (auto &item : crate.items)
item->accept_vis (*this);
}
Expand Down Expand Up @@ -300,5 +314,178 @@ TopLevel::visit (AST::ConstantItem &const_item)
ctx.scoped (Rib::Kind::ConstantItem, const_item.get_node_id (), expr_vis);
}

bool
TopLevel::handle_use_dec (AST::SimplePath path)
{
// TODO: Glob imports can get shadowed by regular imports and regular items.
// So we need to store them in a specific way in the ForeverStack - which can
// also probably be used by labels and macros etc. Like store it as a
// `Shadowable(NodeId)` instead of just a `NodeId`

auto locus = path.get_final_segment ().get_locus ();
auto declared_name = path.get_final_segment ().as_string ();

// in what namespace do we perform path resolution? All of them? see which one
// matches? Error out on ambiguities?
// so, apparently, for each one that matches, add it to the proper namespace
// :(

auto found = false;

auto resolve_and_insert = [this, &found, &declared_name,
locus] (Namespace ns,
const AST::SimplePath &path) {
tl::optional<NodeId> resolved = tl::nullopt;

// FIXME: resolve_path needs to return an `expected<NodeId, Error>` so
// that we can improve it with hints or location or w/ever. and maybe
// only emit it the first time.
switch (ns)
{
case Namespace::Values:
resolved = ctx.values.resolve_path (path.get_segments ());
break;
case Namespace::Types:
resolved = ctx.types.resolve_path (path.get_segments ());
break;
case Namespace::Macros:
resolved = ctx.macros.resolve_path (path.get_segments ());
break;
case Namespace::Labels:
// TODO: Is that okay?
rust_unreachable ();
}

// FIXME: Ugly
(void) resolved.map ([this, &found, &declared_name, locus, ns] (NodeId id) {
found = true;

// what do we do with the id?
insert_or_error_out (declared_name, locus, id, ns);

return id;
});
};

// do this for all namespaces (even Labels?)

resolve_and_insert (Namespace::Values, path);
resolve_and_insert (Namespace::Types, path);
resolve_and_insert (Namespace::Macros, path);

// TODO: No labels? No, right?

return found;
}

static void
flatten_rebind (const AST::UseTreeRebind &glob,
std::vector<AST::SimplePath> &paths);
static void
flatten_list (const AST::UseTreeList &glob,
std::vector<AST::SimplePath> &paths);
static void
flatten_glob (const AST::UseTreeGlob &glob,
std::vector<AST::SimplePath> &paths);

static void
flatten (const AST::UseTree *tree, std::vector<AST::SimplePath> &paths)
{
switch (tree->get_kind ())
{
case AST::UseTree::Rebind: {
auto rebind = static_cast<const AST::UseTreeRebind *> (tree);
flatten_rebind (*rebind, paths);
break;
}
case AST::UseTree::List: {
auto list = static_cast<const AST::UseTreeList *> (tree);
flatten_list (*list, paths);
break;
}
case AST::UseTree::Glob: {
rust_sorry_at (tree->get_locus (), "cannot resolve glob imports yet");
auto glob = static_cast<const AST::UseTreeGlob *> (tree);
flatten_glob (*glob, paths);
break;
}
break;
}
}

static void
flatten_rebind (const AST::UseTreeRebind &rebind,
std::vector<AST::SimplePath> &paths)
{
auto path = rebind.get_path ();

// FIXME: Do we want to emplace the rebind here as well?
if (rebind.has_identifier ())
{
auto rebind_path = path;
auto new_seg = rebind.get_identifier ();

// Add the identifier as a new path
rebind_path.get_segments ().back ()
= AST::SimplePathSegment (new_seg.as_string (), UNDEF_LOCATION);

paths.emplace_back (rebind_path);
}
else
{
paths.emplace_back (path);
}
}

static void
flatten_list (const AST::UseTreeList &list, std::vector<AST::SimplePath> &paths)
{
auto prefix = AST::SimplePath::create_empty ();
if (list.has_path ())
prefix = list.get_path ();

for (const auto &tree : list.get_trees ())
{
auto sub_paths = std::vector<AST::SimplePath> ();
flatten (tree.get (), sub_paths);

for (auto &sub_path : sub_paths)
{
auto new_path = prefix;
std::copy (sub_path.get_segments ().begin (),
sub_path.get_segments ().end (),
std::back_inserter (new_path.get_segments ()));

paths.emplace_back (new_path);
}
}
}

static void
flatten_glob (const AST::UseTreeGlob &glob, std::vector<AST::SimplePath> &paths)
{
if (glob.has_path ())
paths.emplace_back (glob.get_path ());
}

void
TopLevel::visit (AST::UseDeclaration &use)
{
auto paths = std::vector<AST::SimplePath> ();

// FIXME: How do we handle `use foo::{self}` imports? Some beforehand cleanup?
// How do we handle module imports in general? Should they get added to all
// namespaces?

const auto &tree = use.get_tree ();
flatten (tree.get (), paths);

for (auto &path : paths)
if (!handle_use_dec (path))
rust_error_at (path.get_final_segment ().get_locus (), ErrorCode::E0433,
"could not resolve import %qs",
path.as_string ().c_str ());
}

} // namespace Resolver2_0
} // namespace Rust
11 changes: 11 additions & 0 deletions gcc/rust/resolve/rust-toplevel-name-resolver-2.0.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ class TopLevel : public DefaultResolver
template <typename T>
void insert_or_error_out (const Identifier &identifier, const T &node,
Namespace ns);
void insert_or_error_out (const Identifier &identifier,
const location_t &locus, const NodeId &id,
Namespace ns);

// FIXME: Do we move these to our mappings?
std::unordered_map<NodeId, location_t> node_locations;
Expand All @@ -73,6 +76,14 @@ class TopLevel : public DefaultResolver
void visit (AST::Union &union_item) override;
void visit (AST::ConstantItem &const_item) override;
void visit (AST::ExternCrate &crate) override;

// FIXME: Documentation
// Call this on all the paths of a UseDec - so each flattened path in a
// UseTreeList for example
// FIXME: Should that return `found`?
bool handle_use_dec (AST::SimplePath path);

void visit (AST::UseDeclaration &use) override;
};

} // namespace Resolver2_0
Expand Down