refactor: typed solc ast

rebase with master
This commit is contained in:
Oliver Nordbjerg 2022-08-05 01:38:16 +02:00 committed by franfran
parent 5bc9ee73b2
commit d5ebff42e4
7 changed files with 368 additions and 112 deletions

View File

@ -16,9 +16,9 @@ use crate::{
BytecodeOutputSelection, ContractOutputSelection, EvmOutputSelection, BytecodeOutputSelection, ContractOutputSelection, EvmOutputSelection,
EwasmOutputSelection, EwasmOutputSelection,
}, },
Ast, CompactContractBytecodeCow, DevDoc, Evm, Ewasm, FunctionDebugData, GasEstimates, CompactContractBytecodeCow, DevDoc, Evm, Ewasm, FunctionDebugData, GasEstimates,
GeneratedSource, LosslessAbi, LosslessMetadata, Metadata, Offsets, Settings, StorageLayout, GeneratedSource, LosslessAbi, LosslessMetadata, Metadata, Offsets, Settings, SourceUnit,
UserDoc, StorageLayout, UserDoc,
}, },
sources::VersionedSourceFile, sources::VersionedSourceFile,
ArtifactOutput, SolcConfig, SolcError, SourceFile, ArtifactOutput, SolcConfig, SolcError, SourceFile,
@ -68,7 +68,7 @@ pub struct ConfigurableContractArtifact {
#[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 = "Option::is_none")] #[serde(default, skip_serializing_if = "Option::is_none")]
pub ast: Option<Ast>, pub ast: Option<SourceUnit>,
/// The identifier of the source file /// The identifier of the source file
#[serde(default, skip_serializing_if = "Option::is_none")] #[serde(default, skip_serializing_if = "Option::is_none")]
pub id: Option<u32>, pub id: Option<u32>,

View File

@ -0,0 +1,56 @@
/// Macro that expands to a struct with common AST node fields.
macro_rules! ast_node {
(
$(#[$struct_meta:meta])*
struct $name:ident {
$(
$(#[$field_meta:meta])*
$field:ident: $ty:ty
),* $(,)*
}
) => {
$(#[$struct_meta])*
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct $name {
pub id: usize,
#[serde(with = "serde_helpers::display_from_str")]
pub src: SourceLocation,
$(
$(#[$field_meta])*
pub $field: $ty
),*
}
};
}
/// A macro that expands to a struct with common expression node fields.
macro_rules! expr_node {
(
$(#[$struct_meta:meta])*
struct $name:ident {
$(
$(#[$field_meta:meta])*
$field:ident: $ty:ty
),* $(,)*
}
) => {
ast_node!(
$(#[$struct_meta])*
struct $name {
argument_types: Vec<TypeDescriptions>,
is_constant: bool,
is_l_value: bool,
is_pure: bool,
l_value_requested: bool,
type_descriptions: TypeDescriptions,
$(
$(#[$field_meta])*
$field: $ty
),*
}
);
}
}
pub(crate) use ast_node;
pub(crate) use expr_node;

View File

@ -1,63 +1,5 @@
//! Bindings for solc's `ast` output field use serde::{Deserialize, Serialize};
use std::{fmt, fmt::Write, str::FromStr};
use crate::artifacts::serde_helpers;
use serde::{de::DeserializeOwned, 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)]
pub nodes: Vec<Node>,
/// Node attributes that were not deserialized.
#[serde(flatten)]
pub other: BTreeMap<String, serde_json::Value>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Node {
/// The node ID.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub id: Option<usize>,
/// The node type.
#[serde(rename = "nodeType")]
pub node_type: NodeType,
/// The location of the node in the source file.
#[serde(with = "serde_helpers::display_from_str")]
pub src: SourceLocation,
/// Child nodes for some node types.
#[serde(default)]
pub nodes: Vec<Node>,
/// Body node for some node types.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub body: Option<Box<Node>>,
/// Node attributes that were not deserialized.
#[serde(flatten)]
pub other: BTreeMap<String, serde_json::Value>,
}
impl Node {
/// Deserialize a serialized node attribute.
pub fn attribute<D: DeserializeOwned>(&self, key: impl AsRef<str>) -> Option<D> {
// TODO: Can we avoid this clone?
self.other.get(key.as_ref()).and_then(|v| serde_json::from_value(v.clone()).ok())
}
}
/// Represents the source location of a node: `<start byte>:<length>:<source index>`. /// Represents the source location of a node: `<start byte>:<length>:<source index>`.
/// ///
@ -119,8 +61,7 @@ impl fmt::Display for SourceLocation {
} }
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum NodeType { pub enum Expression {
// Expressions
Assignment, Assignment,
BinaryOperation, BinaryOperation,
Conditional, Conditional,
@ -135,8 +76,50 @@ pub enum NodeType {
NewExpression, NewExpression,
TupleExpression, TupleExpression,
UnaryOperation, UnaryOperation,
}
// Statements #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum StateMutability {
Payable,
Pure,
Nonpayable,
View,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum TypeName {
ArrayTypeName,
ElementaryTypeName,
FunctionTypeName,
Mapping,
UserDefinedTypeName,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum Mutability {
Mutable,
Immutable,
Constant,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum StorageLocation {
Calldata,
Default,
Memory,
Storage,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum Visibility {
External,
Public,
Internal,
Private,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum Statement {
Block, Block,
Break, Break,
Continue, Continue,
@ -152,10 +135,11 @@ pub enum NodeType {
TryStatement, TryStatement,
UncheckedBlock, UncheckedBlock,
VariableDeclarationStatement, VariableDeclarationStatement,
VariableDeclaration,
WhileStatement, WhileStatement,
}
// Yul statements #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum YulStatement {
YulAssignment, YulAssignment,
YulBlock, YulBlock,
YulBreak, YulBreak,
@ -167,51 +151,17 @@ pub enum NodeType {
YulIf, YulIf,
YulSwitch, YulSwitch,
YulVariableDeclaration, YulVariableDeclaration,
}
// Yul expressions #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum YulExpression {
YulFunctionCall, YulFunctionCall,
YulIdentifier, YulIdentifier,
YulLiteral, YulLiteral,
}
// Yul literals #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum YulLiteral {
YulLiteralValue, YulLiteralValue,
YulHexValue, YulLiteralHexValue,
// Definitions
ContractDefinition,
FunctionDefinition,
EventDefinition,
ErrorDefinition,
ModifierDefinition,
StructDefinition,
EnumDefinition,
UserDefinedValueTypeDefinition,
// Directives
PragmaDirective,
ImportDirective,
UsingForDirective,
// Misc
SourceUnit,
InheritanceSpecifier,
ElementaryTypeName,
FunctionTypeName,
ParameterList,
TryCatchClause,
ModifierInvocation,
/// An unknown AST node type.
Other(String),
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn can_parse_ast() {
let ast = include_str!("../../test-data/ast/ast-erc4626.json");
let _ast: Ast = serde_json::from_str(ast).unwrap();
}
} }

View File

@ -0,0 +1,248 @@
//! Bindings for solc's `ast` output field
mod macros;
mod misc;
pub use misc::*;
pub mod util;
pub mod visitor;
use crate::artifacts::serde_helpers;
use macros::{ast_node, expr_node};
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
// TODO: Node ID
ast_node!(
/// The root node of a Solidity AST.
struct SourceUnit {
#[serde(rename = "absolutePath")]
absolute_path: String,
#[serde(default, rename = "exportedSymbols")]
exported_symbols: BTreeMap<String, Vec<usize>>,
license: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
nodes: Vec<SourceUnitPart>,
}
);
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum SourceUnitPart {
PragmaDirective,
ImportDirective,
UsingForDirective,
VariableDeclaration,
EnumDefinition,
ErrorDefinition,
FunctionDefinition,
StructDefinition,
UserDefinedValueTypeDefinition,
ContractDefinition,
}
ast_node!(
struct ContractDefinition {
name: String,
name_location: Option<String>,
is_abstract: bool,
base_contracts: Vec<InheritanceSpecifier>,
canonical_name: Option<String>,
contract_dependencies: Vec<usize>,
kind: ContractKind,
documentation: Option<StructuredDocumentation>,
fully_implemented: bool,
linearized_base_contracts: Vec<usize>,
nodes: Vec<ContractDefinitionPart>,
scope: usize,
used_errors: Vec<usize>,
}
);
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum ContractKind {
Contract,
Interface,
Library,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum ContractDefinitionPart {
EnumDefinition,
ErrorDefinition,
EventDefinition,
FunctionDefinition,
ModifierDefinition,
StructDefinition,
UserDefinedValueTypeDefinition,
UsingForDirective,
VariableDeclaration,
}
ast_node!(
struct InheritanceSpecifier {
arguments: Vec<Expression>,
base_name: UserDefinedTypeNameOrIdentifierPath,
}
);
// TODO: Better name
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum UserDefinedTypeNameOrIdentifierPath {
UserDefinedTypeName,
IdentifierPath,
}
expr_node!(
struct Assignment {
lhs: Expression,
operator: AssignmentOperator,
rhs: Expression,
}
);
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum AssignmentOperator {
/// =
Assign,
/// +=
AddAssign,
/// -=
SubAssign,
/// *=
MulAssign,
/// /=
DivAssign,
/// %=
ModAssign,
/// |=
OrAssign,
/// &=
AndAssign,
/// ^=
XorAssign,
/// >>=
ShrAssign,
/// <<=
ShlAssign,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct TypeDescriptions {
pub type_identifier: Option<String>,
pub type_string: Option<String>,
}
ast_node!(
struct BinaryOperation {
common_type: TypeDescriptions,
lhs: Expression,
operator: BinaryOperator,
rhs: Expression,
}
);
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum BinaryOperator {
/// +
Add,
/// -
Sub,
/// *
Mul,
/// /
Div,
/// %
Mod,
/// **
Pow,
/// &&
And,
/// ||
Or,
/// !=
NotEqual,
/// ==
Equal,
/// <
LessThan,
/// <=
LessThanOrEqual,
/// >
GreaterThan,
/// >=
GreaterThanOrEqual,
/// ^
Xor,
/// &
BitAnd,
/// |
BitOr,
/// <<
Shl,
/// >>
Shr,
}
expr_node!(
struct Conditional {
condition: Expression,
false_expression: Expression,
true_expression: Expression,
}
);
expr_node!(
struct ElementaryTypeNameExpression {
type_name: ElementaryTypeName,
}
);
ast_node!(
struct ElementaryTypeName {
type_descriptions: TypeDescriptions,
name: String,
state_mutability: Option<StateMutability>,
}
);
expr_node!(
struct FunctionCall {
arguments: Vec<Expression>,
expression: Expression,
kind: FunctionCallKind,
names: Vec<String>,
try_call: bool,
}
);
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum FunctionCallKind {
FunctionCall,
TypeConversion,
StructConstructorCall,
}
expr_node!(
struct FunctionCallOptions {
expression: Expression,
names: Vec<String>,
options: Vec<Expression>,
}
);
ast_node!(
struct StructuredDocumentation {
text: String,
}
);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn can_parse_ast() {
let ast = include_str!("../../test-data/ast/ast-erc4626.json");
let _ast: SourceUnit = serde_json::from_str(ast).unwrap();
}
}

View File

@ -0,0 +1 @@

View File

@ -0,0 +1 @@

View File

@ -1835,7 +1835,7 @@ pub struct SecondarySourceLocation {
pub struct SourceFile { pub struct SourceFile {
pub id: u32, pub id: u32,
#[serde(default, with = "serde_helpers::empty_json_object_opt")] #[serde(default, with = "serde_helpers::empty_json_object_opt")]
pub ast: Option<Ast>, pub ast: Option<SourceUnit>,
} }
// === impl SourceFile === // === impl SourceFile ===
@ -1846,7 +1846,7 @@ impl SourceFile {
pub fn contains_contract_definition(&self) -> bool { pub fn contains_contract_definition(&self) -> bool {
if let Some(ref ast) = self.ast { if let Some(ref ast) = self.ast {
// contract definitions are only allowed at the source-unit level <https://docs.soliditylang.org/en/latest/grammar.html> // contract definitions are only allowed at the source-unit level <https://docs.soliditylang.org/en/latest/grammar.html>
return ast.nodes.iter().any(|node| node.node_type == NodeType::ContractDefinition) return ast.nodes.iter().any(|node| matches!(node, SourceUnitPart::ContractDefinition))
// abstract contract, interfaces: ContractDefinition // abstract contract, interfaces: ContractDefinition
} }