@@ -10,10 +10,8 @@ use crate::{
1010 solc:: { SolcCompiler , SolcVersionedInput } ,
1111 Compiler , ProjectPathsConfig , Result ,
1212} ;
13- use alloy_primitives:: hex;
14- use foundry_compilers_artifacts:: { SolcLanguage , Source } ;
13+ use foundry_compilers_artifacts:: SolcLanguage ;
1514use foundry_compilers_core:: { error:: SolcError , utils} ;
16- use md5:: Digest ;
1715use solar_parse:: {
1816 ast:: { FunctionKind , ItemKind , Span , Visibility } ,
1917 interface:: { diagnostics:: EmittedDiagnostics , source_map:: FileName , Session , SourceMap } ,
@@ -48,7 +46,7 @@ impl Preprocessor<SolcCompiler> for TestOptimizerPreprocessor {
4846 ) -> Result < ( ) > {
4947 let sources = & mut input. input . sources ;
5048 // Skip if we are not preprocessing any tests or scripts. Avoids unnecessary AST parsing.
51- if sources. iter ( ) . all ( |( path, _) | !is_test_or_script ( path, paths ) ) {
49+ if sources. iter ( ) . all ( |( path, _) | !paths . is_test_or_script ( path) ) {
5250 trace ! ( "no tests or sources to preprocess" ) ;
5351 return Ok ( ( ) ) ;
5452 }
@@ -72,7 +70,7 @@ impl Preprocessor<SolcCompiler> for TestOptimizerPreprocessor {
7270 // Add the sources into the context.
7371 let mut preprocessed_paths = vec ! [ ] ;
7472 for ( path, source) in sources. iter ( ) {
75- if is_test_or_script ( path, paths ) {
73+ if paths . is_test_or_script ( path) {
7674 if let Ok ( src_file) =
7775 sess. source_map ( ) . new_source_file ( path. clone ( ) , source. content . as_str ( ) )
7876 {
@@ -136,91 +134,86 @@ impl Preprocessor<MultiCompiler> for TestOptimizerPreprocessor {
136134 }
137135}
138136
139- /// Helper function to compute hash of [`interface_representation`] of the source.
140- pub ( crate ) fn interface_representation_hash ( source : & Source , file : & Path ) -> String {
141- let Ok ( repr) = interface_representation ( & source. content , file) else {
142- return source. content_hash ( ) ;
143- } ;
144- let mut hasher = md5:: Md5 :: new ( ) ;
145- hasher. update ( & repr) ;
146- let result = hasher. finalize ( ) ;
147- hex:: encode ( result)
137+ pub ( crate ) fn parse_one_source < R > (
138+ content : & str ,
139+ path : & Path ,
140+ f : impl FnOnce ( solar_sema:: ast:: SourceUnit < ' _ > ) -> R ,
141+ ) -> Result < R , EmittedDiagnostics > {
142+ let sess = Session :: builder ( ) . with_buffer_emitter ( Default :: default ( ) ) . build ( ) ;
143+ let res = sess. enter ( || -> solar_parse:: interface:: Result < _ > {
144+ let arena = solar_parse:: ast:: Arena :: new ( ) ;
145+ let filename = FileName :: Real ( path. to_path_buf ( ) ) ;
146+ let mut parser = Parser :: from_source_code ( & sess, & arena, filename, content. to_string ( ) ) ?;
147+ let ast = parser. parse_file ( ) . map_err ( |e| e. emit ( ) ) ?;
148+ Ok ( f ( ast) )
149+ } ) ;
150+
151+ // Return if any diagnostics emitted during content parsing.
152+ if let Err ( err) = sess. emitted_errors ( ) . unwrap ( ) {
153+ trace ! ( "failed parsing {path:?}:\n {err}" ) ;
154+ return Err ( err) ;
155+ }
156+
157+ Ok ( res. unwrap ( ) )
148158}
149159
150160/// Helper function to remove parts of the contract which do not alter its interface:
151161/// - Internal functions
152162/// - External functions bodies
153163///
154164/// Preserves all libraries and interfaces.
155- fn interface_representation ( content : & str , file : & Path ) -> Result < String , EmittedDiagnostics > {
165+ pub ( crate ) fn interface_representation_ast (
166+ content : & str ,
167+ ast : & solar_parse:: ast:: SourceUnit < ' _ > ,
168+ ) -> String {
156169 let mut spans_to_remove: Vec < Span > = Vec :: new ( ) ;
157- let sess = Session :: builder ( ) . with_buffer_emitter ( Default :: default ( ) ) . build ( ) ;
158- sess. enter ( || {
159- let arena = solar_parse:: ast:: Arena :: new ( ) ;
160- let filename = FileName :: Real ( file. to_path_buf ( ) ) ;
161- let Ok ( mut parser) = Parser :: from_source_code ( & sess, & arena, filename, content. to_string ( ) )
162- else {
163- return ;
170+ for item in ast. items . iter ( ) {
171+ let ItemKind :: Contract ( contract) = & item. kind else {
172+ continue ;
164173 } ;
165- let Ok ( ast) = parser. parse_file ( ) . map_err ( |e| e. emit ( ) ) else { return } ;
166- for item in ast. items {
167- let ItemKind :: Contract ( contract) = & item. kind else {
168- continue ;
169- } ;
170-
171- if contract. kind . is_interface ( ) || contract. kind . is_library ( ) {
172- continue ;
173- }
174174
175- for contract_item in contract. body . iter ( ) {
176- if let ItemKind :: Function ( function) = & contract_item. kind {
177- let is_exposed = match function. kind {
178- // Function with external or public visibility
179- FunctionKind :: Function => {
180- function. header . visibility >= Some ( Visibility :: Public )
181- }
182- FunctionKind :: Constructor
183- | FunctionKind :: Fallback
184- | FunctionKind :: Receive => true ,
185- FunctionKind :: Modifier => false ,
186- } ;
187-
188- // If function is not exposed we remove the entire span (signature and
189- // body). Otherwise we keep function signature and
190- // remove only the body.
191- if !is_exposed {
192- spans_to_remove. push ( contract_item. span ) ;
193- } else {
194- spans_to_remove. push ( function. body_span ) ;
175+ if contract. kind . is_interface ( ) || contract. kind . is_library ( ) {
176+ continue ;
177+ }
178+
179+ for contract_item in contract. body . iter ( ) {
180+ if let ItemKind :: Function ( function) = & contract_item. kind {
181+ let is_exposed = match function. kind {
182+ // Function with external or public visibility
183+ FunctionKind :: Function => {
184+ function. header . visibility >= Some ( Visibility :: Public )
185+ }
186+ FunctionKind :: Constructor | FunctionKind :: Fallback | FunctionKind :: Receive => {
187+ true
195188 }
189+ FunctionKind :: Modifier => false ,
190+ } ;
191+
192+ // If function is not exposed we remove the entire span (signature and
193+ // body). Otherwise we keep function signature and
194+ // remove only the body.
195+ if !is_exposed {
196+ spans_to_remove. push ( contract_item. span ) ;
197+ } else {
198+ spans_to_remove. push ( function. body_span ) ;
196199 }
197200 }
198201 }
199- } ) ;
200-
201- // Return if any diagnostics emitted during content parsing.
202- if let Err ( err) = sess. emitted_errors ( ) . unwrap ( ) {
203- trace ! ( "failed parsing {file:?}: {err}" ) ;
204- return Err ( err) ;
205202 }
206-
207203 let content =
208204 replace_source_content ( content, spans_to_remove. iter ( ) . map ( |span| ( span. to_range ( ) , "" ) ) )
209205 . replace ( "\n " , "" ) ;
210- Ok ( utils:: RE_TWO_OR_MORE_SPACES . replace_all ( & content, "" ) . to_string ( ) )
211- }
212-
213- /// Checks if the given path is a test/script file.
214- fn is_test_or_script < L > ( path : & Path , paths : & ProjectPathsConfig < L > ) -> bool {
215- let test_dir = paths. tests . strip_prefix ( & paths. root ) . unwrap_or ( & paths. root ) ;
216- let script_dir = paths. scripts . strip_prefix ( & paths. root ) . unwrap_or ( & paths. root ) ;
217- path. starts_with ( test_dir) || path. starts_with ( script_dir)
206+ utils:: RE_TWO_OR_MORE_SPACES . replace_all ( & content, "" ) . into_owned ( )
218207}
219208
220209#[ cfg( test) ]
221210mod tests {
222211 use super :: * ;
223- use std:: path:: PathBuf ;
212+
213+ fn interface_representation ( content : & str ) -> String {
214+ parse_one_source ( content, Path :: new ( "" ) , |ast| interface_representation_ast ( content, & ast) )
215+ . unwrap ( )
216+ }
224217
225218 #[ test]
226219 fn test_interface_representation ( ) {
@@ -242,7 +235,7 @@ contract A {
242235 }
243236}"# ;
244237
245- let result = interface_representation ( content, & PathBuf :: new ( ) ) . unwrap ( ) ;
238+ let result = interface_representation ( content) ;
246239 assert_eq ! (
247240 result,
248241 r#"library Lib {function libFn() internal {// logic to keep}}contract A {function a() externalfunction b() publicfunction e() external }"#
0 commit comments