Skip to content

rust: track inline module scopes for module file resolution #785

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

Merged
merged 1 commit into from
Nov 3, 2021
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
71 changes: 18 additions & 53 deletions gcc/rust/ast/rust-ast-full-test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -3984,53 +3984,11 @@ file_exists (const std::string path)
static std::string
filename_from_path_attribute (std::vector<Attribute> &outer_attrs)
{
Attribute path_attr = Attribute::create_empty ();
for (auto attr : outer_attrs)
{
if (attr.get_path ().as_string () == "path")
{
path_attr = attr;
break;
}
}

// We didn't find a path attribute. This is not an error, there simply isn't
// one present
if (path_attr.is_empty ())
return "";

// Here, we found a path attribute, but it has no associated string. This is
// invalid
if (!path_attr.has_attr_input ())
{
rust_error_at (
path_attr.get_locus (),
// Split the format string so that -Wformat-diag does not complain...
"path attributes must contain a filename: '%s'", "#[path = \"file\"]");
return "";
}

auto path_value = path_attr.get_attr_input ().as_string ();

// At this point, the 'path' is of the following format: '= "<file.rs>"'
// We need to remove the equal sign and only keep the actual filename.
// In order to do this, we can simply go through the string until we find
// a character that is not an equal sign or whitespace
auto filename_begin = path_value.find_first_not_of ("=\t ");

auto path = path_value.substr (filename_begin);

// On windows, the path might mix '/' and '\' separators. Replace the
// UNIX-like separators by MSDOS separators to make sure the path will resolve
// properly.
//
// Source: rustc compiler
// (https://github.com/rust-lang/rust/blob/9863bf51a52b8e61bcad312f81b5193d53099f9f/compiler/rustc_expand/src/module.rs#L174)
#if defined(HAVE_DOS_BASED_FILE_SYSTEM)
path.replace ('/', '\\');
#endif /* HAVE_DOS_BASED_FILE_SYSTEM */

return path;
// An out-of-line module cannot have inner attributes. Additionally, the
// default name is specified as `""` so that the caller can detect the case
// of "no path given" and use the default path logic (`name.rs` or
// `name/mod.rs`).
return extract_module_path ({}, outer_attrs, "");
}

void
Expand All @@ -4057,6 +4015,13 @@ Module::process_file_path ()
current_directory_name
= including_fname.substr (0, dir_slash_pos) + file_separator;

// Handle inline module declarations adding path components.
for (auto const &name : module_scope)
{
current_directory_name.append (name);
current_directory_name.append (file_separator);
}

auto path_string = filename_from_path_attribute (get_outer_attrs ());
if (!path_string.empty ())
{
Expand All @@ -4070,12 +4035,13 @@ Module::process_file_path ()
// current file is titled `mod.rs`.

// First, we search for <directory>/<module_name>.rs
bool file_mod_found
= file_exists (current_directory_name + expected_file_path);
std::string file_mod_path = current_directory_name + expected_file_path;
bool file_mod_found = file_exists (file_mod_path);

// Then, search for <directory>/<module_name>/mod.rs
current_directory_name += module_name + file_separator;
bool dir_mod_found = file_exists (current_directory_name + expected_dir_path);
std::string dir_mod_path
= current_directory_name + module_name + file_separator + expected_dir_path;
bool dir_mod_found = file_exists (dir_mod_path);

bool multiple_candidates_found = file_mod_found && dir_mod_found;
bool no_candidates_found = !file_mod_found && !dir_mod_found;
Expand All @@ -4093,8 +4059,7 @@ Module::process_file_path ()
if (no_candidates_found || multiple_candidates_found)
return;

module_file = file_mod_found ? expected_file_path
: current_directory_name + expected_dir_path;
module_file = std::move (file_mod_found ? file_mod_path : dir_mod_path);
}

