Repository navigation
fix(resolve): v1.6.1 receiver evidence, store and object-literal bindings, and extraction recall - #277
Conversation
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.
There was a problem hiding this comment.
💡 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".
| 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)) |
There was a problem hiding this comment.
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 👍 / 👎.
| 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") |
There was a problem hiding this comment.
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 👍 / 👎.
| while index < code.len() { | ||
| match code[index] { | ||
| '{' | '(' | '[' => depth += 1, | ||
| ')' | ']' => depth = depth.saturating_sub(1), | ||
| '}' if depth == 0 => { |
There was a problem hiding this comment.
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 Report❌ Patch coverage is ❌ 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. @@ 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
... and 1 file with indirect coverage changes 🚀 New features to boost your workflow:
|
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 inUPSTREAM.mdstaysv1.6.0until the release gate.Receiver evidence — extraction version 13 → 14
(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.list.map(),cache.get()andemitter.on()no longer bind to project methods through capitalization or word overlap; typed, constructed, imported, object-literal and class receivers still resolve.this.#field.m()resolves on the field's declared or constructed type, public and private names stay apart, a constructor'sthis.field = new T()counts, and each class field declaration is read once per pass instead of compiling four regexes per call.d.setdefault(k, []).append(v)keepsd.setdefault().appendand never binds a same-named function.//export,extern "C"); cherry-picked from the integration branch together with #2040 (unknown Python import members stay unresolved) and #2055 (a value+interface pair bindsimplementsto the interface).Recall
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.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.{ getMode: Effect.fn("x")(function* () {}) }is a function namedgetModethat owns its body's calls.NATIVE_FN(get_version) { … }isget_versionwhen a local#defineproves it.Corrections found along the way
propertykey (upstream #808) instead of<anonymous>.this.a.b.m()keeps its last segment (b.m) rather than upstream's barem, 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 unresolvedapi.nested.run()site is nowapi.nested.run(wasnested.run);edges.jsonunchanged.python/nodes.json: thepkg/__init__.pyfile node gains its module docstring.Performance
initof the generated 5,000-reference corpora (one run, same host):this.field+ awaited receiversVerification
make pre-ci: all checks passed; 3,757 tests, 0 failed; archive smokecodegraph 0.50.3.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.equivalenceself-equivalence passes for the regenerated corpora.oxfmt --checkanddocs-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