GitNexus/docs/languages/local-import-scoping.md

17 KiB

Local import scoping

GitNexus resolves statically known local imports through their lexical owner and actual declaration identity. A nearer name that is unresolved, unavailable, or noncallable blocks an unrelated outer or workspace fallback. An absent name can continue through the existing lookup rules. This is static analysis: it does not execute loaders or infer arbitrary runtime initialization order.

Shared contract

Language rules stay in providers. The shared name-claim lookup distinguishes absent, blocked, and resolved before filtering by target kind. Its regressions cover noncallable barriers, unresolved imports, type/value lookup, declaration positions, and inferred aliases that retain their initializer's lookup position. Calls, constructors, receiver/type inference, inheritance, namespace lookup, and fallback consumers use these ownership facts rather than independently choosing a same-named symbol.

The shared records carry ownership, activation positions, directive destinations, and lookup purpose as serializable data. They retain declaration identity and ambiguity; they contain no AST nodes or callbacks. declaredAtScope describes ownership, while runsOnlyWhenCalled describes deferred execution. A function-local Rust use or C #include is not deferred merely because of its position; Python and Ruby loading can be. The function-local import chain tests pin that distinction.

Providers with expanded lexical support

These seven entries are part of the 18-provider registry. The evidence links cover exact targets and negative cases, not just edge counts.

Provider Supported behavior Source and regression evidence
Python Function-local named, aliased, and namespace imports; compiler-local ownership; ordered class lookup; global/nonlocal destinations. Lexical facts, local imports, namespace semantics
TypeScript Literal genuine require, immediately awaited import, and import-equals; namespace, named, and renamed bindings; type/value separation. Loader collector, loader tests, type ownership
JavaScript The same supported runtime loaders and lexical barriers, using JavaScript captures for JS/JSX. Captures, JS/JSX loader cases
Vue Local loader facts in extracted scripts, with original declaration graph positions preserved. Explicit JS/JSX uses its own semantic grammar inside the worker; TS reuses the cached tree. Script delegation, direct/pre-extracted parity, real workers
Rust Lexical use, producer identity for imported factories/types, and Cargo-aware crate/module identity. Unresolved explicit imports block unrelated fallback. Lexical facts, import targets, local scope, Cargo boundaries
C++ Position-sensitive local using declarations/directives, separate from includes; propagated header claims retain unresolved named using barriers. Captures, local using, header using
Zig Literal @import namespace/member bindings, block activation, container hoisting, stable deep aliases, and inline @import receivers. Captures, local imports, static gating

Python boundaries

Compiler-local binders include parameters, assignment patterns, loop/with/except targets, match captures, augmented assignment, deletion, and comprehension/walrus bindings. Comprehension targets do not leak, and the first iterable is evaluated outside the comprehension environment. Class bodies use ordered locals; methods skip class locals for bare-name lookup while explicit instance receivers remain available. Annotation-only declarations distinguish compiler ownership from a runtime store. These cases are characterized in the Python local-import tests.

Mixed import/value rebinding and conflicting import identities conservatively abstain throughout the environment, including simple sequential rebinding; this is not a control-flow evaluator. A definite def/class declaration can still supply its own target after activation. The provider policy and rebinding regressions define this boundary. There is no full annotation evaluator, exec evaluation, or general closure scheduling model. A deferred global/nonlocal installer is not assumed to have executed for unrelated callers.

Conditional imports with one compatible identity remain available for structural reference resolution, including TYPE_CHECKING annotations and try imports. Their runtime execution is not guaranteed by a resolved graph edge.

Default expressions look up names in the enclosing environment. FastAPI's synthetic dependency graph caller is recorded separately as callerScope; it does not move lexical lookup into the handler body. Both direct extraction and worker transport check this distinction.

JavaScript-family boundaries

Loader recognition checks AST shape and binding identity, including parameters, catch/destructuring bindings, temporal-dead-zone ownership, and writes to the loader or imported handle. CommonJS forwarding uses that same identity check. Reassigning one destructured import leaves unchanged sibling re-exports intact. Writes to ordinary locals retain their declared type and callable-value facts. Unawaited dynamic imports remain Promise/file-dependency facts rather than module namespace aliases. Computed specifiers, indirect loader/Promise chains, and unsupported destructuring remain unresolved; see the loader regressions.

Vue's explicit JS/JSX script requires an additional bounded semantic parse because the worker's structural tree uses TypeScript. That parse runs inside the real worker. The parity test witnesses returned worker facts; a main-thread fallback does not satisfy it.

Rust, C++, and Zig boundaries

