ethers_solc/artifacts/ast/
lowfidelity.rs

1//! Bindings for solc's `ast` output field
2
3use crate::artifacts::serde_helpers;
4use serde::{de::DeserializeOwned, Deserialize, Serialize};
5use std::{collections::BTreeMap, fmt, fmt::Write, str::FromStr};
6
7/// Represents the AST field in the solc output
8#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
9pub struct Ast {
10    #[serde(rename = "absolutePath")]
11    pub absolute_path: String,
12    pub id: usize,
13    #[serde(default, rename = "exportedSymbols")]
14    pub exported_symbols: BTreeMap<String, Vec<usize>>,
15    #[serde(rename = "nodeType")]
16    pub node_type: NodeType,
17    #[serde(with = "serde_helpers::display_from_str")]
18    pub src: SourceLocation,
19    #[serde(default)]
20    pub nodes: Vec<Node>,
21
22    /// Node attributes that were not deserialized.
23    #[serde(flatten)]
24    pub other: BTreeMap<String, serde_json::Value>,
25}
26
27#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
28pub struct Node {
29    /// The node ID.
30    #[serde(default, skip_serializing_if = "Option::is_none")]
31    pub id: Option<usize>,
32
33    /// The node type.
34    #[serde(rename = "nodeType")]
35    pub node_type: NodeType,
36
37    /// The location of the node in the source file.
38    #[serde(with = "serde_helpers::display_from_str")]
39    pub src: SourceLocation,
40
41    /// Child nodes for some node types.
42    #[serde(default)]
43    pub nodes: Vec<Node>,
44
45    /// Body node for some node types.
46    #[serde(default, skip_serializing_if = "Option::is_none")]
47    pub body: Option<Box<Node>>,
48
49    /// Node attributes that were not deserialized.
50    #[serde(flatten)]
51    pub other: BTreeMap<String, serde_json::Value>,
52}
53
54impl Node {
55    /// Deserialize a serialized node attribute.
56    pub fn attribute<D: DeserializeOwned>(&self, key: impl AsRef<str>) -> Option<D> {
57        // TODO: Can we avoid this clone?
58        self.other.get(key.as_ref()).and_then(|v| serde_json::from_value(v.clone()).ok())
59    }
60}
61
62/// Represents the source location of a node: `<start byte>:<length>:<source index>`.
63///
64/// The `length` and `index` can be -1 which is represented as `None`
65#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
66pub struct SourceLocation {
67    pub start: usize,
68    pub length: Option<usize>,
69    pub index: Option<usize>,
70}
71
72impl FromStr for SourceLocation {
73    type Err = String;
74
75    fn from_str(s: &str) -> Result<Self, Self::Err> {
76        let invalid_location = move || format!("{s} invalid source location");
77
78        let mut split = s.split(':');
79        let start = split
80            .next()
81            .ok_or_else(invalid_location)?
82            .parse::<usize>()
83            .map_err(|_| invalid_location())?;
84        let length = split
85            .next()
86            .ok_or_else(invalid_location)?
87            .parse::<isize>()
88            .map_err(|_| invalid_location())?;
89        let index = split
90            .next()
91            .ok_or_else(invalid_location)?
92            .parse::<isize>()
93            .map_err(|_| invalid_location())?;
94
95        let length = if length < 0 { None } else { Some(length as usize) };
96        let index = if index < 0 { None } else { Some(index as usize) };
97
98        Ok(Self { start, length, index })
99    }
100}
101
102impl fmt::Display for SourceLocation {
103    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
104        self.start.fmt(f)?;
105        f.write_char(':')?;
106        if let Some(length) = self.length {
107            length.fmt(f)?;
108        } else {
109            f.write_str("-1")?;
110        }
111        f.write_char(':')?;
112        if let Some(index) = self.index {
113            index.fmt(f)?;
114        } else {
115            f.write_str("-1")?;
116        }
117        Ok(())
118    }
119}
120
121#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
122pub enum NodeType {
123    // Expressions
124    Assignment,
125    BinaryOperation,
126    Conditional,
127    ElementaryTypeNameExpression,
128    FunctionCall,
129    FunctionCallOptions,
130    Identifier,
131    IndexAccess,
132    IndexRangeAccess,
133    Literal,
134    MemberAccess,
135    NewExpression,
136    TupleExpression,
137    UnaryOperation,
138
139    // Statements
140    Block,
141    Break,
142    Continue,
143    DoWhileStatement,
144    EmitStatement,
145    ExpressionStatement,
146    ForStatement,
147    IfStatement,
148    InlineAssembly,
149    PlaceholderStatement,
150    Return,
151    RevertStatement,
152    TryStatement,
153    UncheckedBlock,
154    VariableDeclarationStatement,
155    VariableDeclaration,
156    WhileStatement,
157
158    // Yul statements
159    YulAssignment,
160    YulBlock,
161    YulBreak,
162    YulContinue,
163    YulExpressionStatement,
164    YulLeave,
165    YulForLoop,
166    YulFunctionDefinition,
167    YulIf,
168    YulSwitch,
169    YulVariableDeclaration,
170
171    // Yul expressions
172    YulFunctionCall,
173    YulIdentifier,
174    YulLiteral,
175
176    // Yul literals
177    YulLiteralValue,
178    YulHexValue,
179
180    // Definitions
181    ContractDefinition,
182    FunctionDefinition,
183    EventDefinition,
184    ErrorDefinition,
185    ModifierDefinition,
186    StructDefinition,
187    EnumDefinition,
188    UserDefinedValueTypeDefinition,
189
190    // Directives
191    PragmaDirective,
192    ImportDirective,
193    UsingForDirective,
194
195    // Misc
196    SourceUnit,
197    InheritanceSpecifier,
198    ElementaryTypeName,
199    FunctionTypeName,
200    ParameterList,
201    TryCatchClause,
202    ModifierInvocation,
203
204    /// An unknown AST node type.
205    Other(String),
206}
207
208#[cfg(test)]
209mod tests {
210    use super::*;
211
212    #[test]
213    fn can_parse_ast() {
214        let ast = include_str!("../../../test-data/ast/ast-erc4626.json");
215        let _ast: Ast = serde_json::from_str(ast).unwrap();
216    }
217}