Skip to content

fix(resolve): v1.6.1 receiver evidence, store and object-literal bindings, and extraction recall - #277

Merged
sunerpy merged 12 commits into
mainfrom
sync/v1.6.1-alignment
Oct 1, 2026
Merged

sunerpy merged 12 commits into
mainfrom
sync/v1.6.1-alignment

Conversation

@sunerpy

@sunerpy sunerpy commented Oct 1, 2026

Copy link
Copy Markdown
Owner

Summary

Third PR of the v1.6.1 alignment: the resolution and extraction wave that follows #275. Every change ports the final form upstream released in v1.6.1 (2026-09-29). Per-item dispositions, the landing state of each row and the KEEP-RUST divergences are in the new docs/upstream-sync/V1_6_1_AUDIT_2026-09-30.md. Tracked parity in UPSTREAM.md stays v1.6.0 until the release gate.

Receiver evidence — extraction version 13 → 14

  • TS/JS receiver shapes (#1986, #1862, #1987): wrappers that keep the receiver ((x), x!, x as T, x satisfies T, <T>x, await x) are looked through; an identifier-rooted chain keeps its qualified call site (holder.values.get, ?. read as .), which only a framework resolver may bind; an untyped expression receiver ((a ?? b).map(), arr[0].run(), f().list.map()) emits nothing; this.#items.add() keeps its private field.
  • Built-in method names need receiver evidence (#1987): list.map(), cache.get() and emitter.on() no longer bind to project methods through capitalization or word overlap; typed, constructed, imported, object-literal and class receivers still resolve.
  • Private fields (#1987): this.#field.m() resolves on the field's declared or constructed type, public and private names stay apart, a constructor's this.field = new T() counts, and each class field declaration is read once per pass instead of compiling four regexes per call.
  • Python call results (#1683): d.setdefault(k, []).append(v) keeps d.setdefault().append and never binds a same-named function.
  • Cross-language matches (#1986): code references crossing code families need bridge evidence (cgo //export, extern "C"); cherry-picked from the integration branch together with #2040 (unknown Python import members stay unresolved) and #2055 (a value+interface pair binds implements to the interface).

Recall

  • Zustand stores (#1862, #647): exported store initializers own the inline actions of the object they return; get().x(), useStore.getState().x(), destructured bindings and Zustand selectors resolve inside exactly one identified store, through import aliases and barrels, never through shadows, sibling blocks or non-Zustand factories.
  • Object-literal aliases (#1932): api.getUser() for { getUser } / { getUser: fetchUser } follows the binding from where the literal is written — last own property wins, spreads and computed keys invalidate, shadowing parameters and nearer values block — same-file, cross-file and through alias forwarding.
  • Curried wrapper members (#1747): { getMode: Effect.fn("x")(function* () {}) } is a function named getMode that owns its body's calls.
  • Python docstrings (#1905): module, class and function body docstrings (PEP 257) reach the docstring column, joined after a preceding comment.
  • Single-argument C/C++ macros (#1373): NATIVE_FN(get_version) { … } is get_version when a local #define proves it.

Corrections found along the way

  • TSX and JSX specs now delegate the class-member hooks, so a plain TSX field is a property as in TypeScript; JavaScript class fields are named by their property key (upstream #808) instead of <anonymous>.
  • KEEP-RUST: this.a.b.m() keeps its last segment (b.m) rather than upstream's bare m, which would reintroduce the #1496 self-edge.

Golden impact

The drift check re-indexes all 19 corpora with this build: 17 are byte-identical. Two changed, as intended, and were regenerated with the documented recipes:

  • typescript/refs.json: the unresolved api.nested.run() site is now api.nested.run (was nested.run); edges.json unchanged.
  • python/nodes.json: the pkg/__init__.py file node gains its module docstring.

Performance

init of the generated 5,000-reference corpora (one run, same host):

corpus #275 user s / RSS MB this PR
TS this.field + awaited receivers 27.14 / 103 17.36 / 110
JS 5,000 distinct / same-name bare calls 11.87 / 54, 0.26 / 28 11.86 / 55, 0.34 / 29
Go / C / Rust 11.78, 6.52, 11.32 11.75, 6.55, 11.27

Verification

  • make pre-ci: all checks passed; 3,757 tests, 0 failed; archive smoke codegraph 0.50.3.
  • Every new suite was run against the previous sources first and fails there (negative controls excepted): js_receiver_shapes, js_receiver_evidence (receiver, store and object-literal cases), python_docstrings, the TSX/JSX class-member and curried-member cases, and the single-argument macro cases.
  • Golden drift check as above; equivalence self-equivalence passes for the regenerated corpora.
  • The last commit is documentation only (owner decisions on #1349 and the UI family, 2026-10-01); checked with oxfmt --check and docs-check.

BEGIN_COMMIT_OVERRIDE
fix(resolve): keep unknown Python import members unresolved
fix(resolve): bind implements to the interface of a value+interface pair
fix(resolve): reject unrelated cross-language matches
fix(resolve): require receiver evidence for TS/JS member calls
feat(resolve): bind Zustand store actions through accessors, destructuring and selectors
fix(resolve): follow object-literal member bindings lexically
feat(extract): name curried-wrapper object members by their key
feat(extract): index Python body docstrings
fix(extract): recover functions defined through single-argument C/C++ macros
END_COMMIT_OVERRIDE

🤖 Generated with Claude Code

CodeGraph Test added 12 commits September 30, 2026 22:32
A named Python import proves the receiver exists, not the attribute: an
unresolved member such as task.delay() no longer falls back to the imported
task function (upstream #2040). Named imports, including aliases, also get
the absolute-module lookup and the top-level-definition fallback Python
needs because its symbols are never marked exported (#1820), so
`Store as Actual; Actual.fetch()` now reaches Store::fetch.
A TypeScript value that shares its name with a type in the same file (the
VS Code service idiom) now moves an inheritance edge to that type instead of
dropping it (upstream #2055).
Adopt upstream v1.6.1's language families (C, C++, Objective-C and Swift are
one native family; ArkTS, Vue, Svelte and Astro join web) and its code-family
boundary: every chosen result that crosses between two code families needs
C ABI evidence (a cgo //export, a Rust extern "C" function, or a Go/Rust
caller naming a C symbol); config and markup transitions stay open. The gate
judges the chosen result, never the candidate pool, and also covers
alias-forwarded and framework results (#1986).

Godot scene, resource, and project path references into scripts stay
unresolved rows because impact/affected/audit consume them path-keyed. The
Python golden gains the aliased member import edge that #1820's
named-import fallback now resolves.
Extraction follows upstream v1.6.1's TS/JS receiver shapes: wrappers that
keep the receiver ((x), x!, x as T, x satisfies T, <T>x, await x) are looked
through, an identifier-rooted chain keeps its qualified call site
(holder.values.get, with ?. read as .), an untyped expression receiver
emits nothing, and an ES private field keeps its # (this.#items.add).
Python call-result receivers keep <inner>().<method> like TS/JS (#1683).
KEEP-RUST: a this/super-rooted chain through more than one field keeps its
last segment instead of upstream's bare name, which would reintroduce the
#1496 self-edge.

Resolution reads this.#field declarations with public and private names
kept apart, memoises each class field declaration per pass with static
patterns instead of compiling four regexes per call, and accepts a
this.<field> assignment inside a constructor. Built-in method names need a
validated receiver (#1987), retained untyped chains bind only through a
framework resolver (#1862), and Python call-result chains stay unresolved.

TSX and JSX now delegate their class-member hooks, so a plain field is a
property as in TS/JS, and JS class fields are named by their property key
(#808) instead of <anonymous>. Extraction version 14.
…uring and selectors

An exported store initializer — create((set, get) => ({ reset: ... })),
middleware wrappers included, or a binding exported by a later statement —
now owns the inline actions of the object it returns, the extraction the
store resolution depends on (upstream #647).

Resolution follows upstream v1.6.1 #1862: get().reset() inside the store
factory, useStore.getState().reset(), a bare call destructured from
useStore.getState() in an enclosing block, and a selector binding of a
Zustand create hook each resolve to the member of exactly one identified
store. Import aliases and barrels are followed, and a shadowing parameter
or declaration, a sibling block, an unknown store or a non-Zustand factory
leaves the call unresolved. Python call-result chains keep upstream's
unique-callable accessor fallback.
api.getUser() where api is { getUser } or { getUser: fetchUser } and the
function is declared outside the literal now resolves in the literal's own
file as well as through an import (upstream v1.6.1 #1932). The member is
read off the literal's own top-level properties on comment- and
string-blanked source: the last own property wins, an unknown spread or
computed key invalidates earlier evidence, and a nested object, a member
body, a comment or a string never donates a binding. The binding is then
followed from where the literal is written — the nearest visible
declaration of the file, else one of its imports — and a shadowing
parameter or nearer value leaves it unresolved.

Alias forwarding uses the same reader for JS-family members instead of a
pattern over the truncated signature, and containment lookups only accept
a member defined by the selected property.
An Effect-style service is usually an object a factory returns, so a
wrapped function lands in a pair: { getMode: Effect.fn("x")(function* () {}) }
produced no function node and its body's calls were attributed to the
enclosing factory. The curried-wrapper rule (the callee is itself a call)
now also names a pair value by its property key, from both the body walk
and object-literal member extraction, while single-call computations such
as key: useMemo(...) and key: arr.map(...) stay anonymous (upstream #1747).
…eScript corpus

The TypeScript fixture's nested api.nested.run() now reaches refs.json as
the retained chain api.nested.run instead of its last segment, the call
site shape of upstream #1862. It stays unresolved and edges.json is
unchanged; regenerated with the documented recipe.
A bare string literal first in a module, class or function body is now the
node's docstring, joined after any preceding comment, so Python prose
reaches search and node output (upstream #1905). The grammar's
string_content is read directly, so r/u prefixes, both triple-quote forms,
concatenated literals and interleaved comments work, while bytes,
f-strings, tuples and computed expressions are not docstrings. Cleaning
follows PEP 257: tabs expand to 8-column stops, lines after the first lose
their common indentation, and blank edges are dropped.

The Python corpus's pkg/__init__.py file node gains its module docstring.
… macros

#define NATIVE_FN(name) int name(void) followed by NATIVE_FN(get_version)
{ ... } was indexed as "(get_version)" in C and under the macro name in C++,
so callers never reached it (upstream #1373). Both parser shapes now
recover the function name, but only when a preceding local #define's
replacement declares a function named by its sole parameter; registration
macros, token pasting, typedefs, an #undef or redefinition in between, and
any conditional block leave the name alone. The multi-argument recovery is
unchanged.
Classifies every commit in 3ed73bc..v1.6.1 against the 2026-09-15 baseline:
the resolution/extraction and MCP/CLI/lifecycle/explore rows with their
landing state, the KEEP-RUST divergences taken by this round, the
re-triaged UI family, and the rows that wait for an owner decision because
they touch a hard invariant or would add a deferral. The tracked release in
UPSTREAM.md stays v1.6.0 until the ported rows ship.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7e38673688

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +3127 to +3130
Regex::new(&format!(
r"(?m)^[ \t]*export\s+(?:default\s+{name}\s*;?[ \t\r]*$|\{{[^}}]*\b{name}\b[^}}]*\}})"
))
.is_ok_and(|pattern| pattern.is_match(self.source))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Resolve later exports by lexical binding

This source-wide regex treats every same-named declaration as exported and does not distinguish the local side of an export alias. For example, in const store = create(...); const other = 1; export { other as store };, store is incorrectly considered exported; likewise, a nested const store is considered exported when an unrelated top-level store is exported. The initializer path then emits action nodes for bindings that are not actually exported, introducing incorrect graph nodes and potential resolutions.

Useful? React with 👍 / 👎.

Comment on lines +237 to +240
static OPEN: OnceLock<Regex> = OnceLock::new();
let open = OPEN.get_or_init(|| {
Regex::new(r"^[^=]*=\s*(?:(?:Object\.(?:freeze|seal)\s*)?\(\s*)*\{")
.expect("object literal opening pattern")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Scan past arrows in typed object declarations

The opening pattern assumes the first = in a declarator is the initializer assignment. A common annotation such as const api: { run: () => void } = { run: impl }; contains the = from => first, so the regex fails and returns Unknown; consequently the new alias-binding path cannot resolve api.run() to impl, either locally or through an import.

Useful? React with 👍 / 👎.

Comment on lines +251 to +255
while index < code.len() {
match code[index] {
'{' | '(' | '[' => depth += 1,
')' | ']' => depth = depth.saturating_sub(1),
'}' if depth == 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.

P2 Badge Ignore regex-literal delimiters while scanning members

blank_string_contents recognizes regex literals but deliberately leaves their contents unchanged, while this loop interprets every brace and comma as object structure. Thus a valid literal such as const api = { pattern: /}/, run() {} }; terminates at the } inside the regex and reports run absent, preventing api.run() from resolving to the extracted member; braces or commas in other regex properties similarly corrupt member boundaries.

Useful? React with 👍 / 👎.

@codecov

codecov Bot commented Oct 1, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 91.66667% with 138 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
crates/codegraph-resolve/src/name_matcher.rs 88.33% 47 Missing ⚠️
crates/codegraph-resolve/src/js_store.rs 89.64% 32 Missing ⚠️
crates/codegraph-extract/src/walker.rs 85.84% 31 Missing ⚠️
crates/codegraph-resolve/src/object_literal.rs 92.33% 20 Missing ⚠️
crates/codegraph-extract/src/lang/cpp.rs 96.42% 3 Missing ⚠️
crates/codegraph-resolve/src/alias_binding.rs 84.21% 3 Missing ⚠️
crates/codegraph-resolve/src/import_resolver.rs 98.07% 1 Missing ⚠️
crates/codegraph-resolve/src/resolver.rs 98.63% 1 Missing ⚠️

❌ Your patch check has failed because the patch coverage (91.66%) is below the target coverage (95.00%). You can increase the patch coverage or adjust the target coverage.

Impacted file tree graph

@@            Coverage Diff             @@
##             main     #277      +/-   ##
==========================================
- Coverage   95.22%   95.20%   -0.02%     
==========================================
  Files         151      153       +2     
  Lines       91465    92926    +1461     
==========================================
+ Hits        87099    88472    +1373     
- Misses       4366     4454      +88     
Files with missing lines Coverage Δ
crates/codegraph-extract/src/lang/c.rs 94.51% <100.00%> (+0.35%) ⬆️
crates/codegraph-extract/src/lang/javascript.rs 90.22% <100.00%> (+0.46%) ⬆️
crates/codegraph-extract/src/lang/jsx.rs 88.00% <100.00%> (+5.39%) ⬆️
crates/codegraph-extract/src/lang/python.rs 94.65% <100.00%> (+7.15%) ⬆️
crates/codegraph-extract/src/lang/tsx.rs 96.42% <100.00%> (+7.96%) ⬆️
crates/codegraph-extract/src/spec.rs 88.00% <100.00%> (+0.37%) ⬆️
crates/codegraph-resolve/src/awaited.rs 96.06% <100.00%> (+1.35%) ⬆️
crates/codegraph-resolve/src/source_facts.rs 99.13% <100.00%> (+0.34%) ⬆️
crates/codegraph-store/src/index_state.rs 83.33% <ø> (ø)
crates/codegraph-resolve/src/import_resolver.rs 96.78% <98.07%> (+0.12%) ⬆️
... and 7 more

... and 1 file with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@sunerpy
sunerpy merged commit 4231ee5 into main Oct 1, 2026
9 checks passed
@sunerpy
sunerpy deleted the sync/v1.6.1-alignment branch October 1, 2026 07:38
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.

1 participant