Rust uses Cargo editions, target roots, and module identities, including files with multiple valid identities. Reusable per-analysis indexes bound repeated lookup work; this is not a claim of a global indexing speedup. See Cargo metadata and its tests. Exact same-name inline pub use forwarding is supported. A forwarding alias that changes symbol spelling through a file-only target conservatively remains unresolved; ambiguous same-file homonyms and re-export cycles cannot select an unrelated declaration. These limits are explicit in the Cargo fallback regressions. Inferred receiver types retain their receiving declaration range. When same-block shadowing replaces a retained type fact, earlier uses cannot consume the later declaration's type and may remain unresolved; this is not full SSA inference. See the factory ownership regressions.

C++ includes keep their compilation-unit behavior alongside lexical using. The header tests cover literal includes, declaration positions, unresolved named using claims, and overload identity. They do not establish a full preprocessor, namespace-alias evaluator, or arbitrary transitive using namespace chain support. Zig nonliteral imports remain unresolved; its block-local names do not escape or activate before their declarations.

Retained provider behavior

The other eleven providers retain their existing import/loading models. This work does not reinterpret every dependency construct as a local value import.

Provider Retained model and boundary Evidence
Ruby Wildcard loading; method-local loading is deferred. The extractor recognizes string arguments to require, require_relative, and load. The linked behavioral tests establish require/require_relative and deferral, not arbitrary load execution. Extractor, loading, deferral
PHP Namespace use imports are distinct from closure capture and trait use. The scope provider does not propagate names through include/require. Query, ownership, resolver coverage
C Header includes remain preprocessor/compilation-unit dependencies, including includes written inside functions; no full preprocessing is added. Include interpretation, include tests, deferral boundary
Objective-C Headers, modules, and compilation-unit siblings retain the dedicated provider rules. Provider guide, resolver, workspace tests
COBOL COPY stays on the dedicated copybook path with module ownership. Interpretation, COPY target parity
Go File imports and package sibling visibility retain separate provider rules. Import ownership, imports, package siblings
Java Imports belong to the compilation unit. Ownership, named/wildcard import resolution
Kotlin File import headers retain their existing alias/package resolution. Header decomposition, resolver tests
C# Namespace/file using directives remain imports; resource using statements are not import directives. Directive query, import tests
Swift Imports and target/module visibility retain provider ownership. Ownership, import decomposition, target siblings
Dart Library imports retain existing package identity and visibility rules. Ownership, library/package regressions

Taint provenance

Taint source, sink, and sanitizer matching uses the same lexical provenance. A genuine external import may match its raw module name even when that package has no indexed source. A sibling import, local shadow, or type-only import cannot authorize that match. General copied-handle tracking is outside this support. The taint regressions cover extraction, emission, and function summaries, including abstention when exact call-site positions are missing.

Cache migration and verification evidence

Parse-cache schema 136 replaces 133, 134, and 135. It invalidates overly broad Python/TS/JS binding barriers, incorrect parameter-annotation lookup ownership, and embedded Vue grammar and source positions. The same version gate applies to the durable ParsedFile store. Incompatible records are rejected and rebuilt on the next analysis; unchanged records can then be reused. The schema regression explicitly rejects valid schema-133, schema-134, and schema-135 records in both stores. PDG-mode chunk keys also advance to pdg:6, so embedded-script CFGs with incorrect call anchors are rebuilt before the resolved-callee join.

The real-worker parity suite witnesses worker boot, dispatch, and returned ParsedFiles, then compares complete provider facts with direct extraction, including callerScope and C++ side-channel data. Optional grammar availability is explicit in the test gate.

The persistence suite loads both stores afresh from disk. Its parse-cache chunks contain no embedded ParsedFiles, so warm equality requires durable-store reuse. It checks identical CALLS/IMPORTS identities with zero extraction and zero worker dispatch on an unchanged warm run. Editing a local import target or shadowing require removes the stale target, and a second warm run preserves the edited graph.

Both suites are in the cross-platform manifest; the shard regression ensures each is selected exactly once across the four Windows/macOS shards. CI owns native execution on those systems; Ubuntu's full suite discovers the tests normally. These are focused evidence boundaries, not a statement that the final full suite or every platform has passed.

Relationship to main and issue #3499

The integrated main baseline 50aa4be3 already includes #3502's nested Python declaration ownership fix for the original #3499 case and #3504's aliased package re-export fix. Their Python integration coverage and module namespace coverage remain relevant. This branch extends that base with the lexical claims, provider semantics, and persistence evidence described above. Issue coverage is Related #3499; this document does not assert that all dynamic Python behavior is solved or make an issue-status claim.