1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
//! State variable ([`VariableDefinition`]) expansion.

use super::ExpCtxt;
use ast::{ItemFunction, ParameterList, Spanned, Type, VariableDeclaration, VariableDefinition};
use proc_macro2::TokenStream;
use syn::{Error, Result};

/// Expands a [`VariableDefinition`].
///
/// See [`ItemFunction::from_variable_definition`].
pub(super) fn expand(cx: &ExpCtxt<'_>, var_def: &VariableDefinition) -> Result<TokenStream> {
    let Some(function) = var_as_function(cx, var_def)? else {
        return Ok(TokenStream::new());
    };
    super::function::expand(cx, &function)
}

pub(super) fn var_as_function(
    cx: &ExpCtxt<'_>,
    var_def: &VariableDefinition,
) -> Result<Option<ItemFunction>> {
    // only expand public or external state variables
    if !var_def.attributes.visibility().map_or(false, |v| v.is_public() || v.is_external()) {
        return Ok(None);
    }

    let mut function = ItemFunction::from_variable_definition(var_def.clone());
    expand_returns(cx, &mut function)?;
    Ok(Some(function))
}

/// Expands return-position custom types.
fn expand_returns(cx: &ExpCtxt<'_>, f: &mut ItemFunction) -> Result<()> {
    let returns = f.returns.as_mut().expect("generated getter function with no returns");
    let ret = returns.returns.first_mut().unwrap();
    if !ret.ty.has_custom_simple() {
        return Ok(());
    }

    let mut ty = &ret.ty;

    // resolve if custom
    if let Type::Custom(name) = ty {
        ty = cx.custom_type(name);
    }

    // skip if not tuple with complex types
    let Type::Tuple(tup) = ty else { return Ok(()) };
    if !tup.types.iter().any(type_is_complex) {
        return Ok(());
    }

    // retain only non-complex types
    // TODO: assign return types' names from the original struct
    let mut new_returns = ParameterList::new();
    for p in tup.types.pairs() {
        let (ty, comma) = p.into_tuple();
        if !type_is_complex(ty) {
            new_returns.push_value(VariableDeclaration::new(ty.clone()));
            if let Some(comma) = comma {
                new_returns.push_punct(*comma);
            }
        }
    }

    // all types were complex, Solidity doesn't accept this
    if new_returns.is_empty() {
        return Err(Error::new(f.name().span(), "invalid state variable type"));
    }

    returns.returns = new_returns;
    Ok(())
}

/// Returns `true` if a type is complex for the purposes of state variable
/// getters.
///
/// Technically tuples are also complex if they contain complex types, but only
/// at the first "depth" level.
///
/// Here, `mapA` is fine but `mapB` throws an error; you can test that pushing
/// and reading to `mapA` works fine (last checked at Solc version `0.8.21`):
///
/// ```solidity
/// contract Complex {
///     struct A {
///         B b;
///     }
///     struct B {
///         uint[] arr;
///     }
///
///     mapping(uint => A) public mapA;
///
///     function pushValueA(uint idx, uint val) public {
///         mapA[idx].b.arr.push(val);
///     }
///
///     mapping(uint => B) public mapB;
///
///     function pushValueB(uint idx, uint val) public {
///         mapB[idx].arr.push(val);
///     }
/// }
/// ```
fn type_is_complex(ty: &Type) -> bool {
    matches!(ty, Type::Mapping(_) | Type::Array(_))
}