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
16pub 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
41pub 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 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
75pub 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
84pub 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
111pub 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}