rapx/helpers/
name.rs

1use rustc_hir::def_id::DefId;
2use rustc_middle::ty::{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).
54pub fn 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.
76pub fn get_struct_self_ty<'tcx>(tcx: TyCtxt<'tcx>, def_id: DefId) -> Option<Ty<'tcx>> {
77    let assoc_item = tcx.opt_associated_item(def_id)?;
78    let impl_id = assoc_item.impl_container(tcx)?;
79    let self_ty = tcx.type_of(impl_id).skip_binder();
80    match self_ty.kind() {
81        TyKind::Adt(_, _) => Some(self_ty),
82        _ => None,
83    }
84}
85
86/// Return the JSON value loaded from the pre-computed standard-library
87/// signature map (`data/std_sig.json`).
88pub fn get_std_api_signature_json() -> Value {
89    serde_json::from_str(include_str!("data/std_sig.json")).expect("Unable to parse JSON")
90}
91
92/// Look up known argument names for standard-library APIs.
93///
94/// The lookup key is the cleaned `DefId` path (see
95/// `get_cleaned_def_path_name`).  When no names are recorded the list is
96/// filled with numeric defaults (`"0"`, `"1"`, …).
97pub fn get_known_std_names<'tcx>(tcx: TyCtxt<'tcx>, def_id: DefId) -> Option<Vec<String>> {
98    let std_func_name = get_cleaned_def_path_name(tcx, def_id);
99    let json_data = get_std_api_signature_json();
100
101    if let Some(arg_info) = json_data.get(&std_func_name) {
102        if let Some(args_name) = arg_info.as_array() {
103            if args_name.is_empty() {
104                return Some(vec!["0".to_string()]);
105            }
106            let mut result = Vec::new();
107            for arg in args_name {
108                if let Some(sp_name) = arg.as_str() {
109                    result.push(sp_name.to_string());
110                }
111            }
112            return Some(result);
113        }
114    }
115    None
116}
117
118/// Parse argument names and types from a local function's HIR body.
119/// Recursively unwrap Ref/Paren patterns to find the inner binding identifier.
120/// Needed because `&self` / `&mut self` produce PatKind::Ref(PatKind::Binding(...)).
121fn extract_pat_ident(pat: &rustc_hir::Pat<'_>) -> Option<rustc_span::symbol::Ident> {
122    match &pat.kind {
123        rustc_hir::PatKind::Binding(_, _, ident, _) => Some(*ident),
124        rustc_hir::PatKind::Ref(inner, ..) => extract_pat_ident(inner),
125        _ => None,
126    }
127}
128
129pub fn parse_local_signature<'tcx>(
130    tcx: TyCtxt<'tcx>,
131    def_id: DefId,
132) -> (Vec<String>, Vec<Ty<'tcx>>) {
133    let local_def_id = def_id.as_local().unwrap();
134    let hir_body = tcx.hir_body_owned_by(local_def_id);
135    if hir_body.params.is_empty() {
136        return (vec!["0".to_string()], Vec::new());
137    }
138
139    let params = hir_body.params;
140    let typeck_results = tcx.typeck_body(hir_body.id());
141    let mut param_names = Vec::new();
142    let mut param_tys = Vec::new();
143    for param in params {
144        let ident = extract_pat_ident(&param.pat);
145        match ident {
146            Some(name) => {
147                param_names.push(name.name.to_string());
148            }
149            None => {
150                param_names.push(String::new());
151            }
152        }
153        param_tys.push(typeck_results.pat_ty(param.pat));
154    }
155    (param_names, param_tys)
156}
157
158/// Parse argument names and types from an external function's type signature.
159///
160/// First tries the pre-defined standard-library names; falls back to
161/// numeric indices (`"0"`, `"1"`, …).
162pub fn parse_outside_signature<'tcx>(
163    tcx: TyCtxt<'tcx>,
164    def_id: DefId,
165) -> (Vec<String>, Vec<Ty<'tcx>>) {
166    let sig = tcx.fn_sig(def_id).skip_binder();
167    let param_tys: Vec<Ty<'tcx>> = sig.inputs().skip_binder().iter().copied().collect();
168
169    if let Some(args_name) = get_known_std_names(tcx, def_id) {
170        return (args_name, param_tys);
171    }
172
173    let args_name = (0..param_tys.len()).map(|i| format!("{}", i)).collect();
174    (args_name, param_tys)
175}
176
177/// Dispatch argument-name/type parsing to either the local HIR path or the
178/// external type-based path.
179pub fn parse_signature<'tcx>(tcx: TyCtxt<'tcx>, def_id: DefId) -> (Vec<String>, Vec<Ty<'tcx>>) {
180    if def_id.as_local().is_some() && tcx.is_mir_available(def_id) {
181        parse_local_signature(tcx, def_id)
182    } else if def_id.is_local() {
183        (vec!["0".to_string()], Vec::new())
184    } else {
185        parse_outside_signature(tcx, def_id)
186    }
187}
188
189/// Walk a `syn::Expr` and produce the root identifier together with any
190/// field projections.
191///
192/// Examples:
193/// - `ptr`         → `("ptr", [])`
194/// - `region.size` → `("region", ["size"])`
195/// - `tuple.0.val` → `("tuple", ["0", "val"])`
196pub fn access_ident_recursive(expr: &Expr) -> Option<(String, Vec<String>)> {
197    match expr {
198        Expr::Path(syn::ExprPath { path, .. }) => {
199            if path.segments.len() == 1 {
200                rap_debug!("expr2 {:?}", expr);
201                let ident = path.segments[0].ident.to_string();
202                Some((ident, Vec::new()))
203            } else {
204                None
205            }
206        }
207        Expr::Field(syn::ExprField { base, member, .. }) => {
208            let (base_ident, mut fields) =
209                if let Some((base_ident, fields)) = access_ident_recursive(base) {
210                    (base_ident, fields)
211                } else {
212                    return None;
213                };
214            let field_name = match member {
215                syn::Member::Named(ident) => ident.to_string(),
216                syn::Member::Unnamed(index) => index.index.to_string(),
217            };
218            fields.push(field_name);
219            Some((base_ident, fields))
220        }
221        _ => None,
222    }
223}
224
225/// Match a type-identifier string to a concrete `Ty`.
226///
227/// Checks in order:
228/// 1. Primitive types (`u32`, `bool`, …)
229/// 2. Generic type parameters in the function signature or `self` type
230pub fn match_ty_with_ident<'tcx>(
231    tcx: TyCtxt<'tcx>,
232    def_id: DefId,
233    type_ident: String,
234) -> Option<Ty<'tcx>> {
235    if let Some(primitive_ty) = match_primitive_type(tcx, type_ident.clone()) {
236        return Some(primitive_ty);
237    }
238    if let Some(param_ty) = find_declared_generic_param(tcx, def_id, &type_ident) {
239        return Some(param_ty);
240    }
241    find_generic_param(tcx, def_id, type_ident)
242}
243
244fn find_declared_generic_param<'tcx>(
245    tcx: TyCtxt<'tcx>,
246    def_id: DefId,
247    type_ident: &str,
248) -> Option<Ty<'tcx>> {
249    tcx.generics_of(def_id)
250        .own_params
251        .iter()
252        .find(|param| param.name.as_str() == type_ident)
253        .map(|param| {
254            tcx.mk_ty_from_kind(TyKind::Param(rustc_middle::ty::ParamTy {
255                index: param.index,
256                name: param.name,
257            }))
258        })
259}
260
261/// Match a string against Rust's primitive types, returning the
262/// corresponding `Ty` from the type context.
263pub fn match_primitive_type<'tcx>(tcx: TyCtxt<'tcx>, type_ident: String) -> Option<Ty<'tcx>> {
264    match type_ident.as_str() {
265        "i8" => Some(tcx.types.i8),
266        "i16" => Some(tcx.types.i16),
267        "i32" => Some(tcx.types.i32),
268        "i64" => Some(tcx.types.i64),
269        "i128" => Some(tcx.types.i128),
270        "isize" => Some(tcx.types.isize),
271        "u8" => Some(tcx.types.u8),
272        "u16" => Some(tcx.types.u16),
273        "u32" => Some(tcx.types.u32),
274        "u64" => Some(tcx.types.u64),
275        "u128" => Some(tcx.types.u128),
276        "usize" => Some(tcx.types.usize),
277        "f16" => Some(tcx.types.f16),
278        "f32" => Some(tcx.types.f32),
279        "f64" => Some(tcx.types.f64),
280        "f128" => Some(tcx.types.f128),
281        "bool" => Some(tcx.types.bool),
282        "char" => Some(tcx.types.char),
283        "str" => Some(tcx.types.str_),
284        _ => None,
285    }
286}
287
288/// Search function parameters (and the `self` type for methods) for a
289/// generic type whose name matches `type_ident`.
290pub fn find_generic_param<'tcx>(
291    tcx: TyCtxt<'tcx>,
292    def_id: DefId,
293    type_ident: String,
294) -> Option<Ty<'tcx>> {
295    rap_debug!(
296        "Searching for generic param: {} in {:?}",
297        type_ident,
298        def_id
299    );
300    let (_, param_tys) = parse_signature(tcx, def_id);
301    rap_debug!("Function parameter types: {:?} of {:?}", param_tys, def_id);
302    for &ty in &param_tys {
303        if let Some(found) = find_generic_in_ty(tcx, ty, &type_ident) {
304            return Some(found);
305        }
306    }
307
308    if let Some(struct_ty) = get_struct_self_ty(tcx, def_id) {
309        if let Some(found) = find_generic_in_ty(tcx, struct_ty, &type_ident) {
310            return Some(found);
311        }
312    }
313
314    None
315}
316
317/// Recursively walk a `Ty` tree looking for a type whose name matches
318/// `type_ident`.
319///
320/// This handles parameter types, pointers, references, slices, arrays,
321/// tuples, and ADT fields.
322pub fn find_generic_in_ty<'tcx>(
323    tcx: TyCtxt<'tcx>,
324    ty: Ty<'tcx>,
325    type_ident: &str,
326) -> Option<Ty<'tcx>> {
327    match ty.kind() {
328        TyKind::Param(param_ty) => {
329            if param_ty.name.as_str() == type_ident {
330                return Some(ty);
331            }
332        }
333        TyKind::RawPtr(ty, _)
334        | TyKind::Ref(_, ty, _)
335        | TyKind::Slice(ty)
336        | TyKind::Array(ty, _) => {
337            if let Some(found) = find_generic_in_ty(tcx, *ty, type_ident) {
338                return Some(found);
339            }
340        }
341        TyKind::Tuple(tys) => {
342            for tuple_ty in tys.iter() {
343                if let Some(found) = find_generic_in_ty(tcx, tuple_ty, type_ident) {
344                    return Some(found);
345                }
346            }
347        }
348        TyKind::Adt(adt_def, substs) => {
349            let name = tcx.item_name(adt_def.did()).to_string();
350            if name == type_ident {
351                return Some(ty);
352            }
353            for field in adt_def.all_fields() {
354                #[cfg(not(rapx_rustc_ge_198))]
355                let field_ty = field.ty(tcx, substs);
356                #[cfg(rapx_rustc_ge_198)]
357                let field_ty = field.ty(tcx, substs).skip_norm_wip();
358                if let Some(found) = find_generic_in_ty(tcx, field_ty, type_ident) {
359                    return Some(found);
360                }
361            }
362        }
363        _ => {}
364    }
365    None
366}