@@ -18,42 +18,22 @@ impl SolInput {
1818
1919 let mut abi = abi. ok_or_else ( || syn:: Error :: new ( name. span ( ) , "ABI not found in JSON" ) ) ?;
2020 let sol = abi_to_sol ( & name, & mut abi) ;
21- let mut all_tokens = tokens_for_sol ( & name, & sol) ?. into_iter ( ) ;
21+ let all_tokens = tokens_for_sol ( & name, & sol) ?;
22+ let mut ast: ast:: File = syn:: parse2 ( all_tokens) . map_err ( |e| {
23+ let msg = format ! (
24+ "failed to parse ABI-generated tokens into a Solidity AST for `{name}`: {e}.\n \
25+ This is a bug. We would appreciate a bug report: \
26+ https://github.com/alloy-rs/core/issues/new/choose"
27+ ) ;
28+ syn:: Error :: new ( name. span ( ) , msg)
29+ } ) ?;
2230
2331 let ( inner_attrs, attrs) = attrs
2432 . into_iter ( )
2533 . partition :: < Vec < _ > , _ > ( |attr| matches ! ( attr. style, AttrStyle :: Inner ( _) ) ) ;
2634
2735 let ( derives, sol_derives) = extract_derive_attrs ( & attrs) ;
2836
29- let mut library_tokens_iter = all_tokens
30- . by_ref ( )
31- . take_while ( |tt| !matches ! ( tt, TokenTree :: Ident ( id) if id == "interface" ) )
32- . skip_while ( |tt| matches ! ( tt, TokenTree :: Ident ( id) if id == "library" ) )
33- . peekable ( ) ;
34-
35- let library_tokens = library_tokens_iter. by_ref ( ) ;
36-
37- let mut libraries = Vec :: new ( ) ;
38-
39- while library_tokens. peek ( ) . is_some ( ) {
40- let sol_library_tokens: TokenStream = std:: iter:: once ( TokenTree :: Ident ( id ( "library" ) ) )
41- . chain (
42- library_tokens
43- . take_while ( |tt| !matches ! ( tt, TokenTree :: Ident ( id) if id == "library" ) ) ,
44- )
45- . collect ( ) ;
46-
47- let tokens = quote ! {
48- #( #derives) *
49- #( #sol_derives) *
50- #sol_library_tokens
51- } ;
52-
53- libraries. push ( tokens) ;
54- }
55- let sol_interface_tokens: TokenStream =
56- std:: iter:: once ( TokenTree :: Ident ( id ( "interface" ) ) ) . chain ( all_tokens) . collect ( ) ;
5737 let bytecode = bytecode. map ( |bytes| {
5838 let s = bytes. to_string ( ) ;
5939 quote ! ( bytecode = #s, )
@@ -63,44 +43,34 @@ impl SolInput {
6343 quote ! ( deployed_bytecode = #s)
6444 } ) ;
6545
66- let attrs_iter = attrs. iter ( ) ;
67- let doc_str = format ! (
68- "\n \n \
69- Generated by the following Solidity interface...
70- ```solidity
71- {sol}
72- ```
73-
74- ...which was generated by the following JSON ABI:
75- ```json
76- {json_s}
77- ```" ,
78- json_s = serde_json:: to_string_pretty( & abi) . unwrap( )
79- ) ;
80- let tokens = quote ! {
81- #( #inner_attrs) *
82- #( #libraries) *
83-
84- #( #attrs_iter) *
85- #[ doc = #doc_str]
86- #[ sol( #bytecode #deployed_bytecode) ]
87- #sol_interface_tokens
46+ let ctx = ApplyAttrsCtx {
47+ derives : & derives,
48+ sol_derives : & sol_derives,
49+ interface_attrs : & attrs,
50+ bytecode : bytecode. as_ref ( ) ,
51+ deployed_bytecode : deployed_bytecode. as_ref ( ) ,
52+ sol : & sol,
53+ abi : & abi,
8854 } ;
89-
90- let ast: ast:: File = syn:: parse2 ( tokens) . map_err ( |e| {
91- let msg = format ! (
92- "failed to parse ABI-generated tokens into a Solidity AST for `{name}`: {e}.\n \
93- This is a bug. We would appreciate a bug report: \
94- https://github.com/alloy-rs/core/issues/new/choose"
95- ) ;
96- syn:: Error :: new ( name. span ( ) , msg)
97- } ) ?;
55+ apply_attrs_to_items ( & mut ast. items , & ctx) ;
56+ ast. attrs . extend ( inner_attrs) ;
9857
9958 let kind = SolInputKind :: Sol ( ast) ;
10059 Ok ( SolInput { attrs, path, kind } )
10160 }
10261}
10362
63+ /// Shared context for applying user attributes to ABI-derived items.
64+ struct ApplyAttrsCtx < ' a > {
65+ derives : & ' a [ & ' a syn:: Attribute ] ,
66+ sol_derives : & ' a [ & ' a syn:: Attribute ] ,
67+ interface_attrs : & ' a [ syn:: Attribute ] ,
68+ bytecode : Option < & ' a TokenStream > ,
69+ deployed_bytecode : Option < & ' a TokenStream > ,
70+ sol : & ' a str ,
71+ abi : & ' a JsonAbi ,
72+ }
73+
10474// doesn't parse Json
10575
10676fn abi_to_sol ( name : & Ident , abi : & mut JsonAbi ) -> String {
@@ -153,18 +123,87 @@ fn extract_derive_attrs(attrs: &[syn::Attribute]) -> (Vec<&syn::Attribute>, Vec<
153123 } )
154124}
155125
156- #[ inline]
157- #[ track_caller]
158- fn id ( s : impl AsRef < str > ) -> Ident {
159- // Ident::new panics on Rust keywords and `r#` prefixes
160- syn:: parse_str ( s. as_ref ( ) ) . unwrap ( )
126+ /// Applies derive/`sol` attributes to ABI-derived items.
127+ ///
128+ /// - Non-interface contracts, structs, enums, and UDVTs get the user-specified derive and `sol`
129+ /// attributes cloned onto them.
130+ /// - The single interface gets the outer attributes, a generated doc (including the original
131+ /// Solidity/JSON ABI), and the `#[sol(bytecode = ..., deployed_bytecode = ...)]` attribute.
132+ fn apply_attrs_to_items ( items : & mut [ ast:: Item ] , ctx : & ApplyAttrsCtx < ' _ > ) {
133+ for item in items {
134+ match item {
135+ ast:: Item :: Contract ( contract) if contract. kind . is_interface ( ) => {
136+ apply_interface_attrs ( contract, ctx) ;
137+ }
138+ ast:: Item :: Contract ( contract) => {
139+ extend_attrs ( & mut contract. attrs , ctx. derives , ctx. sol_derives ) ;
140+ }
141+ ast:: Item :: Struct ( strukt) => {
142+ extend_attrs ( & mut strukt. attrs , ctx. derives , ctx. sol_derives ) ;
143+ }
144+ ast:: Item :: Udt ( udt) => {
145+ extend_attrs ( & mut udt. attrs , ctx. derives , ctx. sol_derives ) ;
146+ }
147+ // Globals from `to_sol` are only structs, UDVTs; enums are flattened to `uint8`,
148+ // while errors/functions/events,etc are emitted in the interface.
149+ _ => debug_assert ! ( false , "unexpected global item type" ) ,
150+ }
151+ }
152+ }
153+
154+ /// Merge user outer attrs with generated docs/metadata for the sole interface.
155+ fn apply_interface_attrs ( contract : & mut ast:: ItemContract , ctx : & ApplyAttrsCtx < ' _ > ) {
156+ let bytecode = ctx. bytecode ;
157+ let deployed_bytecode = ctx. deployed_bytecode ;
158+ let doc_str = format ! (
159+ "\n \n \
160+ Generated by the following Solidity interface...\
161+ ```solidity\
162+ {sol}\
163+ ```\
164+ \n \
165+ ...which was generated by the following JSON ABI:\
166+ ```json\
167+ {json_s}\
168+ ```",
169+ sol = ctx. sol,
170+ json_s = serde_json:: to_string_pretty( ctx. abi) . unwrap( ) ,
171+ ) ;
172+ let doc_attr: syn:: Attribute = syn:: parse_quote!( #[ doc = #doc_str] ) ;
173+ let sol_attr: syn:: Attribute = syn:: parse_quote!( #[ sol( #bytecode #deployed_bytecode) ] ) ;
174+
175+ let mut merged = ctx. interface_attrs . to_vec ( ) ;
176+ merged. push ( doc_attr) ;
177+ merged. push ( sol_attr) ;
178+ contract. attrs = merged;
179+ }
180+
181+ /// Clone user-specified `derive`/`sol(...)` attributes onto the given item.
182+ /// Used for globals (structs/UDVTs) and non-interface contracts emitted by `to_sol`.
183+ fn extend_attrs (
184+ attrs : & mut Vec < syn:: Attribute > ,
185+ derives : & [ & syn:: Attribute ] ,
186+ sol_derives : & [ & syn:: Attribute ] ,
187+ ) {
188+ attrs. reserve ( derives. len ( ) + sol_derives. len ( ) ) ;
189+ for attr in derives {
190+ attrs. push ( ( * attr) . clone ( ) ) ;
191+ }
192+ for attr in sol_derives {
193+ attrs. push ( ( * attr) . clone ( ) ) ;
194+ }
161195}
162196
163197#[ cfg( test) ]
164198mod tests {
165199 use super :: * ;
166200 use std:: path:: { Path , PathBuf } ;
167201
202+ fn id ( s : impl AsRef < str > ) -> Ident {
203+ // Ident::new panics on Rust keywords and `r#` prefixes
204+ syn:: parse_str ( s. as_ref ( ) ) . unwrap ( )
205+ }
206+
168207 #[ test]
169208 #[ cfg_attr( miri, ignore = "no fs" ) ]
170209 fn abi ( ) {
0 commit comments