Skip to content

fix(modules): build test PCMs and use AST import tokens - #429

Closed
Perdixky wants to merge 2 commits into
clice-io:mainfrom
Perdixky:pr/40bb6ed-modules-pcm-import-tokens
Closed

Perdixky wants to merge 2 commits into
clice-io:mainfrom
Perdixky:pr/40bb6ed-modules-pcm-import-tokens

Conversation

@Perdixky

@Perdixky Perdixky commented Apr 19, 2026 •

Copy link
Copy Markdown
Contributor

Build module test inputs through the PCM path instead of syntax-only compilation. Take import semantic tokens from ImportDecl locations so export import is highlighted from AST.

Summary by CodeRabbit

  • Refactor

    • Simplified module import tracking; module directives are no longer stored separately and tokenization now derives from active module definitions.
    • Module identifier handling now uses precise source locations, improving accuracy of semantic tokens.
  • Tests

    • Streamlined module compilation path and added a test covering module partitions to validate semantic token behavior.

Build module test inputs through the PCM path instead of syntax-only compilation.
Take import semantic tokens from ImportDecl locations so export import is highlighted from the AST.
Copilot AI review requested due to automatic review settings April 19, 2026 09:06
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@coderabbitai

coderabbitai Bot commented Apr 19, 2026 •

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 3fd31c7c-4a16-4ad9-abb0-18d789bc96a8

📥 Commits

Reviewing files that changed from the base of the PR and between 9515b5a and ef15b61.

📒 Files selected for processing (1)
  • tests/unit/feature/semantic_tokens_tests.cpp

📝 Walkthrough

Walkthrough

The diff removes directive-side tracking of module imports (deleting Directive::imports and the preprocessor moduleImport callback) and shifts module occurrence reporting to semantic visitors and the semantic tokens collector, which now work from import declaration source locations and the active named module.

Changes

Cohort / File(s) Summary
Directive Infrastructure
src/compile/directive.h, src/compile/directive.cpp
Removed the Import struct and Directive::imports; deleted DirectiveCollector::moduleImport callback handling.
Semantic Visitor
src/semantic/semantic_visitor.h
Changed handleModuleOccurrence signature to accept llvm::ArrayRef<clang::SourceLocation>; VisitImportDecl now extracts the import keyword location and forwards declaration identifier locations.
Semantic Tokens
src/feature/semantic_tokens.cpp
Added SemanticTokensCollector::handleModuleOccurrence (emits keyword/module tokens from source locations); removed iteration over unit.directives() imports; highlight_modules() now scans the active named module definition.
Test Framework
tests/unit/test/tester.cpp
compile_with_modules now calls clice::compile directly, uses built.completed()/built.diagnostics() for error handling, and records PCMs from returned PCMInfo.
Tests
tests/unit/feature/semantic_tokens_tests.cpp
Added ModulePartitionImport test validating semantic tokens for import keyword and module partition identifiers after module compilation.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

🐇 I hopped through headers, tokens in tow,
No imports stored where directives grow,
Visitors sniff source-locs neat and spry,
Modules now sparkle where tokens lie,
A tiny rabbit cheers: hop, compile, and go! 🥕✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 10.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the two main changes: building test PCMs and using AST import tokens instead of directives for semantic highlighting.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR updates the modules test harness to build real module PCMs (instead of syntax-only compilation), and updates semantic token generation to derive module import tokens from AST ImportDecl locations so export import highlights the import keyword correctly.

Changes:

  • Build module test inputs through compile(params, PCMInfo&) to generate PCMs and feed them via PrebuiltModuleFiles.
  • Collect module import semantic tokens from AST ImportDecl (using identifier source locations) instead of preprocessor directive tracking.
  • Remove the now-unused Directive::imports collection and related preprocessor callback plumbing.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
