Skip to main content

rapx/helpers/
path.rs

1#[cfg(not(rapx_has_skip_norm_wip))]
2use crate::compat::SkipNormWip;
3use itertools::Itertools;
4use rustc_hir::def::DefKind;
5use rustc_hir::def_id::{DefId, LOCAL_CRATE};
6use rustc_middle::ty::{self, Ty, TyCtxt, TyKind};
7use std::collections::HashMap;
8
9/// A utility to resolve the actual visible path for re-export items.
10pub struct PathResolver<'tcx> {
11    tcx: TyCtxt<'tcx>,
12    path_map: HashMap<DefId, String>,
13}
14
15pub fn get_path_resolver<'tcx>(tcx: TyCtxt<'tcx>) -> PathResolver<'tcx> {
16    let mut resolver = PathResolver::new(tcx);
17    resolver.build(LOCAL_CRATE.as_def_id(), String::new());
18    resolver
19}
20
21fn join_path_with_double_colon(parts: &[&str]) -> String {
22    parts.iter().filter(|s| !s.is_empty()).join("::")
23}
24
25impl<'tcx> PathResolver<'tcx> {
26    fn new(tcx: TyCtxt<'tcx>) -> Self {
27        PathResolver {
28            tcx,
29            path_map: HashMap::new(),
30        }
31    }
32
33    fn build(&mut self, mod_id: DefId, current_path: String) {
34        rap_trace!("enter module: {:?}, path: {}", mod_id, current_path);
35        let childs = if mod_id.is_local() {
36            self.tcx.module_children_local(mod_id.expect_local())
37        } else {
38            self.tcx.module_children(mod_id)
39        };
40
41        for child in childs {
42            rap_trace!(
43                "processing child: {:?}, ident = {}",
44                child.res,
45                child.ident.as_str()
46            );
47            if !child.vis.is_public() || child.ident.as_str() == "_" {
48                continue;
49            }
50            if let Some(did) = child.res.opt_def_id() {
51                let path = join_path_with_double_colon(&[&current_path, child.ident.as_str()]);
52                self.path_map.entry(did).or_insert(path.clone());
53                if self.tcx.def_kind(did).is_module_like() {
54                    self.build(did, path);
55                }
56            }
57        }
58    }
59
60    fn non_assoc_path_str(&self, def_id: DefId) -> String {
61        match self.path_map.get(&def_id) {
62            Some(path) => path.clone(),
63            None => {
64                // if def_id is from local crate, but we cannot find it in path_map,
65                // report this error.
66                if def_id.is_local() {
67                    rap_error!(
68                        "[PathResolver] cannot find path for {:?}, fallback to self.tcx.def_path_str",
69                        def_id
70                    );
71                }
72                self.tcx.def_path_str(def_id)
73            }
74        }
75    }
76
77    pub fn path_exists(&self, did: DefId) -> bool {
78        let Some((assoc_id, kind)) = self.tcx.assoc_parent(did) else {
79            // check non associated item
80            return did.is_local() && self.path_map.contains_key(&did)
81            // for did from other crate, we rely on tcx.visibility, 
82            // which is not 100% accurate, but should be good enough in most cases.
83                || !did.is_local() && self.tcx.visibility(did).is_public();
84        };
85
86        if !self.tcx.visibility(did).is_public() {
87            return false;
88        }
89
90        match kind {
91            DefKind::Impl { .. } => {
92                let self_ty = self
93                    .tcx
94                    .type_of(assoc_id)
95                    .instantiate_identity()
96                    .skip_norm_wip();
97                match self_ty.kind() {
98                    // Theoretically, we need to check visibility of generic args.
99                    // However, it is a bit complicated and we currently do not consider it.
100                    TyKind::Adt(adt_def, _) => self.path_exists(adt_def.did()),
101                    _ => true,
102                }
103            }
104            DefKind::Trait => self.path_exists(assoc_id),
105            _ => panic!(
106                "unexpected parent kind: {:?} for assoc item: {:?}",
107                kind, did
108            ),
109        }
110    }
111
112    pub fn ty_str(&self, ty: Ty<'tcx>) -> String {
113        match ty.kind() {
114            TyKind::Adt(adt_def, args) => self.path_str_with_args(adt_def.did(), args),
115            TyKind::Array(inner_ty, const_) => {
116                format!("[{};{}]", self.ty_str(*inner_ty), const_)
117            }
118            TyKind::Tuple(tys) => {
119                format!("({})", tys.iter().map(|ty| self.ty_str(ty)).join(", "))
120            }
121            TyKind::Ref(region, inner_ty, mutability) => {
122                format!(
123                    "&{} {}{}",
124                    region,
125                    mutability.prefix_str(),
126                    self.ty_str(*inner_ty)
127                )
128            }
129            TyKind::RawPtr(inner_ty, mutability) => {
130                format!("*{} {}", mutability.ptr_str(), self.ty_str(*inner_ty))
131            }
132            TyKind::Slice(inner_ty) => {
133                format!("[{}]", self.ty_str(*inner_ty))
134            }
135            #[cfg(rapx_alias_ty_structured_kind)]
136            TyKind::Alias(is_rigid, alias_ty) => match alias_ty.kind {
137                ty::AliasTyKind::Projection { def_id } => {
138                    self.path_str_with_args(def_id, alias_ty.args)
139                }
140                ty::AliasTyKind::Opaque { .. } => {
141                    let ty_str = alias_ty.to_ty(self.tcx, *is_rigid).to_string();
142                    rap_warn!(
143                        "encounter opaque type {}, type string might be private",
144                        ty_str
145                    );
146                    ty_str
147                }
148                kind => {
149                    panic!(
150                        "unexpected alias kind: {:?} for alias_ty: {:?}",
151                        kind, alias_ty
152                    );
153                }
154            },
155            #[cfg(not(rapx_alias_ty_structured_kind))]
156            TyKind::Alias(alias_kind, alias_ty) => match alias_kind {
157                ty::AliasTyKind::Projection => {
158                    self.path_str_with_args(alias_ty.def_id, alias_ty.args)
159                }
160                ty::AliasTyKind::Opaque => {
161                    let ty_str = alias_ty.to_ty(self.tcx).to_string();
162                    rap_warn!(
163                        "encounter opaque type {}, type string might be private",
164                        ty_str
165                    );
166                    ty_str
167                }
168                kind => {
169                    panic!(
170                        "unexpected alias kind: {:?} for alias_ty: {:?}",
171                        kind, alias_ty
172                    );
173                }
174            },
175            _ => ty.to_string(),
176        }
177    }
178
179    #[allow(unused)]
180    pub fn path_str(&self, def_id: DefId) -> String {
181        self.path_str_with_args(def_id, ty::GenericArgs::identity_for_item(self.tcx, def_id))
182    }
183
184    pub fn path_str_with_args(&self, def_id: DefId, args: ty::GenericArgsRef<'tcx>) -> String {
185        // `{assoc_path}::{item_name}`
186        if let Some((assoc_id, kind)) = self.tcx.assoc_parent(def_id) {
187            rap_trace!("assoc item: {:?} => {:?}", assoc_id, kind);
188            // the number of generic of assoc parent
189            let num_generic = self.tcx.generics_of(assoc_id).own_params.len();
190
191            let (parent_args, own_args) = args.split_at(num_generic);
192
193            let parent_path_str = match kind {
194                // Trait Impl
195                DefKind::Impl { of_trait: true } => {
196                    let trait_ref = self
197                        .tcx
198                        .impl_trait_ref(assoc_id)
199                        .instantiate(self.tcx, parent_args);
200
201                    #[cfg(rapx_ge_99)]
202                    let trait_ref = trait_ref.skip_norm_wip();
203
204                    self.qualified_path_str(
205                        trait_ref.self_ty(),
206                        trait_ref.def_id,
207                        &trait_ref.args[1..],
208                    )
209                }
210                // inherent impl
211                DefKind::Impl { of_trait: false } => {
212                    let self_ty = self
213                        .tcx
214                        .type_of(assoc_id)
215                        .instantiate(self.tcx, parent_args);
216                    #[cfg(rapx_ge_99)]
217                    let self_ty = self_ty.skip_norm_wip();
218                    self.ty_str(self_ty)
219                }
220                // Trait
221                DefKind::Trait => {
222                    self.qualified_path_str(parent_args[0].expect_ty(), assoc_id, &parent_args[1..])
223                }
224                _ => {
225                    unreachable!(
226                        "unexpected assoc parent: {:?} => {:?}, def_id: {:?}, path: {:?}",
227                        assoc_id,
228                        kind,
229                        def_id,
230                        self.tcx.def_path_str_with_args(def_id, args)
231                    );
232                }
233            };
234
235            let args_str = self.non_syn_generic_args_str(def_id, own_args);
236
237            join_path_with_double_colon(&[
238                &parent_path_str,
239                self.tcx.item_name(def_id).as_str(),
240                &args_str,
241            ])
242        } else {
243            // non assoc item
244            let path_str = self.non_assoc_path_str(def_id);
245            let args_str = self.non_syn_generic_args_str(def_id, args);
246            join_path_with_double_colon(&[path_str.as_str(), args_str.as_str()])
247        }
248    }
249
250    pub fn generic_arg_str(&self, arg: ty::GenericArg<'tcx>) -> String {
251        match arg.kind() {
252            ty::GenericArgKind::Lifetime(_) => "'_".to_string(),
253            ty::GenericArgKind::Type(ty) => self.ty_str(ty),
254            ty::GenericArgKind::Const(const_) => format!("{}", const_),
255        }
256    }
257
258    /// Format `<Self as Trait<args>>` for a trait impl or trait-associated item.
259    fn qualified_path_str(
260        &self,
261        self_ty: Ty<'tcx>,
262        trait_id: DefId,
263        args: &[ty::GenericArg<'tcx>],
264    ) -> String {
265        let self_ty_str = self.ty_str(self_ty);
266        let trait_str = self.non_assoc_path_str(trait_id);
267        if args.is_empty() {
268            format!("<{} as {}>", self_ty_str, trait_str)
269        } else {
270            format!(
271                "<{} as {}{}>",
272                self_ty_str,
273                trait_str,
274                self.generic_args_str(args)
275            )
276        }
277    }
278
279    fn generic_args_str(&self, generic_args: &[ty::GenericArg<'tcx>]) -> String {
280        format!(
281            "<{}>",
282            generic_args
283                .iter()
284                .map(|arg| self.generic_arg_str(*arg))
285                .join(", ")
286        )
287    }
288
289    fn non_syn_generic_args_str(&self, def_id: DefId, args: &[ty::GenericArg<'tcx>]) -> String {
290        let defs = self.tcx.generics_of(def_id).own_params.as_slice();
291
292        assert!(defs.len() == args.len());
293
294        let non_syn_args = args
295            .iter()
296            .zip(defs.iter())
297            .filter_map(|(arg, param)| {
298                if param.kind.is_synthetic() {
299                    None
300                } else {
301                    Some(*arg)
302                }
303            })
304            .collect_vec();
305
306        // No suffix for empty generic args, e.g. render `foo` instead of `foo::<>`.
307        if non_syn_args.is_empty() {
308            String::new()
309        } else {
310            self.generic_args_str(&non_syn_args)
311        }
312    }
313}