Skip to content

Shift Module Resolution to AST and Introduce Scope Analysis#345

Merged
KyrylR merged 5 commits into
BlockstreamResearch:dev/modulesfrom
LesterEvSe:update/driver-resolution-strategy
Jun 5, 2026
Merged

Shift Module Resolution to AST and Introduce Scope Analysis#345
KyrylR merged 5 commits into
BlockstreamResearch:dev/modulesfrom
LesterEvSe:update/driver-resolution-strategy

Conversation

@LesterEvSe
Copy link
Copy Markdown
Collaborator

@LesterEvSe LesterEvSe commented Jun 2, 2026

Motivation

This PR is deliberately monolithic because the driver and AST resolution phases are tightly coupled. Updating driver/resolve_order.rs without ast.rs (or vice versa) breaks the test suite across both files, as well as lib.rs. The new driver outputs multi-file programs that the old AST cannot process, requiring them to be implemented and tested together.

Changes

  • driver/resolve_order.rs: Stripped out alias and visibility resolution. The driver now strictly focuses on resolving multiple files and packing them into Item::Module blocks.
  • ast.rs: Implemented native scopes, alias resolution, and visibility checks. The analyze stage can now properly process Item::Use and Item::Module blocks.
  • Testing: Added full test coverage for the new scopes and driver logic, logically separated into 3 commits (driver/resolve_order.rs, ast.rs, and global lib.rs).

@LesterEvSe LesterEvSe requested a review from KyrylR June 2, 2026 14:53
@LesterEvSe LesterEvSe self-assigned this Jun 2, 2026
@LesterEvSe LesterEvSe requested a review from delta1 as a code owner June 2, 2026 14:53
@LesterEvSe LesterEvSe added the enhancement New feature or request label Jun 2, 2026
@LesterEvSe LesterEvSe force-pushed the update/driver-resolution-strategy branch from 3386db6 to 415a69d Compare June 2, 2026 14:55
Comment thread src/lib.rs Outdated
let source = SourceFile::anonymous(file.clone());
let mut error_handler = ErrorCollector::new();
let parse_program = parse::Program::parse_from_str_with_errors(source, &mut error_handler);
dbg!(&file);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I forgot about that. I delete it during the refactoring

Comment thread src/driver/mod.rs
@@ -13,36 +13,30 @@
//! to build a Directed Acyclic Graph (DAG) of the project's dependencies. Because
//! the final AST requires a flat array of items, the driver applies a deterministic
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

because import cache is keyed only by UseDecl, identical use crate::... in different package roots can rewrite to the wrong file

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed and added more test cases

@LesterEvSe LesterEvSe force-pushed the update/driver-resolution-strategy branch 3 times, most recently from e81b73e to ce3f943 Compare June 3, 2026 15:25
@LesterEvSe
Copy link
Copy Markdown
Collaborator Author

@apoelstra, I’d like to get your thoughts on our commit and PR structure. I'm trying my best to follow the CONTRIBUTING.md rules for atomic PRs, but for massive, tightly coupled features like the current Modules (or recent imports in PR #264), it’s becoming a huge bottleneck.

My current workflow is: brainstorm the architecture -> implement the entire feature on a single branch -> painstakingly divide it into separate PRs. I have to do it this way because I frequently discover bugs in my earlier assumptions only after I've written the later commits.

However, this means if a reviewer catches an issue in PR 3 that requires a change in the code from PR 1, keeping the commit history clean and linear becomes incredibly difficult. I feel like I am spending 50% of my time just organizing Git history instead of building the new features.

Is there a better or accepted way to handle these large, monolithic architectural changes? Should we be relaxing the commit rules for these specific cases? Let me know what you think!

Comment thread src/driver/mod.rs Outdated
Comment on lines +168 to +169
dbg!(curr_id);
dbg!(&queue);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Delete it

