Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// The case's own standard library under `noLib`.
interface Object {}
interface Boolean {}
interface Number {}
interface String {}
interface Array<T> { length: number; }
interface Function {}
interface CallableFunction extends Function {}
interface NewableFunction extends Function {}
interface IArguments {}
interface RegExp {}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{ "compilerOptions": { "strict": true, "noLib": true, "target": "es2022" } }
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// A name destructured out of a value whose declared type is an object TYPE LITERAL
// (inline, or through an alias) takes that member's type, as `opts.port.execute()` does.
export interface Port { execute(x: number): number }
export interface Clock { now(): number }

export function inlineVar(opts: { port: Port; n: number }) {
const { port } = opts;
return port.execute(1); // -> Port.execute
}
export function memberAccess(opts: { port: Port }) {
return opts.port.execute(2); // -> Port.execute (already resolved: the reference)
}
type Opts = { port: Port; clock: Clock };
export function aliasParam({ port, clock }: Opts) {
clock.now(); // -> Clock.now
return port.execute(3); // -> Port.execute
}
export function inlineParam({ port }: { port: Port }) {
return port.execute(4); // -> Port.execute
}
export function aliasVar(o: Opts) {
const { clock } = o;
return clock.now(); // -> Clock.now
}
export function nestedShape(opts: { deps: { port: Port } }) {
const { deps } = opts;
const { port } = deps;
return port.execute(5); // -> Port.execute
}
export function shapeMethod(opts: { run(x: number): number }) {
const { run } = opts;
return run(6); // -> the type literal's run
}

