Skip to content

suggestions from other crates only work when an explicit extern crate is present #51212

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

Closed
nikomatsakis opened this issue May 30, 2018 · 7 comments · Fixed by #51456
Closed
Assignees
Labels
A-diagnostics Area: Messages for errors, warnings, and lints A-edition-2018 Area: The 2018 edition F-rust_2018_preview `#![feature(rust_2018_preview)]` P-high High priority T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. WG-epoch Working group: Epoch (2018) management
Milestone

Comments

@nikomatsakis
Copy link
Contributor

nikomatsakis commented May 30, 2018

UPDATE: There has been some progress on this issue. crate:: prefixes now work, but you still don't get suggestions from other crates except when you write an explicit extern crate.

When you get name resolution suggestions from resolve after an error, they should use crate:: prefixes where appropriate.

Example (play):

#![feature(crate_visibility_modifier)]
#![feature(crate_in_paths)]

mod bar {
    crate struct Foo;
}

fn main() {
    Foo;
}

gives

error[E0425]: cannot find value `Foo` in this scope
 --> src/main.rs:9:5
  |
9 |     Foo;
  |     ^^^ not found in this scope
help: possible candidate is found in another module, you can import it into scope
  |
4 | use bar::Foo;
  |

but I want to see use crate::bar::Foo suggested.

@nikomatsakis nikomatsakis added A-diagnostics Area: Messages for errors, warnings, and lints T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. WG-epoch Working group: Epoch (2018) management F-rust_2018_preview `#![feature(rust_2018_preview)]` labels May 30, 2018
@nikomatsakis
Copy link
Contributor Author

It looks like the suggestions are issued here:

let better = if better { "better " } else { "" };
let msg_diff = match path_strings.len() {
1 => " is found in another module, you can import it",
_ => "s are found in other modules, you can import them",
};
let msg = format!("possible {}candidate{} into scope", better, msg_diff);
if let Some(span) = span {
for candidate in &mut path_strings {
// produce an additional newline to separate the new use statement
// from the directly following item.
let additional_newline = if found_use {
""
} else {
"\n"
};
*candidate = format!("use {};\n{}", candidate, additional_newline);
}
err.span_suggestions(span, &msg, path_strings);
} else {
let mut msg = msg;
msg.push(':');
for candidate in path_strings {
msg.push('\n');
msg.push_str(&candidate);
}
}

The behavior I think we want is this:

If the crate_in_paths feature is enabled, then we should suggest use crate::bar::Foo when bar is from a module and not an external crate.

The function that prepares those suggestions appears to be this one:

fn path_names_to_string(path: &Path) -> String {
names_to_string(&path.segments.iter()
.map(|seg| seg.ident)
.collect::<Vec<_>>())
}

Here is an example of checking for whether the feature is enabled:

