Skip to main content

rapx/helpers/
name.rs

1use rustc_hir::def_id::DefId;
2use rustc_middle::ty::print::with_no_trimmed_paths;
3use rustc_middle::ty::{GenericArgKind, Ty, TyCtxt, TyKind};
4use serde_json::Value;
5use std::sync::OnceLock;
6use syn::Expr;
7
8/// The internal `crate::module::…` path of a `DefId` (e.g. `alloc::rcs::rc::Rc`),
9/// normalised from its `def_path_str` debug form.  Used for display/debug and as
10/// the fallback in [`public_def_path`] for items without a `Ty`.
11pub fn get_cleaned_def_path_name(tcx: TyCtxt<'_>, def_id: DefId) -> String {
12    let def_id_str = format!("{:?}", def_id);
13    let mut parts: Vec<&str> = def_id_str.split("::").collect();
14
15    // The `DefId` debug form is `DefId(.. ~ crate_name[hash]::path…)`; extract
16    // the crate name *exactly* (not by substring — `free_list_allocator` must
17    // not be misread as `alloc`).
18    let crate_name = def_id_str
19        .split("~ ")
20        .nth(1)
21        .and_then(|p| p.split('[').next())
22        .unwrap_or("");
23
24    let mut remove_first = false;
25    if let Some(first_part) = parts.get_mut(0) {
26        match crate_name {
27            "core" => *first_part = "core",
28            "std" => *first_part = "std",
29            "alloc" => *first_part = "alloc",
30            _ => remove_first = true,
31        }
32    }
33    if remove_first && !parts.is_empty() {
34        parts.remove(0);
35    }
36
37    let new_parts: Vec<String> = parts
38        .into_iter()
39        .filter_map(|s| {
40            if s.contains("{") {
41                if remove_first {
42                    get_struct_name(tcx, def_id)
43                } else {
44                    None
45                }
46            } else {
47                Some(s.to_string())
48            }
49        })
50        .collect();
51
52    let mut cleaned_path = new_parts.join("::");
53    cleaned_path = cleaned_path.trim_end_matches(')').to_string();
54    cleaned_path
55}
56
57/// Extract the implementing struct name from a `DefId` that belongs to an
58/// associated item (method / associated function).
59fn get_struct_name(tcx: TyCtxt<'_>, def_id: DefId) -> Option<String> {
60    if let Some(assoc_item) = tcx.opt_associated_item(def_id) {
61        if let Some(impl_id) = assoc_item.impl_container(tcx) {
62            let ty = tcx.type_of(impl_id).skip_binder();
63            let type_name = ty.to_string();
64            let struct_name = type_name
65                .split('<')
66                .next()
67                .unwrap_or("")
68                .split("::")
69                .last()
70                .unwrap_or("")
71                .to_string();
72
73            return Some(struct_name);
74        }
75    }
76    None
77}
78
79/// The public path of a type (re-exports resolved: `alloc::rc::Rc`), with the
80/// crate prefix and generic args stripped.  `public_def_path` re-adds the crate.
81fn public_ty_path<'tcx>(ty: Ty<'tcx>) -> String {
82    // Disable "good path" trimming, which depends on the current crate's imports.
83    let full = with_no_trimmed_paths!(ty.to_string());
84    let base = full.split('<').next().unwrap_or(&full);
85    base.split("::").skip(1).collect::<Vec<_>>().join("::")
86}
87
88/// The canonical std JSON asset key for a `DefId`, prefixed by its defining
89/// crate.  Types resolve re-exports; inherent methods keep the `module::method`
90/// shape, except that the private `alloc::rcs` module (Rust 1.96+) is mapped
91/// back to the public `rc`/`sync`.
92pub fn public_def_path<'tcx>(tcx: TyCtxt<'tcx>, def_id: DefId) -> String {
93    let defining_crate = tcx.crate_name(def_id.krate).to_string();
94    match tcx.def_kind(def_id) {
95        rustc_hir::def::DefKind::Struct
96        | rustc_hir::def::DefKind::Enum
97        | rustc_hir::def::DefKind::Union
98        | rustc_hir::def::DefKind::TyAlias => {
99            let path = public_ty_path(tcx.type_of(def_id).skip_binder());
100            if path.is_empty() {
101                defining_crate
102            } else {
103                format!("{defining_crate}::{path}")
104            }
105        }
106        rustc_hir::def::DefKind::AssocFn => {
107            let assoc_item = tcx.associated_item(def_id);
108            let name = assoc_item.name();
109            // Trait methods key by the trait; inherent methods by the self type.
110            if let Some(trait_item) = assoc_item.trait_item_def_id() {
111                let trait_path = get_cleaned_def_path_name(tcx, tcx.parent(trait_item));
112                format!("{trait_path}::{name}")
113            } else if let Some(impl_id) = assoc_item.impl_container(tcx) {
114                let self_ty = tcx.type_of(impl_id).skip_binder();
115                let def_path = get_cleaned_def_path_name(tcx, def_id);
116                // Map the private `alloc::rcs` module back to `rc`/`sync`.
117                if matches!(self_ty.kind(), TyKind::Adt(..)) && def_path.contains("::rcs::") {
118                    let type_path = public_ty_path(self_ty);
119                    let module = type_path.rsplit_once("::").map(|(m, _)| m).unwrap_or("");
120                    if module.is_empty() {
121                        def_path
122                    } else {
123                        format!("{defining_crate}::{module}::{name}")
124                    }
125                } else {
126                    def_path
127                }
128            } else {
129                get_cleaned_def_path_name(tcx, def_id)
130            }
131        }
132        _ => get_cleaned_def_path_name(tcx, def_id),
133    }
134}
135
136/// Return the resolved `self` type for a method whose `DefId` points to an
137/// associated item that lives inside an `impl` block returning an ADT, or for
138/// a struct/enum DefId directly (needed for parsing struct-invariant annotations).
139pub fn get_struct_self_ty<'tcx>(tcx: TyCtxt<'tcx>, def_id: DefId) -> Option<Ty<'tcx>> {
140    if let Some(assoc_item) = tcx.opt_associated_item(def_id) {
141        let impl_id = assoc_item.impl_container(tcx)?;
142        let self_ty = tcx.type_of(impl_id).skip_binder();
143        if matches!(self_ty.kind(), TyKind::Adt(_, _)) {
144            return Some(self_ty);
145        }
146        return None;
147    }
148    if matches!(
149        tcx.def_kind(def_id),
150        rustc_hir::def::DefKind::Struct | rustc_hir::def::DefKind::Enum
151    ) {
152        let self_ty = tcx.type_of(def_id).skip_binder();
153        if matches!(self_ty.kind(), TyKind::Adt(_, _)) {
154            return Some(self_ty);
155        }
156    }
157    None
158}
159
160/// Return the JSON value loaded from the pre-computed standard-library
161/// signature map (`data/std_sig.json`).
162fn get_std_api_signature_json() -> &'static Value {
163    static JSON: OnceLock<Value> = OnceLock::new();
164    JSON.get_or_init(|| {
165        serde_json::from_str(include_str!("data/std_sig.json")).expect("Unable to parse JSON")
166    })
167}
168
169/// Look up known argument names for standard-library APIs.
170///
171/// The lookup key is the public `DefId` path (see [`public_def_path`]).  When
172/// no names are recorded the list is filled with numeric defaults (`"0"`,
173/// `"1"`, …).
174fn get_known_std_names<'tcx>(tcx: TyCtxt<'tcx>, def_id: DefId) -> Option<Vec<String>> {
175    let std_func_name = public_def_path(tcx, def_id);
176    let json_data = get_std_api_signature_json();
177
178    if let Some(arg_info) = json_data.get(&std_func_name) {
179        if let Some(args_name) = arg_info.as_array() {
180            if args_name.is_empty() {
181                return Some(vec!["0".to_string()]);
182            }
183            let mut result = Vec::new();
184            for arg in args_name {
185                if let Some(sp_name) = arg.as_str() {
186                    result.push(sp_name.to_string());
187                }
188            }
189            return Some(result);
190        }
191    }
192    None
193}
194
195/// Parse argument names and types from a local function's HIR body.
196/// Recursively unwrap Ref/Paren patterns to find the inner binding identifier.
197/// Needed because `&self` / `&mut self` produce PatKind::Ref(PatKind::Binding(...)).
198fn extract_pat_ident(pat: &rustc_hir::Pat<'_>) -> Option<rustc_span::symbol::Ident> {
199    match &pat.kind {
200        rustc_hir::PatKind::Binding(_, _, ident, _) => Some(*ident),
201        rustc_hir::PatKind::Ref(inner, ..) => extract_pat_ident(inner),
202        _ => None,
203    }
204}
205
206fn parse_local_signature<'tcx>(tcx: TyCtxt<'tcx>, def_id: DefId) -> (Vec<String>, Vec<Ty<'tcx>>) {
207    let Some(local_def_id) = def_id.as_local() else {
208        return (vec!["0".to_string()], Vec::new());
209    };
210    let hir_body = tcx.hir_body_owned_by(local_def_id);
211    if hir_body.params.is_empty() {
212        return (vec!["0".to_string()], Vec::new());
213    }
214
215    let params = hir_body.params;
216    let typeck_results = tcx.typeck_body(hir_body.id());
217    let mut param_names = Vec::new();
218    let mut param_tys = Vec::new();
219    for param in params {
220        let ident = extract_pat_ident(param.pat);
221        match ident {
222            Some(name) => {
223                param_names.push(name.name.to_string());
224            }
225            None => {
226                param_names.push(String::new());
227            }
228        }
229        param_tys.push(typeck_results.pat_ty(param.pat));
230    }
231    (param_names, param_tys)
232}
233
234/// Parse argument names and types from an external function's type signature.
235///
236/// First tries the pre-defined standard-library names; falls back to
237/// numeric indices (`"0"`, `"1"`, …).
238fn parse_outside_signature<'tcx>(tcx: TyCtxt<'tcx>, def_id: DefId) -> (Vec<String>, Vec<Ty<'tcx>>) {
239    let sig = tcx.fn_sig(def_id).skip_binder();
240    let param_tys: Vec<Ty<'tcx>> = sig.inputs().skip_binder().iter().copied().collect();
241
242    if let Some(args_name) = get_known_std_names(tcx, def_id) {
243        return (args_name, param_tys);
244    }
245
246    let args_name = (0..param_tys.len()).map(|i| format!("{}", i)).collect();
247    (args_name, param_tys)
248}
249
250/// Extract parameter names from a HIR trait method declaration and pair them
251/// with types from the function signature.  Handles `TraitFn::Required` (names
252/// directly in the declaration) and `TraitFn::Provided` (names in the body).
253fn parse_trait_fn_sig<'tcx>(
254    tcx: TyCtxt<'tcx>,
255    def_id: DefId,
256) -> Option<(Vec<String>, Vec<Ty<'tcx>>)> {
257    let local_def_id = def_id.as_local()?;
258    if !matches!(tcx.def_kind(def_id), rustc_hir::def::DefKind::AssocFn) {
259        return None;
260    }
261    let trait_item_id = rustc_hir::TraitItemId {
262        owner_id: rustc_hir::OwnerId {
263            def_id: local_def_id,
264        },
265    };
266    let item = tcx.hir_trait_item(trait_item_id);
267    let (_sig, trait_fn) = match &item.kind {
268        rustc_hir::TraitItemKind::Fn(sig, tf) => (sig, tf),
269        _ => return None,
270    };
271    let names: Vec<String> = match trait_fn {
272        rustc_hir::TraitFn::Required(param_names) => param_names
273            .iter()
274            .filter_map(|opt| opt.map(|ident| ident.name.to_string()))
275            .collect(),
276        rustc_hir::TraitFn::Provided(body_id) => {
277            let body = tcx.hir_body(*body_id);
278            body.params
279                .iter()
280                .filter_map(|param| extract_pat_ident(param.pat).map(|i| i.name.to_string()))
281                .collect()
282        }
283    };
284    let sig = tcx.fn_sig(def_id).skip_binder();
285    let param_tys: Vec<Ty<'tcx>> = sig.inputs().skip_binder().iter().copied().collect();
286    if names.len() == param_tys.len() {
287        Some((names, param_tys))
288    } else {
289        None
290    }
291}
292
293/// Dispatch argument-name/type parsing to either the local HIR path or the
294/// external type-based path.
295pub fn parse_signature<'tcx>(tcx: TyCtxt<'tcx>, def_id: DefId) -> (Vec<String>, Vec<Ty<'tcx>>) {
296    if def_id.as_local().is_some() && tcx.is_mir_available(def_id) {
297        parse_local_signature(tcx, def_id)
298    } else if def_id.is_local() {
299        if let Some((names, tys)) = parse_trait_fn_sig(tcx, def_id) {
300            return (names, tys);
301        }
302        if matches!(
303            tcx.def_kind(def_id),
304            rustc_hir::def::DefKind::Fn | rustc_hir::def::DefKind::AssocFn
305        ) {
306            parse_outside_signature(tcx, def_id)
307        } else {
308            (vec!["0".to_string()], Vec::new())
309        }
310    } else {
311        parse_outside_signature(tcx, def_id)
312    }
313}
314
315/// Walk a `syn::Expr` and produce the root identifier together with any
316/// field projections.
317///
318/// Examples:
319/// - `ptr`         → `("ptr", [])`
320/// - `region.size` → `("region", ["size"])`
321/// - `tuple.0.val` → `("tuple", ["0", "val"])`
322pub fn access_ident_recursive(expr: &Expr) -> Option<(String, Vec<String>)> {
323    match expr {
324        Expr::Path(syn::ExprPath { path, .. }) => {
325            if path.segments.len() == 1 {
326                rap_debug!("expr2 {:?}", expr);
327                let ident = path.segments[0].ident.to_string();
328                Some((ident, Vec::new()))
329            } else {
330                None
331            }
332        }
333        Expr::Field(syn::ExprField { base, member, .. }) => {
334            let (base_ident, mut fields) =
335                if let Some((base_ident, fields)) = access_ident_recursive(base) {
336                    (base_ident, fields)
337                } else {
338                    return None;
339                };
340            let field_name = match member {
341                syn::Member::Named(ident) => ident.to_string(),
342                syn::Member::Unnamed(index) => index.index.to_string(),
343            };
344            fields.push(field_name);
345            Some((base_ident, fields))
346        }
347        _ => None,
348    }
349}
350
351/// Match a type-identifier string to a concrete `Ty`.
352///
353/// Checks in order:
354/// 1. Primitive types (`u32`, `bool`, …)
355/// 2. Generic type parameters in the function signature or `self` type
356pub fn match_ty_with_ident<'tcx>(
357    tcx: TyCtxt<'tcx>,
358    def_id: DefId,
359    type_ident: String,
360) -> Option<Ty<'tcx>> {
361    if let Some(primitive_ty) = match_primitive_type(tcx, &type_ident) {
362        return Some(primitive_ty);
363    }
364    if let Some(std_ty) = match_std_type(tcx, &type_ident) {
365        return Some(std_ty);
366    }
367    if let Some(param_ty) = find_declared_generic_param(tcx, def_id, &type_ident) {
368        return Some(param_ty);
369    }
370    find_generic_param(tcx, def_id, &type_ident)
371}
372
373/// Match a short type name against std types resolved by name-scan (no
374/// lang/diagnostic item), e.g. `std::ascii::Char` (`AsciiChar`) whose `Char`
375/// short name otherwise collides with the primitive `char`.
376fn match_std_type<'tcx>(tcx: TyCtxt<'tcx>, type_ident: &str) -> Option<Ty<'tcx>> {
377    match type_ident {
378        "Char" | "AsciiChar" => {
379            let did = *crate::def_id::ascii_char_types().first()?;
380            let adt = tcx.adt_def(did);
381            Some(Ty::new_adt(
382                tcx,
383                adt,
384                rustc_middle::ty::GenericArgs::empty(),
385            ))
386        }
387        _ => None,
388    }
389}
390
391fn find_declared_generic_param<'tcx>(
392    tcx: TyCtxt<'tcx>,
393    def_id: DefId,
394    type_ident: &str,
395) -> Option<Ty<'tcx>> {
396    tcx.generics_of(def_id)
397        .own_params
398        .iter()
399        .find(|param| param.name.as_str() == type_ident)
400        .map(|param| {
401            tcx.mk_ty_from_kind(TyKind::Param(rustc_middle::ty::ParamTy {
402                index: param.index,
403                name: param.name,
404            }))
405        })
406}
407
408/// Match a string against Rust's primitive types, returning the
409/// corresponding `Ty` from the type context.
410fn match_primitive_type<'tcx>(tcx: TyCtxt<'tcx>, type_ident: &str) -> Option<Ty<'tcx>> {
411    match type_ident {
412        "i8" => Some(tcx.types.i8),
413        "i16" => Some(tcx.types.i16),
414        "i32" => Some(tcx.types.i32),
415        "i64" => Some(tcx.types.i64),
416        "i128" => Some(tcx.types.i128),
417        "isize" => Some(tcx.types.isize),
418        "u8" => Some(tcx.types.u8),
419        "u16" => Some(tcx.types.u16),
420        "u32" => Some(tcx.types.u32),
421        "u64" => Some(tcx.types.u64),
422        "u128" => Some(tcx.types.u128),
423        "usize" => Some(tcx.types.usize),
424        "f16" => Some(tcx.types.f16),
425        "f32" => Some(tcx.types.f32),
426        "f64" => Some(tcx.types.f64),
427        "f128" => Some(tcx.types.f128),
428        "bool" => Some(tcx.types.bool),
429        "char" => Some(tcx.types.char),
430        "str" => Some(tcx.types.str_),
431        _ => None,
432    }
433}
434
435/// Search function parameters (and the `self` type for methods) for a
436/// generic type whose name matches `type_ident`.
437fn find_generic_param<'tcx>(
438    tcx: TyCtxt<'tcx>,
439    def_id: DefId,
440    type_ident: &str,
441) -> Option<Ty<'tcx>> {
442    rap_debug!(
443        "Searching for generic param: {} in {:?}",
444        type_ident,
445        def_id
446    );
447    let (_, param_tys) = parse_signature(tcx, def_id);
448    rap_debug!("Function parameter types: {:?} of {:?}", param_tys, def_id);
449    for &ty in &param_tys {
450        if let Some(found) = find_generic_in_ty(tcx, ty, type_ident) {
451            return Some(found);
452        }
453    }
454
455    if let Some(struct_ty) = get_struct_self_ty(tcx, def_id) {
456        if let Some(found) = find_generic_in_ty(tcx, struct_ty, type_ident) {
457            return Some(found);
458        }
459    }
460
461    // Search the return type as well, so generic types that only appear there
462    // (e.g. `NonZero<T>` / `Option<NonZero<T>>` in a generic fn) resolve to the
463    // concrete ADT instead of falling back to `never`.
464    let ret_ty = tcx.fn_sig(def_id).skip_binder().output().skip_binder();
465    if let Some(found) = find_generic_in_ty(tcx, ret_ty, type_ident) {
466        return Some(found);
467    }
468
469    None
470}
471
472/// Recursively walk a `Ty` tree looking for a type whose name matches
473/// `type_ident`.
474///
475/// This handles parameter types, pointers, references, slices, arrays,
476/// tuples, and ADT fields.
477fn find_generic_in_ty<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, type_ident: &str) -> Option<Ty<'tcx>> {
478    match ty.kind() {
479        TyKind::Param(param_ty) => {
480            if param_ty.name.as_str() == type_ident {
481                return Some(ty);
482            }
483        }
484        TyKind::RawPtr(ty, _)
485        | TyKind::Ref(_, ty, _)
486        | TyKind::Slice(ty)
487        | TyKind::Array(ty, _) => {
488            if let Some(found) = find_generic_in_ty(tcx, *ty, type_ident) {
489                return Some(found);
490            }
491        }
492        TyKind::Tuple(tys) => {
493            for tuple_ty in tys.iter() {
494                if let Some(found) = find_generic_in_ty(tcx, tuple_ty, type_ident) {
495                    return Some(found);
496                }
497            }
498        }
499        TyKind::Adt(adt_def, substs) => {
500            let name = tcx.item_name(adt_def.did()).to_string();
501            if name == type_ident {
502                return Some(ty);
503            }
504            for field in adt_def.all_fields() {
505                let field_ty = crate::helpers::mir_utils::field_ty(tcx, field, substs);
506                if let Some(found) = find_generic_in_ty(tcx, field_ty, type_ident) {
507                    return Some(found);
508                }
509            }
510            for subst in substs.iter() {
511                if let GenericArgKind::Type(subst_ty) = subst.kind() {
512                    if let Some(found) = find_generic_in_ty(tcx, subst_ty, type_ident) {
513                        return Some(found);
514                    }
515                }
516            }
517        }
518        _ => {}
519    }
520    None
521}
522
523pub fn short_fn_name(tcx: TyCtxt<'_>, def_id: DefId) -> String {
524    let path = tcx.def_path_str(def_id);
525    path.rsplit("::").next().unwrap_or(&path).to_string()
526}
527
528pub fn resolve_field_name(tcx: TyCtxt<'_>, index: &usize, struct_def_id: Option<DefId>) -> String {
529    if let Some(struct_def_id) = struct_def_id
530        && let TyKind::Adt(adt_def, _) = tcx.type_of(struct_def_id).skip_binder().kind()
531    {
532        let variant = adt_def.non_enum_variant();
533        let field_idx = rustc_abi::FieldIdx::from_usize(*index);
534        if field_idx.as_usize() < variant.fields.len() {
535            return variant.fields[field_idx].name.to_string();
536        }
537    }
538    index.to_string()
539}