void
Expand Down
11 changes: 8 additions & 3 deletions gcc/rust/ast/rust-item.h
Original file line number Diff line number Diff line change
Expand Up @@ -991,6 +991,8 @@ class Module : public VisItem
std::vector<Attribute> inner_attrs;
// bool has_items;
std::vector<std::unique_ptr<Item>> items;
// Names of including inline modules (immediate parent is last in the list)
std::vector<std::string> module_scope;

// Filename the module refers to. Empty string on LOADED modules or if an
// error occured when dealing with UNLOADED modules
Expand All @@ -1013,11 +1015,12 @@ class Module : public VisItem
// Unloaded module constructor
Module (Identifier module_name, Visibility visibility,
std::vector<Attribute> outer_attrs, Location locus,
std::string outer_filename)
std::string outer_filename, std::vector<std::string> module_scope)
: VisItem (std::move (visibility), std::move (outer_attrs)),
module_name (module_name), locus (locus), kind (ModuleKind::UNLOADED),
outer_filename (outer_filename), inner_attrs (std::vector<Attribute> ()),
items (std::vector<std::unique_ptr<Item>> ())
items (std::vector<std::unique_ptr<Item>> ()),
module_scope (std::move (module_scope))
{}

// Loaded module constructor, with items
Expand All @@ -1035,7 +1038,8 @@ class Module : public VisItem
// Copy constructor with vector clone
Module (Module const &other)
: VisItem (other), module_name (other.module_name), locus (other.locus),
kind (other.kind), inner_attrs (other.inner_attrs)
kind (other.kind), inner_attrs (other.inner_attrs),
module_scope (other.module_scope)
{
// We need to check whether we are copying a loaded module or an unloaded
// one. In the second case, clear the `items` vector.
Expand All @@ -1054,6 +1058,7 @@ class Module : public VisItem
locus = other.locus;
kind = other.kind;
inner_attrs = other.inner_attrs;
module_scope = other.module_scope;

// Likewise, we need to clear the `items` vector in case the other module is
// unloaded
Expand Down
8 changes: 6 additions & 2 deletions gcc/rust/parse/rust-parse-impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -2104,14 +2104,18 @@ Parser<ManagedTokenSource>::parse_module (AST::Visibility vis,
// Construct an external module
return std::unique_ptr<AST::Module> (
new AST::Module (std::move (name), std::move (vis),
std::move (outer_attrs), locus,
lexer.get_filename ()));
std::move (outer_attrs), locus, lexer.get_filename (),
inline_module_stack));
case LEFT_CURLY: {
lexer.skip_token ();

// parse inner attributes
AST::AttrVec inner_attrs = parse_inner_attributes ();

std::string module_path_name
= extract_module_path (inner_attrs, outer_attrs, name);
InlineModuleStackScope scope (*this, std::move (module_path_name));

// parse items
std::vector<std::unique_ptr<AST::Item>> items;
const_TokenPtr tok = lexer.peek_token ();
Expand Down
73 changes: 73 additions & 0 deletions gcc/rust/parse/rust-parse.cc
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,77 @@ along with GCC; see the file COPYING3. If not see

namespace Rust {

std::string
extract_module_path (const AST::AttrVec &inner_attrs,
const AST::AttrVec &outer_attrs, const std::string &name)
{
AST::Attribute path_attr = AST::Attribute::create_empty ();
for (const auto &attr : inner_attrs)
{
if (attr.get_path ().as_string () == "path")
{
path_attr = attr;
break;
}
}

// Here, we found a path attribute, but it has no associated string. This is
// invalid
if (!path_attr.is_empty () && !path_attr.has_attr_input ())
{
rust_error_at (
path_attr.get_locus (),
// Split the format string so that -Wformat-diag does not complain...
"path attributes must contain a filename: '%s'", "#![path = \"file\"]");
return name;
}

for (const auto &attr : outer_attrs)
{
if (attr.get_path ().as_string () == "path")
{
path_attr = attr;
break;
}
}

// We didn't find a path attribute. This is not an error, there simply isn't
// one present
if (path_attr.is_empty ())
return name;

// Here, we found a path attribute, but it has no associated string. This is
// invalid
if (!path_attr.has_attr_input ())
{
rust_error_at (
path_attr.get_locus (),
// Split the format string so that -Wformat-diag does not complain...
"path attributes must contain a filename: '%s'", "#[path = \"file\"]");
return name;
}

auto path_value = path_attr.get_attr_input ().as_string ();

// At this point, the 'path' is of the following format: '= "<file.rs>"'
// We need to remove the equal sign and only keep the actual filename.
// In order to do this, we can simply go through the string until we find
// a character that is not an equal sign or whitespace
auto filename_begin = path_value.find_first_not_of ("=\t ");

auto path = path_value.substr (filename_begin);

// On windows, the path might mix '/' and '\' separators. Replace the
// UNIX-like separators by MSDOS separators to make sure the path will resolve
// properly.
//
// Source: rustc compiler
// (https://github.com/rust-lang/rust/blob/9863bf51a52b8e61bcad312f81b5193d53099f9f/compiler/rustc_expand/src/module.rs#L174)
#if defined(HAVE_DOS_BASED_FILE_SYSTEM)
path.replace ('/', '\\');
#endif /* HAVE_DOS_BASED_FILE_SYSTEM */

return path;
}

} // namespace Rust
19 changes: 19 additions & 0 deletions gcc/rust/parse/rust-parse.h
Original file line number Diff line number Diff line change
Expand Up @@ -622,7 +622,26 @@ template <typename ManagedTokenSource> class Parser
ManagedTokenSource lexer;
// The error list.
std::vector<Error> error_table;
// The names of inline modules while parsing.
std::vector<std::string> inline_module_stack;

class InlineModuleStackScope
{
private:
Parser &parser;

public:
InlineModuleStackScope (Parser &parser, std::string name) : parser (parser)
{
parser.inline_module_stack.emplace_back (std::move (name));
}
~InlineModuleStackScope () { parser.inline_module_stack.pop_back (); }
};
};

