alloy_sol_type_parser/
type_spec.rs

1use crate::{
2    Error, Input, Result, TypeStem, new_input,
3    utils::{spanned, str_parser},
4};
5use alloc::vec::Vec;
6use core::num::NonZeroUsize;
7use winnow::{
8    ModalResult, Parser,
9    ascii::digit0,
10    combinator::{cut_err, delimited, repeat, trace},
11    error::{ErrMode, FromExternalError},
12};
13
14/// Represents a type-name. Consists of an identifier and optional array sizes.
15///
16/// A type specifier has a stem, which is [`TypeStem`] representing either a
17/// [`RootType`] or a [`TupleSpecifier`], and a list of array sizes. The array
18/// sizes are in innermost-to-outermost order. An empty array size vec indicates
19/// that the specified type is not an array
20///
21/// Type specifier examples:
22/// - `uint256`
23/// - `uint256[2]`
24/// - `uint256[2][]`
25/// - `(uint256,uint256)`
26/// - `(uint256,uint256)[2]`
27/// - `MyStruct`
28/// - `MyStruct[2]`
29///
30/// <https://docs.soliditylang.org/en/latest/grammar.html#a4.SolidityParser.typeName>
31///
32/// [`RootType`]: crate::RootType
33/// [`TupleSpecifier`]: crate::TupleSpecifier
34///
35/// ## Compatibility with JSON ABI
36///
37/// This type supports the `internalType` semantics for JSON-ABI compatibility.
38///
39/// Examples of valid JSON ABI internal types:
40/// - `contract MyContract`
41/// - `struct MyStruct`
42/// - `enum MyEnum`
43/// - `struct MyContract.MyStruct\[333\]`
44/// - `enum MyContract.MyEnum[][][][][][]`
45/// - `MyValueType`
46///
47/// # Examples
48///
49/// ```
50/// # use alloy_sol_type_parser::TypeSpecifier;
51/// # use core::num::NonZeroUsize;
52/// let spec = TypeSpecifier::parse("uint256[2][]")?;
53/// assert_eq!(spec.span(), "uint256[2][]");
54/// assert_eq!(spec.stem.span(), "uint256");
55/// // The sizes are in innermost-to-outermost order.
56/// assert_eq!(spec.sizes.as_slice(), &[NonZeroUsize::new(2), None]);
57/// # Ok::<_, alloy_sol_type_parser::Error>(())
58/// ```
59#[derive(Clone, Debug, PartialEq, Eq)]
60pub struct TypeSpecifier<'a> {
61    /// The full span of the specifier.
62    pub span: &'a str,
63    /// The type stem, which is either a root type or a tuple type.
64    pub stem: TypeStem<'a>,
65    /// Array sizes, in innermost-to-outermost order. If the size is `None`,
66    /// then the array is dynamic. If the size is `Some`, then the array is
67    /// fixed-size. If the vec is empty, then the type is not an array.
68    pub sizes: Vec<Option<NonZeroUsize>>,
69}
70
71impl<'a> TryFrom<&'a str> for TypeSpecifier<'a> {
72    type Error = Error;
73
74    #[inline]
75    fn try_from(s: &'a str) -> Result<Self> {
76        Self::parse(s)
77    }
78}
79
80impl AsRef<str> for TypeSpecifier<'_> {
81    #[inline]
82    fn as_ref(&self) -> &str {
83        self.span()
84    }
85}
86
87impl<'a> TypeSpecifier<'a> {
88    /// Parse a type specifier from a string.
89    #[inline]
90    pub fn parse(s: &'a str) -> Result<Self> {
91        Self::parser.parse(new_input(s)).map_err(Error::parser)
92    }
93
94    /// [`winnow`] parser for this type.
95    pub(crate) fn parser(input: &mut Input<'a>) -> ModalResult<Self> {
96        trace(
97            "TypeSpecifier",
98            spanned(|input: &mut Input<'a>| {
99                let stem = TypeStem::parser(input)?;
100                let sizes = if input.starts_with('[') {
101                    repeat(
102                        1..,
103                        delimited(str_parser("["), array_size_parser, cut_err(str_parser("]"))),
104                    )
105                    .parse_next(input)?
106                } else {
107                    Vec::new()
108                };
109                Ok((stem, sizes))
110            }),
111        )
112        .parse_next(input)
113        .map(|(span, (stem, sizes))| Self { span, stem, sizes })
114    }
115
116    /// Parse a type specifier from a string with support for `:` in identifiers.
117    ///
118    /// Extends the standard type parsing.
119    #[cfg(feature = "eip712")]
120    #[inline]
121    pub fn parse_eip712(s: &'a str) -> Result<Self> {
122        Self::eip712_parser.parse(new_input(s)).map_err(Error::parser)
123    }
124
125    /// [`winnow`] parser for EIP-712 types with extended support for `:` in identifiers.
126    #[cfg(feature = "eip712")]
127    pub(crate) fn eip712_parser(input: &mut Input<'a>) -> ModalResult<Self> {
128        trace(
129            "TypeSpecifier::eip712",
130            spanned(|input: &mut Input<'a>| {
131                let stem = TypeStem::eip712_parser(input)?;
132                let sizes = if input.starts_with('[') {
133                    repeat(
134                        1..,
135                        delimited(str_parser("["), array_size_parser, cut_err(str_parser("]"))),
136                    )
137                    .parse_next(input)?
138                } else {
139                    Vec::new()
140                };
141                Ok((stem, sizes))
142            }),
143        )
144        .parse_next(input)
145        .map(|(span, (stem, sizes))| Self { span, stem, sizes })
146    }
147
148    /// Returns the type stem as a string.
149    #[inline]
150    pub const fn span(&self) -> &'a str {
151        self.span
152    }
153
154    /// Returns the type stem.
155    #[inline]
156    pub const fn stem(&self) -> &TypeStem<'_> {
157        &self.stem
158    }
159
160    /// Returns true if the type is a basic Solidity type.
161    #[inline]
162    pub fn try_basic_solidity(&self) -> Result<()> {
163        self.stem.try_basic_solidity()
164    }
165
166    /// Returns true if this type is an array.
167    #[inline]
168    pub fn is_array(&self) -> bool {
169        !self.sizes.is_empty()
170    }
171}
172
173fn array_size_parser(input: &mut Input<'_>) -> ModalResult<Option<NonZeroUsize>> {
174    let digits = digit0(input)?;
175    if digits.is_empty() {
176        return Ok(None);
177    }
178    digits.parse().map(Some).map_err(|e| ErrMode::from_external_error(input, e))
179}
180
181#[cfg(test)]
182mod test {
183    use super::*;
184    use crate::TupleSpecifier;
185    use alloc::string::ToString;
186
187    #[track_caller]
188    fn assert_error_contains(e: &Error, s: &str) {
189        if cfg!(feature = "std") {
190            let es = e.to_string();
191            assert!(es.contains(s), "{s:?} not in {es:?}");
192        }
193    }
194
195    #[test]
196    fn parse_test() {
197        assert_eq!(
198            TypeSpecifier::parse("uint"),
199            Ok(TypeSpecifier {
200                span: "uint",
201                stem: TypeStem::parse("uint256").unwrap(),
202                sizes: vec![],
203            })
204        );
205
206        assert_eq!(
207            TypeSpecifier::parse("uint256"),
208            Ok(TypeSpecifier {
209                span: "uint256",
210                stem: TypeStem::parse("uint256").unwrap(),
211                sizes: vec![],
212            })
213        );
214
215        assert_eq!(
216            TypeSpecifier::parse("uint256[2]"),
217            Ok(TypeSpecifier {
218                span: "uint256[2]",
219                stem: TypeStem::parse("uint256").unwrap(),
220                sizes: vec![NonZeroUsize::new(2)],
221            })
222        );
223
224        assert_eq!(
225            TypeSpecifier::parse("uint256[2][]"),
226            Ok(TypeSpecifier {
227                span: "uint256[2][]",
228                stem: TypeStem::parse("uint256").unwrap(),
229                sizes: vec![NonZeroUsize::new(2), None],
230            })
231        );
232
233        assert_eq!(
234            TypeSpecifier::parse("(uint256,uint256)"),
235            Ok(TypeSpecifier {
236                span: "(uint256,uint256)",
237                stem: TypeStem::Tuple(TupleSpecifier::parse("(uint256,uint256)").unwrap()),
238                sizes: vec![],
239            })
240        );
241
242        assert_eq!(
243            TypeSpecifier::parse("(uint256,uint256)[2]"),
244            Ok(TypeSpecifier {
245                span: "(uint256,uint256)[2]",
246                stem: TypeStem::Tuple(TupleSpecifier::parse("(uint256,uint256)").unwrap()),
247                sizes: vec![NonZeroUsize::new(2)],
248            })
249        );
250
251        assert_eq!(
252            TypeSpecifier::parse("MyStruct"),
253            Ok(TypeSpecifier {
254                span: "MyStruct",
255                stem: TypeStem::parse("MyStruct").unwrap(),
256                sizes: vec![],
257            })
258        );
259
260        assert_eq!(
261            TypeSpecifier::parse("MyStruct[2]"),
262            Ok(TypeSpecifier {
263                span: "MyStruct[2]",
264                stem: TypeStem::parse("MyStruct").unwrap(),
265                sizes: vec![NonZeroUsize::new(2)],
266            })
267        );
268    }
269
270    #[test]
271    fn sizes() {
272        TypeSpecifier::parse("a[").unwrap_err();
273        TypeSpecifier::parse("a[][").unwrap_err();
274
275        assert_eq!(
276            TypeSpecifier::parse("a[]"),
277            Ok(TypeSpecifier {
278                span: "a[]",
279                stem: TypeStem::parse("a").unwrap(),
280                sizes: vec![None],
281            }),
282        );
283
284        assert_eq!(
285            TypeSpecifier::parse("a[1]"),
286            Ok(TypeSpecifier {
287                span: "a[1]",
288                stem: TypeStem::parse("a").unwrap(),
289                sizes: vec![NonZeroUsize::new(1)],
290            }),
291        );
292
293        let e = TypeSpecifier::parse("a[0]").unwrap_err();
294        assert_error_contains(&e, "number would be zero for non-zero type");
295        TypeSpecifier::parse("a[x]").unwrap_err();
296
297        TypeSpecifier::parse("a[ ]").unwrap_err();
298        TypeSpecifier::parse("a[  ]").unwrap_err();
299        TypeSpecifier::parse("a[ 0]").unwrap_err();
300        TypeSpecifier::parse("a[0 ]").unwrap_err();
301
302        TypeSpecifier::parse("a[a]").unwrap_err();
303        TypeSpecifier::parse("a[ a]").unwrap_err();
304        TypeSpecifier::parse("a[a ]").unwrap_err();
305
306        TypeSpecifier::parse("a[ 1]").unwrap_err();
307        TypeSpecifier::parse("a[1 ]").unwrap_err();
308
309        TypeSpecifier::parse(&format!("a[{}]", usize::MAX)).unwrap();
310        let e = TypeSpecifier::parse(&format!("a[{}0]", usize::MAX)).unwrap_err();
311        assert_error_contains(&e, "number too large to fit in target type");
312    }
313
314    #[test]
315    fn try_basic_solidity() {
316        assert_eq!(TypeSpecifier::parse("uint").unwrap().try_basic_solidity(), Ok(()));
317        assert_eq!(TypeSpecifier::parse("int").unwrap().try_basic_solidity(), Ok(()));
318        assert_eq!(TypeSpecifier::parse("uint256").unwrap().try_basic_solidity(), Ok(()));
319        assert_eq!(TypeSpecifier::parse("uint256[]").unwrap().try_basic_solidity(), Ok(()));
320        assert_eq!(TypeSpecifier::parse("(uint256,uint256)").unwrap().try_basic_solidity(), Ok(()));
321        assert_eq!(
322            TypeSpecifier::parse("(uint256,uint256)[2]").unwrap().try_basic_solidity(),
323            Ok(())
324        );
325        assert_eq!(
326            TypeSpecifier::parse("tuple(uint256,uint256)").unwrap().try_basic_solidity(),
327            Ok(())
328        );
329        assert_eq!(
330            TypeSpecifier::parse("tuple(address,bytes,(bool,(string,uint256)[][3]))[2]")
331                .unwrap()
332                .try_basic_solidity(),
333            Ok(())
334        );
335    }
336
337    #[test]
338    fn not_basic_solidity() {
339        assert_eq!(
340            TypeSpecifier::parse("MyStruct").unwrap().try_basic_solidity(),
341            Err(Error::invalid_type_string("MyStruct"))
342        );
343    }
344
345    #[test]
346    fn parse_type_specifier_colon() {
347        // This is an invalid type specifier, as it contains a colon
348        let _spec = TypeSpecifier::parse("Test:Message").unwrap_err();
349    }
350}