feat: add scan_module_decl() fallback for conditional module declarations (#373)
## Summary - Add `scan_module_decl()` — a lightweight preprocessor-based fallback that resolves module declarations inside `#if`/`#ifdef` conditionals. When `scan()` detects `need_preprocess=true`, this function runs clang's preprocessor to evaluate conditions and extract the actual module name. It stops lexing as soon as the module declaration is found, making it much cheaper than `scan_precise()`. - Integrate the fallback into `scan_dependency_graph()` for wave 0 source files, so conditional module declarations (e.g. `#ifdef USE_MODULES / export module M; / #endif`) are correctly registered in the dependency graph. - Add comprehensive test cases covering all C++20 module declaration forms from cppreference, including `scan_module_decl()` tests for conditional resolution and `scan_precise()` tests for module import semantics. ## Test plan - [x] All 310 unit tests pass (0 failures, 9 skipped) - [x] `scan()` tests cover: primary interface, implementation, dotted names, partitions, GMF, conditional module declarations, private module fragment - [x] `scan_module_decl()` tests cover: basic, conditional with `-D`, conditional with `#if` expression, GMF with conditional, implementation unit, dotted name, partition, no-module file - [x] `scan_precise()` tests cover: named import, multiple imports, dotted import, partition import, export-import, export-import partition, implementation import, GMF with import, mixed includes/imports, no-module file 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Improved detection of module declarations hidden by conditional compilation via a lightweight fallback scan. Resolved module vs. interface classification is cached to avoid repeated work and is used consistently in dependency mapping. * Better handling and classification of module imports, partitions, and global-fragment includes when building module relationships. * **Tests** * Added comprehensive unit tests covering module declaration extraction, fallback resolution under preprocessor guards, imports, partitions, includes, and macro-driven cases. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -513,6 +513,35 @@ et::task<> scan_impl(CompilationDatabase& cdb,
|
||||
}
|
||||
|
||||
// Record module interface unit mapping.
|
||||
// When the module declaration is inside a conditional directive
|
||||
// (need_preprocess=true), fall back to scan_module_decl() which
|
||||
// runs a lightweight preprocessor pass to resolve the actual
|
||||
// module name. This only applies to source files (wave 0) since
|
||||
// headers cannot contain module declarations.
|
||||
if(scan_result.scan_result.need_preprocess && wave_num == 0) {
|
||||
auto file_path = llvm::StringRef(scan_result.path);
|
||||
auto contexts =
|
||||
cdb.lookup(file_path, {.query_toolchain = true, .suppress_logging = true});
|
||||
if(!contexts.empty()) {
|
||||
auto& ctx = contexts[0];
|
||||
auto fallback = scan_module_decl(ctx.arguments, ctx.directory, /*content=*/{});
|
||||
if(!fallback.module_name.empty()) {
|
||||
scan_result.scan_result.module_name = std::move(fallback.module_name);
|
||||
scan_result.scan_result.is_interface_unit = fallback.is_interface_unit;
|
||||
// Update cache so warm runs don't re-trigger fallback.
|
||||
if(ext_cache) {
|
||||
auto cache_it = ext_cache->scan_results.find(scan_result.path_id);
|
||||
if(cache_it != ext_cache->scan_results.end()) {
|
||||
cache_it->second.module_name = scan_result.scan_result.module_name;
|
||||
cache_it->second.is_interface_unit =
|
||||
scan_result.scan_result.is_interface_unit;
|
||||
cache_it->second.need_preprocess = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(scan_result.scan_result.is_interface_unit) {
|
||||
graph.add_module(scan_result.scan_result.module_name, scan_result.path_id);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user