alloy_sol_macro_expander/expand/
var_def.rs

1//! State variable ([`VariableDefinition`]) expansion.
2
3use super::ExpCtxt;
4use ast::{ItemFunction, ParameterList, Spanned, Type, VariableDeclaration, VariableDefinition};
5use proc_macro2::TokenStream;
6use syn::{Error, Result};
7
8/// Expands a [`VariableDefinition`].
9///
10/// See [`ItemFunction::from_variable_definition`].
11pub(super) fn expand(cx: &ExpCtxt<'_>, var_def: &VariableDefinition) -> Result<TokenStream> {
12    let Some(function) = var_as_function(cx, var_def)? else {
13        return Ok(TokenStream::new());
14    };
15    super::function::expand(cx, &function)
16}
17
18pub(super) fn var_as_function(
19    cx: &ExpCtxt<'_>,
20    var_def: &VariableDefinition,
21) -> Result<Option<ItemFunction>> {
22    // Only expand public or external state variables.
23    if !var_def.attributes.visibility().is_some_and(|v| v.is_public() || v.is_external()) {
24        return Ok(None);
25    }
26
27    let mut function = ItemFunction::from_variable_definition(var_def.clone());
28    expand_returns(cx, &mut function)?;
29    Ok(Some(function))
30}
31
32/// Expands return-position custom types.
33fn expand_returns(cx: &ExpCtxt<'_>, f: &mut ItemFunction) -> Result<()> {
34    let returns = f.returns.as_mut().expect("generated getter function with no returns");
35    let ret = returns.returns.first_mut().unwrap();
36    if !ret.ty.has_custom_simple() {
37        return Ok(());
38    }
39
40    let mut ty = &ret.ty;
41
42    // resolve if custom
43    if let Type::Custom(name) = ty {
44        ty = cx.custom_type(name);
45    }
46    let Type::Tuple(tup) = ty else { return Ok(()) };
47
48    // retain only non-complex types
49    // TODO: assign return types' names from the original struct
50    let mut new_returns = ParameterList::new();
51    for p in tup.types.pairs() {
52        let (ty, comma) = p.into_tuple();
53        if !type_is_complex(ty) {
54            new_returns.push_value(VariableDeclaration::new(ty.clone()));
55            if let Some(comma) = comma {
56                new_returns.push_punct(*comma);
57            }
58        }
59    }
60
61    // all types were complex, Solidity doesn't accept this
62    if new_returns.is_empty() {
63        return Err(Error::new(f.name().span(), "invalid state variable type"));
64    }
65
66    returns.returns = new_returns;
67    Ok(())
68}
69
70/// Returns `true` if a type is complex for the purposes of state variable
71/// getters.
72///
73/// Technically tuples are also complex if they contain complex types, but only
74/// at the first "depth" level.
75///
76/// Here, `mapA` is fine but `mapB` throws an error; you can test that pushing
77/// and reading to `mapA` works fine (last checked at Solc version `0.8.21`):
78///
79/// ```solidity
80/// contract Complex {
81///     struct A {
82///         B b;
83///     }
84///     struct B {
85///         uint[] arr;
86///     }
87///
88///     mapping(uint => A) public mapA;
89///
90///     function pushValueA(uint idx, uint val) public {
91///         mapA[idx].b.arr.push(val);
92///     }
93///
94///     mapping(uint => B) public mapB;
95///
96///     function pushValueB(uint idx, uint val) public {
97///         mapB[idx].arr.push(val);
98///     }
99/// }
100/// ```
101///
102/// Ref: <https://github.com/ethereum/solidity/blob/9d7cc42bc1c12bb43e9dccf8c6c36833fdfcbbca/libsolidity/ast/Types.cpp#L2887-L2891>
103fn type_is_complex(ty: &Type) -> bool {
104    matches!(ty, Type::Mapping(_) | Type::Array(_))
105}