Auto merge of #147486 - petrochenkov:optpriv, r=lcnr

privacy: Introduce some caching to type visiting in `DefIdVisitorSkeleton`

The caching fixes compilation speed issues in special cases like https://github.com/rust-lang/rust/issues/145741, without introducing too much overhead in general cases.

I tried to cache more, but it caused regressions from the caching overhead, like it can be seen from benchmark runs below.

Inspired by https://github.com/rust-lang/rust/pull/146128.
Closes https://github.com/rust-lang/rust/issues/145741.
This commit is contained in:
bors 2025-10-23 14:30:10 +00:00
commit 6501e64fcb

View file

@ -75,6 +75,9 @@ pub trait DefIdVisitor<'tcx> {
}
fn tcx(&self) -> TyCtxt<'tcx>;
/// NOTE: Def-id visiting should be idempotent (or at least produce duplicated errors),
/// because `DefIdVisitorSkeleton` will use caching and sometimes avoid visiting duplicate
/// def-ids. All the current visitors follow this rule.
fn visit_def_id(&mut self, def_id: DefId, kind: &str, descr: &dyn fmt::Display)
-> Self::Result;
@ -82,7 +85,7 @@ pub trait DefIdVisitor<'tcx> {
fn skeleton(&mut self) -> DefIdVisitorSkeleton<'_, 'tcx, Self> {
DefIdVisitorSkeleton {
def_id_visitor: self,
visited_opaque_tys: Default::default(),
visited_tys: Default::default(),
dummy: Default::default(),
}
}
@ -102,7 +105,7 @@ pub trait DefIdVisitor<'tcx> {
pub struct DefIdVisitorSkeleton<'v, 'tcx, V: ?Sized> {
def_id_visitor: &'v mut V,
visited_opaque_tys: FxHashSet<DefId>,
visited_tys: FxHashSet<Ty<'tcx>>,
dummy: PhantomData<TyCtxt<'tcx>>,
}
@ -183,7 +186,8 @@ where
let tcx = self.def_id_visitor.tcx();
// GenericArgs are not visited here because they are visited below
// in `super_visit_with`.
match *ty.kind() {
let ty_kind = *ty.kind();
match ty_kind {
ty::Adt(ty::AdtDef(Interned(&ty::AdtDefData { did: def_id, .. }, _)), ..)
| ty::Foreign(def_id)
| ty::FnDef(def_id, ..)
@ -197,7 +201,7 @@ where
// Default type visitor doesn't visit signatures of fn types.
// Something like `fn() -> Priv {my_func}` is considered a private type even if
// `my_func` is public, so we need to visit signatures.
if let ty::FnDef(..) = ty.kind() {
if let ty::FnDef(..) = ty_kind {
// FIXME: this should probably use `args` from `FnDef`
try_visit!(tcx.fn_sig(def_id).instantiate_identity().visit_with(self));
}
@ -220,6 +224,12 @@ where
// free type aliases, but this isn't done yet.
return V::Result::output();
}
if !self.visited_tys.insert(ty) {
// Avoid repeatedly visiting alias types (including projections).
// This helps with special cases like #145741, but doesn't introduce
// too much overhead in general case, unlike caching for other types.
return V::Result::output();
}
try_visit!(self.def_id_visitor.visit_def_id(
data.def_id,
@ -259,7 +269,7 @@ where
}
ty::Alias(ty::Opaque, ty::AliasTy { def_id, .. }) => {
// Skip repeated `Opaque`s to avoid infinite recursion.
if self.visited_opaque_tys.insert(def_id) {
if self.visited_tys.insert(ty) {
// The intent is to treat `impl Trait1 + Trait2` identically to
// `dyn Trait1 + Trait2`. Therefore we ignore def-id of the opaque type itself
// (it either has no visibility, or its visibility is insignificant, like
@ -929,7 +939,7 @@ impl<'tcx> NamePrivacyVisitor<'tcx> {
// Checks that a field in a struct constructor (expression or pattern) is accessible.
fn check_field(
&mut self,
&self,
hir_id: hir::HirId, // ID of the field use
use_ctxt: Span, // syntax context of the field name at the use site
def: ty::AdtDef<'tcx>, // definition of the struct or enum
@ -947,7 +957,7 @@ impl<'tcx> NamePrivacyVisitor<'tcx> {
// Checks that a field in a struct constructor (expression or pattern) is accessible.
fn emit_unreachable_field_error(
&mut self,
&self,
fields: Vec<(Symbol, Span, bool /* field is present */)>,
def: ty::AdtDef<'tcx>, // definition of the struct or enum
update_syntax: Option<Span>,
@ -1010,7 +1020,7 @@ impl<'tcx> NamePrivacyVisitor<'tcx> {
}
fn check_expanded_fields(
&mut self,
&self,
adt: ty::AdtDef<'tcx>,
variant: &'tcx ty::VariantDef,
fields: &[hir::ExprField<'tcx>],
@ -1148,7 +1158,7 @@ impl<'tcx> TypePrivacyVisitor<'tcx> {
result.is_break()
}
fn check_def_id(&mut self, def_id: DefId, kind: &str, descr: &dyn fmt::Display) -> bool {
fn check_def_id(&self, def_id: DefId, kind: &str, descr: &dyn fmt::Display) -> bool {
let is_error = !self.item_is_accessible(def_id);
if is_error {
self.tcx.dcx().emit_err(ItemIsPrivate { span: self.span, kind, descr: descr.into() });
@ -1405,7 +1415,7 @@ impl SearchInterfaceForPrivateItemsVisitor<'_> {
self
}
fn check_def_id(&mut self, def_id: DefId, kind: &str, descr: &dyn fmt::Display) -> bool {
fn check_def_id(&self, def_id: DefId, kind: &str, descr: &dyn fmt::Display) -> bool {
if self.leaks_private_dep(def_id) {
self.tcx.emit_node_span_lint(
lint::builtin::EXPORTED_PRIVATE_DEPENDENCIES,