feat(resolution): add C/C++ include path resolution (#453)
* feat(resolution): add C/C++ include path resolution Add full import resolution pipeline for C and C++ #include directives, connecting extracted import nodes to actual header files in the project. - Add C/C++ extension resolution (.h, .hpp, .hxx, .cpp, .cc, .cxx) - Add system header filtering with ~80 C and ~80 C++ stdlib headers - Add extractCppImports() for #include import mapping extraction - Add compile_commands.json parsing for -I/-isystem include directories - Add heuristic include dir discovery (include/, src/, lib/, api/) - Add resolveCppIncludePath() for include directory search - Add C/C++ built-in symbol filtering (printf, malloc, std::*, etc.) - Wire getCppIncludeDirs into ResolutionContext - Add 13 new tests for C/C++ import resolution and extraction Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * review: wire #include resolution into pipeline + fix builtin filter The PR landed the include-dir scan logic (loadCppIncludeDirs + resolveCppIncludePath) but the indexer never reached it: imports references with referenceName='X.h' fell into resolveViaImport's symbol-lookup branch (matched extractCppImports' basename-without-ext localName via .startsWith, then tried to find a symbol named like the extension and failed). End result on bitcoin-core: 0 new file→file imports vs main, despite the include-dir scan resolving paths correctly when probed directly. resolveViaImport now has a C/C++ imports branch that resolves the include path to the actual file node and returns that — skipping the irrelevant symbol scan. Measured on bitcoin-core: +2,059 newly resolved file→file imports (6,027 → 8,086, +34%). The unconditional CPP_BUILT_INS / C_BUILT_INS filter also misfired: C/C++ codebases routinely shadow stdlib names (bitcoin's mp::move, custom allocators with free/malloc, stream classes with read/write/ close/open, logging libs wrapping printf). Filtering those names killed legitimate edges — 1,179 → 0 for move(), 33 → 0 for free(), 149 → 7 for write() on bitcoin. The filter now defers to hasAnyPossibleMatch: only filter when no user-defined symbol with the name exists. std:: prefix stays unconditional (never user-shadowed in practice). After: printf/free/open/close/read/write/swap all preserved at main's counts; the std::move-binds-to-mp::move false-positives still drop (correctly: −2,154 C/C++ calls). Also: drop the duplicate 'FILE' in C_BUILT_INS; add an end-to-end test that asserts `#include "X.h"` produces a file→file imports edge in the real indexing pipeline (not just direct resolver probes); add a test documenting the cross-language `.h` heuristic claim (Obj-C dirs are intentionally allowed as C/C++ include dirs); add CHANGELOG entry under [Unreleased] with measured numbers. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> Co-authored-by: Colby McHenry <me@colbymchenry.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
Colby McHenry
parent
893256b88e
commit
48eebe1e3e
+22
-1
@@ -9,7 +9,28 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
- **C/C++ `#include` resolution — bare-basename includes now connect to the
|
||||
actual header file, not a phantom import node (#453).** Path-prefixed
|
||||
includes (`#include "common/args.h"`) already resolved via file-path
|
||||
suffix matching, but bare-basename includes (`#include "uint256.h"` from a
|
||||
caller in another directory) used to leave only a phantom edge to a
|
||||
floating `import` node owned by the including file. The resolver now walks
|
||||
C/C++ include search directories — pulled from `compile_commands.json`
|
||||
(`-I`/`-isystem` flags) when present, otherwise discovered by probing
|
||||
conventional dirs (`include/`, `src/`, `lib/`, `api/`, `inc/`) plus any
|
||||
top-level directory containing `.h`/`.hpp` files — and resolves the
|
||||
include to a real file node, producing a true file→file `imports` edge.
|
||||
System headers (`<stdio.h>`, `<vector>`, `<iostream>`, ~80 C and ~80 C++
|
||||
stdlib names) are filtered before the scan so they don't false-resolve
|
||||
via heuristic dir matching. C/C++ built-in symbols (`std::*` unconditionally,
|
||||
plus `printf`/`malloc`/`cout`/`make_shared`/etc. when **no user-defined
|
||||
symbol with that name exists**) are filtered from name-matching too —
|
||||
C/C++ projects routinely shadow stdlib names (custom allocators, stream
|
||||
wrappers, logging libs), so the filter only fires when there's no real
|
||||
definition to bind to. Measured on bitcoin-core (1,989 indexed files):
|
||||
C/C++ file→file `imports` edges 6,027 → 8,086 (**+34%**), false-positive
|
||||
call edges from `std::move`/`std::swap` etc. into similarly-named user
|
||||
methods −2,154 (**−3.6%** of C/C++ `calls`).
|
||||
- **Enterprise Spring / MyBatis flow now traces end-to-end (#389).** Three gaps that previously forced agents back to grep on large Spring/MyBatis codebases are closed:
|
||||
- **MyBatis XML mapper indexing + Java↔XML bridge.** `*.xml` files containing `<mapper namespace="...">` are now first-class: each `<select|insert|update|delete id="X">` and `<sql id="X">` becomes a method-shaped node qualified as `<namespace>::<id>`, and a new synthesizer (`mybatis-java-xml`) links the matching Java mapper interface method → its XML statement with a `calls` edge. `<include refid="...">` to a `<sql>` fragment in the same mapper also resolves. Non-mapper XML (`pom.xml`, `web.xml`, `log4j.xml`, etc.) emits just a file node — no symbol noise. Validated on macrozheng/mall-tiny: all 6 custom-SQL Java mapper methods reach their XML counterparts; `trace(UmsRoleController.listResource, UmsResourceMapper::getResourceListByRoleId-xml)` connects in 4 hops across controller → service-iface → impl → mapper-iface → XML.
|
||||
- **Spring `@Value`/`@ConfigurationProperties` config-key linkage.** `application.{yml,yaml,properties}` (+ profile variants `application-dev.yml`, `bootstrap.yml`, etc.) is parsed during indexing, with one `constant` node per leaf key qualified by its dotted path (`app.cache.name.user-token`). `@Value("${app.cache.name.user-token}")` and `@ConfigurationProperties(prefix = "app.cache")` references in Java/Kotlin emit binding nodes that resolve to the matching key (or, for `@ConfigurationProperties`, a key under the prefix). Spring's **relaxed binding** applies (kebab `cache-list` ↔ camel `cacheList` ↔ snake `cache_list` ↔ `CACHE_LIST`), so a Java `@Value("${app.retryCount}")` finds `app.retry-count` in `application.properties`. `${key:default}` form is supported; the default is stripped before lookup.
|
||||
|
||||
Reference in New Issue
Block a user