std::string
extract_module_path (const AST::AttrVec &inner_attrs,
const AST::AttrVec &outer_attrs, const std::string &name);
} // namespace Rust

// as now template, include implementations of all methods
Expand Down
3 changes: 3 additions & 0 deletions gcc/testsuite/rust/compile/missing_middle/both_path.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
pub fn f() -> u32 {
5
}
1 change: 1 addition & 0 deletions gcc/testsuite/rust/compile/missing_middle/explicit.not.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
mod other;
3 changes: 3 additions & 0 deletions gcc/testsuite/rust/compile/missing_middle/inner_path.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
pub fn f() -> u32 {
4
}
3 changes: 3 additions & 0 deletions gcc/testsuite/rust/compile/missing_middle/other.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
pub fn f() -> u32 {
2
}
3 changes: 3 additions & 0 deletions gcc/testsuite/rust/compile/missing_middle/outer_path.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
pub fn f() -> u32 {
3
}
3 changes: 3 additions & 0 deletions gcc/testsuite/rust/compile/missing_middle/sub/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
pub fn f() -> u32 {
1
}
29 changes: 29 additions & 0 deletions gcc/testsuite/rust/compile/mod_missing_middle.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// { dg-additional-options "-w" }

mod missing_middle {
mod sub;

#[path = "explicit.not.rs"]
mod explicit;
}

#[path = "missing_middle"]
mod with_outer_path_attr {
#[path = "outer_path.rs"]
mod inner;
}

mod with_inner_path_attr {
#![path = "missing_middle"]

#[path = "inner_path.rs"]
mod inner;
}

#[path = "missing_middle"]
mod with_both_path_attr {
#![path = "this_is_ignored"]

#[path = "both_path.rs"]
mod inner;
}