if !self.session.features_untracked().crate_in_paths {

@nikomatsakis
Copy link
Contributor Author

@qmx expressed some interest in tackling this.

@nikomatsakis
Copy link
Contributor Author

Hmm, now i'm worried that this will apply even when it shouldn't. Consider this testcase:

fn main() {
    let x: &Debug = &22;
}

This currently prints:

error[E0412]: cannot find type `Debug` in this scope
 --> src/main.rs:2:13
  |
2 |     let x: &Debug = &22;
  |             ^^^^^ not found in this scope
help: possible candidate is found in another module, you can import it into scope
  |
1 | use std::fmt::Debug;
  |

and I think we do not want it to print use crate::std::fmt::Debug.

I think this suggests an alternative fix to what I recommended before (and what @qmx did in #51456) -- but I'd like to get @petrochenkov's opinion. Before I suggested changing how the ImportSuggestion values are printed out, but now I think we should change them at the source:

/// When name resolution fails, this method can be used to look up candidate
/// entities with the expected name. It allows filtering them using the
/// supplied predicate (which should be used to only accept the types of
/// definitions expected e.g. traits). The lookup spans across all crates.
///
/// NOTE: The method does not look into imports, but this is not a problem,
/// since we report the definitions (thus, the de-aliased imports).
fn lookup_import_candidates<FilterFn>(&mut self,
lookup_name: Name,
namespace: Namespace,
filter_fn: FilterFn)
-> Vec<ImportSuggestion>
where FilterFn: Fn(Def) -> bool

Right now, this works by walking through the crate and finding candidates. I suspect (but we should test) that this means it will not work when the extern_prelude option is enabled and no explicit extern crate exists, because there is nothing to walk over. Therefore, the change I propose is as follows:

  1. Modify the function to (if crate_in_paths is enabled) to push a crate at the front of the import suggestions it is currently creating. This is because all of them are found by trawling the crate source.
  2. Then, if extern_prelude is enabled, add a loop that executes first. This would go over all of the crates that are available from the command line, load them up, and search for the item within (basically the same as the existing search, but beginning from the crate directly). In this case, we would not prepend crate:: to the resulting path, but instead the name of the crate we are searching.
  • One flaw I can see here is that it may be that the crate is locally shadowed. I suspect we can overlook that possibility for now.

@nikomatsakis
Copy link
Contributor Author

@petrochenkov do the above notes seem ok?

@petrochenkov
Copy link
Contributor

Looks reasonable.

@nikomatsakis
Copy link
Contributor Author

To get a bit more detailed in how to make this modification, ultimately I envision we'll have a helper like this:

fn lookup_import_candidates_from_module(
    &mut self, 
    lookup_name: Name, 
    namespace: Namespace, 
    start_module: Module<'_>,
    filter_fn: impl Fn(Def) -> bool,
) -> Vec<ImportSuggestion> {
    ...
}

This will be roughly the same as the existing lookup_import_candidates, except that instead of starting from self.graph_root, it will seed the worklist with start_module:

worklist.push((self.graph_root, Vec::new(), false));

Then, later, when it finds a suggestion:

candidates.push(ImportSuggestion { path: path });

It will check if start_module == self.graph_root and the crate_in_paths feature is enabled. In that case, it adds crate:: to the start of the suggestion.

This is step 1. Step 2 in a second. =)

@Mark-Simulacrum Mark-Simulacrum added this to the Rust 2018 Preview 2 milestone Jul 24, 2018
@nikomatsakis
Copy link
Contributor Author

OK, so step 2...

The basic idea was to iterate over each of the "available" crates. For each crate, we would load the crate (if not already loaded), and then use the lookup_import_candidates_with_module fn to walk and make a suggestion.

So the first question is where we find that list of available crates and how to load it. Here are some pointers. For example, here is where we find the set of things in the prelude:

let mut extern_prelude: FxHashSet<Name> =
session.opts.externs.iter().map(|kv| Symbol::intern(kv.0)).collect();

which ends up in the Resolver struct. And this is where we resolve paths that rely on external prelude:

// `record_used` means that we don't try to load crates during speculative resolution
if record_used && ns == TypeNS && self.extern_prelude.contains(&ident.name) {

It seems sort of like this is the snippet of code to "load a crate if needed":

let crate_id = self.crate_loader.process_path_extern(ident.name, ident.span);
let crate_root = self.get_module(DefId { krate: crate_id, index: CRATE_DEF_INDEX });
self.populate_module_if_necessary(crate_root);

I think the idea would be to refactor that last snippet of code into a helper fn, and then invoke it from lookup_import_candidates. So lookup_import_candidates would look something like:

    fn lookup_import_candidates<FilterFn>(&mut self,
                                          lookup_name: Name,
                                          namespace: Namespace,
                                          filter_fn: FilterFn)
                                          -> Vec<ImportSuggestion>
        where FilterFn: Fn(Def) -> bool
    {
        let mut suggestions = vec![];

        suggestions.extend(
            self.lookup_import_candidates_from_module(
                lookup_name, namespace, self.graph_root, keywords::Crate.name(), filter_fn
            )
        );

        if extern_prelude_feature_is_enabled {
            let extern_prelude_names = self.extern_prelude.clone();
            for &krate_name in extern_prelude_names.iter() {
                let extern_prelude_module = self.load_extern_prelude_crate_if_needed(krate_name);
                // ^^ that is the new helper method we added

                suggestions.extend(
                    self.lookup_import_candidates_from_module(
                        lookup_name, namespace, extern_prelude_module, krate_name, filter_fn
                    )
                );
            }
        }
    }

OK, there is a slight flaw -- I think that lookup_import_candidates_from_module should take not a bool ("in root") but rather a Name with the crate name to prepend (which would be crate in the case of the root module). I've modified the code above accordingly.

@nikomatsakis nikomatsakis changed the title resolve suggestions do not use crate:: when enabled suggestions from other crates only work when an explicit extern crate is present Aug 9, 2018
qmx added a commit to qmx/rust that referenced this issue Aug 16, 2018
bors added a commit that referenced this issue Aug 27, 2018
resolve suggestions should use `crate::` when enabled

I couldn't find a way to add a specific assertion for the ui test, so the expected output is living under the `crates-in-path.stderr` ui test.

- is this the right place for the test?

fixes #51212
@fmease fmease added the A-edition-2018 Area: The 2018 edition label Dec 21, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-diagnostics Area: Messages for errors, warnings, and lints A-edition-2018 Area: The 2018 edition F-rust_2018_preview `#![feature(rust_2018_preview)]` P-high High priority T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. WG-epoch Working group: Epoch (2018) management
Projects
None yet
Development

Successfully merging a pull request may close this issue.

7 participants