Skip to content

Commit ab89fe4

Browse files
committed
[hermes] Discover mod foo; declarations
gherrit-pr-id: G93b59d9ea956b7e2e06b2e91c45dea803a391801
1 parent 7ee6fde commit ab89fe4

File tree

5 files changed

+93
-30
lines changed

5 files changed

+93
-30
lines changed

tools/hermes/src/main.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ fn main() {
3535
let mut has_errors = false;
3636
for (package, kind, path) in roots.roots {
3737
let mut edits = Vec::new();
38-
let res = parse::read_file_and_visit_hermes_items(&path, |_src, res| {
38+
let res = parse::read_file_and_scan_compilation_unit(&path, |_src, res| {
3939
if let Err(e) = res {
4040
has_errors = true;
4141
eprint!("{:?}", miette::Report::new(e));
@@ -44,7 +44,7 @@ fn main() {
4444
}
4545
});
4646

47-
let source = res.unwrap_or_else(|e| {
47+
let (source, unloaded_modules) = res.unwrap_or_else(|e| {
4848
eprintln!("Error parsing file: {}", e);
4949
exit(1);
5050
});

tools/hermes/src/parse.rs

Lines changed: 88 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -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>
7687
where
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>
86102
where
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>
153176
where
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>
194218
where
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+
330381
fn 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()) {

tools/hermes/src/shadow.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ pub fn create_shadow_skeleton(
1717
target_dir: &Path,
1818
skip_paths: &HashSet<PathBuf>,
1919
) -> Result<()> {
20+
// TODO: Can't do this here – need to do it before we start parsing/transforming/copying.
2021
if dest_root.exists() {
2122
std::fs::remove_dir_all(&dest_root)?;
2223
}

tools/hermes/src/transform.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ mod tests {
7878
}
7979
";
8080
let mut items = Vec::new();
81-
crate::parse::visit_hermes_items(source, |_src, res| items.push(res));
81+
crate::parse::scan_compilation_unit(source, |_src, res| items.push(res));
8282

8383
let item = items.into_iter().next().unwrap().unwrap();
8484
let mut edits = Vec::new();

tools/hermes/src/ui_test_shim.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ pub fn run() {
3636
// Run logic with JSON emitter
3737
let mut has_errors = false;
3838

39-
parse::read_file_and_visit_hermes_items(&file_path, |source, res| {
39+
parse::read_file_and_scan_compilation_unit(&file_path, |source, res| {
4040
if let Err(e) = res {
4141
has_errors = true;
4242
emit_rustc_json(&e, source, file_path.to_str().unwrap());

0 commit comments

Comments
 (0)