5475: Support `Trait as _` imports r=matklad a=jonas-schievink

Fixes https://github.com/rust-analyzer/rust-analyzer/issues/2736

Co-authored-by: Jonas Schievink <jonas.schievink@ferrous-systems.com>
This commit is contained in:
bors[bot] 2020-07-22 12:17:51 +00:00 committed by GitHub
commit dba534a103
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 270 additions and 26 deletions

View File

@ -36,6 +36,8 @@ pub struct ItemScope {
defs: Vec<ModuleDefId>,
impls: Vec<ImplId>,
/// Traits imported via `use Trait as _;`.
unnamed_trait_imports: FxHashMap<TraitId, Visibility>,
/// Macros visible in current module in legacy textual scope
///
/// For macros invoked by an unqualified identifier like `bar!()`, `legacy_macros` will be searched in first.
@ -126,10 +128,13 @@ pub(crate) fn name_of(&self, item: ItemInNs) -> Option<(&Name, Visibility)> {
}
pub(crate) fn traits<'a>(&'a self) -> impl Iterator<Item = TraitId> + 'a {
self.types.values().filter_map(|(def, _)| match def {
ModuleDefId::TraitId(t) => Some(*t),
_ => None,
})
self.types
.values()
.filter_map(|(def, _)| match def {
ModuleDefId::TraitId(t) => Some(*t),
_ => None,
})
.chain(self.unnamed_trait_imports.keys().copied())
}
pub(crate) fn define_def(&mut self, def: ModuleDefId) {
@ -148,6 +153,14 @@ pub(crate) fn define_legacy_macro(&mut self, name: Name, mac: MacroDefId) {
self.legacy_macros.insert(name, mac);
}
pub(crate) fn unnamed_trait_vis(&self, tr: TraitId) -> Option<Visibility> {
self.unnamed_trait_imports.get(&tr).copied()
}
pub(crate) fn push_unnamed_trait(&mut self, tr: TraitId, vis: Visibility) {
self.unnamed_trait_imports.insert(tr, vis);
}
pub(crate) fn push_res(&mut self, name: Name, def: PerNs) -> bool {
let mut changed = false;
@ -241,8 +254,12 @@ macro_rules! check_changed {
changed
}
pub(crate) fn resolutions<'a>(&'a self) -> impl Iterator<Item = (Name, PerNs)> + 'a {
self.entries().map(|(name, res)| (name.clone(), res))
pub(crate) fn resolutions<'a>(&'a self) -> impl Iterator<Item = (Option<Name>, PerNs)> + 'a {
self.entries().map(|(name, res)| (Some(name.clone()), res)).chain(
self.unnamed_trait_imports
.iter()
.map(|(tr, vis)| (None, PerNs::types(ModuleDefId::TraitId(*tr), *vis))),
)
}
pub(crate) fn collect_legacy_macros(&self) -> FxHashMap<Name, MacroDefId> {

View File

@ -239,7 +239,7 @@ fn go(buf: &mut String, map: &CrateDefMap, path: &str, module: LocalModuleId) {
entries.sort_by_key(|(name, _)| name.clone());
for (name, def) in entries {
format_to!(buf, "{}:", name);
format_to!(buf, "{}:", name.map_or("_".to_string(), |name| name.to_string()));
if def.types.is_some() {
buf.push_str(" t");

View File

@ -310,7 +310,7 @@ fn define_macro(
if export {
self.update(
self.def_map.root,
&[(name, PerNs::macros(macro_, Visibility::Public))],
&[(Some(name), PerNs::macros(macro_, Visibility::Public))],
Visibility::Public,
ImportType::Named,
);
@ -336,7 +336,7 @@ fn define_legacy_macro(&mut self, module_id: LocalModuleId, name: Name, mac: Mac
fn define_proc_macro(&mut self, name: Name, macro_: MacroDefId) {
self.update(
self.def_map.root,
&[(name, PerNs::macros(macro_, Visibility::Public))],
&[(Some(name), PerNs::macros(macro_, Visibility::Public))],
Visibility::Public,
ImportType::Named,
);
@ -534,7 +534,7 @@ fn record_resolved_import(&mut self, directive: &ImportDirective) {
let name = variant_data.name.clone();
let variant = EnumVariantId { parent: e, local_id };
let res = PerNs::both(variant.into(), variant.into(), vis);
(name, res)
(Some(name), res)
})
.collect::<Vec<_>>();
self.update(module_id, &resolutions, vis, ImportType::Glob);
@ -550,15 +550,15 @@ fn record_resolved_import(&mut self, directive: &ImportDirective) {
match import.path.segments.last() {
Some(last_segment) => {
let name = match &import.alias {
Some(ImportAlias::Alias(name)) => name.clone(),
Some(ImportAlias::Underscore) => last_segment.clone(), // FIXME rust-analyzer#2736
None => last_segment.clone(),
Some(ImportAlias::Alias(name)) => Some(name.clone()),
Some(ImportAlias::Underscore) => None,
None => Some(last_segment.clone()),
};
log::debug!("resolved import {:?} ({:?}) to {:?}", name, import, def);
// extern crates in the crate root are special-cased to insert entries into the extern prelude: rust-lang/rust#54658
if import.is_extern_crate && module_id == self.def_map.root {
if let Some(def) = def.take_types() {
if let (Some(def), Some(name)) = (def.take_types(), name.as_ref()) {
self.def_map.extern_prelude.insert(name.clone(), def);
}
}
@ -573,7 +573,7 @@ fn record_resolved_import(&mut self, directive: &ImportDirective) {
fn update(
&mut self,
module_id: LocalModuleId,
resolutions: &[(Name, PerNs)],
resolutions: &[(Option<Name>, PerNs)],
vis: Visibility,
import_type: ImportType,
) {
@ -584,7 +584,7 @@ fn update(
fn update_recursive(
&mut self,
module_id: LocalModuleId,
resolutions: &[(Name, PerNs)],
resolutions: &[(Option<Name>, PerNs)],
// All resolutions are imported with this visibility; the visibilies in
// the `PerNs` values are ignored and overwritten
vis: Visibility,
@ -595,15 +595,51 @@ fn update_recursive(
// prevent stack overflows (but this shouldn't be possible)
panic!("infinite recursion in glob imports!");
}
let scope = &mut self.def_map.modules[module_id].scope;
let mut changed = false;
for (name, res) in resolutions {
changed |= scope.push_res_with_import(
&mut self.from_glob_import,
(module_id, name.clone()),
res.with_visibility(vis),
import_type,
);
match name {
Some(name) => {
let scope = &mut self.def_map.modules[module_id].scope;
changed |= scope.push_res_with_import(
&mut self.from_glob_import,
(module_id, name.clone()),
res.with_visibility(vis),
import_type,
);
}
None => {
let tr = match res.take_types() {
Some(ModuleDefId::TraitId(tr)) => tr,
Some(other) => {
log::debug!("non-trait `_` import of {:?}", other);
continue;
}
None => continue,
};
let old_vis = self.def_map.modules[module_id].scope.unnamed_trait_vis(tr);
let should_update = match old_vis {
None => true,
Some(old_vis) => {
let max_vis = old_vis.max(vis, &self.def_map).unwrap_or_else(|| {
panic!("`Tr as _` imports with unrelated visibilities {:?} and {:?} (trait {:?})", old_vis, vis, tr);
});
if max_vis == old_vis {
false
} else {
mark::hit!(upgrade_underscore_visibility);
true
}
}
};
if should_update {
changed = true;
self.def_map.modules[module_id].scope.push_unnamed_trait(tr, vis);
}
}
}
}
if !changed {
@ -950,7 +986,7 @@ fn collect(&mut self, items: &[ModItem]) {
.unwrap_or(Visibility::Public);
self.def_collector.update(
self.module_id,
&[(name.clone(), PerNs::from_def(id, vis, has_constructor))],
&[(Some(name.clone()), PerNs::from_def(id, vis, has_constructor))],
vis,
ImportType::Named,
)
@ -1057,7 +1093,7 @@ fn push_child_module(
self.def_collector.def_map.modules[self.module_id].scope.define_def(def);
self.def_collector.update(
self.module_id,
&[(name, PerNs::from_def(def, vis, false))],
&[(Some(name), PerNs::from_def(def, vis, false))],
vis,
ImportType::Named,
);

View File

@ -558,3 +558,133 @@ mod b {
"#]],
);
}
#[test]
fn underscore_import() {
check(
r#"
//- /main.rs
use tr::Tr as _;
use tr::Tr2 as _;
mod tr {
pub trait Tr {}
pub trait Tr2 {}
}
"#,
expect![[r#"
crate
_: t
_: t
tr: t
crate::tr
Tr: t
Tr2: t
"#]],
);
}
#[test]
fn underscore_reexport() {
check(
r#"
//- /main.rs
mod tr {
pub trait PubTr {}
pub trait PrivTr {}
}
mod reex {
use crate::tr::PrivTr as _;
pub use crate::tr::PubTr as _;
}
use crate::reex::*;
"#,
expect![[r#"
crate
_: t
reex: t
tr: t
crate::tr
PrivTr: t
PubTr: t
crate::reex
_: t
_: t
"#]],
);
}
#[test]
fn underscore_pub_crate_reexport() {
mark::check!(upgrade_underscore_visibility);
check(
r#"
//- /main.rs crate:main deps:lib
use lib::*;
//- /lib.rs crate:lib
use tr::Tr as _;
pub use tr::Tr as _;
mod tr {
pub trait Tr {}
}
"#,
expect![[r#"
crate
_: t
"#]],
);
}
#[test]
fn underscore_nontrait() {
check(
r#"
//- /main.rs
mod m {
pub struct Struct;
pub enum Enum {}
pub const CONST: () = ();
}
use crate::m::{Struct as _, Enum as _, CONST as _};
"#,
expect![[r#"
crate
m: t
crate::m
CONST: v
Enum: t
Struct: t v
"#]],
);
}
#[test]
fn underscore_name_conflict() {
check(
r#"
//- /main.rs
struct Tr;
use tr::Tr as _;
mod tr {
pub trait Tr {}
}
"#,
expect![[r#"
crate
_: t
Tr: t v
tr: t
crate::tr
Tr: t
"#]],
);
}

View File

@ -5,6 +5,7 @@
use crate::{
db::DefDatabase,
nameres::CrateDefMap,
path::{ModPath, PathKind},
ModuleId,
};
@ -115,7 +116,7 @@ pub(crate) fn is_visible_from_other_crate(self) -> bool {
pub(crate) fn is_visible_from_def_map(
self,
def_map: &crate::nameres::CrateDefMap,
def_map: &CrateDefMap,
from_module: crate::LocalModuleId,
) -> bool {
let to_module = match self {
@ -129,4 +130,42 @@ pub(crate) fn is_visible_from_def_map(
});
ancestors.any(|m| m == to_module.local_id)
}
/// Returns the most permissive visibility of `self` and `other`.
///
/// If there is no subset relation between `self` and `other`, returns `None` (ie. they're only
/// visible in unrelated modules).
pub(crate) fn max(self, other: Visibility, def_map: &CrateDefMap) -> Option<Visibility> {
match (self, other) {
(Visibility::Module(_), Visibility::Public)
| (Visibility::Public, Visibility::Module(_))
| (Visibility::Public, Visibility::Public) => Some(Visibility::Public),
(Visibility::Module(mod_a), Visibility::Module(mod_b)) => {
if mod_a.krate != mod_b.krate {
return None;
}
let mut a_ancestors = std::iter::successors(Some(mod_a.local_id), |m| {
let parent_id = def_map[*m].parent?;
Some(parent_id)
});
let mut b_ancestors = std::iter::successors(Some(mod_b.local_id), |m| {
let parent_id = def_map[*m].parent?;
Some(parent_id)
});
if a_ancestors.any(|m| m == mod_b.local_id) {
// B is above A
return Some(Visibility::Module(mod_b));
}
if b_ancestors.any(|m| m == mod_a.local_id) {
// A is above B
return Some(Visibility::Module(mod_a));
}
None
}
}
}
}

View File

@ -3089,3 +3089,25 @@ fn test() {
"#,
);
}
#[test]
fn underscore_import() {
check_types(
r#"
mod tr {
pub trait Tr {
fn method(&self) -> u8 { 0 }
}
}
struct Tr;
impl crate::tr::Tr for Tr {}
use crate::tr::Tr as _;
fn test() {
Tr.method();
//^^^^^^^^^^^ u8
}
"#,
);
}