Comment thread src/driver/mod.rs Outdated
Self::parse_and_get_source_module(&path, current.source, import_span, ctx.handler)
else {
ctx.invalid_imports.push(path);
ctx.invalid_imports.insert(path);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
ctx.invalid_imports.insert(path);
let _ = ctx.invalid_imports.insert(path);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why it is ok to ignore?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It checked before in .contains method. Added comment for this

Comment thread src/driver/mod.rs Outdated
/// through every recursive call. Lives only for the duration of [`resolve_imports`].
struct ImportContext<'a> {
importer_source: &'a CanonSourceFile,
current: &'a CurrentModule<'a>,
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this lifetime really needed?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, deleted it

Comment thread src/driver/mod.rs
// might resolve to completely different implementations of the `foo` function.
let mut use_decl = use_decl.clone();
use_decl.set_file_id(self.current.id);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: you can create span here to prevent second clone

Comment thread src/driver/mod.rs Outdated
let mut use_decl = use_decl.clone();
use_decl.set_file_id(self.current.id);

let result = (resolved.path.clone(), *use_decl.span());
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
let result = (resolved.path.clone(), *use_decl.span());
let result: <add explicit type> = (resolved.path.clone(), *use_decl.span());

Comment thread src/driver/resolve_order.rs Outdated
parse::Item::Module(_) | parse::Item::Use(_) | parse::Item::Ignored => continue,
}
items.push(new_elem);
/*
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have already deleted this and the other unnecessary comments

Comment thread src/driver/resolve_order.rs Outdated

let name = item.name();
let local_id = (name.clone(), source_id);
//if target_id != MAIN_MODULE {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

?

Comment thread src/ast.rs Outdated
/// * May also return errors propagated from item collection and insertion, such as [`Error::PrivateItem`] or [`Error::RedefinedItem`].
pub fn resolve_use(&mut self, use_decl: &UseDecl) -> Result<(), Error> {
let path = use_decl.path();
if path[0].as_inner() != CRATE_STR {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

panic check?

Copy link
Copy Markdown
Collaborator Author

@LesterEvSe LesterEvSe Jun 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rewrote

Comment thread src/ast.rs
};

// Phase 1: navigate to target and collect items. Immutable borrow, dropped at end of block
let collected: Vec<_> = {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's expand the type

Comment thread src/ast.rs
///
/// * Returns a specific error (e.g., [`Error::PrivateItem`], [`Error::RedefinedItem`]) if one occurred.
/// * Returns a fallback [`Error::UnresolvedItem`] if the item could not be found in any of the checked namespaces.
fn resolve_processing_use_items_error(results: &[Result<(), Error>]) -> Result<(), Error> {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's have GH issue

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment thread src/ast.rs Outdated
Comment on lines +1179 to +1184
/*
if !scope.module_path.is_empty() {
return Err(Error::MainOutOfEntryFile).with_span(from);
}
*/

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

?

Comment thread src/lib.rs Outdated
Comment on lines +699 to +716
/// THE DEFAULT HELPER
pub(crate) fn flatten_dependency_test(root_path: &str, lib_alias: &str) {
let root_path = PathBuf::from(root_path);
let lib_path = root_path.join(lib_alias);
let main_path = root_path.join(MAIN);
let flattened_path = root_path.join(FLATTENED);

let dependency_map = build_dependency_map(&main_path, [(&root_path, lib_alias, &lib_path)]);

let expected = format_program_file(&flattened_path);
let actual = flatten_template_file(&main_path, &dependency_map);

println!("Expected:\n{}", expected);
println!("----------");
println!("Actual:\n{}", actual);

assert_eq!(expected, actual);
}
Copy link
Copy Markdown
Collaborator

@KyrylR KyrylR Jun 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need printlns?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need at all

Comment thread src/lib.rs
.assert_run_success();
}

/// THE ADVANCED HELPER
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// THE ADVANCED HELPER

Comment thread src/lib.rs Outdated
.assert_run_success();
}

/// THE DEFAULT HELPER
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// THE DEFAULT HELPER

Comment thread src/parse.rs
}

impl_eq_hash!(UseDecl; visibility, path, items);
impl_eq_hash!(UseDecl; file_id, visibility, path, items, span);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

explain why those two new args are needed

@LesterEvSe LesterEvSe force-pushed the update/driver-resolution-strategy branch from ce3f943 to 693f2a2 Compare June 4, 2026 13:53
@LesterEvSe LesterEvSe force-pushed the update/driver-resolution-strategy branch 2 times, most recently from b619e5c to ec6cbb7 Compare June 5, 2026 09:43
@LesterEvSe LesterEvSe force-pushed the update/driver-resolution-strategy branch from ec6cbb7 to 03e782d Compare June 5, 2026 12:35
Copy link
Copy Markdown
Collaborator

@KyrylR KyrylR left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ACK 03e782d; successfully ran local tests

@KyrylR KyrylR merged commit 03e782d into BlockstreamResearch:dev/modules Jun 5, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants