From c2ab50ce98efb2e0ce37939599ccee196bad382f Mon Sep 17 00:00:00 2001 From: Swapnil <78632212+swapnilpaliwal-sd@users.noreply.github.com> Date: Thu, 29 Oct 2026 01:54:50 -0700 Subject: [PATCH 1/2] typescript: construct, namespace, inline-chain, ThisType and bound-method callees resolve (#1997, #1998, #1999, #2000, #2001) Five callee shapes resolved to nothing though the compiler names their target: - new C() where C is typed by a construct-signature literal or an alias of one: construction read expr_type only, the plain-call side already read expr_shape (#1997) - ns.f() where f is an exported const of a function type whose value is not a function literal: the namespace read gave var_type, never var_shape (#1998) - a.b.c.m() through nested inline object types: a shape-typed member read off a shape qualifier had no field_shape clause (#1999) - this.m() in an object literal typed X & ThisType: the marker is read through intersections and aliases, with the alias parameter and the call's explicit type argument substituted (#2000) - this.m.bind/call/apply and the bound result's call: the Function members attach to a method read as a value (#2001) Two guards keep sites that were right from hedging: the namespace shape is not added where the const holds a function literal, and the construct shape is not added where the callee variable holds a class. Cases 91 to 95 pin each shape with its controls. Goldens moved in 08, 80 and 83 (see the pull request); 83's known-missing edge now resolves. --- .../cases/91-construct-through-shape/src/x.ts | 16 ++++ .../92-namespace-typed-const/src/scorers.ts | 9 +++ .../cases/92-namespace-typed-const/src/x.ts | 9 +++ .../cases/93-literal-member-chain/src/x.ts | 17 ++++ .../cases/94-thistype-object-literal/src/x.ts | 31 ++++++++ .../95-bind-on-method-value/src/globals.d.ts | 66 ++++++++++++++++ .../95-bind-on-method-value/src/tsconfig.json | 6 ++ .../cases/95-bind-on-method-value/src/x.ts | 21 +++++ .../08-function-values-and-callbacks.edges | 3 +- .../08-function-values-and-callbacks.envelope | 1 - ...08-function-values-and-callbacks.lib.edges | 3 +- ...function-values-and-callbacks.lib.envelope | 1 - ...8-function-values-and-callbacks.lib.oracle | 3 +- .../08-function-values-and-callbacks.oracle | 3 +- .../80-method-passed-by-reference.edges | 6 +- .../80-method-passed-by-reference.oracle | 5 +- .../expected/83-closed-world-dispatch.edges | 2 +- .../83-closed-world-dispatch.known-missing | 7 -- .../expected/83-closed-world-dispatch.oracle | 3 +- .../expected/91-construct-through-shape.edges | 5 ++ .../91-construct-through-shape.entries | 6 ++ .../91-construct-through-shape.fields | 0 .../91-construct-through-shape.fields-oracle | 7 ++ .../91-construct-through-shape.oracle | 1 + .../91-construct-through-shape.type-use | 10 +++ .../91-construct-through-shape.types-oracle | 8 ++ .../expected/92-namespace-typed-const.edges | 5 ++ .../expected/92-namespace-typed-const.entries | 6 ++ .../92-namespace-typed-const.envelope | 4 + .../expected/92-namespace-typed-const.fields | 4 + .../92-namespace-typed-const.fields-oracle | 7 ++ .../92-namespace-typed-const.known-missing | 7 ++ .../expected/92-namespace-typed-const.oracle | 3 + .../92-namespace-typed-const.type-use | 2 + .../92-namespace-typed-const.types-oracle | 7 ++ .../expected/93-literal-member-chain.edges | 4 + .../expected/93-literal-member-chain.entries | 5 ++ .../expected/93-literal-member-chain.fields | 10 +++ .../93-literal-member-chain.fields-oracle | 18 +++++ .../expected/93-literal-member-chain.oracle | 1 + .../expected/93-literal-member-chain.type-use | 8 ++ .../93-literal-member-chain.types-oracle | 7 ++ .../expected/94-thistype-object-literal.edges | 8 ++ .../94-thistype-object-literal.entries | 2 + .../94-thistype-object-literal.envelope | 2 + .../94-thistype-object-literal.fields | 0 .../94-thistype-object-literal.fields-oracle | 7 ++ .../94-thistype-object-literal.known-missing | 6 ++ .../94-thistype-object-literal.oracle | 2 + .../94-thistype-object-literal.type-use | 10 +++ .../94-thistype-object-literal.types-oracle | 7 ++ .../expected/95-bind-on-method-value.edges | 9 +++ .../expected/95-bind-on-method-value.entries | 4 + .../expected/95-bind-on-method-value.envelope | 5 ++ .../expected/95-bind-on-method-value.fields | 1 + .../95-bind-on-method-value.fields-oracle | 7 ++ .../expected/95-bind-on-method-value.oracle | 2 + .../expected/95-bind-on-method-value.type-use | 3 + .../95-bind-on-method-value.types-oracle | 7 ++ .../callee-resolution.dl | 54 +++++++++++++ .../engine/expression-resolution/expr-type.dl | 78 +++++++++++++++++++ graph/typescript/souffle/decls_all.dl | 4 + 62 files changed, 545 insertions(+), 20 deletions(-) create mode 100644 graph/test/typescript/cases/91-construct-through-shape/src/x.ts create mode 100644 graph/test/typescript/cases/92-namespace-typed-const/src/scorers.ts create mode 100644 graph/test/typescript/cases/92-namespace-typed-const/src/x.ts create mode 100644 graph/test/typescript/cases/93-literal-member-chain/src/x.ts create mode 100644 graph/test/typescript/cases/94-thistype-object-literal/src/x.ts create mode 100644 graph/test/typescript/cases/95-bind-on-method-value/src/globals.d.ts create mode 100644 graph/test/typescript/cases/95-bind-on-method-value/src/tsconfig.json create mode 100644 graph/test/typescript/cases/95-bind-on-method-value/src/x.ts delete mode 100644 graph/test/typescript/expected/83-closed-world-dispatch.known-missing create mode 100644 graph/test/typescript/expected/91-construct-through-shape.edges create mode 100644 graph/test/typescript/expected/91-construct-through-shape.entries create mode 100644 graph/test/typescript/expected/91-construct-through-shape.fields create mode 100644 graph/test/typescript/expected/91-construct-through-shape.fields-oracle create mode 100644 graph/test/typescript/expected/91-construct-through-shape.oracle create mode 100644 graph/test/typescript/expected/91-construct-through-shape.type-use create mode 100644 graph/test/typescript/expected/91-construct-through-shape.types-oracle create mode 100644 graph/test/typescript/expected/92-namespace-typed-const.edges create mode 100644 graph/test/typescript/expected/92-namespace-typed-const.entries create mode 100644 graph/test/typescript/expected/92-namespace-typed-const.envelope create mode 100644 graph/test/typescript/expected/92-namespace-typed-const.fields create mode 100644 graph/test/typescript/expected/92-namespace-typed-const.fields-oracle create mode 100644 graph/test/typescript/expected/92-namespace-typed-const.known-missing create mode 100644 graph/test/typescript/expected/92-namespace-typed-const.oracle create mode 100644 graph/test/typescript/expected/92-namespace-typed-const.type-use create mode 100644 graph/test/typescript/expected/92-namespace-typed-const.types-oracle create mode 100644 graph/test/typescript/expected/93-literal-member-chain.edges create mode 100644 graph/test/typescript/expected/93-literal-member-chain.entries create mode 100644 graph/test/typescript/expected/93-literal-member-chain.fields create mode 100644 graph/test/typescript/expected/93-literal-member-chain.fields-oracle create mode 100644 graph/test/typescript/expected/93-literal-member-chain.oracle create mode 100644 graph/test/typescript/expected/93-literal-member-chain.type-use create mode 100644 graph/test/typescript/expected/93-literal-member-chain.types-oracle create mode 100644 graph/test/typescript/expected/94-thistype-object-literal.edges create mode 100644 graph/test/typescript/expected/94-thistype-object-literal.entries create mode 100644 graph/test/typescript/expected/94-thistype-object-literal.envelope create mode 100644 graph/test/typescript/expected/94-thistype-object-literal.fields create mode 100644 graph/test/typescript/expected/94-thistype-object-literal.fields-oracle create mode 100644 graph/test/typescript/expected/94-thistype-object-literal.known-missing create mode 100644 graph/test/typescript/expected/94-thistype-object-literal.oracle create mode 100644 graph/test/typescript/expected/94-thistype-object-literal.type-use create mode 100644 graph/test/typescript/expected/94-thistype-object-literal.types-oracle create mode 100644 graph/test/typescript/expected/95-bind-on-method-value.edges create mode 100644 graph/test/typescript/expected/95-bind-on-method-value.entries create mode 100644 graph/test/typescript/expected/95-bind-on-method-value.envelope create mode 100644 graph/test/typescript/expected/95-bind-on-method-value.fields create mode 100644 graph/test/typescript/expected/95-bind-on-method-value.fields-oracle create mode 100644 graph/test/typescript/expected/95-bind-on-method-value.oracle create mode 100644 graph/test/typescript/expected/95-bind-on-method-value.type-use create mode 100644 graph/test/typescript/expected/95-bind-on-method-value.types-oracle diff --git a/graph/test/typescript/cases/91-construct-through-shape/src/x.ts b/graph/test/typescript/cases/91-construct-through-shape/src/x.ts new file mode 100644 index 000000000..5eeddcc76 --- /dev/null +++ b/graph/test/typescript/cases/91-construct-through-shape/src/x.ts @@ -0,0 +1,16 @@ +// `new C(spec)` where C's type is a construct signature written as a type literal, an alias +// of one, a constructor type, or an interface. A factory that takes "the class to build" +// is written this way, and only the interface form resolved before. +interface WidgetDef { kind: string } +class Button { constructor(public spec: WidgetDef) {} } + +type WidgetClass = { new (spec: WidgetDef): T }; +function viaAlias(C: WidgetClass): T { return new C({ kind: "a" }); } +function viaLiteral(C: { new (spec: WidgetDef): Button }): Button { return new C({ kind: "b" }); } + +// controls: a constructor type and an interface with a construct signature +function viaCtorType(C: new (spec: WidgetDef) => Button): Button { return new C({ kind: "c" }); } +interface ButtonCtor { new (spec: WidgetDef): Button } +function viaInterface(C: ButtonCtor): Button { return new C({ kind: "d" }); } +const made = viaAlias(Button); +export { viaAlias, viaLiteral, viaCtorType, viaInterface, made }; diff --git a/graph/test/typescript/cases/92-namespace-typed-const/src/scorers.ts b/graph/test/typescript/cases/92-namespace-typed-const/src/scorers.ts new file mode 100644 index 000000000..75a9137c0 --- /dev/null +++ b/graph/test/typescript/cases/92-namespace-typed-const/src/scorers.ts @@ -0,0 +1,9 @@ +export type Scorer = (input: string) => number; +export function makeScorer(base: number): Scorer { + const fn: Scorer = (input) => input.length + base; + return fn; +} +// a function-typed const whose value is a factory's result: no literal to read +export const score: Scorer = makeScorer(1); +export const scoreArrow: Scorer = (input) => input.length; +export function plain(input: string): number { return input.length; } diff --git a/graph/test/typescript/cases/92-namespace-typed-const/src/x.ts b/graph/test/typescript/cases/92-namespace-typed-const/src/x.ts new file mode 100644 index 000000000..0517c80a5 --- /dev/null +++ b/graph/test/typescript/cases/92-namespace-typed-const/src/x.ts @@ -0,0 +1,9 @@ +// `ns.score(x)` reads the const off a namespace import. The named import of the same const +// resolved and the namespace read did not. +import * as scorers from "./scorers"; +import { score } from "./scorers"; +export function viaNamespace(s: string): number { return scorers.score(s); } +// controls: the named import, a const holding an arrow, and an exported function +export function viaNamed(s: string): number { return score(s); } +export function viaArrow(s: string): number { return scorers.scoreArrow(s); } +export function viaPlain(s: string): number { return scorers.plain(s); } diff --git a/graph/test/typescript/cases/93-literal-member-chain/src/x.ts b/graph/test/typescript/cases/93-literal-member-chain/src/x.ts new file mode 100644 index 000000000..ef3d4fa5e --- /dev/null +++ b/graph/test/typescript/cases/93-literal-member-chain/src/x.ts @@ -0,0 +1,17 @@ +// A member chain through nested inline object types. Each level reads a shape-typed +// property off a shape, and the chain stopped after the first. +class Logger { write(msg: string): void {} } +interface Order { + meta: { audit: { log: Logger; nested: { deep: { log: Logger } } } }; +} +function record(o: Order): void { + o.meta.audit.log.write("a"); + o.meta.audit.nested.deep.log.write("b"); +} +// controls: one level of literal, and a named type at every level +interface Flat { meta: { log: Logger } } +interface Named { meta: Meta } +interface Meta { log: Logger } +function flat(f: Flat): void { f.meta.log.write("c"); } +function named(n: Named): void { n.meta.log.write("d"); } +export { record, flat, named }; diff --git a/graph/test/typescript/cases/94-thistype-object-literal/src/x.ts b/graph/test/typescript/cases/94-thistype-object-literal/src/x.ts new file mode 100644 index 000000000..a6c0a6042 --- /dev/null +++ b/graph/test/typescript/cases/94-thistype-object-literal/src/x.ts @@ -0,0 +1,31 @@ +// `this` in the methods of an object literal written as `X & ThisType`: the compiler +// types it as T. A prototype table or a mixin is written this way, and every `this.m()` in +// one resolved to nothing. Covered: the marker through a generic alias bound by an explicit +// type argument, through an annotation, and written inline. +interface Greeter { name: string; greet(): string; shout(): string; hail(): string; wave(): string } +type Members = { [K in keyof T]?: T[K] } & ThisType; +interface Mixin { make(): T } +declare function mixin(name: string, members?: Members): Mixin; + +const viaTypeArgument = mixin("Greeter", { + greet() { return this.shout(); }, + shout: function shout() { return this.greet(); }, +}); +const viaAnnotation: Members = { + hail() { return this.greet(); }, +}; +interface Waver { wave?(): string } +const inline: Waver & ThisType = { + wave() { return this.shout(); }, +}; + +// controls: without the marker `this` is the literal; an arrow property keeps the class's +const plainLiteral = { + a() { return 1; }, + b() { return this.a(); }, +}; +class Host { + run(): string { return "r"; } + table = mixin("Host", { greet: () => this.run() }); +} +export { viaTypeArgument, viaAnnotation, inline, plainLiteral, Host }; diff --git a/graph/test/typescript/cases/95-bind-on-method-value/src/globals.d.ts b/graph/test/typescript/cases/95-bind-on-method-value/src/globals.d.ts new file mode 100644 index 000000000..befc3b74c --- /dev/null +++ b/graph/test/typescript/cases/95-bind-on-method-value/src/globals.d.ts @@ -0,0 +1,66 @@ +// ============================================================================ +// The case's OWN standard library: the minimum the checker needs under `noLib`. +// ============================================================================ +// WHY noLib. This mechanism's targets are declared in the REAL `lib.es5.d.ts`, and a +// case never has it: the case oracle only records a target inside the case's own files, +// and the engine is handed only `--library `. So with the real library +// neither side can name the target and the case measures nothing. Declaring `Function` +// and `CallableFunction` HERE puts them on both sides of the comparison. +// +// This is not a mock of the behaviour. The checker resolves `CallableFunction` by NAME +// from whatever is in scope and then applies `strictBindCallApply` itself, so these +// declarations exercise the real decision. Verified: the same two declarations answer +// CallableFunction under `strict: true` and Function under +// `strict: false, strictBindCallApply: false`. +// +// Everything other than Function/CallableFunction is here only because `noLib` removes +// it and the checker requires it. Keep this file minimal: an interface added here is a +// declaration the engine can resolve to, and an accidental one is an accidental answer. + +interface Object { toString(): string; } +interface Boolean {} +interface Number {} +interface String { padStart(width: number): string; padEnd(width: number): string; trim(): string; } +interface Array { length: number; } +interface ReadonlyArray { length: number; } +interface IArguments {} +interface RegExp {} + +// ── the two declarations the flag chooses between ─────────────────────────── +// Mirrors lib.es5.d.ts: `Function` declares all three, and `CallableFunction extends +// Function` REDECLARES the same three with precise generic signatures. Both are in +// scope at every site; only the flag decides. +// NO explicit `this` parameter on these. lib.es5.d.ts declares them with one, but the +// case oracle's edge label includes a `this` parameter in the signature while the engine +// (correctly) does not treat it as a parameter. So `this: Function` renders as +// `Function#apply(Function,any,any)` on one side and `Function#apply(any,any)` on the +// other, and every site scores as a disagreement the engine did not actually make. +interface Function { + apply(thisArg: any, argArray?: any): any; + call(thisArg: any, ...argArray: any[]): any; + bind(thisArg: any, ...argArray: any[]): any; + readonly name: string; + toString(): string; +} + +interface CallableFunction extends Function { + // Simply typed on purpose. lib.es5.d.ts gives these precise generic signatures + // (`this: (this: T, ...args: A) => R`), and this fixture does not need them: what it + // tests is that TWO declarations of the same member names exist and that + // `strictBindCallApply` picks between them, which the checker decides from the flag + // and not from how well a signature fits. A function-typed parameter also renders + // badly in the case oracle's edge labels: the label splits on the commas inside + // `(this: T, ...args: A) => R` and emits `apply(args: A) =,T,T)`, which can never + // match the engine's label, so every site scored as a disagreement. + // + // Arguments are optional and returns are `any` so every call form in the case source + // typechecks. The two declarations stay distinguishable without help: an edge label + // carries the OWNER, so `CallableFunction#call` and `Function#call` never collide, and + // that owner is exactly what the flag decides. + call(thisArg: any, arg0?: any, arg1?: any): any; + apply(thisArg: any, args?: any): any; + bind(thisArg: any, arg0?: any): any; +} + + +interface NewableFunction extends Function {} diff --git a/graph/test/typescript/cases/95-bind-on-method-value/src/tsconfig.json b/graph/test/typescript/cases/95-bind-on-method-value/src/tsconfig.json new file mode 100644 index 000000000..a19ccad92 --- /dev/null +++ b/graph/test/typescript/cases/95-bind-on-method-value/src/tsconfig.json @@ -0,0 +1,6 @@ +{ + "compilerOptions": { + "strict": true, + "noLib": true + } +} diff --git a/graph/test/typescript/cases/95-bind-on-method-value/src/x.ts b/graph/test/typescript/cases/95-bind-on-method-value/src/x.ts new file mode 100644 index 000000000..13b4539d2 --- /dev/null +++ b/graph/test/typescript/cases/95-bind-on-method-value/src/x.ts @@ -0,0 +1,21 @@ +// `call` / `apply` / `bind` on a METHOD read as a value, `this.render.bind(this)`. The +// receiver names a method, so it had no type for the Function members to attach to. +// src/globals.d.ts and noLib, as in case 21, put the targets inside the case. +class View { + render(): string { return "v"; } + get label(): () => string { return () => "l"; } + handler: (x: number) => number = (x) => x; + wire(): void { + this.render = this.render.bind(this); + const r = this.render.call(this); + const s = this.render.apply(this, []); + // the bound result runs the method + const bound = this.render.bind(this); + const b = bound(); + // controls: a function-typed field already resolved; a getter's value is what it returns + const h = this.handler.bind(this); + const l = this.label.bind(this); + } +} +function runOn(v: View): string { return v.render.call(v); } +export { View, runOn }; diff --git a/graph/test/typescript/expected/08-function-values-and-callbacks.edges b/graph/test/typescript/expected/08-function-values-and-callbacks.edges index c0aeef314..f5d5e2e68 100644 --- a/graph/test/typescript/expected/08-function-values-and-callbacks.edges +++ b/graph/test/typescript/expected/08-function-values-and-callbacks.edges @@ -3,7 +3,8 @@ ambiguous_unknown METHOD_CALL values#drive() @L29 -> - callback_registered FUNCTION_CALL values#useLibrary() @L40 -> values#local(?) known_edge CONSTRUCTOR_CALL values#drive() @L27 -> Pipeline#() known_edge FUNCTION_CALL values#apply(Transform,string) @L12 -> values#(string) -known_edge FUNCTION_CALL values#drive() @L30 -> values#(string) known_edge FUNCTION_CALL values#drive() @L30 -> values#apply(Transform,string) known_edge METHOD_CALL Pipeline#run(string) @L19 -> values#(string) known_edge METHOD_CALL values#drive() @L30 -> Pipeline#run(string) +multi_inferred FUNCTION_CALL values#drive() @L30 -> Pipeline#tag(string) +multi_inferred FUNCTION_CALL values#drive() @L30 -> values#(string) diff --git a/graph/test/typescript/expected/08-function-values-and-callbacks.envelope b/graph/test/typescript/expected/08-function-values-and-callbacks.envelope index b629f2f6b..95c6c001d 100644 --- a/graph/test/typescript/expected/08-function-values-and-callbacks.envelope +++ b/graph/test/typescript/expected/08-function-values-and-callbacks.envelope @@ -4,4 +4,3 @@ value values#@4:25 -> values#Pipeline.tag value_call values#Pipeline.run -> values#@6 value_call values#apply -> values#@6 value_call values#apply -> values#@7 -value_call values#drive -> values#Pipeline.tag diff --git a/graph/test/typescript/expected/08-function-values-and-callbacks.lib.edges b/graph/test/typescript/expected/08-function-values-and-callbacks.lib.edges index 20caf154d..03047edcb 100644 --- a/graph/test/typescript/expected/08-function-values-and-callbacks.lib.edges +++ b/graph/test/typescript/expected/08-function-values-and-callbacks.lib.edges @@ -5,7 +5,8 @@ boundary_lib FUNCTION_CALL values#useLibrary() @L40 -> hooks#withHook(Hook,strin callback_registered FUNCTION_CALL values#useLibrary() @L40 -> values#local(?) known_edge CONSTRUCTOR_CALL values#drive() @L27 -> Pipeline#() known_edge FUNCTION_CALL values#apply(Transform,string) @L12 -> values#(string) -known_edge FUNCTION_CALL values#drive() @L30 -> values#(string) known_edge FUNCTION_CALL values#drive() @L30 -> values#apply(Transform,string) known_edge METHOD_CALL Pipeline#run(string) @L19 -> values#(string) known_edge METHOD_CALL values#drive() @L30 -> Pipeline#run(string) +multi_inferred FUNCTION_CALL values#drive() @L30 -> Pipeline#tag(string) +multi_inferred FUNCTION_CALL values#drive() @L30 -> values#(string) diff --git a/graph/test/typescript/expected/08-function-values-and-callbacks.lib.envelope b/graph/test/typescript/expected/08-function-values-and-callbacks.lib.envelope index c3b500e6a..2df0528fc 100644 --- a/graph/test/typescript/expected/08-function-values-and-callbacks.lib.envelope +++ b/graph/test/typescript/expected/08-function-values-and-callbacks.lib.envelope @@ -5,4 +5,3 @@ value values#@4:25 -> values#Pipeline.tag value_call values#Pipeline.run -> values#@6 value_call values#apply -> values#@6 value_call values#apply -> values#@7 -value_call values#drive -> values#Pipeline.tag diff --git a/graph/test/typescript/expected/08-function-values-and-callbacks.lib.oracle b/graph/test/typescript/expected/08-function-values-and-callbacks.lib.oracle index 0b8515211..068febee7 100644 --- a/graph/test/typescript/expected/08-function-values-and-callbacks.lib.oracle +++ b/graph/test/typescript/expected/08-function-values-and-callbacks.lib.oracle @@ -1,2 +1,3 @@ -oracle=8 engine=9 agree=8 missing=0 (known 0, NEW 0) extra=1 +oracle=8 engine=10 agree=8 missing=0 (known 0, NEW 0) extra=2 + extra values#drive() -> Pipeline#tag(string) extra values#useLibrary() -> hooks#identity(?) diff --git a/graph/test/typescript/expected/08-function-values-and-callbacks.oracle b/graph/test/typescript/expected/08-function-values-and-callbacks.oracle index 4794eb8a3..84fbe5b0d 100644 --- a/graph/test/typescript/expected/08-function-values-and-callbacks.oracle +++ b/graph/test/typescript/expected/08-function-values-and-callbacks.oracle @@ -1 +1,2 @@ -oracle=6 engine=6 agree=6 missing=0 (known 0, NEW 0) extra=0 +oracle=6 engine=7 agree=6 missing=0 (known 0, NEW 0) extra=1 + extra values#drive() -> Pipeline#tag(string) diff --git a/graph/test/typescript/expected/80-method-passed-by-reference.edges b/graph/test/typescript/expected/80-method-passed-by-reference.edges index 3d1be41b8..0e5d76362 100644 --- a/graph/test/typescript/expected/80-method-passed-by-reference.edges +++ b/graph/test/typescript/expected/80-method-passed-by-reference.edges @@ -1,8 +1,5 @@ ambient_terminal FUNCTION_CALL Widget#now(number[]) @L40 -> globals#setTimeout(() =,number) ambient_terminal FUNCTION_CALL Widget#wire(Target) @L24 -> globals#setTimeout(() =,number) -ambiguous_unknown METHOD_CALL Widget#now(number[]) @L40 -> - -ambiguous_unknown METHOD_CALL Widget#start(number[]) @L18 -> - -ambiguous_unknown METHOD_CALL Widget#wire(Target) @L24 -> - callback_registered FUNCTION_CALL Widget#wire(Target) @L24 -> Widget#onHover() callback_registered METHOD_CALL Widget#relay(Widget,number[]) @L29 -> Widget#handle(number) callback_registered METHOD_CALL Widget#start(number[]) @L18 -> Widget#handle(number) @@ -10,11 +7,14 @@ callback_registered METHOD_CALL Widget#start(number[]) @L19 -> Widget#handle(num callback_registered METHOD_CALL Widget#tap(Target) @L34 -> Widget#() callback_registered METHOD_CALL Widget#wire(Target) @L23 -> Widget#onClick() known_edge METHOD_CALL Widget#() @L34 -> Widget#onTap() +known_edge METHOD_CALL Widget#now(number[]) @L40 -> Function#call(unknown,unknown[]) known_edge METHOD_CALL Widget#now(number[]) @L41 -> Array#forEach((v: T) =,unknown) known_edge METHOD_CALL Widget#now(number[]) @L42 -> Array#forEach((v: T) =,unknown) known_edge METHOD_CALL Widget#relay(Widget,number[]) @L29 -> Array#forEach((v: T) =,unknown) known_edge METHOD_CALL Widget#start(number[]) @L18 -> Array#forEach((v: T) =,unknown) +known_edge METHOD_CALL Widget#start(number[]) @L18 -> Function#bind(unknown,unknown[]) known_edge METHOD_CALL Widget#start(number[]) @L19 -> Array#map((v: T) =,unknown) known_edge METHOD_CALL Widget#tap(Target) @L34 -> Target#addEventListener(string,() =) known_edge METHOD_CALL Widget#wire(Target) @L23 -> Target#addEventListener(string,() =) +known_edge METHOD_CALL Widget#wire(Target) @L24 -> Function#bind(unknown,unknown[]) known_edge PROPERTY_READ Widget#now(number[]) @L42 -> Widget#current() diff --git a/graph/test/typescript/expected/80-method-passed-by-reference.oracle b/graph/test/typescript/expected/80-method-passed-by-reference.oracle index 196060769..f8e70220b 100644 --- a/graph/test/typescript/expected/80-method-passed-by-reference.oracle +++ b/graph/test/typescript/expected/80-method-passed-by-reference.oracle @@ -1,3 +1,6 @@ -oracle=8 engine=10 agree=8 missing=0 (known 0, NEW 0) extra=2 +oracle=8 engine=13 agree=8 missing=0 (known 0, NEW 0) extra=5 + extra Widget#now(number[]) -> Function#call(unknown,unknown[]) extra Widget#now(number[]) -> globals#setTimeout(() =,number) + extra Widget#start(number[]) -> Function#bind(unknown,unknown[]) + extra Widget#wire(Target) -> Function#bind(unknown,unknown[]) extra Widget#wire(Target) -> globals#setTimeout(() =,number) diff --git a/graph/test/typescript/expected/83-closed-world-dispatch.edges b/graph/test/typescript/expected/83-closed-world-dispatch.edges index 3e35361f4..ad9e18770 100644 --- a/graph/test/typescript/expected/83-closed-world-dispatch.edges +++ b/graph/test/typescript/expected/83-closed-world-dispatch.edges @@ -1,7 +1,7 @@ -ambiguous_unknown CONSTRUCTOR_CALL handlers#main() @L52 -> - ambiguous_unknown METHOD_CALL handlers#register(HandlerClass) @L38 -> - known_edge CONSTRUCTOR_CALL handlers#main() @L46 -> Built#() known_edge CONSTRUCTOR_CALL handlers#main() @L47 -> Base#() +known_edge CONSTRUCTOR_CALL handlers#main() @L52 -> handlers#() known_edge FUNCTION_CALL handlers#() @L39 -> handlers#register(HandlerClass) known_edge FUNCTION_CALL handlers#main() @L50 -> handlers#dispatch(Handler,string) known_edge FUNCTION_CALL handlers#main() @L51 -> handlers#dispatch(Handler,string) diff --git a/graph/test/typescript/expected/83-closed-world-dispatch.known-missing b/graph/test/typescript/expected/83-closed-world-dispatch.known-missing deleted file mode 100644 index 15bb2695f..000000000 --- a/graph/test/typescript/expected/83-closed-world-dispatch.known-missing +++ /dev/null @@ -1,7 +0,0 @@ -# Accepted gaps for 83-closed-world-dispatch — each line is an edge the TypeScript compiler -# 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. -# `new C()` where C is typed by a construct-signature alias: the compiler names the -# alias's signature, the engine leaves the site ambiguous_unknown. Unrelated to the -# closed-world fan this case is about. -handlers#main() -> handlers#() diff --git a/graph/test/typescript/expected/83-closed-world-dispatch.oracle b/graph/test/typescript/expected/83-closed-world-dispatch.oracle index 7f00d858a..35561d9c3 100644 --- a/graph/test/typescript/expected/83-closed-world-dispatch.oracle +++ b/graph/test/typescript/expected/83-closed-world-dispatch.oracle @@ -1,5 +1,4 @@ -oracle=6 engine=12 agree=5 missing=1 (known 1, NEW 0) extra=7 - known handlers#main() -> handlers#() +oracle=6 engine=13 agree=6 missing=0 (known 0, NEW 0) extra=7 extra handlers#dispatch(Handler,string) -> Base#handle(string) extra handlers#dispatch(Handler,string) -> Built#handle(string) extra handlers#dispatch(Handler,string) -> Orphan#handle(string) diff --git a/graph/test/typescript/expected/91-construct-through-shape.edges b/graph/test/typescript/expected/91-construct-through-shape.edges new file mode 100644 index 000000000..d04b2b5ad --- /dev/null +++ b/graph/test/typescript/expected/91-construct-through-shape.edges @@ -0,0 +1,5 @@ +known_edge CONSTRUCTOR_CALL x#viaAlias(WidgetClass) @L8 -> x#(WidgetDef) +known_edge CONSTRUCTOR_CALL x#viaCtorType(new (spec: WidgetDef) =) @L12 -> x#(WidgetDef) +known_edge CONSTRUCTOR_CALL x#viaInterface(ButtonCtor) @L14 -> ButtonCtor#(WidgetDef) +known_edge CONSTRUCTOR_CALL x#viaLiteral({ new (spec: WidgetDef): Button }) @L9 -> x#(WidgetDef) +known_edge FUNCTION_CALL x#() @L15 -> x#viaAlias(WidgetClass) diff --git a/graph/test/typescript/expected/91-construct-through-shape.entries b/graph/test/typescript/expected/91-construct-through-shape.entries new file mode 100644 index 000000000..71704302a --- /dev/null +++ b/graph/test/typescript/expected/91-construct-through-shape.entries @@ -0,0 +1,6 @@ +── entry_point (5) ── + exported_from_entry_module x#viaAlias x.ts:8 + exported_from_entry_module x#viaCtorType x.ts:12 + exported_from_entry_module x#viaInterface x.ts:14 + exported_from_entry_module x#viaLiteral x.ts:9 + unimported_module x# x.ts:1 diff --git a/graph/test/typescript/expected/91-construct-through-shape.fields b/graph/test/typescript/expected/91-construct-through-shape.fields new file mode 100644 index 000000000..e69de29bb diff --git a/graph/test/typescript/expected/91-construct-through-shape.fields-oracle b/graph/test/typescript/expected/91-construct-through-shape.fields-oracle new file mode 100644 index 000000000..2a4ef20c1 --- /dev/null +++ b/graph/test/typescript/expected/91-construct-through-shape.fields-oracle @@ -0,0 +1,7 @@ +91-construct-through-shape [fields] + precision 0.0000 (0 correct, 0 wrong) + recall 0.0000 (0 of 0 the compiler resolved) + sites 0 resolved 0 + tiers + access + not scored: 0 rows whose target is not a client declaration diff --git a/graph/test/typescript/expected/91-construct-through-shape.oracle b/graph/test/typescript/expected/91-construct-through-shape.oracle new file mode 100644 index 000000000..6c4bb5cbb --- /dev/null +++ b/graph/test/typescript/expected/91-construct-through-shape.oracle @@ -0,0 +1 @@ +oracle=5 engine=5 agree=5 missing=0 (known 0, NEW 0) extra=0 diff --git a/graph/test/typescript/expected/91-construct-through-shape.type-use b/graph/test/typescript/expected/91-construct-through-shape.type-use new file mode 100644 index 000000000..746e05578 --- /dev/null +++ b/graph/test/typescript/expected/91-construct-through-shape.type-use @@ -0,0 +1,10 @@ +ambiguous_unknown OBJECT_CREATION_TYPE 0 x [EXPRESSION] -> - +known_edge METHOD_PARAM 0 Button [METHOD_PARAM] -> WidgetDef +known_edge METHOD_PARAM 0 ButtonCtor [METHOD_PARAM] -> WidgetDef +known_edge METHOD_PARAM 0 x [METHOD_PARAM] -> ButtonCtor +known_edge METHOD_PARAM 0 x [METHOD_PARAM] -> WidgetClass +known_edge METHOD_PARAM 0 x [METHOD_PARAM] -> WidgetDef +known_edge METHOD_PARAM 1 x [METHOD_PARAM] -> WidgetDef +known_edge METHOD_RETURN 0 ButtonCtor [METHOD] -> Button +known_edge METHOD_RETURN 0 x [METHOD] -> Button +known_edge METHOD_RETURN 1 x [METHOD_PARAM] -> Button diff --git a/graph/test/typescript/expected/91-construct-through-shape.types-oracle b/graph/test/typescript/expected/91-construct-through-shape.types-oracle new file mode 100644 index 000000000..4be183f12 --- /dev/null +++ b/graph/test/typescript/expected/91-construct-through-shape.types-oracle @@ -0,0 +1,8 @@ +91-construct-through-shape [types] + precision 1.0000 (7 correct, 0 wrong) + recall 0.8750 (7 of 8 the compiler resolved) + sites 17 resolved 13 (76.5%) + tiers ambiguous_unknown=4 known_edge=13 + contexts METHOD_PARAM=7 METHOD_RETURN=6 OBJECT_CREATION_TYPE=4 + not scored: 4 rows whose target is not a client declaration + MISSING WidgetClass USES WidgetDef diff --git a/graph/test/typescript/expected/92-namespace-typed-const.edges b/graph/test/typescript/expected/92-namespace-typed-const.edges new file mode 100644 index 000000000..9205e5e9c --- /dev/null +++ b/graph/test/typescript/expected/92-namespace-typed-const.edges @@ -0,0 +1,5 @@ +known_edge FUNCTION_CALL scorers#() @L7 -> scorers#makeScorer(number) +known_edge FUNCTION_CALL x#viaNamed(string) @L7 -> scorers#(string) +known_edge METHOD_CALL x#viaArrow(string) @L8 -> scorers#scoreArrow(?) +known_edge METHOD_CALL x#viaNamespace(string) @L5 -> scorers#(string) +known_edge METHOD_CALL x#viaPlain(string) @L9 -> scorers#plain(string) diff --git a/graph/test/typescript/expected/92-namespace-typed-const.entries b/graph/test/typescript/expected/92-namespace-typed-const.entries new file mode 100644 index 000000000..637ea1410 --- /dev/null +++ b/graph/test/typescript/expected/92-namespace-typed-const.entries @@ -0,0 +1,6 @@ +── entry_point (5) ── + exported_from_entry_module x#viaArrow x.ts:8 + exported_from_entry_module x#viaNamed x.ts:7 + exported_from_entry_module x#viaNamespace x.ts:5 + exported_from_entry_module x#viaPlain x.ts:9 + unimported_module x# x.ts:1 diff --git a/graph/test/typescript/expected/92-namespace-typed-const.envelope b/graph/test/typescript/expected/92-namespace-typed-const.envelope new file mode 100644 index 000000000..5781aa1ce --- /dev/null +++ b/graph/test/typescript/expected/92-namespace-typed-const.envelope @@ -0,0 +1,4 @@ +value scorers#@1:22 -> scorers#@3 +value scorers#@1:22 -> scorers#@8 +value_call x#viaNamed -> scorers#@3 +value_call x#viaNamespace -> scorers#@3 diff --git a/graph/test/typescript/expected/92-namespace-typed-const.fields b/graph/test/typescript/expected/92-namespace-typed-const.fields new file mode 100644 index 000000000..e9b9013e7 --- /dev/null +++ b/graph/test/typescript/expected/92-namespace-typed-const.fields @@ -0,0 +1,4 @@ +ambiguous_unknown read scorers#fn(?) -> - +ambiguous_unknown read scorers#plain(string) -> - +ambiguous_unknown read scorers#scoreArrow(?) -> - +known_edge read x#viaNamed(string) -> scorers#score diff --git a/graph/test/typescript/expected/92-namespace-typed-const.fields-oracle b/graph/test/typescript/expected/92-namespace-typed-const.fields-oracle new file mode 100644 index 000000000..da0990d4c --- /dev/null +++ b/graph/test/typescript/expected/92-namespace-typed-const.fields-oracle @@ -0,0 +1,7 @@ +92-namespace-typed-const [fields] + precision 0.0000 (0 correct, 0 wrong) + recall 0.0000 (0 of 0 the compiler resolved) + sites 4 resolved 1 (25.0%) + tiers ambiguous_unknown=3 known_edge=1 + access read=4 + not scored: 4 rows whose target is not a client declaration diff --git a/graph/test/typescript/expected/92-namespace-typed-const.known-missing b/graph/test/typescript/expected/92-namespace-typed-const.known-missing new file mode 100644 index 000000000..9dacefbf2 --- /dev/null +++ b/graph/test/typescript/expected/92-namespace-typed-const.known-missing @@ -0,0 +1,7 @@ +# Accepted gaps for 92-namespace-typed-const: each line is an edge the TypeScript compiler +# 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 const that HOLDS an arrow: the compiler names the annotation's signature, the engine names +# the arrow's body, as it does for the same const read by bare name. Kept on purpose: adding the +# bodiless signature beside the body would hedge a site whose implementation is known. +x#viaArrow(string) -> scorers#(string) diff --git a/graph/test/typescript/expected/92-namespace-typed-const.oracle b/graph/test/typescript/expected/92-namespace-typed-const.oracle new file mode 100644 index 000000000..aaf82eab3 --- /dev/null +++ b/graph/test/typescript/expected/92-namespace-typed-const.oracle @@ -0,0 +1,3 @@ +oracle=5 engine=5 agree=4 missing=1 (known 1, NEW 0) extra=1 + known x#viaArrow(string) -> scorers#(string) + extra x#viaArrow(string) -> scorers#scoreArrow(?) diff --git a/graph/test/typescript/expected/92-namespace-typed-const.type-use b/graph/test/typescript/expected/92-namespace-typed-const.type-use new file mode 100644 index 000000000..fca5ea869 --- /dev/null +++ b/graph/test/typescript/expected/92-namespace-typed-const.type-use @@ -0,0 +1,2 @@ +known_edge METHOD_RETURN 0 scorers [METHOD] -> Scorer +known_edge VARIABLE_TYPE 0 scorers [VARIABLE] -> Scorer diff --git a/graph/test/typescript/expected/92-namespace-typed-const.types-oracle b/graph/test/typescript/expected/92-namespace-typed-const.types-oracle new file mode 100644 index 000000000..1318ffdaa --- /dev/null +++ b/graph/test/typescript/expected/92-namespace-typed-const.types-oracle @@ -0,0 +1,7 @@ +92-namespace-typed-const [types] + precision 1.0000 (1 correct, 0 wrong) + recall 1.0000 (1 of 1 the compiler resolved) + sites 4 resolved 4 (100.0%) + tiers known_edge=4 + contexts METHOD_RETURN=1 VARIABLE_TYPE=3 + not scored: 0 rows whose target is not a client declaration diff --git a/graph/test/typescript/expected/93-literal-member-chain.edges b/graph/test/typescript/expected/93-literal-member-chain.edges new file mode 100644 index 000000000..9e9c71b98 --- /dev/null +++ b/graph/test/typescript/expected/93-literal-member-chain.edges @@ -0,0 +1,4 @@ +known_edge METHOD_CALL x#flat(Flat) @L15 -> Logger#write(string) +known_edge METHOD_CALL x#named(Named) @L16 -> Logger#write(string) +known_edge METHOD_CALL x#record(Order) @L8 -> Logger#write(string) +known_edge METHOD_CALL x#record(Order) @L9 -> Logger#write(string) diff --git a/graph/test/typescript/expected/93-literal-member-chain.entries b/graph/test/typescript/expected/93-literal-member-chain.entries new file mode 100644 index 000000000..ffd59efd0 --- /dev/null +++ b/graph/test/typescript/expected/93-literal-member-chain.entries @@ -0,0 +1,5 @@ +── entry_point (4) ── + exported_from_entry_module x#flat x.ts:15 + exported_from_entry_module x#named x.ts:16 + exported_from_entry_module x#record x.ts:7 + unimported_module x# x.ts:1 diff --git a/graph/test/typescript/expected/93-literal-member-chain.fields b/graph/test/typescript/expected/93-literal-member-chain.fields new file mode 100644 index 000000000..a14893e8a --- /dev/null +++ b/graph/test/typescript/expected/93-literal-member-chain.fields @@ -0,0 +1,10 @@ +known_edge read x#flat(Flat) -> Flat#meta +known_edge read x#flat(Flat) -> { log: Logger }#log +known_edge read x#named(Named) -> Meta#log +known_edge read x#named(Named) -> Named#meta +known_edge read x#record(Order) -> Order#meta +known_edge read x#record(Order) -> { audit: { log: Logger; nested: { deep: { log: Logger } } } }#audit +known_edge read x#record(Order) -> { deep: { log: Logger } }#deep +known_edge read x#record(Order) -> { log: Logger }#log +known_edge read x#record(Order) -> { log: Logger; nested: { deep: { log: Logger } } }#log +known_edge read x#record(Order) -> { log: Logger; nested: { deep: { log: Logger } } }#nested diff --git a/graph/test/typescript/expected/93-literal-member-chain.fields-oracle b/graph/test/typescript/expected/93-literal-member-chain.fields-oracle new file mode 100644 index 000000000..27fc7aac8 --- /dev/null +++ b/graph/test/typescript/expected/93-literal-member-chain.fields-oracle @@ -0,0 +1,18 @@ +93-literal-member-chain [fields] + precision 0.4000 (4 correct, 6 wrong) + recall 0.4444 (4 of 9 the compiler resolved) + sites 12 resolved 12 (100.0%) + tiers known_edge=12 + access read=12 + not scored: 0 rows whose target is not a client declaration + WRONG x#flat(Flat) READ { log: Logger }#log + WRONG x#record(Order) READ { audit: { log: Logger; nested: { deep: { log: Logger } } } }#audit + WRONG x#record(Order) READ { deep: { log: Logger } }#deep + WRONG x#record(Order) READ { log: Logger }#log + WRONG x#record(Order) READ { log: Logger; nested: { deep: { log: Logger } } }#log + WRONG x#record(Order) READ { log: Logger; nested: { deep: { log: Logger } } }#nested + MISSING x#flat(Flat) READ Flat#log + MISSING x#record(Order) READ Order#audit + MISSING x#record(Order) READ Order#deep + MISSING x#record(Order) READ Order#log + MISSING x#record(Order) READ Order#nested diff --git a/graph/test/typescript/expected/93-literal-member-chain.oracle b/graph/test/typescript/expected/93-literal-member-chain.oracle new file mode 100644 index 000000000..a43ea4f88 --- /dev/null +++ b/graph/test/typescript/expected/93-literal-member-chain.oracle @@ -0,0 +1 @@ +oracle=3 engine=3 agree=3 missing=0 (known 0, NEW 0) extra=0 diff --git a/graph/test/typescript/expected/93-literal-member-chain.type-use b/graph/test/typescript/expected/93-literal-member-chain.type-use new file mode 100644 index 000000000..cecb1bce8 --- /dev/null +++ b/graph/test/typescript/expected/93-literal-member-chain.type-use @@ -0,0 +1,8 @@ +known_edge FIELD_TYPE 0 Meta [FIELD] -> Logger +known_edge FIELD_TYPE 0 Named [FIELD] -> Meta +known_edge FIELD_TYPE 1 Flat [FIELD] -> Logger +known_edge FIELD_TYPE 2 Order [FIELD] -> Logger +known_edge FIELD_TYPE 4 Order [FIELD] -> Logger +known_edge METHOD_PARAM 0 x [METHOD_PARAM] -> Flat +known_edge METHOD_PARAM 0 x [METHOD_PARAM] -> Named +known_edge METHOD_PARAM 0 x [METHOD_PARAM] -> Order diff --git a/graph/test/typescript/expected/93-literal-member-chain.types-oracle b/graph/test/typescript/expected/93-literal-member-chain.types-oracle new file mode 100644 index 000000000..53acc6dcd --- /dev/null +++ b/graph/test/typescript/expected/93-literal-member-chain.types-oracle @@ -0,0 +1,7 @@ +93-literal-member-chain [types] + precision 1.0000 (7 correct, 0 wrong) + recall 1.0000 (7 of 7 the compiler resolved) + sites 8 resolved 8 (100.0%) + tiers known_edge=8 + contexts FIELD_TYPE=5 METHOD_PARAM=3 + not scored: 0 rows whose target is not a client declaration diff --git a/graph/test/typescript/expected/94-thistype-object-literal.edges b/graph/test/typescript/expected/94-thistype-object-literal.edges new file mode 100644 index 000000000..ebda1c8aa --- /dev/null +++ b/graph/test/typescript/expected/94-thistype-object-literal.edges @@ -0,0 +1,8 @@ +ambient_terminal FUNCTION_CALL x#() @L10 -> x#mixin(string,Members) +ambient_terminal FUNCTION_CALL x#() @L29 -> x#mixin(string,Members) +ambiguous_unknown METHOD_CALL x#b() @L25 -> - +known_edge METHOD_CALL Host#() @L29 -> Host#run() +known_edge METHOD_CALL x#greet() @L11 -> Greeter#shout() +known_edge METHOD_CALL x#hail() @L15 -> Greeter#greet() +known_edge METHOD_CALL x#shout() @L12 -> Greeter#greet() +known_edge METHOD_CALL x#wave() @L19 -> Greeter#shout() diff --git a/graph/test/typescript/expected/94-thistype-object-literal.entries b/graph/test/typescript/expected/94-thistype-object-literal.entries new file mode 100644 index 000000000..6f5cfa1ff --- /dev/null +++ b/graph/test/typescript/expected/94-thistype-object-literal.entries @@ -0,0 +1,2 @@ +── entry_point (1) ── + unimported_module x# x.ts:1 diff --git a/graph/test/typescript/expected/94-thistype-object-literal.envelope b/graph/test/typescript/expected/94-thistype-object-literal.envelope new file mode 100644 index 000000000..6ef842dad --- /dev/null +++ b/graph/test/typescript/expected/94-thistype-object-literal.envelope @@ -0,0 +1,2 @@ +value x#Greeter.hail -> x#hail +value x#Waver.wave -> x#wave diff --git a/graph/test/typescript/expected/94-thistype-object-literal.fields b/graph/test/typescript/expected/94-thistype-object-literal.fields new file mode 100644 index 000000000..e69de29bb diff --git a/graph/test/typescript/expected/94-thistype-object-literal.fields-oracle b/graph/test/typescript/expected/94-thistype-object-literal.fields-oracle new file mode 100644 index 000000000..36c4e6506 --- /dev/null +++ b/graph/test/typescript/expected/94-thistype-object-literal.fields-oracle @@ -0,0 +1,7 @@ +94-thistype-object-literal [fields] + precision 0.0000 (0 correct, 0 wrong) + recall 0.0000 (0 of 0 the compiler resolved) + sites 0 resolved 0 + tiers + access + not scored: 0 rows whose target is not a client declaration diff --git a/graph/test/typescript/expected/94-thistype-object-literal.known-missing b/graph/test/typescript/expected/94-thistype-object-literal.known-missing new file mode 100644 index 000000000..c34d8fcf5 --- /dev/null +++ b/graph/test/typescript/expected/94-thistype-object-literal.known-missing @@ -0,0 +1,6 @@ +# Accepted gaps for 94-thistype-object-literal: each line is an edge the TypeScript compiler +# 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. +# The no-marker control: with no contextual type, `this` in a literal's method is the literal +# itself. The engine does not type it, before this change and after; the marker cases are the fix. +x#b() -> x#a() diff --git a/graph/test/typescript/expected/94-thistype-object-literal.oracle b/graph/test/typescript/expected/94-thistype-object-literal.oracle new file mode 100644 index 000000000..5c112c0a4 --- /dev/null +++ b/graph/test/typescript/expected/94-thistype-object-literal.oracle @@ -0,0 +1,2 @@ +oracle=7 engine=6 agree=6 missing=1 (known 1, NEW 0) extra=0 + known x#b() -> x#a() diff --git a/graph/test/typescript/expected/94-thistype-object-literal.type-use b/graph/test/typescript/expected/94-thistype-object-literal.type-use new file mode 100644 index 000000000..9d88dab9d --- /dev/null +++ b/graph/test/typescript/expected/94-thistype-object-literal.type-use @@ -0,0 +1,10 @@ +ambiguous_unknown TYPE_ELEMENT 1 Members [TYPE] -> - +ambiguous_unknown TYPE_ELEMENT 1 x [VARIABLE] -> - +known_edge METHOD_PARAM 0 x [METHOD_PARAM] -> Members +known_edge METHOD_RETURN 0 x [METHOD] -> Mixin +known_edge METHOD_TYPE_ARGUMENT 0 Host [EXPRESSION] -> Greeter +known_edge METHOD_TYPE_ARGUMENT 0 x [EXPRESSION] -> Greeter +known_edge TYPE_ARGUMENT 1 x [VARIABLE] -> Greeter +known_edge TYPE_ARGUMENT 2 x [VARIABLE] -> Greeter +known_edge TYPE_ELEMENT 1 x [VARIABLE] -> Waver +known_edge VARIABLE_TYPE 0 x [VARIABLE] -> Members diff --git a/graph/test/typescript/expected/94-thistype-object-literal.types-oracle b/graph/test/typescript/expected/94-thistype-object-literal.types-oracle new file mode 100644 index 000000000..cd697dd3d --- /dev/null +++ b/graph/test/typescript/expected/94-thistype-object-literal.types-oracle @@ -0,0 +1,7 @@ +94-thistype-object-literal [types] + precision 1.0000 (5 correct, 0 wrong) + recall 1.0000 (5 of 5 the compiler resolved) + sites 10 resolved 8 (80.0%) + tiers ambiguous_unknown=2 known_edge=8 + contexts METHOD_PARAM=1 METHOD_RETURN=1 METHOD_TYPE_ARGUMENT=2 TYPE_ARGUMENT=2 TYPE_ELEMENT=3 VARIABLE_TYPE=1 + not scored: 2 rows whose target is not a client declaration diff --git a/graph/test/typescript/expected/95-bind-on-method-value.edges b/graph/test/typescript/expected/95-bind-on-method-value.edges new file mode 100644 index 000000000..44e5302ec --- /dev/null +++ b/graph/test/typescript/expected/95-bind-on-method-value.edges @@ -0,0 +1,9 @@ +known_edge FUNCTION_CALL View#wire() @L14 -> View#render() +known_edge METHOD_CALL View#wire() @L10 -> CallableFunction#call(any,any,any) +known_edge METHOD_CALL View#wire() @L11 -> CallableFunction#apply(any,any) +known_edge METHOD_CALL View#wire() @L13 -> CallableFunction#bind(any,any) +known_edge METHOD_CALL View#wire() @L16 -> CallableFunction#bind(any,any) +known_edge METHOD_CALL View#wire() @L17 -> CallableFunction#bind(any,any) +known_edge METHOD_CALL View#wire() @L9 -> CallableFunction#bind(any,any) +known_edge METHOD_CALL x#runOn(View) @L20 -> CallableFunction#call(any,any,any) +known_edge PROPERTY_READ View#wire() @L17 -> View#label() diff --git a/graph/test/typescript/expected/95-bind-on-method-value.entries b/graph/test/typescript/expected/95-bind-on-method-value.entries new file mode 100644 index 000000000..6997e79ce --- /dev/null +++ b/graph/test/typescript/expected/95-bind-on-method-value.entries @@ -0,0 +1,4 @@ +── entry_point (3) ── + exported_from_entry_module x#runOn x.ts:20 + unimported_module globals# globals.d.ts:1 + unimported_module x# x.ts:1 diff --git a/graph/test/typescript/expected/95-bind-on-method-value.envelope b/graph/test/typescript/expected/95-bind-on-method-value.envelope new file mode 100644 index 000000000..46f9e8590 --- /dev/null +++ b/graph/test/typescript/expected/95-bind-on-method-value.envelope @@ -0,0 +1,5 @@ +nominal globals#Function.apply -> globals#CallableFunction.apply +nominal globals#Function.bind -> globals#CallableFunction.bind +nominal globals#Function.call -> globals#CallableFunction.call +value x#@6:16 -> x#View.@6 +value x#@7:12 -> x#View.@7 diff --git a/graph/test/typescript/expected/95-bind-on-method-value.fields b/graph/test/typescript/expected/95-bind-on-method-value.fields new file mode 100644 index 000000000..88df97815 --- /dev/null +++ b/graph/test/typescript/expected/95-bind-on-method-value.fields @@ -0,0 +1 @@ +known_edge read View#wire() -> View#handler diff --git a/graph/test/typescript/expected/95-bind-on-method-value.fields-oracle b/graph/test/typescript/expected/95-bind-on-method-value.fields-oracle new file mode 100644 index 000000000..72328250a --- /dev/null +++ b/graph/test/typescript/expected/95-bind-on-method-value.fields-oracle @@ -0,0 +1,7 @@ +95-bind-on-method-value [fields] + precision 1.0000 (1 correct, 0 wrong) + recall 1.0000 (1 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 diff --git a/graph/test/typescript/expected/95-bind-on-method-value.oracle b/graph/test/typescript/expected/95-bind-on-method-value.oracle new file mode 100644 index 000000000..131c80216 --- /dev/null +++ b/graph/test/typescript/expected/95-bind-on-method-value.oracle @@ -0,0 +1,2 @@ +oracle=5 engine=6 agree=5 missing=0 (known 0, NEW 0) extra=1 + extra View#wire() -> View#render() diff --git a/graph/test/typescript/expected/95-bind-on-method-value.type-use b/graph/test/typescript/expected/95-bind-on-method-value.type-use new file mode 100644 index 000000000..e6dc01bae --- /dev/null +++ b/graph/test/typescript/expected/95-bind-on-method-value.type-use @@ -0,0 +1,3 @@ +known_edge METHOD_PARAM 0 x [METHOD_PARAM] -> View +known_edge SUPER_TYPE 0 CallableFunction [HERITAGE] -> Function +known_edge SUPER_TYPE 0 NewableFunction [HERITAGE] -> Function diff --git a/graph/test/typescript/expected/95-bind-on-method-value.types-oracle b/graph/test/typescript/expected/95-bind-on-method-value.types-oracle new file mode 100644 index 000000000..3cce60dfc --- /dev/null +++ b/graph/test/typescript/expected/95-bind-on-method-value.types-oracle @@ -0,0 +1,7 @@ +95-bind-on-method-value [types] + precision 1.0000 (3 correct, 0 wrong) + recall 1.0000 (3 of 3 the compiler resolved) + sites 3 resolved 3 (100.0%) + tiers known_edge=3 + contexts METHOD_PARAM=1 SUPER_TYPE=2 + not scored: 0 rows whose target is not a client declaration diff --git a/graph/typescript/engine/expression-resolution/callee-resolution.dl b/graph/typescript/engine/expression-resolution/callee-resolution.dl index c42570448..d05fc58a1 100644 --- a/graph/typescript/engine/expression-resolution/callee-resolution.dl +++ b/graph/typescript/engine/expression-resolution/callee-resolution.dl @@ -190,6 +190,45 @@ expr_call_candidate(ce, m) :- call_site("client", _, cn, rk, recv, ce, _), expr_shape(recv, s), method_in_scope(s, cn, _, m). +// (6b) `call` / `apply` / `bind` ON A METHOD READ AS A VALUE: `this.render.bind(this)`, +// `proto.check.call(inst, x)`. The receiver is an access that names a METHOD, so it has no +// type or shape of its own for rules (4) to (6) to search, and the Function prototype carve-out +// in member-lookup.dl attaches only to a scope with a call signature. The compiler types the +// receiver as the method's function type and answers with its `bind`. method_value +// (value-flow.dl) recognises the same receiver but runs after this resolver, so the lookup is +// repeated here exactly as rules (4) and (6) make it. A getter is excluded: reading it runs it, +// and the value handed over is whatever it returns. +expr_call_candidate(ce, fm) :- call_site("client", _, cn, rk, recv, ce, _), + receiver_kind_is_value(rk), + function_prototype_member(cn, fm), + property_access_recv(recv, n, q), + expr_type(q, _, qt), + method_in_scope(qt, n, "false", m), + !method_kind(_, "GETTER", _, m), + !method_kind(_, "SETTER", _, m). +expr_call_candidate(ce, fm) :- call_site("client", _, cn, rk, recv, ce, _), + receiver_kind_is_value(rk), + function_prototype_member(cn, fm), + property_access_recv(recv, n, q), + expr_shape(q, s), + method_in_scope(s, n, _, m), + !method_kind(_, "GETTER", _, m), + !method_kind(_, "SETTER", _, m). +// …and what the bound result runs. `const write = sink.write.bind(sink); write(x)` calls +// `write`, which is what the compiler names. Typed only through `bind`'s generic return, the +// call reached the bodiless function types inside that return in the standard library and +// nothing else, a wrong answer as soon as (6b) gave the `bind` site its target. +expr_call_candidate(ce, m) :- call_callee_expr(_, ce, ne), + expr_referenced("client", "VARIABLE", v, ne), + var_initializer("client", _, bc, v), + call_site("client", _, "bind", _, recv, bc, _), + recv != "", + property_access_recv(recv, n, q), + expr_type(q, _, qt), + method_in_scope(qt, n, "false", m), + !method_kind(_, "GETTER", _, m), + !method_kind(_, "SETTER", _, m). + // (5b) A MEMBER OF AN OBJECT LITERAL BOUND TO A NAME — `handlers.viaShorthand(n)`. // An OBJECT_LITERAL_METHOD's owner is the literal's EXPRESSION, not a type, so it is in // no member table at all: `method_in_scope` is keyed on a scope and there is no scope @@ -484,6 +523,21 @@ expr_call_candidate(ce, ctor) :- call_site("client", "CONSTRUCTOR_CALL", _, _, _ call_callee_expr(_, ce, ne), expr_type(ne, _, ctorType), constructor_in_scope(ctorType, ctor). +// …and where the callee's type is a SHAPE rather than a declared type: a parameter typed +// `{ new (def: D): T }`, or an alias of one, as a factory taking "the class to build" is +// written. Rule (3) reads expr_shape for a plain call through a callable shape; construction +// read only expr_type, so the construct signature on the literal was never consulted and +// `new C(def)` resolved to nothing. Same lookup, construct side. +// +// Not where the callee is a variable known to HOLD a class (`const Ctor: new () => Dog = Dog`): +// the class's own constructor already answers there, and the annotation's bodiless signature +// beside it would hedge a site that was right. +expr_call_candidate(ce, ctor) :- call_site("client", "CONSTRUCTOR_CALL", _, _, _, ce, _), + call_callee_expr(_, ce, ne), + expr_shape(ne, s), + !callee_holds_class(ne), + constructor_in_scope(s, ctor). +callee_holds_class(ne) :- expr_referenced("client", "VARIABLE", v, ne), class_valued_var(v, _). // (10) `super(...)` — the enclosing class's `extends` parent's constructor. Never // an implements edge: `implements` inherits nothing, so it has no constructor to run. diff --git a/graph/typescript/engine/expression-resolution/expr-type.dl b/graph/typescript/engine/expression-resolution/expr-type.dl index 06396ef12..f7fbc0d62 100644 --- a/graph/typescript/engine/expression-resolution/expr-type.dl +++ b/graph/typescript/engine/expression-resolution/expr-type.dl @@ -178,6 +178,60 @@ this_expr(e) :- expr_referenced("client", "THIS", _, e). this_expr(e) :- expr_kind("client", "THIS_REFERENCE", _, e). method_declared_this(m, p) :- param_shape("client", "THIS", _, _, p), param_decl("client", _, _, _, m, p). +// ── `this` IN AN OBJECT LITERAL WRITTEN AS `X & ThisType` ──────────────── +// `defineProto({ inc() {…}, twice() { return this.inc() } })` where the parameter is +// `ProtoFor = { … } & ThisType`: the compiler types `this` in the literal's methods as +// T, which is the whole point of the marker. It is how a prototype table, a mixin or a store +// definition is written when its methods run with the instance as receiver. Nothing typed +// `this` there, so every `this.m()` in such a table resolved to nothing. +// +// thistype_arg(Ref, ArgRef): the contextual reference carries ThisType, with ArgRef as its +// argument written in Ref's own scope. Found through an intersection, a parenthesised type, +// and an alias, where the alias's own parameter is replaced by Ref's argument for it. That +// argument is then either a written type or the CALLEE's type variable, bound by the call +// (call_tvar_arg_binding, which covers an explicit type argument). +// +// Function-valued members only: an arrow property does not bind `this`, so it keeps the +// enclosing one. +thistype_arg(r, a) :- type_ref(prov, "TYPE_REFERENCE", _, "ThisType", _, _, _, r), + type_ref_parent(prov, r, "0", a). +thistype_arg(r, a) :- type_ref(prov, k, _, _, _, _, _, r), + thistype_carrier_kind(k), + type_ref_parent(prov, r, _, c), + thistype_arg(c, a). +thistype_arg(r, a) :- type_ref_resolves(_, r, t), + type_alias_target(_, ar, t), + thistype_arg(ar, v), + type_ref_type_param(_, tp, v), + type_param(_, pname, _, _, t, tp), + type_arg_binding(r, pname, a). +thistype_arg(r, a) :- type_ref_resolves(_, r, t), + type_alias_target(_, ar, t), + thistype_arg(ar, a), + !type_ref_type_param(_, _, a). +thistype_carrier_kind("INTERSECTION"). +thistype_carrier_kind("PARENTHESIZED"). + +objlit_this_type(l, prov, t) :- objlit_ctx_ref(l, r), + thistype_arg(r, a), + !type_ref_type_param(_, _, a), + ref_type_target(a, prov, t). +objlit_this_type(l, prov, t) :- expr_child("client", ce, "ARGUMENT", pos, l), + expr_kind("client", "OBJECT_LITERAL", _, l), + expr_call_candidate(ce, m), + param_decl(_, _, pos, _, m, p), + param_type_ref(_, r, p), + thistype_arg(r, a), + type_ref_type_param(_, tp, a), + type_param(_, _, _, _, m, tp), + call_tvar_arg_binding(ce, tp, prov, t). + +expr_type(e, prov, t) :- this_expr(e), + expr_enclosing_method(e, lm), + objlit_member_fn(l, _, lm), + !method_kind(_, "ARROW_FUNCTION", _, lm), + objlit_this_type(l, prov, t). + // `this` INSIDE A STATIC METHOD IS THE CLASS, so its members are the STATICS. A helper // class that calls its own statics through `this` is ordinary TypeScript — // `private static parse(...)` reached as `this.parse(...)` from another static — and the @@ -426,6 +480,15 @@ expr_type(e, prov, t) :- property_access_recv(e, n, q), expr_shape(q, qs), field_in_scope(qs, n, _, f), field_type(f, prov, t). +// …and the member's SHAPE, off a shape qualifier. The typed-qualifier clause for +// field_shape is below; without this one a chain of inline object types, `ctx.common.issues` +// with `ctx: { common: { issues: Issue[] } }`, went one level deep and stopped: `ctx` had a +// shape, `ctx.common` read a shape-typed member off it and got nothing, and every call on +// the rest of the chain was unresolved. +expr_shape(e, s) :- property_access_recv(e, n, q), + expr_shape(q, qs), + field_in_scope(qs, n, _, f), + field_shape(f, s). // ── A PROPERTY ACCESS WHOSE MEMBER IS A GETTER ───────────────────────────── // `get entityToSyncMetadatas(): EntityMetadata[]`, read as // `this.entityToSyncMetadatas.map(...)`. Every rule above looks the member up in @@ -471,6 +534,21 @@ expr_type(e, prov, t) :- property_access_recv(e, n, q), expr_namespace(q, mprov, m), module_export(mprov, m, n, "VARIABLE", v), var_type(v, prov, t). +// …and its SHAPE, which is the half a function-typed export has: `export const parse: +// Parser = makeParser(1)` where `Parser` aliases a function type. The named import of the +// same const already reads var_shape (the IMPORT_BINDING clause above); read off the +// namespace, `ns.parse(x)` had a type-less, shape-less callee and resolved to nothing, while +// `import { parse }` beside it resolved. The value is a factory's result, so var_is_function +// has no literal to find and the declared shape is the only answer there is. +// +// Only there. Where the const DOES hold a function literal, rule (8) already answers with its +// body, and adding the annotation's bodiless signature beside it turned an implementation the +// site was right to name into a two-way hedge. +expr_shape(e, s) :- property_access_recv(e, n, q), + expr_namespace(q, _, m), + module_export(_, m, n, "VARIABLE", v), + !var_is_function(v, _), + var_shape(v, s). expr_static_type(e, prov, t) :- property_access_recv(e, n, q), expr_namespace(q, prov, m), module_export(prov, m, n, "TYPE", t). diff --git a/graph/typescript/souffle/decls_all.dl b/graph/typescript/souffle/decls_all.dl index be835f2c9..252fbc579 100644 --- a/graph/typescript/souffle/decls_all.dl +++ b/graph/typescript/souffle/decls_all.dl @@ -201,6 +201,7 @@ .decl callee_kind_is_expression(c0:symbol) .decl objlit_ctx_ref(c0:symbol,c1:symbol) .decl objlit_ctx_scope(c0:symbol,c1:symbol) +.decl objlit_this_type(c0:symbol,c1:symbol,c2:symbol) .decl objlit_prop(c0:symbol,c1:symbol,c2:symbol) .decl objlit_member_fn(c0:symbol,c1:symbol,c2:symbol) .decl fn_written_as_sig(c0:symbol,c1:symbol) @@ -393,6 +394,8 @@ .decl method_signature_role_of(c0:symbol,c1:symbol) .decl method_this_param(c0:symbol) .decl this_expr(c0:symbol) +.decl thistype_arg(c0:symbol,c1:symbol) +.decl thistype_carrier_kind(c0:symbol) .decl method_declared_this(c0:symbol,c1:symbol) .decl module_ambient_specifier(c0:symbol,c1:symbol,c2:symbol) .decl module_decl(c0:symbol,c1:symbol,c2:symbol,c3:symbol,c4:symbol,c5:symbol) @@ -556,6 +559,7 @@ .decl return_is_type_param(c0:symbol,c1:symbol) .decl call_type_argument(c0:symbol,c1:symbol,c2:symbol) .decl call_tvar_arg_binding(c0:symbol,c1:symbol,c2:symbol,c3:symbol) +.decl callee_holds_class(c0:symbol) .decl array_of_type_param(c0:symbol,c1:symbol) .decl param_array_wrapper_kind(c0:symbol) .decl return_type_variable(c0:symbol,c1:symbol) From 4765b2a6a48f91983f2b483ec28297a38823077a Mon Sep 17 00:00:00 2001 From: Swapnil <78632212+swapnilpaliwal-sd@users.noreply.github.com> Date: Thu, 29 Oct 2026 02:02:12 -0700 Subject: [PATCH 2/2] release 0.2.3: every package and plugin manifest at 0.2.3 (the parser keeps its own version) 0.2.2 is released; this PR ships in 0.2.3. package.json (version and the five @axiomcode/engine-* pins) and the four editor plugin and extension manifests move from 0.2.2 to 0.2.3. The parser is unchanged by this PR and stays at 0.2.1. --- gemini-extension.json | 2 +- package.json | 12 ++++++------ plugins/axiomcode/.claude-plugin/plugin.json | 2 +- plugins/axiomcode/.codex-plugin/plugin.json | 2 +- plugins/axiomcode/.cursor-plugin/plugin.json | 2 +- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/gemini-extension.json b/gemini-extension.json index ae6411e45..61a14197f 100644 --- a/gemini-extension.json +++ b/gemini-extension.json @@ -1,6 +1,6 @@ { "name": "axiomcode", - "version": "0.2.2", + "version": "0.2.3", "description": "Ask your repository how its code connects: who calls this, what breaks if I change it, which tests an edit reaches, how A reaches B. Answers come from a resolved call graph and are verified against it; nothing is guessed.", "contextFileName": "plugins/axiomcode/AGENTS.md", "mcpServers": { diff --git a/package.json b/package.json index 0d7c265c3..c1fd43e95 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@axiomcode/code-graph", - "version": "0.2.2", + "version": "0.2.3", "description": "AxiomCode Graph: a resolved call graph of your codebase grounded in formal methods, so you and your coding agents can see who calls what, what a change breaks, and which tests it reaches.", "repository": { "type": "git", @@ -31,11 +31,11 @@ ], "license": "FSL-1.1-Apache-2.0", "optionalDependencies": { - "@axiomcode/engine-darwin-arm64": "0.2.2", - "@axiomcode/engine-darwin-x64": "0.2.2", - "@axiomcode/engine-linux-x64": "0.2.2", - "@axiomcode/engine-linux-arm64": "0.2.2", - "@axiomcode/engine-win32-x64": "0.2.2" + "@axiomcode/engine-darwin-arm64": "0.2.3", + "@axiomcode/engine-darwin-x64": "0.2.3", + "@axiomcode/engine-linux-x64": "0.2.3", + "@axiomcode/engine-linux-arm64": "0.2.3", + "@axiomcode/engine-win32-x64": "0.2.3" }, "bin": { "axiomcode": "bin/axiomcode.js" diff --git a/plugins/axiomcode/.claude-plugin/plugin.json b/plugins/axiomcode/.claude-plugin/plugin.json index 6e2eae13a..a45810eee 100644 --- a/plugins/axiomcode/.claude-plugin/plugin.json +++ b/plugins/axiomcode/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "axiomcode", "description": "Ask your repository how its code connects: who calls this, what breaks if I change it, which tests an edit reaches, how A reaches B. Answers come from a resolved call graph and are verified against it; nothing is guessed.", - "version": "0.2.2", + "version": "0.2.3", "author": { "name": "AxiomCode" } diff --git a/plugins/axiomcode/.codex-plugin/plugin.json b/plugins/axiomcode/.codex-plugin/plugin.json index 0348a6a37..29c2ba521 100644 --- a/plugins/axiomcode/.codex-plugin/plugin.json +++ b/plugins/axiomcode/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "axiomcode", - "version": "0.2.2", + "version": "0.2.3", "description": "Ask your repository how its code connects: who calls this, what breaks if I change it, which tests an edit reaches, how A reaches B. Answers come from a resolved call graph and are verified against it; nothing is guessed.", "author": { "name": "AxiomCode", "url": "https://github.com/AxiomCodeAI/axiomcodegraph" }, "homepage": "https://github.com/AxiomCodeAI/axiomcodegraph", diff --git a/plugins/axiomcode/.cursor-plugin/plugin.json b/plugins/axiomcode/.cursor-plugin/plugin.json index 74ce345ef..fadd636d0 100644 --- a/plugins/axiomcode/.cursor-plugin/plugin.json +++ b/plugins/axiomcode/.cursor-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "axiomcode", - "version": "0.2.2", + "version": "0.2.3", "description": "Ask your repository how its code connects: who calls this, what breaks if I change it, which tests an edit reaches, how A reaches B. Answers come from a resolved call graph and are verified against it; nothing is guessed. Java, TypeScript, Python, JavaScript.", "author": { "name": "AxiomCode" }, "homepage": "https://github.com/AxiomCodeAI/axiomcodegraph",