## Summary - Replace hand-written character scanning in `document_links.cpp` with the project's `Lexer` class for finding filename arguments in preprocessor directives - Extend `Lexer` to activate `header_name` mode for `#embed`/`#include_next`, and expose `set_header_name_mode()` for `__has_include`/`__has_embed` contexts - Remove unused `Include::filename_range` field (had a latent assert crash on macro-expanded includes) - Add `MacroInclude` unit test covering `#include MACRO` scenario ## Test plan - [x] 498 unit tests pass (including new `MacroInclude` test) - [x] 119 integration tests pass - [x] 2/2 smoke tests pass 🤖 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** * Document links now resolve includes written via macros; directive parsing recognizes include, include_next, embed and __has_* patterns more reliably using lexer-driven argument detection. * **Refactor** * Removed an internal filename-range field previously stored for include directives. * **Tests** * Added unit tests covering directive argument extraction and macro-based include linking. <!-- end of auto-generated comment: release notes by coderabbit.ai --> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
291 lines
11 KiB
C++
291 lines
11 KiB
C++
#include "compile/directive.h"
|
|
|
|
#include "compile/implement.h"
|
|
|
|
#include "clang/Lex/MacroArgs.h"
|
|
#include "clang/Lex/MacroInfo.h"
|
|
#include "clang/Lex/Preprocessor.h"
|
|
|
|
namespace clice {
|
|
|
|
namespace {
|
|
|
|
class DirectiveCollector : public clang::PPCallbacks {
|
|
public:
|
|
DirectiveCollector(CompilationUnitRef unit) : unit(unit) {}
|
|
|
|
private:
|
|
void add_condition(clang::SourceLocation location,
|
|
Condition::BranchKind kind,
|
|
Condition::ConditionValue value,
|
|
clang::SourceRange cond_range) {
|
|
auto& directive = unit->directives[unit.file_id(location)];
|
|
directive.conditions.emplace_back(kind, value, location, cond_range);
|
|
}
|
|
|
|
void add_condition(clang::SourceLocation loc,
|
|
Condition::BranchKind kind,
|
|
clang::PPCallbacks::ConditionValueKind value,
|
|
clang::SourceRange condition_range) {
|
|
Condition::ConditionValue cond_value =
|
|
value == clang::PPCallbacks::CVK_False ? Condition::None
|
|
: value == clang::PPCallbacks::CVK_True ? Condition::True
|
|
: value == clang::PPCallbacks::CVK_NotEvaluated ? Condition::Skipped
|
|
: Condition::None;
|
|
add_condition(loc, kind, cond_value, condition_range);
|
|
}
|
|
|
|
void add_condition(clang::SourceLocation loc,
|
|
Condition::BranchKind kind,
|
|
const clang::Token& name,
|
|
const clang::MacroDefinition& definition) {
|
|
if(auto def = definition.getMacroInfo()) {
|
|
add_macro(def, MacroRef::Ref, name.getLocation());
|
|
add_condition(loc, kind, Condition::True, name.getLocation());
|
|
} else {
|
|
add_condition(loc, kind, Condition::False, name.getLocation());
|
|
}
|
|
}
|
|
|
|
void add_macro(const clang::MacroInfo* def, MacroRef::Kind kind, clang::SourceLocation loc) {
|
|
if(def->isBuiltinMacro()) {
|
|
return;
|
|
}
|
|
|
|
if(unit.is_builtin_file(unit.file_id(loc))) {
|
|
return;
|
|
}
|
|
|
|
auto& directive = unit->directives[unit.file_id(loc)];
|
|
directive.macros.emplace_back(MacroRef{def, kind, loc});
|
|
}
|
|
|
|
public:
|
|
/// ============================================================================
|
|
/// Rewritten Preprocessor Callbacks
|
|
/// ============================================================================
|
|
|
|
void HasEmbed(clang::SourceLocation location,
|
|
llvm::StringRef filename,
|
|
bool is_angled,
|
|
clang::OptionalFileEntryRef file) override {
|
|
unit->directives[unit.file_id(location)].has_embeds.emplace_back(clice::HasEmbed{
|
|
.file_name = filename,
|
|
.file = file,
|
|
.is_angled = is_angled,
|
|
.loc = location,
|
|
});
|
|
}
|
|
|
|
void EmbedDirective(clang::SourceLocation location,
|
|
clang::StringRef filename,
|
|
bool is_angled,
|
|
clang::OptionalFileEntryRef file,
|
|
const clang::LexEmbedParametersResult&) override {
|
|
unit->directives[unit.file_id(location)].embeds.emplace_back(Embed{
|
|
.file_name = filename,
|
|
.file = file,
|
|
.is_angled = is_angled,
|
|
.loc = location,
|
|
});
|
|
}
|
|
|
|
void InclusionDirective(clang::SourceLocation hash_loc,
|
|
const clang::Token& include_tok,
|
|
llvm::StringRef,
|
|
bool,
|
|
clang::CharSourceRange,
|
|
clang::OptionalFileEntryRef,
|
|
llvm::StringRef,
|
|
llvm::StringRef,
|
|
const clang::Module*,
|
|
bool,
|
|
clang::SrcMgr::CharacteristicKind) override {
|
|
prev_fid = unit.file_id(hash_loc);
|
|
|
|
/// An `IncludeDirective` call is always followed by either a `LexedFileChanged`
|
|
/// or a `FileSkipped`. so we cannot get the file id of included file here.
|
|
unit->directives[prev_fid].includes.emplace_back(Include{
|
|
.fid = {},
|
|
.location = include_tok.getLocation(),
|
|
});
|
|
}
|
|
|
|
void LexedFileChanged(clang::FileID curr_fid,
|
|
LexedFileChangeReason reason,
|
|
clang::SrcMgr::CharacteristicKind,
|
|
clang::FileID prev_fid,
|
|
clang::SourceLocation) override {
|
|
if(reason == LexedFileChangeReason::EnterFile && curr_fid.isValid() && prev_fid.isValid() &&
|
|
this->prev_fid.isValid() && prev_fid == this->prev_fid) {
|
|
/// Once the file has changed, it means that the last include is not skipped.
|
|
/// Therefore, we initialize its file id with the current file id.
|
|
auto& include = unit->directives[prev_fid].includes.back();
|
|
include.skipped = false;
|
|
include.fid = curr_fid;
|
|
}
|
|
}
|
|
|
|
void FileSkipped(const clang::FileEntryRef& file,
|
|
const clang::Token&,
|
|
clang::SrcMgr::CharacteristicKind) override {
|
|
if(prev_fid.isValid()) {
|
|
/// File with guard will have only one file id in `SourceManager`, use
|
|
/// `translateFile` to find it.
|
|
auto& include = unit->directives[prev_fid].includes.back();
|
|
include.skipped = true;
|
|
|
|
/// Get the FileID for the given file. If the source file is included multiple
|
|
/// times, the FileID will be the first inclusion.
|
|
include.fid = unit.file_id(file);
|
|
}
|
|
}
|
|
|
|
void moduleImport(clang::SourceLocation import_location,
|
|
clang::ModuleIdPath names,
|
|
const clang::Module*) override {
|
|
auto fid = unit.file_id(unit.expansion_location(import_location));
|
|
auto& import = unit->directives[fid].imports.emplace_back();
|
|
import.location = import_location;
|
|
for(auto name: names) {
|
|
import.name += name.getIdentifierInfo()->getName();
|
|
import.name_locations.emplace_back(name.getLoc());
|
|
}
|
|
}
|
|
|
|
void HasInclude(clang::SourceLocation location,
|
|
llvm::StringRef,
|
|
bool,
|
|
clang::OptionalFileEntryRef file,
|
|
clang::SrcMgr::CharacteristicKind) override {
|
|
clang::FileID fid;
|
|
if(file) {
|
|
fid = unit.file_id(*file);
|
|
}
|
|
|
|
unit->directives[unit.file_id(location)].has_includes.emplace_back(fid, location);
|
|
}
|
|
|
|
void PragmaDirective(clang::SourceLocation loc,
|
|
clang::PragmaIntroducerKind introducer) override {
|
|
// Ignore other cases except starts with `#pragma`.
|
|
if(introducer != clang::PragmaIntroducerKind::PIK_HashPragma)
|
|
return;
|
|
|
|
clang::FileID fid = unit.file_id(loc);
|
|
|
|
llvm::StringRef text_to_end = unit.file_content(fid).substr(unit.file_offset(loc));
|
|
llvm::StringRef that_line = text_to_end.take_until([](char ch) { return ch == '\n'; });
|
|
|
|
Pragma::Kind kind = that_line.contains("endregion") ? Pragma::EndRegion
|
|
: that_line.contains("region") ? Pragma::Region
|
|
: Pragma::Other;
|
|
|
|
auto& directive = unit->directives[fid];
|
|
directive.pragmas.emplace_back(Pragma{
|
|
that_line,
|
|
kind,
|
|
loc,
|
|
});
|
|
}
|
|
|
|
void If(clang::SourceLocation loc,
|
|
clang::SourceRange cond_range,
|
|
clang::PPCallbacks::ConditionValueKind value) override {
|
|
add_condition(loc, Condition::If, value, cond_range);
|
|
}
|
|
|
|
void Elif(clang::SourceLocation loc,
|
|
clang::SourceRange cond_range,
|
|
clang::PPCallbacks::ConditionValueKind value,
|
|
clang::SourceLocation) override {
|
|
add_condition(loc, Condition::Elif, value, cond_range);
|
|
}
|
|
|
|
void Ifdef(clang::SourceLocation loc,
|
|
const clang::Token& name,
|
|
const clang::MacroDefinition& definition) override {
|
|
add_condition(loc, Condition::Ifdef, name, definition);
|
|
}
|
|
|
|
/// Invoke when #elifdef branch is taken.
|
|
void Elifdef(clang::SourceLocation loc,
|
|
const clang::Token& name,
|
|
const clang::MacroDefinition& definition) override {
|
|
add_condition(loc, Condition::Elifdef, name, definition);
|
|
}
|
|
|
|
/// Invoke when #elif is skipped.
|
|
void Elifdef(clang::SourceLocation loc,
|
|
clang::SourceRange cond_range,
|
|
clang::SourceLocation) override {
|
|
/// FIXME: should we try to evaluate the condition to compute the macro reference?
|
|
add_condition(loc, Condition::Elifdef, Condition::Skipped, cond_range);
|
|
}
|
|
|
|
/// Invoke when #ifndef is taken.
|
|
void Ifndef(clang::SourceLocation loc,
|
|
const clang::Token& name,
|
|
const clang::MacroDefinition& definition) override {
|
|
add_condition(loc, Condition::Ifndef, name, definition);
|
|
}
|
|
|
|
// Invoke when #elifndef is taken.
|
|
void Elifndef(clang::SourceLocation loc,
|
|
const clang::Token& name,
|
|
const clang::MacroDefinition& definition) override {
|
|
add_condition(loc, Condition::Elifndef, name, definition);
|
|
}
|
|
|
|
// Invoke when #elifndef is skipped.
|
|
void Elifndef(clang::SourceLocation loc,
|
|
clang::SourceRange cond_range,
|
|
clang::SourceLocation) override {
|
|
add_condition(loc, Condition::Elifndef, Condition::Skipped, cond_range);
|
|
}
|
|
|
|
void Else(clang::SourceLocation loc, clang::SourceLocation if_loc) override {
|
|
add_condition(loc, Condition::Else, Condition::None, clang::SourceRange());
|
|
}
|
|
|
|
void Endif(clang::SourceLocation loc, clang::SourceLocation if_loc) override {
|
|
add_condition(loc, Condition::EndIf, Condition::None, clang::SourceRange());
|
|
}
|
|
|
|
void MacroDefined(const clang::Token& name, const clang::MacroDirective* md) override {
|
|
if(auto def = md->getMacroInfo()) {
|
|
add_macro(def, MacroRef::Def, name.getLocation());
|
|
}
|
|
}
|
|
|
|
void MacroExpands(const clang::Token& name,
|
|
const clang::MacroDefinition& definition,
|
|
clang::SourceRange range,
|
|
const clang::MacroArgs* args) override {
|
|
if(auto def = definition.getMacroInfo()) {
|
|
add_macro(def, MacroRef::Ref, name.getLocation());
|
|
}
|
|
}
|
|
|
|
void MacroUndefined(const clang::Token& name,
|
|
const clang::MacroDefinition& md,
|
|
const clang::MacroDirective* undef) override {
|
|
if(auto def = md.getMacroInfo()) {
|
|
add_macro(def, MacroRef::Undef, name.getLocation());
|
|
}
|
|
}
|
|
|
|
private:
|
|
clang::FileID prev_fid;
|
|
CompilationUnitRef unit;
|
|
llvm::DenseMap<clang::MacroInfo*, std::size_t> macro_cache;
|
|
};
|
|
|
|
} // namespace
|
|
|
|
void CompilationUnitRef::Self::collect_directives() {
|
|
instance->getPreprocessor().addPPCallbacks(std::make_unique<DirectiveCollector>(this));
|
|
}
|
|
|
|
} // namespace clice
|