diff --git a/crates/sol-macro/src/expand/enum.rs b/crates/sol-macro/src/expand/enum.rs index 3627c2b59..d0be6f9e3 100644 --- a/crates/sol-macro/src/expand/enum.rs +++ b/crates/sol-macro/src/expand/enum.rs @@ -1,7 +1,7 @@ //! [`ItemEnum`] expansion. use super::ExpCtxt; -use ast::ItemEnum; +use ast::{ItemEnum, Spanned}; use proc_macro2::TokenStream; use quote::quote; use syn::Result; diff --git a/crates/sol-macro/src/expand/event.rs b/crates/sol-macro/src/expand/event.rs index c2dee10aa..81b1965b0 100644 --- a/crates/sol-macro/src/expand/event.rs +++ b/crates/sol-macro/src/expand/event.rs @@ -2,7 +2,7 @@ use super::{anon_name, expand_tuple_types, expand_type, ExpCtxt}; use crate::expand::ty::expand_event_tokenize_func; -use ast::{EventParameter, ItemEvent, SolIdent}; +use ast::{EventParameter, ItemEvent, SolIdent, Spanned}; use proc_macro2::TokenStream; use quote::{quote, quote_spanned}; use syn::Result; diff --git a/crates/sol-macro/src/expand/mod.rs b/crates/sol-macro/src/expand/mod.rs index 624404e73..05ab6cfeb 100644 --- a/crates/sol-macro/src/expand/mod.rs +++ b/crates/sol-macro/src/expand/mod.rs @@ -5,11 +5,11 @@ use crate::{ utils::ExprArray, }; use ast::{ - File, Item, ItemError, ItemEvent, ItemFunction, Parameters, SolIdent, SolPath, Type, + File, Item, ItemError, ItemEvent, ItemFunction, Parameters, SolIdent, SolPath, Spanned, Type, VariableDeclaration, Visit, }; use proc_macro2::{Ident, Span, TokenStream}; -use quote::{format_ident, quote, IdentFragment}; +use quote::{format_ident, quote}; use std::{borrow::Borrow, collections::HashMap, fmt::Write}; use syn::{parse_quote, Attribute, Error, Result}; @@ -329,7 +329,7 @@ impl ExpCtxt<'_> { } } - fn raw_call_name(&self, function_name: impl IdentFragment + std::fmt::Display) -> Ident { + fn raw_call_name(&self, function_name: impl quote::IdentFragment + std::fmt::Display) -> Ident { format_ident!("{function_name}Call") } @@ -338,7 +338,10 @@ impl ExpCtxt<'_> { self.raw_call_name(function_name) } - fn raw_return_name(&self, function_name: impl IdentFragment + std::fmt::Display) -> Ident { + fn raw_return_name( + &self, + function_name: impl quote::IdentFragment + std::fmt::Display, + ) -> Ident { format_ident!("{function_name}Return") } diff --git a/crates/sol-macro/src/expand/struct.rs b/crates/sol-macro/src/expand/struct.rs index ce32f4200..988e0d0e5 100644 --- a/crates/sol-macro/src/expand/struct.rs +++ b/crates/sol-macro/src/expand/struct.rs @@ -3,7 +3,7 @@ use super::{ expand_fields, expand_from_into_tuples, expand_type, ty::expand_tokenize_func, ExpCtxt, }; -use ast::{Item, ItemStruct, Type, VariableDeclaration}; +use ast::{Item, ItemStruct, Spanned, Type, VariableDeclaration}; use proc_macro2::TokenStream; use quote::quote; use std::num::NonZeroU16; diff --git a/crates/sol-macro/src/expand/ty.rs b/crates/sol-macro/src/expand/ty.rs index 5b550a4e3..efb43df13 100644 --- a/crates/sol-macro/src/expand/ty.rs +++ b/crates/sol-macro/src/expand/ty.rs @@ -2,7 +2,7 @@ use super::ExpCtxt; use crate::expand::generate_name; -use ast::{EventParameter, Item, Parameters, Type, TypeArray, VariableDeclaration}; +use ast::{EventParameter, Item, Parameters, Spanned, Type, TypeArray, VariableDeclaration}; use proc_macro2::{Literal, TokenStream}; use quote::{quote, quote_spanned, ToTokens}; use std::{fmt, num::NonZeroU16}; @@ -102,7 +102,7 @@ fn rec_expand_type(ty: &Type, tokens: &mut TokenStream) { Type::Array(ref array) => { let ty = expand_type(&array.ty); let span = array.span(); - if let Some(size) = &array.size { + if let Some(size) = array.size() { quote_spanned! {span=> ::alloy_sol_types::sol_data::FixedArray<#ty, #size> } @@ -153,11 +153,13 @@ pub(super) fn type_base_data_size(cx: &ExpCtxt<'_>, ty: &Type) -> usize { Type::String(_) | Type::Bytes(_) | Type::Array(TypeArray { size: None, .. }) => 64, // fixed array: size * encoded size - Type::Array(TypeArray { - ty: inner, - size: Some(size), - .. - }) => type_base_data_size(cx, inner) * size.base10_parse::().unwrap(), + Type::Array( + a @ TypeArray { + ty: inner, + size: Some(_), + .. + }, + ) => type_base_data_size(cx, inner) * a.size().unwrap(), // tuple: sum of encoded sizes Type::Tuple(tuple) => tuple @@ -295,7 +297,7 @@ impl fmt::Display for TypePrinter<'_> { Type::Array(array) => { Self::new(self.cx, &array.ty).fmt(f)?; f.write_str("[")?; - if let Some(size) = &array.size { + if let Some(size) = array.size() { size.fmt(f)?; } f.write_str("]") diff --git a/crates/sol-macro/src/input.rs b/crates/sol-macro/src/input.rs index bb6eb619b..ee7077254 100644 --- a/crates/sol-macro/src/input.rs +++ b/crates/sol-macro/src/input.rs @@ -1,11 +1,13 @@ +use ast::Spanned; use proc_macro2::TokenStream; use quote::quote; use std::path::PathBuf; use syn::{ - parse::{Parse, ParseStream}, + parse::{discouraged::Speculative, Parse, ParseStream}, Error, Ident, LitStr, Result, Token, }; +#[derive(Clone, Debug)] pub enum SolInputKind { Sol(ast::File), Type(ast::Type), @@ -16,15 +18,15 @@ pub enum SolInputKind { // doesn't parse Json impl Parse for SolInputKind { fn parse(input: ParseStream<'_>) -> Result { - let start = input.fork(); - match input.parse() { - Ok(file) => Ok(Self::Sol(file)), - Err(e) => match start.parse() { + let fork = input.fork(); + match fork.parse() { + Ok(file) => { + input.advance_to(&fork); + Ok(Self::Sol(file)) + } + Err(e) => match input.parse() { Ok(ast::Type::Custom(_)) | Err(_) => Err(e), - Ok(ast::Type::Function(f)) => { - Err(Error::new(f.span(), "function types are not yet supported")) - } Ok(ast::Type::Mapping(m)) => { Err(Error::new(m.span(), "mapping types are not yet supported")) } @@ -35,6 +37,7 @@ impl Parse for SolInputKind { } } +#[derive(Clone, Debug)] pub struct SolInput { pub path: Option, pub kind: SolInputKind, diff --git a/crates/sol-macro/src/json.rs b/crates/sol-macro/src/json.rs index adcc96515..2589ae355 100644 --- a/crates/sol-macro/src/json.rs +++ b/crates/sol-macro/src/json.rs @@ -62,7 +62,6 @@ fn expand_abi(name: &Ident, abi: JsonAbi) -> Result { // `Other` is a UDVT if it's not a basic Solidity type if let Some(it) = internal_type.other_specifier() { if it.try_basic_solidity().is_err() { - let _ = dbg!(it.try_basic_solidity()); udvts.insert(struct_ident(ty).to_owned(), real_ty.to_owned()); } } diff --git a/crates/sol-types/tests/ui/type.stderr b/crates/sol-types/tests/ui/type.stderr index eb68fd074..f79ecce46 100644 --- a/crates/sol-types/tests/ui/type.stderr +++ b/crates/sol-types/tests/ui/type.stderr @@ -74,7 +74,7 @@ error: proc macro panicked 741 | | } | |_^ | - = help: message: mapping types are unsupported: TypeMapping { key: TypeMapping { key: Custom([SolIdent("a")]), key_name: Some(SolIdent("b")), value: Custom([SolIdent("c")]), value_name: Some(SolIdent("d")) }, key_name: Some(SolIdent("e")), value: TypeMapping { key: Custom([SolIdent("f")]), key_name: Some(SolIdent("g")), value: Custom([SolIdent("h")]), value_name: Some(SolIdent("i")) }, value_name: Some(SolIdent("j")) } + = help: message: mapping types are unsupported: TypeMapping { key: Type::TypeMapping { key: Type::Custom([SolIdent("a")]), key_name: Some(SolIdent("b")), value: Type::Custom([SolIdent("c")]), value_name: Some(SolIdent("d")) }, key_name: Some(SolIdent("e")), value: Type::TypeMapping { key: Type::Custom([SolIdent("f")]), key_name: Some(SolIdent("g")), value: Type::Custom([SolIdent("h")]), value_name: Some(SolIdent("i")) }, value_name: Some(SolIdent("j")) } error: proc macro panicked --> tests/ui/type.rs:743:1 @@ -84,7 +84,7 @@ error: proc macro panicked 745 | | } | |_^ | - = help: message: mapping types are unsupported: TypeMapping { key: Uint(Some(256)), key_name: Some(SolIdent("a")), value: Bool, value_name: Some(SolIdent("b")) } + = help: message: mapping types are unsupported: TypeMapping { key: Type::Uint(Some(256)), key_name: Some(SolIdent("a")), value: Type::Bool, value_name: Some(SolIdent("b")) } error[E0412]: cannot find type `bytes_` in this scope --> tests/ui/type.rs:205:9 diff --git a/crates/syn-solidity/README.md b/crates/syn-solidity/README.md index c2e177486..bbf330c80 100644 --- a/crates/syn-solidity/README.md +++ b/crates/syn-solidity/README.md @@ -62,7 +62,7 @@ Basic usage: ```rust use quote::quote; -use syn_solidity::{File, Item}; +use syn_solidity::{Expr, File, Item, Lit, Stmt}; // Create a Solidity `TokenStream` let tokens = quote! { @@ -80,16 +80,28 @@ let tokens = quote! { let ast: File = syn_solidity::parse2(tokens)?; let items: &[Item] = &ast.items; -let Some(Item::Contract(contract)) = items.first() else { unreachable!() }; +let Some(Item::Contract(contract)) = items.first() else { + unreachable!() +}; assert_eq!(contract.name, "HelloWorld"); assert_eq!(contract.attrs.len(), 2); // doc comments let body: &[Item] = &contract.body; -let Some(Item::Function(function)) = body.first() else { unreachable!() }; +let Some(Item::Function(function)) = body.first() else { + unreachable!() +}; assert_eq!(function.attrs.len(), 1); // doc comment assert_eq!(function.name.as_ref().unwrap(), "helloWorld"); -assert!(function.arguments.is_empty()); // () +assert!(function.arguments.is_empty()); // () assert_eq!(function.attributes.len(), 2); // external pure assert!(function.returns.is_some()); + +let Some([Stmt::Return(ret)]) = function.body() else { + unreachable!() +}; +let Some(Expr::Lit(Lit::Str(s))) = &ret.expr else { + unreachable!() +}; +assert_eq!(s.value(), "Hello, World!"); # syn::Result::Ok(()) ``` diff --git a/crates/syn-solidity/src/attribute/function.rs b/crates/syn-solidity/src/attribute/function.rs index aebfe83cd..26c1d0130 100644 --- a/crates/syn-solidity/src/attribute/function.rs +++ b/crates/syn-solidity/src/attribute/function.rs @@ -1,7 +1,6 @@ -use super::{kw, Modifier, Mutability, Override, SolPath, VariableAttribute, Visibility}; +use crate::{kw, Modifier, Mutability, Override, SolPath, Spanned, VariableAttribute, Visibility}; use proc_macro2::Span; use std::{ - collections::HashSet, fmt, hash::{Hash, Hasher}, mem, @@ -11,16 +10,22 @@ use syn::{ ext::IdentExt, parse::{Parse, ParseStream}, token::Brace, - Error, Ident, Result, + Error, Ident, Result, Token, }; /// A list of unique function attributes. Used in /// [ItemFunction][crate::ItemFunction]. -#[derive(Clone, Debug, Default, PartialEq, Eq)] -pub struct FunctionAttributes(pub HashSet); +#[derive(Clone, Default, PartialEq, Eq)] +pub struct FunctionAttributes(pub Vec); + +impl fmt::Debug for FunctionAttributes { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } +} impl Deref for FunctionAttributes { - type Target = HashSet; + type Target = Vec; fn deref(&self) -> &Self::Target { &self.0 @@ -35,24 +40,34 @@ impl DerefMut for FunctionAttributes { impl Parse for FunctionAttributes { fn parse(input: ParseStream<'_>) -> Result { - let mut attributes = HashSet::::new(); - while !(input.is_empty() || input.peek(kw::returns) || !input.peek(Ident::peek_any)) { - let attr = input.parse()?; - if let Some(prev) = attributes.get(&attr) { + let mut attributes = Vec::::new(); + while !input.is_empty() && !input.peek(kw::returns) && input.peek(Ident::peek_any) { + let attr: FunctionAttribute = input.parse()?; + if let Some(prev) = attributes.iter().find(|a| **a == attr) { let mut e = Error::new(attr.span(), "duplicate attribute"); e.combine(Error::new(prev.span(), "previous declaration is here")); return Err(e) } - attributes.insert(attr); + attributes.push(attr); } Ok(Self(attributes)) } } +impl Spanned for FunctionAttributes { + fn span(&self) -> Span { + crate::utils::join_spans(&self.0) + } + + fn set_span(&mut self, span: Span) { + crate::utils::set_spans_clone(&mut self.0, span) + } +} + impl FunctionAttributes { #[inline] pub fn new() -> Self { - Self(HashSet::new()) + Self(Vec::new()) } pub fn visibility(&self) -> Option { @@ -112,7 +127,7 @@ pub enum FunctionAttribute { /// A [Mutability] attribute. Mutability(Mutability), /// `virtual` - Virtual(kw::Virtual), + Virtual(Token![virtual]), /// `immutable` Immutable(kw::immutable), /// An [Override] attribute. @@ -174,9 +189,9 @@ impl Parse for FunctionAttribute { input.parse().map(Self::Visibility) } else if Mutability::peek(&lookahead) { input.parse().map(Self::Mutability) - } else if lookahead.peek(kw::Virtual) { + } else if lookahead.peek(Token![virtual]) { input.parse().map(Self::Virtual) - } else if lookahead.peek(kw::Override) { + } else if lookahead.peek(Token![override]) { input.parse().map(Self::Override) } else if lookahead.peek(kw::immutable) { input.parse().map(Self::Immutable) @@ -202,8 +217,8 @@ impl From for FunctionAttribute { } } -impl FunctionAttribute { - pub fn span(&self) -> Span { +impl Spanned for FunctionAttribute { + fn span(&self) -> Span { match self { Self::Visibility(v) => v.span(), Self::Mutability(m) => m.span(), @@ -214,7 +229,7 @@ impl FunctionAttribute { } } - pub fn set_span(&mut self, span: Span) { + fn set_span(&mut self, span: Span) { match self { Self::Visibility(v) => v.set_span(span), Self::Mutability(m) => m.set_span(span), @@ -224,7 +239,9 @@ impl FunctionAttribute { Self::Modifier(m) => m.set_span(span), } } +} +impl FunctionAttribute { #[inline] pub const fn visibility(&self) -> Option { match self { diff --git a/crates/syn-solidity/src/attribute/mod.rs b/crates/syn-solidity/src/attribute/mod.rs index 6e948d4ac..1502608c0 100644 --- a/crates/syn-solidity/src/attribute/mod.rs +++ b/crates/syn-solidity/src/attribute/mod.rs @@ -1,5 +1,5 @@ -use super::{kw, utils::DebugPunctuated, SolPath}; -use proc_macro2::{Span, TokenStream}; +use crate::{kw, utils::DebugPunctuated, Expr, SolPath, Spanned}; +use proc_macro2::Span; use std::{ fmt, hash::{Hash, Hasher}, @@ -21,11 +21,8 @@ pub use variable::{VariableAttribute, VariableAttributes}; kw_enum! { /// A storage location. pub enum Storage { - /// `memory` Memory(kw::memory), - /// `storage` Storage(kw::storage), - /// `calldata` Calldata(kw::calldata), } } @@ -33,13 +30,9 @@ kw_enum! { kw_enum! { /// A visibility attribute. pub enum Visibility { - /// `external` External(kw::external), - /// `public` Public(kw::public), - /// `internal` Internal(kw::internal), - /// `private` Private(kw::private), } } @@ -47,13 +40,9 @@ kw_enum! { kw_enum! { /// A mutability attribute. pub enum Mutability { - /// `pure` Pure(kw::pure), - /// `view` View(kw::view), - /// `constant` Constant(kw::constant), - /// `payable` Payable(kw::payable), } } @@ -61,7 +50,7 @@ kw_enum! { /// The `override` attribute. #[derive(Clone, PartialEq, Eq, Hash)] pub struct Override { - pub override_token: kw::Override, + pub override_token: Token![override], pub paren_token: Option, pub paths: Punctuated, } @@ -112,15 +101,15 @@ impl Parse for Override { } } -impl Override { - pub fn span(&self) -> Span { +impl Spanned for Override { + fn span(&self) -> Span { let span = self.override_token.span; self.paren_token .and_then(|paren_token| span.join(paren_token.span.join())) .unwrap_or(span) } - pub fn set_span(&mut self, span: Span) { + fn set_span(&mut self, span: Span) { self.override_token.span = span; if let Some(paren_token) = &mut self.paren_token { *paren_token = Paren(span); @@ -133,25 +122,7 @@ impl Override { pub struct Modifier { pub name: SolPath, pub paren_token: Option, - // TODO: Expr - pub arguments: Punctuated, -} - -impl fmt::Display for Modifier { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.name.fmt(f)?; - if self.paren_token.is_some() { - f.write_str("(")?; - for (i, arg) in self.arguments.iter().enumerate() { - if i > 0 { - f.write_str(", ")?; - } - arg.fmt(f)?; - } - f.write_str(")")?; - } - Ok(()) - } + pub arguments: Punctuated, } impl PartialEq for Modifier { @@ -177,13 +148,31 @@ impl fmt::Debug for Modifier { } } +impl fmt::Display for Modifier { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.name.fmt(f)?; + if self.paren_token.is_some() { + f.write_str("(")?; + for (i, arg) in self.arguments.iter().enumerate() { + if i > 0 { + f.write_str(", ")?; + } + // TODO: impl fmt::Display for Expr + write!(f, "{arg:?}")?; + } + f.write_str(")")?; + } + Ok(()) + } +} + impl Parse for Modifier { fn parse(input: ParseStream<'_>) -> Result { let name = input.parse()?; let this = if input.peek(Paren) { let content; let paren_token = parenthesized!(content in input); - let arguments = content.parse_terminated(TokenStream::parse, Token![,])?; + let arguments = content.parse_terminated(Expr::parse, Token![,])?; Self { name, paren_token: Some(paren_token), @@ -200,15 +189,15 @@ impl Parse for Modifier { } } -impl Modifier { - pub fn span(&self) -> Span { +impl Spanned for Modifier { + fn span(&self) -> Span { let span = self.name.span(); self.paren_token .and_then(|paren_token| span.join(paren_token.span.join())) .unwrap_or(span) } - pub fn set_span(&mut self, span: Span) { + fn set_span(&mut self, span: Span) { self.name.set_span(span); if let Some(paren_token) = &mut self.paren_token { *paren_token = Paren(span); diff --git a/crates/syn-solidity/src/attribute/variable.rs b/crates/syn-solidity/src/attribute/variable.rs index 6f7b878e6..43514e920 100644 --- a/crates/syn-solidity/src/attribute/variable.rs +++ b/crates/syn-solidity/src/attribute/variable.rs @@ -1,23 +1,22 @@ -use super::{kw, Override, SolPath, Visibility}; +use crate::{kw, Override, SolPath, Spanned, Visibility}; use proc_macro2::Span; use std::{ - collections::HashSet, fmt, hash::{Hash, Hasher}, mem, }; use syn::{ parse::{Parse, ParseStream}, - Error, Result, + Error, Result, Token, }; /// A list of unique variable attributes. #[derive(Clone, Debug)] -pub struct VariableAttributes(pub HashSet); +pub struct VariableAttributes(pub Vec); impl Parse for VariableAttributes { fn parse(input: ParseStream<'_>) -> Result { - let mut attributes = HashSet::new(); + let mut attributes = Vec::new(); while let Ok(attribute) = input.parse::() { let error = |prev: &VariableAttribute| { let mut e = Error::new(attribute.span(), "duplicate attribute"); @@ -28,15 +27,17 @@ impl Parse for VariableAttributes { // Only one of: `constant`, `immutable` match attribute { VariableAttribute::Constant(_) => { - if let Some(prev) = - attributes.get(&VariableAttribute::Immutable(Default::default())) + if let Some(prev) = attributes + .iter() + .find(|a| matches!(a, VariableAttribute::Immutable(_))) { return Err(error(prev)) } } VariableAttribute::Immutable(_) => { - if let Some(prev) = - attributes.get(&VariableAttribute::Constant(Default::default())) + if let Some(prev) = attributes + .iter() + .find(|a| matches!(a, VariableAttribute::Constant(_))) { return Err(error(prev)) } @@ -44,15 +45,25 @@ impl Parse for VariableAttributes { _ => {} } - if let Some(prev) = attributes.get(&attribute) { + if let Some(prev) = attributes.iter().find(|a| **a == attribute) { return Err(error(prev)) } - attributes.insert(attribute); + attributes.push(attribute); } Ok(Self(attributes)) } } +impl Spanned for VariableAttributes { + fn span(&self) -> Span { + crate::utils::join_spans(&self.0) + } + + fn set_span(&mut self, span: Span) { + crate::utils::set_spans_clone(&mut self.0, span) + } +} + impl VariableAttributes { pub fn visibility(&self) -> Option { self.0.iter().find_map(VariableAttribute::visibility) @@ -132,7 +143,7 @@ impl Parse for VariableAttribute { input.parse().map(Self::Visibility) } else if lookahead.peek(kw::constant) { input.parse().map(Self::Constant) - } else if lookahead.peek(kw::Override) { + } else if lookahead.peek(Token![override]) { input.parse().map(Self::Override) } else if lookahead.peek(kw::immutable) { input.parse().map(Self::Immutable) @@ -142,8 +153,8 @@ impl Parse for VariableAttribute { } } -impl VariableAttribute { - pub fn span(&self) -> Span { +impl Spanned for VariableAttribute { + fn span(&self) -> Span { match self { Self::Visibility(v) => v.span(), Self::Constant(c) => c.span, @@ -152,7 +163,7 @@ impl VariableAttribute { } } - pub fn set_span(&mut self, span: Span) { + fn set_span(&mut self, span: Span) { match self { Self::Visibility(v) => v.set_span(span), Self::Constant(c) => c.span = span, @@ -160,7 +171,9 @@ impl VariableAttribute { Self::Immutable(i) => i.span = span, } } +} +impl VariableAttribute { #[inline] pub const fn visibility(&self) -> Option { match self { diff --git a/crates/syn-solidity/src/expr/args.rs b/crates/syn-solidity/src/expr/args.rs new file mode 100644 index 000000000..f8f6c8542 --- /dev/null +++ b/crates/syn-solidity/src/expr/args.rs @@ -0,0 +1,266 @@ +use crate::{ + kw, + utils::{DebugPunctuated, ParseNested}, + Expr, SolIdent, Spanned, +}; +use proc_macro2::Span; +use std::fmt; +use syn::{ + braced, parenthesized, + parse::{Parse, ParseStream}, + punctuated::Punctuated, + token::{Brace, Paren}, + Result, Token, +}; + +/// A function call expression: `foo(42)` or `foo({ bar: 42 })`. +#[derive(Clone, Debug)] +pub struct ExprCall { + pub expr: Box, + pub args: ArgList, +} + +impl ParseNested for ExprCall { + fn parse_nested(expr: Box, input: ParseStream<'_>) -> Result { + Ok(Self { + expr, + args: input.parse()?, + }) + } +} + +derive_parse!(ExprCall); + +impl Spanned for ExprCall { + fn span(&self) -> Span { + let span = self.expr.span(); + span.join(self.args.span()).unwrap_or(span) + } + + fn set_span(&mut self, span: Span) { + self.expr.set_span(span); + self.args.set_span(span); + } +} + +/// A `payable` expression: `payable(address(0x...))`. +#[derive(Clone)] +pub struct ExprPayable { + pub payable_token: kw::payable, + pub args: ArgList, +} + +impl fmt::Debug for ExprPayable { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("ExprPayable") + .field("args", &self.args) + .finish() + } +} + +impl From for ExprCall { + fn from(value: ExprPayable) -> Self { + Self { + expr: Box::new(Expr::Ident(SolIdent::new_spanned( + "payable", + value.payable_token.span, + ))), + args: value.args, + } + } +} + +impl Parse for ExprPayable { + fn parse(input: ParseStream<'_>) -> Result { + Ok(Self { + payable_token: input.parse()?, + args: input.parse()?, + }) + } +} + +impl Spanned for ExprPayable { + fn span(&self) -> Span { + let span = self.payable_token.span; + span.join(self.args.span()).unwrap_or(span) + } + + fn set_span(&mut self, span: Span) { + self.payable_token.span = span; + self.args.set_span(span); + } +} + +/// A list of named or unnamed arguments: `{ foo: 42, bar: 64 }` or `(42, 64)`. +/// +/// Solidity reference: +/// +#[derive(Clone)] +pub struct ArgList { + pub paren_token: Paren, + /// The list of arguments. Can be named or unnamed. + /// + /// When empty, this is an empty unnamed list. + pub list: ArgListImpl, +} + +impl fmt::Debug for ArgList { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("ArgList").field("list", &self.list).finish() + } +} + +impl Parse for ArgList { + fn parse(input: ParseStream<'_>) -> Result { + let content; + Ok(Self { + paren_token: parenthesized!(content in input), + list: content.parse()?, + }) + } +} + +impl Spanned for ArgList { + fn span(&self) -> Span { + self.paren_token.span.join() + } + + fn set_span(&mut self, span: Span) { + self.paren_token = Paren(span); + } +} + +/// A list of either unnamed or named arguments. +#[derive(Clone)] +pub enum ArgListImpl { + Unnamed(Punctuated), + Named(NamedArgList), +} + +impl fmt::Debug for ArgListImpl { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Unnamed(list) => f + .debug_tuple("Unnamed") + .field(DebugPunctuated::new(list)) + .finish(), + Self::Named(list) => f.debug_tuple("Named").field(list).finish(), + } + } +} + +impl Parse for ArgListImpl { + fn parse(input: ParseStream<'_>) -> Result { + if input.peek(Brace) { + input.parse().map(Self::Named) + } else { + input + .parse_terminated(Expr::parse, Token![,]) + .map(Self::Unnamed) + } + } +} + +/// Function call options: `foo.bar{ value: 1, gas: 2 }`. +#[derive(Clone, Debug)] +pub struct ExprCallOptions { + pub expr: Box, + pub args: NamedArgList, +} + +impl ParseNested for ExprCallOptions { + fn parse_nested(expr: Box, input: ParseStream<'_>) -> Result { + Ok(Self { + expr, + args: input.parse()?, + }) + } +} + +derive_parse!(ExprCallOptions); + +impl Spanned for ExprCallOptions { + fn span(&self) -> Span { + let span = self.expr.span(); + span.join(self.args.span()).unwrap_or(span) + } + + fn set_span(&mut self, span: Span) { + self.expr.set_span(span); + self.args.set_span(span); + } +} + +/// A named argument list: `{ foo: uint256(42), bar: true }`. +#[derive(Clone)] +pub struct NamedArgList { + pub brace_token: Brace, + pub list: Punctuated, +} + +impl fmt::Debug for NamedArgList { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("NamedArgList") + .field("list", DebugPunctuated::new(&self.list)) + .finish() + } +} + +impl Parse for NamedArgList { + fn parse(input: ParseStream<'_>) -> Result { + let content; + Ok(Self { + brace_token: braced!(content in input), + list: content.parse_terminated(NamedArg::parse, Token![,])?, + }) + } +} + +impl Spanned for NamedArgList { + fn span(&self) -> Span { + self.brace_token.span.join() + } + + fn set_span(&mut self, span: Span) { + self.brace_token = Brace(span); + } +} + +/// A named argument in an argument list: `foo: uint256(42)`. +#[derive(Clone)] +pub struct NamedArg { + pub name: SolIdent, + pub colon_token: Token![:], + pub arg: Expr, +} + +impl fmt::Debug for NamedArg { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("NamedArg") + .field("name", &self.name) + .field("arg", &self.arg) + .finish() + } +} + +impl Parse for NamedArg { + fn parse(input: ParseStream<'_>) -> Result { + Ok(Self { + name: input.parse()?, + colon_token: input.parse()?, + arg: input.parse()?, + }) + } +} + +impl Spanned for NamedArg { + fn span(&self) -> Span { + let span = self.name.span(); + span.join(self.arg.span()).unwrap_or(span) + } + + fn set_span(&mut self, span: Span) { + self.name.set_span(span); + self.arg.set_span(span); + } +} diff --git a/crates/syn-solidity/src/expr/array.rs b/crates/syn-solidity/src/expr/array.rs new file mode 100644 index 000000000..254ff7290 --- /dev/null +++ b/crates/syn-solidity/src/expr/array.rs @@ -0,0 +1,117 @@ +use crate::{ + utils::{DebugPunctuated, ParseNested}, + Expr, Spanned, +}; +use proc_macro2::Span; +use std::fmt; +use syn::{ + bracketed, + parse::{Parse, ParseStream}, + punctuated::Punctuated, + token::Bracket, + Result, Token, +}; + +/// An array literal expression: `[a, b, c, d]`. +#[derive(Clone)] +pub struct ExprArray { + pub bracket_token: Bracket, + pub elems: Punctuated, +} + +impl fmt::Debug for ExprArray { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("ExprArray") + .field("elems", DebugPunctuated::new(&self.elems)) + .finish() + } +} + +impl Parse for ExprArray { + fn parse(input: ParseStream<'_>) -> Result { + let content; + Ok(Self { + bracket_token: bracketed!(content in input), + elems: content.parse_terminated(Expr::parse, Token![,])?, + }) + } +} + +impl Spanned for ExprArray { + fn span(&self) -> Span { + self.bracket_token.span.join() + } + + fn set_span(&mut self, span: Span) { + self.bracket_token = Bracket(span); + } +} + +/// A square bracketed indexing expression: `vector[2]`. +#[derive(Clone)] +pub struct ExprIndex { + pub expr: Box, + pub bracket_token: Bracket, + pub start: Option>, + pub colon_token: Option, + pub end: Option>, +} + +impl fmt::Debug for ExprIndex { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("ExprIndex") + .field("expr", &self.expr) + .field("start", &self.start) + .field("end", &self.end) + .finish() + } +} + +impl ParseNested for ExprIndex { + fn parse_nested(expr: Box, input: ParseStream<'_>) -> Result { + let content; + let bracket_token = bracketed!(content in input); + let start = if content.is_empty() || content.peek(Token![:]) { + None + } else { + Some(content.parse()?) + }; + let colon_token = if content.is_empty() { + None + } else { + Some(content.parse()?) + }; + let end = if content.is_empty() || colon_token.is_none() { + None + } else { + Some(content.parse()?) + }; + Ok(Self { + expr, + bracket_token, + start, + colon_token, + end, + }) + } +} + +derive_parse!(ExprIndex); + +impl Spanned for ExprIndex { + fn span(&self) -> Span { + let span = self.expr.span(); + span.join(self.bracket_token.span.join()).unwrap_or(span) + } + + fn set_span(&mut self, span: Span) { + self.expr.set_span(span); + self.bracket_token = Bracket(span); + } +} + +impl ExprIndex { + pub fn is_range(&self) -> bool { + self.colon_token.is_some() + } +} diff --git a/crates/syn-solidity/src/expr/binary.rs b/crates/syn-solidity/src/expr/binary.rs new file mode 100644 index 000000000..5fc0fcd3e --- /dev/null +++ b/crates/syn-solidity/src/expr/binary.rs @@ -0,0 +1,80 @@ +use crate::{utils::ParseNested, Expr, Spanned}; +use proc_macro2::Span; +use syn::{ + parse::{Parse, ParseStream}, + Result, +}; + +/// A binary operation: `a + b`, `a += b`. +#[derive(Clone, Debug)] +pub struct ExprBinary { + pub left: Box, + pub op: BinOp, + pub right: Box, +} + +impl ParseNested for ExprBinary { + fn parse_nested(expr: Box, input: ParseStream<'_>) -> Result { + Ok(Self { + left: expr, + op: input.parse()?, + right: input.parse()?, + }) + } +} + +derive_parse!(ExprBinary); + +impl Spanned for ExprBinary { + fn span(&self) -> Span { + let span = self.left.span(); + span.join(self.right.span()).unwrap_or(span) + } + + fn set_span(&mut self, span: Span) { + self.left.set_span(span); + self.right.set_span(span); + } +} + +op_enum! { + /// A binary operator: `+`, `+=`, `&`. + pub enum BinOp { + Le(<=), + Ge(>=), + Eq(==), + Neq(!=), + Or(||), + And(&&), + + Assign(=), + AddAssign(+=), + SubAssign(-=), + MulAssign(*=), + DivAssign(/=), + RemAssign(%=), + BitAndAssign(&=), + BitOrAssign(|=), + BitXorAssign(^=), + SarAssign(>>>=) peek3, + ShlAssign(<<=), + ShrAssign(>>=), + + Sar(>>>) peek3, + Shr(>>), + Shl(<<), + BitAnd(&), + BitOr(|), + BitXor(^), + + Lt(<), + Gt(>), + + Add(+), + Sub(-), + Pow(**) peek2, + Mul(*), + Div(/), + Rem(%), + } +} diff --git a/crates/syn-solidity/src/expr/member.rs b/crates/syn-solidity/src/expr/member.rs new file mode 100644 index 000000000..a39412d3b --- /dev/null +++ b/crates/syn-solidity/src/expr/member.rs @@ -0,0 +1,54 @@ +use crate::{utils::ParseNested, Expr, Spanned}; +use proc_macro2::Span; +use std::fmt; +use syn::{ + parse::{Parse, ParseStream}, + Result, Token, +}; + +/// Access of a named member: `obj.k`. +#[derive(Clone)] +pub struct ExprMember { + pub expr: Box, + pub dot_token: Token![.], + pub member: Box, +} + +impl fmt::Debug for ExprMember { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("ExprMember") + .field("expr", &self.expr) + .field("member", &self.member) + .finish() + } +} + +impl ParseNested for ExprMember { + fn parse_nested(expr: Box, input: ParseStream<'_>) -> Result { + Ok(Self { + expr, + dot_token: input.parse()?, + member: input.parse()?, + }) + } +} + +derive_parse!(ExprMember); + +impl Spanned for ExprMember { + fn span(&self) -> Span { + self.expr + .span() + .join(self.member.span()) + .unwrap_or_else(|| { + self.dot_token + .span + .join(self.member.span()) + .unwrap_or_else(|| self.expr.span()) + }) + } + + fn set_span(&mut self, span: Span) { + self.expr.set_span(span); + } +} diff --git a/crates/syn-solidity/src/expr/mod.rs b/crates/syn-solidity/src/expr/mod.rs new file mode 100644 index 000000000..4d326e218 --- /dev/null +++ b/crates/syn-solidity/src/expr/mod.rs @@ -0,0 +1,273 @@ +use crate::{ + kw, utils::ParseNested, Lit, LitDenominated, SolIdent, Spanned, SubDenomination, Type, +}; +use proc_macro2::{Ident, Span}; +use std::fmt; +use syn::{ + ext::IdentExt, + parse::{Parse, ParseStream}, + token::{Brace, Bracket, Paren}, + Result, Token, +}; + +mod array; +pub use array::{ExprArray, ExprIndex}; + +mod args; +pub use args::{ + ArgList, ArgListImpl, ExprCall, ExprCallOptions, ExprPayable, NamedArg, NamedArgList, +}; + +mod binary; +pub use binary::{BinOp, ExprBinary}; + +mod member; +pub use member::ExprMember; + +mod ternary; +pub use ternary::ExprTernary; + +mod tuple; +pub use tuple::ExprTuple; + +mod r#type; +pub use r#type::{ExprNew, ExprTypeCall}; + +mod unary; +pub use unary::{ExprDelete, ExprPostfix, ExprUnary, PostUnOp, UnOp}; + +/// An expression. +/// +/// Solidity reference: +/// +#[derive(Clone)] +pub enum Expr { + /// An array literal expression: `[a, b, c, d]`. + Array(ExprArray), + + /// A binary operation: `a + b`, `a += b`. + Binary(ExprBinary), + + /// A function call expression: `foo(42)` or `foo({ bar: 42 })`. + Call(ExprCall), + + /// Function call options: `foo.bar{ value: 1, gas: 2 }`. + CallOptions(ExprCallOptions), + + /// A unary `delete` expression: `delete vector`. + Delete(ExprDelete), + + /// An identifier: `foo`. + Ident(SolIdent), + + /// A square bracketed indexing expression: `vector[2]`. + Index(ExprIndex), + + /// A literal: `hex"1234"`. + Lit(Lit), + + /// A number literal with a sub-denomination: `1 ether`. + LitDenominated(LitDenominated), + + /// Access of a named member: `obj.k`. + Member(ExprMember), + + /// A `new` expression: `new Contract`. + New(ExprNew), + + /// A `payable` expression: `payable(address(0x...))`. + Payable(ExprPayable), + + /// A postfix unary expression: `foo++`. + Postfix(ExprPostfix), + + /// A ternary (AKA conditional) expression: `foo ? bar : baz`. + Ternary(ExprTernary), + + /// A tuple expression: `(a, b, c, d)`. + Tuple(ExprTuple), + + /// A type name. + /// + /// Cannot be `Custom`, as custom identifiers are parsed as `Ident` instead. + Type(Type), + + /// A `type()` expression: `type(uint256)` + TypeCall(ExprTypeCall), + + /// A unary operation: `!x`, `-x`. + Unary(ExprUnary), +} + +impl fmt::Debug for Expr { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("Expr::")?; + match self { + Self::Array(expr) => expr.fmt(f), + Self::Binary(expr) => expr.fmt(f), + Self::Call(expr) => expr.fmt(f), + Self::CallOptions(expr) => expr.fmt(f), + Self::Delete(expr) => expr.fmt(f), + Self::Ident(ident) => ident.fmt(f), + Self::Index(expr) => expr.fmt(f), + Self::Lit(lit) => lit.fmt(f), + Self::LitDenominated(lit) => lit.fmt(f), + Self::Member(expr) => expr.fmt(f), + Self::New(expr) => expr.fmt(f), + Self::Payable(expr) => expr.fmt(f), + Self::Postfix(expr) => expr.fmt(f), + Self::Ternary(expr) => expr.fmt(f), + Self::Tuple(expr) => expr.fmt(f), + Self::Type(ty) => ty.fmt(f), + Self::TypeCall(expr) => expr.fmt(f), + Self::Unary(expr) => expr.fmt(f), + } + } +} + +impl Parse for Expr { + fn parse(input: ParseStream<'_>) -> Result { + // skip any attributes + let _ = input.call(syn::Attribute::parse_outer)?; + + debug!(" > Expr: {:?}", input.to_string()); + let mut expr = Self::parse_simple(input)?; + debug!(" < Expr: {expr:?}"); + loop { + let (new, cont) = Self::parse_nested(expr, input)?; + if cont { + debug!(" << Expr: {new:?}"); + expr = new; + } else { + return Ok(new) + } + } + } +} + +impl Spanned for Expr { + fn span(&self) -> Span { + match self { + Self::Array(expr) => expr.span(), + Self::Binary(expr) => expr.span(), + Self::Call(expr) => expr.span(), + Self::CallOptions(expr) => expr.span(), + Self::Delete(expr) => expr.span(), + Self::Ident(ident) => ident.span(), + Self::Index(expr) => expr.span(), + Self::Lit(lit) => lit.span(), + Self::LitDenominated(lit) => lit.span(), + Self::Member(expr) => expr.span(), + Self::New(expr) => expr.span(), + Self::Payable(expr) => expr.span(), + Self::Postfix(expr) => expr.span(), + Self::Ternary(expr) => expr.span(), + Self::Tuple(expr) => expr.span(), + Self::Type(ty) => ty.span(), + Self::TypeCall(expr) => expr.span(), + Self::Unary(expr) => expr.span(), + } + } + + fn set_span(&mut self, span: Span) { + match self { + Self::Array(expr) => expr.set_span(span), + Self::Binary(expr) => expr.set_span(span), + Self::Call(expr) => expr.set_span(span), + Self::CallOptions(expr) => expr.set_span(span), + Self::Delete(expr) => expr.set_span(span), + Self::Ident(ident) => ident.set_span(span), + Self::Index(expr) => expr.set_span(span), + Self::Lit(lit) => lit.set_span(span), + Self::LitDenominated(lit) => lit.set_span(span), + Self::Member(expr) => expr.set_span(span), + Self::New(expr) => expr.set_span(span), + Self::Payable(expr) => expr.set_span(span), + Self::Postfix(expr) => expr.set_span(span), + Self::Ternary(expr) => expr.set_span(span), + Self::Tuple(expr) => expr.set_span(span), + Self::Type(ty) => ty.set_span(span), + Self::TypeCall(expr) => expr.set_span(span), + Self::Unary(expr) => expr.set_span(span), + } + } +} + +impl Expr { + fn parse_simple(input: ParseStream<'_>) -> Result { + let lookahead = input.lookahead1(); + if lookahead.peek(Paren) { + input.parse().map(Self::Tuple) + } else if lookahead.peek(Bracket) { + input.parse().map(Self::Array) + } else if UnOp::peek(input, &lookahead) { + input.parse().map(Self::Unary) + } else if Lit::peek(&lookahead) { + match (input.parse()?, input.call(SubDenomination::parse_opt)?) { + (Lit::Number(number), Some(denom)) => { + Ok(Self::LitDenominated(LitDenominated { number, denom })) + } + (lit, None) => Ok(Self::Lit(lit)), + (_, Some(denom)) => Err(syn::Error::new( + denom.span(), + "unexpected subdenomination for literal", + )), + } + } else if lookahead.peek(kw::payable) { + input.parse().map(Self::Payable) + } else if lookahead.peek(Token![type]) { + input.parse().map(Self::TypeCall) + } else if lookahead.peek(kw::new) { + input.parse().map(Self::New) + } else if lookahead.peek(kw::delete) { + input.parse().map(Self::Delete) + } else if lookahead.peek(Ident::peek_any) { + let ident = input.call(Ident::parse_any)?; + match Type::parse_ident(ident.clone()) { + Ok(ty) if !ty.is_custom() => ty.parse_payable(input).map(Self::Type), + _ => Ok(Self::Ident(ident.into())), + } + } else { + Err(lookahead.error()) + } + } + + /// Parse an expression that starts with an expression. + /// + /// Returns `(ParseResult, continue_parsing)` + fn parse_nested(expr: Self, input: ParseStream<'_>) -> Result<(Self, bool)> { + macro_rules! parse { + (break) => { + Ok((expr, false)) + }; + + ($map:expr) => { + ParseNested::parse_nested(expr.into(), input).map(|e| ($map(e), true)) + }; + } + + let lookahead = input.lookahead1(); + if lookahead.peek(Bracket) { + parse!(Self::Index) + } else if lookahead.peek(Brace) { + // Special case: `try` stmt block + if input.peek2(kw::catch) { + parse!(break) + } else { + parse!(Self::CallOptions) + } + } else if lookahead.peek(Paren) { + parse!(Self::Call) + } else if lookahead.peek(Token![.]) { + parse!(Self::Member) + } else if lookahead.peek(Token![?]) { + parse!(Self::Ternary) + } else if PostUnOp::peek(input, &lookahead) { + parse!(Self::Postfix) + } else if BinOp::peek(input, &lookahead) { + parse!(Self::Binary) + } else { + parse!(break) + } + } +} diff --git a/crates/syn-solidity/src/expr/ternary.rs b/crates/syn-solidity/src/expr/ternary.rs new file mode 100644 index 000000000..8523590fa --- /dev/null +++ b/crates/syn-solidity/src/expr/ternary.rs @@ -0,0 +1,53 @@ +use crate::{utils::ParseNested, Expr, Spanned}; +use proc_macro2::Span; +use std::fmt; +use syn::{ + parse::{Parse, ParseStream}, + Result, Token, +}; + +/// A ternary (AKA conditional) expression: `foo ? bar : baz`. +#[derive(Clone)] +pub struct ExprTernary { + pub cond: Box, + pub question_token: Token![?], + pub if_true: Box, + pub colon_token: Token![:], + pub if_false: Box, +} + +impl fmt::Debug for ExprTernary { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("ExprTernary") + .field("cond", &self.cond) + .field("if_true", &self.if_true) + .field("if_false", &self.if_false) + .finish() + } +} + +impl ParseNested for ExprTernary { + fn parse_nested(expr: Box, input: ParseStream<'_>) -> Result { + Ok(Self { + cond: expr, + question_token: input.parse()?, + if_true: input.parse()?, + colon_token: input.parse()?, + if_false: input.parse()?, + }) + } +} + +derive_parse!(ExprTernary); + +impl Spanned for ExprTernary { + fn span(&self) -> Span { + let span = self.cond.span(); + span.join(self.if_false.span()).unwrap_or(span) + } + + fn set_span(&mut self, span: Span) { + self.cond.set_span(span); + self.if_false.set_span(span); + } +} diff --git a/crates/syn-solidity/src/expr/tuple.rs b/crates/syn-solidity/src/expr/tuple.rs new file mode 100644 index 000000000..e3637c4bf --- /dev/null +++ b/crates/syn-solidity/src/expr/tuple.rs @@ -0,0 +1,45 @@ +use crate::{utils::DebugPunctuated, Expr, Spanned}; +use proc_macro2::Span; +use std::fmt; +use syn::{ + parenthesized, + parse::{Parse, ParseStream}, + punctuated::Punctuated, + token::Paren, + Result, Token, +}; + +/// A tuple expression: `(a, b, c, d)`. +#[derive(Clone)] +pub struct ExprTuple { + pub paren_token: Paren, + pub elems: Punctuated, +} + +impl fmt::Debug for ExprTuple { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("ExprTuple") + .field("elems", DebugPunctuated::new(&self.elems)) + .finish() + } +} + +impl Parse for ExprTuple { + fn parse(input: ParseStream<'_>) -> Result { + let content; + Ok(Self { + paren_token: parenthesized!(content in input), + elems: content.parse_terminated(Expr::parse, Token![,])?, + }) + } +} + +impl Spanned for ExprTuple { + fn span(&self) -> Span { + self.paren_token.span.join() + } + + fn set_span(&mut self, span: Span) { + self.paren_token = Paren(span); + } +} diff --git a/crates/syn-solidity/src/expr/type.rs b/crates/syn-solidity/src/expr/type.rs new file mode 100644 index 000000000..01e5f94f4 --- /dev/null +++ b/crates/syn-solidity/src/expr/type.rs @@ -0,0 +1,84 @@ +use crate::{kw, Spanned, Type}; +use proc_macro2::Span; +use std::fmt; +use syn::{ + parenthesized, + parse::{Parse, ParseStream}, + token::Paren, + Result, Token, +}; + +/// A `type()` expression: `type(uint256)` +#[derive(Clone)] +pub struct ExprTypeCall { + pub type_token: Token![type], + pub paren_token: Paren, + pub ty: Type, +} + +impl fmt::Debug for ExprTypeCall { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("ExprTypeCall") + .field("ty", &self.ty) + .finish() + } +} + +impl Parse for ExprTypeCall { + fn parse(input: ParseStream<'_>) -> Result { + let content; + Ok(Self { + type_token: input.parse()?, + paren_token: parenthesized!(content in input), + ty: content.parse()?, + }) + } +} + +impl Spanned for ExprTypeCall { + fn span(&self) -> Span { + let span = self.type_token.span; + span.join(self.paren_token.span.join()).unwrap_or(span) + } + + fn set_span(&mut self, span: Span) { + self.type_token.span = span; + self.paren_token = Paren(span); + } +} + +/// A `new` expression: `new Contract`. +/// +/// i.e. a contract creation or the allocation of a dynamic memory array. +#[derive(Clone)] +pub struct ExprNew { + pub new_token: kw::new, + pub ty: Type, +} + +impl fmt::Debug for ExprNew { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("ExprNew").field("ty", &self.ty).finish() + } +} + +impl Parse for ExprNew { + fn parse(input: ParseStream<'_>) -> Result { + Ok(Self { + new_token: input.parse()?, + ty: input.parse()?, + }) + } +} + +impl Spanned for ExprNew { + fn span(&self) -> Span { + let span = self.new_token.span; + span.join(self.ty.span()).unwrap_or(span) + } + + fn set_span(&mut self, span: Span) { + self.new_token.span = span; + self.ty.set_span(span); + } +} diff --git a/crates/syn-solidity/src/expr/unary.rs b/crates/syn-solidity/src/expr/unary.rs new file mode 100644 index 000000000..ca7fa339a --- /dev/null +++ b/crates/syn-solidity/src/expr/unary.rs @@ -0,0 +1,120 @@ +use crate::{kw, utils::ParseNested, Expr, Spanned}; +use proc_macro2::Span; +use std::fmt; +use syn::{ + parse::{Parse, ParseStream}, + Result, +}; + +/// A unary operation: `!x`, `-x`. +#[derive(Clone, Debug)] +pub struct ExprUnary { + pub op: UnOp, + pub expr: Box, +} + +impl Parse for ExprUnary { + fn parse(input: ParseStream<'_>) -> Result { + Ok(Self { + op: input.parse()?, + expr: input.parse()?, + }) + } +} + +impl Spanned for ExprUnary { + fn span(&self) -> Span { + let span = self.op.span(); + span.join(self.expr.span()).unwrap_or(span) + } + + fn set_span(&mut self, span: Span) { + self.op.set_span(span); + self.expr.set_span(span); + } +} + +/// A unary `delete` expression: `delete vector`. +#[derive(Clone)] +pub struct ExprDelete { + pub delete_token: kw::delete, + pub expr: Box, +} + +impl fmt::Debug for ExprDelete { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("ExprDelete") + .field("expr", &self.expr) + .finish() + } +} + +impl Parse for ExprDelete { + fn parse(input: ParseStream<'_>) -> Result { + Ok(Self { + delete_token: input.parse()?, + expr: input.parse()?, + }) + } +} + +impl Spanned for ExprDelete { + fn span(&self) -> Span { + let span = self.delete_token.span; + span.join(self.expr.span()).unwrap_or(span) + } + + fn set_span(&mut self, span: Span) { + self.delete_token.span = span; + self.expr.set_span(span); + } +} + +/// A postfix unary expression: `foo++`. +#[derive(Clone, Debug)] +pub struct ExprPostfix { + pub expr: Box, + pub op: PostUnOp, +} + +impl ParseNested for ExprPostfix { + fn parse_nested(expr: Box, input: ParseStream<'_>) -> Result { + Ok(Self { + expr, + op: input.parse()?, + }) + } +} + +derive_parse!(ExprPostfix); + +impl Spanned for ExprPostfix { + fn span(&self) -> Span { + let span = self.op.span(); + span.join(self.expr.span()).unwrap_or(span) + } + + fn set_span(&mut self, span: Span) { + self.op.set_span(span); + self.expr.set_span(span); + } +} + +op_enum! { + /// Unary operators. + pub enum UnOp { + Increment(++) peek2, + Decrement(--) peek2, + Not(!), + BitNot(~), + Neg(-), + } +} + +op_enum! { + /// Postfix unary operators. + pub enum PostUnOp { + Increment(++) peek2, + Decrement(--) peek2, + } +} diff --git a/crates/syn-solidity/src/file.rs b/crates/syn-solidity/src/file.rs index c716fd5d2..477f63d5b 100644 --- a/crates/syn-solidity/src/file.rs +++ b/crates/syn-solidity/src/file.rs @@ -1,11 +1,12 @@ -use super::Item; +use crate::{Item, Spanned}; +use proc_macro2::Span; use syn::{ parse::{Parse, ParseStream}, Attribute, Result, }; /// A Solidity file. The root of the AST. -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct File { /// The inner attributes of the file. pub attrs: Vec, @@ -17,13 +18,21 @@ impl Parse for File { fn parse(input: ParseStream<'_>) -> Result { let attrs = input.call(Attribute::parse_inner)?; let mut items = Vec::new(); - while !input.is_empty() { + let mut first = true; + while first || !input.is_empty() { + first = false; items.push(input.parse()?); } - if items.is_empty() { - Err(input.parse::().unwrap_err()) - } else { - Ok(Self { attrs, items }) - } + Ok(Self { attrs, items }) + } +} + +impl Spanned for File { + fn span(&self) -> Span { + crate::utils::join_spans(&self.items) + } + + fn set_span(&mut self, span: Span) { + crate::utils::set_spans(&mut self.items, span); } } diff --git a/crates/syn-solidity/src/ident/mod.rs b/crates/syn-solidity/src/ident/mod.rs index c2f8b39fe..a598ad98d 100644 --- a/crates/syn-solidity/src/ident/mod.rs +++ b/crates/syn-solidity/src/ident/mod.rs @@ -1,3 +1,4 @@ +use crate::Spanned; use proc_macro2::{Ident, Span}; use quote::ToTokens; use std::fmt; @@ -21,7 +22,7 @@ impl quote::IdentFragment for SolIdent { } fn span(&self) -> Option { - Some(self.span()) + Some(self.0.span()) } } @@ -43,9 +44,28 @@ impl> PartialEq for SolIdent { } } +impl From for SolIdent { + fn from(value: Ident) -> Self { + Self(value) + } +} + +impl From for Ident { + fn from(value: SolIdent) -> Self { + value.0 + } +} + +impl From<&str> for SolIdent { + fn from(value: &str) -> Self { + Self::new(value) + } +} + impl Parse for SolIdent { fn parse(input: ParseStream<'_>) -> Result { - input.call(Ident::parse_any).map(Self) + // TODO: Deny Solidity keywords + Self::parse_any(input) } } @@ -55,15 +75,13 @@ impl ToTokens for SolIdent { } } -impl From for SolIdent { - fn from(value: Ident) -> Self { - Self(value) +impl Spanned for SolIdent { + fn span(&self) -> Span { + self.0.span() } -} -impl From for Ident { - fn from(value: SolIdent) -> Self { - value.0 + fn set_span(&mut self, span: Span) { + self.0.set_span(span); } } @@ -76,14 +94,6 @@ impl SolIdent { Self(Ident::new(s, span)) } - pub fn span(&self) -> Span { - self.0.span() - } - - pub fn set_span(&mut self, span: Span) { - self.0.set_span(span); - } - /// Returns the identifier as a string, without the `r#` prefix if present. pub fn as_string(&self) -> String { let mut s = self.0.to_string(); @@ -93,7 +103,12 @@ impl SolIdent { s } - /// See `[Ident::peek_any]`. + /// Parses any identifier including keywords. + pub fn parse_any(input: ParseStream<'_>) -> Result { + input.call(Ident::parse_any).map(Self) + } + + /// Peeks any identifier including keywords. pub fn peek_any(input: ParseStream<'_>) -> bool { input.peek(Ident::peek_any) } diff --git a/crates/syn-solidity/src/ident/path.rs b/crates/syn-solidity/src/ident/path.rs index e95705786..f09c96714 100644 --- a/crates/syn-solidity/src/ident/path.rs +++ b/crates/syn-solidity/src/ident/path.rs @@ -1,4 +1,4 @@ -use super::SolIdent; +use crate::{SolIdent, Spanned}; use proc_macro2::{Ident, Span}; use std::{ fmt, @@ -21,12 +21,6 @@ macro_rules! sol_path { $(path.push($crate::SolIdent::from($e));)+ path }}; - - ($($id:ident).+) => {{ - let mut path = $crate::SolPath::new(); - $(path.push($crate::SolIdent::new(stringify!($id))));+ - path - }}; } /// A list of identifiers, separated by dots. @@ -98,6 +92,16 @@ impl Parse for SolPath { } } +impl Spanned for SolPath { + fn span(&self) -> Span { + crate::utils::join_spans(&self.0) + } + + fn set_span(&mut self, span: Span) { + crate::utils::set_spans(&mut self.0, span); + } +} + impl SolPath { pub const fn new() -> Self { Self(Punctuated::new()) @@ -127,21 +131,4 @@ impl SolPath { pub fn last_mut(&mut self) -> &mut SolIdent { self.0.last_mut().unwrap() } - - pub fn span(&self) -> Span { - let Some(first) = self.0.first() else { - return Span::call_site() - }; - let span = first.span(); - self.0 - .last() - .and_then(|last| span.join(last.span())) - .unwrap_or(span) - } - - pub fn set_span(&mut self, span: Span) { - for ident in self.0.iter_mut() { - ident.set_span(span); - } - } } diff --git a/crates/syn-solidity/src/item/contract.rs b/crates/syn-solidity/src/item/contract.rs index 706a68c5a..dcd7e8b24 100644 --- a/crates/syn-solidity/src/item/contract.rs +++ b/crates/syn-solidity/src/item/contract.rs @@ -1,5 +1,4 @@ -use super::Item; -use crate::{kw, utils::DebugPunctuated, Modifier, SolIdent}; +use crate::{kw, utils::DebugPunctuated, Item, Modifier, SolIdent, Spanned}; use proc_macro2::Span; use std::{cmp::Ordering, fmt}; use syn::{ @@ -11,7 +10,7 @@ use syn::{ }; /// A contract, abstract contract, interface, or library definition: -/// `contract Foo is Bar("foo"), Baz { ... }` +/// `contract Foo is Bar("foo"), Baz { ... }`. /// /// Solidity reference: /// @@ -27,7 +26,7 @@ pub struct ItemContract { impl fmt::Debug for ItemContract { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("Contract") + f.debug_struct("ItemContract") .field("attrs", &self.attrs) .field("kind", &self.kind) .field("name", &self.name) @@ -74,15 +73,17 @@ impl Parse for ItemContract { } } -impl ItemContract { - pub fn span(&self) -> Span { +impl Spanned for ItemContract { + fn span(&self) -> Span { self.name.span() } - pub fn set_span(&mut self, span: Span) { + fn set_span(&mut self, span: Span) { self.name.set_span(span); } +} +impl ItemContract { /// Returns true if `self` is an abstract contract. pub fn is_abstract_contract(&self) -> bool { self.kind.is_abstract_contract() @@ -107,7 +108,7 @@ impl ItemContract { /// The kind of contract. #[derive(Clone, Copy, PartialEq, Eq, Hash)] pub enum ContractKind { - AbstractContract(kw::Abstract, kw::contract), + AbstractContract(Token![abstract], kw::contract), Contract(kw::contract), Interface(kw::interface), Library(kw::library), @@ -140,7 +141,7 @@ impl Ord for ContractKind { impl Parse for ContractKind { fn parse(input: ParseStream<'_>) -> Result { let lookahead = input.lookahead1(); - if lookahead.peek(kw::Abstract) { + if lookahead.peek(Token![abstract]) { Ok(Self::AbstractContract(input.parse()?, input.parse()?)) } else if lookahead.peek(kw::contract) { input.parse().map(Self::Contract) @@ -154,15 +155,8 @@ impl Parse for ContractKind { } } -impl ContractKind { - pub fn peek(lookahead: &Lookahead1<'_>) -> bool { - lookahead.peek(kw::Abstract) - || lookahead.peek(kw::contract) - || lookahead.peek(kw::interface) - || lookahead.peek(kw::library) - } - - pub fn span(self) -> Span { +impl Spanned for ContractKind { + fn span(&self) -> Span { match self { Self::AbstractContract(kw_abstract, kw_contract) => { let span = kw_abstract.span; @@ -174,7 +168,7 @@ impl ContractKind { } } - pub fn set_span(&mut self, span: Span) { + fn set_span(&mut self, span: Span) { match self { Self::AbstractContract(kw_abstract, kw_contract) => { kw_abstract.span = span; @@ -185,6 +179,15 @@ impl ContractKind { Self::Library(kw) => kw.span = span, } } +} + +impl ContractKind { + pub fn peek(lookahead: &Lookahead1<'_>) -> bool { + lookahead.peek(Token![abstract]) + || lookahead.peek(kw::contract) + || lookahead.peek(kw::interface) + || lookahead.peek(kw::library) + } /// Returns true if `self` is an abstract contract. pub fn is_abstract_contract(self) -> bool { @@ -278,8 +281,8 @@ impl Parse for Inheritance { } } -impl Inheritance { - pub fn span(&self) -> Span { +impl Spanned for Inheritance { + fn span(&self) -> Span { let span = self.is_token.span; self.inheritance .last() @@ -287,7 +290,7 @@ impl Inheritance { .unwrap_or(span) } - pub fn set_span(&mut self, span: Span) { + fn set_span(&mut self, span: Span) { self.is_token.span = span; } } diff --git a/crates/syn-solidity/src/item/enum.rs b/crates/syn-solidity/src/item/enum.rs index d9dc14478..79583a4bd 100644 --- a/crates/syn-solidity/src/item/enum.rs +++ b/crates/syn-solidity/src/item/enum.rs @@ -1,4 +1,4 @@ -use crate::{utils::DebugPunctuated, SolIdent, Type}; +use crate::{utils::DebugPunctuated, SolIdent, Spanned, Type}; use proc_macro2::Span; use std::{fmt, num::NonZeroU16}; use syn::{ @@ -9,7 +9,7 @@ use syn::{ Attribute, Result, Token, }; -/// An enum definition: `enum Foo { A, B, C }` +/// An enum definition: `enum Foo { A, B, C }`. /// /// Solidity reference: /// @@ -24,7 +24,7 @@ pub struct ItemEnum { impl fmt::Debug for ItemEnum { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("Enum") + f.debug_struct("ItemEnum") .field("attrs", &self.attrs) .field("name", &self.name) .field("variants", DebugPunctuated::new(&self.variants)) @@ -45,15 +45,17 @@ impl Parse for ItemEnum { } } -impl ItemEnum { - pub fn span(&self) -> Span { +impl Spanned for ItemEnum { + fn span(&self) -> Span { self.name.span() } - pub fn set_span(&mut self, span: Span) { + fn set_span(&mut self, span: Span) { self.name.set_span(span); } +} +impl ItemEnum { pub fn as_type(&self) -> Type { Type::Uint(self.span(), Some(NonZeroU16::new(8).unwrap())) } diff --git a/crates/syn-solidity/src/item/error.rs b/crates/syn-solidity/src/item/error.rs index 4e804b5ae..3cb3576e3 100644 --- a/crates/syn-solidity/src/item/error.rs +++ b/crates/syn-solidity/src/item/error.rs @@ -1,4 +1,4 @@ -use crate::{kw, ParameterList, SolIdent, Type}; +use crate::{kw, ParameterList, SolIdent, Spanned, Type}; use proc_macro2::Span; use std::fmt; use syn::{ @@ -8,7 +8,7 @@ use syn::{ Attribute, Result, Token, }; -/// An error definition: `error Foo(uint256 a, uint256 b);` +/// An error definition: `error Foo(uint256 a, uint256 b);`. /// /// Solidity reference: /// @@ -24,7 +24,7 @@ pub struct ItemError { impl fmt::Debug for ItemError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("Error") + f.debug_struct("ItemError") .field("attrs", &self.attrs) .field("name", &self.name) .field("fields", &self.parameters) @@ -46,15 +46,17 @@ impl Parse for ItemError { } } -impl ItemError { - pub fn span(&self) -> Span { +impl Spanned for ItemError { + fn span(&self) -> Span { self.name.span() } - pub fn set_span(&mut self, span: Span) { + fn set_span(&mut self, span: Span) { self.name.set_span(span); } +} +impl ItemError { pub fn as_type(&self) -> Type { let mut ty = Type::Tuple(self.parameters.types().cloned().collect()); ty.set_span(self.span()); diff --git a/crates/syn-solidity/src/item/event.rs b/crates/syn-solidity/src/item/event.rs index 42234fcc8..d9fa4090a 100644 --- a/crates/syn-solidity/src/item/event.rs +++ b/crates/syn-solidity/src/item/event.rs @@ -1,4 +1,6 @@ -use crate::{kw, utils::DebugPunctuated, ParameterList, SolIdent, Type, VariableDeclaration}; +use crate::{ + kw, utils::DebugPunctuated, ParameterList, SolIdent, Spanned, Type, VariableDeclaration, +}; use proc_macro2::Span; use std::fmt; use syn::{ @@ -26,7 +28,7 @@ impl fmt::Debug for ItemEvent { .field("attrs", &self.attrs) .field("name", &self.name) .field("arguments", DebugPunctuated::new(&self.parameters)) - .field("anonymous", &self.anonymous.is_some()) + .field("anonymous", &self.is_anonymous()) .finish() } } @@ -46,15 +48,17 @@ impl Parse for ItemEvent { } } -impl ItemEvent { - pub fn span(&self) -> Span { +impl Spanned for ItemEvent { + fn span(&self) -> Span { self.name.span() } - pub fn set_span(&mut self, span: Span) { + fn set_span(&mut self, span: Span) { self.name.set_span(span); } +} +impl ItemEvent { /// Returns `true` if the event is anonymous. #[inline] pub const fn is_anonymous(&self) -> bool { @@ -159,9 +163,9 @@ impl Parse for EventParameter { } } -impl EventParameter { +impl Spanned for EventParameter { /// Get the span of the event parameter - pub fn span(&self) -> Span { + fn span(&self) -> Span { let span = self.ty.span(); self.name .as_ref() @@ -170,7 +174,7 @@ impl EventParameter { } /// Sets the span of the event parameter. - pub fn set_span(&mut self, span: Span) { + fn set_span(&mut self, span: Span) { self.ty.set_span(span); if let Some(kw) = &mut self.indexed { kw.span = span; @@ -179,7 +183,9 @@ impl EventParameter { name.set_span(span); } } +} +impl EventParameter { /// Convert to a parameter declaration. pub fn as_param(&self) -> VariableDeclaration { VariableDeclaration { diff --git a/crates/syn-solidity/src/item/function.rs b/crates/syn-solidity/src/item/function.rs index 7a9647509..7fdf8087c 100644 --- a/crates/syn-solidity/src/item/function.rs +++ b/crates/syn-solidity/src/item/function.rs @@ -1,5 +1,6 @@ use crate::{ - kw, Block, FunctionAttributes, ParameterList, Parameters, SolIdent, Type, VariableDefinition, + kw, Block, FunctionAttributes, ParameterList, Parameters, SolIdent, Spanned, Stmt, Type, + VariableDefinition, }; use proc_macro2::Span; use std::{ @@ -14,7 +15,7 @@ use syn::{ }; /// A function, constructor, fallback, receive, or modifier definition: -/// `function helloWorld() external pure returns(string memory);` +/// `function helloWorld() external pure returns(string memory);`. /// /// Solidity reference: /// @@ -35,13 +36,14 @@ pub struct ItemFunction { impl fmt::Debug for ItemFunction { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("Function") + f.debug_struct("ItemFunction") .field("attrs", &self.attrs) .field("kind", &self.kind) .field("name", &self.name) .field("arguments", &self.arguments) .field("attributes", &self.attributes) .field("returns", &self.returns) + .field("body", &self.body) .finish() } } @@ -62,6 +64,23 @@ impl Parse for ItemFunction { } } +impl Spanned for ItemFunction { + fn span(&self) -> Span { + if let Some(name) = &self.name { + name.span() + } else { + self.kind.span() + } + } + + fn set_span(&mut self, span: Span) { + self.kind.set_span(span); + if let Some(name) = &mut self.name { + name.set_span(span); + } + } +} + impl ItemFunction { pub fn new(kind: FunctionKind, name: Option) -> Self { let span = name @@ -86,7 +105,7 @@ impl ItemFunction { let span = name.span(); let kind = FunctionKind::new_function(span); - let mut function = ItemFunction::new(kind, Some(name)); + let mut function = Self::new(kind, Some(name)); let mut returns = ParameterList::new(); returns.push(var.as_declaration()); @@ -101,21 +120,6 @@ impl ItemFunction { function } - pub fn span(&self) -> Span { - if let Some(name) = &self.name { - name.span() - } else { - self.kind.span() - } - } - - pub fn set_span(&mut self, span: Span) { - self.kind.set_span(span); - if let Some(name) = &mut self.name { - name.set_span(span); - } - } - /// Returns the name of the function. /// /// # Panics @@ -159,20 +163,38 @@ impl ItemFunction { ) }) } + + /// Returns a reference to the function's body, if any. + pub fn body(&self) -> Option<&[Stmt]> { + match &self.body { + FunctionBody::Block(block) => Some(&block.stmts), + _ => None, + } + } + + /// Returns a mutable reference to the function's body, if any. + pub fn body_mut(&mut self) -> Option<&mut Vec> { + match &mut self.body { + FunctionBody::Block(block) => Some(&mut block.stmts), + _ => None, + } + } + + pub fn into_body(self) -> std::result::Result, Self> { + match self.body { + FunctionBody::Block(block) => Ok(block.stmts), + _ => Err(self), + } + } } kw_enum! { /// The kind of function. pub enum FunctionKind { - /// `constructor` Constructor(kw::constructor), - /// `function` Function(kw::function), - /// `fallback` Fallback(kw::fallback), - /// `receive` Receive(kw::receive), - /// `modifier` Modifier(kw::modifier), } } @@ -238,6 +260,18 @@ impl Parse for Returns { } } +impl Spanned for Returns { + fn span(&self) -> Span { + let span = self.returns_token.span; + span.join(self.paren_token.span.join()).unwrap_or(span) + } + + fn set_span(&mut self, span: Span) { + self.returns_token.span = span; + self.paren_token = Paren(span); + } +} + impl Returns { pub fn new(span: Span, returns: ParameterList) -> Self { Self { @@ -247,16 +281,6 @@ impl Returns { } } - pub fn span(&self) -> Span { - let span = self.returns_token.span; - span.join(self.paren_token.span.join()).unwrap_or(span) - } - - pub fn set_span(&mut self, span: Span) { - self.returns_token.span = span; - self.paren_token = Paren(span); - } - pub fn parse_opt(input: ParseStream<'_>) -> Result> { if input.peek(kw::returns) { input.parse().map(Some) @@ -266,7 +290,8 @@ impl Returns { } } -#[derive(Clone, Debug)] +/// The body of a function. +#[derive(Clone)] pub enum FunctionBody { /// A function body delimited by curly braces. Block(Block), @@ -274,6 +299,16 @@ pub enum FunctionBody { Empty(Token![;]), } +impl fmt::Debug for FunctionBody { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("FunctionBody::")?; + match self { + Self::Block(block) => block.fmt(f), + Self::Empty(_) => f.write_str("Empty"), + } + } +} + impl Parse for FunctionBody { fn parse(input: ParseStream<'_>) -> Result { let lookahead = input.lookahead1(); diff --git a/crates/syn-solidity/src/item/import.rs b/crates/syn-solidity/src/item/import.rs index aa8032171..627c2fbda 100644 --- a/crates/syn-solidity/src/item/import.rs +++ b/crates/syn-solidity/src/item/import.rs @@ -1,4 +1,4 @@ -use crate::{kw, LitStr, SolIdent}; +use crate::{kw, LitStr, SolIdent, Spanned}; use proc_macro2::Span; use std::fmt; use syn::{ @@ -9,17 +9,25 @@ use syn::{ Result, Token, }; -/// An import directive: `import "foo.sol";` +/// An import directive: `import "foo.sol";`. /// /// Solidity reference: /// -#[derive(Clone, Debug)] +#[derive(Clone)] pub struct ImportDirective { pub import_token: kw::import, pub path: ImportPath, pub semi_token: Token![;], } +impl fmt::Debug for ImportDirective { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("ImportDirective") + .field("path", &self.path) + .finish() + } +} + impl Parse for ImportDirective { fn parse(input: ParseStream<'_>) -> Result { Ok(Self { @@ -30,13 +38,13 @@ impl Parse for ImportDirective { } } -impl ImportDirective { - pub fn span(&self) -> Span { +impl Spanned for ImportDirective { + fn span(&self) -> Span { let span = self.import_token.span; span.join(self.semi_token.span).unwrap_or(span) } - pub fn set_span(&mut self, span: Span) { + fn set_span(&mut self, span: Span) { self.import_token.span = span; self.path.set_span(span); self.semi_token.span = span; @@ -67,8 +75,8 @@ impl Parse for ImportPath { } } -impl ImportPath { - pub fn span(&self) -> Span { +impl Spanned for ImportPath { + fn span(&self) -> Span { match self { Self::Plain(p) => p.span(), Self::Aliases(p) => p.span(), @@ -76,14 +84,16 @@ impl ImportPath { } } - pub fn set_span(&mut self, span: Span) { + fn set_span(&mut self, span: Span) { match self { Self::Plain(p) => p.set_span(span), Self::Aliases(p) => p.set_span(span), Self::Glob(p) => p.set_span(span), } } +} +impl ImportPath { pub fn path(&self) -> &LitStr { match self { Self::Plain(ImportPlain { path, .. }) @@ -123,17 +133,19 @@ impl Parse for ImportAlias { } } -impl ImportAlias { - pub fn span(&self) -> Span { +impl Spanned for ImportAlias { + fn span(&self) -> Span { let span = self.as_token.span; span.join(self.alias.span()).unwrap_or(span) } - pub fn set_span(&mut self, span: Span) { + fn set_span(&mut self, span: Span) { self.as_token.span = span; self.alias.set_span(span); } +} +impl ImportAlias { pub fn parse_opt(input: ParseStream<'_>) -> Result> { if input.peek(Token![as]) { input.parse().map(Some) @@ -168,8 +180,8 @@ impl Parse for ImportPlain { } } -impl ImportPlain { - pub fn span(&self) -> Span { +impl Spanned for ImportPlain { + fn span(&self) -> Span { let span = self.path.span(); if let Some(alias) = &self.alias { span.join(alias.span()).unwrap_or(span) @@ -178,7 +190,7 @@ impl ImportPlain { } } - pub fn set_span(&mut self, span: Span) { + fn set_span(&mut self, span: Span) { self.path.set_span(span); if let Some(alias) = &mut self.alias { alias.set_span(span); @@ -216,13 +228,13 @@ impl Parse for ImportAliases { } } -impl ImportAliases { - pub fn span(&self) -> Span { +impl Spanned for ImportAliases { + fn span(&self) -> Span { let span = self.brace_token.span.join(); span.join(self.path.span()).unwrap_or(span) } - pub fn set_span(&mut self, span: Span) { + fn set_span(&mut self, span: Span) { self.brace_token = Brace(span); self.from_token.span = span; self.path.set_span(span); @@ -258,13 +270,13 @@ impl Parse for ImportGlob { } } -impl ImportGlob { - pub fn span(&self) -> Span { +impl Spanned for ImportGlob { + fn span(&self) -> Span { let span = self.star_token.span; span.join(self.path.span()).unwrap_or(span) } - pub fn set_span(&mut self, span: Span) { + fn set_span(&mut self, span: Span) { self.star_token.span = span; self.alias.set_span(span); self.from_token.span = span; diff --git a/crates/syn-solidity/src/item/mod.rs b/crates/syn-solidity/src/item/mod.rs index ca70d31ca..05e95fe34 100644 --- a/crates/syn-solidity/src/item/mod.rs +++ b/crates/syn-solidity/src/item/mod.rs @@ -1,12 +1,13 @@ -use crate::{kw, variable::VariableDefinition, SolIdent}; +use crate::{kw, variable::VariableDefinition, SolIdent, Spanned}; use proc_macro2::Span; +use std::fmt; use syn::{ parse::{Parse, ParseStream}, Attribute, Result, Token, }; mod contract; -pub use contract::ItemContract; +pub use contract::{ContractKind, Inheritance, ItemContract}; mod r#enum; pub use r#enum::ItemEnum; @@ -18,7 +19,7 @@ mod event; pub use event::{EventParameter, ItemEvent}; mod function; -pub use function::{FunctionKind, ItemFunction, Returns}; +pub use function::{FunctionBody, FunctionKind, ItemFunction, Returns}; mod import; pub use import::{ @@ -40,7 +41,7 @@ pub use using::{UserDefinableOperator, UsingDirective, UsingList, UsingListItem, /// An AST item. A more expanded version of a [Solidity source unit][ref]. /// /// [ref]: https://docs.soliditylang.org/en/latest/grammar.html#a4.SolidityParser.sourceUnit -#[derive(Clone, Debug)] +#[derive(Clone)] pub enum Item { /// A contract, abstract contract, interface, or library definition: /// `contract Foo is Bar, Baz { ... }` @@ -79,6 +80,25 @@ pub enum Item { Variable(VariableDefinition), } +impl fmt::Debug for Item { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("Item::")?; + match self { + Self::Contract(item) => item.fmt(f), + Self::Enum(item) => item.fmt(f), + Self::Error(item) => item.fmt(f), + Self::Event(item) => item.fmt(f), + Self::Function(item) => item.fmt(f), + Self::Import(item) => item.fmt(f), + Self::Pragma(item) => item.fmt(f), + Self::Struct(item) => item.fmt(f), + Self::Udt(item) => item.fmt(f), + Self::Using(item) => item.fmt(f), + Self::Variable(item) => item.fmt(f), + } + } +} + impl Parse for Item { fn parse(input: ParseStream<'_>) -> Result { let mut attrs = input.call(Attribute::parse_outer)?; @@ -92,7 +112,7 @@ impl Parse for Item { input.parse().map(Self::Event) } else if lookahead.peek(kw::error) { input.parse().map(Self::Error) - } else if contract::ContractKind::peek(&lookahead) { + } else if ContractKind::peek(&lookahead) { input.parse().map(Self::Contract) } else if lookahead.peek(Token![enum]) { input.parse().map(Self::Enum) @@ -117,8 +137,8 @@ impl Parse for Item { } } -impl Item { - pub fn span(&self) -> Span { +impl Spanned for Item { + fn span(&self) -> Span { match self { Self::Contract(contract) => contract.span(), Self::Enum(enumm) => enumm.span(), @@ -134,7 +154,7 @@ impl Item { } } - pub fn set_span(&mut self, span: Span) { + fn set_span(&mut self, span: Span) { match self { Self::Contract(contract) => contract.set_span(span), Self::Enum(enumm) => enumm.set_span(span), @@ -149,7 +169,9 @@ impl Item { Self::Variable(variable) => variable.set_span(span), } } +} +impl Item { pub fn name(&self) -> Option<&SolIdent> { match self { Self::Contract(ItemContract { name, .. }) diff --git a/crates/syn-solidity/src/item/pragma.rs b/crates/syn-solidity/src/item/pragma.rs index c024efa73..838bf2703 100644 --- a/crates/syn-solidity/src/item/pragma.rs +++ b/crates/syn-solidity/src/item/pragma.rs @@ -1,9 +1,8 @@ -use crate::{kw, utils::tts_until_semi, SolIdent}; +use crate::{kw, utils::tts_until_semi, SolIdent, Spanned}; use proc_macro2::{Span, TokenStream}; use std::fmt; use syn::{ parse::{Parse, ParseStream}, - spanned::Spanned, Result, Token, }; @@ -17,7 +16,9 @@ pub struct PragmaDirective { impl fmt::Debug for PragmaDirective { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_tuple("Pragma").field(&self.tokens).finish() + f.debug_tuple("PragmaDirective") + .field(&self.tokens) + .finish() } } @@ -31,13 +32,13 @@ impl Parse for PragmaDirective { } } -impl PragmaDirective { - pub fn span(&self) -> Span { +impl Spanned for PragmaDirective { + fn span(&self) -> Span { let span = self.pragma_token.span; span.join(self.semi_token.span).unwrap_or(span) } - pub fn set_span(&mut self, span: Span) { + fn set_span(&mut self, span: Span) { self.pragma_token.span = span; self.tokens.set_span(span); self.semi_token.span = span; @@ -72,8 +73,8 @@ impl Parse for PragmaTokens { } } -impl PragmaTokens { - pub fn span(&self) -> Span { +impl Spanned for PragmaTokens { + fn span(&self) -> Span { match self { Self::Version(solidity, version) => { let span = solidity.span; @@ -91,7 +92,7 @@ impl PragmaTokens { } } - pub fn set_span(&mut self, span: Span) { + fn set_span(&mut self, span: Span) { match self { Self::Version(solidity, _version) => { solidity.span = span; diff --git a/crates/syn-solidity/src/item/struct.rs b/crates/syn-solidity/src/item/struct.rs index 3d13dbe21..c9ac5ca61 100644 --- a/crates/syn-solidity/src/item/struct.rs +++ b/crates/syn-solidity/src/item/struct.rs @@ -1,4 +1,4 @@ -use crate::{FieldList, SolIdent, Type}; +use crate::{FieldList, SolIdent, Spanned, Type}; use proc_macro2::Span; use std::{ fmt, @@ -11,7 +11,7 @@ use syn::{ Attribute, Result, Token, }; -/// A struct definition: `struct Foo { uint256 bar; }` +/// A struct definition: `struct Foo { uint256 bar; }`. /// /// Solidity reference: /// @@ -41,7 +41,7 @@ impl Hash for ItemStruct { impl fmt::Debug for ItemStruct { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("Struct") + f.debug_struct("ItemStruct") .field("attrs", &self.attrs) .field("name", &self.name) .field("fields", &self.fields) @@ -62,17 +62,19 @@ impl Parse for ItemStruct { } } -impl ItemStruct { - pub fn span(&self) -> Span { +impl Spanned for ItemStruct { + fn span(&self) -> Span { self.name.span() } - pub fn set_span(&mut self, span: Span) { + fn set_span(&mut self, span: Span) { self.struct_token = Token![struct](span); self.name.set_span(span); self.brace_token = Brace(span); } +} +impl ItemStruct { pub fn as_type(&self) -> Type { let mut ty = Type::Tuple(self.fields.types().cloned().collect()); ty.set_span(self.span()); diff --git a/crates/syn-solidity/src/item/udt.rs b/crates/syn-solidity/src/item/udt.rs index 41e47a4e6..b85aaccc1 100644 --- a/crates/syn-solidity/src/item/udt.rs +++ b/crates/syn-solidity/src/item/udt.rs @@ -1,4 +1,4 @@ -use crate::{kw, SolIdent, Type}; +use crate::{kw, SolIdent, Spanned, Type}; use proc_macro2::Span; use std::{ fmt, @@ -9,7 +9,7 @@ use syn::{ Attribute, Result, Token, }; -/// A user-defined value type definition: `type Foo is uint256;` +/// A user-defined value type definition: `type Foo is uint256;`. /// /// Solidity reference: /// @@ -25,7 +25,7 @@ pub struct ItemUdt { impl fmt::Debug for ItemUdt { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("Udt") + f.debug_struct("ItemUdt") .field("attrs", &self.attrs) .field("name", &self.name) .field("ty", &self.ty) @@ -71,12 +71,12 @@ impl Parse for ItemUdt { } } -impl ItemUdt { - pub fn span(&self) -> Span { +impl Spanned for ItemUdt { + fn span(&self) -> Span { self.name.span() } - pub fn set_span(&mut self, span: Span) { + fn set_span(&mut self, span: Span) { self.type_token.span = span; self.name.set_span(span); self.is_token.span = span; diff --git a/crates/syn-solidity/src/item/using.rs b/crates/syn-solidity/src/item/using.rs index 9fa5e854e..3214b56fa 100644 --- a/crates/syn-solidity/src/item/using.rs +++ b/crates/syn-solidity/src/item/using.rs @@ -1,5 +1,6 @@ -use crate::{kw, SolPath, Type}; +use crate::{kw, SolPath, Spanned, Type}; use proc_macro2::Span; +use std::fmt; use syn::{ braced, parse::{Parse, ParseStream}, @@ -8,11 +9,11 @@ use syn::{ Result, Token, }; -/// A `using` directive: `using { A, B.mul as * } for uint256 global;` +/// A `using` directive: `using { A, B.mul as * } for uint256 global;`. /// /// Solidity reference: /// -#[derive(Clone, Debug)] +#[derive(Clone)] pub struct UsingDirective { pub using_token: kw::using, pub list: UsingList, @@ -22,6 +23,16 @@ pub struct UsingDirective { pub semi_token: Token![;], } +impl fmt::Debug for UsingDirective { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("UsingDirective") + .field("list", &self.list) + .field("ty", &self.ty) + .field("global", &self.global_token.is_some()) + .finish() + } +} + impl Parse for UsingDirective { fn parse(input: ParseStream<'_>) -> Result { Ok(Self { @@ -35,13 +46,13 @@ impl Parse for UsingDirective { } } -impl UsingDirective { - pub fn span(&self) -> Span { +impl Spanned for UsingDirective { + fn span(&self) -> Span { let span = self.using_token.span; span.join(self.semi_token.span).unwrap_or(span) } - pub fn set_span(&mut self, span: Span) { + fn set_span(&mut self, span: Span) { self.using_token.span = span; self.semi_token.span = span; } @@ -67,6 +78,28 @@ impl Parse for UsingList { } } +impl Spanned for UsingList { + fn span(&self) -> Span { + match self { + Self::Single(path) => path.span(), + Self::Multiple(brace, list) => { + let span = brace.span.join(); + span.join(list.span()).unwrap_or(span) + } + } + } + + fn set_span(&mut self, span: Span) { + match self { + Self::Single(path) => path.set_span(span), + Self::Multiple(brace, list) => { + *brace = Brace(span); + list.set_span(span); + } + } + } +} + #[derive(Clone, Debug)] pub struct UsingListItem { pub path: SolPath, @@ -86,6 +119,16 @@ impl Parse for UsingListItem { } } +impl Spanned for UsingListItem { + fn span(&self) -> Span { + self.path.span() + } + + fn set_span(&mut self, span: Span) { + self.path.set_span(span); + } +} + #[derive(Clone, Debug)] pub enum UsingType { Star(Token![*]), @@ -102,6 +145,22 @@ impl Parse for UsingType { } } +impl Spanned for UsingType { + fn span(&self) -> Span { + match self { + Self::Star(star) => star.span, + Self::Type(ty) => ty.span(), + } + } + + fn set_span(&mut self, span: Span) { + match self { + Self::Star(star) => star.span = span, + Self::Type(ty) => ty.set_span(span), + } + } +} + op_enum! { /// A user-definable operator: `+`, `*`, `|`, etc. /// diff --git a/crates/syn-solidity/src/kw.rs b/crates/syn-solidity/src/kw.rs index f277640bc..818214fcb 100644 --- a/crates/syn-solidity/src/kw.rs +++ b/crates/syn-solidity/src/kw.rs @@ -1,11 +1,21 @@ //! Solidity keywords. -pub use syn::token::{Abstract, Override, Virtual}; - macro_rules! custom_keywords { - ($($name:ident),+ $(,)?) => { - $(syn::custom_keyword!($name);)+ - }; + ($($name:ident),+ $(,)?) => {$( + syn::custom_keyword!($name); + + impl $crate::Spanned for $name { + #[inline] + fn span(&self) -> ::proc_macro2::Span { + self.span + } + + #[inline] + fn set_span(&mut self, span: ::proc_macro2::Span) { + self.span = span; + } + } + )+}; } #[rustfmt::skip] @@ -35,6 +45,7 @@ custom_keywords!( // Error error, + panic, // Event event, @@ -67,7 +78,28 @@ custom_keywords!( using, global, + // Literals + unicode, + hex, + + // Sub-denominations + wei, + gwei, + ether, + seconds, + minutes, + hours, + days, + weeks, + years, + // Other + assembly, + catch, + delete, + emit, is, - unicode, + new, + revert, + unchecked, ); diff --git a/crates/syn-solidity/src/lib.rs b/crates/syn-solidity/src/lib.rs index 046bf7f93..5c1c6badf 100644 --- a/crates/syn-solidity/src/lib.rs +++ b/crates/syn-solidity/src/lib.rs @@ -4,7 +4,6 @@ html_favicon_url = "https://raw.githubusercontent.com/alloy-rs/core/main/assets/favicon.ico" )] #![warn( - missing_copy_implementations, missing_debug_implementations, unreachable_pub, unused_crate_dependencies, @@ -26,6 +25,13 @@ pub use attribute::{ VariableAttribute, VariableAttributes, Visibility, }; +mod expr; +pub use expr::{ + ArgList, ArgListImpl, BinOp, Expr, ExprArray, ExprBinary, ExprCall, ExprCallOptions, + ExprDelete, ExprIndex, ExprMember, ExprNew, ExprPayable, ExprPostfix, ExprTernary, ExprTuple, + ExprTypeCall, ExprUnary, NamedArg, NamedArgList, PostUnOp, UnOp, +}; + mod file; pub use file::File; @@ -34,19 +40,30 @@ pub use ident::{SolIdent, SolPath}; mod item; pub use item::{ - EventParameter, FunctionKind, ImportAlias, ImportAliases, ImportDirective, ImportGlob, - ImportPath, ImportPlain, Item, ItemContract, ItemEnum, ItemError, ItemEvent, ItemFunction, - ItemStruct, ItemUdt, PragmaDirective, PragmaTokens, Returns, UserDefinableOperator, - UsingDirective, UsingList, UsingListItem, UsingType, + ContractKind, EventParameter, FunctionBody, FunctionKind, ImportAlias, ImportAliases, + ImportDirective, ImportGlob, ImportPath, ImportPlain, Inheritance, Item, ItemContract, + ItemEnum, ItemError, ItemEvent, ItemFunction, ItemStruct, ItemUdt, PragmaDirective, + PragmaTokens, Returns, UserDefinableOperator, UsingDirective, UsingList, UsingListItem, + UsingType, }; mod lit; -pub use lit::LitStr; +pub use lit::{ + HexStr, Lit, LitDenominated, LitHexStr, LitNumber, LitStr, LitUnicodeStr, SubDenomination, + UnicodeStr, +}; pub mod kw; +mod spanned; +pub use spanned::Spanned; + mod stmt; -pub use stmt::Block; +pub use stmt::{ + AssemblyFlags, Block, CatchClause, ForInitStmt, Stmt, StmtAssembly, StmtBreak, StmtContinue, + StmtDoWhile, StmtEmit, StmtExpr, StmtFor, StmtIf, StmtReturn, StmtRevert, StmtTry, StmtVarDecl, + StmtWhile, UncheckedBlock, VarDeclDecl, VarDeclTuple, +}; mod r#type; pub use r#type::{Type, TypeArray, TypeFunction, TypeMapping, TypeTuple}; @@ -66,6 +83,9 @@ pub mod visit_mut; #[cfg(feature = "visit-mut")] pub use visit_mut::VisitMut; +mod yul; +pub use yul::YulBlock; + /// Parse a Solidity [`proc_macro::TokenStream`] into a [`File`]. pub fn parse(input: proc_macro::TokenStream) -> Result { syn::parse(input) @@ -75,3 +95,5 @@ pub fn parse(input: proc_macro::TokenStream) -> Result { pub fn parse2(input: proc_macro2::TokenStream) -> Result { syn::parse2(input) } + +const DEBUG: bool = option_env!("SYN_SOLIDITY_DEBUG").is_some(); diff --git a/crates/syn-solidity/src/lit.rs b/crates/syn-solidity/src/lit.rs deleted file mode 100644 index 64d3d45ac..000000000 --- a/crates/syn-solidity/src/lit.rs +++ /dev/null @@ -1,73 +0,0 @@ -use crate::kw; -use proc_macro2::Span; -use std::fmt; -use syn::{ - parse::{Parse, ParseStream}, - Result, -}; - -/// A string literal. -#[derive(Clone)] -pub struct LitStr { - pub unicode_token: Option, - pub values: Vec, -} - -impl fmt::Debug for LitStr { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("LitStr") - .field("unicode", &self.unicode_token.is_some()) - .field("values", &self.values) - .finish() - } -} - -impl fmt::Display for LitStr { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - for value in &self.values { - f.write_str(&value.value())?; - } - Ok(()) - } -} - -impl Parse for LitStr { - fn parse(input: ParseStream<'_>) -> Result { - Ok(Self { - unicode_token: input.parse()?, - values: { - let mut values = Vec::new(); - while !input.peek(syn::LitStr) { - values.push(input.parse()?); - } - if values.is_empty() { - return Err(input.parse::().unwrap_err()) - } - values - }, - }) - } -} - -impl LitStr { - pub fn span(&self) -> Span { - let mut span = if let Some(kw) = &self.unicode_token { - kw.span - } else { - self.values.first().unwrap().span() - }; - for value in &self.values { - span = span.join(value.span()).unwrap_or(span); - } - span - } - - pub fn set_span(&mut self, span: Span) { - if let Some(kw) = &mut self.unicode_token { - kw.span = span; - } - for value in &mut self.values { - value.set_span(span); - } - } -} diff --git a/crates/syn-solidity/src/lit/mod.rs b/crates/syn-solidity/src/lit/mod.rs new file mode 100644 index 000000000..b08032260 --- /dev/null +++ b/crates/syn-solidity/src/lit/mod.rs @@ -0,0 +1,92 @@ +use crate::{kw, Spanned}; +use proc_macro2::Span; +use std::fmt; +use syn::{ + parse::{Lookahead1, Parse, ParseStream}, + LitBool, Result, +}; + +mod number; +pub use number::{LitDenominated, LitNumber, SubDenomination}; + +mod str; +pub use self::str::{HexStr, LitHexStr, LitStr, LitUnicodeStr, UnicodeStr}; + +/// A Solidity literal such as a string or integer or boolean. +#[derive(Clone)] +pub enum Lit { + /// A boolean literal: `true` or `false`. + Bool(LitBool), + + /// A hex string literal: `hex"1234"`. + Hex(LitHexStr), + + /// An integer or fixed-point number literal: `1` or `1.0`. + Number(LitNumber), + + /// A string literal. + Str(LitStr), + + /// A unicode string literal. + Unicode(LitUnicodeStr), +} + +impl fmt::Debug for Lit { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("Lit::")?; + match self { + Self::Bool(lit) => lit.fmt(f), + Self::Hex(lit) => lit.fmt(f), + Self::Number(lit) => lit.fmt(f), + Self::Str(lit) => lit.fmt(f), + Self::Unicode(lit) => lit.fmt(f), + } + } +} + +impl Parse for Lit { + fn parse(input: ParseStream<'_>) -> Result { + let lookahead = input.lookahead1(); + if lookahead.peek(syn::LitStr) { + input.parse().map(Self::Str) + } else if LitNumber::peek(&lookahead) { + input.parse().map(Self::Number) + } else if lookahead.peek(LitBool) { + input.parse().map(Self::Bool) + } else if lookahead.peek(kw::unicode) { + input.parse().map(Self::Unicode) + } else if lookahead.peek(kw::hex) { + input.parse().map(Self::Hex) + } else { + Err(lookahead.error()) + } + } +} + +impl Spanned for Lit { + fn span(&self) -> Span { + match self { + Self::Bool(lit) => lit.span(), + Self::Hex(lit) => lit.span(), + Self::Number(lit) => lit.span(), + Self::Str(lit) => lit.span(), + Self::Unicode(lit) => lit.span(), + } + } + + fn set_span(&mut self, span: Span) { + match self { + Self::Bool(lit) => lit.set_span(span), + Self::Hex(lit) => lit.set_span(span), + Self::Number(lit) => lit.set_span(span), + Self::Str(lit) => lit.set_span(span), + Self::Unicode(lit) => lit.set_span(span), + } + } +} + +impl Lit { + pub fn peek(lookahead: &Lookahead1<'_>) -> bool { + lookahead.peek(syn::Lit) || lookahead.peek(kw::unicode) || lookahead.peek(kw::hex) + } +} diff --git a/crates/syn-solidity/src/lit/number.rs b/crates/syn-solidity/src/lit/number.rs new file mode 100644 index 000000000..85388c980 --- /dev/null +++ b/crates/syn-solidity/src/lit/number.rs @@ -0,0 +1,149 @@ +use crate::{kw, Spanned}; +use proc_macro2::{Literal, Span}; +use std::{fmt, str::FromStr}; +use syn::{ + parse::{Lookahead1, Parse, ParseStream}, + LitFloat, LitInt, Result, +}; + +// TODO: Fixed point numbers + +/// An integer or fixed-point number literal: `1` or `1.0`. +#[derive(Clone)] +pub enum LitNumber { + Int(LitInt), + Float(LitFloat), +} + +impl fmt::Debug for LitNumber { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Int(lit) => lit.fmt(f), + Self::Float(lit) => lit.fmt(f), + } + } +} + +impl Parse for LitNumber { + fn parse(input: ParseStream<'_>) -> Result { + let lookahead = input.lookahead1(); + if lookahead.peek(LitInt) { + input.parse().map(Self::Int) + } else if lookahead.peek(LitFloat) { + input.parse().map(Self::Float) + } else { + Err(lookahead.error()) + } + } +} + +impl Spanned for LitNumber { + fn span(&self) -> Span { + match self { + Self::Int(lit) => lit.span(), + Self::Float(lit) => lit.span(), + } + } + + fn set_span(&mut self, span: Span) { + match self { + Self::Int(lit) => lit.set_span(span), + Self::Float(lit) => lit.set_span(span), + } + } +} + +impl LitNumber { + pub fn new_int(repr: &str, span: Span) -> Self { + Self::Int(LitInt::new(repr, span)) + } + + pub fn new_fixed(repr: &str, span: Span) -> Self { + Self::Float(LitFloat::new(repr, span)) + } + + pub fn peek(lookahead: &Lookahead1<'_>) -> bool { + lookahead.peek(LitInt) || lookahead.peek(LitFloat) + } + + /// Returns the base-10 digits of the literal. + pub fn base10_digits(&self) -> &str { + match self { + Self::Int(lit) => lit.base10_digits(), + Self::Float(lit) => lit.base10_digits(), + } + } + + /// Parses the literal into a selected number type. + /// + /// This is equivalent to `lit.base10_digits().parse()` except that the + /// resulting errors will be correctly spanned to point to the literal token + /// in the macro input. + pub fn base10_parse(&self) -> Result + where + N: FromStr, + N::Err: fmt::Display, + { + match self { + Self::Int(lit) => lit.base10_parse(), + Self::Float(lit) => lit.base10_parse(), + } + } + + pub fn suffix(&self) -> &str { + match self { + Self::Int(lit) => lit.suffix(), + Self::Float(lit) => lit.suffix(), + } + } + + pub fn token(&self) -> Literal { + match self { + Self::Int(lit) => lit.token(), + Self::Float(lit) => lit.token(), + } + } +} + +#[derive(Clone, Debug)] +pub struct LitDenominated { + pub number: LitNumber, + pub denom: SubDenomination, +} + +impl Parse for LitDenominated { + fn parse(input: ParseStream<'_>) -> Result { + Ok(Self { + number: input.parse()?, + denom: input.parse()?, + }) + } +} + +impl Spanned for LitDenominated { + fn span(&self) -> Span { + let span = self.number.span(); + span.join(self.denom.span()).unwrap_or(span) + } + + fn set_span(&mut self, span: Span) { + self.number.set_span(span); + self.denom.set_span(span); + } +} + +kw_enum! { + /// A sub-denomination suffix for a number literal. + pub enum SubDenomination { + Wei(kw::wei), + Gwei(kw::gwei), + Ether(kw::ether), + + Seconds(kw::seconds), + Minutes(kw::minutes), + Hours(kw::hours), + Days(kw::days), + Weeks(kw::weeks), + Years(kw::years), + } +} diff --git a/crates/syn-solidity/src/lit/str.rs b/crates/syn-solidity/src/lit/str.rs new file mode 100644 index 000000000..c982388a1 --- /dev/null +++ b/crates/syn-solidity/src/lit/str.rs @@ -0,0 +1,160 @@ +use crate::{kw, Spanned}; +use proc_macro2::Span; +use std::{ + fmt, + ops::{Deref, DerefMut}, +}; +use syn::{ + parse::{Lookahead1, Parse, ParseStream}, + Result, +}; + +macro_rules! str_lit { + ($(#[$attr:meta])* $vis:vis struct $name:ident($t:ty) $(: $kw:ident)?) => { + #[derive(Clone, Debug)] + $vis struct $name { + $vis values: Vec<$t>, + } + + impl fmt::Display for $name { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + for (i, value) in self.values.iter().enumerate() { + if i > 0 { + f.write_str(" ")?; + } + + $( + f.write_str(stringify!($kw))?; + )? + f.write_str(&value.value())?; + } + Ok(()) + } + } + + impl Parse for $name { + fn parse(input: ParseStream<'_>) -> Result { + let mut values = Vec::new(); + while Self::peek(&input.lookahead1()) { + values.push(input.parse()?); + } + Ok(Self { values }) + } + } + + impl Spanned for $name { + fn span(&self) -> Span { + crate::utils::join_spans(&self.values) + } + + fn set_span(&mut self, span: Span) { + crate::utils::set_spans(&mut self.values, span) + } + } + + impl $name { + pub fn peek(lookahead: &Lookahead1<'_>) -> bool { + $(lookahead.peek(kw::$kw) || )? lookahead.peek(syn::LitStr) + } + + pub fn parse_opt(input: ParseStream<'_>) -> Result> { + if Self::peek(&input.lookahead1()) { + input.parse().map(Some) + } else { + Ok(None) + } + } + + pub fn value(&self) -> String { + self.values.iter().map(|v| v.value()).collect() + } + } + }; +} + +macro_rules! wrap_str { + ($(#[$attr:meta])* $vis:vis struct $name:ident { $token:ident : kw::$kw:ident $(,)? }) => { + $(#[$attr])* + #[derive(Clone)] + $vis struct $name { + /// The prefix of the string. + $vis $token: kw::$kw, + /// The string literal. + $vis value: syn::LitStr, + } + + impl fmt::Debug for $name { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct(stringify!($name)) + .field("value", &self.value) + .finish() + } + } + + impl Deref for $name { + type Target = syn::LitStr; + + #[inline] + fn deref(&self) -> &Self::Target { + &self.value + } + } + + impl DerefMut for $name { + #[inline] + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.value + } + } + + impl Parse for $name { + fn parse(input: ParseStream<'_>) -> Result { + Ok(Self { + $token: input.parse()?, + value: input.parse()?, + }) + } + } + + impl Spanned for $name { + fn span(&self) -> Span { + let span = self.$token.span; + span.join(self.value.span()).unwrap_or(span) + } + + fn set_span(&mut self, span: Span) { + self.$token.span = span; + self.value.set_span(span); + } + } + }; +} + +str_lit! { + /// A string literal. + pub struct LitStr(syn::LitStr) +} + +str_lit! { + /// A unicode string literal. + pub struct LitUnicodeStr(UnicodeStr): unicode +} + +wrap_str! { + /// A unicode string. + pub struct UnicodeStr { + unicode_token: kw::unicode, + } +} + +str_lit! { + /// A hex string literal. + pub struct LitHexStr(HexStr): hex +} + +wrap_str! { + /// A hex string. + pub struct HexStr { + hex_token: kw::hex, + } +} diff --git a/crates/syn-solidity/src/macros.rs b/crates/syn-solidity/src/macros.rs index 7f51bc292..69f90ceb0 100644 --- a/crates/syn-solidity/src/macros.rs +++ b/crates/syn-solidity/src/macros.rs @@ -217,6 +217,8 @@ macro_rules! kw_enum { $(#[$attr])* #[derive(Clone, Copy)] $vis enum $name {$( + #[doc = concat!("`", stringify!($kw), "`")] + /// $(#[$variant_attr])* $variant($crate::kw::$kw), )+} @@ -265,11 +267,26 @@ macro_rules! kw_enum { } } + impl $crate::Spanned for $name { + fn span(&self) -> ::proc_macro2::Span { + match self {$( + Self::$variant(kw) => kw.span, + )+} + } + + fn set_span(&mut self, span: ::proc_macro2::Span) { + match self {$( + Self::$variant(kw) => kw.span = span, + )+} + } + } + impl $name { ::paste::paste! { $( + #[doc = concat!("Creates a new `", stringify!($variant), "` keyword with the given `span`.")] #[inline] - pub fn [](span: ::proc_macro2::Span) -> Self { + pub fn [](span: ::proc_macro2::Span) -> Self { Self::$variant(kw::$kw(span)) } )+ @@ -290,18 +307,6 @@ macro_rules! kw_enum { $( lookahead.peek($crate::kw::$kw) )||+ } - pub const fn span(self) -> ::proc_macro2::Span { - match self {$( - Self::$variant(kw) => kw.span, - )+} - } - - pub fn set_span(&mut self, span: ::proc_macro2::Span) { - match self {$( - Self::$variant(kw) => kw.span = span, - )+} - } - pub const fn as_str(self) -> &'static str { match self {$( Self::$variant(_) => stringify!($kw), @@ -316,8 +321,9 @@ macro_rules! kw_enum { ::paste::paste! { $( + #[doc = concat!("Returns true if `self` matches `Self::", stringify!($variant), "`.")] #[inline] - pub const fn [](self) -> bool { + pub const fn [](self) -> bool { matches!(self, Self::$variant(_)) } )+ @@ -327,18 +333,33 @@ macro_rules! kw_enum { } macro_rules! op_enum { + (@skip $($tt:tt)*) => {}; + (@first $first:tt $($rest:tt)*) => { ::syn::Token![$first] }; + + (@peek $input:ident, $lookahead:ident, $a:tt) => { + $lookahead.peek(::syn::Token![$a]) + }; + // can't use `peek2` for `BinOp::Sar` (`>>>`) since the first token is 2 characters, + // so take it in as input + (@peek $input:ident, $lookahead:ident, $a:tt $b:tt $peek:ident) => { + $lookahead.peek(::syn::Token![$a]) + && $input.$peek(::syn::Token![$b]) + }; + ( $(#[$attr:meta])* $vis:vis enum $name:ident {$( $(#[$variant_attr:meta])* - $variant:ident($op:tt) + $variant:ident($($op:tt)+) $($peek:ident)? ),+ $(,)?} ) => { $(#[$attr])* #[derive(Clone, Copy)] $vis enum $name {$( - #[doc = concat!("`", stringify!($t), "`")] - $variant(::syn::Token![$op]), + #[doc = concat!("`", $(stringify!($op),)+ "`")] + /// + $(#[$variant_attr])* + $variant($(::syn::Token![$op]),+), )+} impl ::core::cmp::PartialEq for $name { @@ -375,8 +396,10 @@ macro_rules! op_enum { fn parse(input: ::syn::parse::ParseStream<'_>) -> ::syn::Result { let lookahead = input.lookahead1(); $( - if lookahead.peek(Token![$op]) { - input.parse().map(Self::$variant) + if op_enum!(@peek input, lookahead, $($op)+ $($peek)?) { + Ok(Self::$variant( + $(input.parse::<::syn::Token![$op]>()?),+ + )) } else )+ { @@ -385,36 +408,80 @@ macro_rules! op_enum { } } + impl $crate::Spanned for $name { + fn span(&self) -> ::proc_macro2::Span { + match self {$( + Self::$variant(kw, ..) => kw.span(), + )+} + } + + fn set_span(&mut self, span: ::proc_macro2::Span) { + match self {$( + Self::$variant(kw, ..) => kw.set_span(span), + )+} + } + } + impl $name { ::paste::paste! { $( + #[doc = concat!("Creates a new `", stringify!($variant), "` operator with the given `span`.")] #[inline] - pub fn [](span: ::proc_macro2::Span) -> Self { - Self::$variant(::syn::Token![$op](span)) + pub fn [](span: ::proc_macro2::Span) -> Self { + Self::$variant($(::syn::Token![$op](span)),+) } )+ } + #[allow(unused_parens, unused_variables)] + pub fn peek(input: syn::parse::ParseStream<'_>, lookahead: &::syn::parse::Lookahead1<'_>) -> bool { + $( + (op_enum!(@peek input, lookahead, $($op)+ $($peek)?)) + )||+ + } + pub const fn as_str(self) -> &'static str { match self {$( - Self::$variant(_) => stringify!($op), + Self::$variant(..) => concat!($(stringify!($op)),+), )+} } pub const fn as_debug_str(self) -> &'static str { match self {$( - Self::$variant(_) => stringify!($variant), + Self::$variant(..) => stringify!($variant), )+} } ::paste::paste! { $( + #[doc = concat!("Returns true if `self` matches `Self::", stringify!($variant), "`.")] #[inline] - pub const fn [](self) -> bool { - matches!(self, Self::$variant(_)) + pub const fn [](self) -> bool { + matches!(self, Self::$variant(..)) } )+ } } }; } + +macro_rules! derive_parse { + ($($t:ty),+ $(,)?) => {$( + impl Parse for $t { + fn parse(input: ParseStream<'_>) -> Result { + ::parse_nested( + input.parse()?, + input, + ) + } + } + )+}; +} + +macro_rules! debug { + ($($t:tt)*) => { + if $crate::DEBUG { + eprintln!($($t)*) + } + }; +} diff --git a/crates/syn-solidity/src/spanned.rs b/crates/syn-solidity/src/spanned.rs new file mode 100644 index 000000000..8e425eba7 --- /dev/null +++ b/crates/syn-solidity/src/spanned.rs @@ -0,0 +1,285 @@ +use proc_macro2::{Span, TokenStream, TokenTree}; +use syn::{punctuated::Punctuated, Token}; + +/// A trait for getting and setting the span of a syntax tree node. +pub trait Spanned { + /// Returns the span of this syntax tree node. + fn span(&self) -> Span; + + /// Sets the span of this syntax tree node. + fn set_span(&mut self, span: Span); +} + +impl Spanned for Span { + #[inline] + fn span(&self) -> Span { + *self + } + + #[inline] + fn set_span(&mut self, span: Span) { + *self = span; + } +} + +impl Spanned for TokenStream { + #[inline] + fn span(&self) -> Span { + syn::spanned::Spanned::span(self) + } + + #[inline] + fn set_span(&mut self, span: Span) { + crate::utils::set_spans_clone(self, span); + } +} + +impl Spanned for TokenTree { + #[inline] + fn span(&self) -> Span { + self.span() + } + + #[inline] + fn set_span(&mut self, span: Span) { + self.set_span(span); + } +} + +impl Spanned for syn::LitStr { + fn span(&self) -> Span { + self.span() + } + + fn set_span(&mut self, span: Span) { + self.set_span(span); + } +} + +impl Spanned for Option { + #[inline] + fn span(&self) -> Span { + match self { + Some(t) => t.span(), + None => Span::call_site(), + } + } + + #[inline] + fn set_span(&mut self, span: Span) { + if let Some(t) = self { + t.set_span(span); + } + } +} + +impl Spanned for &T { + #[inline] + fn span(&self) -> Span { + (**self).span() + } + + #[inline] + fn set_span(&mut self, _span: Span) { + unimplemented!( + "cannot set span of borrowed Spanned: {:?}", + std::any::type_name::() + ) + } +} + +impl Spanned for Punctuated { + fn span(&self) -> Span { + crate::utils::join_spans(self) + } + + fn set_span(&mut self, span: Span) { + crate::utils::set_spans(self, span) + } +} + +macro_rules! deref_impl { + ($($(#[$attr:meta])* [$($gen:tt)*] $t:ty),+ $(,)?) => {$( + $(#[$attr])* + impl<$($gen)*> Spanned for $t { + #[inline] + fn span(&self) -> Span { + (**self).span() + } + + #[inline] + fn set_span(&mut self, span: Span) { + (**self).set_span(span) + } + } + )+}; +} + +deref_impl! { + [T: ?Sized + Spanned] &mut T, + [T: ?Sized + Spanned] Box, + [T: Spanned] Vec, +} + +impl Spanned for [T] { + #[inline] + fn span(&self) -> Span { + join_spans(self.iter().map(Spanned::span)) + } + + #[inline] + fn set_span(&mut self, span: Span) { + self.iter_mut().for_each(|item| item.set_span(span)); + } +} + +// For `syn::Token!`s +macro_rules! kw_impl { + ($([$($t:tt)+])+) => { $(kw_impl!($($t)+);)+ }; + + (__more $t:tt) => { + impl Spanned for Token![$t] { + #[inline] + fn span(&self) -> Span { + join_spans(self.spans) + } + + #[inline] + fn set_span(&mut self, span: Span) { + set_spans(&mut self.spans, span); + } + } + }; + + ($t:tt) => { + impl Spanned for Token![$t] { + #[inline] + fn span(&self) -> Span { + self.span + } + + #[inline] + fn set_span(&mut self, span: Span) { + self.span = span; + } + } + }; +} + +kw_impl! { + [abstract] + [as] + [async] + [auto] + [await] + [become] + [box] + [break] + [const] + [continue] + [crate] + [default] + [do] + [dyn] + [else] + [enum] + [extern] + [final] + [fn] + [for] + [if] + [impl] + [in] + [let] + [loop] + [macro] + [match] + [mod] + [move] + [mut] + [override] + [priv] + [pub] + [ref] + [return] + [Self] + [self] + [static] + [struct] + [super] + [trait] + [try] + [type] + [typeof] + [union] + [unsafe] + [unsized] + [use] + [virtual] + [where] + [while] + [yield] + [&] + [__more &&] + [__more &=] + [@] + [^] + [__more ^=] + [:] + [,] + [$] + [.] + [__more ..] + [__more ...] + [__more ..=] + [=] + [__more ==] + [__more =>] + [__more >=] + [>] + [__more <-] + [__more <=] + [<] + [-] + [__more -=] + [__more !=] + [!] + [|] + [__more |=] + [__more ||] + [__more ::] + [%] + [__more %=] + [+] + [__more +=] + [#] + [?] + [__more ->] + [;] + [__more <<] + [__more <<=] + [__more >>] + [__more >>=] + [/] + [__more /=] + [*] + [__more *=] + [~] + [_] +} + +fn join_spans>(spans: I) -> Span { + let mut iter = spans.into_iter(); + let Some(first) = iter.next() else { + return Span::call_site() + }; + iter.last() + .and_then(|last| first.join(last)) + .unwrap_or(first) +} + +fn set_spans<'a, I: IntoIterator>(spans: I, set_to: Span) { + for span in spans { + *span = set_to; + } +} diff --git a/crates/syn-solidity/src/stmt.rs b/crates/syn-solidity/src/stmt.rs deleted file mode 100644 index 4f6cebf3a..000000000 --- a/crates/syn-solidity/src/stmt.rs +++ /dev/null @@ -1,30 +0,0 @@ -use proc_macro2::TokenStream; -use std::fmt; -use syn::{ - parse::{Parse, ParseStream}, - token::Brace, - Result, -}; - -/// A curly-braced block of statements. -#[derive(Clone)] -pub struct Block { - pub brace_token: Brace, - pub stmts: TokenStream, -} - -impl fmt::Debug for Block { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_tuple("Block").field(&self.stmts).finish() - } -} - -impl Parse for Block { - fn parse(input: ParseStream<'_>) -> Result { - let content; - Ok(Self { - brace_token: syn::braced!(content in input), - stmts: content.parse()?, - }) - } -} diff --git a/crates/syn-solidity/src/stmt/assembly.rs b/crates/syn-solidity/src/stmt/assembly.rs new file mode 100644 index 000000000..9f576c825 --- /dev/null +++ b/crates/syn-solidity/src/stmt/assembly.rs @@ -0,0 +1,96 @@ +use crate::{kw, utils::DebugPunctuated, LitStr, Spanned, YulBlock}; +use proc_macro2::Span; +use std::fmt; +use syn::{ + parse::{Parse, ParseStream}, + punctuated::Punctuated, + token::Paren, + Result, Token, +}; + +/// An assembly block, with optional flags: `assembly "evmasm" { ... }`. +#[derive(Clone)] +pub struct StmtAssembly { + pub assembly_token: kw::assembly, + pub literal: Option, + pub flags: Option, + pub block: YulBlock, +} + +impl fmt::Debug for StmtAssembly { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("StmtAssembly") + .field("literal", &self.literal) + .field("flags", &self.flags) + .field("block", &self.block) + .finish() + } +} + +impl Parse for StmtAssembly { + fn parse(input: ParseStream<'_>) -> Result { + Ok(Self { + assembly_token: input.parse()?, + literal: input.call(LitStr::parse_opt)?, + flags: input.call(AssemblyFlags::parse_opt)?, + block: input.parse()?, + }) + } +} + +impl Spanned for StmtAssembly { + fn span(&self) -> Span { + let span = self.assembly_token.span; + span.join(self.block.span()).unwrap_or(span) + } + + fn set_span(&mut self, span: Span) { + self.assembly_token.span = span; + self.block.set_span(span); + } +} + +/// A list of flags of an assembly statement. +#[derive(Clone)] +pub struct AssemblyFlags { + pub paren_token: Paren, + pub strings: Punctuated, +} + +impl fmt::Debug for AssemblyFlags { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("AssemblyFlags") + .field("strings", DebugPunctuated::new(&self.strings)) + .finish() + } +} + +impl Parse for AssemblyFlags { + fn parse(input: ParseStream<'_>) -> Result { + let content; + Ok(Self { + paren_token: syn::parenthesized!(content in input), + strings: content.parse_terminated(LitStr::parse, Token![,])?, + }) + } +} + +impl Spanned for AssemblyFlags { + fn span(&self) -> Span { + self.paren_token.span.join() + } + + fn set_span(&mut self, span: Span) { + self.paren_token = Paren(span); + } +} + +impl AssemblyFlags { + pub fn parse_opt(input: ParseStream<'_>) -> Result> { + if input.peek(Paren) { + input.parse().map(Some) + } else { + Ok(None) + } + } +} diff --git a/crates/syn-solidity/src/stmt/blocks.rs b/crates/syn-solidity/src/stmt/blocks.rs new file mode 100644 index 000000000..382d501ca --- /dev/null +++ b/crates/syn-solidity/src/stmt/blocks.rs @@ -0,0 +1,85 @@ +use crate::{kw, Spanned, Stmt}; +use proc_macro2::Span; +use std::fmt; +use syn::{ + parse::{Parse, ParseStream}, + token::Brace, + Result, +}; + +/// A curly-braced block of statements: `{ ... }`. +#[derive(Clone)] +pub struct Block { + pub brace_token: Brace, + pub stmts: Vec, +} + +impl fmt::Debug for Block { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_tuple("Block").field(&self.stmts).finish() + } +} + +impl Parse for Block { + fn parse(input: ParseStream<'_>) -> Result { + let content; + Ok(Self { + brace_token: syn::braced!(content in input), + stmts: { + let mut vec = Vec::new(); + debug!("> Block: {:?}", content.to_string()); + while !content.is_empty() { + vec.push(content.parse()?); + } + debug!("< Block: {} stmts\n", vec.len()); + vec + }, + }) + } +} + +impl Spanned for Block { + fn span(&self) -> Span { + self.brace_token.span.join() + } + + fn set_span(&mut self, span: Span) { + self.brace_token = Brace(span); + } +} + +/// An unchecked block: `unchecked { ... }`. +#[derive(Clone)] +pub struct UncheckedBlock { + pub unchecked_token: kw::unchecked, + pub block: Block, +} + +impl fmt::Debug for UncheckedBlock { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("UncheckedBlock") + .field("stmts", &self.block.stmts) + .finish() + } +} + +impl Parse for UncheckedBlock { + fn parse(input: ParseStream<'_>) -> Result { + Ok(Self { + unchecked_token: input.parse()?, + block: input.parse()?, + }) + } +} + +impl Spanned for UncheckedBlock { + fn span(&self) -> Span { + let span = self.unchecked_token.span; + span.join(self.block.span()).unwrap_or(span) + } + + fn set_span(&mut self, span: Span) { + self.unchecked_token.span = span; + self.block.set_span(span); + } +} diff --git a/crates/syn-solidity/src/stmt/break.rs b/crates/syn-solidity/src/stmt/break.rs new file mode 100644 index 000000000..31612aeb6 --- /dev/null +++ b/crates/syn-solidity/src/stmt/break.rs @@ -0,0 +1,44 @@ +use crate::Spanned; +use proc_macro2::Span; +use std::fmt; +use syn::{ + parse::{Parse, ParseStream}, + Result, Token, +}; + +/// A break statement: `break;`. +/// +/// Solidity Reference: +/// +#[derive(Clone)] +pub struct StmtBreak { + pub break_token: Token![break], + pub semi_token: Token![;], +} + +impl fmt::Debug for StmtBreak { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("StmtBreak").finish() + } +} + +impl Parse for StmtBreak { + fn parse(input: ParseStream<'_>) -> Result { + Ok(Self { + break_token: input.parse()?, + semi_token: input.parse()?, + }) + } +} + +impl Spanned for StmtBreak { + fn span(&self) -> Span { + let span = self.break_token.span; + span.join(self.semi_token.span).unwrap_or(span) + } + + fn set_span(&mut self, span: Span) { + self.break_token.span = span; + self.semi_token.span = span; + } +} diff --git a/crates/syn-solidity/src/stmt/continue.rs b/crates/syn-solidity/src/stmt/continue.rs new file mode 100644 index 000000000..06e828b80 --- /dev/null +++ b/crates/syn-solidity/src/stmt/continue.rs @@ -0,0 +1,44 @@ +use crate::Spanned; +use proc_macro2::Span; +use std::fmt; +use syn::{ + parse::{Parse, ParseStream}, + Result, Token, +}; + +/// A continue statement: `continue;`. +/// +/// Solidity Reference: +/// +#[derive(Clone)] +pub struct StmtContinue { + pub continue_token: Token![continue], + pub semi_token: Token![;], +} + +impl fmt::Debug for StmtContinue { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("StmtContinue").finish() + } +} + +impl Parse for StmtContinue { + fn parse(input: ParseStream<'_>) -> Result { + Ok(Self { + continue_token: input.parse()?, + semi_token: input.parse()?, + }) + } +} + +impl Spanned for StmtContinue { + fn span(&self) -> Span { + let span = self.continue_token.span; + span.join(self.semi_token.span).unwrap_or(span) + } + + fn set_span(&mut self, span: Span) { + self.continue_token.span = span; + self.semi_token.span = span; + } +} diff --git a/crates/syn-solidity/src/stmt/do_while.rs b/crates/syn-solidity/src/stmt/do_while.rs new file mode 100644 index 000000000..e35f9c7a2 --- /dev/null +++ b/crates/syn-solidity/src/stmt/do_while.rs @@ -0,0 +1,57 @@ +use crate::{Expr, Spanned, Stmt}; +use proc_macro2::Span; +use std::fmt; +use syn::{ + parse::{Parse, ParseStream}, + token::Paren, + Result, Token, +}; + +/// A do-while statement: `do { ... } while (condition);`. +/// +/// Solidity Reference: +/// +#[derive(Clone)] +pub struct StmtDoWhile { + pub do_token: Token![do], + pub body: Box, + pub while_token: Token![while], + pub paren_token: Paren, + pub cond: Expr, + pub semi_token: Token![;], +} + +impl fmt::Debug for StmtDoWhile { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("DoWhile") + .field("body", &self.body) + .field("condition", &self.cond) + .finish() + } +} + +impl Parse for StmtDoWhile { + fn parse(input: ParseStream<'_>) -> Result { + let content; + Ok(Self { + do_token: input.parse()?, + body: input.parse()?, + while_token: input.parse()?, + paren_token: syn::parenthesized!(content in input), + cond: content.parse()?, + semi_token: input.parse()?, + }) + } +} + +impl Spanned for StmtDoWhile { + fn span(&self) -> Span { + let span = self.do_token.span; + span.join(self.semi_token.span).unwrap_or(span) + } + + fn set_span(&mut self, span: Span) { + self.do_token.span = span; + self.semi_token.span = span; + } +} diff --git a/crates/syn-solidity/src/stmt/emit.rs b/crates/syn-solidity/src/stmt/emit.rs new file mode 100644 index 000000000..956634571 --- /dev/null +++ b/crates/syn-solidity/src/stmt/emit.rs @@ -0,0 +1,51 @@ +use crate::{kw, Expr, Spanned}; +use proc_macro2::Span; +use std::fmt; +use syn::{ + parse::{Parse, ParseStream}, + Result, Token, +}; + +/// An emit statement: `emit FooBar(42);`. +/// +/// Solidity Reference: +/// +#[derive(Clone)] +pub struct StmtEmit { + pub emit_token: kw::emit, + pub expr: Expr, + // pub list: ArgList, // TODO + pub semi_token: Token![;], +} + +impl fmt::Debug for StmtEmit { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("StmtEmit") + .field("expr", &self.expr) + // .field("list", &self.list) + .finish() + } +} + +impl Parse for StmtEmit { + fn parse(input: ParseStream<'_>) -> Result { + Ok(Self { + emit_token: input.parse()?, + expr: input.parse()?, + // list: input.parse()?, + semi_token: input.parse()?, + }) + } +} + +impl Spanned for StmtEmit { + fn span(&self) -> Span { + let span = self.emit_token.span; + span.join(self.semi_token.span).unwrap_or(span) + } + + fn set_span(&mut self, span: Span) { + self.emit_token.span = span; + self.semi_token.span = span; + } +} diff --git a/crates/syn-solidity/src/stmt/expr.rs b/crates/syn-solidity/src/stmt/expr.rs new file mode 100644 index 000000000..e835d485b --- /dev/null +++ b/crates/syn-solidity/src/stmt/expr.rs @@ -0,0 +1,43 @@ +use crate::{Expr, Spanned}; +use proc_macro2::Span; +use std::fmt; +use syn::{ + parse::{Parse, ParseStream}, + Result, Token, +}; + +/// An expression with a trailing semicolon. +#[derive(Clone)] +pub struct StmtExpr { + pub expr: Expr, + pub semi_token: Token![;], +} + +impl fmt::Debug for StmtExpr { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("StmtExpr") + .field("expr", &self.expr) + .finish() + } +} + +impl Parse for StmtExpr { + fn parse(input: ParseStream<'_>) -> Result { + Ok(Self { + expr: input.parse()?, + semi_token: input.parse()?, + }) + } +} + +impl Spanned for StmtExpr { + fn span(&self) -> Span { + let span = self.expr.span(); + span.join(self.semi_token.span).unwrap_or(span) + } + + fn set_span(&mut self, span: Span) { + self.expr.set_span(span); + self.semi_token.span = span; + } +} diff --git a/crates/syn-solidity/src/stmt/for.rs b/crates/syn-solidity/src/stmt/for.rs new file mode 100644 index 000000000..c8d3d4305 --- /dev/null +++ b/crates/syn-solidity/src/stmt/for.rs @@ -0,0 +1,95 @@ +use crate::{Expr, Spanned, Stmt, StmtExpr, StmtVarDecl}; +use proc_macro2::Span; +use std::fmt; +use syn::{ + parenthesized, + parse::{Parse, ParseStream}, + token::Paren, + Result, Token, +}; + +/// A for statement: `for (uint256 i; i < 42; ++i) { ... }`. +/// +/// Solidity Reference: +/// +#[derive(Clone)] +pub struct StmtFor { + pub for_token: Token![for], + pub paren_token: Paren, + pub init: Box, + pub cond: Option>, + pub semi_token: Token![;], + pub post: Option>, + pub body: Box, +} + +impl fmt::Debug for StmtFor { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("StmtFor") + .field("init", &self.init) + .field("cond", &self.cond) + .field("post", &self.post) + .field("body", &self.body) + .finish() + } +} + +impl Parse for StmtFor { + fn parse(input: ParseStream<'_>) -> Result { + let content; + Ok(Self { + for_token: input.parse()?, + paren_token: parenthesized!(content in input), + init: content.parse()?, + cond: if content.peek(Token![;]) { + None + } else { + Some(content.parse()?) + }, + semi_token: content.parse()?, + post: if content.is_empty() { + None + } else { + Some(content.parse()?) + }, + body: input.parse()?, + }) + } +} + +impl Spanned for StmtFor { + fn span(&self) -> Span { + let span = self.for_token.span; + span.join(self.body.span()).unwrap_or(span) + } + + fn set_span(&mut self, span: Span) { + self.for_token.span = span; + self.body.set_span(span); + } +} + +/// A for statement initializer. +/// +/// This can either be empty, a variable declaration, or an expression. +#[derive(Clone, Debug)] +pub enum ForInitStmt { + Empty(Token![;]), + VarDecl(StmtVarDecl), + Expr(StmtExpr), +} + +impl Parse for ForInitStmt { + fn parse(input: ParseStream<'_>) -> Result { + let lookahead = input.lookahead1(); + if lookahead.peek(Token![;]) { + input.parse().map(Self::Empty) + } else { + match StmtVarDecl::parse_or_expr(input)? { + Stmt::VarDecl(decl) => Ok(Self::VarDecl(decl)), + Stmt::Expr(expr) => Ok(Self::Expr(expr)), + s => unreachable!("StmtVarDecl::parse_or_expr: invalid output {s:?}"), + } + } + } +} diff --git a/crates/syn-solidity/src/stmt/if.rs b/crates/syn-solidity/src/stmt/if.rs new file mode 100644 index 000000000..fe157bc3f --- /dev/null +++ b/crates/syn-solidity/src/stmt/if.rs @@ -0,0 +1,65 @@ +use crate::{Expr, Spanned, Stmt}; +use proc_macro2::Span; +use std::fmt; +use syn::{ + parse::{Parse, ParseStream}, + token::Paren, + Result, Token, +}; + +/// An `if` statement with an optional `else` block: `if (expr) { ... } else { +/// ... }`. +#[derive(Clone)] +pub struct StmtIf { + pub if_token: Token![if], + pub paren_token: Paren, + pub cond: Expr, + pub then_branch: Box, + pub else_branch: Option<(Token![else], Box)>, +} + +impl fmt::Debug for StmtIf { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("StmtIf") + .field("cond", &self.cond) + .field("then_branch", &self.then_branch) + .field("else_branch", &self.else_branch) + .finish() + } +} + +impl Parse for StmtIf { + fn parse(input: ParseStream<'_>) -> Result { + let content; + Ok(Self { + if_token: input.parse()?, + paren_token: syn::parenthesized!(content in input), + cond: content.parse()?, + then_branch: input.parse()?, + else_branch: if input.peek(Token![else]) { + Some((input.parse()?, input.parse()?)) + } else { + None + }, + }) + } +} + +impl Spanned for StmtIf { + fn span(&self) -> Span { + let span = self.if_token.span; + self.else_branch + .as_ref() + .and_then(|(_, stmt)| stmt.span().join(span)) + .or_else(|| span.join(self.then_branch.span())) + .unwrap_or(span) + } + + fn set_span(&mut self, span: Span) { + self.if_token.span = span; + self.then_branch.set_span(span); + if let Some((_, stmt)) = &mut self.else_branch { + stmt.set_span(span); + } + } +} diff --git a/crates/syn-solidity/src/stmt/mod.rs b/crates/syn-solidity/src/stmt/mod.rs new file mode 100644 index 000000000..5e2b40212 --- /dev/null +++ b/crates/syn-solidity/src/stmt/mod.rs @@ -0,0 +1,219 @@ +use crate::{kw, Spanned}; +use proc_macro2::Span; +use std::fmt; +use syn::{ + parse::{Parse, ParseStream}, + token::{Brace, Bracket, Paren}, + Result, Token, +}; + +mod assembly; +pub use assembly::{AssemblyFlags, StmtAssembly}; + +mod blocks; +pub use blocks::{Block, UncheckedBlock}; + +mod r#break; +pub use r#break::StmtBreak; + +mod r#continue; +pub use r#continue::StmtContinue; + +mod do_while; +pub use do_while::StmtDoWhile; + +mod emit; +pub use emit::StmtEmit; + +mod expr; +pub use expr::StmtExpr; + +mod r#for; +pub use r#for::{ForInitStmt, StmtFor}; + +mod r#if; +pub use r#if::StmtIf; + +mod r#return; +pub use r#return::StmtReturn; + +mod revert; +pub use revert::StmtRevert; + +mod r#try; +pub use r#try::{CatchClause, StmtTry}; + +mod var_decl; +pub use var_decl::{StmtVarDecl, VarDeclDecl, VarDeclTuple}; + +mod r#while; +pub use r#while::StmtWhile; + +/// A statement, usually ending in a semicolon. +/// +/// Solidity reference: +/// +#[derive(Clone)] +pub enum Stmt { + /// An assembly block, with optional flags: `assembly "evmasm" { ... }`. + Assembly(StmtAssembly), + + /// A blocked scope: `{ ... }`. + Block(Block), + + /// A break statement: `break;`. + Break(StmtBreak), + + /// A continue statement: `continue;`. + Continue(StmtContinue), + + /// A do-while statement: `do { ... } while (condition);`. + DoWhile(StmtDoWhile), + + /// An emit statement: `emit FooBar(42);`. + Emit(StmtEmit), + + /// An expression with a trailing semicolon. + Expr(StmtExpr), + + /// A for statement: `for (uint256 i; i < 42; ++i) { ... }`. + For(StmtFor), + + /// An `if` statement with an optional `else` block: `if (expr) { ... } else + /// { ... }`. + If(StmtIf), + + /// A return statement: `return 42;`. + Return(StmtReturn), + + /// A revert statement: `revert("error");`. + Revert(StmtRevert), + + /// A try statement: `try fooBar(42) catch { ... }`. + Try(StmtTry), + + /// An unchecked block: `unchecked { ... }`. + UncheckedBlock(UncheckedBlock), + + /// A variable declaration statement: `uint256 foo = 42;`. + VarDecl(StmtVarDecl), + + /// A while statement: `while (i < 42) { ... }`. + While(StmtWhile), +} + +impl fmt::Debug for Stmt { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("Stmt::")?; + match self { + Self::Assembly(stmt) => stmt.fmt(f), + Self::Block(block) => block.fmt(f), + Self::Break(stmt) => stmt.fmt(f), + Self::Continue(stmt) => stmt.fmt(f), + Self::DoWhile(stmt) => stmt.fmt(f), + Self::Emit(stmt) => stmt.fmt(f), + Self::Expr(stmt) => stmt.fmt(f), + Self::For(stmt) => stmt.fmt(f), + Self::If(stmt) => stmt.fmt(f), + Self::Return(stmt) => stmt.fmt(f), + Self::Revert(stmt) => stmt.fmt(f), + Self::Try(stmt) => stmt.fmt(f), + Self::UncheckedBlock(block) => block.fmt(f), + Self::VarDecl(stmt) => stmt.fmt(f), + Self::While(stmt) => stmt.fmt(f), + } + } +} + +impl Parse for Stmt { + fn parse(input: ParseStream<'_>) -> Result { + // skip any attributes + let _ = input.call(syn::Attribute::parse_outer)?; + + debug!(" > Stmt: {:?}", input.to_string()); + let stmt = Self::parse_simple(input)?; + debug!(" < Stmt: {stmt:?}\n"); + Ok(stmt) + } +} + +impl Spanned for Stmt { + fn span(&self) -> Span { + match self { + Self::Assembly(stmt) => stmt.span(), + Self::Block(block) => block.span(), + Self::Break(stmt) => stmt.span(), + Self::Continue(stmt) => stmt.span(), + Self::DoWhile(stmt) => stmt.span(), + Self::Emit(stmt) => stmt.span(), + Self::Expr(stmt) => stmt.span(), + Self::For(stmt) => stmt.span(), + Self::If(stmt) => stmt.span(), + Self::Return(stmt) => stmt.span(), + Self::Revert(stmt) => stmt.span(), + Self::Try(stmt) => stmt.span(), + Self::UncheckedBlock(block) => block.span(), + Self::VarDecl(stmt) => stmt.span(), + Self::While(stmt) => stmt.span(), + } + } + + fn set_span(&mut self, span: Span) { + match self { + Self::Assembly(stmt) => stmt.set_span(span), + Self::Block(block) => block.set_span(span), + Self::Break(stmt) => stmt.set_span(span), + Self::Continue(stmt) => stmt.set_span(span), + Self::DoWhile(stmt) => stmt.set_span(span), + Self::Emit(stmt) => stmt.set_span(span), + Self::Expr(stmt) => stmt.set_span(span), + Self::For(stmt) => stmt.set_span(span), + Self::If(stmt) => stmt.set_span(span), + Self::Return(stmt) => stmt.set_span(span), + Self::Revert(stmt) => stmt.set_span(span), + Self::Try(stmt) => stmt.set_span(span), + Self::UncheckedBlock(block) => block.set_span(span), + Self::VarDecl(stmt) => stmt.set_span(span), + Self::While(stmt) => stmt.set_span(span), + } + } +} + +impl Stmt { + fn parse_simple(input: ParseStream<'_>) -> Result { + let lookahead = input.lookahead1(); + if lookahead.peek(Brace) { + input.parse().map(Self::Block) + } else if lookahead.peek(Paren) { + StmtVarDecl::parse_or_expr(input) + } else if lookahead.peek(Bracket) { + input.parse().map(Self::Expr) + } else if lookahead.peek(kw::unchecked) { + input.parse().map(Self::UncheckedBlock) + } else if lookahead.peek(Token![if]) { + input.parse().map(Self::If) + } else if lookahead.peek(Token![for]) { + input.parse().map(Self::For) + } else if lookahead.peek(Token![while]) { + input.parse().map(Self::While) + } else if lookahead.peek(Token![do]) { + input.parse().map(Self::DoWhile) + } else if lookahead.peek(Token![continue]) { + input.parse().map(Self::Continue) + } else if lookahead.peek(Token![break]) { + input.parse().map(Self::Break) + } else if lookahead.peek(Token![try]) { + input.parse().map(Self::Try) + } else if lookahead.peek(Token![return]) { + input.parse().map(Self::Return) + } else if lookahead.peek(kw::emit) { + input.parse().map(Self::Emit) + } else if lookahead.peek(kw::revert) { + input.parse().map(Self::Revert) + } else if lookahead.peek(kw::assembly) { + input.parse().map(Self::Assembly) + } else { + StmtVarDecl::parse_or_expr(input) + } + } +} diff --git a/crates/syn-solidity/src/stmt/return.rs b/crates/syn-solidity/src/stmt/return.rs new file mode 100644 index 000000000..4e98ea00e --- /dev/null +++ b/crates/syn-solidity/src/stmt/return.rs @@ -0,0 +1,52 @@ +use crate::{Expr, Spanned}; +use proc_macro2::Span; +use std::fmt; +use syn::{ + parse::{Parse, ParseStream}, + Result, Token, +}; + +/// A return statement: `return 42;`. +/// +/// Solidity Reference: +/// +#[derive(Clone)] +pub struct StmtReturn { + pub return_token: Token![return], + pub expr: Option, + pub semi_token: Token![;], +} + +impl fmt::Debug for StmtReturn { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("StmtReturn") + .field("expr", &self.expr) + .finish() + } +} + +impl Parse for StmtReturn { + fn parse(input: ParseStream<'_>) -> Result { + Ok(Self { + return_token: input.parse()?, + expr: if input.peek(Token![;]) { + None + } else { + Some(input.parse()?) + }, + semi_token: input.parse()?, + }) + } +} + +impl Spanned for StmtReturn { + fn span(&self) -> Span { + let span = self.return_token.span; + span.join(self.semi_token.span).unwrap_or(span) + } + + fn set_span(&mut self, span: Span) { + self.return_token.span = span; + self.semi_token.span = span; + } +} diff --git a/crates/syn-solidity/src/stmt/revert.rs b/crates/syn-solidity/src/stmt/revert.rs new file mode 100644 index 000000000..0fd423ff4 --- /dev/null +++ b/crates/syn-solidity/src/stmt/revert.rs @@ -0,0 +1,51 @@ +use crate::{kw, Expr, Spanned}; +use proc_macro2::Span; +use std::fmt; +use syn::{ + parse::{Parse, ParseStream}, + Result, Token, +}; + +/// A revert statement: `revert("error");`. +/// +/// Solidity Reference: +/// +#[derive(Clone)] +pub struct StmtRevert { + pub revert_token: kw::revert, + pub expr: Expr, + // pub list: ArgList, // TODO + pub semi_token: Token![;], +} + +impl fmt::Debug for StmtRevert { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("StmtRevert") + .field("expr", &self.expr) + // .field("list", &self.list) + .finish() + } +} + +impl Parse for StmtRevert { + fn parse(input: ParseStream<'_>) -> Result { + Ok(Self { + revert_token: input.parse()?, + expr: input.parse()?, + // list: input.parse()?, + semi_token: input.parse()?, + }) + } +} + +impl Spanned for StmtRevert { + fn span(&self) -> Span { + let span = self.revert_token.span; + span.join(self.semi_token.span).unwrap_or(span) + } + + fn set_span(&mut self, span: Span) { + self.revert_token.span = span; + self.semi_token.span = span; + } +} diff --git a/crates/syn-solidity/src/stmt/try.rs b/crates/syn-solidity/src/stmt/try.rs new file mode 100644 index 000000000..dd1abf964 --- /dev/null +++ b/crates/syn-solidity/src/stmt/try.rs @@ -0,0 +1,123 @@ +use crate::{kw, Block, Expr, ParameterList, Returns, SolIdent, Spanned}; +use proc_macro2::Span; +use std::fmt; +use syn::{ + parenthesized, + parse::{Parse, ParseStream}, + token::Paren, + Result, Token, +}; + +/// A try statement: `try fooBar(42) catch { ... }`. +/// +/// Solidity reference: +/// +#[derive(Clone)] +pub struct StmtTry { + pub try_token: Token![try], + pub expr: Box, + pub returns: Option, + /// The try block. + pub block: Block, + /// The list of catch clauses. Cannot be parsed empty. + pub catch: Vec, +} + +impl fmt::Debug for StmtTry { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("StmtTry") + .field("expr", &self.expr) + .field("returns", &self.returns) + .field("block", &self.block) + .field("catch", &self.catch) + .finish() + } +} + +impl Parse for StmtTry { + fn parse(input: ParseStream<'_>) -> Result { + Ok(Self { + try_token: input.parse()?, + expr: input.parse()?, + returns: input.call(Returns::parse_opt)?, + block: input.parse()?, + catch: { + let mut catch = Vec::new(); + let mut first = true; + while first || input.peek(kw::catch) { + first = false; + catch.push(input.parse()?); + } + catch + }, + }) + } +} + +impl Spanned for StmtTry { + fn span(&self) -> Span { + let span = self.try_token.span; + span.join(self.block.span()).unwrap_or(span) + } + + fn set_span(&mut self, span: Span) { + self.try_token.span = span; + self.block.set_span(span); + } +} + +/// A catch clause of a [`StmtTry`]: `catch { ... }`. +/// +/// Solidity reference: +/// +#[derive(Clone)] +pub struct CatchClause { + pub catch_token: kw::catch, + pub name: Option, + pub paren_token: Option, + pub list: ParameterList, + pub block: Block, +} + +impl fmt::Debug for CatchClause { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("CatchClause") + .field("name", &self.name) + .field("list", &self.list) + .field("block", &self.block) + .finish() + } +} + +impl Parse for CatchClause { + fn parse(input: ParseStream<'_>) -> Result { + let catch_token = input.parse()?; + let name = input.call(SolIdent::parse_opt)?; + let (paren_token, list) = if input.peek(Paren) { + let content; + (Some(parenthesized!(content in input)), content.parse()?) + } else { + (None, ParameterList::new()) + }; + let block = input.parse()?; + Ok(Self { + catch_token, + name, + paren_token, + list, + block, + }) + } +} + +impl Spanned for CatchClause { + fn span(&self) -> Span { + let span = self.catch_token.span; + span.join(self.block.span()).unwrap_or(span) + } + + fn set_span(&mut self, span: Span) { + self.catch_token.span = span; + self.block.set_span(span); + } +} diff --git a/crates/syn-solidity/src/stmt/var_decl.rs b/crates/syn-solidity/src/stmt/var_decl.rs new file mode 100644 index 000000000..db3d794ab --- /dev/null +++ b/crates/syn-solidity/src/stmt/var_decl.rs @@ -0,0 +1,179 @@ +use crate::{utils::DebugPunctuated, Expr, Spanned, Stmt, VariableDeclaration}; +use proc_macro2::Span; +use std::fmt; +use syn::{ + parenthesized, + parse::{discouraged::Speculative, Parse, ParseStream}, + punctuated::Punctuated, + token::Paren, + Result, Token, +}; + +/// A variable declaration statement: `uint256 foo = 42;`. +/// +/// Solidity Reference: +/// +#[derive(Clone)] +pub struct StmtVarDecl { + pub declaration: Box, + pub assignment: Option<(Token![=], Expr)>, + pub semi_token: Token![;], +} + +impl fmt::Debug for StmtVarDecl { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("StmtVarDecl") + .field("declaration", &self.declaration) + .field("assignment", &self.assignment) + .finish() + } +} + +impl Parse for StmtVarDecl { + fn parse(input: ParseStream<'_>) -> Result { + let declaration: VarDeclDecl = input.parse()?; + + // tuple requires assignment + let assignment = if matches!(declaration, VarDeclDecl::Tuple(_)) || input.peek(Token![=]) { + Some((input.parse()?, input.parse()?)) + } else { + None + }; + + let semi_token = input.parse()?; + + Ok(Self { + declaration: Box::new(declaration), + assignment, + semi_token, + }) + } +} + +impl Spanned for StmtVarDecl { + fn span(&self) -> Span { + let span = self.declaration.span(); + self.assignment + .as_ref() + .and_then(|(_, expr)| expr.span().join(span)) + .unwrap_or(span) + } + + fn set_span(&mut self, span: Span) { + self.declaration.set_span(span); + if let Some((eq, expr)) = &mut self.assignment { + eq.span = span; + expr.set_span(span); + } + self.semi_token.span = span; + } +} + +impl StmtVarDecl { + pub fn parse_or_expr(input: ParseStream<'_>) -> Result { + // TODO: Figure if we can do this without forking + let speculative_parse = || { + let fork = input.fork(); + match fork.parse() { + Ok(var) => { + input.advance_to(&fork); + Ok(Stmt::VarDecl(var)) + } + Err(_) => input.parse().map(Stmt::Expr), + } + }; + + if input.peek(Paren) { + if input.peek2(Token![=]) { + speculative_parse() + } else { + input.parse().map(Stmt::Expr) + } + } else { + speculative_parse() + } + } +} + +/// The declaration of the variable(s) in a variable declaration statement. +#[derive(Clone, Debug)] +pub enum VarDeclDecl { + VarDecl(VariableDeclaration), + Tuple(VarDeclTuple), +} + +impl Parse for VarDeclDecl { + fn parse(input: ParseStream<'_>) -> Result { + if input.peek(Paren) { + input.parse().map(Self::Tuple) + } else { + VariableDeclaration::parse_with_name(input).map(Self::VarDecl) + } + } +} + +impl Spanned for VarDeclDecl { + fn span(&self) -> Span { + match self { + Self::VarDecl(decl) => decl.span(), + Self::Tuple(decl) => decl.span(), + } + } + + fn set_span(&mut self, span: Span) { + match self { + Self::VarDecl(decl) => decl.set_span(span), + Self::Tuple(decl) => decl.set_span(span), + } + } +} + +/// A declaration of variables in a tuple: `(,,uint256 foo,string memory bar)`. +/// +/// Solidity Reference: +/// +#[derive(Clone)] +pub struct VarDeclTuple { + pub paren_token: Paren, + /// The list of variables being declared. The list can't be empty, but it + /// can contain `None` elements, indicating the field is empty. + pub vars: Punctuated, Token![,]>, +} + +impl fmt::Debug for VarDeclTuple { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("VarDeclTuple") + .field("vars", DebugPunctuated::new(&self.vars)) + .finish() + } +} + +impl Parse for VarDeclTuple { + fn parse(input: ParseStream<'_>) -> Result { + let content; + Ok(Self { + paren_token: parenthesized!(content in input), + vars: content.parse_terminated(Self::parse_var_opt, Token![,])?, + }) + } +} + +impl Spanned for VarDeclTuple { + fn span(&self) -> Span { + self.paren_token.span.join() + } + + fn set_span(&mut self, span: Span) { + self.paren_token = Paren(span); + } +} + +impl VarDeclTuple { + fn parse_var_opt(input: ParseStream<'_>) -> Result> { + if input.peek(Token![,]) { + Ok(None) + } else { + input.parse().map(Some) + } + } +} diff --git a/crates/syn-solidity/src/stmt/while.rs b/crates/syn-solidity/src/stmt/while.rs new file mode 100644 index 000000000..6d5398714 --- /dev/null +++ b/crates/syn-solidity/src/stmt/while.rs @@ -0,0 +1,52 @@ +use crate::{Expr, Spanned, Stmt}; +use proc_macro2::Span; +use std::fmt; +use syn::{ + parse::{Parse, ParseStream}, + token::Paren, + Result, Token, +}; + +/// A while statement: `while (i < 42) { ... }`. +/// +/// +#[derive(Clone)] +pub struct StmtWhile { + pub while_token: Token![while], + pub paren_token: Paren, + pub cond: Expr, + pub body: Box, +} + +impl fmt::Debug for StmtWhile { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("StmtWhile") + .field("cond", &self.cond) + .field("body", &self.body) + .finish() + } +} + +impl Parse for StmtWhile { + fn parse(input: ParseStream<'_>) -> Result { + let content; + Ok(Self { + while_token: input.parse()?, + paren_token: syn::parenthesized!(content in input), + cond: content.parse()?, + body: input.parse()?, + }) + } +} + +impl Spanned for StmtWhile { + fn span(&self) -> Span { + let span = self.while_token.span; + span.join(self.body.span()).unwrap_or(span) + } + + fn set_span(&mut self, span: Span) { + self.while_token.span = span; + self.body.set_span(span); + } +} diff --git a/crates/syn-solidity/src/type/array.rs b/crates/syn-solidity/src/type/array.rs index 0f677d814..1d467104e 100644 --- a/crates/syn-solidity/src/type/array.rs +++ b/crates/syn-solidity/src/type/array.rs @@ -1,4 +1,4 @@ -use crate::Type; +use crate::{Expr, Lit, LitNumber, Spanned, Type}; use proc_macro2::Span; use std::{ fmt, @@ -7,9 +7,9 @@ use std::{ }; use syn::{ bracketed, - parse::{Parse, ParseStream}, + parse::{discouraged::Speculative, Parse, ParseStream}, token::Bracket, - LitInt, Result, + Result, }; /// An array type. @@ -17,12 +17,12 @@ use syn::{ pub struct TypeArray { pub ty: Box, pub bracket_token: Bracket, - pub size: Option, + pub size: Option>, } impl PartialEq for TypeArray { fn eq(&self, other: &Self) -> bool { - self.ty == other.ty && self.size == other.size + self.ty == other.ty && self.size() == other.size() } } @@ -31,7 +31,7 @@ impl Eq for TypeArray {} impl Hash for TypeArray { fn hash(&self, state: &mut H) { self.ty.hash(state); - self.size.hash(state); + self.size().hash(state); } } @@ -39,7 +39,7 @@ impl fmt::Debug for TypeArray { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_tuple("TypeArray") .field(&self.ty) - .field(&self.size.as_ref().map(|s| s.base10_digits())) + .field(&self.size()) .finish() } } @@ -48,7 +48,7 @@ impl fmt::Display for TypeArray { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.ty.fmt(f)?; f.write_str("[")?; - if let Some(s) = &self.size { + if let Some(s) = self.size_lit() { f.write_str(s.base10_digits())?; } f.write_str("]") @@ -58,27 +58,37 @@ impl fmt::Display for TypeArray { impl Parse for TypeArray { fn parse(input: ParseStream<'_>) -> Result { let ty = input.parse()?; - Self::wrap(input, ty) + Self::parse_nested(Box::new(ty), input) } } -impl TypeArray { - pub fn span(&self) -> Span { +impl Spanned for TypeArray { + fn span(&self) -> Span { let span = self.ty.span(); span.join(self.bracket_token.span.join()).unwrap_or(span) } - pub fn set_span(&mut self, span: Span) { + fn set_span(&mut self, span: Span) { self.ty.set_span(span); self.bracket_token = Bracket(span); if let Some(size) = &mut self.size { size.set_span(span); } } +} +impl TypeArray { /// Returns the size of the array, or None if dynamic. pub fn size(&self) -> Option { - self.size.as_ref().map(|s| s.base10_parse().unwrap()) + self.size_lit().map(|s| s.base10_parse().unwrap()) + } + + /// Returns the size of the array, or None if dynamic. + pub fn size_lit(&self) -> Option<&LitNumber> { + self.size.as_ref().map(|s| match &**s { + Expr::Lit(Lit::Number(n)) => n, + _ => panic!("unevaluated literal in array size"), + }) } /// See [`Type::is_abi_dynamic`]. @@ -90,18 +100,33 @@ impl TypeArray { } /// Parses an array type from the given input stream, wrapping `ty` with it. - pub fn wrap(input: ParseStream<'_>, ty: Type) -> Result { + pub fn parse_nested(ty: Box, input: ParseStream<'_>) -> Result { let content; Ok(Self { - ty: Box::new(ty), + ty, bracket_token: bracketed!(content in input), size: { - let size = content.parse::>()?; - // Validate the size - if let Some(sz) = &size { + if content.is_empty() { + None + } else { + let fork = content.fork(); + let sz = match fork.parse::() { + Ok(lit) => lit, + Err(e) => { + return Err(match fork.parse::() { + Ok(_) => syn::Error::new( + e.span(), + "generic expressions are not supported in array type sizes", + ), + Err(e) => e, + }) + } + }; + content.advance_to(&fork); + // Validate the size sz.base10_parse::()?; + Some(Box::new(Expr::Lit(Lit::Number(LitNumber::Int(sz))))) } - size }, }) } diff --git a/crates/syn-solidity/src/type/function.rs b/crates/syn-solidity/src/type/function.rs index 966121c12..ae6b829d1 100644 --- a/crates/syn-solidity/src/type/function.rs +++ b/crates/syn-solidity/src/type/function.rs @@ -1,4 +1,4 @@ -use crate::{kw, FunctionAttributes, ParameterList, Returns}; +use crate::{kw, FunctionAttributes, ParameterList, Returns, Spanned}; use proc_macro2::Span; use std::{ fmt, @@ -11,7 +11,7 @@ use syn::{ Result, }; -/// A function type: `function() returns (string memory)` +/// A function type: `function() returns (string memory)`. /// /// Solidity reference: /// @@ -84,12 +84,12 @@ impl Parse for TypeFunction { } } -impl TypeFunction { - pub fn span(&self) -> Span { +impl Spanned for TypeFunction { + fn span(&self) -> Span { self.function_token.span } - pub fn set_span(&mut self, span: Span) { + fn set_span(&mut self, span: Span) { self.function_token.span = span; } } diff --git a/crates/syn-solidity/src/type/mapping.rs b/crates/syn-solidity/src/type/mapping.rs index c7fc95873..fb5986646 100644 --- a/crates/syn-solidity/src/type/mapping.rs +++ b/crates/syn-solidity/src/type/mapping.rs @@ -1,4 +1,4 @@ -use crate::{kw, SolIdent, Type}; +use crate::{kw, SolIdent, Spanned, Type}; use proc_macro2::Span; use std::{ fmt, @@ -78,13 +78,13 @@ impl Parse for TypeMapping { } } -impl TypeMapping { - pub fn span(&self) -> Span { +impl Spanned for TypeMapping { + fn span(&self) -> Span { let span = self.mapping_token.span; span.join(self.paren_token.span.join()).unwrap_or(span) } - pub fn set_span(&mut self, span: Span) { + fn set_span(&mut self, span: Span) { self.mapping_token.span = span; self.paren_token = Paren(span); self.key.set_span(span); diff --git a/crates/syn-solidity/src/type/mod.rs b/crates/syn-solidity/src/type/mod.rs index f0927330f..f40018cda 100644 --- a/crates/syn-solidity/src/type/mod.rs +++ b/crates/syn-solidity/src/type/mod.rs @@ -1,4 +1,4 @@ -use crate::{kw, sol_path, SolPath}; +use crate::{kw, sol_path, SolPath, Spanned}; use proc_macro2::Span; use std::{ fmt, @@ -30,6 +30,7 @@ pub use tuple::TypeTuple; /// #[derive(Clone)] pub enum Type { + // TODO: `fixed` and `ufixed` /// `address $(payable)?` Address(Span, Option), /// `bool` @@ -106,6 +107,7 @@ impl Hash for Type { impl fmt::Debug for Type { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("Type::")?; match self { Self::Address(_, None) => f.write_str("Address"), Self::Address(_, Some(_)) => f.write_str("AddressPayable"), @@ -155,27 +157,15 @@ impl Parse for Type { // while the next token is a bracket, parse an array size and nest the // candidate into an array while input.peek(Bracket) { - candidate = Self::Array(TypeArray::wrap(input, candidate)?); + candidate = Self::Array(TypeArray::parse_nested(Box::new(candidate), input)?); } Ok(candidate) } } -impl Type { - pub fn peek(lookahead: &Lookahead1<'_>) -> bool { - lookahead.peek(syn::token::Paren) - || lookahead.peek(kw::tuple) - || lookahead.peek(kw::function) - || lookahead.peek(kw::mapping) - || lookahead.peek(Ident::peek_any) - } - - pub fn custom(ident: Ident) -> Self { - Self::Custom(sol_path![ident]) - } - - pub fn span(&self) -> Span { +impl Spanned for Type { + fn span(&self) -> Span { match self { &Self::Address(span, payable) => { payable.and_then(|kw| span.join(kw.span)).unwrap_or(span) @@ -194,7 +184,7 @@ impl Type { } } - pub fn set_span(&mut self, new_span: Span) { + fn set_span(&mut self, new_span: Span) { match self { Self::Address(span, payable) => { *span = new_span; @@ -216,6 +206,76 @@ impl Type { Self::Custom(custom) => custom.set_span(new_span), } } +} + +impl Type { + pub fn custom(ident: Ident) -> Self { + Self::Custom(sol_path![ident]) + } + + pub fn peek(lookahead: &Lookahead1<'_>) -> bool { + lookahead.peek(syn::token::Paren) + || lookahead.peek(kw::tuple) + || lookahead.peek(kw::function) + || lookahead.peek(kw::mapping) + || lookahead.peek(Ident::peek_any) + } + + /// Parses an identifier as an [elementary type name][ref]. + /// + /// Note that you will have to check for the existence of a `payable` + /// keyword separately. + /// + /// [ref]: https://docs.soliditylang.org/en/latest/grammar.html#a4.SolidityParser.elementaryTypeName + pub fn parse_ident(ident: Ident) -> Result { + let span = ident.span(); + let s = ident.to_string(); + let ret = match s.as_str() { + "address" => Self::Address(span, None), + "bool" => Self::Bool(span), + "string" => Self::String(span), + s => { + if let Some(s) = s.strip_prefix("bytes") { + match parse_size(s, span)? { + None => Self::custom(ident), + Some(Some(size)) if size.get() > 32 => { + return Err(Error::new(span, "fixed bytes range is 1-32")) + } + Some(Some(size)) => Self::FixedBytes(span, size), + Some(None) => Self::Bytes(span), + } + } else if let Some(s) = s.strip_prefix("int") { + match parse_size(s, span)? { + None => Self::custom(ident), + Some(Some(size)) if size.get() > 256 || size.get() % 8 != 0 => { + return Err(Error::new(span, "intX must be a multiple of 8 up to 256")) + } + Some(size) => Self::Int(span, size), + } + } else if let Some(s) = s.strip_prefix("uint") { + match parse_size(s, span)? { + None => Self::custom(ident), + Some(Some(size)) if size.get() > 256 || size.get() % 8 != 0 => { + return Err(Error::new(span, "uintX must be a multiple of 8 up to 256")) + } + Some(size) => Self::Uint(span, size), + } + } else { + Self::custom(ident) + } + } + }; + Ok(ret) + } + + /// Parses the `payable` keyword from the input stream if this type is an + /// address. + pub fn parse_payable(mut self, input: ParseStream<'_>) -> Result { + if let Self::Address(_, opt @ None) = &mut self { + *opt = input.parse()? + } + Ok(self) + } /// Returns whether this type is ABI-encoded as a single EVM word (32 /// bytes). @@ -268,7 +328,7 @@ impl Type { match self { Self::Custom(_) => true, Self::Array(a) => a.ty.has_custom(), - Self::Tuple(t) => t.types.iter().any(Type::has_custom), + Self::Tuple(t) => t.types.iter().any(Self::has_custom), Self::Function(f) => { f.arguments.iter().any(|arg| arg.ty.has_custom()) || f.returns.as_ref().map_or(false, |ret| { @@ -327,50 +387,7 @@ impl Type { input.parse().map(Self::Custom) } else if input.peek(Ident::peek_any) { let ident = input.call(Ident::parse_any)?; - let span = ident.span(); - let s = ident.to_string(); - let ret = match s.as_str() { - "address" => Self::Address(span, input.parse()?), - "bool" => Self::Bool(span), - "string" => Self::String(span), - s => { - if let Some(s) = s.strip_prefix("bytes") { - match parse_size(s, span)? { - None => Self::custom(ident), - Some(Some(size)) if size.get() > 32 => { - return Err(Error::new(span, "fixed bytes range is 1-32")) - } - Some(None) => Self::Bytes(span), - Some(Some(size)) => Self::FixedBytes(span, size), - } - } else if let Some(s) = s.strip_prefix("int") { - match parse_size(s, span)? { - None => Self::custom(ident), - Some(Some(size)) if size.get() > 256 || size.get() % 8 != 0 => { - return Err(Error::new( - span, - "intX must be a multiple of 8 up to 256", - )) - } - Some(size) => Self::Int(span, size), - } - } else if let Some(s) = s.strip_prefix("uint") { - match parse_size(s, span)? { - None => Self::custom(ident), - Some(Some(size)) if size.get() > 256 || size.get() % 8 != 0 => { - return Err(Error::new( - span, - "uintX must be a multiple of 8 up to 256", - )) - } - Some(size) => Self::Uint(span, size), - } - } else { - Self::custom(ident) - } - } - }; - Ok(ret) + Self::parse_ident(ident)?.parse_payable(input) } else { Err(input.error( "expected a Solidity type: \ diff --git a/crates/syn-solidity/src/type/tuple.rs b/crates/syn-solidity/src/type/tuple.rs index bc577ba98..cf9831525 100644 --- a/crates/syn-solidity/src/type/tuple.rs +++ b/crates/syn-solidity/src/type/tuple.rs @@ -1,4 +1,4 @@ -use crate::{kw, utils::DebugPunctuated, Type}; +use crate::{kw, utils::DebugPunctuated, Spanned, Type}; use proc_macro2::Span; use std::{ fmt, @@ -79,7 +79,7 @@ impl Parse for TypeTuple { impl FromIterator for TypeTuple { fn from_iter>(iter: T) -> Self { - TypeTuple { + Self { tuple_token: None, paren_token: Paren::default(), types: { @@ -94,21 +94,23 @@ impl FromIterator for TypeTuple { } } -impl TypeTuple { - pub fn span(&self) -> Span { +impl Spanned for TypeTuple { + fn span(&self) -> Span { let span = self.paren_token.span.join(); self.tuple_token .and_then(|tuple_token| tuple_token.span.join(span)) .unwrap_or(span) } - pub fn set_span(&mut self, span: Span) { + fn set_span(&mut self, span: Span) { if let Some(tuple_token) = &mut self.tuple_token { tuple_token.span = span; } self.paren_token = Paren(span); } +} +impl TypeTuple { /// See [`Type::is_abi_dynamic`]. pub fn is_abi_dynamic(&self) -> bool { self.types.iter().any(Type::is_abi_dynamic) diff --git a/crates/syn-solidity/src/utils.rs b/crates/syn-solidity/src/utils.rs index 7c842875e..aa3d5a5ff 100644 --- a/crates/syn-solidity/src/utils.rs +++ b/crates/syn-solidity/src/utils.rs @@ -1,6 +1,13 @@ -use proc_macro2::{TokenStream, TokenTree}; +use crate::{Expr, Spanned}; +use proc_macro2::{Delimiter, Span, TokenStream, TokenTree}; use std::fmt; -use syn::{parse::ParseStream, punctuated::Punctuated, Token}; +use syn::{parse::ParseStream, punctuated::Punctuated, Result, Token}; + +/// Helper trait to parsing nested expressions. +pub(crate) trait ParseNested: Sized { + /// Parse `Self` as an expression that starts with `expr`. + fn parse_nested(expr: Box, input: ParseStream<'_>) -> Result; +} #[repr(transparent)] pub(crate) struct DebugPunctuated(Punctuated); @@ -18,6 +25,17 @@ impl fmt::Debug for DebugPunctuated { } } +#[allow(dead_code)] +pub(crate) fn peek_tt(input: ParseStream<'_>) -> Option { + let tt = input.cursor().token_tree(); + tt.and_then(|(tt, _)| match tt { + TokenTree::Group(g) if matches!(g.delimiter(), Delimiter::None) => { + g.stream().into_iter().next() + } + _ => Some(tt), + }) +} + pub(crate) fn tts_until_semi(input: ParseStream<'_>) -> TokenStream { let mut tts = TokenStream::new(); while !input.is_empty() && !input.peek(Token![;]) { @@ -26,3 +44,39 @@ pub(crate) fn tts_until_semi(input: ParseStream<'_>) -> TokenStream { } tts } + +pub(crate) fn join_spans>(items: I) -> Span { + let mut iter = items.into_iter(); + let Some(first) = iter.next() else { + return Span::call_site() + }; + let first = first.span(); + iter.last() + .and_then(|last| first.join(last.span())) + .unwrap_or(first) +} + +pub(crate) fn set_spans<'a, T, I>(items: I, span: Span) +where + T: Spanned + 'a, + I: IntoIterator, +{ + for item in items { + item.set_span(span); + } +} + +pub(crate) fn set_spans_clone<'a, T, I>(items: &mut I, span: Span) +where + T: Spanned + 'a, + I: Clone + IntoIterator + FromIterator, +{ + *items = items + .clone() + .into_iter() + .map(|mut item| { + item.set_span(span); + item + }) + .collect(); +} diff --git a/crates/syn-solidity/src/variable/list.rs b/crates/syn-solidity/src/variable/list.rs index b195d5475..6184344e7 100644 --- a/crates/syn-solidity/src/variable/list.rs +++ b/crates/syn-solidity/src/variable/list.rs @@ -1,5 +1,5 @@ -use super::VariableDeclaration; -use crate::{SolIdent, Type}; +use crate::{SolIdent, Spanned, Type, VariableDeclaration}; +use proc_macro2::Span; use std::{ fmt, ops::{Deref, DerefMut}, @@ -59,7 +59,7 @@ impl Parse for ParameterList { /// Struct: enforce semicolon after each field and field name. impl Parse for FieldList { fn parse(input: ParseStream<'_>) -> Result { - let this = input.parse_terminated(VariableDeclaration::parse_for_struct, Token![;])?; + let this = input.parse_terminated(VariableDeclaration::parse_with_name, Token![;])?; if this.is_empty() { Err(input.error("defining empty structs is disallowed")) } else if !this.trailing_punct() { @@ -70,6 +70,16 @@ impl Parse for FieldList { } } +impl

