mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-10-10 03:27:59 +00:00
fix(zig): emit value-ref for a callable named in value position
`mapReferenceKindToEdgeType` has handled the registration-vs-invocation case since #2437, and TypeScript, JavaScript and C++ all emit `value-ref`. Zig emitted zero — so a Zig function handed somewhere as a VALUE was absent from the graph entirely. That is not a corner: Zig's JS bridge is built out of this one shape, pub const namespaceURI = bridge.accessor(Element.getNamespaceUri, null, .{}); and `bridge.{accessor,function,indexed,…}` appears 2,047 times across 257 files in lightpanda-io/browser — the project's whole JS<->Zig surface, none of it reaching the graph. `impact` on `Element.getNamespaceUri`, which IS the DOM `Element.namespaceURI` accessor, answered with its two in-file callers. Three query rules, tagging `@reference.value-ref` on: a bare identifier in argument position (`bridge.accessor(_tagName, …)`), a qualified one (`bridge.accessor(Element.getNamespaceUri, …)`), and a const binding initialiser (`pub const defaultHandler = onReset;`). Everything downstream already existed — no new edge type, no schema change, no capture-machinery change, no baseline edited. One grammar detail is load-bearing: in tree-sitter-zig, call arguments are DIRECT children of `call_expression` — there is no `arguments` node, only builtins have one — so the callee has to be consumed explicitly by `function:`. Without that binding the same rule also matches the callee of `foo(bar)` and mints a USES edge shadowing the call's own CALLS edge. There is a test for it. The rules are deliberately broad — `js.Bridge(Element)` and `register(count)` match too — because the callable gate in the property-dispatch pass (Function/Method/Constructor only) is the filter, the same design that keeps TypeScript's `{ port: DEFAULT_PORT }` from registering anything. Measured on the real corpus: all 3,169 emitted edges land on a Method (3,146) or Function (23). Deliberately NOT modelled: the terminal invoke. The value reaches `Accessor.init` -> a struct field -> `Factory.zig` reflection (`inline for`, `@typeInfo`) -> `Caller.zig`'s `@call(.auto, func, args)` over `func: anytype`. Resolving that needs comptime evaluation. The point is to stop dropping the reference; the shortfall is now reported as `epistemic: "lower-bound"` by the preceding commit instead of being papered over. Measured on lightpanda-io/browser (698 .zig files), wiped index both times: 30,222 nodes / 71,070 edges -> 30,222 nodes / 74,229 edges. The delta is entirely `USES` (3,169 after vs 10 before, and every one is a value-ref); CALLS, ACCESSES, IMPORTS, HAS_METHOD, DEFINES and the rest are unchanged — purely additive, no node invented. The fixture joins the existing `zig-idioms` corpus rather than adding a new one, because `bench/receiver-resolution` uses `test/fixtures/lang-resolution` as its `--check` corpus. That gate, `scope-capture` (15 languages), `zig-cross-file-resolution` and every other bench `--check` pass unchanged.
This commit is contained in:
parent
ea2372e453
commit
fda651dd75
5 changed files with 374 additions and 0 deletions
132
DECISIONS.md
132
DECISIONS.md
|
|
@ -96,3 +96,135 @@ Test: `test/integration/impact-callable-value-references.test.ts` (5 cases,
|
|||
including three controls: an ordinary CALLS-only target, a non-value-ref `USES`
|
||||
edge, and the downstream direction).
|
||||
|
||||
|
||||
### D1 — the missing edge (Zig emits `value-ref`)
|
||||
|
||||
**D1-1. Query rules only; no new capture machinery.** Three rules added to
|
||||
`ZIG_SCOPE_QUERY`, tagging `@reference.name @reference.value-ref`. Everything
|
||||
downstream already existed: `referenceKindFromAnchor` → `'value-ref'` →
|
||||
`mapReferenceKindToEdgeType` → `USES`, resolved by the property-dispatch pass.
|
||||
Zero changes to `captures.ts`, to `callable-flow-captures.ts`, to the schema, or
|
||||
to any edge kind.
|
||||
|
||||
*Rejected:* extending `zigCallableCaptureOptions` / `synthesizeCallableFlowCaptures`
|
||||
(the cell/site model). That machinery exists to trace a value to its INVOKE site
|
||||
and emit CALLS — which is the explicit non-goal here, needs comptime evaluation,
|
||||
and already reports its own all-or-nothing failure (`callable-value-flow:
|
||||
candidate set exceeded the cap`). The task is to stop dropping the reference,
|
||||
and a reference is exactly what `value-ref` is for.
|
||||
|
||||
**D1-2. The callee must be consumed by `function:`.** In tree-sitter-zig,
|
||||
call arguments are DIRECT children of `call_expression` — there is no `arguments`
|
||||
node (only builtins have one; verified against the grammar). A naive
|
||||
`(call_expression (identifier) @x)` therefore also matches the callee of
|
||||
`foo(bar)`, minting a USES edge that shadows the call's own CALLS edge. Binding
|
||||
`function: (_)` consumes it. Pinned by a test
|
||||
(`does not mint a value reference for the CALLEE of an ordinary call`).
|
||||
|
||||
**D1-3. Both the bare and the qualified argument shape.** The motivating line is
|
||||
qualified (`bridge.accessor(Element.getNamespaceUri, …)`) and its sibling is bare
|
||||
(`bridge.accessor(_tagName, …)`); the real table uses both. For the qualified
|
||||
form `@reference.name` is the MEMBER and the object is captured as
|
||||
`@reference.receiver`, because the member is the name the scope walk resolves.
|
||||
*Trade-off accepted:* the property-dispatch pass ignores the receiver, so a
|
||||
qualified reference resolves by tail name and could in principle bind a
|
||||
same-named local callable. Measured on the real corpus this does not bite: all
|
||||
3,169 emitted edges land on `Method` (3,146) or `Function` (23), and the
|
||||
94 registrations in `Element.zig`'s `JsApi` read as the DOM Element API surface
|
||||
one for one. *Rejected:* resolving through the receiver — that is the
|
||||
receiver-bound-call path, a much larger change, and the callable gate already
|
||||
carries the precision.
|
||||
|
||||
**D1-4. Rules are deliberately broad; the CALLABLE GATE is the filter.**
|
||||
`js.Bridge(Element)` and `register(count)` match too. `findCallableBindingInScope`
|
||||
keeps only Function/Method/Constructor, so they emit nothing — the same design
|
||||
that stops TypeScript's `{ port: DEFAULT_PORT }` from registering anything
|
||||
(tie-breaker 2: consistency with how TS/JS already emit `value-ref`).
|
||||
|
||||
**D1-5. No `@reference.property-key`.** Zig has no object-literal key to
|
||||
dispatch through, so these register a reference and never synthesize CALLS.
|
||||
`emitPropertyDispatchCalls` already skips the registration index when
|
||||
`propertyKey` is undefined, and still emits the USES edge.
|
||||
|
||||
**D1-6. Fixture lives in the EXISTING `zig-idioms` corpus**, as
|
||||
`src/webapi/Element.zig`, alongside the `AbortController`/`AbortSignal` JsApi
|
||||
files already there. *Rejected:* a new `zig-callable-values/` fixture directory —
|
||||
`bench/receiver-resolution` uses `test/fixtures/lang-resolution` as its `--check`
|
||||
corpus, so every added fixture risks moving `countArm`. Measured after the fact:
|
||||
it did not move (`[receiver-resolution] OK — shape states and call-drop counts
|
||||
match baseline`), so no rebaseline was needed either way.
|
||||
|
||||
---
|
||||
|
||||
## Measured result — D1 + D2 together
|
||||
|
||||
Re-analyzed from scratch (`rm -rf ~/code/browser/.gitnexus`) with the same
|
||||
command as the baseline.
|
||||
|
||||
| | baseline | after | delta |
|
||||
| --- | --- | --- | --- |
|
||||
| index | 30,222 nodes / 71,070 edges | 30,222 nodes / 74,229 edges | **+3,159 edges, 0 nodes** |
|
||||
| `getNamespaceUri` impactedCount | 5 | 7 | +2 |
|
||||
| `getNamespaceUri` direct | 2 | 3 | +1 |
|
||||
| `getNamespaceUri` risk | LOW | LOW | — |
|
||||
| `getNamespaceUri` **epistemic** | **`exact`** | **`lower-bound`** | the fix |
|
||||
| `getTagNameLower` impactedCount | 30 | 31 | +1 (explained below) |
|
||||
| `getTagNameLower` direct / risk / epistemic | 10 / HIGH / `exact` | 10 / HIGH / `exact` | unchanged |
|
||||
|
||||
**The +3,159 edges are fully accounted for.** Per-type counts in the new index:
|
||||
`USES` = 3,169 and value-ref edges = 3,169 — i.e. every USES edge in this repo is
|
||||
a value reference. The baseline had 10 (all from the single `.js` file,
|
||||
`src/browser/tests/testing.js`), which still has exactly 10. `CALLS` (25,649),
|
||||
`ACCESSES`, `IMPORTS`, `HAS_METHOD`, `DEFINES`, … did not move. The change is
|
||||
purely additive and confined to `USES`.
|
||||
|
||||
**`getTagNameLower` +1, explained.** The single added entry is at depth 2:
|
||||
`USES Struct:src/browser/webapi/Element.zig:JsApi`. `JsApi` registers 94
|
||||
accessors, three of which (`Element.getLocalName`, `Element._prefix`,
|
||||
`Element.getTagNameDump`) are depth-1 callers of `getTagNameLower`. So the
|
||||
binding table genuinely does sit two hops upstream of it, and one node is the
|
||||
correct amount to add. `direct` (10), `risk` (HIGH) and `epistemic` (`exact`)
|
||||
are all unchanged — and `exact` is the meaningful half of the control: the hedge
|
||||
is targeted at symbols with an INBOUND value reference, not sprayed over
|
||||
everything the change touches. `getTagNameLower` is registered nowhere, so it
|
||||
keeps its certainty. I am recording the +1 rather than calling the control
|
||||
"unchanged", because it is a real new fact of exactly the kind D1 exists to
|
||||
record.
|
||||
|
||||
## Gates
|
||||
|
||||
- `npm run build` — clean; `tsc --noEmit -p tsconfig.json` — clean.
|
||||
(`tsconfig.test.json` has ~1,194 pre-existing errors across the fixture tree
|
||||
and test helpers on `origin/main`; none are in the files this branch touches,
|
||||
and no npm script or CI job runs it.)
|
||||
- Bench `--check` gates, all PASS with **no baseline edited**: `scope-capture`
|
||||
(15 languages), `receiver-resolution`, `zig-cross-file-resolution`,
|
||||
`scope-emission`, `python-scope`, `callable-value-flow`, `import-target`,
|
||||
`emit-persistence`, `finalize-reexport`, `cpp-qualified-ns`,
|
||||
`parse-dispatch-rounds`, `spring-config-bindings`.
|
||||
- `npx vitest run` (full suite): **20,552 passed / 10 failed / 78 skipped**
|
||||
(20,640 tests, 1,005 files, ~17 min).
|
||||
|
||||
All 10 failures are **pre-existing on `origin/main` and machine-specific**.
|
||||
Verified, not assumed: I stashed the branch, checked out `0d1aed94`
|
||||
(`origin/main`), rebuilt, and re-ran the six affected files — the **same 10
|
||||
tests fail, identically**. They are:
|
||||
|
||||
| file | tests | cause |
|
||||
| --- | --- | --- |
|
||||
| `analyzer-identity` | 4 | macOS realpath `/var` vs `/private/var` |
|
||||
| `analyzer-identity-in-process-guards` | 2 | EACCES cases, #3092 |
|
||||
| `hooks-e2e` | 2 | "prefers pnpm dlx" — local `pnpm` shim is 1.40.0, below the ≥10.2 gate |
|
||||
| `evidence-provenance-helper` | 1 | golden digest |
|
||||
| `review-agent-workflow` | 1 | pinned-install retry helper |
|
||||
|
||||
None is in a file or dependency cone this branch touches.
|
||||
- Targeted re-run of every suite that exercises `value-ref`, after the final
|
||||
build: `resolvers/zig`, `impact-callable-value-references`,
|
||||
`resolvers/typescript-value-refs`, `resolvers/value-ref-locality`,
|
||||
`resolvers/cpp`, `resolvers/typescript` — **734 / 734 passed**.
|
||||
|
||||
## Nothing marked UNRESOLVED or BLOCKED
|
||||
|
||||
Every fork above was decided and recorded. No gate failed three times; no gate
|
||||
needed a baseline edit.
|
||||
|
|
|
|||
|
|
@ -359,6 +359,60 @@ const ZIG_SCOPE_QUERY = `
|
|||
"const" . (identifier) @type-binding.name
|
||||
(call_expression) @type-binding.type .) @type-binding.alias
|
||||
|
||||
;; References — VALUE positions (#3399): a callable named where a value is
|
||||
;; expected rather than where a callee is. Zig's JS bridge is built entirely
|
||||
;; out of this shape —
|
||||
;;
|
||||
;; pub const namespaceURI = bridge.accessor(Element.getNamespaceUri, null, .{});
|
||||
;;
|
||||
;; — 2,047 such declarations across 257 files in lightpanda-io/browser, the
|
||||
;; project's whole JS↔Zig surface, and NONE of them reached the graph: Zig
|
||||
;; emitted no \`value-ref\` capture at all, so a public DOM accessor's only
|
||||
;; recorded callers were the two internal ones and \`impact\` called that \`exact\`.
|
||||
;;
|
||||
;; These become reference-class USES edges through the existing
|
||||
;; \`mapReferenceKindToEdgeType\` mapping — a registration is not an invocation
|
||||
;; (Kythe \`ref\` vs \`ref/call\`; Joern \`METHOD_REF\`) — resolved by the
|
||||
;; property-dispatch pass, which keeps ONLY callable targets. That callable gate
|
||||
;; is what makes these deliberately broad rules safe: \`js.Bridge(Element)\` and
|
||||
;; \`register(count)\` match too, and emit nothing, exactly as TypeScript's
|
||||
;; \`{ port: DEFAULT_PORT }\` does.
|
||||
;;
|
||||
;; No \`@reference.property-key\` is attached: Zig has no object-literal key to
|
||||
;; dispatch through, so these register a reference and never synthesize CALLS.
|
||||
;; The terminal invoke — \`Accessor.init\` → a struct field → \`Factory.zig\`'s
|
||||
;; \`inline for\`/\`@typeInfo\` → \`@call(.auto, func, args)\` — needs comptime
|
||||
;; evaluation and is deliberately NOT modelled; \`impact\` reports the shortfall
|
||||
;; as \`epistemic: "lower-bound"\` instead of pretending to certainty.
|
||||
|
||||
;; Call ARGUMENTS. In tree-sitter-zig arguments are direct children of
|
||||
;; \`call_expression\`, NOT wrapped in an \`arguments\` node (only builtins have
|
||||
;; one), so the callee has to be consumed explicitly by \`function:\` — without
|
||||
;; that binding the same rule also matches the callee of \`foo(bar)\` and mints a
|
||||
;; USES edge duplicating the call.
|
||||
(call_expression
|
||||
function: (_)
|
||||
(identifier) @reference.name @reference.value-ref)
|
||||
|
||||
;; Qualified argument — \`bridge.accessor(Element.getNamespaceUri, …)\`. The
|
||||
;; RECEIVER is captured alongside the member so the site carries the owner it
|
||||
;; was written with; \`@reference.name\` stays the member, which is the name the
|
||||
;; scope walk resolves.
|
||||
(call_expression
|
||||
function: (_)
|
||||
(field_expression
|
||||
object: (_) @reference.receiver
|
||||
member: (identifier) @reference.name) @reference.value-ref)
|
||||
|
||||
;; Const binding initialiser — \`pub const defaultHandler = onReset;\`. Both
|
||||
;; anchors are load-bearing: the leading \`.\` pins the bound name to the first
|
||||
;; named child (see the declaration rules above), and the trailing \`.\` keeps the
|
||||
;; initializer as the LAST child, so a \`const x: T = y\` annotation shape cannot
|
||||
;; put the TYPE in value position.
|
||||
(variable_declaration
|
||||
"const" . (identifier)
|
||||
(identifier) @reference.name @reference.value-ref .)
|
||||
|
||||
;; References — free calls: foo(...)
|
||||
(call_expression
|
||||
function: (identifier) @reference.name) @reference.call.free
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ pub usingnamespace @import("mixin.zig");
|
|||
pub const Interfaces = .{
|
||||
@import("webapi/AbortController.zig"),
|
||||
@import("webapi/AbortSignal.zig"),
|
||||
@import("webapi/Element.zig"),
|
||||
};
|
||||
|
||||
pub fn main() void {
|
||||
|
|
|
|||
99
gitnexus/test/fixtures/lang-resolution/zig-idioms/src/webapi/Element.zig
vendored
Normal file
99
gitnexus/test/fixtures/lang-resolution/zig-idioms/src/webapi/Element.zig
vendored
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
// The JS-API binding-table idiom, as Lightpanda writes it (#3399).
|
||||
//
|
||||
// Every accessor below hands a Zig function to `bridge.accessor` AS A VALUE:
|
||||
// the function is REGISTERED here, never called here. The eventual invocation
|
||||
// runs through comptime reflection (`@call(.auto, func, args)` over a
|
||||
// `func: anytype` field), which no static walk can follow — that terminal hop
|
||||
// is out of scope. What was NOT acceptable is dropping the reference entirely:
|
||||
// `impact` then reported the accessor as having only its two in-file callers
|
||||
// and called that answer `exact`.
|
||||
//
|
||||
// A file-as-struct, like every webapi module in the real tree.
|
||||
const Element = @This();
|
||||
|
||||
_namespace: u8 = 0,
|
||||
|
||||
// ── Registered accessors ────────────────────────────────────────────────────
|
||||
|
||||
pub fn getNamespaceUri(self: *Element) u8 {
|
||||
return self._namespace;
|
||||
}
|
||||
|
||||
// An ordinary in-file caller. The point of the defect is that the REGISTRATION
|
||||
// was missing, not that the symbol looked like a leaf: a plausible-but-short
|
||||
// caller list is exactly what makes `epistemic: "exact"` dangerous.
|
||||
pub fn lookupNamespaceUri(self: *Element) u8 {
|
||||
return self.getNamespaceUri();
|
||||
}
|
||||
|
||||
// ── The control ─────────────────────────────────────────────────────────────
|
||||
|
||||
// Called normally and registered NOWHERE. Its edges must not move: a change
|
||||
// that hedges or re-links every method would be indistinguishable from one
|
||||
// that models value references, and only the second is correct.
|
||||
pub fn getTagNameLower(self: *Element) u8 {
|
||||
return self._namespace;
|
||||
}
|
||||
|
||||
pub fn describe(self: *Element) u8 {
|
||||
return self.getTagNameLower();
|
||||
}
|
||||
|
||||
// ── The binding table ───────────────────────────────────────────────────────
|
||||
|
||||
pub const JsApi = struct {
|
||||
pub const bridge = Bridge(Element);
|
||||
|
||||
// QUALIFIED value reference — the accessor names its container explicitly.
|
||||
// This is the exact line from Element.zig:2296 that #3399 was filed over.
|
||||
pub const namespaceURI = bridge.accessor(Element.getNamespaceUri, null, .{});
|
||||
|
||||
// BARE value reference to a sibling declared in this same container.
|
||||
pub const tagName = bridge.accessor(_tagName, null, .{});
|
||||
|
||||
fn _tagName(self: *Element) u8 {
|
||||
return self.getTagNameLower();
|
||||
}
|
||||
};
|
||||
|
||||
// ── Const binding initialiser ───────────────────────────────────────────────
|
||||
|
||||
fn onReset(self: *Element) u8 {
|
||||
return self._namespace;
|
||||
}
|
||||
|
||||
// A `const` whose initialiser IS a function value. Second value position, same
|
||||
// class of drop.
|
||||
pub const defaultHandler = onReset;
|
||||
|
||||
// ── comptime anytype sink ───────────────────────────────────────────────────
|
||||
|
||||
var registered: ?*const fn (*Element) u8 = null;
|
||||
|
||||
// Takes a callable by value into a `comptime … anytype` parameter and STORES
|
||||
// it. Nothing in this file calls `f`.
|
||||
pub fn register(comptime f: anytype) void {
|
||||
registered = f;
|
||||
}
|
||||
|
||||
fn onTick(self: *Element) u8 {
|
||||
return self._namespace;
|
||||
}
|
||||
|
||||
pub fn boot() void {
|
||||
// `onTick` is never called in this file — it is handed over as a value and
|
||||
// invoked later through `registered`.
|
||||
register(onTick);
|
||||
}
|
||||
|
||||
fn Bridge(comptime T: type) type {
|
||||
_ = T;
|
||||
return struct {
|
||||
pub fn accessor(comptime getter: anytype, comptime setter: anytype, comptime opts: anytype) u8 {
|
||||
_ = getter;
|
||||
_ = setter;
|
||||
_ = opts;
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
@ -229,6 +229,94 @@ describe.skipIf(!zigAvailable)('Zig idioms (zig-idioms fixture)', () => {
|
|||
expect(calls).toContain('main → incr');
|
||||
});
|
||||
|
||||
/**
|
||||
* #3399 — a callable named in VALUE position.
|
||||
*
|
||||
* `src/webapi/Element.zig` is the JS-API binding-table idiom verbatim:
|
||||
* `pub const namespaceURI = bridge.accessor(Element.getNamespaceUri, null, .{});`
|
||||
* registers a Zig function with the JS bridge instead of calling it. Zig
|
||||
* emitted NO `value-ref` capture at all, so every one of those references was
|
||||
* dropped — 2,047 of them across 257 files in lightpanda-io/browser, the whole
|
||||
* JS<->Zig surface — and `impact` on a public DOM accessor answered with its
|
||||
* two in-file callers and the verdict `epistemic: "exact"`.
|
||||
*
|
||||
* These assert USES and not CALLS on purpose. A registration is not an
|
||||
* invocation (Kythe `ref` vs `ref/call`; Joern METHOD_REF), and the call that
|
||||
* eventually happens goes through comptime reflection this analyzer cannot
|
||||
* follow. The claim being pinned is the reference, not the dispatch.
|
||||
*/
|
||||
describe('callable values (#3399)', () => {
|
||||
let uses: string[];
|
||||
let valueRefs: string[];
|
||||
beforeAll(() => {
|
||||
const edges = getRelationships(result, 'USES');
|
||||
uses = edgeSet(edges);
|
||||
// The reason text is spelled out rather than imported from
|
||||
// `VALUE_REF_EDGE_REASON`, deliberately and as `typescript-value-refs.test.ts`
|
||||
// already does: `impact`'s epistemic probe matches this exact string in
|
||||
// the stored graph, so a change to the constant's VALUE (as opposed to
|
||||
// its name) must fail a test rather than quietly agree with itself on
|
||||
// both sides.
|
||||
valueRefs = edgeSet(edges.filter((e) => e.rel.reason === 'scope-resolution: value-ref'));
|
||||
});
|
||||
|
||||
it('records a QUALIFIED function value handed to a call (`bridge.accessor(Element.getNamespaceUri, …)`)', () => {
|
||||
expect(valueRefs).toContain('JsApi → getNamespaceUri');
|
||||
});
|
||||
|
||||
it('records a BARE function value handed to a call (`bridge.accessor(_tagName, …)`)', () => {
|
||||
expect(valueRefs).toContain('JsApi → _tagName');
|
||||
});
|
||||
|
||||
it('records a function value in a const initialiser (`pub const defaultHandler = onReset;`)', () => {
|
||||
expect(valueRefs).toContain('Element → onReset');
|
||||
});
|
||||
|
||||
it('records a function passed into a `comptime f: anytype` parameter and stored', () => {
|
||||
// The shape the non-goal is about: `register(onTick)` stores the value in
|
||||
// a module-level field and nothing in the file ever calls `onTick`. The
|
||||
// terminal invoke needs comptime evaluation and is NOT modelled — but the
|
||||
// reference must survive, or `onTick` reads as dead code.
|
||||
expect(valueRefs).toContain('boot → onTick');
|
||||
expect(calls).not.toContain('boot → onTick');
|
||||
});
|
||||
|
||||
it('emits USES, never CALLS, for a registration', () => {
|
||||
// The whole distinction: if these became CALLS, `impact` would claim the
|
||||
// accessor is invoked from the binding table, which is not a fact the
|
||||
// analyzer has.
|
||||
expect(calls).not.toContain('JsApi → getNamespaceUri');
|
||||
expect(calls).not.toContain('JsApi → _tagName');
|
||||
});
|
||||
|
||||
it('does not mint a value reference for a non-callable argument', () => {
|
||||
// `Bridge(Element)` passes a TYPE. The property-dispatch pass keeps only
|
||||
// Function/Method/Constructor targets, which is what makes the broad
|
||||
// capture rules safe — the same gate that stops TypeScript's
|
||||
// `{ port: DEFAULT_PORT }` from registering anything.
|
||||
expect(valueRefs).not.toContain('JsApi → Element');
|
||||
expect(uses.filter((u) => u === 'JsApi → Element')).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('leaves a method that is only ever CALLED untouched', () => {
|
||||
// The control. `getTagNameLower` is called twice and registered nowhere;
|
||||
// a change that sprayed USES edges over every method would satisfy every
|
||||
// assertion above and still be wrong.
|
||||
expect(calls).toContain('describe → getTagNameLower');
|
||||
expect(calls).toContain('_tagName → getTagNameLower');
|
||||
expect(valueRefs.filter((v) => v.endsWith(' → getTagNameLower'))).toEqual([]);
|
||||
});
|
||||
|
||||
it('does not mint a value reference for the CALLEE of an ordinary call', () => {
|
||||
// `register(onTick)` must produce ONE value reference (the argument), not
|
||||
// two: without binding the callee to the `function:` field the same rule
|
||||
// also matches `register` itself and every call in the repo would emit a
|
||||
// USES edge shadowing its own CALLS edge.
|
||||
expect(valueRefs).not.toContain('boot → register');
|
||||
expect(calls).toContain('boot → register');
|
||||
});
|
||||
});
|
||||
|
||||
it('types a receiver from its ANNOTATION (`var b: Counter = undefined; b.twice()`, `const c: Counter = .init(); c.get()`)', () => {
|
||||
// The declared type is the ONLY type source for `= undefined` and for
|
||||
// 0.14+ decl literals (`.init`, `.empty`), which current std uses for
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue