@suss/resolution 0.27.0 → 0.28.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -4
- package/dist/index.cjs +699 -352
- package/dist/index.d.cts +234 -2
- package/dist/index.d.ts +234 -2
- package/dist/index.js +339 -2
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,3 +1,260 @@
|
|
|
1
|
+
// src/program.ts
|
|
2
|
+
import { clearRelations, deriveOnDemand, evaluate } from "@suss/datalog";
|
|
3
|
+
var ASKING_RELATIONS = [
|
|
4
|
+
"wanted",
|
|
5
|
+
"wantedOrigin",
|
|
6
|
+
"wantedCallOrigin",
|
|
7
|
+
"wantedExportsOf",
|
|
8
|
+
"wantedSubject",
|
|
9
|
+
"wantedAnchor",
|
|
10
|
+
"wantedAncestry"
|
|
11
|
+
];
|
|
12
|
+
var NO_LANGUAGE_RULES = [];
|
|
13
|
+
var programs = /* @__PURE__ */ new WeakMap();
|
|
14
|
+
function resolutionProgram(languageRules = NO_LANGUAGE_RULES) {
|
|
15
|
+
const built = programs.get(languageRules);
|
|
16
|
+
if (built !== void 0) {
|
|
17
|
+
return built;
|
|
18
|
+
}
|
|
19
|
+
const rules = [
|
|
20
|
+
...RESOLUTION_RULES,
|
|
21
|
+
...languageRules,
|
|
22
|
+
...RESOLUTION_QUESTIONS
|
|
23
|
+
];
|
|
24
|
+
const program = process.env.SUSS_RESOLUTION_ON_DEMAND === "0" ? { rules, demandDriven: [], demands: [] } : deriveOnDemand(rules, ANSWER_RELATIONS);
|
|
25
|
+
programs.set(languageRules, program);
|
|
26
|
+
return program;
|
|
27
|
+
}
|
|
28
|
+
function queryFacts(program) {
|
|
29
|
+
if (program.demandDriven.length === 0) {
|
|
30
|
+
return [];
|
|
31
|
+
}
|
|
32
|
+
return [...program.demandDriven, ...ASKING_RELATIONS];
|
|
33
|
+
}
|
|
34
|
+
var askedByDb = /* @__PURE__ */ new WeakMap();
|
|
35
|
+
function askedOf(db, asking) {
|
|
36
|
+
let byRelation = askedByDb.get(db);
|
|
37
|
+
if (byRelation === void 0) {
|
|
38
|
+
byRelation = /* @__PURE__ */ new Map();
|
|
39
|
+
askedByDb.set(db, byRelation);
|
|
40
|
+
}
|
|
41
|
+
let asked = byRelation.get(asking);
|
|
42
|
+
if (asked === void 0) {
|
|
43
|
+
asked = /* @__PURE__ */ new Set();
|
|
44
|
+
byRelation.set(asking, asked);
|
|
45
|
+
}
|
|
46
|
+
return asked;
|
|
47
|
+
}
|
|
48
|
+
function askResolution(db, keys, asking = "wanted", program = resolutionProgram()) {
|
|
49
|
+
const asked = askedOf(db, asking);
|
|
50
|
+
const fresh = [...keys].filter((key) => !asked.has(key));
|
|
51
|
+
if (fresh.length === 0) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
for (const key of fresh) {
|
|
55
|
+
asked.add(key);
|
|
56
|
+
db.add(asking, [key]);
|
|
57
|
+
}
|
|
58
|
+
evaluate(db, program.rules);
|
|
59
|
+
const forget = queryFacts(program);
|
|
60
|
+
if (forget.length > 0) {
|
|
61
|
+
clearRelations(db, program.rules, forget);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// src/callee.ts
|
|
66
|
+
var UNDECLARED = { kind: "undeclared" };
|
|
67
|
+
var UNSETTLED = { kind: "unsettled" };
|
|
68
|
+
var OUTSIDE_RUN = { kind: "outsideRun" };
|
|
69
|
+
function calleeOutcomes(db, keys, program = resolutionProgram()) {
|
|
70
|
+
askAboutSources(db, keys, program);
|
|
71
|
+
const reading = new Reading(db);
|
|
72
|
+
const outcomes = /* @__PURE__ */ new Map();
|
|
73
|
+
for (const key of keys) {
|
|
74
|
+
outcomes.set(key, reading.outcomeOf(key));
|
|
75
|
+
}
|
|
76
|
+
return outcomes;
|
|
77
|
+
}
|
|
78
|
+
function calleeOutcomeOf(db, key, program = resolutionProgram()) {
|
|
79
|
+
return calleeOutcomes(db, [key], program).get(key) ?? UNDECLARED;
|
|
80
|
+
}
|
|
81
|
+
function askAboutSources(db, keys, program) {
|
|
82
|
+
let wave = [...new Set(keys)];
|
|
83
|
+
const asked = new Set(wave);
|
|
84
|
+
while (wave.length > 0) {
|
|
85
|
+
askResolution(db, wave, "wanted", program);
|
|
86
|
+
const next = [];
|
|
87
|
+
for (const key of wave) {
|
|
88
|
+
if (!decidedByCallers(db, key) && answersFor(db, key).length > 0) {
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
for (const source of sourcesOf(db, key)) {
|
|
92
|
+
if (!asked.has(source)) {
|
|
93
|
+
asked.add(source);
|
|
94
|
+
next.push(source);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
wave = next;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
function sourcesOf(db, key) {
|
|
102
|
+
const base = db.lookup("readsProperty", 0, key)[0];
|
|
103
|
+
if (base !== void 0) {
|
|
104
|
+
return [String(base[1])];
|
|
105
|
+
}
|
|
106
|
+
const call = db.lookup("call", 0, key)[0];
|
|
107
|
+
if (call !== void 0) {
|
|
108
|
+
return [String(call[1])];
|
|
109
|
+
}
|
|
110
|
+
return writtenSourcesOf(db, key);
|
|
111
|
+
}
|
|
112
|
+
var WRITE_RELATIONS = ["binds", "endsHolding", "mayHold"];
|
|
113
|
+
function writtenSourcesOf(db, key) {
|
|
114
|
+
const found = [];
|
|
115
|
+
for (const relation of WRITE_RELATIONS) {
|
|
116
|
+
for (const row of db.lookup(relation, 0, key)) {
|
|
117
|
+
found.push(String(row[1]));
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return found;
|
|
121
|
+
}
|
|
122
|
+
function answersFor(db, key) {
|
|
123
|
+
const found = /* @__PURE__ */ new Map();
|
|
124
|
+
for (const row of db.lookup("wantedComesTo", 0, key)) {
|
|
125
|
+
const answer = String(row[1]);
|
|
126
|
+
if (db.has("func", [answer])) {
|
|
127
|
+
found.set(answer, { kind: "function", key: answer });
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
if (db.has("objectValue", [answer])) {
|
|
131
|
+
found.set(answer, { kind: "object", key: answer });
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return [...found.values()];
|
|
135
|
+
}
|
|
136
|
+
function outcomeKey(outcome) {
|
|
137
|
+
if (outcome.kind === "function" || outcome.kind === "object") {
|
|
138
|
+
return `${outcome.kind}:${outcome.key}`;
|
|
139
|
+
}
|
|
140
|
+
return outcome.kind;
|
|
141
|
+
}
|
|
142
|
+
var THROUGH_BASE = {
|
|
143
|
+
function: UNDECLARED,
|
|
144
|
+
object: UNDECLARED
|
|
145
|
+
};
|
|
146
|
+
var THROUGH_CALL = {
|
|
147
|
+
function: UNSETTLED,
|
|
148
|
+
object: UNSETTLED
|
|
149
|
+
};
|
|
150
|
+
var Reading = class {
|
|
151
|
+
constructor(db) {
|
|
152
|
+
this.db = db;
|
|
153
|
+
}
|
|
154
|
+
db;
|
|
155
|
+
memo = /* @__PURE__ */ new Map();
|
|
156
|
+
walking = /* @__PURE__ */ new Set();
|
|
157
|
+
outcomeOf(key) {
|
|
158
|
+
return this.walk(key).outcome;
|
|
159
|
+
}
|
|
160
|
+
walk(key) {
|
|
161
|
+
const known = this.memo.get(key);
|
|
162
|
+
if (known !== void 0) {
|
|
163
|
+
return { outcome: known, looped: false };
|
|
164
|
+
}
|
|
165
|
+
if (this.walking.has(key)) {
|
|
166
|
+
return { outcome: UNDECLARED, looped: true };
|
|
167
|
+
}
|
|
168
|
+
this.walking.add(key);
|
|
169
|
+
const found = this.decide(key);
|
|
170
|
+
this.walking.delete(key);
|
|
171
|
+
if (!found.looped) {
|
|
172
|
+
this.memo.set(key, found.outcome);
|
|
173
|
+
}
|
|
174
|
+
return found;
|
|
175
|
+
}
|
|
176
|
+
decide(key) {
|
|
177
|
+
if (decidedByCallers(this.db, key)) {
|
|
178
|
+
return this.fromWrites(key);
|
|
179
|
+
}
|
|
180
|
+
const answers = answersFor(this.db, key);
|
|
181
|
+
if (answers.length > 1) {
|
|
182
|
+
return {
|
|
183
|
+
outcome: { kind: "severalSources", sources: answers.map(outcomeKey) },
|
|
184
|
+
looped: false
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
const only = answers[0];
|
|
188
|
+
if (only !== void 0) {
|
|
189
|
+
return { outcome: only, looped: false };
|
|
190
|
+
}
|
|
191
|
+
return this.refusal(key);
|
|
192
|
+
}
|
|
193
|
+
refusal(key) {
|
|
194
|
+
const base = this.db.lookup("readsProperty", 0, key)[0];
|
|
195
|
+
if (base !== void 0) {
|
|
196
|
+
return this.through(THROUGH_BASE, String(base[1]));
|
|
197
|
+
}
|
|
198
|
+
const call = this.db.lookup("call", 0, key)[0];
|
|
199
|
+
if (call !== void 0) {
|
|
200
|
+
return this.through(THROUGH_CALL, String(call[1]));
|
|
201
|
+
}
|
|
202
|
+
return this.fromWrites(key);
|
|
203
|
+
}
|
|
204
|
+
through(table, source) {
|
|
205
|
+
const found = this.walk(source);
|
|
206
|
+
return {
|
|
207
|
+
outcome: table[found.outcome.kind] ?? found.outcome,
|
|
208
|
+
looped: found.looped
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* What the writes to a name, and the arguments a call put in a
|
|
213
|
+
* parameter, agree the value is. A source that says nothing is left
|
|
214
|
+
* out, so a name narrowed through a second name keeps the outcome it
|
|
215
|
+
* started with rather than counting as a second source.
|
|
216
|
+
*/
|
|
217
|
+
fromWrites(key) {
|
|
218
|
+
const found = /* @__PURE__ */ new Map();
|
|
219
|
+
let looped = false;
|
|
220
|
+
for (const source of writtenSourcesOf(this.db, key)) {
|
|
221
|
+
const from = this.walk(source);
|
|
222
|
+
looped = looped || from.looped;
|
|
223
|
+
if (from.outcome.kind !== "undeclared") {
|
|
224
|
+
found.set(outcomeKey(from.outcome), from.outcome);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
if (isParameter(this.db, key)) {
|
|
228
|
+
found.set("callerSupplied", { kind: "callerSupplied", key });
|
|
229
|
+
}
|
|
230
|
+
if (found.size > 1) {
|
|
231
|
+
return {
|
|
232
|
+
outcome: { kind: "severalSources", sources: [...found.keys()] },
|
|
233
|
+
looped
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
const only = [...found.values()][0];
|
|
237
|
+
return { outcome: only ?? whereItCameFrom(this.db, key), looped };
|
|
238
|
+
}
|
|
239
|
+
};
|
|
240
|
+
function isParameter(db, key) {
|
|
241
|
+
return db.lookup("paramNamed", 2, key).length > 0 || db.lookup("paramOf", 2, key).length > 0;
|
|
242
|
+
}
|
|
243
|
+
function decidedByCallers(db, key) {
|
|
244
|
+
return isParameter(db, key) && db.lookup("binds", 0, key).length === 0;
|
|
245
|
+
}
|
|
246
|
+
function whereItCameFrom(db, key) {
|
|
247
|
+
for (const row of db.lookup("imports", 0, key)) {
|
|
248
|
+
if (db.lookup("exportsAs", 0, String(row[1])).length === 0) {
|
|
249
|
+
return OUTSIDE_RUN;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
if (db.has("writtenValue", [key])) {
|
|
253
|
+
return OUTSIDE_RUN;
|
|
254
|
+
}
|
|
255
|
+
return db.has("writesUnstated", [key]) ? UNSETTLED : UNDECLARED;
|
|
256
|
+
}
|
|
257
|
+
|
|
1
258
|
// src/contract.ts
|
|
2
259
|
var distinct = (values) => new Set(values).size;
|
|
3
260
|
var RELATIONS_THAT_CAN_CROSS = ["imports", "binds", "reExports"];
|
|
@@ -477,6 +734,47 @@ function pathsOf(edges, memo, childId, visiting) {
|
|
|
477
734
|
return result;
|
|
478
735
|
}
|
|
479
736
|
|
|
737
|
+
// src/nameReads.ts
|
|
738
|
+
function writesRunInOrder(scope, name, writes, rules) {
|
|
739
|
+
const last = writes[writes.length - 1];
|
|
740
|
+
if (scope === null || last === void 0) {
|
|
741
|
+
return false;
|
|
742
|
+
}
|
|
743
|
+
if (!writes.every((write) => write.direct)) {
|
|
744
|
+
return false;
|
|
745
|
+
}
|
|
746
|
+
return !isReadBefore(scope, name, last.at.startIndex, rules);
|
|
747
|
+
}
|
|
748
|
+
function isReadBefore(scope, name, position, rules) {
|
|
749
|
+
let found = false;
|
|
750
|
+
const visit = (node) => {
|
|
751
|
+
if (found || node.startIndex >= position) {
|
|
752
|
+
return;
|
|
753
|
+
}
|
|
754
|
+
if (rules.laterBodies.has(node.type)) {
|
|
755
|
+
return;
|
|
756
|
+
}
|
|
757
|
+
if (rules.nameTypes.has(node.type) && node.text === name) {
|
|
758
|
+
found = rules.isRead(node);
|
|
759
|
+
return;
|
|
760
|
+
}
|
|
761
|
+
for (const child of rules.childrenOf(node)) {
|
|
762
|
+
visit(child);
|
|
763
|
+
}
|
|
764
|
+
};
|
|
765
|
+
for (const child of rules.childrenOf(scope)) {
|
|
766
|
+
visit(child);
|
|
767
|
+
}
|
|
768
|
+
return found;
|
|
769
|
+
}
|
|
770
|
+
function startsAtName(node, name, rules) {
|
|
771
|
+
if (rules.nameTypes.has(node.type)) {
|
|
772
|
+
return node.text === name;
|
|
773
|
+
}
|
|
774
|
+
const inner = rules.readFirst(node);
|
|
775
|
+
return inner !== null && startsAtName(inner, name, rules);
|
|
776
|
+
}
|
|
777
|
+
|
|
480
778
|
// src/nodeKey.ts
|
|
481
779
|
var SPAN_KEY = /^(.*):(\d+)-(\d+)$/;
|
|
482
780
|
function nodeOfKey(rootsByFile, key) {
|
|
@@ -504,7 +802,7 @@ function nodeOfKey(rootsByFile, key) {
|
|
|
504
802
|
}
|
|
505
803
|
|
|
506
804
|
// src/session.ts
|
|
507
|
-
import { Database, evaluate, proofOf, witnesses } from "@suss/datalog";
|
|
805
|
+
import { Database, evaluate as evaluate2, proofOf, witnesses } from "@suss/datalog";
|
|
508
806
|
function explainResolvedKey(options) {
|
|
509
807
|
const { db, rules, key, locate, displayPath } = options;
|
|
510
808
|
const proofDb = new Database();
|
|
@@ -516,7 +814,7 @@ function explainResolvedKey(options) {
|
|
|
516
814
|
}
|
|
517
815
|
}
|
|
518
816
|
const started = performance.now();
|
|
519
|
-
|
|
817
|
+
evaluate2(proofDb, rules, witnesses);
|
|
520
818
|
const evaluateMs = performance.now() - started;
|
|
521
819
|
const derivedFacts = proofDb.relationNames().reduce((count, relation) => count + proofDb.size(relation), 0) - baseFacts;
|
|
522
820
|
const targets = new Set(
|
|
@@ -590,6 +888,35 @@ function withoutPlaceholders(answers, placeholders) {
|
|
|
590
888
|
return kept;
|
|
591
889
|
}
|
|
592
890
|
|
|
891
|
+
// src/writes.ts
|
|
892
|
+
function valueLeftByWrites(writes, ordered) {
|
|
893
|
+
const deciding = writes.filter((write) => !write.narrowsName);
|
|
894
|
+
const left = deciding.length === 0 ? writes : deciding;
|
|
895
|
+
if (left.some((write) => write.value === null)) {
|
|
896
|
+
return null;
|
|
897
|
+
}
|
|
898
|
+
if (ordered) {
|
|
899
|
+
return left[left.length - 1]?.value ?? null;
|
|
900
|
+
}
|
|
901
|
+
return sharedConstruction(left);
|
|
902
|
+
}
|
|
903
|
+
function sharedConstruction(writes) {
|
|
904
|
+
let settled = null;
|
|
905
|
+
for (const write of writes) {
|
|
906
|
+
if (write.placeholder) {
|
|
907
|
+
continue;
|
|
908
|
+
}
|
|
909
|
+
if (write.construction === null) {
|
|
910
|
+
return null;
|
|
911
|
+
}
|
|
912
|
+
if (settled !== null && write.construction !== settled.construction) {
|
|
913
|
+
return null;
|
|
914
|
+
}
|
|
915
|
+
settled = write;
|
|
916
|
+
}
|
|
917
|
+
return settled?.value ?? null;
|
|
918
|
+
}
|
|
919
|
+
|
|
593
920
|
// src/writtenValue.ts
|
|
594
921
|
function writtenValueOf(db, key, ask) {
|
|
595
922
|
const placeholders = placeholderValues(db);
|
|
@@ -1359,6 +1686,7 @@ var ANSWER_RELATIONS = [
|
|
|
1359
1686
|
];
|
|
1360
1687
|
export {
|
|
1361
1688
|
ANSWER_RELATIONS,
|
|
1689
|
+
ASKING_RELATIONS,
|
|
1362
1690
|
FACT_CONTRACT_CASES,
|
|
1363
1691
|
NAMESPACE_IMPORT,
|
|
1364
1692
|
NAMESPACE_IMPORT_NAME,
|
|
@@ -1367,6 +1695,9 @@ export {
|
|
|
1367
1695
|
RESULT_STEP,
|
|
1368
1696
|
VALUE_STEP,
|
|
1369
1697
|
agreedMountPrefix,
|
|
1698
|
+
askResolution,
|
|
1699
|
+
calleeOutcomeOf,
|
|
1700
|
+
calleeOutcomes,
|
|
1370
1701
|
checkFactContract,
|
|
1371
1702
|
explainResolutionProof,
|
|
1372
1703
|
explainResolvedKey,
|
|
@@ -1374,7 +1705,13 @@ export {
|
|
|
1374
1705
|
mountPathsOf,
|
|
1375
1706
|
nodeOfKey,
|
|
1376
1707
|
placeholderValues,
|
|
1708
|
+
queryFacts,
|
|
1377
1709
|
renderExplanation,
|
|
1710
|
+
resolutionProgram,
|
|
1378
1711
|
singleAnswers,
|
|
1712
|
+
startsAtName,
|
|
1713
|
+
valueLeftByWrites,
|
|
1714
|
+
writesRunInOrder,
|
|
1715
|
+
writtenSourcesOf,
|
|
1379
1716
|
writtenValueOf
|
|
1380
1717
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@suss/resolution",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.28.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "The rules suss uses to follow a name in TypeScript, Python or Ruby to the function it comes down to.",
|
|
6
6
|
"keywords": [
|
|
@@ -56,6 +56,6 @@
|
|
|
56
56
|
"access": "public"
|
|
57
57
|
},
|
|
58
58
|
"dependencies": {
|
|
59
|
-
"@suss/datalog": "0.
|
|
59
|
+
"@suss/datalog": "0.28.0"
|
|
60
60
|
}
|
|
61
61
|
}
|