sylvia_derive/
utils.rs

1use convert_case::Casing;
2use proc_macro2::TokenStream;
3use proc_macro_error::emit_error;
4use quote::{quote, ToTokens};
5use syn::fold::Fold;
6use syn::spanned::Spanned;
7use syn::visit::Visit;
8use syn::{
9    parse_quote, GenericArgument, GenericParam, Ident, Path, PathArguments, ReturnType, Type,
10    WhereClause, WherePredicate,
11};
12
13use crate::fold::StripGenerics;
14use crate::parser::check_generics::{CheckGenerics, GetPath};
15
16/// Filters where predicates leaving only ones found in `used_generics`
17pub fn filter_wheres<'a, Generic: GetPath + PartialEq>(
18    clause: &'a Option<WhereClause>,
19    generics: &[&Generic],
20    used_generics: &[&Generic],
21) -> Vec<&'a WherePredicate> {
22    clause
23        .as_ref()
24        .map(|clause| {
25            clause
26                .predicates
27                .iter()
28                .filter(|pred| {
29                    let mut generics_checker = CheckGenerics::new(generics);
30                    generics_checker.visit_where_predicate(pred);
31                    generics_checker
32                        .used()
33                        .into_iter()
34                        .all(|gen| used_generics.contains(&gen))
35                })
36                .collect()
37        })
38        .unwrap_or_default()
39}
40
41/// Extracts return type from the method return type.
42pub fn extract_return_type(ret_type: &ReturnType) -> &Path {
43    let ReturnType::Type(_, ty) = ret_type else {
44        unreachable!()
45    };
46
47    let Type::Path(type_path) = ty.as_ref() else {
48        unreachable!()
49    };
50    let segments = &type_path.path.segments;
51    assert!(!segments.is_empty());
52    let segment = &segments[0];
53
54    // In case of aliased result user need to define the return type by hand
55    if segment.ident != "Result" && segment.ident != "StdResult" {
56        emit_error!(
57            segment.span(),
58            "Neither Result nor StdResult found in return type. \
59                    You might be using aliased return type. \
60                    Please use #[sv::msg(return_type=<your_return_type>)]"
61        );
62    }
63    let PathArguments::AngleBracketed(args) = &segments[0].arguments else {
64        unreachable!()
65    };
66    let args = &args.args;
67    assert!(!args.is_empty());
68    let GenericArgument::Type(Type::Path(type_path)) = &args[0] else {
69        unreachable!()
70    };
71
72    &type_path.path
73}
74
75/// Creates [`Option<WhereClause>`] based on the provided predicates.
76/// Returns [`None`] if predicates array is empty.
77pub fn as_where_clause(where_predicates: &[&WherePredicate]) -> Option<WhereClause> {
78    match where_predicates.is_empty() {
79        true => None,
80        false => Some(parse_quote! { where #(#where_predicates),* }),
81    }
82}
83
84/// Creates a bracketed generics [`TokenStream`].
85pub fn emit_bracketed_generics<GenericT: ToTokens>(unbonded_generics: &[GenericT]) -> TokenStream {
86    match unbonded_generics.is_empty() {
87        true => quote! {},
88        false => quote! { < #(#unbonded_generics,)* > },
89    }
90}
91
92pub fn get_ident_from_type(contract_name: &Type) -> &Ident {
93    let Type::Path(type_path) = contract_name else {
94        unreachable!()
95    };
96    let segments = &type_path.path.segments;
97    assert!(!segments.is_empty());
98    let segment = &segments.last().unwrap();
99    &segment.ident
100}
101
102pub fn emit_turbofish(ty: &Type, generics: &[&GenericParam]) -> Type {
103    if !generics.is_empty() {
104        let stripped_ty = StripGenerics.fold_type(ty.clone());
105        parse_quote! { #stripped_ty :: < #(#generics),* > }
106    } else {
107        parse_quote! { #ty }
108    }
109}
110
111/// Trait for converting `Ident` to different cases preserving original [proc_macro2::Span].
112pub trait SvCasing {
113    fn to_case(&self, case: convert_case::Case) -> Self;
114}
115
116impl SvCasing for Ident {
117    fn to_case(&self, case: convert_case::Case) -> Ident {
118        let new_name = &self.to_string().to_case(case);
119        Ident::new(new_name, self.span())
120    }
121}