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,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" } }
97 changes: 97 additions & 0 deletions graph/test/typescript/cases/80-contextual-lambda-param/src/use.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// A callback parameter with no annotation takes its type from the signature it is passed to:
// `Array<T>.map((value: T) => U)`, `forEach`, `Promise<T>.then((value: T) => …)`. Two classes
// share every member name, so a name match cannot pass for a resolution — the owner is the answer.
//
// Each shape is written twice, `(u) => …` and `u => …`, because a parameter written without
// parentheses is the commoner spelling and the two must type alike.
export class User {
display(): string {
return 'd';
}
save(): string {
return 's';
}
touch(): string {
return 't';
}
}
export class Admin {
display(): string {
return 'D';
}
save(): string {
return 'S';
}
touch(): string {
return 'T';
}
}

export class Repo {
constructor(private readonly pending: Promise<User>, private readonly users: User[]) {}
all(): User[] {
return this.users;
}
one(): Promise<User> {
return this.pending;
}
readonlyAll(): readonly User[] {
return this.users;
}
}

export interface Handler {
handle(n: number): number;
}

// ── map: the method declares its own `U` beside Array's `T` ─────────────────
export function mapParen(r: Repo): string[] {
return r.all().map((u) => u.display());
}
export function mapBare(r: Repo): string[] {
return r.all().map(u => u.display());
}
export function mapDeclared(hs: Handler[]): number[] {
return hs.map(h => h.handle(1));
}

// ── forEach / filter / some: no type parameter of their own ───────────────
export function forEachParen(r: Repo): void {
r.all().forEach((u) => {
u.save();
});
}
export function forEachBare(r: Repo): void {
r.all().forEach(u => {
u.save();
});
}
export function filterBare(r: Repo): User[] {
return r.all().filter(u => u.save() !== '');
}
export function someReadonly(r: Repo): boolean {
return r.readonlyAll().some(u => u.touch() === 't');
}

// ── then: the callback's parameter type is `((value: T) => …) | undefined | null` ──
export function thenParen(r: Repo): Promise<string> {
return r.one().then((u) => u.touch());
}
export function thenBare(r: Repo): Promise<string> {
return r.one().then(u => u.touch());
}
export function thenDeclared(p: Promise<Admin>): Promise<string> {
return p.then(a => a.touch());
}

