feat: add minimal ast bindings (#1167)
* feat: add minimal ast bindings * feat: add recursive nodes
This commit is contained in:
parent
6fcde371a3
commit
8dd72723d1
|
@ -8,7 +8,7 @@ use crate::{
|
||||||
BytecodeOutputSelection, ContractOutputSelection, EvmOutputSelection,
|
BytecodeOutputSelection, ContractOutputSelection, EvmOutputSelection,
|
||||||
EwasmOutputSelection,
|
EwasmOutputSelection,
|
||||||
},
|
},
|
||||||
CompactContractBytecodeCow, DevDoc, Evm, Ewasm, FunctionDebugData, GasEstimates,
|
Ast, CompactContractBytecodeCow, DevDoc, Evm, Ewasm, FunctionDebugData, GasEstimates,
|
||||||
LosslessAbi, Metadata, Offsets, Settings, StorageLayout, UserDoc,
|
LosslessAbi, Metadata, Offsets, Settings, StorageLayout, UserDoc,
|
||||||
},
|
},
|
||||||
ArtifactOutput, SolcConfig, SolcError, SourceFile,
|
ArtifactOutput, SolcConfig, SolcError, SourceFile,
|
||||||
|
@ -52,8 +52,8 @@ pub struct ConfigurableContractArtifact {
|
||||||
pub ir_optimized: Option<String>,
|
pub ir_optimized: Option<String>,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub ewasm: Option<Ewasm>,
|
pub ewasm: Option<Ewasm>,
|
||||||
#[serde(default, skip_serializing_if = "serde_json::Value::is_null")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub ast: serde_json::Value,
|
pub ast: Option<Ast>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ConfigurableContractArtifact {
|
impl ConfigurableContractArtifact {
|
||||||
|
@ -303,7 +303,7 @@ impl ArtifactOutput for ConfigurableArtifacts {
|
||||||
ir: artifact_ir,
|
ir: artifact_ir,
|
||||||
ir_optimized: artifact_ir_optimized,
|
ir_optimized: artifact_ir_optimized,
|
||||||
ewasm: artifact_ewasm,
|
ewasm: artifact_ewasm,
|
||||||
ast: source_file.map(|s| s.ast.clone()).unwrap_or_default(),
|
ast: source_file.and_then(|s| s.ast.clone()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,223 @@
|
||||||
|
//! Bindings for solc's `ast` output field
|
||||||
|
|
||||||
|
use crate::artifacts::serde_helpers;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::{collections::BTreeMap, fmt, fmt::Write, str::FromStr};
|
||||||
|
|
||||||
|
/// Represents the AST field in the solc output
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
pub struct Ast {
|
||||||
|
#[serde(rename = "absolutePath")]
|
||||||
|
pub absolute_path: String,
|
||||||
|
pub id: usize,
|
||||||
|
#[serde(default, rename = "exportedSymbols")]
|
||||||
|
pub exported_symbols: BTreeMap<String, Vec<usize>>,
|
||||||
|
#[serde(rename = "nodeType")]
|
||||||
|
pub node_type: NodeType,
|
||||||
|
#[serde(with = "serde_helpers::display_from_str")]
|
||||||
|
pub src: SourceLocation,
|
||||||
|
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||||
|
pub nodes: Vec<Node>,
|
||||||
|
#[serde(flatten)]
|
||||||
|
pub other: BTreeMap<String, serde_json::Value>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
pub struct Node {
|
||||||
|
pub id: usize,
|
||||||
|
#[serde(rename = "nodeType")]
|
||||||
|
pub node_type: NodeType,
|
||||||
|
#[serde(with = "serde_helpers::display_from_str")]
|
||||||
|
pub src: SourceLocation,
|
||||||
|
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||||
|
pub nodes: Vec<Node>,
|
||||||
|
#[serde(flatten)]
|
||||||
|
pub other: BTreeMap<String, serde_json::Value>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents the source location of a node : `<start>:<length>:<index>`
|
||||||
|
///
|
||||||
|
/// The `length` and `index` can be -1 which is represented as `None`
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
pub struct SourceLocation {
|
||||||
|
pub start: usize,
|
||||||
|
pub length: Option<usize>,
|
||||||
|
pub index: Option<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for SourceLocation {
|
||||||
|
type Err = String;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
let invalid_location = move || format!("{} invalid source location", s);
|
||||||
|
|
||||||
|
let mut split = s.split(':');
|
||||||
|
let start = split
|
||||||
|
.next()
|
||||||
|
.ok_or_else(invalid_location)?
|
||||||
|
.parse::<usize>()
|
||||||
|
.map_err(|_| invalid_location())?;
|
||||||
|
let length = split
|
||||||
|
.next()
|
||||||
|
.ok_or_else(invalid_location)?
|
||||||
|
.parse::<isize>()
|
||||||
|
.map_err(|_| invalid_location())?;
|
||||||
|
let index = split
|
||||||
|
.next()
|
||||||
|
.ok_or_else(invalid_location)?
|
||||||
|
.parse::<isize>()
|
||||||
|
.map_err(|_| invalid_location())?;
|
||||||
|
|
||||||
|
let length = if length < 0 { None } else { Some(length as usize) };
|
||||||
|
let index = if index < 0 { None } else { Some(index as usize) };
|
||||||
|
|
||||||
|
Ok(Self { start, length, index })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for SourceLocation {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
self.start.fmt(f)?;
|
||||||
|
f.write_char(':')?;
|
||||||
|
if let Some(length) = self.length {
|
||||||
|
length.fmt(f)?;
|
||||||
|
} else {
|
||||||
|
f.write_str("-1")?;
|
||||||
|
}
|
||||||
|
f.write_char(':')?;
|
||||||
|
if let Some(index) = self.index {
|
||||||
|
index.fmt(f)?;
|
||||||
|
} else {
|
||||||
|
f.write_str("-1")?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
#[serde(untagged)]
|
||||||
|
pub enum NodeType {
|
||||||
|
YulAssignment,
|
||||||
|
YulBlock,
|
||||||
|
YulExpressionStatement,
|
||||||
|
YulForLoop,
|
||||||
|
YulIf,
|
||||||
|
YulVariableDeclaration,
|
||||||
|
YulFunctionDefinition,
|
||||||
|
SourceUnit,
|
||||||
|
PragmaDirective,
|
||||||
|
ContractDefinition,
|
||||||
|
EventDefinition,
|
||||||
|
ErrorDefinition,
|
||||||
|
Other(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn can_parse_ast() {
|
||||||
|
let ast = r#"
|
||||||
|
{
|
||||||
|
"absolutePath": "input.sol",
|
||||||
|
"exportedSymbols":
|
||||||
|
{
|
||||||
|
"Ballot":
|
||||||
|
[
|
||||||
|
2
|
||||||
|
],
|
||||||
|
"Ballot2":
|
||||||
|
[
|
||||||
|
3
|
||||||
|
],
|
||||||
|
"Ballot3":
|
||||||
|
[
|
||||||
|
4
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"id": 5,
|
||||||
|
"nodeType": "SourceUnit",
|
||||||
|
"nodes":
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"literals":
|
||||||
|
[
|
||||||
|
"solidity",
|
||||||
|
">=",
|
||||||
|
"0.4",
|
||||||
|
".0"
|
||||||
|
],
|
||||||
|
"nodeType": "PragmaDirective",
|
||||||
|
"src": "1:24:0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"abstract": false,
|
||||||
|
"baseContracts": [],
|
||||||
|
"canonicalName": "Ballot",
|
||||||
|
"contractDependencies": [],
|
||||||
|
"contractKind": "contract",
|
||||||
|
"fullyImplemented": true,
|
||||||
|
"id": 2,
|
||||||
|
"linearizedBaseContracts":
|
||||||
|
[
|
||||||
|
2
|
||||||
|
],
|
||||||
|
"name": "Ballot",
|
||||||
|
"nameLocation": "36:6:0",
|
||||||
|
"nodeType": "ContractDefinition",
|
||||||
|
"nodes": [],
|
||||||
|
"scope": 5,
|
||||||
|
"src": "27:20:0",
|
||||||
|
"usedErrors": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"abstract": false,
|
||||||
|
"baseContracts": [],
|
||||||
|
"canonicalName": "Ballot2",
|
||||||
|
"contractDependencies": [],
|
||||||
|
"contractKind": "contract",
|
||||||
|
"fullyImplemented": true,
|
||||||
|
"id": 3,
|
||||||
|
"linearizedBaseContracts":
|
||||||
|
[
|
||||||
|
3
|
||||||
|
],
|
||||||
|
"name": "Ballot2",
|
||||||
|
"nameLocation": "58:7:0",
|
||||||
|
"nodeType": "ContractDefinition",
|
||||||
|
"nodes": [],
|
||||||
|
"scope": 5,
|
||||||
|
"src": "49:21:0",
|
||||||
|
"usedErrors": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"abstract": false,
|
||||||
|
"baseContracts": [],
|
||||||
|
"canonicalName": "Ballot3",
|
||||||
|
"contractDependencies": [],
|
||||||
|
"contractKind": "contract",
|
||||||
|
"fullyImplemented": true,
|
||||||
|
"id": 4,
|
||||||
|
"linearizedBaseContracts":
|
||||||
|
[
|
||||||
|
4
|
||||||
|
],
|
||||||
|
"name": "Ballot3",
|
||||||
|
"nameLocation": "81:7:0",
|
||||||
|
"nodeType": "ContractDefinition",
|
||||||
|
"nodes": [],
|
||||||
|
"scope": 5,
|
||||||
|
"src": "72:21:0",
|
||||||
|
"usedErrors": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"src": "1:92:0"
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
let _ast: Ast = serde_json::from_str(ast).unwrap();
|
||||||
|
|
||||||
|
dbg!(serde_json::from_str::<serde_json::Value>("{}").unwrap());
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,6 +15,8 @@ use crate::{compile::*, error::SolcIoError, remappings::Remapping, utils};
|
||||||
|
|
||||||
use serde::{de::Visitor, Deserialize, Deserializer, Serialize, Serializer};
|
use serde::{de::Visitor, Deserialize, Deserializer, Serialize, Serializer};
|
||||||
|
|
||||||
|
pub mod ast;
|
||||||
|
pub use ast::*;
|
||||||
pub mod bytecode;
|
pub mod bytecode;
|
||||||
pub mod contract;
|
pub mod contract;
|
||||||
pub mod output_selection;
|
pub mod output_selection;
|
||||||
|
@ -1402,8 +1404,8 @@ pub struct SecondarySourceLocation {
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
|
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
|
||||||
pub struct SourceFile {
|
pub struct SourceFile {
|
||||||
pub id: u32,
|
pub id: u32,
|
||||||
#[serde(default)]
|
#[serde(default, with = "serde_helpers::empty_json_object_opt")]
|
||||||
pub ast: serde_json::Value,
|
pub ast: Option<Ast>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A wrapper type for a list of source files
|
/// A wrapper type for a list of source files
|
||||||
|
|
|
@ -3,14 +3,14 @@
|
||||||
use ethers_core::types::Bytes;
|
use ethers_core::types::Bytes;
|
||||||
use serde::{Deserialize, Deserializer};
|
use serde::{Deserialize, Deserializer};
|
||||||
|
|
||||||
pub fn deserialize_bytes<'de, D>(d: D) -> std::result::Result<Bytes, D::Error>
|
pub fn deserialize_bytes<'de, D>(d: D) -> Result<Bytes, D::Error>
|
||||||
where
|
where
|
||||||
D: Deserializer<'de>,
|
D: Deserializer<'de>,
|
||||||
{
|
{
|
||||||
String::deserialize(d)?.parse::<Bytes>().map_err(|e| serde::de::Error::custom(e.to_string()))
|
String::deserialize(d)?.parse::<Bytes>().map_err(|e| serde::de::Error::custom(e.to_string()))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deserialize_opt_bytes<'de, D>(d: D) -> std::result::Result<Option<Bytes>, D::Error>
|
pub fn deserialize_opt_bytes<'de, D>(d: D) -> Result<Option<Bytes>, D::Error>
|
||||||
where
|
where
|
||||||
D: Deserializer<'de>,
|
D: Deserializer<'de>,
|
||||||
{
|
{
|
||||||
|
@ -70,6 +70,43 @@ pub mod json_string_opt {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// deserializes empty json object `{}` as `None`
|
||||||
|
pub mod empty_json_object_opt {
|
||||||
|
use serde::{
|
||||||
|
de::{self, DeserializeOwned},
|
||||||
|
ser, Deserialize, Deserializer, Serialize, Serializer,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn serialize<T, S>(value: &Option<T>, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: Serializer,
|
||||||
|
T: Serialize,
|
||||||
|
{
|
||||||
|
if let Some(value) = value {
|
||||||
|
let value = serde_json::to_string(value).map_err(ser::Error::custom)?;
|
||||||
|
serializer.serialize_str(&value)
|
||||||
|
} else {
|
||||||
|
let empty = serde_json::Value::Object(Default::default());
|
||||||
|
serde_json::Value::serialize(&empty, serializer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deserialize<'de, T, D>(deserializer: D) -> Result<Option<T>, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
T: DeserializeOwned,
|
||||||
|
{
|
||||||
|
let json = serde_json::Value::deserialize(deserializer)?;
|
||||||
|
if json.is_null() {
|
||||||
|
return Ok(None)
|
||||||
|
}
|
||||||
|
if json.as_object().map(|obj| obj.is_empty()).unwrap_or_default() {
|
||||||
|
return Ok(None)
|
||||||
|
}
|
||||||
|
serde_json::from_value(json).map_err(de::Error::custom).map(Some)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// serde support for string
|
/// serde support for string
|
||||||
pub mod string_bytes {
|
pub mod string_bytes {
|
||||||
use serde::{Deserialize, Deserializer, Serializer};
|
use serde::{Deserialize, Deserializer, Serializer};
|
||||||
|
@ -128,6 +165,28 @@ pub mod display_from_str_opt {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub mod display_from_str {
|
||||||
|
use serde::{de, Deserialize, Deserializer, Serializer};
|
||||||
|
use std::{fmt, str::FromStr};
|
||||||
|
|
||||||
|
pub fn serialize<T, S>(value: &T, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
T: fmt::Display,
|
||||||
|
S: Serializer,
|
||||||
|
{
|
||||||
|
serializer.collect_str(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deserialize<'de, T, D>(deserializer: D) -> Result<T, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
T: FromStr,
|
||||||
|
T::Err: fmt::Display,
|
||||||
|
{
|
||||||
|
String::deserialize(deserializer)?.parse().map_err(de::Error::custom)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// (De)serialize vec of tuples as map
|
/// (De)serialize vec of tuples as map
|
||||||
pub mod tuple_vec_map {
|
pub mod tuple_vec_map {
|
||||||
use serde::{de::DeserializeOwned, Deserialize, Deserializer, Serialize, Serializer};
|
use serde::{de::DeserializeOwned, Deserialize, Deserializer, Serialize, Serializer};
|
||||||
|
|
|
@ -692,9 +692,9 @@ mod tests {
|
||||||
assert_eq!(state.output.sources.len(), 3);
|
assert_eq!(state.output.sources.len(), 3);
|
||||||
for (f, source) in &state.output.sources {
|
for (f, source) in &state.output.sources {
|
||||||
if f.ends_with("A.sol") {
|
if f.ends_with("A.sol") {
|
||||||
assert!(source.ast.is_object());
|
assert!(source.ast.is_some());
|
||||||
} else {
|
} else {
|
||||||
assert!(source.ast.is_null());
|
assert!(source.ast.is_none());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue