Skip to content

Commit ffcbeef

Browse files
authored
Rollup merge of #80765 - petrochenkov:traitsinscope, r=matthewjasper
resolve: Simplify collection of traits in scope "Traits in scope" for a given location are collected by walking all scopes in type namespace, collecting traits in them and pruning traits that don't have an associated item with the given name and namespace. Previously we tried to prune traits using some kind of hygienic resolution for associated items, but that was complex and likely incorrect, e.g. in #80762 correction to visibilites of trait items caused some traits to not be in scope anymore. I previously had some comments and concerns about this in #65351. In this PR we are doing some much simpler pruning based on `Symbol` and `Namespace` comparisons, it should be enough to throw away 99.9% of unnecessary traits. It is not necessary for pruning to be precise because for trait aliases, for example, we don't do any pruning at all, and precise hygienic resolution for associated items needs to be done in typeck anyway. The somewhat unexpected effect is that trait imports introduced by macros 2.0 now bring traits into scope due to the removed hygienic check on associated item names. I'm not sure whether it is desirable or not, but I think it's acceptable for now. The old check was certainly incorrect because macros 2.0 did bring trait aliases into scope. If doing this is not desirable, then we should come up with some other way to avoid bringing traits from macros 2.0 into scope, that would accommodate for trait aliases as well. --- The PR also contains a couple of pure refactorings - Scope walk is done by using `visit_scopes` instead of a hand-rolled version. - Code is restructured to accomodate for rustdoc that also wants to query traits in scope, but doesn't want to filter them by associated items at all. r? ```@matthewjasper```
2 parents 19f9780 + b7071b2 commit ffcbeef

File tree

7 files changed

+139
-161
lines changed

7 files changed

+139
-161
lines changed

compiler/rustc_resolve/src/build_reduced_graph.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ impl<'a> Resolver<'a> {
115115
self.get_module(parent_id)
116116
}
117117

118-
crate fn get_module(&mut self, def_id: DefId) -> Module<'a> {
118+
pub fn get_module(&mut self, def_id: DefId) -> Module<'a> {
119119
// If this is a local module, it will be in `module_map`, no need to recalculate it.
120120
if let Some(def_id) = def_id.as_local() {
121121
return self.module_map[&def_id];

compiler/rustc_resolve/src/late.rs

+10-62
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ use crate::{ResolutionError, Resolver, Segment, UseError};
1414
use rustc_ast::ptr::P;
1515
use rustc_ast::visit::{self, AssocCtxt, FnCtxt, FnKind, Visitor};
1616
use rustc_ast::*;
17-
use rustc_ast::{unwrap_or, walk_list};
1817
use rustc_ast_lowering::ResolverAstLowering;
1918
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
2019
use rustc_errors::DiagnosticId;
@@ -1911,7 +1910,7 @@ impl<'a: 'ast, 'b, 'ast> LateResolutionVisitor<'a, 'b, 'ast> {
19111910
// it needs to be added to the trait map.
19121911
if ns == ValueNS {
19131912
let item_name = path.last().unwrap().ident;
1914-
let traits = self.get_traits_containing_item(item_name, ns);
1913+
let traits = self.traits_in_scope(item_name, ns);
19151914
self.r.trait_map.insert(id, traits);
19161915
}
19171916

@@ -2371,12 +2370,12 @@ impl<'a: 'ast, 'b, 'ast> LateResolutionVisitor<'a, 'b, 'ast> {
23712370
// field, we need to add any trait methods we find that match
23722371
// the field name so that we can do some nice error reporting
23732372
// later on in typeck.
2374-
let traits = self.get_traits_containing_item(ident, ValueNS);
2373+
let traits = self.traits_in_scope(ident, ValueNS);
23752374
self.r.trait_map.insert(expr.id, traits);
23762375
}
23772376
ExprKind::MethodCall(ref segment, ..) => {
23782377
debug!("(recording candidate traits for expr) recording traits for {}", expr.id);
2379-
let traits = self.get_traits_containing_item(segment.ident, ValueNS);
2378+
let traits = self.traits_in_scope(segment.ident, ValueNS);
23802379
self.r.trait_map.insert(expr.id, traits);
23812380
}
23822381
_ => {
@@ -2385,64 +2384,13 @@ impl<'a: 'ast, 'b, 'ast> LateResolutionVisitor<'a, 'b, 'ast> {
23852384
}
23862385
}
23872386

