@@ -67,33 +67,54 @@ pub struct ParsedLeanItem {
6767 source_file : Option < PathBuf > ,
6868}
6969
70+ /// Represents a `mod foo;` declaration found in the source.
71+ #[ derive( Debug , Clone ) ]
72+ pub struct UnloadedModule {
73+ pub name : String ,
74+ /// The value of `#[path = "..."]` if present.
75+ pub path_attr : Option < String > ,
76+ pub span : proc_macro2:: Span ,
77+ }
78+
7079/// Parses the given Rust source code and invokes the callback `f` for each item
7180/// annotated with a `/// ```lean` block.
7281///
82+ /// While parsing, collects every `mod foo;` declaration and returns them all.
83+ ///
7384/// If parsing fails, or if any item has multiple Lean blocks, the callback is
7485/// invoked with an `Err`.
75- pub fn visit_hermes_items < F > ( source : & str , f : F )
86+ pub fn scan_compilation_unit < F > ( source : & str , f : F ) -> Vec < UnloadedModule >
7687where
7788 F : FnMut ( & str , Result < ParsedLeanItem , HermesError > ) ,
7889{
79- visit_hermes_items_internal ( source, None , f)
90+ let mut unloaded_modules = Vec :: new ( ) ;
91+ scan_compilation_unit_internal ( source, None , f, |m| unloaded_modules. push ( m) ) ;
92+ unloaded_modules
8093}
8194
82- /// Parses the given Rust source code from a file path and invokes the callback `f`
83- /// for each item annotated with a `/// ```lean` block. Parsing errors and generated
84- /// items will be associated with this file path.
85- pub fn read_file_and_visit_hermes_items < F > ( path : & Path , f : F ) -> Result < String , io:: Error >
95+ /// Like [`scan_compilation_unit`], but reads the source code from a file path.
96+ ///
97+ /// Parsing errors and generated items will be associated with this file path.
98+ pub fn read_file_and_scan_compilation_unit < F > (
99+ path : & Path ,
100+ f : F ,
101+ ) -> Result < ( String , Vec < UnloadedModule > ) , io:: Error >
86102where
87103 F : FnMut ( & str , Result < ParsedLeanItem , HermesError > ) ,
88104{
89105 let source = fs:: read_to_string ( path) . expect ( "Failed to read file" ) ;
90- visit_hermes_items_internal ( & source, Some ( path . to_path_buf ( ) ) , f) ;
91- Ok ( source)
106+ let unloaded_modules = scan_compilation_unit ( & source, f) ;
107+ Ok ( ( source, unloaded_modules ) )
92108}
93109
94- fn visit_hermes_items_internal < F > ( source : & str , source_file : Option < PathBuf > , mut f : F )
95- where
96- F : FnMut ( & str , Result < ParsedLeanItem , HermesError > ) ,
110+ fn scan_compilation_unit_internal < I , M > (
111+ source : & str ,
112+ source_file : Option < PathBuf > ,
113+ mut item_cb : I ,
114+ mod_cb : M ,
115+ ) where
116+ I : FnMut ( & str , Result < ParsedLeanItem , HermesError > ) ,
117+ M : FnMut ( UnloadedModule ) ,
97118{
98119 trace ! ( "Parsing source code into syn::File" ) ;
99120 let file_name = {
@@ -116,7 +137,7 @@ where
116137 }
117138 Err ( e) => {
118139 debug ! ( "Failed to parse source code: {}" , e) ;
119- f (
140+ item_cb (
120141 source,
121142 Err ( HermesError :: SynError {
122143 src : named_source. clone ( ) ,
@@ -131,7 +152,8 @@ where
131152 trace ! ( "Initializing HermesVisitor to traverse AST" ) ;
132153 let mut visitor = HermesVisitor {
133154 current_path : Vec :: new ( ) ,
134- callback : f,
155+ item_cb,
156+ mod_cb,
135157 source_file,
136158 source_code : source. to_string ( ) ,
137159 named_source,
@@ -141,17 +163,19 @@ where
141163 trace ! ( "Finished traversing AST" ) ;
142164}
143165
144- struct HermesVisitor < F > {
166+ struct HermesVisitor < I , M > {
145167 current_path : Vec < String > ,
146- callback : F ,
168+ item_cb : I ,
169+ mod_cb : M ,
147170 source_file : Option < PathBuf > ,
148171 source_code : String ,
149172 named_source : NamedSource < String > ,
150173}
151174
152- impl < F > HermesVisitor < F >
175+ impl < I , M > HermesVisitor < I , M >
153176where
154- F : FnMut ( & str , Result < ParsedLeanItem , HermesError > ) ,
177+ I : FnMut ( & str , Result < ParsedLeanItem , HermesError > ) ,
178+ M : FnMut ( UnloadedModule ) ,
155179{
156180 fn check_and_add ( & mut self , item : ParsedItem , span : Span ) {
157181 let Range { start, end } = span. byte_range ( ) ;
@@ -162,7 +186,7 @@ where
162186 match extract_lean_block ( attrs) {
163187 Ok ( Some ( lean_block) ) => {
164188 debug ! ( "Found valid ```lean block for item in `{:?}`" , self . current_path) ;
165- ( self . callback ) (
189+ ( self . item_cb ) (
166190 source,
167191 Ok ( ParsedLeanItem {
168192 item,
@@ -177,7 +201,7 @@ where
177201 } // Skip item
178202 Err ( e) => {
179203 debug ! ( "Error extracting ```lean block: {}" , e) ;
180- ( self . callback ) (
204+ ( self . item_cb ) (
181205 source,
182206 Err ( HermesError :: DocBlockError {
183207 src : self . named_source . clone ( ) ,
@@ -190,12 +214,23 @@ where
190214 }
191215}
192216
193- impl < ' ast , F > Visit < ' ast > for HermesVisitor < F >
217+ impl < ' ast , I , M > Visit < ' ast > for HermesVisitor < I , M >
194218where
195- F : FnMut ( & str , Result < ParsedLeanItem , HermesError > ) ,
219+ I : FnMut ( & str , Result < ParsedLeanItem , HermesError > ) ,
220+ M : FnMut ( UnloadedModule ) ,
196221{
197222 fn visit_item_mod ( & mut self , node : & ' ast ItemMod ) {
198223 let mod_name = node. ident . to_string ( ) ;
224+
225+ // Check for unloaded modules (mod foo;)
226+ if node. content . is_none ( ) {
227+ ( self . mod_cb ) ( UnloadedModule {
228+ name : mod_name. clone ( ) ,
229+ path_attr : extract_path_attr ( & node. attrs ) ,
230+ span : node. span ( ) ,
231+ } ) ;
232+ }
233+
199234 trace ! ( "Entering module: {}" , mod_name) ;
200235 self . current_path . push ( mod_name) ;
201236 syn:: visit:: visit_item_mod ( self , node) ;
@@ -327,6 +362,22 @@ fn extract_lean_block(attrs: &[Attribute]) -> Result<Option<String>, Error> {
327362 }
328363}
329364
365+ /// Extracts the `...` from the first `#[path = "..."]` attribute found, if any.
366+ fn extract_path_attr ( attrs : & [ Attribute ] ) -> Option < String > {
367+ for attr in attrs {
368+ if attr. path ( ) . is_ident ( "path" ) {
369+ if let Meta :: NameValue ( nv) = & attr. meta {
370+ if let Expr :: Lit ( expr_lit) = & nv. value {
371+ if let Lit :: Str ( lit_str) = & expr_lit. lit {
372+ return Some ( lit_str. value ( ) ) ;
373+ }
374+ }
375+ }
376+ }
377+ }
378+ None
379+ }
380+
330381fn span_to_miette ( span : proc_macro2:: Span ) -> SourceSpan {
331382 let range = span. byte_range ( ) ;
332383 SourceSpan :: new ( range. start . into ( ) , range. end - range. start )
@@ -340,7 +391,7 @@ mod tests {
340391
341392 fn parse_to_vec ( code : & str ) -> Vec < ( String , Result < ParsedLeanItem , HermesError > ) > {
342393 let mut items = Vec :: new ( ) ;
343- visit_hermes_items ( code, |src, res| items. push ( ( src. to_string ( ) , res) ) ) ;
394+ scan_compilation_unit ( code, |src, res| items. push ( ( src. to_string ( ) , res) ) ) ;
344395 items
345396 }
346397
@@ -444,10 +495,11 @@ mod tests {
444495 fn foo() {}
445496 "# ;
446497 let mut items = Vec :: new ( ) ;
447- visit_hermes_items_internal (
498+ scan_compilation_unit_internal (
448499 code,
449500 Some ( Path :: new ( "src/foo.rs" ) . to_path_buf ( ) ) ,
450501 |source : & str , res| items. push ( ( source. to_string ( ) , res) ) ,
502+ |_| { } ,
451503 ) ;
452504 let ( src, res) = items. into_iter ( ) . next ( ) . unwrap ( ) ;
453505 assert_eq ! (
@@ -480,7 +532,7 @@ mod c {
480532}
481533" ;
482534 let mut items = Vec :: new ( ) ;
483- visit_hermes_items ( source, |_src, res| items. push ( res) ) ;
535+ scan_compilation_unit ( source, |_src, res| items. push ( res) ) ;
484536 let i1 = items[ 0 ] . as_ref ( ) . unwrap ( ) ;
485537 let i2 = items[ 1 ] . as_ref ( ) . unwrap ( ) ;
486538
@@ -527,8 +579,18 @@ mod c {
527579 let path = std:: path:: Path :: new ( "src/foo.rs" ) ;
528580 let mut items = Vec :: new ( ) ;
529581
530- visit_hermes_items_internal ( code1, Some ( path. to_path_buf ( ) ) , |_src, res| items. push ( res) ) ;
531- visit_hermes_items_internal ( code2, Some ( path. to_path_buf ( ) ) , |_src, res| items. push ( res) ) ;
582+ scan_compilation_unit_internal (
583+ code1,
584+ Some ( path. to_path_buf ( ) ) ,
585+ |_src, res| items. push ( res) ,
586+ |_| { } ,
587+ ) ;
588+ scan_compilation_unit_internal (
589+ code2,
590+ Some ( path. to_path_buf ( ) ) ,
591+ |_src, res| items. push ( res) ,
592+ |_| { } ,
593+ ) ;
532594
533595 let mut report_string = String :: new ( ) ;
534596 for err in items. into_iter ( ) . filter_map ( |r| r. err ( ) ) {
0 commit comments