use std::collections::HashSet;
use rustc_hir::AmbigArg;
use rustc_hir::Expr;
use rustc_hir::ExprKind;
use rustc_hir::HirId;
use rustc_hir::ImplItem;
use rustc_hir::Item;
use rustc_hir::ItemKind;
use rustc_hir::Pat;
use rustc_hir::PatExprKind;
use rustc_hir::PatKind;
use rustc_hir::QPath;
use rustc_hir::TraitItem;
use rustc_hir::Ty;
use rustc_hir::TyKind;
use rustc_hir::def::DefKind;
use rustc_hir::def::Res;
use rustc_hir::def_id::CRATE_DEF_ID;
use rustc_hir::def_id::DefId;
use rustc_hir::def_id::LocalDefId;
use rustc_hir::intravisit::Visitor;
use rustc_hir::intravisit::walk_expr;
use rustc_hir::intravisit::walk_impl_item;
use rustc_hir::intravisit::walk_item;
use rustc_hir::intravisit::walk_trait_item;
use rustc_middle::hir::nested_filter::All;
use rustc_middle::ty;
use rustc_middle::ty::TyCtxt;
use rustc_middle::ty::Visibility;
use crate::compiler::persistence::UseSite;
use crate::rust_syntax::PathAnchor;
pub(super) fn collect_use_sites(tcx: TyCtxt<'_>, out: &mut Vec<UseSite>) {
let mut collector = UseSiteCollector {
tcx,
current_module: CRATE_DEF_ID.to_def_id(),
out,
};
let crate_items = tcx.hir_crate_items(());
for item_id in crate_items.free_items() {
let item = tcx.hir_item(item_id);
collector.visit_item(item);
}
for impl_item_id in crate_items.impl_items() {
let impl_item = tcx.hir_impl_item(impl_item_id);
collector.visit_impl_item(impl_item);
}
for trait_item_id in crate_items.trait_items() {
let trait_item = tcx.hir_trait_item(trait_item_id);
collector.visit_trait_item(trait_item);
}
}
struct UseSiteCollector<'a, 'tcx> {
tcx: TyCtxt<'tcx>,
current_module: DefId,
out: &'a mut Vec<UseSite>,
}
impl<'tcx> UseSiteCollector<'_, 'tcx> {
fn record_target(&mut self, target: DefId) {
if target.is_local() {
self.push_site(target);
}
match self.tcx.def_kind(target) {
DefKind::TyAlias => self.record_alias_components(target),
DefKind::Fn | DefKind::AssocFn => self.record_fn_signature_components(target),
_ => {},
}
}
fn record_fn_signature_components(&mut self, func: DefId) {
let signature = self.tcx.fn_sig(func).instantiate_identity();
let mut seen = HashSet::new();
for input_or_output in signature.skip_binder().inputs_and_output {
for arg in input_or_output.walk() {
if let Some(component) = arg.as_type()
&& let ty::TyKind::Adt(adt_def, _) = component.kind()
{
self.record_exposed_adt(adt_def.did(), &mut seen);
}
}
}
}
fn record_alias_components(&mut self, alias: DefId) {
let aliased = self.tcx.type_of(alias).instantiate_identity();
let mut seen = HashSet::new();
for arg in aliased.walk() {
if let Some(component) = arg.as_type()
&& let ty::TyKind::Adt(adt_def, _) = component.kind()
{
self.record_exposed_adt(adt_def.did(), &mut seen);
}
}
}
fn record_exposed_adt(&mut self, did: DefId, seen: &mut HashSet<DefId>) {
let Some(local) = did.as_local() else {
return;
};
if !seen.insert(did) {
return;
}
self.push_site(did);
let owning_module = self.tcx.parent_module_from_def_id(local).to_def_id();
for field in self.tcx.adt_def(did).all_fields() {
if !self.field_escapes_module(field.did, owning_module) {
continue;
}
for arg in self.tcx.type_of(field.did).instantiate_identity().walk() {
if let Some(component) = arg.as_type()
&& let ty::TyKind::Adt(adt_def, _) = component.kind()
{
self.record_exposed_adt(adt_def.did(), seen);
}
}
}
}
fn record_trait_impl_interface(&mut self, impl_def: LocalDefId) {
if !matches!(
self.tcx.def_kind(impl_def.to_def_id()),
DefKind::Impl { of_trait: true }
) {
return;
}
let trait_ref = self.tcx.impl_trait_ref(impl_def).instantiate_identity();
let self_adt = trait_ref.self_ty().ty_adt_def().map(ty::AdtDef::did);
let previous_module = self.current_module;
self.current_module = self.interface_scope_module(trait_ref.def_id, self_adt);
let mut seen = HashSet::new();
for arg in trait_ref.args {
if let Some(arg_type) = arg.as_type() {
self.record_interface_component_types(arg_type, self_adt, &mut seen);
}
}
for assoc_def_id in self.tcx.associated_item_def_ids(impl_def) {
match self.tcx.def_kind(*assoc_def_id) {
DefKind::AssocTy | DefKind::AssocConst { .. } => {
let assoc_type = self.tcx.type_of(*assoc_def_id).instantiate_identity();
self.record_interface_component_types(assoc_type, self_adt, &mut seen);
},
DefKind::AssocFn => {
let signature = self.tcx.fn_sig(*assoc_def_id).instantiate_identity();
for input_or_output in signature.skip_binder().inputs_and_output {
self.record_interface_component_types(input_or_output, self_adt, &mut seen);
}
},
_ => {},
}
}
self.current_module = previous_module;
}
fn interface_scope_module(&self, trait_def_id: DefId, self_adt: Option<DefId>) -> DefId {
let trait_visibility = self.tcx.visibility(trait_def_id);
let self_visibility =
self_adt.map_or(Visibility::Public, |adt_did| self.tcx.visibility(adt_did));
match (trait_visibility, self_visibility) {
(Visibility::Restricted(trait_scope), Visibility::Restricted(self_scope)) => {
if self.tcx.is_descendant_of(trait_scope, self_scope) {
trait_scope
} else {
self_scope
}
},
(Visibility::Restricted(scope), Visibility::Public)
| (Visibility::Public, Visibility::Restricted(scope)) => scope,
(Visibility::Public, Visibility::Public) => CRATE_DEF_ID.to_def_id(),
}
}
fn record_interface_component_types(
&mut self,
component_type: ty::Ty<'tcx>,
self_adt: Option<DefId>,
seen: &mut HashSet<DefId>,
) {
for arg in component_type.walk() {
if let Some(component) = arg.as_type()
&& let ty::TyKind::Adt(adt_def, _) = component.kind()
&& adt_def.did().is_local()
&& Some(adt_def.did()) != self_adt
&& seen.insert(adt_def.did())
{
self.push_site(adt_def.did());
}
}
}
fn field_escapes_module(&self, field: DefId, owning_module: DefId) -> bool {
match self.tcx.visibility(field) {
Visibility::Public => true,
Visibility::Restricted(scope) => scope != owning_module,
}
}
fn push_site(&mut self, target: DefId) {
self.out.push(UseSite {
target_def_path: self.tcx.def_path_str(target),
caller_module_def_path: self.tcx.def_path_str(self.current_module),
});
}
fn record_qpath(&mut self, qpath: &QPath<'_>, hir_id: HirId) {
let res = match qpath {
QPath::Resolved(_, path) => path.res,
QPath::TypeRelative(..) => {
let owner = hir_id.owner.def_id;
if !self.tcx.has_typeck_results(owner) {
return;
}
let typeck = self.tcx.typeck(owner);
typeck.qpath_res(qpath, hir_id)
},
};
if let Res::Def(_, def_id) = res {
self.record_target(def_id);
}
}
}
impl<'tcx> Visitor<'tcx> for UseSiteCollector<'_, 'tcx> {
type NestedFilter = All;
fn maybe_tcx(&mut self) -> TyCtxt<'tcx> { self.tcx }
fn visit_item(&mut self, item: &'tcx Item<'tcx>) {
let prev = self.current_module;
if matches!(item.kind, ItemKind::Mod(..)) {
self.current_module = item.owner_id.def_id.to_def_id();
} else {
self.current_module = self
.tcx
.parent_module_from_def_id(item.owner_id.def_id)
.to_def_id();
}
if matches!(item.kind, ItemKind::Impl(..)) {
self.record_trait_impl_interface(item.owner_id.def_id);
}
walk_item(self, item);
self.current_module = prev;
}
fn visit_impl_item(&mut self, item: &'tcx ImplItem<'tcx>) {
let prev = self.current_module;
self.current_module = self
.tcx
.parent_module_from_def_id(item.owner_id.def_id)
.to_def_id();
walk_impl_item(self, item);
self.current_module = prev;
}
fn visit_trait_item(&mut self, item: &'tcx TraitItem<'tcx>) {
let prev = self.current_module;
self.current_module = self
.tcx
.parent_module_from_def_id(item.owner_id.def_id)
.to_def_id();
walk_trait_item(self, item);
self.current_module = prev;
}
fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
match &expr.kind {
ExprKind::Path(qpath) => self.record_qpath(qpath, expr.hir_id),
ExprKind::MethodCall(..) => {
let owner = expr.hir_id.owner.def_id;
if self.tcx.has_typeck_results(owner)
&& let Some(def_id) = self.tcx.typeck(owner).type_dependent_def_id(expr.hir_id)
{
self.record_target(def_id);
}
},
ExprKind::Struct(qpath, ..) => self.record_qpath(qpath, expr.hir_id),
_ => {},
}
walk_expr(self, expr);
}
fn visit_ty(&mut self, ty: &'tcx Ty<'tcx, AmbigArg>) {
if let TyKind::Path(qpath) = &ty.kind {
self.record_qpath(qpath, ty.hir_id);
}
rustc_hir::intravisit::walk_ty(self, ty);
}
fn visit_pat(&mut self, pat: &'tcx Pat<'tcx>) {
if let PatKind::Expr(expr) = &pat.kind
&& let PatExprKind::Path(qpath) = &expr.kind
{
self.record_qpath(qpath, expr.hir_id);
}
rustc_hir::intravisit::walk_pat(self, pat);
}
}
pub(super) fn def_path_string(tcx: TyCtxt<'_>, def_id: LocalDefId) -> String {
tcx.def_path_str(def_id.to_def_id())
}
pub(super) fn parent_module_def_path(tcx: TyCtxt<'_>, def_id: LocalDefId) -> String {
let parent = tcx.parent_module_from_def_id(def_id);
tcx.def_path_str(parent.to_def_id())
}
pub(super) fn parent_module_path_segments(tcx: TyCtxt<'_>, def_id: LocalDefId) -> Vec<String> {
let mut segments = parent_module_def_path(tcx, def_id)
.split("::")
.filter(|segment| !segment.is_empty())
.map(String::from)
.collect::<Vec<_>>();
if PathAnchor::first(&segments) == Some(PathAnchor::Crate) {
segments.remove(0);
}
segments
}