Auto merge of #104602 - petrochenkov:effvisperf5, r=oli-obk
privacy: Fix more (potential) issues with effective visibilities Continuation of https://github.com/rust-lang/rust/pull/103965. See individual commits for more detailed description of the changes. The shortcuts removed in https://github.com/rust-lang/rust/pull/104602/commits/4eb63f618e601efee657d24cd4e8833fb03fac4c and https://github.com/rust-lang/rust/pull/104602/commits/c7c7d1672739e38c8d39ae861b284486aefd5b48 could actually be correct (or correct after some tweaks), but they used global reasoning like "we can skip this update because if the code compiles then some other update should do the same thing eventually". I have some expertise in this area, but I still have doubt whether such global reasoning was correct or not, especially in presence of all possible exotic cases with imports. After this PR all table changes should be "locally correct" after every update, even if it may be overcautious. If similar optimizations are introduced again they will need detailed comments explaining why it's legal to do what they do and providing proofs. Fixes https://github.com/rust-lang/rust/issues/104249. Fixes https://github.com/rust-lang/rust/issues/104539.
This commit is contained in:
commit
41e0363055
6 changed files with 178 additions and 101 deletions
|
@ -7,8 +7,10 @@ use rustc_ast::EnumDef;
|
|||
use rustc_data_structures::intern::Interned;
|
||||
use rustc_hir::def_id::LocalDefId;
|
||||
use rustc_hir::def_id::CRATE_DEF_ID;
|
||||
use rustc_middle::middle::privacy::{EffectiveVisibilities, EffectiveVisibility, Level};
|
||||
use rustc_middle::ty::Visibility;
|
||||
use rustc_middle::middle::privacy::{EffectiveVisibilities, EffectiveVisibility};
|
||||
use rustc_middle::middle::privacy::{IntoDefIdTree, Level};
|
||||
use rustc_middle::ty::{DefIdTree, Visibility};
|
||||
use std::mem;
|
||||
|
||||
type ImportId<'a> = Interned<'a, NameBinding<'a>>;
|
||||
|
||||
|
@ -29,13 +31,49 @@ impl ParentId<'_> {
|
|||
|
||||
pub struct EffectiveVisibilitiesVisitor<'r, 'a> {
|
||||
r: &'r mut Resolver<'a>,
|
||||
def_effective_visibilities: EffectiveVisibilities,
|
||||
/// While walking import chains we need to track effective visibilities per-binding, and def id
|
||||
/// keys in `Resolver::effective_visibilities` are not enough for that, because multiple
|
||||
/// bindings can correspond to a single def id in imports. So we keep a separate table.
|
||||
import_effective_visibilities: EffectiveVisibilities<ImportId<'a>>,
|
||||
// It's possible to recalculate this at any point, but it's relatively expensive.
|
||||
current_private_vis: Visibility,
|
||||
changed: bool,
|
||||
}
|
||||
|
||||
impl Resolver<'_> {
|
||||
fn nearest_normal_mod(&mut self, def_id: LocalDefId) -> LocalDefId {
|
||||
self.get_nearest_non_block_module(def_id.to_def_id()).nearest_parent_mod().expect_local()
|
||||
}
|
||||
|
||||
fn private_vis_import(&mut self, binding: ImportId<'_>) -> Visibility {
|
||||
let NameBindingKind::Import { import, .. } = binding.kind else { unreachable!() };
|
||||
Visibility::Restricted(
|
||||
import
|
||||
.id()
|
||||
.map(|id| self.nearest_normal_mod(self.local_def_id(id)))
|
||||
.unwrap_or(CRATE_DEF_ID),
|
||||
)
|
||||
}
|
||||
|
||||
fn private_vis_def(&mut self, def_id: LocalDefId) -> Visibility {
|
||||
// For mod items `nearest_normal_mod` returns its argument, but we actually need its parent.
|
||||
let normal_mod_id = self.nearest_normal_mod(def_id);
|
||||
if normal_mod_id == def_id {
|
||||
self.opt_local_parent(def_id).map_or(Visibility::Public, Visibility::Restricted)
|
||||
} else {
|
||||
Visibility::Restricted(normal_mod_id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b> IntoDefIdTree for &'b mut Resolver<'a> {
|
||||
type Tree = &'b Resolver<'a>;
|
||||
fn tree(self) -> Self::Tree {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'r, 'a> EffectiveVisibilitiesVisitor<'r, 'a> {
|
||||
/// Fills the `Resolver::effective_visibilities` table with public & exported items
|
||||
/// For now, this doesn't resolve macros (FIXME) and cannot resolve Impl, as we
|
||||
|
@ -43,17 +81,21 @@ impl<'r, 'a> EffectiveVisibilitiesVisitor<'r, 'a> {
|
|||
pub fn compute_effective_visibilities<'c>(r: &'r mut Resolver<'a>, krate: &'c Crate) {
|
||||
let mut visitor = EffectiveVisibilitiesVisitor {
|
||||
r,
|
||||
def_effective_visibilities: Default::default(),
|
||||
import_effective_visibilities: Default::default(),
|
||||
current_private_vis: Visibility::Public,
|
||||
changed: false,
|
||||
};
|
||||
|
||||
visitor.update(CRATE_DEF_ID, CRATE_DEF_ID);
|
||||
visitor.current_private_vis = Visibility::Restricted(CRATE_DEF_ID);
|
||||
visitor.set_bindings_effective_visibilities(CRATE_DEF_ID);
|
||||
|
||||
while visitor.changed {
|
||||
visitor.changed = false;
|
||||
visit::walk_crate(&mut visitor, krate);
|
||||
}
|
||||
visitor.r.effective_visibilities = visitor.def_effective_visibilities;
|
||||
|
||||
// Update visibilities for import def ids. These are not used during the
|
||||
// `EffectiveVisibilitiesVisitor` pass, because we have more detailed binding-based
|
||||
|
@ -90,10 +132,6 @@ impl<'r, 'a> EffectiveVisibilitiesVisitor<'r, 'a> {
|
|||
info!("resolve::effective_visibilities: {:#?}", r.effective_visibilities);
|
||||
}
|
||||
|
||||
fn nearest_normal_mod(&mut self, def_id: LocalDefId) -> LocalDefId {
|
||||
self.r.get_nearest_non_block_module(def_id.to_def_id()).nearest_parent_mod().expect_local()
|
||||
}
|
||||
|
||||
/// Update effective visibilities of bindings in the given module,
|
||||
/// including their whole reexport chains.
|
||||
fn set_bindings_effective_visibilities(&mut self, module_id: LocalDefId) {
|
||||
|
@ -122,62 +160,47 @@ impl<'r, 'a> EffectiveVisibilitiesVisitor<'r, 'a> {
|
|||
}
|
||||
}
|
||||
|
||||
fn effective_vis(&self, parent_id: ParentId<'a>) -> Option<EffectiveVisibility> {
|
||||
match parent_id {
|
||||
ParentId::Def(def_id) => self.r.effective_visibilities.effective_vis(def_id),
|
||||
ParentId::Import(binding) => self.import_effective_visibilities.effective_vis(binding),
|
||||
}
|
||||
.copied()
|
||||
fn cheap_private_vis(&self, parent_id: ParentId<'_>) -> Option<Visibility> {
|
||||
matches!(parent_id, ParentId::Def(_)).then_some(self.current_private_vis)
|
||||
}
|
||||
|
||||
/// The update is guaranteed to not change the table and we can skip it.
|
||||
fn is_noop_update(
|
||||
&self,
|
||||
parent_id: ParentId<'a>,
|
||||
nominal_vis: Visibility,
|
||||
default_vis: Visibility,
|
||||
) -> bool {
|
||||
nominal_vis == default_vis
|
||||
|| match parent_id {
|
||||
ParentId::Def(def_id) => self.r.visibilities[&def_id],
|
||||
ParentId::Import(binding) => binding.vis.expect_local(),
|
||||
} == default_vis
|
||||
fn effective_vis_or_private(&mut self, parent_id: ParentId<'a>) -> EffectiveVisibility {
|
||||
// Private nodes are only added to the table for caching, they could be added or removed at
|
||||
// any moment without consequences, so we don't set `changed` to true when adding them.
|
||||
*match parent_id {
|
||||
ParentId::Def(def_id) => self
|
||||
.def_effective_visibilities
|
||||
.effective_vis_or_private(def_id, || self.r.private_vis_def(def_id)),
|
||||
ParentId::Import(binding) => self
|
||||
.import_effective_visibilities
|
||||
.effective_vis_or_private(binding, || self.r.private_vis_import(binding)),
|
||||
}
|
||||
}
|
||||
|
||||
fn update_import(&mut self, binding: ImportId<'a>, parent_id: ParentId<'a>) {
|
||||
let NameBindingKind::Import { import, .. } = binding.kind else { unreachable!() };
|
||||
let nominal_vis = binding.vis.expect_local();
|
||||
let default_vis = Visibility::Restricted(
|
||||
import
|
||||
.id()
|
||||
.map(|id| self.nearest_normal_mod(self.r.local_def_id(id)))
|
||||
.unwrap_or(CRATE_DEF_ID),
|
||||
);
|
||||
if self.is_noop_update(parent_id, nominal_vis, default_vis) {
|
||||
return;
|
||||
}
|
||||
let private_vis = self.cheap_private_vis(parent_id);
|
||||
let inherited_eff_vis = self.effective_vis_or_private(parent_id);
|
||||
self.changed |= self.import_effective_visibilities.update(
|
||||
binding,
|
||||
nominal_vis,
|
||||
default_vis,
|
||||
self.effective_vis(parent_id),
|
||||
|r| (private_vis.unwrap_or_else(|| r.private_vis_import(binding)), r),
|
||||
inherited_eff_vis,
|
||||
parent_id.level(),
|
||||
ResolverTree(&self.r.definitions, &self.r.crate_loader),
|
||||
&mut *self.r,
|
||||
);
|
||||
}
|
||||
|
||||
fn update_def(&mut self, def_id: LocalDefId, nominal_vis: Visibility, parent_id: ParentId<'a>) {
|
||||
let default_vis = Visibility::Restricted(self.nearest_normal_mod(def_id));
|
||||
if self.is_noop_update(parent_id, nominal_vis, default_vis) {
|
||||
return;
|
||||
}
|
||||
self.changed |= self.r.effective_visibilities.update(
|
||||
let private_vis = self.cheap_private_vis(parent_id);
|
||||
let inherited_eff_vis = self.effective_vis_or_private(parent_id);
|
||||
self.changed |= self.def_effective_visibilities.update(
|
||||
def_id,
|
||||
nominal_vis,
|
||||
if def_id == CRATE_DEF_ID { Visibility::Public } else { default_vis },
|
||||
self.effective_vis(parent_id),
|
||||
|r| (private_vis.unwrap_or_else(|| r.private_vis_def(def_id)), r),
|
||||
inherited_eff_vis,
|
||||
parent_id.level(),
|
||||
ResolverTree(&self.r.definitions, &self.r.crate_loader),
|
||||
&mut *self.r,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -201,8 +224,11 @@ impl<'r, 'ast> Visitor<'ast> for EffectiveVisibilitiesVisitor<'ast, 'r> {
|
|||
),
|
||||
|
||||
ast::ItemKind::Mod(..) => {
|
||||
let prev_private_vis =
|
||||
mem::replace(&mut self.current_private_vis, Visibility::Restricted(def_id));
|
||||
self.set_bindings_effective_visibilities(def_id);
|
||||
visit::walk_item(self, item);
|
||||
self.current_private_vis = prev_private_vis;
|
||||
}
|
||||
|
||||
ast::ItemKind::Enum(EnumDef { ref variants }, _) => {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue