Skip to main content

rapx/helpers/
name.rs

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