From 4fc62fcbe70a53745fcdd80d9633595adde3fe23 Mon Sep 17 00:00:00 2001 From: swapnil <78632212+swapnilpaliwal-sd@users.noreply.github.com> Date: Tue, 3 Nov 2026 09:16:58 -0800 Subject: [PATCH 1/2] typescript: a name destructured from a type-literal-typed value takes its member's type `const { port } = opts` with `opts: { port: Port }`, and `({ port }: Opts)` with `type Opts = { port: Port }`, left `port` untyped, so `port.execute()` was unresolved while `opts.port.execute()` beside it resolved. A type literal has no declaration for param_type / expr_type to name: it is a shape. The destructuring rules for parameters and variables now read the shape too (a shape is its own scope), for fields, nested shapes and methods. Case 100 covers inline and aliased literals, parameters and variables, a nested shape and a shape method, with controls: an untyped source stays unresolved, and a look-alike literal resolves to its own method, not to the interface. Case 75: five destructured-prop calls now resolve to the targets the compiler oracle had listed as known-missing (the shadowing guard still holds); they still show as missing+extra only because a destructured caller is labelled once per bound name. On a 1.9k-file Deno TypeScript repo: known_edge 7,410 -> 7,441, ambiguous_unknown 35,820 -> 35,789, 0 resolved edges lost; spot-checked against the source. Co-authored-by: axiomcode-bot[bot] <334110751+axiomcode-bot[bot]@users.noreply.github.com> --- .../src/globals.d.ts | 11 +++++ .../src/tsconfig.json | 1 + .../src/use.ts | 47 +++++++++++++++++++ .../100-destructure-from-type-literal.edges | 10 ++++ .../100-destructure-from-type-literal.entries | 13 +++++ .../100-destructure-from-type-literal.fields | 1 + ...estructure-from-type-literal.fields-oracle | 9 ++++ ...estructure-from-type-literal.known-missing | 6 +++ .../100-destructure-from-type-literal.oracle | 7 +++ ...100-destructure-from-type-literal.type-use | 9 ++++ ...destructure-from-type-literal.types-oracle | 7 +++ .../expected/75-jsx-wrapper-guards.edges | 10 ++-- .../expected/75-jsx-wrapper-guards.oracle | 7 ++- .../engine/resolution/reference-types.dl | 43 +++++++++++++++++ graph/typescript/souffle/decls_all.dl | 2 + 15 files changed, 177 insertions(+), 6 deletions(-) create mode 100644 graph/test/typescript/cases/100-destructure-from-type-literal/src/globals.d.ts create mode 100644 graph/test/typescript/cases/100-destructure-from-type-literal/src/tsconfig.json create mode 100644 graph/test/typescript/cases/100-destructure-from-type-literal/src/use.ts create mode 100644 graph/test/typescript/expected/100-destructure-from-type-literal.edges create mode 100644 graph/test/typescript/expected/100-destructure-from-type-literal.entries create mode 100644 graph/test/typescript/expected/100-destructure-from-type-literal.fields create mode 100644 graph/test/typescript/expected/100-destructure-from-type-literal.fields-oracle create mode 100644 graph/test/typescript/expected/100-destructure-from-type-literal.known-missing create mode 100644 graph/test/typescript/expected/100-destructure-from-type-literal.oracle create mode 100644 graph/test/typescript/expected/100-destructure-from-type-literal.type-use create mode 100644 graph/test/typescript/expected/100-destructure-from-type-literal.types-oracle diff --git a/graph/test/typescript/cases/100-destructure-from-type-literal/src/globals.d.ts b/graph/test/typescript/cases/100-destructure-from-type-literal/src/globals.d.ts new file mode 100644 index 000000000..dd5843cc1 --- /dev/null +++ b/graph/test/typescript/cases/100-destructure-from-type-literal/src/globals.d.ts @@ -0,0 +1,11 @@ +// The case's own standard library under `noLib`. +interface Object {} +interface Boolean {} +interface Number {} +interface String {} +interface Array { length: number; } +interface Function {} +interface CallableFunction extends Function {} +interface NewableFunction extends Function {} +interface IArguments {} +interface RegExp {} diff --git a/graph/test/typescript/cases/100-destructure-from-type-literal/src/tsconfig.json b/graph/test/typescript/cases/100-destructure-from-type-literal/src/tsconfig.json new file mode 100644 index 000000000..dcbfb4f8d --- /dev/null +++ b/graph/test/typescript/cases/100-destructure-from-type-literal/src/tsconfig.json @@ -0,0 +1 @@ +{ "compilerOptions": { "strict": true, "noLib": true, "target": "es2022" } } diff --git a/graph/test/typescript/cases/100-destructure-from-type-literal/src/use.ts b/graph/test/typescript/cases/100-destructure-from-type-literal/src/use.ts new file mode 100644 index 000000000..1012b8536 --- /dev/null +++ b/graph/test/typescript/cases/100-destructure-from-type-literal/src/use.ts @@ -0,0 +1,47 @@ +// A name destructured out of a value whose declared type is an object TYPE LITERAL +// (inline, or through an alias) takes that member's type, as `opts.port.execute()` does. +export interface Port { execute(x: number): number } +export interface Clock { now(): number } + +export function inlineVar(opts: { port: Port; n: number }) { + const { port } = opts; + return port.execute(1); // -> Port.execute +} +export function memberAccess(opts: { port: Port }) { + return opts.port.execute(2); // -> Port.execute (already resolved: the reference) +} +type Opts = { port: Port; clock: Clock }; +export function aliasParam({ port, clock }: Opts) { + clock.now(); // -> Clock.now + return port.execute(3); // -> Port.execute +} +export function inlineParam({ port }: { port: Port }) { + return port.execute(4); // -> Port.execute +} +export function aliasVar(o: Opts) { + const { clock } = o; + return clock.now(); // -> Clock.now +} +export function nestedShape(opts: { deps: { port: Port } }) { + const { deps } = opts; + const { port } = deps; + return port.execute(5); // -> Port.execute +} +export function shapeMethod(opts: { run(x: number): number }) { + const { run } = opts; + return run(6); // -> the type literal's run +} + +// controls: no declared type, and a name the shape does not have +export function untyped(opts: any) { + const { port } = opts; + return port.execute(7); // stays unresolved +} +export function absent(opts: { clock: Clock }) { + const { port } = opts as { clock: Clock; port?: never }; + return port; // no call +} +export function otherShape(opts: { port: { execute(x: number): number } }) { + const { port } = opts; + return port.execute(8); // -> its own literal's execute, NOT Port.execute +} diff --git a/graph/test/typescript/expected/100-destructure-from-type-literal.edges b/graph/test/typescript/expected/100-destructure-from-type-literal.edges new file mode 100644 index 000000000..98740484c --- /dev/null +++ b/graph/test/typescript/expected/100-destructure-from-type-literal.edges @@ -0,0 +1,10 @@ +ambiguous_unknown METHOD_CALL use#untyped(any) @L38 -> - +known_edge FUNCTION_CALL use#shapeMethod({ run(x: number): number }) @L32 -> use#run(number) +known_edge METHOD_CALL use#aliasParam(Opts,Opts,Opts) @L15 -> Clock#now() +known_edge METHOD_CALL use#aliasParam(Opts,Opts,Opts) @L16 -> Port#execute(number) +known_edge METHOD_CALL use#aliasVar(Opts) @L23 -> Clock#now() +known_edge METHOD_CALL use#inlineParam({ port: Port },{ port: Port }) @L19 -> Port#execute(number) +known_edge METHOD_CALL use#inlineVar({ port: Port; n: number }) @L8 -> Port#execute(number) +known_edge METHOD_CALL use#memberAccess({ port: Port }) @L11 -> Port#execute(number) +known_edge METHOD_CALL use#nestedShape({ deps: { port: Port } }) @L28 -> Port#execute(number) +known_edge METHOD_CALL use#otherShape({ port: { execute(x: number): number } }) @L46 -> use#execute(number) diff --git a/graph/test/typescript/expected/100-destructure-from-type-literal.entries b/graph/test/typescript/expected/100-destructure-from-type-literal.entries new file mode 100644 index 000000000..17fbd316e --- /dev/null +++ b/graph/test/typescript/expected/100-destructure-from-type-literal.entries @@ -0,0 +1,13 @@ +── entry_point (12) ── + exported_from_entry_module use#absent use.ts:40 + exported_from_entry_module use#aliasParam use.ts:14 + exported_from_entry_module use#aliasVar use.ts:21 + exported_from_entry_module use#inlineParam use.ts:18 + exported_from_entry_module use#inlineVar use.ts:6 + exported_from_entry_module use#memberAccess use.ts:10 + exported_from_entry_module use#nestedShape use.ts:25 + exported_from_entry_module use#otherShape use.ts:44 + exported_from_entry_module use#shapeMethod use.ts:30 + exported_from_entry_module use#untyped use.ts:36 + unimported_module globals# globals.d.ts:1 + unimported_module use# use.ts:1 diff --git a/graph/test/typescript/expected/100-destructure-from-type-literal.fields b/graph/test/typescript/expected/100-destructure-from-type-literal.fields new file mode 100644 index 000000000..33a761ca6 --- /dev/null +++ b/graph/test/typescript/expected/100-destructure-from-type-literal.fields @@ -0,0 +1 @@ +known_edge read use#memberAccess({ port: Port }) -> { port: Port }#port diff --git a/graph/test/typescript/expected/100-destructure-from-type-literal.fields-oracle b/graph/test/typescript/expected/100-destructure-from-type-literal.fields-oracle new file mode 100644 index 000000000..503afa2c0 --- /dev/null +++ b/graph/test/typescript/expected/100-destructure-from-type-literal.fields-oracle @@ -0,0 +1,9 @@ +100-destructure-from-type-literal [fields] + precision 0.0000 (0 correct, 1 wrong) + recall 0.0000 (0 of 1 the compiler resolved) + sites 1 resolved 1 (100.0%) + tiers known_edge=1 + access read=1 + not scored: 0 rows whose target is not a client declaration + WRONG use#memberAccess({ port: Port }) READ { port: Port }#port + MISSING use#memberAccess({ port: Port }) READ use#port diff --git a/graph/test/typescript/expected/100-destructure-from-type-literal.known-missing b/graph/test/typescript/expected/100-destructure-from-type-literal.known-missing new file mode 100644 index 000000000..28825d635 --- /dev/null +++ b/graph/test/typescript/expected/100-destructure-from-type-literal.known-missing @@ -0,0 +1,6 @@ +# A destructured parameter's caller is labelled with one slot per bound name, `aliasParam(Opts,Opts,Opts)`, where the +# compiler oracle prints the annotation, `aliasParam(Opts)`. The callee is right: each of +# these appears in the engine output under that caller label (see .edges). +use#aliasParam(Opts) -> Clock#now() +use#aliasParam(Opts) -> Port#execute(number) +use#inlineParam({ port: Port }) -> Port#execute(number) diff --git a/graph/test/typescript/expected/100-destructure-from-type-literal.oracle b/graph/test/typescript/expected/100-destructure-from-type-literal.oracle new file mode 100644 index 000000000..a21d560fd --- /dev/null +++ b/graph/test/typescript/expected/100-destructure-from-type-literal.oracle @@ -0,0 +1,7 @@ +oracle=9 engine=9 agree=6 missing=3 (known 3, NEW 0) extra=3 + known use#aliasParam(Opts) -> Clock#now() + known use#aliasParam(Opts) -> Port#execute(number) + known use#inlineParam({ port: Port }) -> Port#execute(number) + extra use#aliasParam(Opts,Opts,Opts) -> Clock#now() + extra use#aliasParam(Opts,Opts,Opts) -> Port#execute(number) + extra use#inlineParam({ port: Port },{ port: Port }) -> Port#execute(number) diff --git a/graph/test/typescript/expected/100-destructure-from-type-literal.type-use b/graph/test/typescript/expected/100-destructure-from-type-literal.type-use new file mode 100644 index 000000000..07d5aaba0 --- /dev/null +++ b/graph/test/typescript/expected/100-destructure-from-type-literal.type-use @@ -0,0 +1,9 @@ +known_edge FIELD_TYPE 1 Opts [TYPE] -> Clock +known_edge FIELD_TYPE 1 Opts [TYPE] -> Port +known_edge FIELD_TYPE 1 use [EXPRESSION] -> Clock +known_edge FIELD_TYPE 1 use [METHOD_PARAM] -> Clock +known_edge FIELD_TYPE 1 use [METHOD_PARAM] -> Port +known_edge FIELD_TYPE 2 use [METHOD_PARAM] -> Port +known_edge METHOD_PARAM 0 use [METHOD_PARAM] -> Opts +known_edge SUPER_TYPE 0 CallableFunction [HERITAGE] -> Function +known_edge SUPER_TYPE 0 NewableFunction [HERITAGE] -> Function diff --git a/graph/test/typescript/expected/100-destructure-from-type-literal.types-oracle b/graph/test/typescript/expected/100-destructure-from-type-literal.types-oracle new file mode 100644 index 000000000..feb77f3a8 --- /dev/null +++ b/graph/test/typescript/expected/100-destructure-from-type-literal.types-oracle @@ -0,0 +1,7 @@ +100-destructure-from-type-literal [types] + precision 1.0000 (7 correct, 0 wrong) + recall 1.0000 (7 of 7 the compiler resolved) + sites 12 resolved 12 (100.0%) + tiers known_edge=12 + contexts FIELD_TYPE=8 METHOD_PARAM=2 SUPER_TYPE=2 + not scored: 0 rows whose target is not a client declaration diff --git a/graph/test/typescript/expected/75-jsx-wrapper-guards.edges b/graph/test/typescript/expected/75-jsx-wrapper-guards.edges index 78ebdfe74..9ccfa6609 100644 --- a/graph/test/typescript/expected/75-jsx-wrapper-guards.edges +++ b/graph/test/typescript/expected/75-jsx-wrapper-guards.edges @@ -7,14 +7,9 @@ ambient_terminal FUNCTION_CALL app#() @L22 -> parts#dynamic(() =Fc ambient_terminal FUNCTION_CALL app#() @L29 -> parts#memo(Fc) ambient_terminal FUNCTION_CALL app#() @L30 -> parts#observer(Fc) ambiguous_unknown FUNCTION_CALL app#MemoInner() @L29 -> - -ambiguous_unknown FUNCTION_CALL app#RenameCall({ run: () =,{ run: () =) @L67 -> - -ambiguous_unknown FUNCTION_CALL app#ShorthandCall({ Button: () =,{ Button: () =) @L64 -> - ambiguous_unknown FUNCTION_CALL heavy#Heavy() @L11 -> - ambiguous_unknown FUNCTION_CALL heavy#NamedHeavy() @L15 -> - -ambiguous_unknown JSX_COMPONENT_CALL app#(?,?) @L57 -> - ambiguous_unknown JSX_COMPONENT_CALL app#Page() @L42 -> - -ambiguous_unknown JSX_COMPONENT_CALL app#RenameTag({ icon: Fc },{ icon: Fc }) @L54 -> - -ambiguous_unknown JSX_COMPONENT_CALL app#ShorthandTag({ Button: Fc },{ Button: Fc }) @L51 -> - ambiguous_unknown METHOD_CALL app#() @L15 -> - ambiguous_unknown METHOD_CALL app#() @L16 -> - ambiguous_unknown METHOD_CALL app#() @L18 -> - @@ -41,10 +36,13 @@ known_edge FUNCTION_CALL app#() @L25 -> app#factoryArgBody() known_edge FUNCTION_CALL app#() @L25 -> parts#makeThing(() =) known_edge FUNCTION_CALL app#() @L26 -> parts#useMemoLike(() =,unknown[]) known_edge FUNCTION_CALL app#MemoInner() @L29 -> app#memoBody() +known_edge FUNCTION_CALL app#RenameCall({ run: () =,{ run: () =) @L67 -> app#() +known_edge FUNCTION_CALL app#ShorthandCall({ Button: () =,{ Button: () =) @L64 -> app#() known_edge FUNCTION_CALL heavy#Heavy() @L11 -> heavy#heavyWork() known_edge FUNCTION_CALL heavy#NamedHeavy() @L15 -> heavy#namedWork() known_edge FUNCTION_CALL parts#makeThing(() =) @L23 -> parts#() known_edge FUNCTION_CALL parts#useMemoLike(() =,unknown[]) @L27 -> parts#() +known_edge JSX_COMPONENT_CALL app#(?,?) @L57 -> parts#(T) known_edge JSX_COMPONENT_CALL app#Page() @L35 -> heavy#NamedHeavy() known_edge JSX_COMPONENT_CALL app#Page() @L36 -> heavy#NamedHeavy() known_edge JSX_COMPONENT_CALL app#Page() @L37 -> heavy#NamedHeavy() @@ -55,4 +53,6 @@ known_edge JSX_COMPONENT_CALL app#Page() @L41 -> parts#(T) known_edge JSX_COMPONENT_CALL app#Page() @L43 -> app#MemoInner() known_edge JSX_COMPONENT_CALL app#Page() @L44 -> app#ObservedImpl() known_edge JSX_COMPONENT_CALL app#PlainParamTag(Fc) @L60 -> parts#(T) +known_edge JSX_COMPONENT_CALL app#RenameTag({ icon: Fc },{ icon: Fc }) @L54 -> parts#(T) +known_edge JSX_COMPONENT_CALL app#ShorthandTag({ Button: Fc },{ Button: Fc }) @L51 -> parts#(T) known_edge JSX_COMPONENT_CALL app#UsesButton() @L72 -> ui/Button#Button({ label: string }) diff --git a/graph/test/typescript/expected/75-jsx-wrapper-guards.oracle b/graph/test/typescript/expected/75-jsx-wrapper-guards.oracle index ff8858f30..93823a803 100644 --- a/graph/test/typescript/expected/75-jsx-wrapper-guards.oracle +++ b/graph/test/typescript/expected/75-jsx-wrapper-guards.oracle @@ -1,11 +1,16 @@ -oracle=21 engine=19 agree=15 missing=6 (known 6, NEW 0) extra=4 +oracle=21 engine=24 agree=15 missing=6 (known 6, NEW 0) extra=9 known app#(?) -> parts#(T) known app#Page() -> app#MadeInner() known app#RenameCall({ run: () =) -> app#() known app#RenameTag({ icon: Fc }) -> parts#(T) known app#ShorthandCall({ Button: () =) -> app#() known app#ShorthandTag({ Button: Fc }) -> parts#(T) + extra app#(?,?) -> parts#(T) extra app#Page() -> app#MemoInner() extra app#Page() -> app#ObservedImpl() extra app#Page() -> heavy#Heavy() extra app#Page() -> heavy#NamedHeavy() + extra app#RenameCall({ run: () =,{ run: () =) -> app#() + extra app#RenameTag({ icon: Fc },{ icon: Fc }) -> parts#(T) + extra app#ShorthandCall({ Button: () =,{ Button: () =) -> app#() + extra app#ShorthandTag({ Button: Fc },{ Button: Fc }) -> parts#(T) diff --git a/graph/typescript/engine/resolution/reference-types.dl b/graph/typescript/engine/resolution/reference-types.dl index 04b5345bb..831f1f27a 100644 --- a/graph/typescript/engine/resolution/reference-types.dl +++ b/graph/typescript/engine/resolution/reference-types.dl @@ -494,6 +494,28 @@ param_binding_method(bh, m2) :- param_binding(_, _, _, "PROPERTY", src, _, bh), scope_sibling(t, sib), declared_method(sib, src, _, m2). +// (c) the pattern is typed by an OBJECT TYPE LITERAL — `({ port }: { port: Port })`, or +// `({ port }: Opts)` with `type Opts = { port: Port }`. A type literal has no declaration +// to give param_type; it is a SHAPE (param_shape_target), and a shape is its own scope +// (scope_sibling(s, s)), so the field and method lookups above read off it unchanged. +binding_pattern_shape(bh, sh) :- param_binding(_, _, pos, "PROPERTY", _, m, bh), + param_decl(_, _, pos, _, m, ph), + param_shape_target(ph, sh). +param_type(bh, prov, ft) :- param_binding(_, _, _, "PROPERTY", src, _, bh), + binding_pattern_shape(bh, sh), + scope_sibling(sh, sib), + declared_field(sib, src, _, f), + field_type(f, prov, ft). +param_shape_target(bh, fs) :- param_binding(_, _, _, "PROPERTY", src, _, bh), + binding_pattern_shape(bh, sh), + scope_sibling(sh, sib), + declared_field(sib, src, _, f), + field_shape(f, fs). +param_binding_method(bh, m2) :- param_binding(_, _, _, "PROPERTY", src, _, bh), + binding_pattern_shape(bh, sh), + scope_sibling(sh, sib), + declared_method(sib, src, _, m2). + // ── a DESTRUCTURED VARIABLE takes the type of the member it names ─────────── // `const { helper, nested } = ctx` — the mirror of the parameter case, and the more @@ -527,6 +549,27 @@ var_binding_method(v, m) :- var_binding("client", _, "PROPERTY", src, _, v), scope_sibling(t, sib), declared_method(sib, src, _, m). +// (c) the initializer has a SHAPE, not a type — `const { port } = opts` with +// `opts: { port: Port }`. `opts.port.execute()` already resolved through the shape +// (expr_shape off a shape qualifier); the destructured name beside it did not. +var_binding_shape(v, sh) :- var_binding("client", _, "PROPERTY", _, e, v), + e != "", + expr_shape(e, sh). +var_type(v, prov, ft) :- var_binding("client", _, "PROPERTY", src, _, v), + var_binding_shape(v, sh), + scope_sibling(sh, sib), + declared_field(sib, src, _, f), + field_type(f, prov, ft). +var_shape(v, fs) :- var_binding("client", _, "PROPERTY", src, _, v), + var_binding_shape(v, sh), + scope_sibling(sh, sib), + declared_field(sib, src, _, f), + field_shape(f, fs). +var_binding_method(v, m) :- var_binding("client", _, "PROPERTY", src, _, v), + var_binding_shape(v, sh), + scope_sibling(sh, sib), + declared_method(sib, src, _, m). + // ── an INDEX-DESTRUCTURED VARIABLE takes the ELEMENT type of its source ───── // `const [first] = ops` and `const [a, b] = makePair()`. The parser has emitted these diff --git a/graph/typescript/souffle/decls_all.dl b/graph/typescript/souffle/decls_all.dl index a703e6015..ae3f25e50 100644 --- a/graph/typescript/souffle/decls_all.dl +++ b/graph/typescript/souffle/decls_all.dl @@ -464,6 +464,7 @@ .decl owner_kind_is_module_level(c0:symbol) .decl owner_name_has_overload_signature(c0:symbol,c1:symbol) .decl param_call_signature(c0:symbol,c1:symbol) +.decl binding_pattern_shape(c0:symbol,c1:symbol) .decl binding_pattern_type(c0:symbol,c1:symbol,c2:symbol) .decl param_binding_method(c0:symbol,c1:symbol) .decl param_binding(c0:symbol,c1:symbol,c2:symbol,c3:symbol,c4:symbol,c5:symbol,c6:symbol) @@ -641,6 +642,7 @@ .decl library_receiver(c0:symbol,c1:symbol) .decl unresolved_receiver(c0:symbol,c1:symbol,c2:symbol,c3:symbol) .decl var_binding_method(c0:symbol,c1:symbol) +.decl var_binding_shape(c0:symbol,c1:symbol) .decl var_binding_target(c0:symbol,c1:symbol,c2:symbol) .decl var_binding(c0:symbol,c1:symbol,c2:symbol,c3:symbol,c4:symbol,c5:symbol) .decl var_is_destructured(c0:symbol) From 861ead47440d91ec54b68f78792a96cb3698cd74 Mon Sep 17 00:00:00 2001 From: swapnil <78632212+swapnilpaliwal-sd@users.noreply.github.com> Date: Tue, 3 Nov 2026 09:42:54 -0800 Subject: [PATCH 2/2] ts suite: label a destructured parameter once, as the compiler does The parser emits a row for a destructuring pattern and one per name it binds, all at the pattern's position. normalize_edges.py turned each into a parameter, so `aliasParam({ port, clock }: Opts)` was labelled `aliasParam(Opts,Opts,Opts)` while the compiler oracle prints `aliasParam(Opts)`: the same edge showed as one missing and one extra. One row per position, the pattern's when present. Case 100's three known-missing edges and five of case 75's now agree with the oracle; both debt lists are trimmed. Suite 110/110. Co-authored-by: axiomcode-bot[bot] <334110751+axiomcode-bot[bot]@users.noreply.github.com> --- .../expected/100-destructure-from-type-literal.edges | 6 +++--- .../100-destructure-from-type-literal.known-missing | 6 ------ .../100-destructure-from-type-literal.oracle | 8 +------- .../typescript/expected/75-jsx-wrapper-guards.edges | 12 ++++++------ .../expected/75-jsx-wrapper-guards.known-missing | 9 --------- .../typescript/expected/75-jsx-wrapper-guards.oracle | 12 +----------- graph/test/typescript/tools/normalize_edges.py | 10 +++++++++- 7 files changed, 20 insertions(+), 43 deletions(-) delete mode 100644 graph/test/typescript/expected/100-destructure-from-type-literal.known-missing diff --git a/graph/test/typescript/expected/100-destructure-from-type-literal.edges b/graph/test/typescript/expected/100-destructure-from-type-literal.edges index 98740484c..6b4bede19 100644 --- a/graph/test/typescript/expected/100-destructure-from-type-literal.edges +++ b/graph/test/typescript/expected/100-destructure-from-type-literal.edges @@ -1,9 +1,9 @@ ambiguous_unknown METHOD_CALL use#untyped(any) @L38 -> - known_edge FUNCTION_CALL use#shapeMethod({ run(x: number): number }) @L32 -> use#run(number) -known_edge METHOD_CALL use#aliasParam(Opts,Opts,Opts) @L15 -> Clock#now() -known_edge METHOD_CALL use#aliasParam(Opts,Opts,Opts) @L16 -> Port#execute(number) +known_edge METHOD_CALL use#aliasParam(Opts) @L15 -> Clock#now() +known_edge METHOD_CALL use#aliasParam(Opts) @L16 -> Port#execute(number) known_edge METHOD_CALL use#aliasVar(Opts) @L23 -> Clock#now() -known_edge METHOD_CALL use#inlineParam({ port: Port },{ port: Port }) @L19 -> Port#execute(number) +known_edge METHOD_CALL use#inlineParam({ port: Port }) @L19 -> Port#execute(number) known_edge METHOD_CALL use#inlineVar({ port: Port; n: number }) @L8 -> Port#execute(number) known_edge METHOD_CALL use#memberAccess({ port: Port }) @L11 -> Port#execute(number) known_edge METHOD_CALL use#nestedShape({ deps: { port: Port } }) @L28 -> Port#execute(number) diff --git a/graph/test/typescript/expected/100-destructure-from-type-literal.known-missing b/graph/test/typescript/expected/100-destructure-from-type-literal.known-missing deleted file mode 100644 index 28825d635..000000000 --- a/graph/test/typescript/expected/100-destructure-from-type-literal.known-missing +++ /dev/null @@ -1,6 +0,0 @@ -# A destructured parameter's caller is labelled with one slot per bound name, `aliasParam(Opts,Opts,Opts)`, where the -# compiler oracle prints the annotation, `aliasParam(Opts)`. The callee is right: each of -# these appears in the engine output under that caller label (see .edges). -use#aliasParam(Opts) -> Clock#now() -use#aliasParam(Opts) -> Port#execute(number) -use#inlineParam({ port: Port }) -> Port#execute(number) diff --git a/graph/test/typescript/expected/100-destructure-from-type-literal.oracle b/graph/test/typescript/expected/100-destructure-from-type-literal.oracle index a21d560fd..dbaf45eaa 100644 --- a/graph/test/typescript/expected/100-destructure-from-type-literal.oracle +++ b/graph/test/typescript/expected/100-destructure-from-type-literal.oracle @@ -1,7 +1 @@ -oracle=9 engine=9 agree=6 missing=3 (known 3, NEW 0) extra=3 - known use#aliasParam(Opts) -> Clock#now() - known use#aliasParam(Opts) -> Port#execute(number) - known use#inlineParam({ port: Port }) -> Port#execute(number) - extra use#aliasParam(Opts,Opts,Opts) -> Clock#now() - extra use#aliasParam(Opts,Opts,Opts) -> Port#execute(number) - extra use#inlineParam({ port: Port },{ port: Port }) -> Port#execute(number) +oracle=9 engine=9 agree=9 missing=0 (known 0, NEW 0) extra=0 diff --git a/graph/test/typescript/expected/75-jsx-wrapper-guards.edges b/graph/test/typescript/expected/75-jsx-wrapper-guards.edges index 9ccfa6609..562520af9 100644 --- a/graph/test/typescript/expected/75-jsx-wrapper-guards.edges +++ b/graph/test/typescript/expected/75-jsx-wrapper-guards.edges @@ -25,7 +25,7 @@ callback_registered FUNCTION_CALL app#() @L30 -> app#ObservedImpl() callback_registered METHOD_CALL app#() @L15 -> app#(?) callback_registered METHOD_CALL app#() @L16 -> app#(?) callback_registered METHOD_CALL app#() @L18 -> app#(?) -callback_registered METHOD_CALL app#ArrowRenameTag({ icon: Fc }[]) @L57 -> app#(?,?) +callback_registered METHOD_CALL app#ArrowRenameTag({ icon: Fc }[]) @L57 -> app#(?) intrinsic_terminal DYNAMIC_IMPORT_CALL app#() @L15 -> - intrinsic_terminal DYNAMIC_IMPORT_CALL app#() @L16 -> - intrinsic_terminal DYNAMIC_IMPORT_CALL app#() @L18 -> - @@ -36,13 +36,13 @@ known_edge FUNCTION_CALL app#() @L25 -> app#factoryArgBody() known_edge FUNCTION_CALL app#() @L25 -> parts#makeThing(() =) known_edge FUNCTION_CALL app#() @L26 -> parts#useMemoLike(() =,unknown[]) known_edge FUNCTION_CALL app#MemoInner() @L29 -> app#memoBody() -known_edge FUNCTION_CALL app#RenameCall({ run: () =,{ run: () =) @L67 -> app#() -known_edge FUNCTION_CALL app#ShorthandCall({ Button: () =,{ Button: () =) @L64 -> app#() +known_edge FUNCTION_CALL app#RenameCall({ run: () =) @L67 -> app#() +known_edge FUNCTION_CALL app#ShorthandCall({ Button: () =) @L64 -> app#() known_edge FUNCTION_CALL heavy#Heavy() @L11 -> heavy#heavyWork() known_edge FUNCTION_CALL heavy#NamedHeavy() @L15 -> heavy#namedWork() known_edge FUNCTION_CALL parts#makeThing(() =) @L23 -> parts#() known_edge FUNCTION_CALL parts#useMemoLike(() =,unknown[]) @L27 -> parts#() -known_edge JSX_COMPONENT_CALL app#(?,?) @L57 -> parts#(T) +known_edge JSX_COMPONENT_CALL app#(?) @L57 -> parts#(T) known_edge JSX_COMPONENT_CALL app#Page() @L35 -> heavy#NamedHeavy() known_edge JSX_COMPONENT_CALL app#Page() @L36 -> heavy#NamedHeavy() known_edge JSX_COMPONENT_CALL app#Page() @L37 -> heavy#NamedHeavy() @@ -53,6 +53,6 @@ known_edge JSX_COMPONENT_CALL app#Page() @L41 -> parts#(T) known_edge JSX_COMPONENT_CALL app#Page() @L43 -> app#MemoInner() known_edge JSX_COMPONENT_CALL app#Page() @L44 -> app#ObservedImpl() known_edge JSX_COMPONENT_CALL app#PlainParamTag(Fc) @L60 -> parts#(T) -known_edge JSX_COMPONENT_CALL app#RenameTag({ icon: Fc },{ icon: Fc }) @L54 -> parts#(T) -known_edge JSX_COMPONENT_CALL app#ShorthandTag({ Button: Fc },{ Button: Fc }) @L51 -> parts#(T) +known_edge JSX_COMPONENT_CALL app#RenameTag({ icon: Fc }) @L54 -> parts#(T) +known_edge JSX_COMPONENT_CALL app#ShorthandTag({ Button: Fc }) @L51 -> parts#(T) known_edge JSX_COMPONENT_CALL app#UsesButton() @L72 -> ui/Button#Button({ label: string }) diff --git a/graph/test/typescript/expected/75-jsx-wrapper-guards.known-missing b/graph/test/typescript/expected/75-jsx-wrapper-guards.known-missing index 4b3ae9b66..cb34e31cb 100644 --- a/graph/test/typescript/expected/75-jsx-wrapper-guards.known-missing +++ b/graph/test/typescript/expected/75-jsx-wrapper-guards.known-missing @@ -2,15 +2,6 @@ # resolves and this engine does not. A NEW missing edge fails the suite; # a line here that STARTS working also fails, so the debt cannot rot. # -# A name bound by a DESTRUCTURED parameter carries no declared type the engine reads, so -# the call through it stays unresolved. The compiler names the prop's type signature. -# Before this case these sites resolved to the same-named IMPORT (a wrong edge); unresolved -# is the honest answer until destructured parameters are typed. -app#(?) -> parts#(T) -app#RenameTag({ icon: Fc }) -> parts#(T) -app#ShorthandTag({ Button: Fc }) -> parts#(T) -app#RenameCall({ run: () =) -> app#() -app#ShorthandCall({ Button: () =) -> app#() # `useMemoLike(() => function MadeInner…)` returns what its argument RETURNS, through a # generic T. Argument 0 is not a component wrapper, so the tag no longer lands on the # argument arrow; following the generic return to MadeInner is value flow the engine diff --git a/graph/test/typescript/expected/75-jsx-wrapper-guards.oracle b/graph/test/typescript/expected/75-jsx-wrapper-guards.oracle index 93823a803..94c50e44c 100644 --- a/graph/test/typescript/expected/75-jsx-wrapper-guards.oracle +++ b/graph/test/typescript/expected/75-jsx-wrapper-guards.oracle @@ -1,16 +1,6 @@ -oracle=21 engine=24 agree=15 missing=6 (known 6, NEW 0) extra=9 - known app#(?) -> parts#(T) +oracle=21 engine=24 agree=20 missing=1 (known 1, NEW 0) extra=4 known app#Page() -> app#MadeInner() - known app#RenameCall({ run: () =) -> app#() - known app#RenameTag({ icon: Fc }) -> parts#(T) - known app#ShorthandCall({ Button: () =) -> app#() - known app#ShorthandTag({ Button: Fc }) -> parts#(T) - extra app#(?,?) -> parts#(T) extra app#Page() -> app#MemoInner() extra app#Page() -> app#ObservedImpl() extra app#Page() -> heavy#Heavy() extra app#Page() -> heavy#NamedHeavy() - extra app#RenameCall({ run: () =,{ run: () =) -> app#() - extra app#RenameTag({ icon: Fc },{ icon: Fc }) -> parts#(T) - extra app#ShorthandCall({ Button: () =,{ Button: () =) -> app#() - extra app#ShorthandTag({ Button: Fc },{ Button: Fc }) -> parts#(T) diff --git a/graph/test/typescript/tools/normalize_edges.py b/graph/test/typescript/tools/normalize_edges.py index 9807d7574..ad8513cbd 100644 --- a/graph/test/typescript/tools/normalize_edges.py +++ b/graph/test/typescript/tools/normalize_edges.py @@ -198,7 +198,15 @@ def enclosing_owner(type_hash): for r in rows(f'{ir}/all-typescript-methods.csv'): h = r['tsMethodUniqueHash'] ps = [] - for p in sorted(params.get(h, []), key=lambda x: int(x.get('position') or 0)): + # A destructured parameter has a row for the pattern and one per name it binds, all at the pattern's + # position (bindingSourceKind PROPERTY/INDEX): one parameter, labelled once, as the compiler prints it. + by_pos = {} + for p in params.get(h, []): + pos = int(p.get('position') or 0) + bound = (p.get('bindingSourceKind') or 'NONE') != 'NONE' + if pos not in by_pos or (by_pos[pos][0] and not bound): + by_pos[pos] = (bound, p) + for _, p in sorted((pos, bp[1]) for pos, bp in by_pos.items()): if p.get('isReceiverParameter') == 'true': continue t = simple(p.get('parameterTypeName') or p.get('parameterBaseType'))