tests/unit/test/tester.cpp Switches module test setup to actually generate PCMs and reuse them for dependent compilations.
src/semantic/semantic_visitor.h Adds AST-based ImportDecl handling and changes module occurrence callback to use identifier source locations.
src/feature/semantic_tokens.cpp Implements handleModuleOccurrence to emit module/import semantic tokens; removes directive-based module highlighting.
src/compile/directive.h Removes Import directive structure and Directive::imports storage.
src/compile/directive.cpp Removes PPCallbacks::moduleImport collection into directives.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

void handleModuleOccurrence(clang::SourceLocation keyword,
llvm::ArrayRef<clang::syntax::Token> identifiers) {
llvm::ArrayRef<clang::SourceLocation> identifiers) {
assert(keyword.isValid() && keyword.isFileID() && "Invalid keyword location");

Copilot AI Apr 19, 2026

Copy link

Choose a reason for hiding this comment

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

handleModuleOccurrence asserts keyword.isFileID(), but module/import keywords can legally come from macro expansions (MacroID). This assertion becomes reachable now that VisitImportDecl calls handleModuleOccurrence, and it can crash on macro-expanded imports. Consider relaxing the assertion (e.g., allow MacroID and/or normalize via spelling/expansion location) consistent with SemanticTokensCollector::add_token() which already handles MacroID locations.

Suggested change
assert(keyword.isValid() && keyword.isFileID() && "Invalid keyword location");
assert(keyword.isValid() && "Invalid keyword location");

Copilot uses AI. Check for mistakes.
auto keyword = decl->getLocation();
auto tokens = unit.expanded_tokens(decl->getSourceRange());
for(const auto& token: tokens) {
if(token.text(unit.context().getSourceManager()) == "import") {

Copilot AI Apr 19, 2026

Copy link

Choose a reason for hiding this comment

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

VisitImportDecl searches for the import keyword by comparing token.text(...) == "import". This is more fragile and slower than checking the token kind (e.g., token.kind() == clang::tok::kw_import), and avoids depending on SourceManager text extraction for each token.

Suggested change
if(token.text(unit.context().getSourceManager()) == "import") {
if(token.kind() == clang::tok::kw_import) {

Copilot uses AI. Check for mistakes.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/feature/semantic_tokens.cpp`:
- Around line 245-251: In handleModuleOccurrence, avoid emitting a duplicate
Keyword token for the import keyword or ensure merge preserves identical kinds:
either skip calling add_token(keyword, SymbolKind::Keyword, 0) when a Keyword
token for the same source range already exists (check the token store via the
existing token lookup/contains method), or modify the merge_tokens logic to
treat same-range tokens with identical SymbolKind as non-conflicting (preserve
the existing SymbolKind instead of converting to Conflict); update references to
handleModuleOccurrence, add_token, and merge_tokens accordingly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 44278dd2-ae1c-43bd-92fa-83ffc35e54ac

📥 Commits

Reviewing files that changed from the base of the PR and between 3fa653b and 9515b5a.

📒 Files selected for processing (5)
  • src/compile/directive.cpp
  • src/compile/directive.h
  • src/feature/semantic_tokens.cpp
  • src/semantic/semantic_visitor.h
  • tests/unit/test/tester.cpp
💤 Files with no reviewable changes (2)
  • src/compile/directive.cpp
  • src/compile/directive.h

Comment on lines +245 to +251
void handleModuleOccurrence(clang::SourceLocation keyword,
llvm::ArrayRef<clang::SourceLocation> identifiers) {
add_token(keyword, SymbolKind::Keyword, 0);
for(auto loc: identifiers) {
add_token(loc, SymbolKind::Module, 0);
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Avoid turning duplicate import keyword tokens into Conflict.

Line 247 can emit the same Keyword range already produced by the lexical pass; merge_tokens() then treats same-range non-directive duplicates as SymbolKind::Conflict. Preserve identical kinds when resolving conflicts, or avoid emitting the duplicate keyword here.

🐛 Proposed fix: preserve identical token kinds during merge
 static void resolve_conflict(RawToken& last, const RawToken& current) {
     if(last.kind == SymbolKind::Conflict) {
         return;
     }
+    if(last.kind == current.kind) {
+        last.modifiers |= current.modifiers;
+        return;
+    }
     // Directive is a low-priority lexical kind; semantic tokens override it.
     if(last.kind == SymbolKind::Directive) {
         last = current;
         return;
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/feature/semantic_tokens.cpp` around lines 245 - 251, In
handleModuleOccurrence, avoid emitting a duplicate Keyword token for the import
keyword or ensure merge preserves identical kinds: either skip calling
add_token(keyword, SymbolKind::Keyword, 0) when a Keyword token for the same
source range already exists (check the token store via the existing token
lookup/contains method), or modify the merge_tokens logic to treat same-range
tokens with identical SymbolKind as non-conflicting (preserve the existing
SymbolKind instead of converting to Conflict); update references to
handleModuleOccurrence, add_token, and merge_tokens accordingly.

@Perdixky

Copy link
Copy Markdown
Contributor Author

Now this PR is ready for review, I hope you could take a glance at it and leave any suggestions.
@16bit-ykiko

@16bit-ykiko

Copy link
Copy Markdown
Member

So why were these changes made? Are there any cases that the old method couldn't handle?

@Perdixky

Perdixky commented Apr 19, 2026 •

Copy link
Copy Markdown
Contributor Author

The import expression is parsed as an ImportDecl and should not be handled as a directive. It should be handled by the SemanticVisitor. This fix makes Clice's behavior more appropriate.

@16bit-ykiko

Copy link
Copy Markdown
Member

The situation is a bit complicated, but at least keeping the import preprocessor directive is the right call; we might have other uses for it in the future. I don't think the current approach needs to be changed unless you can find an error case that it can't handle properly.

@Perdixky

Perdixky commented Apr 19, 2026 •

Copy link
Copy Markdown
Contributor Author

Now I have figured out a suitable case to demonstrate the incorrect behavior of the legacy implementation.

For non-partition modules:
import foo; // PPCallbacks returns name='foo' loc='main.cpp:1:8' length=3 -> OK

For import :part; // PPCallbacks returns name='foo:part' loc='main.cppm:2:8' -> Wrong

Additionally, there are other cases that do not even trigger this callback. See:
https://raw.githubusercontent.com/llvm/llvm-project/main/clang/lib/Lex/PPDirectives.cpp#:~:text=//%20FIXME%3A%20Call%20the%20moduleImport%20callback%3F

https://raw.githubusercontent.com/llvm/llvm-project/main/clang/lib/Lex/PPDirectives.cpp#:~:text=//%20We%20don't/shouldn't%20load%20the%20standard%20c%2B%2B20%20modules%20when%20preprocessing.%0A%20%20%20%20%20%20//%20so%20the%20imported%20module%20is%20nullptr.

We do use additional logic to handle these cases, but that would not be elegant.

I've added the corresponding test cases, and the import std; and import "header.h"; test case are not added since they're not supported yet.

@16bit-ykiko

16bit-ykiko commented Apr 19, 2026 •

Copy link
Copy Markdown
Member

OK, that makes sense. But keep the directive collection, and if possible, figure out why it ignores the partition modules. Resolve all review comments, otherwise merging is blocked.

@Perdixky

Perdixky commented Apr 20, 2026 •

Copy link
Copy Markdown
Contributor Author

I still believe we shouldn't restore the directive collection. A bloated implementation will be misleading for contributors, especially LLMs. It may create a gap between the AST trace and the directive trace. However, I respect your opinion—if you insist, I'll keep it.☺️

@16bit-ykiko

Copy link
Copy Markdown
Member

Thanks! This was fixed independently along the way — the test harness now builds real PCMs for module tests instead of silently taking the syntax-only path. Closing as superseded.

@16bit-ykiko 16bit-ykiko closed this Sep 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants