sylvia_derive/parser/attributes/
messages.rs

1use convert_case::{Case, Casing};
2use proc_macro_error::emit_warning;
3use syn::fold::Fold;
4use syn::parse::{Parse, ParseStream, Parser};
5use syn::spanned::Spanned;
6use syn::{parenthesized, Error, Ident, MetaList, Path, Result, Token};
7
8use proc_macro_error::emit_error;
9
10use crate::fold::StripGenerics;
11
12/// Type wrapping data parsed from `sv::message` attribute.
13#[derive(Debug)]
14pub struct ContractMessageAttr {
15    pub module: Path,
16    pub variant: Ident,
17    pub customs: Customs,
18}
19
20impl ContractMessageAttr {
21    pub fn new(attr: &MetaList) -> Result<Self> {
22        ContractMessageAttr::parse
23            .parse2(attr.tokens.clone())
24            .map_err(|err| {
25                emit_error!(err.span(), err);
26                err
27            })
28    }
29}
30
31/// The `Customs` struct is used to represent the presence of `: custom(msg, query)` parameter
32/// within a `sv::message` attribute.
33///
34/// It is used to determine if an interface implemented on the contract uses `Empty` in place
35/// of custom types.
36#[derive(Debug)]
37pub struct Customs {
38    pub has_msg: bool,
39    pub has_query: bool,
40}
41
42fn interface_has_custom(content: ParseStream) -> Result<Customs> {
43    let mut customs = Customs {
44        has_msg: false,
45        has_query: false,
46    };
47
48    if !content.peek(Token![:]) {
49        return Ok(customs);
50    }
51
52    let _: Token![:] = content.parse()?;
53    let attr: Ident = content.parse()?;
54    if attr != "custom" {
55        return Ok(customs);
56    }
57
58    let custom_content;
59    parenthesized!(custom_content in content);
60
61    while !custom_content.is_empty() {
62        let custom = custom_content.parse::<Path>()?;
63        match custom.get_ident() {
64            Some(ident) if ident == "msg" => customs.has_msg = true,
65            Some(ident) if ident == "query" => customs.has_query = true,
66            _ => {
67                return Err(Error::new(
68                    custom.span(),
69                    "Invalid custom attribute, expected one or both of: [`msg`, `query`]\n
70  = note: Expected attribute to be in form `#[sv::messages(interface: custom(msg, query))]`.\n",
71                ))
72            }
73        }
74        if !custom_content.peek(Token![,]) {
75            break;
76        }
77        let _: Token![,] = custom_content.parse()?;
78    }
79    Ok(customs)
80}
81
82impl Parse for ContractMessageAttr {
83    fn parse(input: ParseStream) -> Result<Self> {
84        let module = input.parse()?;
85        let module = StripGenerics.fold_path(module);
86
87        let variant = if input.parse::<Token![as]>().is_ok() {
88            let variant: Ident = input.parse()?;
89            if Some(variant.to_string())
90                == module
91                    .segments
92                    .last()
93                    .map(|name| name.ident.to_string().to_case(Case::UpperCamel))
94            {
95                emit_warning!(
96                    variant.span(), "Redundant `as {}`.", variant;
97                    note = "Interface name is a camel case version of the path and can be auto deduced.";
98                    note = "Attribute can be simplified to: `#[sv::messages(interface_path)]`"
99                )
100            }
101            variant
102        } else if let Some(module_name) = &module.segments.last() {
103            let interface_name = module_name.ident.to_string().to_case(Case::UpperCamel);
104            syn::Ident::new(&interface_name, module.span())
105        } else {
106            Ident::new("", module.span())
107        };
108        let customs = interface_has_custom(input)?;
109        if !input.is_empty() {
110            return Err(Error::new(input.span(),
111                "Unexpected tokens inside `sv::messages` attribtue.\n
112  = note: Maximal supported form of attribute: `#[sv::messages(interface::path as InterfaceName: custom(msg, query))]`.\n"
113            ));
114        }
115        Ok(Self {
116            module,
117            variant,
118            customs,
119        })
120    }
121}