feat: add minimal ast bindings (#1167)

* feat: add minimal ast bindings

* feat: add recursive nodes
This commit is contained in:
Matthias Seitz 2022-04-23 11:20:46 +02:00 committed by GitHub
parent 6fcde371a3
commit 8dd72723d1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 294 additions and 10 deletions

View File

@ -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()),
} }
} }
} }

View File

@ -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());
}
}

View File

@ -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

View File

@ -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};

View File

@ -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());
} }
} }