diff --git a/graph/test/typescript/cases/80-generic-return-receiver/lib/container.ts b/graph/test/typescript/cases/80-generic-return-receiver/lib/container.ts new file mode 100644 index 000000000..b50fc584d --- /dev/null +++ b/graph/test/typescript/cases/80-generic-return-receiver/lib/container.ts @@ -0,0 +1,17 @@ +// A dependency-injection container as a library ships it: the service's type is +// the call's own type argument, and nothing else names it. +export class Container { + get(id: symbol): T { + return undefined as unknown as T; + } + bind(id: symbol): Container { + return this; + } +} + +// A generic keyed store, the library-side twin of Map. +export class Registry { + lookup(key: string): V | undefined { + return undefined; + } +} diff --git a/graph/test/typescript/cases/80-generic-return-receiver/src/use.ts b/graph/test/typescript/cases/80-generic-return-receiver/src/use.ts new file mode 100644 index 000000000..4cdb751f9 --- /dev/null +++ b/graph/test/typescript/cases/80-generic-return-receiver/src/use.ts @@ -0,0 +1,88 @@ +// A receiver whose type is what a GENERIC or computed call returns. Each shape is +// statically typed by the compiler, so each `.m()` below has one declared target. + +export interface Warrior { fight(): string; } +export class Ninja implements Warrior { fight(): string { return "ninja"; } } + +export class User { + touch(): string { return "t"; } + save(): string { return "s"; } +} + +export interface Shape { area(): number; } +export class Sq implements Shape { constructor(private s: number) {} area(): number { return this.s * this.s; } } +export class Circ implements Shape { constructor(private r: number) {} area(): number { return 3 * this.r * this.r; } } + +// ── the gap ────────────────────────────────────────────────────────────────── +// `Map` built with `new`, read with `.get(k)!`. +export function recvMapGet(): string { + const m = new Map(); + return m.get("x")!.touch(); +} + +// A function-typed PARAMETER with its own type parameter, called with a written argument. +export function recvGeneric(get: () => T): string { + return get().touch(); +} + +// An index-signature object whose values are factories; the element is CALLED. +const factories: { [k: string]: (n: number) => Shape } = { + sq: (n) => new Sq(n), + circ: (n) => new Circ(n), +}; +export function totalArea(kind: string, n: number): number { + return factories[kind](n).area(); +} + +// The same through a declared Record alias. +const makers: Record Shape> = { sq: (n) => new Sq(n) }; +export function recordArea(kind: string, n: number): number { + return makers[kind](n).area(); +} + +// An inline index signature whose value is NOT a function: the element is a Shape. +const shapes: { [k: string]: Shape } = { sq: new Sq(1) }; +export function indexValue(kind: string): number { + return shapes[kind].area(); +} + +// T bound from the ARGUMENT of a generic function-typed parameter. +export function recvGenericArg(pick: (x: T) => T, u: User): string { + return pick(u).save(); +} + +// ── controls: already typed, must not change ──────────────────────────────── +export function ctlDeclared(): string { + const u: User = JSON.parse("{}"); + return u.save(); +} +export function ctlCast(): string { + const u = JSON.parse("{}") as User; + return u.save(); +} +// ── controls: nothing names the type, so these must STAY unresolved ───────── +// The written type argument is `any`: T is untyped, and `.touch()` is a name only. +export function ctlGenericUnbound(get: () => T): string { + return get().touch(); +} +// An index signature over `any`: the element is untyped. +const anyBag: { [k: string]: any } = {}; +export function ctlAnyIndex(kind: string): number { + return anyBag[kind].area(); +} + +// ── client -> library ──────────────────────────────────────────────────────── +import { Container, Registry } from "../lib/container"; + +const container = new Container(); +const WARRIOR = Symbol.for("Warrior"); +container.bind(WARRIOR); + +export function run(): string { + const w = container.get(WARRIOR); + return w.fight(); +} + +export function viaRegistry(r: Registry): string { + return r.lookup("x")!.touch(); +} diff --git a/graph/test/typescript/expected/13-receiver-forms.edges b/graph/test/typescript/expected/13-receiver-forms.edges index a04966b62..5500c94cf 100644 --- a/graph/test/typescript/expected/13-receiver-forms.edges +++ b/graph/test/typescript/expected/13-receiver-forms.edges @@ -20,6 +20,7 @@ known_edge METHOD_CALL receivers#drive(Holder,Node | undefined) @L22 -> Node#vis known_edge METHOD_CALL receivers#drive(Holder,Node | undefined) @L23 -> Holder#make() known_edge METHOD_CALL receivers#drive(Holder,Node | undefined) @L23 -> Node#visit() known_edge METHOD_CALL receivers#drive(Holder,Node | undefined) @L24 -> Node#visit() +known_edge METHOD_CALL receivers#drive(Holder,Node | undefined) @L25 -> Node#visit() known_edge METHOD_CALL receivers#drive(Holder,Node | undefined) @L26 -> Node#visit() known_edge METHOD_CALL receivers#drive(Holder,Node | undefined) @L27 -> Node#visit() known_edge METHOD_CALL receivers#drive(Holder,Node | undefined) @L28 -> Node#visit() diff --git a/graph/test/typescript/expected/13-receiver-forms.lib.edges b/graph/test/typescript/expected/13-receiver-forms.lib.edges index 974c87f45..f2ad957a0 100644 --- a/graph/test/typescript/expected/13-receiver-forms.lib.edges +++ b/graph/test/typescript/expected/13-receiver-forms.lib.edges @@ -21,6 +21,7 @@ known_edge METHOD_CALL receivers#drive(Holder,Node | undefined) @L22 -> Node#vis known_edge METHOD_CALL receivers#drive(Holder,Node | undefined) @L23 -> Holder#make() known_edge METHOD_CALL receivers#drive(Holder,Node | undefined) @L23 -> Node#visit() known_edge METHOD_CALL receivers#drive(Holder,Node | undefined) @L24 -> Node#visit() +known_edge METHOD_CALL receivers#drive(Holder,Node | undefined) @L25 -> Node#visit() known_edge METHOD_CALL receivers#drive(Holder,Node | undefined) @L26 -> Node#visit() known_edge METHOD_CALL receivers#drive(Holder,Node | undefined) @L27 -> Node#visit() known_edge METHOD_CALL receivers#drive(Holder,Node | undefined) @L28 -> Node#visit() diff --git a/graph/test/typescript/expected/73-assigned-interface-members.type-use b/graph/test/typescript/expected/73-assigned-interface-members.type-use index 86f883b45..260281b6a 100644 --- a/graph/test/typescript/expected/73-assigned-interface-members.type-use +++ b/graph/test/typescript/expected/73-assigned-interface-members.type-use @@ -1,4 +1,3 @@ -ambiguous_unknown METHOD_PARAM 1 Parse [TYPE] -> - known_edge FIELD_TYPE 0 Schema [FIELD] -> Internals known_edge METHOD_PARAM 0 schema [METHOD_PARAM] -> Schema known_edge METHOD_TYPE_PARAM_BOUND 0 - [TYPE_PARAMETER] -> Schema diff --git a/graph/test/typescript/expected/73-assigned-interface-members.types-oracle b/graph/test/typescript/expected/73-assigned-interface-members.types-oracle index 16e28e127..b75276698 100644 --- a/graph/test/typescript/expected/73-assigned-interface-members.types-oracle +++ b/graph/test/typescript/expected/73-assigned-interface-members.types-oracle @@ -1,8 +1,8 @@ 73-assigned-interface-members [types] precision 1.0000 (3 correct, 0 wrong) recall 0.7500 (3 of 4 the compiler resolved) - sites 6 resolved 5 (83.3%) - tiers ambiguous_unknown=1 known_edge=5 - contexts FIELD_TYPE=1 METHOD_PARAM=3 METHOD_TYPE_PARAM_BOUND=1 VARIABLE_TYPE=1 - not scored: 2 rows whose target is not a client declaration + sites 5 resolved 5 (100.0%) + tiers known_edge=5 + contexts FIELD_TYPE=1 METHOD_PARAM=2 METHOD_TYPE_PARAM_BOUND=1 VARIABLE_TYPE=1 + not scored: 1 rows whose target is not a client declaration MISSING Parse USES Schema diff --git a/graph/test/typescript/expected/79-function-typed-parameter-dispatch.type-use b/graph/test/typescript/expected/79-function-typed-parameter-dispatch.type-use index 342764e7b..9ab1cb6c3 100644 --- a/graph/test/typescript/expected/79-function-typed-parameter-dispatch.type-use +++ b/graph/test/typescript/expected/79-function-typed-parameter-dispatch.type-use @@ -1,5 +1,4 @@ ambiguous_unknown AS_TARGET 0 store [EXPRESSION] -> - -ambiguous_unknown TYPE_ARGUMENT 2 LoggerImpl [TYPE] -> - ambiguous_unknown TYPE_ELEMENT 1 store [METHOD_PARAM] -> - known_edge CONDITIONAL_TRUE 2 Mutate [TYPE] -> Mutate known_edge FIELD_TYPE 0 StoreApi [FIELD] -> SetStateInternal diff --git a/graph/test/typescript/expected/79-function-typed-parameter-dispatch.types-oracle b/graph/test/typescript/expected/79-function-typed-parameter-dispatch.types-oracle index 2c07f6cbd..2a1980f44 100644 --- a/graph/test/typescript/expected/79-function-typed-parameter-dispatch.types-oracle +++ b/graph/test/typescript/expected/79-function-typed-parameter-dispatch.types-oracle @@ -1,7 +1,7 @@ 79-function-typed-parameter-dispatch [types] precision 1.0000 (12 correct, 0 wrong) recall 1.0000 (12 of 12 the compiler resolved) - sites 32 resolved 27 (84.4%) - tiers ambiguous_unknown=5 known_edge=27 - contexts AS_TARGET=1 CONDITIONAL_TRUE=1 FIELD_TYPE=1 METHOD_PARAM=6 METHOD_RETURN=2 TYPE_ARGUMENT=8 TYPE_ELEMENT=11 VARIABLE_TYPE=2 - not scored: 5 rows whose target is not a client declaration + sites 30 resolved 27 (90.0%) + tiers ambiguous_unknown=3 known_edge=27 + contexts AS_TARGET=1 CONDITIONAL_TRUE=1 FIELD_TYPE=1 METHOD_PARAM=6 METHOD_RETURN=2 TYPE_ARGUMENT=6 TYPE_ELEMENT=11 VARIABLE_TYPE=2 + not scored: 3 rows whose target is not a client declaration diff --git a/graph/test/typescript/expected/80-generic-return-receiver.edges b/graph/test/typescript/expected/80-generic-return-receiver.edges new file mode 100644 index 000000000..7978877fe --- /dev/null +++ b/graph/test/typescript/expected/80-generic-return-receiver.edges @@ -0,0 +1,34 @@ +ambiguous_unknown CONSTRUCTOR_CALL use#() @L77 -> - +ambiguous_unknown CONSTRUCTOR_CALL use#recvMapGet() @L19 -> - +ambiguous_unknown METHOD_CALL use#() @L78 -> - +ambiguous_unknown METHOD_CALL use#() @L79 -> - +ambiguous_unknown METHOD_CALL use#ctlAnyIndex(string) @L71 -> - +ambiguous_unknown METHOD_CALL use#ctlCast() @L60 -> - +ambiguous_unknown METHOD_CALL use#ctlDeclared() @L56 -> - +ambiguous_unknown METHOD_CALL use#ctlGenericUnbound(() =) @L66 -> - +ambiguous_unknown METHOD_CALL use#recvMapGet() @L20 -> - +ambiguous_unknown METHOD_CALL use#run() @L82 -> - +ambiguous_unknown METHOD_CALL use#run() @L83 -> - +ambiguous_unknown METHOD_CALL use#viaRegistry(Registry) @L87 -> - +known_edge CONSTRUCTOR_CALL use#(?) @L30 -> Sq#(number) +known_edge CONSTRUCTOR_CALL use#(?) @L31 -> Circ#(number) +known_edge CONSTRUCTOR_CALL use#(?) @L38 -> Sq#(number) +known_edge CONSTRUCTOR_CALL use#() @L44 -> Sq#(number) +known_edge FUNCTION_CALL use#ctlGenericUnbound(() =) @L66 -> use#() +known_edge FUNCTION_CALL use#recvGeneric(() =) @L25 -> use#() +known_edge FUNCTION_CALL use#recvGenericArg((x: T) =,User) @L51 -> use#(T) +known_edge METHOD_CALL use#ctlCast() @L61 -> User#save() +known_edge METHOD_CALL use#ctlDeclared() @L57 -> User#save() +known_edge METHOD_CALL use#recordArea(string,number) @L40 -> use#(number) +known_edge METHOD_CALL use#recvGeneric(() =) @L25 -> User#touch() +known_edge METHOD_CALL use#recvGenericArg((x: T) =,User) @L51 -> User#save() +known_edge METHOD_CALL use#totalArea(string,number) @L34 -> use#(number) +multi_inferred METHOD_CALL use#indexValue(string) @L46 -> Circ#area() +multi_inferred METHOD_CALL use#indexValue(string) @L46 -> Shape#area() +multi_inferred METHOD_CALL use#indexValue(string) @L46 -> Sq#area() +multi_inferred METHOD_CALL use#recordArea(string,number) @L40 -> Circ#area() +multi_inferred METHOD_CALL use#recordArea(string,number) @L40 -> Shape#area() +multi_inferred METHOD_CALL use#recordArea(string,number) @L40 -> Sq#area() +multi_inferred METHOD_CALL use#totalArea(string,number) @L34 -> Circ#area() +multi_inferred METHOD_CALL use#totalArea(string,number) @L34 -> Shape#area() +multi_inferred METHOD_CALL use#totalArea(string,number) @L34 -> Sq#area() diff --git a/graph/test/typescript/expected/80-generic-return-receiver.entries b/graph/test/typescript/expected/80-generic-return-receiver.entries new file mode 100644 index 000000000..a56bf62ed --- /dev/null +++ b/graph/test/typescript/expected/80-generic-return-receiver.entries @@ -0,0 +1,14 @@ +── entry_point (13) ── + exported_from_entry_module use#ctlAnyIndex use.ts:70 + exported_from_entry_module use#ctlCast use.ts:59 + exported_from_entry_module use#ctlDeclared use.ts:55 + exported_from_entry_module use#ctlGenericUnbound use.ts:65 + exported_from_entry_module use#indexValue use.ts:45 + exported_from_entry_module use#recordArea use.ts:39 + exported_from_entry_module use#recvGeneric use.ts:24 + exported_from_entry_module use#recvGenericArg use.ts:50 + exported_from_entry_module use#recvMapGet use.ts:18 + exported_from_entry_module use#run use.ts:81 + exported_from_entry_module use#totalArea use.ts:33 + exported_from_entry_module use#viaRegistry use.ts:86 + unimported_module use# use.ts:1 diff --git a/graph/test/typescript/expected/80-generic-return-receiver.envelope b/graph/test/typescript/expected/80-generic-return-receiver.envelope new file mode 100644 index 000000000..aee022b92 --- /dev/null +++ b/graph/test/typescript/expected/80-generic-return-receiver.envelope @@ -0,0 +1,6 @@ +nominal use#Shape.area -> use#Circ.area +nominal use#Shape.area -> use#Sq.area +nominal use#Warrior.fight -> use#Ninja.fight +value_call use#recordArea -> use#@38 +value_call use#totalArea -> use#@30 +value_call use#totalArea -> use#@31 diff --git a/graph/test/typescript/expected/80-generic-return-receiver.fields b/graph/test/typescript/expected/80-generic-return-receiver.fields new file mode 100644 index 000000000..b7d27851d --- /dev/null +++ b/graph/test/typescript/expected/80-generic-return-receiver.fields @@ -0,0 +1,2 @@ +known_edge read Circ#area() -> Circ#r +known_edge read Sq#area() -> Sq#s diff --git a/graph/test/typescript/expected/80-generic-return-receiver.fields-oracle b/graph/test/typescript/expected/80-generic-return-receiver.fields-oracle new file mode 100644 index 000000000..f770da18f --- /dev/null +++ b/graph/test/typescript/expected/80-generic-return-receiver.fields-oracle @@ -0,0 +1,7 @@ +80-generic-return-receiver [fields] + precision 1.0000 (2 correct, 0 wrong) + recall 1.0000 (2 of 2 the compiler resolved) + sites 4 resolved 4 (100.0%) + tiers known_edge=4 + access read=4 + not scored: 0 rows whose target is not a client declaration diff --git a/graph/test/typescript/expected/80-generic-return-receiver.known-missing b/graph/test/typescript/expected/80-generic-return-receiver.known-missing new file mode 100644 index 000000000..54ca2f05b --- /dev/null +++ b/graph/test/typescript/expected/80-generic-return-receiver.known-missing @@ -0,0 +1,10 @@ +# Accepted gaps for 80-generic-return-receiver — 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 receiver's type is the return of a LIBRARY generic: with no library there is no +# signature to read it from. run and viaRegistry resolve in the .lib pass. +use#run() -> Warrior#fight() +use#viaRegistry(Registry) -> User#touch() +# `Map` is the standard library's, which no case pass loads; it resolves on a real index +# given the TypeScript lib as a library. +use#recvMapGet() -> User#touch() diff --git a/graph/test/typescript/expected/80-generic-return-receiver.lib.edges b/graph/test/typescript/expected/80-generic-return-receiver.lib.edges new file mode 100644 index 000000000..342e368bd --- /dev/null +++ b/graph/test/typescript/expected/80-generic-return-receiver.lib.edges @@ -0,0 +1,36 @@ +ambiguous_unknown CONSTRUCTOR_CALL use#recvMapGet() @L19 -> - +ambiguous_unknown METHOD_CALL use#() @L78 -> - +ambiguous_unknown METHOD_CALL use#ctlAnyIndex(string) @L71 -> - +ambiguous_unknown METHOD_CALL use#ctlCast() @L60 -> - +ambiguous_unknown METHOD_CALL use#ctlDeclared() @L56 -> - +ambiguous_unknown METHOD_CALL use#ctlGenericUnbound(() =) @L66 -> - +ambiguous_unknown METHOD_CALL use#recvMapGet() @L20 -> - +boundary_lib CONSTRUCTOR_CALL use#() @L77 -> Container#() +boundary_lib METHOD_CALL use#() @L79 -> Container#bind(symbol) +boundary_lib METHOD_CALL use#run() @L82 -> Container#get(symbol) +boundary_lib METHOD_CALL use#viaRegistry(Registry) @L87 -> Registry#lookup(string) +known_edge CONSTRUCTOR_CALL use#(?) @L30 -> Sq#(number) +known_edge CONSTRUCTOR_CALL use#(?) @L31 -> Circ#(number) +known_edge CONSTRUCTOR_CALL use#(?) @L38 -> Sq#(number) +known_edge CONSTRUCTOR_CALL use#() @L44 -> Sq#(number) +known_edge FUNCTION_CALL use#ctlGenericUnbound(() =) @L66 -> use#() +known_edge FUNCTION_CALL use#recvGeneric(() =) @L25 -> use#() +known_edge FUNCTION_CALL use#recvGenericArg((x: T) =,User) @L51 -> use#(T) +known_edge METHOD_CALL use#ctlCast() @L61 -> User#save() +known_edge METHOD_CALL use#ctlDeclared() @L57 -> User#save() +known_edge METHOD_CALL use#recordArea(string,number) @L40 -> use#(number) +known_edge METHOD_CALL use#recvGeneric(() =) @L25 -> User#touch() +known_edge METHOD_CALL use#recvGenericArg((x: T) =,User) @L51 -> User#save() +known_edge METHOD_CALL use#totalArea(string,number) @L34 -> use#(number) +known_edge METHOD_CALL use#viaRegistry(Registry) @L87 -> User#touch() +multi_inferred METHOD_CALL use#indexValue(string) @L46 -> Circ#area() +multi_inferred METHOD_CALL use#indexValue(string) @L46 -> Shape#area() +multi_inferred METHOD_CALL use#indexValue(string) @L46 -> Sq#area() +multi_inferred METHOD_CALL use#recordArea(string,number) @L40 -> Circ#area() +multi_inferred METHOD_CALL use#recordArea(string,number) @L40 -> Shape#area() +multi_inferred METHOD_CALL use#recordArea(string,number) @L40 -> Sq#area() +multi_inferred METHOD_CALL use#run() @L83 -> Ninja#fight() +multi_inferred METHOD_CALL use#run() @L83 -> Warrior#fight() +multi_inferred METHOD_CALL use#totalArea(string,number) @L34 -> Circ#area() +multi_inferred METHOD_CALL use#totalArea(string,number) @L34 -> Shape#area() +multi_inferred METHOD_CALL use#totalArea(string,number) @L34 -> Sq#area() diff --git a/graph/test/typescript/expected/80-generic-return-receiver.lib.envelope b/graph/test/typescript/expected/80-generic-return-receiver.lib.envelope new file mode 100644 index 000000000..aee022b92 --- /dev/null +++ b/graph/test/typescript/expected/80-generic-return-receiver.lib.envelope @@ -0,0 +1,6 @@ +nominal use#Shape.area -> use#Circ.area +nominal use#Shape.area -> use#Sq.area +nominal use#Warrior.fight -> use#Ninja.fight +value_call use#recordArea -> use#@38 +value_call use#totalArea -> use#@30 +value_call use#totalArea -> use#@31 diff --git a/graph/test/typescript/expected/80-generic-return-receiver.lib.known-missing b/graph/test/typescript/expected/80-generic-return-receiver.lib.known-missing new file mode 100644 index 000000000..53d6b5be2 --- /dev/null +++ b/graph/test/typescript/expected/80-generic-return-receiver.lib.known-missing @@ -0,0 +1,6 @@ +# Accepted gaps for 80-generic-return-receiver — 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. +# `Map` is the standard library's, which no case pass loads; it resolves on a real index +# given the TypeScript lib as a library. +use#recvMapGet() -> User#touch() diff --git a/graph/test/typescript/expected/80-generic-return-receiver.lib.oracle b/graph/test/typescript/expected/80-generic-return-receiver.lib.oracle new file mode 100644 index 000000000..cab2609dc --- /dev/null +++ b/graph/test/typescript/expected/80-generic-return-receiver.lib.oracle @@ -0,0 +1,9 @@ +oracle=23 engine=29 agree=22 missing=1 (known 1, NEW 0) extra=7 + known use#recvMapGet() -> User#touch() + extra use#indexValue(string) -> Circ#area() + extra use#indexValue(string) -> Sq#area() + extra use#recordArea(string,number) -> Circ#area() + extra use#recordArea(string,number) -> Sq#area() + extra use#run() -> Ninja#fight() + extra use#totalArea(string,number) -> Circ#area() + extra use#totalArea(string,number) -> Sq#area() diff --git a/graph/test/typescript/expected/80-generic-return-receiver.oracle b/graph/test/typescript/expected/80-generic-return-receiver.oracle new file mode 100644 index 000000000..431cee181 --- /dev/null +++ b/graph/test/typescript/expected/80-generic-return-receiver.oracle @@ -0,0 +1,10 @@ +oracle=19 engine=22 agree=16 missing=3 (known 3, NEW 0) extra=6 + known use#recvMapGet() -> User#touch() + known use#run() -> Warrior#fight() + known use#viaRegistry(Registry) -> User#touch() + extra use#indexValue(string) -> Circ#area() + extra use#indexValue(string) -> Sq#area() + extra use#recordArea(string,number) -> Circ#area() + extra use#recordArea(string,number) -> Sq#area() + extra use#totalArea(string,number) -> Circ#area() + extra use#totalArea(string,number) -> Sq#area() diff --git a/graph/test/typescript/expected/80-generic-return-receiver.type-use b/graph/test/typescript/expected/80-generic-return-receiver.type-use new file mode 100644 index 000000000..2c639014f --- /dev/null +++ b/graph/test/typescript/expected/80-generic-return-receiver.type-use @@ -0,0 +1,16 @@ +ambiguous_unknown METHOD_PARAM 0 use [METHOD_PARAM] -> - +ambiguous_unknown OBJECT_CREATION_TYPE 0 use [EXPRESSION] -> - +ambiguous_unknown VARIABLE_TYPE 0 use [VARIABLE] -> - +known_edge AS_TARGET 0 use [EXPRESSION] -> User +known_edge IMPLEMENTS_INTERFACE 0 Circ [HERITAGE] -> Shape +known_edge IMPLEMENTS_INTERFACE 0 Ninja [HERITAGE] -> Warrior +known_edge IMPLEMENTS_INTERFACE 0 Sq [HERITAGE] -> Shape +known_edge INDEX_SIGNATURE_VALUE 1 use [VARIABLE] -> Shape +known_edge METHOD_PARAM 0 use [METHOD_PARAM] -> User +known_edge METHOD_RETURN 2 use [VARIABLE] -> Shape +known_edge METHOD_TYPE_ARGUMENT 0 use [EXPRESSION] -> User +known_edge METHOD_TYPE_ARGUMENT 0 use [EXPRESSION] -> Warrior +known_edge OBJECT_CREATION_TYPE 0 use [EXPRESSION] -> Circ +known_edge OBJECT_CREATION_TYPE 0 use [EXPRESSION] -> Sq +known_edge TYPE_ARGUMENT 1 use [METHOD_PARAM] -> User +known_edge VARIABLE_TYPE 0 use [VARIABLE] -> User diff --git a/graph/test/typescript/expected/80-generic-return-receiver.types-oracle b/graph/test/typescript/expected/80-generic-return-receiver.types-oracle new file mode 100644 index 000000000..5b8259a0b --- /dev/null +++ b/graph/test/typescript/expected/80-generic-return-receiver.types-oracle @@ -0,0 +1,7 @@ +80-generic-return-receiver [types] + precision 1.0000 (8 correct, 0 wrong) + recall 1.0000 (8 of 8 the compiler resolved) + sites 22 resolved 18 (81.8%) + tiers ambiguous_unknown=4 known_edge=18 + contexts AS_TARGET=1 IMPLEMENTS_INTERFACE=3 INDEX_SIGNATURE_VALUE=1 METHOD_PARAM=2 METHOD_RETURN=2 METHOD_TYPE_ARGUMENT=4 OBJECT_CREATION_TYPE=6 TYPE_ARGUMENT=1 VARIABLE_TYPE=2 + not scored: 4 rows whose target is not a client declaration diff --git a/graph/typescript/engine/expression-resolution/expr-type.dl b/graph/typescript/engine/expression-resolution/expr-type.dl index 30c4bcc97..7e0a11fc4 100644 --- a/graph/typescript/engine/expression-resolution/expr-type.dl +++ b/graph/typescript/engine/expression-resolution/expr-type.dl @@ -601,6 +601,20 @@ expr_element_type(e, prov, t) :- expr_type(e, _, qt), expr_element_type(e, prov, t) :- expr_type(e, _, qt), index_signature_in_scope(qt, f), field_type(f, prov, t). +// …and the same signature written INLINE — `const reg: { [k: string]: Handler }` — +// where the qualifier has a SHAPE rather than a named type. And where the signature's +// value is itself a function type, `{ [k: string]: (n: number) => Shape }`, the element +// is that signature: `factories[kind](n).area()` is a call on it, and without the shape +// the call had no candidate and `.area()` no receiver type. +expr_element_type(e, prov, t) :- expr_shape(e, qs), + index_signature_in_scope(qs, f), + field_type(f, prov, t). +expr_element_shape(e, sh) :- expr_type(e, _, qt), + index_signature_in_scope(qt, f), + field_shape(f, sh). +expr_element_shape(e, sh) :- expr_shape(e, qs), + index_signature_in_scope(qs, f), + field_shape(f, sh). // ── `new C()` — the constructed type ─────────────────────────────────────── expr_type(e, prov, t) :- expr_kind("client", "NEW_EXPRESSION", _, e), diff --git a/graph/typescript/engine/resolution/type-resolution.dl b/graph/typescript/engine/resolution/type-resolution.dl index 7f4b94038..2c8e5ef20 100644 --- a/graph/typescript/engine/resolution/type-resolution.dl +++ b/graph/typescript/engine/resolution/type-resolution.dl @@ -615,6 +615,15 @@ ref_element_target(ref, prov, t) :- type_ref(rp, "TYPE_REFERENCE", _, tn, _, _, ref_name_is_container(tn), type_ref_parent(rp, ref, "0", child), ref_type_target(child, prov, t). +// `Record` read by key — `makers[kind](n)` — yields V, which is type argument 1, +// not 0, so it is kept off the container list: iterating a Record does not compile, and +// the list's promise is argument 0. +ref_element_target(ref, prov, t) :- type_ref(rp, "TYPE_REFERENCE", _, "Record", _, _, _, ref), + type_ref_parent(rp, ref, "1", child), + ref_type_target(child, prov, t). +ref_element_shape(ref, sh) :- type_ref(rp, "TYPE_REFERENCE", _, "Record", _, _, _, ref), + type_ref_parent(rp, ref, "1", child), + ref_shape_target(child, sh). ref_name_is_container("Array"). ref_name_is_container("ReadonlyArray"). ref_name_is_container("Iterable"). diff --git a/parser/src/parsers/typescript/extractors/ts-declaration-extractor.ts b/parser/src/parsers/typescript/extractors/ts-declaration-extractor.ts index 092f6f49a..4a816c7ff 100644 --- a/parser/src/parsers/typescript/extractors/ts-declaration-extractor.ts +++ b/parser/src/parsers/typescript/extractors/ts-declaration-extractor.ts @@ -263,6 +263,14 @@ export class TsDeclarationExtractor { */ private readonly pendingMemberTypeLinks: { row: TsFieldRegistry; node: ts.TypeNode }[] = []; + /** + * Parameters of a function type whose declared type the enclosing tree emits. + * + * The same shape again: `(x: T) => T` had parameter rows with the type only as + * text, so `pick(u)` through it could not bind T from the argument it was passed. + */ + private readonly pendingParameterTypeLinks: { row: TsMethodParameterRegistry; node: ts.TypeNode }[] = []; + /** * Object-literal members awaiting the hash of the literal that owns them. * @@ -1887,6 +1895,8 @@ export class TsDeclarationExtractor { tsModuleLinkHash: context.moduleHash, }) ); + } else if (parameter.type) { + this.pendingParameterTypeLinks.push({ row, node: parameter.type }); } if (parameter.initializer) { this.pendingExpressionLinks.push({ @@ -2524,6 +2534,12 @@ export class TsDeclarationExtractor { pending.row.setTypeReferenceLinkHash(hash); } } + for (const pending of this.pendingParameterTypeLinks) { + const hash = this.typeReferenceExtractor.hashForTypeNode(pending.node); + if (hash !== '') { + pending.row.setTypeReferenceLinkHash(hash); + } + } for (const pending of this.pendingConstraintLinks) { const hash = this.typeReferenceExtractor.hashForTypeNode(pending.node); if (hash !== '') { diff --git a/parser/src/parsers/typescript/extractors/ts-type-reference-extractor.ts b/parser/src/parsers/typescript/extractors/ts-type-reference-extractor.ts index c69103e41..d355a2390 100644 --- a/parser/src/parsers/typescript/extractors/ts-type-reference-extractor.ts +++ b/parser/src/parsers/typescript/extractors/ts-type-reference-extractor.ts @@ -50,6 +50,8 @@ interface PlannedChild { readonly context: TsTypeRefContext; readonly isOptionalElement?: boolean; readonly isRestElement?: boolean; + /** Type parameters in scope for this child only: a type-literal signature's own ``. */ + readonly introduces?: readonly ts.TypeParameterDeclaration[]; } export class TsTypeReferenceExtractor { @@ -321,6 +323,11 @@ export class TsTypeReferenceExtractor { introduced.push(node.typeParameter); } else if (ts.isConditionalTypeNode(node)) { collectInferParameters(node.extendsType, introduced); + } else if (ts.isFunctionTypeNode(node) || ts.isConstructorTypeNode(node)) { + // `() => T`: the signature's own parameters scope its parameters and its + // return. Without this the return `T` was a TYPE_REFERENCE to nothing, so + // `get()` through a parameter of that type bound T to nothing. + introduced.push(...(node.typeParameters ?? [])); } for (const declared of introduced) { this.typeLevelParameterScope.push(declared); @@ -328,11 +335,21 @@ export class TsTypeReferenceExtractor { try { let index = 0; for (const child of children) { - this.emit(child.node, child.context, owner, row.getHash(), index, depth + 1, - isTypeOnlyPosition, { - isOptionalElement: child.isOptionalElement, - isRestElement: child.isRestElement, - }); + const own = child.introduces ?? []; + for (const declared of own) { + this.typeLevelParameterScope.push(declared); + } + try { + this.emit(child.node, child.context, owner, row.getHash(), index, depth + 1, + isTypeOnlyPosition, { + isOptionalElement: child.isOptionalElement, + isRestElement: child.isRestElement, + }); + } finally { + for (let i = 0; i < own.length; i += 1) { + this.typeLevelParameterScope.pop(); + } + } index += 1; } } finally { @@ -356,7 +373,7 @@ export class TsTypeReferenceExtractor { function plannedChildren(node: ts.TypeNode): PlannedChild[] { const out: PlannedChild[] = []; const push = (child: ts.TypeNode | undefined, context: TsTypeRefContext, - extra?: { isOptionalElement?: boolean; isRestElement?: boolean }): void => { + extra?: Omit): void => { if (child) { out.push({ node: child, context, ...extra }); } @@ -459,6 +476,9 @@ function plannedChildren(node: ts.TypeNode): PlannedChild[] { const memberType = (member as { type?: ts.TypeNode }).type; push(memberType, contextForTypeElement(member), { isOptionalElement: (member as { questionToken?: ts.QuestionToken }).questionToken !== undefined, + // `{ get(): T }`: the member's `T` is in scope for its return, as a function type's is. + introduces: ts.isMethodSignature(member) || ts.isCallSignatureDeclaration(member) + || ts.isConstructSignatureDeclaration(member) ? member.typeParameters : undefined, }); } return out;