Skip to content
Open
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,35 @@
// The case's OWN standard library, for the reason 21 gives: under `noLib` the target has
// to be declared HERE or neither side of the comparison can name it.
//
// Every signature below is copied from lib.es5.d.ts / lib.es2015.promise.d.ts as written,
// because the shape of the callback parameter IS the case: `then` takes a UNION of a
// function type with `undefined | null`, and `map` declares its own `U` next to Array's `T`.
interface Array<T> {
length: number;
forEach(callbackfn: (value: T, index: number, array: T[]) => void, thisArg?: any): void;
map<U>(callbackfn: (value: T, index: number, array: T[]) => U, thisArg?: any): U[];
filter(predicate: (value: T, index: number, array: T[]) => unknown, thisArg?: any): T[];
some(predicate: (value: T, index: number, array: T[]) => unknown, thisArg?: any): boolean;
}
interface ReadonlyArray<T> {
readonly length: number;
forEach(callbackfn: (value: T, index: number, array: readonly T[]) => void, thisArg?: any): void;
map<U>(callbackfn: (value: T, index: number, array: readonly T[]) => U, thisArg?: any): U[];
some(predicate: (value: T, index: number, array: readonly T[]) => unknown, thisArg?: any): boolean;
}
interface PromiseLike<T> {
then<TResult1 = T, TResult2 = never>(onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null): PromiseLike<TResult1 | TResult2>;
}
interface Promise<T> {
then<TResult1 = T, TResult2 = never>(onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null): Promise<TResult1 | TResult2>;
catch<TResult = never>(onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | undefined | null): Promise<T | TResult>;
}
interface Object { toString(): string; }
interface Boolean {}
interface Number {}
interface String {}
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", "module": "commonjs" } }
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// A parameter destructured where its type comes from the context, not an annotation.
export interface Utils {
send(url: string): number;
sleep(ms: number): void;
}
export interface Ctx {
url: string;
utils: Utils;
}

// typed by the const's function-type annotation: `utils.send` is Utils.send
export const viaConst: (ctx: Ctx) => number = ({ url, utils }) => {
return utils.send(url);
};

// the same arrow with a plain parameter (already resolved; must not change)
export const plainConst: (ctx: Ctx) => number = (ctx) => {
return ctx.utils.send(ctx.url);
};

// an annotated pattern (already resolved; must not change)
export function annotated({ url, utils }: Ctx): number {
return utils.send(url);
}

// control: a destructured parameter with no type anywhere stays unresolved
export const untyped = ({ url, utils }: any) => {
return utils.send(url);
};

// control: a local object of another type with a member of the same name
export interface Other { send(url: string): string }
export const otherConst: (o: { utils: Other; url: string }) => string = ({ url, utils }) => {
return utils.send(url);
};