Spanned for Parameters

{ + fn span(&self) -> Span { + crate::utils::join_spans(&self.0) + } + + fn set_span(&mut self, span: Span) { + crate::utils::set_spans(&mut self.0, span); + } +} + impl

IntoIterator for Parameters

{ type IntoIter = as IntoIterator>::IntoIter; type Item = ::Item; diff --git a/crates/syn-solidity/src/variable/mod.rs b/crates/syn-solidity/src/variable/mod.rs index b922ddd3d..a42612cec 100644 --- a/crates/syn-solidity/src/variable/mod.rs +++ b/crates/syn-solidity/src/variable/mod.rs @@ -1,6 +1,5 @@ -use super::{SolIdent, Storage, Type}; -use crate::{utils::tts_until_semi, VariableAttributes}; -use proc_macro2::{Span, TokenStream}; +use crate::{Expr, SolIdent, Spanned, Storage, Type, VariableAttributes}; +use proc_macro2::Span; use std::fmt::{self, Write}; use syn::{ ext::IdentExt, @@ -11,9 +10,7 @@ use syn::{ mod list; pub use list::{FieldList, ParameterList, Parameters}; -/// A variable declaration. -/// -/// ` [storage] ` +/// A variable declaration: `string memory hello`. #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct VariableDeclaration { /// The attributes of the variable. @@ -23,7 +20,7 @@ pub struct VariableDeclaration { /// The storage location of the variable, if any. pub storage: Option, /// The name of the variable. This is always Some if parsed as part of - /// [`Parameters`]. + /// [`Parameters`] or a [`Stmt`][crate::Stmt]. pub name: Option, } @@ -48,17 +45,8 @@ impl Parse for VariableDeclaration { } } -impl VariableDeclaration { - pub const fn new(ty: Type) -> Self { - Self { - attrs: Vec::new(), - ty, - storage: None, - name: None, - } - } - - pub fn span(&self) -> Span { +impl Spanned for VariableDeclaration { + fn span(&self) -> Span { let span = self.ty.span(); match (&self.storage, &self.name) { (Some(storage), None) => span.join(storage.span()), @@ -68,7 +56,7 @@ impl VariableDeclaration { .unwrap_or(span) } - pub fn set_span(&mut self, span: Span) { + fn set_span(&mut self, span: Span) { self.ty.set_span(span); if let Some(storage) = &mut self.storage { storage.set_span(span); @@ -77,6 +65,17 @@ impl VariableDeclaration { name.set_span(span); } } +} + +impl VariableDeclaration { + pub const fn new(ty: Type) -> Self { + Self { + attrs: Vec::new(), + ty, + storage: None, + name: None, + } + } /// Formats `self` as an EIP-712 field: ` ` pub fn fmt_eip712(&self, f: &mut impl Write) -> fmt::Result { @@ -87,17 +86,16 @@ impl VariableDeclaration { Ok(()) } - pub fn parse_for_struct(input: ParseStream<'_>) -> Result { + pub fn parse_with_name(input: ParseStream<'_>) -> Result { Self::_parse(input, true) } - fn _parse(input: ParseStream<'_>, for_struct: bool) -> Result { + fn _parse(input: ParseStream<'_>, require_name: bool) -> Result { Ok(Self { attrs: input.call(Attribute::parse_outer)?, ty: input.parse()?, storage: input.call(Storage::parse_opt)?, - // structs must have field names - name: if for_struct || input.peek(Ident::peek_any) { + name: if require_name || input.peek(Ident::peek_any) { Some(input.parse()?) } else { None @@ -111,8 +109,7 @@ pub struct VariableDefinition { pub ty: Type, pub attributes: VariableAttributes, pub name: SolIdent, - // TODO: Expr - pub initializer: Option<(Token![=], TokenStream)>, + pub initializer: Option<(Token![=], Expr)>, pub semi_token: Token![;], } @@ -123,7 +120,7 @@ impl Parse for VariableDefinition { attributes: input.parse()?, name: input.parse()?, initializer: if input.peek(Token![=]) { - Some((input.parse()?, tts_until_semi(input))) + Some((input.parse()?, input.parse()?)) } else { None }, @@ -132,6 +129,18 @@ impl Parse for VariableDefinition { } } +impl Spanned for VariableDefinition { + fn span(&self) -> Span { + let span = self.ty.span(); + span.join(self.semi_token.span).unwrap_or(span) + } + + fn set_span(&mut self, span: Span) { + self.ty.set_span(span); + self.semi_token.span = span; + } +} + impl VariableDefinition { pub fn as_declaration(&self) -> VariableDeclaration { VariableDeclaration { @@ -141,14 +150,4 @@ impl VariableDefinition { name: Some(self.name.clone()), } } - - pub fn span(&self) -> Span { - let span = self.ty.span(); - span.join(self.semi_token.span).unwrap_or(span) - } - - pub fn set_span(&mut self, span: Span) { - self.ty.set_span(span); - self.semi_token.span = span; - } } diff --git a/crates/syn-solidity/src/yul/block.rs b/crates/syn-solidity/src/yul/block.rs new file mode 100644 index 000000000..75b933c31 --- /dev/null +++ b/crates/syn-solidity/src/yul/block.rs @@ -0,0 +1,45 @@ +use crate::Spanned; +use proc_macro2::{Span, TokenStream}; +use std::fmt; +use syn::{ + braced, + parse::{Parse, ParseStream}, + token::Brace, + Result, +}; + +#[derive(Clone)] +pub struct YulBlock { + pub brace_token: Brace, + // TODO + pub stmts: TokenStream, +} + +impl fmt::Debug for YulBlock { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("YulBlock") + // .field("stmts", &self.stmts) + .field("stmts", &self.stmts.to_string()) + .finish() + } +} + +impl Parse for YulBlock { + fn parse(input: ParseStream<'_>) -> Result { + let content; + Ok(Self { + brace_token: braced!(content in input), + stmts: content.parse()?, + }) + } +} + +impl Spanned for YulBlock { + fn span(&self) -> Span { + self.brace_token.span.join() + } + + fn set_span(&mut self, span: Span) { + self.brace_token = Brace(span); + } +} diff --git a/crates/syn-solidity/src/yul/mod.rs b/crates/syn-solidity/src/yul/mod.rs new file mode 100644 index 000000000..c1265f967 --- /dev/null +++ b/crates/syn-solidity/src/yul/mod.rs @@ -0,0 +1,2 @@ +mod block; +pub use block::YulBlock; diff --git a/crates/syn-solidity/tests/contracts.rs b/crates/syn-solidity/tests/contracts.rs index c11c1d0ce..3a75d1ef5 100644 --- a/crates/syn-solidity/tests/contracts.rs +++ b/crates/syn-solidity/tests/contracts.rs @@ -10,10 +10,11 @@ fn contracts() { .parent() .unwrap(); - let files: Vec<_> = fs::read_dir(PATH) + let mut files: Vec<_> = fs::read_dir(PATH) .unwrap() .collect::>() .unwrap(); + files.sort_by_key(|e| e.path()); let patches = files .iter() .filter(|p| p.path().extension() == Some("patch".as_ref())); @@ -36,13 +37,20 @@ fn contracts() { let mut failed = false; for file in files { let path = file.path(); - match parse_file(&path) { - Ok(()) => {} - Err(e) => { - let name = path.file_name().unwrap().to_str().unwrap(); + let name = path.file_name().unwrap().to_str().unwrap(); + match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| parse_file(&path))) { + Ok(Ok(())) => {} + Ok(Err(e)) => { eprintln!("failed to parse {name}: {e} ({e:?})"); failed = true; } + Err(_) => { + eprintln!("panicked while parsing {name}"); + failed = true; + } + } + if failed { + break } } diff --git a/crates/syn-solidity/tests/ident.rs b/crates/syn-solidity/tests/ident.rs index 367f211db..ef7594db3 100644 --- a/crates/syn-solidity/tests/ident.rs +++ b/crates/syn-solidity/tests/ident.rs @@ -1,7 +1,4 @@ -use syn_solidity::{SolIdent, SolPath}; - -#[macro_use] -mod macros; +use syn_solidity::{sol_path, SolIdent, SolPath}; #[test] fn ident() { @@ -12,7 +9,7 @@ fn ident() { #[test] fn ident_path() { let path: SolPath = syn::parse_str("a.b.c").unwrap(); - assert_eq!(path, path![a, b, c]); + assert_eq!(path, sol_path!["a", "b", "c"]); } #[test] diff --git a/crates/syn-solidity/tests/macros.rs b/crates/syn-solidity/tests/macros.rs deleted file mode 100644 index 4d6a287c5..000000000 --- a/crates/syn-solidity/tests/macros.rs +++ /dev/null @@ -1,9 +0,0 @@ -#![allow(unused_macros, unused_macro_rules)] - -macro_rules! path { - ($($e:ident),* $(,)?) => {{ - let mut path = syn_solidity::SolPath::new(); - $(path.push(syn_solidity::SolIdent::new(stringify!($e)));)+ - path - }} -}