// controls: no declared type, and a name the shape does not have
export function untyped(opts: any) {
const { port } = opts;
return port.execute(7); // stays unresolved
}
export function absent(opts: { clock: Clock }) {
const { port } = opts as { clock: Clock; port?: never };
return port; // no call
}
export function otherShape(opts: { port: { execute(x: number): number } }) {
const { port } = opts;
return port.execute(8); // -> its own literal's execute, NOT Port.execute
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
ambiguous_unknown METHOD_CALL use#untyped(any) @L38 -> -
known_edge FUNCTION_CALL use#shapeMethod({ run(x: number): number }) @L32 -> use#run(number)
known_edge METHOD_CALL use#aliasParam(Opts) @L15 -> Clock#now()
known_edge METHOD_CALL use#aliasParam(Opts) @L16 -> Port#execute(number)
known_edge METHOD_CALL use#aliasVar(Opts) @L23 -> Clock#now()
known_edge METHOD_CALL use#inlineParam({ port: Port }) @L19 -> Port#execute(number)
known_edge METHOD_CALL use#inlineVar({ port: Port; n: number }) @L8 -> Port#execute(number)
known_edge METHOD_CALL use#memberAccess({ port: Port }) @L11 -> Port#execute(number)
known_edge METHOD_CALL use#nestedShape({ deps: { port: Port } }) @L28 -> Port#execute(number)
known_edge METHOD_CALL use#otherShape({ port: { execute(x: number): number } }) @L46 -> use#execute(number)
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
── entry_point (12) ──
exported_from_entry_module use#absent use.ts:40
exported_from_entry_module use#aliasParam use.ts:14
exported_from_entry_module use#aliasVar use.ts:21
exported_from_entry_module use#inlineParam use.ts:18
exported_from_entry_module use#inlineVar use.ts:6
exported_from_entry_module use#memberAccess use.ts:10
exported_from_entry_module use#nestedShape use.ts:25
exported_from_entry_module use#otherShape use.ts:44
exported_from_entry_module use#shapeMethod use.ts:30
exported_from_entry_module use#untyped use.ts:36
unimported_module globals#<module> globals.d.ts:1
unimported_module use#<module> use.ts:1
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
known_edge read use#memberAccess({ port: Port }) -> { port: Port }#port
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
100-destructure-from-type-literal [fields]
precision 0.0000 (0 correct, 1 wrong)
recall 0.0000 (0 of 1 the compiler resolved)
sites 1 resolved 1 (100.0%)
tiers known_edge=1
access read=1
not scored: 0 rows whose target is not a client declaration
WRONG use#memberAccess({ port: Port }) READ { port: Port }#port
MISSING use#memberAccess({ port: Port }) READ use#port
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
oracle=9 engine=9 agree=9 missing=0 (known 0, NEW 0) extra=0
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
known_edge FIELD_TYPE 1 Opts [TYPE] -> Clock
known_edge FIELD_TYPE 1 Opts [TYPE] -> Port
known_edge FIELD_TYPE 1 use [EXPRESSION] -> Clock
known_edge FIELD_TYPE 1 use [METHOD_PARAM] -> Clock
known_edge FIELD_TYPE 1 use [METHOD_PARAM] -> Port
known_edge FIELD_TYPE 2 use [METHOD_PARAM] -> Port
known_edge METHOD_PARAM 0 use [METHOD_PARAM] -> Opts
known_edge SUPER_TYPE 0 CallableFunction [HERITAGE] -> Function
known_edge SUPER_TYPE 0 NewableFunction [HERITAGE] -> Function
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
100-destructure-from-type-literal [types]
precision 1.0000 (7 correct, 0 wrong)
recall 1.0000 (7 of 7 the compiler resolved)
sites 12 resolved 12 (100.0%)
tiers known_edge=12
contexts FIELD_TYPE=8 METHOD_PARAM=2 SUPER_TYPE=2
not scored: 0 rows whose target is not a client declaration
12 changes: 6 additions & 6 deletions graph/test/typescript/expected/75-jsx-wrapper-guards.edges
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,9 @@ ambient_terminal FUNCTION_CALL app#<module-init>() @L22 -> parts#dynamic(() =Fc
ambient_terminal FUNCTION_CALL app#<module-init>() @L29 -> parts#memo(Fc)
ambient_terminal FUNCTION_CALL app#<module-init>() @L30 -> parts#observer(Fc)
ambiguous_unknown FUNCTION_CALL app#MemoInner() @L29 -> -
ambiguous_unknown FUNCTION_CALL app#RenameCall({ run: () =,{ run: () =) @L67 -> -
ambiguous_unknown FUNCTION_CALL app#ShorthandCall({ Button: () =,{ Button: () =) @L64 -> -
ambiguous_unknown FUNCTION_CALL heavy#Heavy() @L11 -> -
ambiguous_unknown FUNCTION_CALL heavy#NamedHeavy() @L15 -> -
ambiguous_unknown JSX_COMPONENT_CALL app#<arrow@57>(?,?) @L57 -> -
ambiguous_unknown JSX_COMPONENT_CALL app#Page() @L42 -> -
ambiguous_unknown JSX_COMPONENT_CALL app#RenameTag({ icon: Fc },{ icon: Fc }) @L54 -> -
ambiguous_unknown JSX_COMPONENT_CALL app#ShorthandTag({ Button: Fc },{ Button: Fc }) @L51 -> -
ambiguous_unknown METHOD_CALL app#<arrow@15>() @L15 -> -
ambiguous_unknown METHOD_CALL app#<arrow@16>() @L16 -> -
ambiguous_unknown METHOD_CALL app#<arrow@18>() @L18 -> -
Expand All @@ -30,7 +25,7 @@ callback_registered FUNCTION_CALL app#<module-init>() @L30 -> app#ObservedImpl()
callback_registered METHOD_CALL app#<arrow@15>() @L15 -> app#<arrow@15>(?)
callback_registered METHOD_CALL app#<arrow@16>() @L16 -> app#<arrow@16>(?)
callback_registered METHOD_CALL app#<arrow@18>() @L18 -> app#<arrow@18>(?)
callback_registered METHOD_CALL app#ArrowRenameTag({ icon: Fc }[]) @L57 -> app#<arrow@57>(?,?)
callback_registered METHOD_CALL app#ArrowRenameTag({ icon: Fc }[]) @L57 -> app#<arrow@57>(?)
intrinsic_terminal DYNAMIC_IMPORT_CALL app#<arrow@15>() @L15 -> -
intrinsic_terminal DYNAMIC_IMPORT_CALL app#<arrow@16>() @L16 -> -
intrinsic_terminal DYNAMIC_IMPORT_CALL app#<arrow@18>() @L18 -> -
Expand All @@ -41,10 +36,13 @@ known_edge FUNCTION_CALL app#<arrow@25>() @L25 -> app#factoryArgBody()
known_edge FUNCTION_CALL app#<module-init>() @L25 -> parts#makeThing(() =)
known_edge FUNCTION_CALL app#<module-init>() @L26 -> parts#useMemoLike(() =,unknown[])
known_edge FUNCTION_CALL app#MemoInner() @L29 -> app#memoBody()
known_edge FUNCTION_CALL app#RenameCall({ run: () =) @L67 -> app#<arrow@66>()
known_edge FUNCTION_CALL app#ShorthandCall({ Button: () =) @L64 -> app#<arrow@63>()
known_edge FUNCTION_CALL heavy#Heavy() @L11 -> heavy#heavyWork()
known_edge FUNCTION_CALL heavy#NamedHeavy() @L15 -> heavy#namedWork()
known_edge FUNCTION_CALL parts#makeThing(() =) @L23 -> parts#<arrow@22>()
known_edge FUNCTION_CALL parts#useMemoLike(() =,unknown[]) @L27 -> parts#<arrow@26>()
known_edge JSX_COMPONENT_CALL app#<arrow@57>(?) @L57 -> parts#<arrow@15>(T)
known_edge JSX_COMPONENT_CALL app#Page() @L35 -> heavy#NamedHeavy()
known_edge JSX_COMPONENT_CALL app#Page() @L36 -> heavy#NamedHeavy()
known_edge JSX_COMPONENT_CALL app#Page() @L37 -> heavy#NamedHeavy()
Expand All @@ -55,4 +53,6 @@ known_edge JSX_COMPONENT_CALL app#Page() @L41 -> parts#<arrow@15>(T)
known_edge JSX_COMPONENT_CALL app#Page() @L43 -> app#MemoInner()
known_edge JSX_COMPONENT_CALL app#Page() @L44 -> app#ObservedImpl()
known_edge JSX_COMPONENT_CALL app#PlainParamTag(Fc) @L60 -> parts#<arrow@15>(T)
known_edge JSX_COMPONENT_CALL app#RenameTag({ icon: Fc }) @L54 -> parts#<arrow@15>(T)
known_edge JSX_COMPONENT_CALL app#ShorthandTag({ Button: Fc }) @L51 -> parts#<arrow@15>(T)
known_edge JSX_COMPONENT_CALL app#UsesButton() @L72 -> ui/Button#Button({ label: string })
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,6 @@
# resolves and this engine does not. A NEW missing edge fails the suite;
# a line here that STARTS working also fails, so the debt cannot rot.
#
# A name bound by a DESTRUCTURED parameter carries no declared type the engine reads, so
# the call through it stays unresolved. The compiler names the prop's type signature.
# Before this case these sites resolved to the same-named IMPORT (a wrong edge); unresolved
# is the honest answer until destructured parameters are typed.
app#<arrow@57>(?) -> parts#<arrow@15>(T)
app#RenameTag({ icon: Fc }) -> parts#<arrow@15>(T)
app#ShorthandTag({ Button: Fc }) -> parts#<arrow@15>(T)
app#RenameCall({ run: () =) -> app#<arrow@66>()
app#ShorthandCall({ Button: () =) -> app#<arrow@63>()
# `useMemoLike(() => function MadeInner…)` returns what its argument RETURNS, through a
# generic T. Argument 0 is not a component wrapper, so the tag no longer lands on the
# argument arrow; following the generic return to MadeInner is value flow the engine
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
oracle=21 engine=19 agree=15 missing=6 (known 6, NEW 0) extra=4
known app#<arrow@57>(?) -> parts#<arrow@15>(T)
oracle=21 engine=24 agree=20 missing=1 (known 1, NEW 0) extra=4
known app#Page() -> app#MadeInner()
known app#RenameCall({ run: () =) -> app#<arrow@66>()
known app#RenameTag({ icon: Fc }) -> parts#<arrow@15>(T)
known app#ShorthandCall({ Button: () =) -> app#<arrow@63>()
known app#ShorthandTag({ Button: Fc }) -> parts#<arrow@15>(T)
extra app#Page() -> app#MemoInner()
extra app#Page() -> app#ObservedImpl()
extra app#Page() -> heavy#Heavy()
Expand Down
10 changes: 9 additions & 1 deletion graph/test/typescript/tools/normalize_edges.py
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,15 @@ def enclosing_owner(type_hash):
for r in rows(f'{ir}/all-typescript-methods.csv'):
h = r['tsMethodUniqueHash']
ps = []
for p in sorted(params.get(h, []), key=lambda x: int(x.get('position') or 0)):
# A destructured parameter has a row for the pattern and one per name it binds, all at the pattern's
# position (bindingSourceKind PROPERTY/INDEX): one parameter, labelled once, as the compiler prints it.
by_pos = {}
for p in params.get(h, []):
pos = int(p.get('position') or 0)
bound = (p.get('bindingSourceKind') or 'NONE') != 'NONE'
if pos not in by_pos or (by_pos[pos][0] and not bound):
by_pos[pos] = (bound, p)
for _, p in sorted((pos, bp[1]) for pos, bp in by_pos.items()):
if p.get('isReceiverParameter') == 'true':
continue
t = simple(p.get('parameterTypeName') or p.get('parameterBaseType'))
Expand Down
43 changes: 43 additions & 0 deletions graph/typescript/engine/resolution/reference-types.dl
Original file line number Diff line number Diff line change
Expand Up @@ -494,6 +494,28 @@ param_binding_method(bh, m2) :- param_binding(_, _, _, "PROPERTY", src, _, bh),
scope_sibling(t, sib),
declared_method(sib, src, _, m2).

// (c) the pattern is typed by an OBJECT TYPE LITERAL — `({ port }: { port: Port })`, or
// `({ port }: Opts)` with `type Opts = { port: Port }`. A type literal has no declaration
// to give param_type; it is a SHAPE (param_shape_target), and a shape is its own scope
// (scope_sibling(s, s)), so the field and method lookups above read off it unchanged.
binding_pattern_shape(bh, sh) :- param_binding(_, _, pos, "PROPERTY", _, m, bh),
param_decl(_, _, pos, _, m, ph),
param_shape_target(ph, sh).
param_type(bh, prov, ft) :- param_binding(_, _, _, "PROPERTY", src, _, bh),
binding_pattern_shape(bh, sh),
scope_sibling(sh, sib),
declared_field(sib, src, _, f),
field_type(f, prov, ft).
param_shape_target(bh, fs) :- param_binding(_, _, _, "PROPERTY", src, _, bh),
binding_pattern_shape(bh, sh),
scope_sibling(sh, sib),
declared_field(sib, src, _, f),
field_shape(f, fs).
param_binding_method(bh, m2) :- param_binding(_, _, _, "PROPERTY", src, _, bh),
binding_pattern_shape(bh, sh),
scope_sibling(sh, sib),
declared_method(sib, src, _, m2).


// ── a DESTRUCTURED VARIABLE takes the type of the member it names ───────────
// `const { helper, nested } = ctx` — the mirror of the parameter case, and the more
Expand Down Expand Up @@ -527,6 +549,27 @@ var_binding_method(v, m) :- var_binding("client", _, "PROPERTY", src, _, v),
scope_sibling(t, sib),
declared_method(sib, src, _, m).

// (c) the initializer has a SHAPE, not a type — `const { port } = opts` with
// `opts: { port: Port }`. `opts.port.execute()` already resolved through the shape
// (expr_shape off a shape qualifier); the destructured name beside it did not.
var_binding_shape(v, sh) :- var_binding("client", _, "PROPERTY", _, e, v),
e != "",
expr_shape(e, sh).
var_type(v, prov, ft) :- var_binding("client", _, "PROPERTY", src, _, v),
var_binding_shape(v, sh),
scope_sibling(sh, sib),
declared_field(sib, src, _, f),
field_type(f, prov, ft).
var_shape(v, fs) :- var_binding("client", _, "PROPERTY", src, _, v),
var_binding_shape(v, sh),
scope_sibling(sh, sib),
declared_field(sib, src, _, f),
field_shape(f, fs).
var_binding_method(v, m) :- var_binding("client", _, "PROPERTY", src, _, v),
var_binding_shape(v, sh),
scope_sibling(sh, sib),
declared_method(sib, src, _, m).


// ── an INDEX-DESTRUCTURED VARIABLE takes the ELEMENT type of its source ─────
// `const [first] = ops` and `const [a, b] = makePair()`. The parser has emitted these
Expand Down
2 changes: 2 additions & 0 deletions graph/typescript/souffle/decls_all.dl
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,7 @@
.decl owner_kind_is_module_level(c0:symbol)
.decl owner_name_has_overload_signature(c0:symbol,c1:symbol)
.decl param_call_signature(c0:symbol,c1:symbol)
.decl binding_pattern_shape(c0:symbol,c1:symbol)
.decl binding_pattern_type(c0:symbol,c1:symbol,c2:symbol)
.decl param_binding_method(c0:symbol,c1:symbol)
.decl param_binding(c0:symbol,c1:symbol,c2:symbol,c3:symbol,c4:symbol,c5:symbol,c6:symbol)
Expand Down Expand Up @@ -641,6 +642,7 @@
.decl library_receiver(c0:symbol,c1:symbol)
.decl unresolved_receiver(c0:symbol,c1:symbol,c2:symbol,c3:symbol)
.decl var_binding_method(c0:symbol,c1:symbol)
.decl var_binding_shape(c0:symbol,c1:symbol)
.decl var_binding_target(c0:symbol,c1:symbol,c2:symbol)
.decl var_binding(c0:symbol,c1:symbol,c2:symbol,c3:symbol,c4:symbol,c5:symbol)
.decl var_is_destructured(c0:symbol)
Expand Down
Loading