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,14 @@
// The PRODUCTION program's standard library (noLib, as case 21 explains). `Array.map` here
// and `Pipeline.map` below both take one callback.
interface Array<T> {
map<U>(fn: (v: T) => U): U[];
}
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,16 @@
// Production code, in its own program. `Pipeline` has a `map`, so by shape it "satisfies"
// the FIXTURE program's hand-written `Array` too — but no value of it can ever reach a
// receiver typed by that `Array`: neither program sees the other's files. So the fixture's
// `rows.map(...)` must not dispatch here, and `this.items.map(project)` must not reach the
// fixture's callback through that dispatch.
export class Pipeline<T> {
constructor(private readonly items: T[]) {}

map<U>(project: (value: T) => U): Pipeline<U> {
return new Pipeline(this.items.map(project));
}
}

export function build(xs: string[]): Pipeline<string> {
return new Pipeline(xs).map((s) => s);
}
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,16 @@
// A TEST FIXTURE's own standard library, in a SIBLING program: these globals belong to
// ./tsconfig.json alone. The parameter name differs from ../app/globals.d.ts on purpose,
// so the two `Array.map` declarations are told apart in the goldens, and this header is
// one line longer so neither `Array` spans the line of the other's `map`.
interface Array<T> {
map<U>(each: (item: T) => U): U[];
}
interface Object { toString(radix?: number): 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,16 @@
// CONTROL: `Bag` lives in this program, has the fixture `Array`'s one member and is
// constructed, so it stays a structural conformer of this program's `Array`: `rows.map`
// keeps its dispatch to `Bag.map`. Only the other program's `Pipeline` drops out.
export class Row {
key(): string { return "row"; }
}

export class Bag<T> {
map<U>(each: (item: T) => U): U[] { return []; }
}

export function keys(rows: Row[]): string[] {
return rows.map((r) => r.key());
}

export const bag = new Bag<Row>();
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
known_edge CONSTRUCTOR_CALL Pipeline#map((value: T) =) @L10 -> Pipeline#<new>(T[])
known_edge CONSTRUCTOR_CALL app/pipeline#build(string[]) @L15 -> Pipeline#<new>(T[])
known_edge CONSTRUCTOR_CALL fixture/use#<module-init>() @L16 -> Bag#<new>()
known_edge METHOD_CALL app/pipeline#build(string[]) @L15 -> Pipeline#map((value: T) =)
known_edge METHOD_CALL fixture/use#<arrow@13>(?) @L13 -> Row#key()
multi_inferred METHOD_CALL Pipeline#map((value: T) =) @L10 -> Array#map((v: T) =)
multi_inferred METHOD_CALL Pipeline#map((value: T) =) @L10 -> Pipeline#map((value: T) =)
multi_inferred METHOD_CALL fixture/use#keys(Row[]) @L13 -> Array#map((item: T) =)
multi_inferred METHOD_CALL fixture/use#keys(Row[]) @L13 -> Bag#map((item: T) =)
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
── entry_point (6) ──
exported_from_entry_module app/pipeline#build pipeline.ts:14
exported_from_entry_module fixture/use#keys use.ts:12
unimported_module app/globals#<module> globals.d.ts:1
unimported_module app/pipeline#<module> pipeline.ts:1
unimported_module fixture/globals#<module> globals.d.ts:1
unimported_module fixture/use#<module> use.ts:1
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
structural app/globals#Array.map -> app/pipeline#Pipeline.map
structural fixture/globals#Array.map -> fixture/use#Bag.map
value app/globals#<function-type>@4:14 -> app/pipeline#<arrow>
value app/pipeline#<function-type>@9:19 -> app/pipeline#<arrow>
value fixture/globals#<function-type>@6:16 -> fixture/use#<arrow>
value fixture/use#<function-type>@9:16 -> fixture/use#<arrow>
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
known_edge read Pipeline#map((value: T) =) -> Pipeline#items
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
82-structural-conformer-across-programs [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
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# The per-case oracle compiles every file under src/ as ONE program (tsc-program.mjs walks
# the directory), so the two programs' `interface Array` merge and the fixture's later
# `map` overload is the one it picks. In the real layout app/ and fixture/ are separate
# programs and `this.items.map` resolves to app/globals.d.ts, which is what the engine says.
Pipeline#map((value: T) =) -> Array#map((item: T) =)
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
oracle=7 engine=9 agree=6 missing=1 (known 1, NEW 0) extra=3
known Pipeline#map((value: T) =) -> Array#map((item: T) =)
extra Pipeline#map((value: T) =) -> Array#map((v: T) =)
extra Pipeline#map((value: T) =) -> Pipeline#map((value: T) =)
extra fixture/use#keys(Row[]) -> Bag#map((item: T) =)
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
known_edge METHOD_RETURN 0 Pipeline [METHOD] -> Pipeline
known_edge METHOD_RETURN 0 app/pipeline [METHOD] -> Pipeline
known_edge METHOD_TYPE_ARGUMENT 0 fixture/use [EXPRESSION] -> Row
known_edge OBJECT_CREATION_TYPE 0 Pipeline [EXPRESSION] -> Pipeline
known_edge OBJECT_CREATION_TYPE 0 app/pipeline [EXPRESSION] -> Pipeline
known_edge OBJECT_CREATION_TYPE 0 fixture/use [EXPRESSION] -> Bag
known_edge SUPER_TYPE 0 CallableFunction [HERITAGE] -> Function
known_edge SUPER_TYPE 0 NewableFunction [HERITAGE] -> Function
known_edge TYPE_ELEMENT 1 fixture/use [METHOD_PARAM] -> Row
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
82-structural-conformer-across-programs [types]
precision 1.0000 (6 correct, 0 wrong)
recall 1.0000 (6 of 6 the compiler resolved)
sites 11 resolved 11 (100.0%)
tiers known_edge=11
contexts METHOD_RETURN=2 METHOD_TYPE_ARGUMENT=1 OBJECT_CREATION_TYPE=3 SUPER_TYPE=4 TYPE_ELEMENT=1
not scored: 0 rows whose target is not a client declaration
39 changes: 37 additions & 2 deletions graph/typescript/engine/resolution/structural-satisfaction.dl
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,41 @@ target_has_nominal_implementor(t) :- implementors(t, _).
structural_implementor(t, s) :- type_satisfies(s, t),
satisfaction_target(t),
s != t,
!target_has_nominal_implementor(t).
!target_has_nominal_implementor(t),
!sat_cross_program(s, t).

// ── sat_cross_program(Source, Target) — a shape match no value can cross (#1574) ──
// A CLIENT GLOBAL belongs to one program (module-graph.dl). A class in ANOTHER program
// that happens to have the global's members — production's `Pipeline.map` against a test
// fixture's hand-written `interface Array { map }` — was counted as its implementor, so
// every fixture `xs.map(cb)` dispatched into production code and production's own
// `this.items.map(f)` dispatched into the fixture's classes: callback reach then spanned
// every program in the repository. A value of the class can reach a receiver typed by
// that global only in code that sees both, so the pair is dropped when NEITHER program
// can see the other's file: the class's module is outside the global's program, and the
// global's file is outside the class's. Governed-by or under-the-directory-of, the same
// test program_sees makes, so a root `types/global.d.ts` keeps every nested package's
// conformers, and a nested package's global keeps a root class its code may import.
// Only pairs of two different governing tsconfigs are asked, so this is a handful of
// prefix tests, not (modules) x (programs). A module with no tsconfig is never cut.
path_key(p) :- module_tsconfig(_, p).
tsconfig_dir(p, d) :- module_tsconfig(_, p), path_dir(p, d).
sat_cross_program_pair(s, t, ms, pt, mt, ps) :- sat_pair_seed(s, t),
client_global_program(t, pt),
type_module("client", ms, s), module_tsconfig(ms, ps), ps != pt,
type_module("client", mt, t).
sat_tree_ask(ms, pt) :- sat_cross_program_pair(_, _, ms, pt, _, _).
sat_tree_ask(mt, ps) :- sat_cross_program_pair(_, _, _, _, mt, ps).
module_in_program_tree(m, p) :- sat_tree_ask(m, p), module_tsconfig(m, p).
module_in_program_tree(m, p) :- sat_tree_ask(m, p),
tsconfig_dir(p, d),
module_file("client", fp, m),
n = strlen(d),
strlen(fp) >= n,
substr(fp, 0, n) = d.
sat_cross_program(s, t) :- sat_cross_program_pair(s, t, ms, pt, mt, ps),
!module_in_program_tree(ms, pt),
!module_in_program_tree(mt, ps).

// ── …AND A CONFORMER THE PROGRAM ACTUALLY CONSTRUCTS, EVEN THEN (#428) ─────
// The gate above is right about EVIDENCE and wrong about what it does with it. Where a
Expand Down Expand Up @@ -169,7 +203,8 @@ structural_implementor(t, s) :- type_satisfies(s, t),
s != t,
target_has_nominal_implementor(t),
!implementors(t, s),
type_instantiated(s, _).
type_instantiated(s, _),
!sat_cross_program(s, t).

// ── satisfaction_unmeasured(TargetTypeHash, Name) ───────────────────────────
// EVERY SUPPRESSION COUNTABLE. A required member of a tested interface that NO
Expand Down
5 changes: 5 additions & 0 deletions graph/typescript/souffle/decls_all.dl
Original file line number Diff line number Diff line change
Expand Up @@ -692,6 +692,11 @@
.decl program_module_count(c0:symbol,c1:number)
.decl program_is_wide(c0:symbol)
.decl global_decl_wide(c0:symbol,c1:symbol,c2:symbol,c3:symbol)
.decl tsconfig_dir(c0:symbol,c1:symbol)
.decl sat_cross_program_pair(c0:symbol,c1:symbol,c2:symbol,c3:symbol,c4:symbol,c5:symbol)
.decl sat_tree_ask(c0:symbol,c1:symbol)
.decl module_in_program_tree(c0:symbol,c1:symbol)
.decl sat_cross_program(c0:symbol,c1:symbol)
.decl remote_edge(c0:symbol,c1:symbol,c2:symbol,c3:symbol,c4:symbol)
.decl remote_undetermined(c0:symbol,c1:symbol,c2:symbol)
.decl remote_unsent(c0:symbol,c1:symbol,c2:symbol)
Expand Down
Loading