// written in a slot of a nested literal whose type comes from the call's parameter
type Slots = { start?: (ctx: Ctx) => number };
export function define(seed: { name: string } & { lifecycle?: Slots }) { return seed; }
export const op = define({
name: "op",
lifecycle: {
start: ({ url, utils }) => utils.send(url),
},
});
export const op2 = define({
name: "op2",
lifecycle: {
start: (ctx) => ctx.utils.send(ctx.url),
},
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
ambiguous_unknown METHOD_CALL use#otherConst(?,?,?) @L34 -> -
ambiguous_unknown METHOD_CALL use#untyped(any,any,any) @L28 -> -
known_edge FUNCTION_CALL use#<module-init>() @L40 -> use#define({ name: string } & { lifecycle?: Slots })
known_edge FUNCTION_CALL use#<module-init>() @L46 -> use#define({ name: string } & { lifecycle?: Slots })
known_edge METHOD_CALL use#<arrow@43>(?,?,?) @L43 -> Utils#send(string)
known_edge METHOD_CALL use#<arrow@49>(?) @L49 -> Utils#send(string)
known_edge METHOD_CALL use#annotated(Ctx,Ctx,Ctx) @L23 -> Utils#send(string)
known_edge METHOD_CALL use#plainConst(?) @L18 -> Utils#send(string)
known_edge METHOD_CALL use#viaConst(?,?,?) @L13 -> Utils#send(string)
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
── entry_point (4) ──
exported_from_entry_module use#annotated use.ts:22
exported_from_entry_module use#define use.ts:39
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,5 @@
value use#<function-type>@12:24 -> use#<arrow>@12
value use#<function-type>@17:26 -> use#<arrow>@17
value use#<function-type>@33:26 -> use#<arrow>@33
value use#<function-type>@38:24 -> use#<arrow>@43
value use#<function-type>@38:24 -> use#<arrow>@49
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
known_edge read use#<arrow@49>(?) -> Ctx#url
known_edge read use#<arrow@49>(?) -> Ctx#utils
known_edge read use#plainConst(?) -> Ctx#url
known_edge read use#plainConst(?) -> Ctx#utils
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
99-contextual-destructured-param [fields]
precision 1.0000 (4 correct, 0 wrong)
recall 1.0000 (4 of 4 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Accepted gaps for 99-contextual-destructured-param — 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 engine DOES resolve these three (they show as `extra` with the same target): the edge differs
# only in its caller's label. A function whose parameter is destructured is labelled with one slot
# per bound name as well as the pattern, `(?,?,?)` for `({ url, utils })`, where the compiler names
# one parameter. `annotated` had this before contextual typing was added; it is the label, not the edge.
use#<arrow@43>(?) -> Utils#send(string)
use#annotated(Ctx) -> Utils#send(string)
use#viaConst(?) -> Utils#send(string)
# A parameter typed by an object type LITERAL ({ utils: Other }) has a shape, not a type
# declaration, and a bound name reads only a declaration's fields. Not covered yet.
use#otherConst(?) -> Other#send(string)
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
oracle=7 engine=6 agree=3 missing=4 (known 4, NEW 0) extra=3
known use#<arrow@43>(?) -> Utils#send(string)
known use#annotated(Ctx) -> Utils#send(string)
known use#otherConst(?) -> Other#send(string)
known use#viaConst(?) -> Utils#send(string)
extra use#<arrow@43>(?,?,?) -> Utils#send(string)
extra use#annotated(Ctx,Ctx,Ctx) -> Utils#send(string)
extra use#viaConst(?,?,?) -> Utils#send(string)
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
known_edge FIELD_TYPE 0 Ctx [FIELD] -> Utils
known_edge FIELD_TYPE 2 use [METHOD_PARAM] -> Slots
known_edge FIELD_TYPE 2 use [VARIABLE] -> Other
known_edge METHOD_PARAM 0 use [METHOD_PARAM] -> Ctx
known_edge METHOD_PARAM 1 use [VARIABLE] -> Ctx
known_edge METHOD_PARAM 2 Slots [TYPE] -> Ctx
known_edge METHOD_RETURN 0 Promise [METHOD] -> Promise
known_edge METHOD_RETURN 0 PromiseLike [METHOD] -> PromiseLike
known_edge SUPER_TYPE 0 CallableFunction [HERITAGE] -> Function
known_edge SUPER_TYPE 0 NewableFunction [HERITAGE] -> Function
known_edge TYPE_ELEMENT 4 Promise [METHOD_PARAM] -> PromiseLike
known_edge TYPE_ELEMENT 4 PromiseLike [METHOD_PARAM] -> PromiseLike
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
99-contextual-destructured-param [types]
precision 1.0000 (10 correct, 0 wrong)
recall 1.0000 (10 of 10 the compiler resolved)
sites 17 resolved 17 (100.0%)
tiers known_edge=17
contexts FIELD_TYPE=3 METHOD_PARAM=4 METHOD_RETURN=3 SUPER_TYPE=2 TYPE_ELEMENT=5
not scored: 0 rows whose target is not a client declaration
36 changes: 36 additions & 0 deletions graph/typescript/engine/resolution/contextual-params.dl
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,33 @@ expr_decl_ref(e, c) :- expr_referenced("client", "PARAMETER", p, e),
expr_type(e, prov, t) :- expr_referenced("client", "PARAMETER", p, e),
lambda_param_type(p, prov, t).

// ── …AND THE NAMES A CONTEXTUALLY TYPED PARAMETER IS DESTRUCTURED INTO ──────
// `const poll: (ctx: TickCtx) => R = async ({ data, utils }) => utils.http(..)`, and the same
// arrow written where an interface member or a function-typed field is expected. The pattern
// parameter takes its type from that context above, but a bound name reads the pattern's type
// through param_type (reference-types.dl, binding_pattern_type), which only an annotation fills:
// so `utils` stayed untyped and every call on it was unresolved, while `ctx.utils.http(..)` on
// the same arrow resolved. The bound name takes the type of the field it names, exactly as for
// an annotated pattern; its uses are typed here, beside the plain parameter's, and not through
// param_type, which overload rejection reads under negation.
lambda_binding_target(bh, prov, t) :- param_binding(_, _, pos, "PROPERTY", _, m, bh),
param_decl(_, _, pos, _, m, ph),
lambda_param_type(ph, prov, t).
lambda_binding_type(bh, prov, ft) :- param_binding(_, _, _, "PROPERTY", src, _, bh),
lambda_binding_target(bh, _, t),
scope_sibling(t, sib),
declared_field(sib, src, _, f),
field_type(f, prov, ft).
lambda_binding_shape(bh, sh) :- param_binding(_, _, _, "PROPERTY", src, _, bh),
lambda_binding_target(bh, _, t),
scope_sibling(t, sib),
declared_field(sib, src, _, f),
field_shape(f, sh).
expr_type(e, prov, t) :- expr_referenced("client", "PARAMETER", bh, e),
lambda_binding_type(bh, prov, t).
expr_shape(e, sh) :- expr_referenced("client", "PARAMETER", bh, e),
lambda_binding_shape(bh, sh).

// ── …AND A CALLBACK THAT RECEIVES A MODULE NAMESPACE ────────────────────────
// `import("./m").then((m) => m.run(1))` — the same namespace as the awaited form, only
// reached through a callback. `await` is not required for a dynamic import to be
Expand Down Expand Up @@ -653,6 +680,15 @@ objlit_ctx_ref(l, r) :- expr_child("client", ce, "ARGUMENT", pos, l),
param_type_ref(_, r, p).
objlit_ctx_scope(l, s) :- objlit_ctx_ref(l, r), ref_type_target(r, _, s).
objlit_ctx_scope(l, s) :- objlit_ctx_ref(l, r), ref_shape_target(r, s).
// …and a literal written as a PROPERTY of a typed literal takes the type of the field it fills:
// `define({ lifecycle: { start: async (ctx) => .. } })`, where the parameter is
// `{ lifecycle?: Slots }`. The outer literal had a scope and the inner one none, so every
// function in a nested slot (a config's hooks, a router's handlers) took no parameter types.
objlit_ctx_ref(l, r) :- objlit_prop(p, name, l),
expr_kind("client", "OBJECT_LITERAL", _, l),
objlit_ctx_scope(p, s),
field_in_scope(s, name, _, f),
field_type_ref(_, r, f).

// a property is a KEY and a VALUE at the same position under the literal; a method is its own row
objlit_prop(l, name, val) :- expr_child("client", l, "OBJECT_PROPERTY_KEY", pos, key),
Expand Down
3 changes: 3 additions & 0 deletions graph/typescript/souffle/decls_all.dl
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@
.decl builtin_promise_ref(c0:symbol,c1:symbol)
.decl lambda_param_ref(c0:symbol,c1:symbol)
.decl fn_value_kind(c0:symbol)
.decl lambda_binding_shape(c0:symbol,c1:symbol)
.decl lambda_binding_target(c0:symbol,c1:symbol,c2:symbol)
.decl lambda_binding_type(c0:symbol,c1:symbol,c2:symbol)
.decl lambda_param_shape(c0:symbol,c1:symbol)
.decl lambda_param_namespace(c0:symbol,c1:symbol,c2:symbol)
.decl lambda_param_type(c0:symbol,c1:symbol,c2:symbol)
Expand Down
Loading