Skip to content

Commit ac51f63

Browse files
committed
Merge #363: Move file_id into Span
7b6db6c feat: move `file_id` into `Span` (LesterEvSe) Pull request description: This PR moves `file_id` into `Span` as a structural component of the source location. It also removes the unnecessary `file_id` and `set_file_id` methods from the `parse.rs` file. Refactored the driver code a bit ACKs for top commit: apoelstra: ACK 7b6db6c; successfully ran local tests KyrylR: ACK 7b6db6c; successfully ran local tests Tree-SHA512: e5859cc7329444e5a06d0cdf5fefaca1e61b1cd8c634419a92da82d357687c016cb4d2c22ff0e54a6b251b60126421df20abc901eb6d6f81d424abea1e980302
2 parents 2f3abdf + 7b6db6c commit ac51f63

7 files changed

Lines changed: 216 additions & 242 deletions

File tree

src/driver/mod.rs

Lines changed: 76 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -85,11 +85,15 @@ impl SourceMap {
8585
}
8686
}
8787

88-
fn insert(&mut self, path: CanonPath) -> usize {
88+
fn insert(&mut self, path: CanonPath) {
8989
let id = self.paths.len();
9090
self.ids.insert(path.clone(), id);
9191
self.paths.push(path);
92-
id
92+
debug_assert_eq!(self.ids.len(), self.paths.len());
93+
}
94+
95+
fn len(&self) -> usize {
96+
self.paths.len()
9397
}
9498

9599
pub fn get_id(&self, path: &CanonPath) -> Option<usize> {
@@ -137,10 +141,10 @@ pub(crate) struct DependencyGraph {
137141
/// Fast bidirectional lookup between `CanonPath` and Module IDs.
138142
source_map: SourceMap,
139143

140-
/// Memoized results of [`crate::resolution::DependencyMap::resolve_path`] to avoid
141-
/// resolving the same [`parse::UseDecl`] twice — once during the driver phase
142-
/// and once during the building phase after linearization.
143-
use_cache: HashMap<parse::UseDecl, ResolvedUse>,
144+
/// Memoizes [`crate::resolution::DependencyMap::resolve_path_internal`] results,
145+
/// keyed by the source [`Span`] of each `use` declaration, so the resolver runs
146+
/// once per occurrence and the linearization phase can look the result up directly.
147+
use_cache: HashMap<Span, ResolvedUse>,
144148

145149
// TODO: Consider to optimising this with `Vec` instead of `HashMap`
146150
/// The Adjacency List: Defines the Directed acyclic Graph (DAG) of imports.
@@ -241,45 +245,8 @@ impl DependencyGraph {
241245
Ok((!handler.has_errors()).then_some(graph))
242246
}
243247

244-
/// This helper cleanly encapsulates the process of loading source text, parsing it
245-
/// into an `parse::Program`, and combining them so the compiler can easily work with the file.
246-
/// If the file is missing or contains syntax errors, it logs the diagnostic to the
247-
/// `ErrorCollector` and safely returns `None`.
248-
fn parse_and_get_source_module(
249-
path: &CanonPath,
250-
importer_source: &CanonSourceFile,
251-
span: Span,
252-
handler: &mut ErrorCollector,
253-
unstable_features: &UnstableFeatures,
254-
) -> Option<SourceModule> {
255-
let Ok(content) = std::fs::read_to_string(path.as_path()) else {
256-
let err = RichError::new(
257-
Error::FileNotFound {
258-
filename: PathBuf::from(path.as_path()),
259-
},
260-
span,
261-
)
262-
.with_source(importer_source.clone());
263-
264-
handler.push(err);
265-
return None;
266-
};
267-
268-
let mut error_handler = ErrorCollector::new();
269-
let source = CanonSourceFile::new(path.clone(), Arc::from(content));
270-
271-
let ast = parse::Program::parse_from_str_with_errors(
272-
source.clone(),
273-
unstable_features,
274-
&mut error_handler,
275-
);
276-
277-
if error_handler.has_errors() {
278-
handler.extend_with_handler(source, &error_handler);
279-
None
280-
} else {
281-
ast.map(|program| SourceModule { source, program })
282-
}
248+
pub fn source_map(&self) -> &SourceMap {
249+
&self.source_map
283250
}
284251

285252
/// PHASE 1 OF GRAPH CONSTRUCTION: Resolves all `use` declarations within a single
@@ -292,7 +259,7 @@ impl DependencyGraph {
292259
current_program: &parse::Program,
293260
current_module: &CurrentModule,
294261
dependency_map: &DependencyMap,
295-
use_cache: &mut HashMap<parse::UseDecl, ResolvedUse>,
262+
use_cache: &mut HashMap<Span, ResolvedUse>,
296263
handler: &mut ErrorCollector,
297264
) -> Vec<(CanonPath, Span)> {
298265
let mut ctx = ImportContext {
@@ -330,7 +297,10 @@ impl DependencyGraph {
330297
continue;
331298
}
332299

300+
let new_id = self.source_map.len();
301+
333302
let Some(module) = Self::parse_and_get_source_module(
303+
new_id,
334304
&path,
335305
&current.source,
336306
import_span,
@@ -342,7 +312,7 @@ impl DependencyGraph {
342312
continue;
343313
};
344314

345-
let new_id = self.source_map.insert(path.clone());
315+
self.source_map.insert(path.clone());
346316
self.modules.push(module);
347317

348318
self.dependencies
@@ -353,8 +323,46 @@ impl DependencyGraph {
353323
}
354324
}
355325

356-
pub fn source_map(&self) -> &SourceMap {
357-
&self.source_map
326+
/// This helper cleanly encapsulates the process of loading source text, parsing it
327+
/// into an `parse::Program`, and combining them so the compiler can easily work with the file.
328+
/// If the file is missing or contains syntax errors, it logs the diagnostic to the
329+
/// `ErrorCollector` and safely returns `None`.
330+
fn parse_and_get_source_module(
331+
new_id: usize,
332+
path: &CanonPath,
333+
importer_source: &CanonSourceFile,
334+
span: Span,
335+
handler: &mut ErrorCollector,
336+
unstable_features: &UnstableFeatures,
337+
) -> Option<SourceModule> {
338+
let Ok(content) = std::fs::read_to_string(path.as_path()) else {
339+
let err = RichError::new(
340+
Error::FileNotFound {
341+
filename: PathBuf::from(path.as_path()),
342+
},
343+
span,
344+
)
345+
.with_source(importer_source.clone());
346+
347+
handler.push(err);
348+
return None;
349+
};
350+
351+
let mut error_handler = ErrorCollector::new();
352+
let source = CanonSourceFile::new(path.clone(), Arc::from(content));
353+
let ast = parse::Program::parse_from_str_with_errors(
354+
new_id,
355+
source.clone(),
356+
unstable_features,
357+
&mut error_handler,
358+
);
359+
360+
if error_handler.has_errors() {
361+
handler.extend_with_handler(source, &error_handler);
362+
return None;
363+
}
364+
365+
ast.map(|program| SourceModule { source, program })
358366
}
359367
}
360368

@@ -363,11 +371,27 @@ impl DependencyGraph {
363371
struct ImportContext<'a> {
364372
current: CurrentModule,
365373
dependency_map: &'a DependencyMap,
366-
use_cache: &'a mut HashMap<parse::UseDecl, ResolvedUse>,
374+
use_cache: &'a mut HashMap<Span, ResolvedUse>,
367375
handler: &'a mut ErrorCollector,
368376
}
369377

370378
impl<'a> ImportContext<'a> {
379+
/// Recursively walks an item, collecting resolved imports.
380+
/// Recurses into inline `mod` blocks.
381+
fn process_item(&mut self, item: &parse::Item, valid_imports: &mut Vec<(CanonPath, Span)>) {
382+
match item {
383+
parse::Item::Use(use_decl) => valid_imports.extend(self.resolve_single(use_decl)),
384+
parse::Item::Module(module) => {
385+
for item in module.items() {
386+
self.process_item(item, valid_imports);
387+
}
388+
}
389+
390+
// These items carry no import information at this stage and can be safely skipped.
391+
parse::Item::TypeAlias(_) | parse::Item::Function(_) | parse::Item::Ignored => {}
392+
}
393+
}
394+
371395
/// Resolves a single `use` declaration, caches the result for reuse during
372396
/// later graph construction phases, and returns the resolved path and span.
373397
/// Returns `None` and reports to `handler` if resolution fails.
@@ -384,18 +408,12 @@ impl<'a> ImportContext<'a> {
384408
}
385409
};
386410

387-
// We assign a file ID to prevent collisions between identical `use` statements across different files.
388-
// For instance, `use crate::A::foo;` in the local workspace and in an external dependency
389-
// might resolve to completely different implementations of the `foo` function.
390-
let mut use_decl = use_decl.clone();
391411
let span = *use_decl.span();
392-
use_decl.set_file_id(self.current.id);
393-
394412
let result: (CanonPath, Span) = (resolved.path.clone(), span);
395413

396414
// Since we found an error, when we can reevalute the result, we do not want to break it again
397415
// So, add error to prevent similar cases in the future
398-
if let Some(old_value) = self.use_cache.insert(use_decl, resolved) {
416+
if let Some(old_value) = self.use_cache.insert(span, resolved) {
399417
let msg = format!(
400418
"Reevaluated an existing use_decl. Old value was: {:?}",
401419
old_value
@@ -406,26 +424,6 @@ impl<'a> ImportContext<'a> {
406424
}
407425
Some(result)
408426
}
409-
410-
/// Recursively walks an item, collecting resolved imports.
411-
/// Recurses into inline `mod` blocks.
412-
fn process_item(&mut self, item: &parse::Item, valid_imports: &mut Vec<(CanonPath, Span)>) {
413-
match item {
414-
parse::Item::Use(use_decl) => {
415-
if let Some(import) = self.resolve_single(use_decl) {
416-
valid_imports.push(import);
417-
}
418-
}
419-
parse::Item::Module(module) => {
420-
for item in module.items() {
421-
self.process_item(item, valid_imports);
422-
}
423-
}
424-
425-
// These items carry no import information at this stage and can be safely skipped.
426-
parse::Item::TypeAlias(_) | parse::Item::Function(_) | parse::Item::Ignored => {}
427-
}
428-
}
429427
}
430428

431429
/// Shared mutable state threaded through dependency loading.
@@ -508,6 +506,7 @@ pub(crate) mod tests {
508506
let main_canon_source = CanonSourceFile::new(root_p, Arc::from(root_content));
509507

510508
let main_program_option = parse::Program::parse_from_str_with_errors(
509+
MAIN_MODULE,
511510
main_canon_source.clone(),
512511
&UnstableFeatures::all(),
513512
&mut handler,

src/driver/resolve_order.rs

Lines changed: 21 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,6 @@ impl DependencyGraph {
1616
}
1717
}
1818

19-
fn get_module_name(source_id: usize) -> Identifier {
20-
Identifier::from_str_unchecked(format!("unit_{}", source_id).as_str())
21-
}
22-
2319
/// Constructs the unified array of items for the entire multi-program.
2420
fn build_program(
2521
&self,
@@ -35,7 +31,7 @@ impl DependencyGraph {
3531
.program
3632
.items()
3733
.iter()
38-
.filter_map(|item| self.rewrite_item(source_id, item))
34+
.filter_map(|item| self.rewrite_item(item))
3935
.collect();
4036

4137
if source_id == MAIN_MODULE {
@@ -64,63 +60,58 @@ impl DependencyGraph {
6460
}
6561

6662
/// Rewrites a single item for the flattened single-file representation.
67-
fn rewrite_item(&self, source_id: usize, item: &parse::Item) -> Option<parse::Item> {
63+
fn rewrite_item(&self, item: &parse::Item) -> Option<parse::Item> {
6864
match item {
69-
parse::Item::TypeAlias(alias) => {
70-
let mut alias = alias.clone();
71-
alias.set_file_id(source_id);
72-
Some(parse::Item::TypeAlias(alias))
73-
}
74-
parse::Item::Function(function) => {
75-
let mut function = function.clone();
76-
function.set_file_id(source_id);
77-
Some(parse::Item::Function(function))
78-
}
79-
parse::Item::Use(use_decl) => Some(self.rewrite_use(source_id, use_decl)),
65+
parse::Item::Use(use_decl) => Some(self.rewrite_use(use_decl)),
8066
parse::Item::Module(module) => {
8167
let items: Vec<parse::Item> = module
8268
.items()
8369
.iter()
84-
.filter_map(|inner_item| self.rewrite_item(source_id, inner_item))
70+
.filter_map(|inner_item| self.rewrite_item(inner_item))
8571
.collect();
8672

8773
Some(parse::Item::Module(parse::Module::new(
88-
source_id,
74+
module.span().file_id,
8975
module.visibility().clone(),
9076
module.name().clone(),
9177
&items,
9278
)))
9379
}
80+
parse::Item::TypeAlias(_) | parse::Item::Function(_) => Some(item.clone()),
9481
parse::Item::Ignored => None,
9582
}
9683
}
9784

98-
/// Rewrites a `use` declaration by replacing the drp alias with the canonical
99-
/// `file_N` module name, prepending it to the remaining `mod_path` from the cache.
100-
/// If the target is the `MAIN_MODULE`, the `file_N` segment is safely omitted.
85+
/// Rewrites a `use` declaration to its canonical `crate`-rooted form.
10186
///
102-
/// ## Example
87+
/// The resolved path becomes `crate::<module>::<mod_path...>`, where
88+
/// `<module>` is `file_N` for dependency files and is omitted when the
89+
/// target is `MAIN_MODULE` (via `get_module_name`).
10390
///
104-
/// `use base_math::simple_op::hash` into `use file_2::hash`
105-
/// `use crate::inline_mod::item` into `use crate::inline_mod::item`
106-
fn rewrite_use(&self, source_id: usize, use_decl: &parse::UseDecl) -> parse::Item {
107-
let mut use_decl = use_decl.clone();
108-
use_decl.set_file_id(source_id);
109-
let resolved = &self.use_cache[&use_decl];
91+
/// ## Examples
92+
///
93+
/// - `use base_math::simple_op::hash` → `use crate::file_2::hash`
94+
/// - `use some_dep::item` (target = `MAIN_MODULE`) → `use crate::item`
95+
fn rewrite_use(&self, use_decl: &parse::UseDecl) -> parse::Item {
96+
let resolved = &self.use_cache[use_decl.span()];
11097
let target_id = self
11198
.source_map
11299
.get_id(&resolved.path)
113100
.expect("resolved path must be registered");
114101

115102
let mut new_path = Vec::with_capacity(resolved.mod_path.len() + 2);
116103
new_path.push(Identifier::from_str_unchecked(CRATE_STR));
117-
118104
new_path.push(Self::get_module_name(target_id));
119105
new_path.extend(resolved.mod_path.iter().cloned());
120106

107+
let mut use_decl = use_decl.clone();
121108
use_decl.set_path(&new_path);
122109
parse::Item::Use(use_decl)
123110
}
111+
112+
fn get_module_name(source_id: usize) -> Identifier {
113+
Identifier::from_str_unchecked(format!("unit_{}", source_id).as_str())
114+
}
124115
}
125116

126117
#[cfg(test)]

0 commit comments

Comments
 (0)