2388-
fn get_traits_containing_item(
2389-
&mut self,
2390-
mut ident: Ident,
2391-
ns: Namespace,
2392-
) -> Vec<TraitCandidate> {
2393-
debug!("(getting traits containing item) looking for '{}'", ident.name);
2394-
2395-
let mut found_traits = Vec::new();
2396-
// Look for the current trait.
2397-
if let Some((module, _)) = self.current_trait_ref {
2398-
if self
2399-
.r
2400-
.resolve_ident_in_module(
2401-
ModuleOrUniformRoot::Module(module),
2402-
ident,
2403-
ns,
2404-
&self.parent_scope,
2405-
false,
2406-
module.span,
2407-
)
2408-
.is_ok()
2409-
{
2410-
let def_id = module.def_id().unwrap();
2411-
found_traits.push(TraitCandidate { def_id, import_ids: smallvec![] });
2412-
}
2413-
}
2414-
2415-
ident.span = ident.span.normalize_to_macros_2_0();
2416-
let mut search_module = self.parent_scope.module;
2417-
loop {
2418-
self.r.get_traits_in_module_containing_item(
2419-
ident,
2420-
ns,
2421-
search_module,
2422-
&mut found_traits,
2423-
&self.parent_scope,
2424-
);
2425-
let mut span_data = ident.span.data();
2426-
search_module = unwrap_or!(
2427-
self.r.hygienic_lexical_parent(search_module, &mut span_data.ctxt),
2428-
break
2429-
);
2430-
ident.span = span_data.span();
2431-
}
2432-
2433-
if let Some(prelude) = self.r.prelude {
2434-
if !search_module.no_implicit_prelude {
2435-
self.r.get_traits_in_module_containing_item(
2436-
ident,
2437-
ns,
2438-
prelude,
2439-
&mut found_traits,
2440-
&self.parent_scope,
2441-
);
2442-
}
2443-
}
2444-
2445-
found_traits
2387+
fn traits_in_scope(&mut self, ident: Ident, ns: Namespace) -> Vec<TraitCandidate> {
2388+
self.r.traits_in_scope(
2389+
self.current_trait_ref.as_ref().map(|(module, _)| *module),
2390+
&self.parent_scope,
2391+
ident.span.ctxt(),
2392+
Some((ident.name, ns)),
2393+
)
24462394
}
24472395
}
24482396

compiler/rustc_resolve/src/lib.rs

+63-64
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,9 @@ use rustc_index::vec::IndexVec;
4444
use rustc_metadata::creader::{CStore, CrateLoader};
4545
use rustc_middle::hir::exports::ExportMap;
4646
use rustc_middle::middle::cstore::{CrateStore, MetadataLoaderDyn};
47+
use rustc_middle::span_bug;
4748
use rustc_middle::ty::query::Providers;
4849
use rustc_middle::ty::{self, DefIdTree, ResolverOutputs};
49-
use rustc_middle::{bug, span_bug};
5050
use rustc_session::lint;
5151
use rustc_session::lint::{BuiltinLintDiagnostics, LintBuffer};
5252
use rustc_session::Session;
@@ -1477,49 +1477,76 @@ impl<'a> Resolver<'a> {
14771477
self.crate_loader.postprocess(krate);
14781478
}
14791479