// ── CONTROLS: these must keep resolving, and only to their own owner ────────
export function ctlAnnotated(r: Repo): string[] {
return r.all().map((u: User) => u.save());
}
export function ctlOtherOwner(as: Admin[]): void {
as.forEach((a) => a.display());
}
// `catch`'s parameter is `any`: nothing to type, so `e.touch()` must stay unresolved.
export function ctlCatchAny(r: Repo): Promise<User | string> {
return r.one().catch(e => e.touch());
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// CONTROLS: a module that declares its own `Set` and `Promise`. They are not the built-ins,
// so `forEach` / `then` callbacks take their parameter from these classes' own signatures
// (a Key), never from the type argument (an Item).
export class Item {
size = 1;
display(): string {
return 'i';
}
}
export class Key {
display(): string {
return 'k';
}
}
export class Set<T> {
forEach(cb: (k: Key) => void): void {
cb(new Key());
}
}
export class Promise<T> {
then(cb: (k: Key) => void): void {
cb(new Key());
}
}
export function ctlOwnSet(s: Set<Item>): void {
s.forEach(k => k.display());
}
export function ctlOwnPromise(p: Promise<Item>): void {
p.then(k => k.display());
}
// A union of two function types over DIFFERENT parameter types gives no contextual type
// (the compiler reports the parameter as implicitly `any`), so neither class is picked.
export function either(cb: ((i: Item) => void) | ((k: Key) => void)): void {}
export function ctlUnionOfFns(): void {
// @ts-expect-error z is implicitly any
either(z => z.display());
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{ "compilerOptions": { "strict": true, "target": "es2022", "module": "commonjs" } }
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
// 80's shapes with NO standard library in the graph, which is what a default index is:
// `map`, `forEach` and `then` have no declaration to read a callback signature from, so
// the parameter's type has to come off the receiver's own reference, the way a `for…of`
// binding over the same receiver already does. Two classes share every member name, so a
// name match cannot pass for a resolution.
export class User {
display(): string {
return 'd';
}
save(): string {
return 's';
}
touch(): string {
return 't';
}
}
export class Admin {
display(): string {
return 'D';
}
save(): string {
return 'S';
}
touch(): string {
return 'T';
}
}

export class Repo {
constructor(private readonly pending: Promise<User>, private readonly users: User[]) {}
all(): User[] {
return this.users;
}
one(): Promise<User> {
return this.pending;
}
readonlyAll(): ReadonlyArray<User> {
return this.users;
}
}

export function mapBare(r: Repo): string[] {
return r.all().map(u => u.display());
}
export function forEachParen(r: Repo): void {
r.all().forEach((u) => {
u.save();
});
}
export function findReadonly(r: Repo): User | undefined {
return r.readonlyAll().find(u => u.touch() === 't');
}
export function everyParam(as: Admin[]): boolean {
return as.every(a => a.save() !== '');
}
// `reduce`'s first parameter is the accumulator: only the second is the element.
export function reduceElement(us: User[]): string {
return us.reduce((acc, u) => acc + u.save(), '');
}
export function thenBare(r: Repo): Promise<string> {
return r.one().then(u => u.touch());
}
export function thenParam(p: Promise<Admin>): Promise<string> {
return p.then(a => a.display());
}
// Through a type alias, plain and generic.
export type Users = User[];
export type Many<T> = Array<T>;
export function aliasPlain(us: Users): void {
us.forEach(u => u.save());
}
export function aliasGeneric(as: Many<Admin>): void {
as.forEach(a => a.touch());
}
// A callback over a parameter that was itself typed from context.
export function nestedArrays(xss: Admin[][]): void {
xss.forEach(xs => {
xs.forEach(a => a.save());
});
}
export function thenThenMap(p: Promise<User[]>): void {
p.then(us => {
us.map(u => u.touch());
});
}
// `Map.forEach((value, key) => …)`: the value is type argument 1, the key argument 0.
export function mapForEach(m: Map<Admin, User>): void {
m.forEach((v, k) => {
v.display();
k.touch();
});
}

// ── CONTROLS ────────────────────────────────────────────────────────────────
// A class of the user's own with a `then` and a `map`: its own signatures type the
// callback, and nothing about Promise or Array may be added to them.
export class Lazy {
then(cb: (a: Admin) => string): string {
return cb(new Admin());
}
map(cb: (u: User) => string): string {
return cb(new User());
}
}
export function ctlOwnThen(l: Lazy): string {
return l.then(a => a.touch());
}
export function ctlOwnMap(l: Lazy): string {
return l.map(u => u.touch());
}
// An annotated parameter keeps its annotation.
export function ctlAnnotated(us: User[]): string[] {
return us.map((u: User) => u.save());
}
49 changes: 49 additions & 0 deletions graph/test/typescript/expected/80-contextual-lambda-param.edges
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
ambiguous_unknown METHOD_CALL use#<arrow@96>(?) @L96 -> -
callback_registered METHOD_CALL use#ctlAnnotated(Repo) @L89 -> use#<arrow@89>(User)
callback_registered METHOD_CALL use#ctlCatchAny(Repo) @L96 -> use#<arrow@96>(?)
callback_registered METHOD_CALL use#ctlOtherOwner(Admin[]) @L92 -> use#<arrow@92>(?)
callback_registered METHOD_CALL use#filterBare(Repo) @L70 -> use#<arrow@70>(?)
callback_registered METHOD_CALL use#forEachBare(Repo) @L65 -> use#<arrow@65>(?)
callback_registered METHOD_CALL use#forEachParen(Repo) @L60 -> use#<arrow@60>(?)
callback_registered METHOD_CALL use#mapBare(Repo) @L52 -> use#<arrow@52>(?)
callback_registered METHOD_CALL use#mapDeclared(Handler[]) @L55 -> use#<arrow@55>(?)
callback_registered METHOD_CALL use#mapParen(Repo) @L49 -> use#<arrow@49>(?)
callback_registered METHOD_CALL use#someReadonly(Repo) @L73 -> use#<arrow@73>(?)
callback_registered METHOD_CALL use#thenBare(Repo) @L81 -> use#<arrow@81>(?)
callback_registered METHOD_CALL use#thenDeclared(Promise) @L84 -> use#<arrow@84>(?)
callback_registered METHOD_CALL use#thenParen(Repo) @L78 -> use#<arrow@78>(?)
known_edge METHOD_CALL use#<arrow@49>(?) @L49 -> User#display()
known_edge METHOD_CALL use#<arrow@52>(?) @L52 -> User#display()
known_edge METHOD_CALL use#<arrow@55>(?) @L55 -> Handler#handle(number)
known_edge METHOD_CALL use#<arrow@60>(?) @L61 -> User#save()
known_edge METHOD_CALL use#<arrow@65>(?) @L66 -> User#save()
known_edge METHOD_CALL use#<arrow@70>(?) @L70 -> User#save()
known_edge METHOD_CALL use#<arrow@73>(?) @L73 -> User#touch()
known_edge METHOD_CALL use#<arrow@78>(?) @L78 -> User#touch()
known_edge METHOD_CALL use#<arrow@81>(?) @L81 -> User#touch()
known_edge METHOD_CALL use#<arrow@84>(?) @L84 -> Admin#touch()
known_edge METHOD_CALL use#<arrow@89>(User) @L89 -> User#save()
known_edge METHOD_CALL use#<arrow@92>(?) @L92 -> Admin#display()
known_edge METHOD_CALL use#ctlAnnotated(Repo) @L89 -> Array#map((value: T, index: number, array: T[]) =,any)
known_edge METHOD_CALL use#ctlAnnotated(Repo) @L89 -> Repo#all()
known_edge METHOD_CALL use#ctlCatchAny(Repo) @L96 -> Promise#catch(((reason: any) =TResult)
known_edge METHOD_CALL use#ctlCatchAny(Repo) @L96 -> Repo#one()
known_edge METHOD_CALL use#ctlOtherOwner(Admin[]) @L92 -> Array#forEach((value: T, index: number, array: T[]) =,any)
known_edge METHOD_CALL use#filterBare(Repo) @L70 -> Array#filter((value: T, index: number, array: T[]) =,any)
known_edge METHOD_CALL use#filterBare(Repo) @L70 -> Repo#all()
known_edge METHOD_CALL use#forEachBare(Repo) @L65 -> Array#forEach((value: T, index: number, array: T[]) =,any)
known_edge METHOD_CALL use#forEachBare(Repo) @L65 -> Repo#all()
known_edge METHOD_CALL use#forEachParen(Repo) @L60 -> Array#forEach((value: T, index: number, array: T[]) =,any)
known_edge METHOD_CALL use#forEachParen(Repo) @L60 -> Repo#all()
known_edge METHOD_CALL use#mapBare(Repo) @L52 -> Array#map((value: T, index: number, array: T[]) =,any)
known_edge METHOD_CALL use#mapBare(Repo) @L52 -> Repo#all()
known_edge METHOD_CALL use#mapDeclared(Handler[]) @L55 -> Array#map((value: T, index: number, array: T[]) =,any)
known_edge METHOD_CALL use#mapParen(Repo) @L49 -> Array#map((value: T, index: number, array: T[]) =,any)
known_edge METHOD_CALL use#mapParen(Repo) @L49 -> Repo#all()
known_edge METHOD_CALL use#someReadonly(Repo) @L73 -> ReadonlyArray#some((value: T, index: number, array: readonly T[]) =,any)
known_edge METHOD_CALL use#someReadonly(Repo) @L73 -> Repo#readonlyAll()
known_edge METHOD_CALL use#thenBare(Repo) @L81 -> Promise#then(((value: T) =TResult1,((reason: any) =TResult2)
known_edge METHOD_CALL use#thenBare(Repo) @L81 -> Repo#one()
known_edge METHOD_CALL use#thenDeclared(Promise) @L84 -> Promise#then(((value: T) =TResult1,((reason: any) =TResult2)
known_edge METHOD_CALL use#thenParen(Repo) @L78 -> Promise#then(((value: T) =TResult1,((reason: any) =TResult2)
known_edge METHOD_CALL use#thenParen(Repo) @L78 -> Repo#one()
16 changes: 16 additions & 0 deletions graph/test/typescript/expected/80-contextual-lambda-param.entries
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
── entry_point (15) ──
exported_from_entry_module use#ctlAnnotated use.ts:88
exported_from_entry_module use#ctlCatchAny use.ts:95
exported_from_entry_module use#ctlOtherOwner use.ts:91
exported_from_entry_module use#filterBare use.ts:69
exported_from_entry_module use#forEachBare use.ts:64
exported_from_entry_module use#forEachParen use.ts:59
exported_from_entry_module use#mapBare use.ts:51
exported_from_entry_module use#mapDeclared use.ts:54
exported_from_entry_module use#mapParen use.ts:48
exported_from_entry_module use#someReadonly use.ts:72
exported_from_entry_module use#thenBare use.ts:80
exported_from_entry_module use#thenDeclared use.ts:83
exported_from_entry_module use#thenParen use.ts:77
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,3 @@
known_edge read Repo#all() -> Repo#users
known_edge read Repo#one() -> Repo#pending
known_edge read Repo#readonlyAll() -> Repo#users
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
80-contextual-lambda-param [fields]
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
access read=3
not scored: 0 rows whose target is not a client declaration
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
oracle=35 engine=35 agree=35 missing=0 (known 0, NEW 0) extra=0
22 changes: 22 additions & 0 deletions graph/test/typescript/expected/80-contextual-lambda-param.type-use
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
known_edge METHOD_PARAM 0 Repo [METHOD_PARAM] -> Promise
known_edge METHOD_PARAM 0 use [METHOD_PARAM] -> Promise
known_edge METHOD_PARAM 0 use [METHOD_PARAM] -> Repo
known_edge METHOD_PARAM 0 use [METHOD_PARAM] -> User
known_edge METHOD_RETURN 0 Promise [METHOD] -> Promise
known_edge METHOD_RETURN 0 PromiseLike [METHOD] -> PromiseLike
known_edge METHOD_RETURN 0 Repo [METHOD] -> Promise
known_edge METHOD_RETURN 0 use [METHOD] -> Promise
known_edge SUPER_TYPE 0 CallableFunction [HERITAGE] -> Function
known_edge SUPER_TYPE 0 NewableFunction [HERITAGE] -> Function
known_edge TYPE_ARGUMENT 1 Repo [METHOD] -> User
known_edge TYPE_ARGUMENT 1 Repo [METHOD_PARAM] -> User
known_edge TYPE_ARGUMENT 1 use [METHOD_PARAM] -> Admin
known_edge TYPE_ELEMENT 1 Repo [METHOD] -> User
known_edge TYPE_ELEMENT 1 Repo [METHOD_PARAM] -> User
known_edge TYPE_ELEMENT 1 use [METHOD] -> User
known_edge TYPE_ELEMENT 1 use [METHOD_PARAM] -> Admin
known_edge TYPE_ELEMENT 1 use [METHOD_PARAM] -> Handler
known_edge TYPE_ELEMENT 2 Repo [METHOD] -> User
known_edge TYPE_ELEMENT 2 use [METHOD] -> User
known_edge TYPE_ELEMENT 4 Promise [METHOD_PARAM] -> PromiseLike
known_edge TYPE_ELEMENT 4 PromiseLike [METHOD_PARAM] -> PromiseLike
Loading
Loading