1480-
fn get_traits_in_module_containing_item(
1480+
pub fn traits_in_scope(
1481+
&mut self,
1482+
current_trait: Option<Module<'a>>,
1483+
parent_scope: &ParentScope<'a>,
1484+
ctxt: SyntaxContext,
1485+
assoc_item: Option<(Symbol, Namespace)>,
1486+
) -> Vec<TraitCandidate> {
1487+
let mut found_traits = Vec::new();
1488+
1489+
if let Some(module) = current_trait {
1490+
if self.trait_may_have_item(Some(module), assoc_item) {
1491+
let def_id = module.def_id().unwrap();
1492+
found_traits.push(TraitCandidate { def_id, import_ids: smallvec![] });
1493+
}
1494+
}
1495+
1496+
self.visit_scopes(ScopeSet::All(TypeNS, false), parent_scope, ctxt, |this, scope, _, _| {
1497+
match scope {
1498+
Scope::Module(module) => {
1499+
this.traits_in_module(module, assoc_item, &mut found_traits);
1500+
}
1501+
Scope::StdLibPrelude => {
1502+
if let Some(module) = this.prelude {
1503+
this.traits_in_module(module, assoc_item, &mut found_traits);
1504+
}
1505+
}
1506+
Scope::ExternPrelude | Scope::ToolPrelude | Scope::BuiltinTypes => {}
1507+
_ => unreachable!(),
1508+
}
1509+
None::<()>
1510+
});
1511+
1512+
found_traits
1513+
}
1514+
1515+
fn traits_in_module(
14811516
&mut self,
1482-
ident: Ident,
1483-
ns: Namespace,
14841517
module: Module<'a>,
1518+
assoc_item: Option<(Symbol, Namespace)>,
14851519
found_traits: &mut Vec<TraitCandidate>,
1486-
parent_scope: &ParentScope<'a>,
14871520
) {
1488-
assert!(ns == TypeNS || ns == ValueNS);
14891521
module.ensure_traits(self);
14901522
let traits = module.traits.borrow();
1523+
for (trait_name, trait_binding) in traits.as_ref().unwrap().iter() {
1524+
if self.trait_may_have_item(trait_binding.module(), assoc_item) {
1525+
let def_id = trait_binding.res().def_id();
1526+
let import_ids = self.find_transitive_imports(&trait_binding.kind, *trait_name);
1527+
found_traits.push(TraitCandidate { def_id, import_ids });
1528+
}
1529+
}
1530+
}
14911531

1492-
for &(trait_name, binding) in traits.as_ref().unwrap().iter() {
1493-
// Traits have pseudo-modules that can be used to search for the given ident.
1494-
if let Some(module) = binding.module() {
1495-
let mut ident = ident;
1496-
if ident.span.glob_adjust(module.expansion, binding.span).is_none() {
1497-
continue;
1498-
}
1499-
if self
1500-
.resolve_ident_in_module_unadjusted(
1501-
ModuleOrUniformRoot::Module(module),
1502-
ident,
1503-
ns,
1504-
parent_scope,
1505-
false,
1506-
module.span,
1507-
)
1508-
.is_ok()
1509-
{
1510-
let import_ids = self.find_transitive_imports(&binding.kind, trait_name);
1511-
let trait_def_id = module.def_id().unwrap();
1512-
found_traits.push(TraitCandidate { def_id: trait_def_id, import_ids });
1513-
}
1514-
} else if let Res::Def(DefKind::TraitAlias, _) = binding.res() {
1515-
// For now, just treat all trait aliases as possible candidates, since we don't
1516-
// know if the ident is somewhere in the transitive bounds.
1517-
let import_ids = self.find_transitive_imports(&binding.kind, trait_name);
1518-
let trait_def_id = binding.res().def_id();
1519-
found_traits.push(TraitCandidate { def_id: trait_def_id, import_ids });
1520-
} else {
1521-
bug!("candidate is not trait or trait alias?")
1532+
// List of traits in scope is pruned on best effort basis. We reject traits not having an
1533+
// associated item with the given name and namespace (if specified). This is a conservative
1534+
// optimization, proper hygienic type-based resolution of associated items is done in typeck.
1535+
// We don't reject trait aliases (`trait_module == None`) because we don't have access to their
1536+
// associated items.
1537+
fn trait_may_have_item(
1538+
&mut self,
1539+
trait_module: Option<Module<'a>>,
1540+
assoc_item: Option<(Symbol, Namespace)>,
1541+
) -> bool {
1542+
match (trait_module, assoc_item) {
1543+
(Some(trait_module), Some((name, ns))) => {
1544+
self.resolutions(trait_module).borrow().iter().any(|resolution| {
1545+
let (&BindingKey { ident: assoc_ident, ns: assoc_ns, .. }, _) = resolution;
1546+
assoc_ns == ns && assoc_ident.name == name
1547+
})
15221548
}
1549+
_ => true,
15231550
}
15241551
}
15251552

@@ -3227,34 +3254,6 @@ impl<'a> Resolver<'a> {
32273254
})
32283255
}
32293256

3230-
/// This is equivalent to `get_traits_in_module_containing_item`, but without filtering by the associated item.
3231-
///
3232-
/// This is used by rustdoc for intra-doc links.
3233-
pub fn traits_in_scope(&mut self, module_id: DefId) -> Vec<TraitCandidate> {
3234-
let module = self.get_module(module_id);
3235-
module.ensure_traits(self);
3236-
let traits = module.traits.borrow();
3237-
let to_candidate =
3238-
|this: &mut Self, &(trait_name, binding): &(Ident, &NameBinding<'_>)| TraitCandidate {
3239-
def_id: binding.res().def_id(),
3240-
import_ids: this.find_transitive_imports(&binding.kind, trait_name),
3241-
};
3242-
3243-
let mut candidates: Vec<_> =
3244-
traits.as_ref().unwrap().iter().map(|x| to_candidate(self, x)).collect();
3245-
3246-
if let Some(prelude) = self.prelude {
3247-
if !module.no_implicit_prelude {
3248-
prelude.ensure_traits(self);
3249-
candidates.extend(
3250-
prelude.traits.borrow().as_ref().unwrap().iter().map(|x| to_candidate(self, x)),
3251-
);
3252-
}
3253-
}
3254-
3255-
candidates
3256-
}
3257-
32583257
/// Rustdoc uses this to resolve things in a recoverable way. `ResolutionError<'a>`
32593258
/// isn't something that can be returned because it can't be made to live that long,
32603259
/// and also it's a private type. Fortunately rustdoc doesn't need to know the error,

src/librustdoc/passes/collect_intra_doc_links.rs

+7-2
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ use rustc_session::lint::{
1919
builtin::{BROKEN_INTRA_DOC_LINKS, PRIVATE_INTRA_DOC_LINKS},
2020
Lint,
2121
};
22-
use rustc_span::hygiene::MacroKind;
22+
use rustc_span::hygiene::{MacroKind, SyntaxContext};
2323
use rustc_span::symbol::Ident;
2424
use rustc_span::symbol::Symbol;
2525
use rustc_span::DUMMY_SP;
@@ -770,7 +770,12 @@ fn traits_implemented_by(cx: &DocContext<'_>, type_: DefId, module: DefId) -> Fx
770770
let mut cache = cx.module_trait_cache.borrow_mut();
771771
let in_scope_traits = cache.entry(module).or_insert_with(|| {
772772
cx.enter_resolver(|resolver| {
773-
resolver.traits_in_scope(module).into_iter().map(|candidate| candidate.def_id).collect()
773+
let parent_scope = &ParentScope::module(resolver.get_module(module), resolver);
774+
resolver
775+
.traits_in_scope(None, parent_scope, SyntaxContext::root(), None)
776+
.into_iter()
777+
.map(|candidate| candidate.def_id)
778+
.collect()
774779
})
775780
});
776781

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// Macros with def-site hygiene still bring traits into scope.
2+
// It is not clear whether this is desirable behavior or not.
3+
// It is also not clear how to prevent it if it is not desirable.
4+
5+
// check-pass
6+
7+
#![feature(decl_macro)]
8+
#![feature(trait_alias)]
9+
10+
mod traits {
11+
pub trait Trait1 {
12+
fn simple_import(&self) {}
13+
}
14+
pub trait Trait2 {
15+
fn renamed_import(&self) {}
16+
}
17+
pub trait Trait3 {
18+
fn underscore_import(&self) {}
19+
}
20+
pub trait Trait4 {
21+
fn trait_alias(&self) {}
22+
}
23+
24+
impl Trait1 for () {}
25+
impl Trait2 for () {}
26+
impl Trait3 for () {}
27+
impl Trait4 for () {}
28+
}
29+
30+
macro m1() {
31+
use traits::Trait1;
32+
}
33+
macro m2() {
34+
use traits::Trait2 as Alias;
35+
}
36+
macro m3() {
37+
use traits::Trait3 as _;
38+
}
39+
macro m4() {
40+
trait Alias = traits::Trait4;
41+
}
42+
43+
fn main() {
44+
m1!();
45+
m2!();
46+
m3!();
47+
m4!();
48+
49+
().simple_import();
50+
().renamed_import();
51+
().underscore_import();
52+
().trait_alias();
53+
}

src/test/ui/underscore-imports/hygiene.rs

+5-5
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1-
// Make sure that underscore imports have the same hygiene considerations as
2-
// other imports.
1+
// Make sure that underscore imports have the same hygiene considerations as other imports.
2+
3+
// check-pass
34

45
#![feature(decl_macro)]
56

67
mod x {
78
pub use std::ops::Deref as _;
89
}
910

10-
1111
macro glob_import() {
1212
pub use crate::x::*;
1313
}
@@ -35,6 +35,6 @@ fn main() {
3535
use crate::z::*;
3636
glob_import!();
3737
underscore_import!();
38-
(&()).deref(); //~ ERROR no method named `deref`
39-
(&mut ()).deref_mut(); //~ ERROR no method named `deref_mut`
38+
(&()).deref();
39+
(&mut ()).deref_mut();
4040
}

0 commit comments

Comments
 (0)