context-slice 1.8.2
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/LICENSE +21 -0
- package/README.md +349 -0
- package/dist/src/cli.js +199 -0
- package/dist/src/indexer/index.js +325 -0
- package/dist/src/languages/adapter.js +23 -0
- package/dist/src/languages/go/index.js +15 -0
- package/dist/src/languages/go/parse.js +443 -0
- package/dist/src/languages/go/resolve.js +313 -0
- package/dist/src/languages/java/enterprise/dependency-injection.js +175 -0
- package/dist/src/languages/java/enterprise/jpa-entity.js +89 -0
- package/dist/src/languages/java/enterprise/registry.js +22 -0
- package/dist/src/languages/java/enterprise/spring-data.js +239 -0
- package/dist/src/languages/java/enterprise/spring-mvc.js +183 -0
- package/dist/src/languages/java/enterprise/transactions.js +110 -0
- package/dist/src/languages/java.js +84 -0
- package/dist/src/languages/javascript/index.js +25 -0
- package/dist/src/languages/python/index.js +29 -0
- package/dist/src/languages/python/parse.js +415 -0
- package/dist/src/languages/python/resolve.js +413 -0
- package/dist/src/languages/rust/calls-resolve.js +1405 -0
- package/dist/src/languages/rust/index.js +12 -0
- package/dist/src/languages/rust/parse.js +545 -0
- package/dist/src/languages/rust/resolve.js +284 -0
- package/dist/src/languages/typescript/index.js +25 -0
- package/dist/src/languages/typescript/parse.js +793 -0
- package/dist/src/languages/typescript/resolve.js +463 -0
- package/dist/src/package-info.js +16 -0
- package/dist/src/parser/java-parser.js +339 -0
- package/dist/src/planner/budget.js +1 -0
- package/dist/src/planner/composition.js +372 -0
- package/dist/src/planner/rank.js +10 -0
- package/dist/src/render/compact-context.js +11 -0
- package/dist/src/server/mcp-server.js +154 -0
- package/dist/src/storage/sqlite.js +105 -0
- package/dist/src/types/enterprise.js +1 -0
- package/dist/src/types/model.js +1 -0
- package/dist/src/workflow/errors.js +10 -0
- package/dist/src/workflow/preview.js +220 -0
- package/dist/src/workflow/repository.js +56 -0
- package/mcp.json +10 -0
- package/package.json +76 -0
- package/plugin.json +15 -0
- package/queries/java/annotations.scm +1 -0
- package/queries/java/calls.scm +1 -0
- package/queries/java/imports.scm +1 -0
- package/queries/java/symbols.scm +2 -0
- package/skills/context-slice/SKILL.md +60 -0
|
@@ -0,0 +1,1405 @@
|
|
|
1
|
+
import { callEdge, field, macroArgs, macroIndex, modulePathFor, rustParser, typeParamNames } from "./parse.js";
|
|
2
|
+
const TYPE_KINDS = new Set(["class", "enum", "interface", "type"]);
|
|
3
|
+
const IDENT = /^[A-Za-z_]\w*$/;
|
|
4
|
+
// Rust prelude items a call can name without a `use`: documented, deliberately small.
|
|
5
|
+
const PRELUDE_FNS = new Set(["Some", "Ok", "Err", "drop"]);
|
|
6
|
+
const STD_TYPES = new Set([
|
|
7
|
+
"Option", "Result", "Vec", "String", "Box", "Default", "Clone", "Iterator", "IntoIterator", "From", "Into",
|
|
8
|
+
"TryFrom", "TryInto", "AsRef", "AsMut", "ToString", "ToOwned", "PartialEq", "Eq", "PartialOrd", "Ord", "Drop",
|
|
9
|
+
"Fn", "FnMut", "FnOnce", "Send", "Sync", "Copy", "Sized", "Extend", "FromIterator", "DoubleEndedIterator",
|
|
10
|
+
"ExactSizeIterator",
|
|
11
|
+
"bool", "char", "str", "u8", "u16", "u32", "u64", "u128", "usize", "i8", "i16", "i32", "i64", "i128", "isize", "f32", "f64",
|
|
12
|
+
]);
|
|
13
|
+
// Methods a `#[derive(..)]` also provides, keyed to the derived trait.
|
|
14
|
+
const DERIVE_TRAIT = {
|
|
15
|
+
clone: "Clone", default: "Default", fmt: "Debug", eq: "PartialEq", ne: "PartialEq",
|
|
16
|
+
cmp: "Ord", partial_cmp: "PartialOrd", hash: "Hash",
|
|
17
|
+
};
|
|
18
|
+
const STD_ROOTS = new Set(["std", "core", "alloc"]);
|
|
19
|
+
// A glob import can shadow these prelude names (e.g. `use io::*` brings its own `Result`).
|
|
20
|
+
const SHADOWABLE = new Set(["Result"]);
|
|
21
|
+
const isImpl = (s) => s.kind === "type" && s.name.startsWith("impl ");
|
|
22
|
+
const isTupleStruct = (s) => s.kind === "class" && s.metadata?.declaredTypes?.["0"] !== undefined;
|
|
23
|
+
const contains = (outer, inner) => (outer.startLine < inner.startLine || (outer.startLine === inner.startLine && outer.startColumn <= inner.startColumn)) &&
|
|
24
|
+
(outer.endLine > inner.endLine || (outer.endLine === inner.endLine && outer.endColumn >= inner.endColumn));
|
|
25
|
+
const BASE = (e) => e.startsWith("macro:") || e.startsWith("qualified:") || e === "no-type:callee-expression";
|
|
26
|
+
// Keeps parse-time evidence (idempotent across warm re-resolution), drops earlier resolver evidence.
|
|
27
|
+
const withBase = (call, ev) => [...new Set([...call.evidence.filter(BASE), ev])];
|
|
28
|
+
/** The conservative outcome when resolution throws: no target, `no-type:resolver-error` (macro edges keep theirs). */
|
|
29
|
+
export function leaveUnresolvedOnError(call) {
|
|
30
|
+
call.declaredTargetId = call.resolvedTargetId = call.externalPackage = call.runtimeTargetIds = undefined;
|
|
31
|
+
call.resolutionKind = "unresolved";
|
|
32
|
+
call.confidence = "unresolved";
|
|
33
|
+
if (isMacroArg(call) || !call.evidence.some((e) => e.startsWith("macro:")))
|
|
34
|
+
call.evidence = withBase(call, "no-type:resolver-error");
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Parse results that depend ONLY on the exact text they were parsed from, kept across resolve runs so warm
|
|
38
|
+
* rebuilds do not re-parse unchanged files / fns. Every hit is checked against the current text (a changed
|
|
39
|
+
* file or fn is re-parsed); entries for files / symbols no longer indexed are dropped at the start of each run.
|
|
40
|
+
*/
|
|
41
|
+
/** Trees re-parsed from a macro's arguments -> maps their indices to caller-source indices. */
|
|
42
|
+
const MACRO_SHIFT = new WeakMap();
|
|
43
|
+
const callerIndex = (n) => MACRO_SHIFT.get(n.tree)?.(n.startIndex) ?? n.startIndex;
|
|
44
|
+
/** A call recovered from a macro's arguments (never `exact`); a bare `macro:<name>` edge is the invocation itself. */
|
|
45
|
+
const MACRO_ARG = "macro:arg ";
|
|
46
|
+
const isMacroArg = (call) => call.evidence.some((e) => e.startsWith(MACRO_ARG));
|
|
47
|
+
const PERSIST = {
|
|
48
|
+
files: new Map(),
|
|
49
|
+
bindings: new Map(),
|
|
50
|
+
generics: new Map(),
|
|
51
|
+
};
|
|
52
|
+
export function resolveCallsA(context, deps) {
|
|
53
|
+
const byId = new Map();
|
|
54
|
+
const childrenOf = new Map();
|
|
55
|
+
const topByFile = new Map();
|
|
56
|
+
/** Top-level symbols by `file\0name`, in symbol order (same order a filter over topByFile gives). */
|
|
57
|
+
const topNamed = new Map();
|
|
58
|
+
const push = (m, k, v) => (m.get(k)?.push(v) ?? m.set(k, [v]));
|
|
59
|
+
const symbolsByFile = new Map();
|
|
60
|
+
const methodCount = new Map();
|
|
61
|
+
for (const s of context.symbols) {
|
|
62
|
+
byId.set(s.id, s);
|
|
63
|
+
push(symbolsByFile, s.filePath, s);
|
|
64
|
+
if (s.parentId)
|
|
65
|
+
push(childrenOf, s.parentId, s);
|
|
66
|
+
else {
|
|
67
|
+
push(topByFile, s.filePath, s);
|
|
68
|
+
push(topNamed, `${s.filePath}\0${s.name}`, s);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
for (const s of context.symbols) {
|
|
72
|
+
const p = s.parentId ? byId.get(s.parentId) : undefined;
|
|
73
|
+
if (s.kind === "function" && p && (isImpl(p) || p.kind === "interface"))
|
|
74
|
+
methodCount.set(s.name, (methodCount.get(s.name) ?? 0) + 1);
|
|
75
|
+
}
|
|
76
|
+
for (const k of PERSIST.files.keys())
|
|
77
|
+
if (!symbolsByFile.has(k))
|
|
78
|
+
PERSIST.files.delete(k);
|
|
79
|
+
for (const m of [PERSIST.bindings, PERSIST.generics])
|
|
80
|
+
for (const k of m.keys())
|
|
81
|
+
if (!byId.has(k))
|
|
82
|
+
m.delete(k);
|
|
83
|
+
const importsByFile = new Map();
|
|
84
|
+
for (const r of context.imports)
|
|
85
|
+
push(importsByFile, r.filePath, r);
|
|
86
|
+
// Files that are the root of a crate other than the library: `src/bin/*`, `tests/*`, `examples/*`,
|
|
87
|
+
// `benches/*`, `build.rs`, and `src/main.rs` next to a `src/lib.rs`. The shared module index maps every file
|
|
88
|
+
// into one tree, so anchored paths (`crate::`/`self::`/`super::`) there would land in the library: never trusted.
|
|
89
|
+
const allFiles = new Set([...symbolsByFile.keys(), ...context.imports.map((r) => r.filePath), ...context.exports.map((r) => r.filePath)]);
|
|
90
|
+
const foreignRoot = (file) => {
|
|
91
|
+
const p = file.replace(/\\/g, "/");
|
|
92
|
+
return (/(^|\/)src\/bin\//.test(p) ||
|
|
93
|
+
(/(^|\/)(tests|examples|benches)\//.test(p) && !/(^|\/)src\//.test(p)) ||
|
|
94
|
+
/(^|\/)build\.rs$/.test(p) ||
|
|
95
|
+
(/(^|\/)src\/main\.rs$/.test(p) && allFiles.has(p.replace(/main\.rs$/, "lib.rs"))));
|
|
96
|
+
};
|
|
97
|
+
const isAnchor = (seg) => seg === "crate" || seg === "self" || seg === "super";
|
|
98
|
+
const ancestors = (s) => {
|
|
99
|
+
const ids = new Set();
|
|
100
|
+
for (let c = s; c; c = c.parentId ? byId.get(c.parentId) : undefined)
|
|
101
|
+
ids.add(c.id);
|
|
102
|
+
return ids;
|
|
103
|
+
};
|
|
104
|
+
const enclosing = (caller) => (caller.parentId ? byId.get(caller.parentId) : undefined);
|
|
105
|
+
const modContainer = (s) => {
|
|
106
|
+
for (let c = s.parentId ? byId.get(s.parentId) : undefined; c; c = c.parentId ? byId.get(c.parentId) : undefined)
|
|
107
|
+
if (c.kind === "namespace")
|
|
108
|
+
return c;
|
|
109
|
+
return undefined;
|
|
110
|
+
};
|
|
111
|
+
/** Module scope a symbol lives in: its nearest inline `mod`, else the file's top level. */
|
|
112
|
+
const ownScope = (s) => modContainer(s)?.id ?? `file:${s.filePath}`;
|
|
113
|
+
// ---- scopes of `use` / `pub use` records (by range containment in the file's fn/mod symbols)
|
|
114
|
+
const scopeCache = new WeakMap();
|
|
115
|
+
const scopeOf = (rec) => {
|
|
116
|
+
let c = scopeCache.get(rec);
|
|
117
|
+
if (c)
|
|
118
|
+
return c;
|
|
119
|
+
let vis;
|
|
120
|
+
let mod;
|
|
121
|
+
for (const s of symbolsByFile.get(rec.filePath) ?? []) {
|
|
122
|
+
if (s.kind === "namespace" && s.metadata?.moduleScope === true)
|
|
123
|
+
continue; // synthetic per-file import symbol, not a real scope
|
|
124
|
+
if ((s.kind !== "function" && s.kind !== "namespace") || !contains(s.range, rec.range))
|
|
125
|
+
continue;
|
|
126
|
+
if (!vis || contains(vis.range, s.range))
|
|
127
|
+
vis = s;
|
|
128
|
+
if (s.kind === "namespace" && (!mod || contains(mod.range, s.range)))
|
|
129
|
+
mod = s;
|
|
130
|
+
}
|
|
131
|
+
scopeCache.set(rec, (c = { vis, mod }));
|
|
132
|
+
return c;
|
|
133
|
+
};
|
|
134
|
+
// A `use` is visible only inside the module scope that declares it (an inline mod does not
|
|
135
|
+
// inherit its parent's imports) and, when declared in a fn body, only inside that fn.
|
|
136
|
+
const visible = (rec, from) => {
|
|
137
|
+
const { vis, mod } = scopeOf(rec);
|
|
138
|
+
if ((mod?.id ?? "") !== (modContainer(from)?.id ?? ""))
|
|
139
|
+
return false;
|
|
140
|
+
return !vis || ancestors(from).has(vis.id);
|
|
141
|
+
};
|
|
142
|
+
const sourceCache = new Map();
|
|
143
|
+
const fileText = (sample) => {
|
|
144
|
+
let c = sourceCache.get(sample.filePath);
|
|
145
|
+
if (!c) {
|
|
146
|
+
const text = context.sourceOf(sample);
|
|
147
|
+
c = PERSIST.files.get(sample.filePath);
|
|
148
|
+
if (c?.text !== text) {
|
|
149
|
+
const lines = [0];
|
|
150
|
+
for (let i = 0; i < text.length; i++)
|
|
151
|
+
if (text.charCodeAt(i) === 10)
|
|
152
|
+
lines.push(i + 1);
|
|
153
|
+
PERSIST.files.set(sample.filePath, (c = { text, lines }));
|
|
154
|
+
}
|
|
155
|
+
sourceCache.set(sample.filePath, c);
|
|
156
|
+
}
|
|
157
|
+
return c;
|
|
158
|
+
};
|
|
159
|
+
const offsetIn = (sample, line, col) => fileText(sample).lines[line - 1] + col;
|
|
160
|
+
/** Innermost node at `offset` of the sample's whole-file syntax tree, or undefined when unparsable. */
|
|
161
|
+
const nodeAt = (sample, offset) => {
|
|
162
|
+
const c = fileText(sample);
|
|
163
|
+
try {
|
|
164
|
+
c.tree ??= rustParser().parse((i) => c.text.slice(i, i + 4_096));
|
|
165
|
+
return c.tree.rootNode.descendantForIndex(offset);
|
|
166
|
+
}
|
|
167
|
+
catch {
|
|
168
|
+
return undefined;
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
/** [start, end) of the innermost `{ }` block enclosing `offset`, or undefined at module level. */
|
|
172
|
+
const blockAt = (sample, offset) => {
|
|
173
|
+
for (let n = nodeAt(sample, offset); n; n = n.parent)
|
|
174
|
+
if (n.type === "block")
|
|
175
|
+
return [n.startIndex, n.endIndex];
|
|
176
|
+
return undefined;
|
|
177
|
+
};
|
|
178
|
+
/** True when the block declaring the item at `at` also encloses the call being resolved. */
|
|
179
|
+
const declEnclosesCall = (sample, at) => {
|
|
180
|
+
if (!currentCall)
|
|
181
|
+
return true;
|
|
182
|
+
const blk = blockAt(sample, offsetIn(sample, at.startLine, at.startColumn));
|
|
183
|
+
if (!blk)
|
|
184
|
+
return true;
|
|
185
|
+
const c = offsetIn(sample, currentCall.range.startLine, currentCall.range.startColumn);
|
|
186
|
+
return c >= blk[0] && c < blk[1];
|
|
187
|
+
};
|
|
188
|
+
// The call being resolved (set by the main loop; undefined while resolving impl headers). A fn-local
|
|
189
|
+
// `use` counts only if the block it sits in encloses this call.
|
|
190
|
+
let currentCall;
|
|
191
|
+
const importsFor = (file, from) => (importsByFile.get(file) ?? []).filter((r) => visible(r, from) && (scopeOf(r).vis?.kind !== "function" || declEnclosesCall(from, r.range)));
|
|
192
|
+
/** Imports declared in a fn body (enclosing the call) that bind `name`: they shadow module-level items. */
|
|
193
|
+
const localImportsNamed = (file, from, name) => importsFor(file, from).filter((r) => !r.wildcard && r.localName === name && scopeOf(r).vis?.kind === "function");
|
|
194
|
+
// ---- where a `use` points
|
|
195
|
+
const targetCache = new WeakMap();
|
|
196
|
+
const importTarget = (rec) => {
|
|
197
|
+
let t = targetCache.get(rec);
|
|
198
|
+
if (t)
|
|
199
|
+
return t;
|
|
200
|
+
t = computeTarget(rec);
|
|
201
|
+
targetCache.set(rec, t);
|
|
202
|
+
return t;
|
|
203
|
+
};
|
|
204
|
+
const computeTarget = (rec) => {
|
|
205
|
+
if (rec.externalPackage)
|
|
206
|
+
return { t: "ext", pkg: rec.externalPackage };
|
|
207
|
+
const mod = rec.module.split("::");
|
|
208
|
+
if (!rec.wildcard && (!rec.importedName || (mod.length === 1 && mod[0] === rec.importedName)))
|
|
209
|
+
return { t: "unknown" };
|
|
210
|
+
let segs = rec.wildcard ? mod : [...mod, rec.importedName];
|
|
211
|
+
if (isAnchor(segs[0]) && foreignRoot(rec.filePath))
|
|
212
|
+
return { t: "unknown" };
|
|
213
|
+
const fromFile = rec.filePath;
|
|
214
|
+
const container = scopeOf(rec).mod;
|
|
215
|
+
if (container) {
|
|
216
|
+
// `use` inside an inline mod: anchors are relative to that mod, not the file's module.
|
|
217
|
+
if (segs[0] === "super" && !container.parentId)
|
|
218
|
+
segs = ["self", ...segs.slice(1)];
|
|
219
|
+
else if (segs[0] !== "crate")
|
|
220
|
+
return { t: "unknown" };
|
|
221
|
+
}
|
|
222
|
+
return { t: "path", fromFile, segs };
|
|
223
|
+
};
|
|
224
|
+
// Exact module file of `segs`, unless a prefix's file declares an INLINE mod on the way: that mod has no
|
|
225
|
+
// file, and a same-named orphan/cfg-alternative file must never stand in for it.
|
|
226
|
+
const moduleFile = (segs, fromFile) => {
|
|
227
|
+
if (!segs.length)
|
|
228
|
+
return undefined;
|
|
229
|
+
for (let i = isAnchor(segs[0]) ? 1 : 0; i < segs.length; i++) {
|
|
230
|
+
const pf = i === 0 ? fromFile : deps.moduleOf(segs.slice(0, i), fromFile).file;
|
|
231
|
+
const inline = (topNamed.get(`${pf ?? ""}\0${segs[i]}`) ?? []).some((s) => s.kind === "namespace" && s.bodyRange !== undefined);
|
|
232
|
+
if (inline)
|
|
233
|
+
return undefined;
|
|
234
|
+
}
|
|
235
|
+
return deps.moduleOf(segs, fromFile).file;
|
|
236
|
+
};
|
|
237
|
+
// ---- item lookup in a module file, following `pub use` chains
|
|
238
|
+
// `use` declarations with a visibility modifier (they also produce export records).
|
|
239
|
+
const pubUses = new Set(context.exports.map((e) => `${e.filePath}:${e.range.startLine}:${e.range.startColumn}`));
|
|
240
|
+
/** True when module file `asker` is `file`'s module or one of its descendants. */
|
|
241
|
+
const within = (asker, file) => {
|
|
242
|
+
const a = modulePathFor(asker);
|
|
243
|
+
return modulePathFor(file).every((seg, i) => a[i] === seg);
|
|
244
|
+
};
|
|
245
|
+
/** Items named `name` in module `file` as seen from module file `asker` (private `use` only from within). */
|
|
246
|
+
const lookupItems = (file, name, asker, seen = new Set()) => {
|
|
247
|
+
const key = `${file}#${name}`;
|
|
248
|
+
if (seen.has(key))
|
|
249
|
+
return [];
|
|
250
|
+
seen.add(key);
|
|
251
|
+
const direct = (topNamed.get(`${file}\0${name}`) ?? []).filter((s) => !isImpl(s));
|
|
252
|
+
if (direct.length)
|
|
253
|
+
return direct;
|
|
254
|
+
const out = new Map();
|
|
255
|
+
const add = (list) => list.forEach((s) => out.set(s.id, s));
|
|
256
|
+
// Module-level `use` records: `pub` ones always, private ones only for the module itself and its descendants.
|
|
257
|
+
const inside = within(asker, file);
|
|
258
|
+
for (const rec of importsByFile.get(file) ?? []) {
|
|
259
|
+
const sc = scopeOf(rec);
|
|
260
|
+
if (sc.vis || sc.mod || rec.externalPackage)
|
|
261
|
+
continue;
|
|
262
|
+
if (!inside && !pubUses.has(`${rec.filePath}:${rec.range.startLine}:${rec.range.startColumn}`))
|
|
263
|
+
continue;
|
|
264
|
+
const mod = rec.module.split("::");
|
|
265
|
+
if (isAnchor(mod[0]) && foreignRoot(file))
|
|
266
|
+
continue;
|
|
267
|
+
if (rec.wildcard) {
|
|
268
|
+
const f = moduleFile(mod, file);
|
|
269
|
+
if (f)
|
|
270
|
+
add(lookupItems(f, name, file, seen));
|
|
271
|
+
}
|
|
272
|
+
else if (rec.localName === name && rec.importedName && !(mod.length === 1 && mod[0] === rec.importedName)) {
|
|
273
|
+
const f = moduleFile(mod, file);
|
|
274
|
+
if (f)
|
|
275
|
+
add(lookupItems(f, rec.importedName, file, seen));
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
return [...out.values()];
|
|
279
|
+
};
|
|
280
|
+
const itemsVia = (t, name) => {
|
|
281
|
+
const segs = name === undefined ? t.segs.slice(0, -1) : t.segs;
|
|
282
|
+
const last = name ?? t.segs.at(-1);
|
|
283
|
+
const f = moduleFile(segs, t.fromFile);
|
|
284
|
+
return f ? lookupItems(f, last, t.fromFile) : [];
|
|
285
|
+
};
|
|
286
|
+
// ---- glob imports visible from a scope
|
|
287
|
+
// `unknown`: a glob we cannot see into may provide `name`. A glob of a CamelCase path (`use Enum::*`)
|
|
288
|
+
// only brings that enum's variants, which are CamelCase too, so it cannot provide a lowercase name.
|
|
289
|
+
// This rests on the naming CONVENTION only (`#[allow(non_camel_case_types)]` variants can break it), so a
|
|
290
|
+
// file mentioning that attribute gets the conservative answer: every unseeable glob may provide the name.
|
|
291
|
+
/** Start offset of the innermost `{ }` block holding a fn-local import (-1 when not fn-local). */
|
|
292
|
+
const blockStartOf = (from, rec) => scopeOf(rec).vis?.kind === "function" ? (blockAt(from, offsetIn(from, rec.range.startLine, rec.range.startColumn))?.[0] ?? -1) : -1;
|
|
293
|
+
const globs = (file, from, name, onlyLocal = false, minBlock = -1) => {
|
|
294
|
+
const ft = fileText(from);
|
|
295
|
+
const conventional = (ft.conventional ??= !ft.text.includes("non_camel_case_types"));
|
|
296
|
+
const files = [];
|
|
297
|
+
// Items an ancestor scope imports by name, which `use super::*` / `use crate::*` also brings in.
|
|
298
|
+
const extra = [];
|
|
299
|
+
let unknown = false;
|
|
300
|
+
const wild = importsFor(file, from).filter((r) => r.wildcard && (!onlyLocal || (scopeOf(r).vis?.kind === "function" && blockStartOf(from, r) > minBlock)));
|
|
301
|
+
for (const rec of wild) {
|
|
302
|
+
const t = importTarget(rec);
|
|
303
|
+
const f = t.t === "path" ? moduleFile(t.segs, t.fromFile) : undefined;
|
|
304
|
+
if (f) {
|
|
305
|
+
files.push({ file: f });
|
|
306
|
+
if (t.t === "path" && t.segs.every(isAnchor))
|
|
307
|
+
for (const r of importsByFile.get(f) ?? []) {
|
|
308
|
+
if (r === rec || r.wildcard || r.localName !== name || scopeOf(r).vis)
|
|
309
|
+
continue;
|
|
310
|
+
const rt = importTarget(r);
|
|
311
|
+
if (rt.t === "path")
|
|
312
|
+
extra.push(...itemsVia(rt));
|
|
313
|
+
else
|
|
314
|
+
unknown = true; // an external / unknown import of that name: cannot see into it
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
else if (!(conventional && /^[a-z_]/.test(name) && /^[A-Z]/.test(rec.module.split("::").at(-1) ?? "")))
|
|
318
|
+
unknown = true;
|
|
319
|
+
}
|
|
320
|
+
return { files, extra, unknown, hit: files.length > 0 || unknown };
|
|
321
|
+
};
|
|
322
|
+
// ---- types
|
|
323
|
+
// Type declarations by module scope (never merged across inline mods) and, loosely, by file.
|
|
324
|
+
const typeDeclsByScope = new Map();
|
|
325
|
+
const typeNamesInFile = new Map();
|
|
326
|
+
for (const s of context.symbols) {
|
|
327
|
+
const p = s.parentId ? byId.get(s.parentId) : undefined;
|
|
328
|
+
if (!TYPE_KINDS.has(s.kind) || isImpl(s) || (p && p.kind !== "namespace"))
|
|
329
|
+
continue;
|
|
330
|
+
const key = ownScope(s);
|
|
331
|
+
let m = typeDeclsByScope.get(key);
|
|
332
|
+
if (!m)
|
|
333
|
+
typeDeclsByScope.set(key, (m = new Map()));
|
|
334
|
+
push(m, s.name, s);
|
|
335
|
+
let names = typeNamesInFile.get(s.filePath);
|
|
336
|
+
if (!names)
|
|
337
|
+
typeNamesInFile.set(s.filePath, (names = new Set()));
|
|
338
|
+
names.add(s.name);
|
|
339
|
+
}
|
|
340
|
+
/** Items declared in a fn body (in a block enclosing the call) that shadow module-level names. */
|
|
341
|
+
const localItems = (from, name) => {
|
|
342
|
+
const out = [];
|
|
343
|
+
for (let a = from; a; a = a.parentId ? byId.get(a.parentId) : undefined)
|
|
344
|
+
if (a.kind === "function")
|
|
345
|
+
for (const c of childrenOf.get(a.id) ?? [])
|
|
346
|
+
if (c.name === name && c.kind !== "function" && !isImpl(c) && declEnclosesCall(from, c.range))
|
|
347
|
+
out.push(c);
|
|
348
|
+
return out;
|
|
349
|
+
};
|
|
350
|
+
const declsHere = (from, name) => typeDeclsByScope.get(ownScope(from))?.get(name) ?? [];
|
|
351
|
+
const fromDecls = (list) => {
|
|
352
|
+
const uniq = [...new Map(list.map((s) => [s.id, s])).values()];
|
|
353
|
+
return uniq.length === 1 ? { t: "decl", sym: uniq[0] } : uniq.length > 1 ? { t: "ambiguous", n: uniq.length } : undefined;
|
|
354
|
+
};
|
|
355
|
+
const typeName = (name, file, from) => {
|
|
356
|
+
const viaImports = (recs) => {
|
|
357
|
+
const decls = [];
|
|
358
|
+
const ext = new Set();
|
|
359
|
+
let unknown = false;
|
|
360
|
+
for (const r of recs) {
|
|
361
|
+
const t = importTarget(r);
|
|
362
|
+
if (t.t === "ext")
|
|
363
|
+
ext.add(t.pkg);
|
|
364
|
+
else if (t.t === "unknown")
|
|
365
|
+
unknown = true;
|
|
366
|
+
else
|
|
367
|
+
decls.push(...itemsVia(t).filter((s) => TYPE_KINDS.has(s.kind)));
|
|
368
|
+
}
|
|
369
|
+
if (unknown || (ext.size && decls.length) || ext.size > 1)
|
|
370
|
+
return undefined;
|
|
371
|
+
if (ext.size)
|
|
372
|
+
return { t: "external", pkg: [...ext][0], exact: true };
|
|
373
|
+
return fromDecls(decls);
|
|
374
|
+
};
|
|
375
|
+
const localTypes = localItems(from, name).filter((s) => TYPE_KINDS.has(s.kind));
|
|
376
|
+
if (localTypes.length)
|
|
377
|
+
return fromDecls(localTypes);
|
|
378
|
+
const local = localImportsNamed(file, from, name);
|
|
379
|
+
const named = local.length ? Math.max(...local.map((r) => blockStartOf(from, r))) : -1;
|
|
380
|
+
const lg = globs(file, from, name, true, named);
|
|
381
|
+
if (lg.hit) {
|
|
382
|
+
const lc = fromDecls([...lg.files.flatMap((x) => lookupItems(x.file, name, file)), ...lg.extra].filter((s) => TYPE_KINDS.has(s.kind)));
|
|
383
|
+
if (lc)
|
|
384
|
+
return lg.unknown ? undefined : lc;
|
|
385
|
+
if (lg.unknown)
|
|
386
|
+
return undefined;
|
|
387
|
+
}
|
|
388
|
+
if (local.length)
|
|
389
|
+
return viaImports(local); // a fn-local `use` shadows module-level items
|
|
390
|
+
const same = declsHere(from, name);
|
|
391
|
+
if (same.length)
|
|
392
|
+
return fromDecls(same);
|
|
393
|
+
const recs = importsFor(file, from).filter((r) => !r.wildcard && r.localName === name);
|
|
394
|
+
if (recs.length)
|
|
395
|
+
return viaImports(recs);
|
|
396
|
+
const g = globs(file, from, name);
|
|
397
|
+
const cands = [...g.files.flatMap((x) => lookupItems(x.file, name, file)), ...g.extra].filter((s) => TYPE_KINDS.has(s.kind));
|
|
398
|
+
const found = fromDecls(cands);
|
|
399
|
+
if (found)
|
|
400
|
+
return g.unknown ? undefined : found;
|
|
401
|
+
if (STD_TYPES.has(name) && !(g.unknown && SHADOWABLE.has(name)))
|
|
402
|
+
return { t: "external", pkg: "std", exact: false };
|
|
403
|
+
return undefined;
|
|
404
|
+
};
|
|
405
|
+
/** Type named by `segs` (`Foo` or `a::b::Foo`), seen from `from` in `file`. */
|
|
406
|
+
const typePath = (segs, file, from) => {
|
|
407
|
+
if (segs.length === 1)
|
|
408
|
+
return typeName(segs[0], file, from);
|
|
409
|
+
const full = expand(segs, file, from);
|
|
410
|
+
if (!full)
|
|
411
|
+
return undefined;
|
|
412
|
+
if (full.t === "ext")
|
|
413
|
+
return { t: "external", pkg: full.pkg, exact: full.exact };
|
|
414
|
+
return fromDecls(itemsVia({ t: "path", fromFile: full.fromFile, segs: full.segs }).filter((s) => TYPE_KINDS.has(s.kind)));
|
|
415
|
+
};
|
|
416
|
+
/** Replaces a first segment that is a visible `use` alias by the aliased path. */
|
|
417
|
+
const expand = (segs, file, from) => {
|
|
418
|
+
const [first, ...rest] = segs;
|
|
419
|
+
if (first === "crate" || first === "self" || first === "super")
|
|
420
|
+
return { t: "path", fromFile: file, segs };
|
|
421
|
+
const recs = importsFor(file, from).filter((r) => !r.wildcard && r.localName === first);
|
|
422
|
+
if (recs.length > 1)
|
|
423
|
+
return undefined;
|
|
424
|
+
if (recs.length === 1) {
|
|
425
|
+
const t = importTarget(recs[0]);
|
|
426
|
+
if (t.t === "ext")
|
|
427
|
+
return { t: "ext", pkg: t.pkg, exact: true };
|
|
428
|
+
if (t.t === "unknown")
|
|
429
|
+
return undefined;
|
|
430
|
+
return { t: "path", fromFile: t.fromFile, segs: [...t.segs, ...rest], via: recs[0] };
|
|
431
|
+
}
|
|
432
|
+
if (STD_ROOTS.has(first) && !boundNames(file, first))
|
|
433
|
+
return { t: "ext", pkg: first, exact: true }; // `std`, `core` or `alloc`
|
|
434
|
+
return { t: "path", fromFile: file, segs };
|
|
435
|
+
};
|
|
436
|
+
const boundNames = (file, name) => (typeNamesInFile.get(file)?.has(name) ?? false) ||
|
|
437
|
+
(symbolsByFile.get(file) ?? []).some((s) => s.kind === "namespace" && s.name === name) ||
|
|
438
|
+
(importsByFile.get(file) ?? []).some((r) => r.localName === name);
|
|
439
|
+
// ---- impls
|
|
440
|
+
const implTexts = (impl) => {
|
|
441
|
+
const label = impl.name.slice("impl ".length);
|
|
442
|
+
const at = label.lastIndexOf(" for ");
|
|
443
|
+
const parts = at >= 0 ? [label.slice(0, at), label.slice(at + 5)] : [undefined, label];
|
|
444
|
+
const segsOf = (t, base) => {
|
|
445
|
+
if (!t || !base)
|
|
446
|
+
return undefined;
|
|
447
|
+
let s = "";
|
|
448
|
+
for (let depth = 0, i = 0; i < t.length; i++) {
|
|
449
|
+
if (t[i] === "<")
|
|
450
|
+
depth++;
|
|
451
|
+
else if (t[i] === ">")
|
|
452
|
+
depth--;
|
|
453
|
+
else if (depth === 0)
|
|
454
|
+
s += t[i];
|
|
455
|
+
}
|
|
456
|
+
const segs = s.trim().split("::").map((x) => x.trim());
|
|
457
|
+
return segs.every((x) => IDENT.test(x)) && segs.at(-1) === base ? segs : undefined;
|
|
458
|
+
};
|
|
459
|
+
return {
|
|
460
|
+
self: segsOf(parts[1], impl.metadata?.implSelfType),
|
|
461
|
+
trait: parts[0] === undefined ? undefined : segsOf(parts[0], impl.metadata?.implTrait),
|
|
462
|
+
};
|
|
463
|
+
};
|
|
464
|
+
const implSelf = (impl) => {
|
|
465
|
+
const segs = implTexts(impl).self;
|
|
466
|
+
return segs ? typePath(segs, impl.filePath, impl) : undefined;
|
|
467
|
+
};
|
|
468
|
+
const implTrait = (impl) => {
|
|
469
|
+
const segs = implTexts(impl).trait;
|
|
470
|
+
return segs ? typePath(segs, impl.filePath, impl) : undefined;
|
|
471
|
+
};
|
|
472
|
+
const methodsByType = new Map();
|
|
473
|
+
const traitsByType = new Map();
|
|
474
|
+
for (const impl of context.symbols.filter((s) => isImpl(s) && s.metadata?.implSelfType)) {
|
|
475
|
+
const self = implSelf(impl);
|
|
476
|
+
if (self?.t !== "decl")
|
|
477
|
+
continue;
|
|
478
|
+
for (const m of childrenOf.get(impl.id) ?? [])
|
|
479
|
+
if (m.kind === "function")
|
|
480
|
+
push(methodsByType, self.sym.id, { sym: m, impl });
|
|
481
|
+
if (impl.metadata?.implTrait) {
|
|
482
|
+
const tr = implTrait(impl);
|
|
483
|
+
if (tr?.t === "decl" && tr.sym.kind === "interface") {
|
|
484
|
+
let set = traitsByType.get(self.sym.id);
|
|
485
|
+
if (!set)
|
|
486
|
+
traitsByType.set(self.sym.id, (set = new Set()));
|
|
487
|
+
set.add(tr.sym);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
const traitMethods = (trait, name) => (childrenOf.get(trait.id) ?? []).filter((s) => s.kind === "function" && s.name === name);
|
|
492
|
+
// Method names an in-repo impl gives to something that is not an in-repo type (blanket `impl<T> Tr for T`,
|
|
493
|
+
// `impl Tr for String`): a receiver of an external type may reach them.
|
|
494
|
+
const foreignNames = new Set();
|
|
495
|
+
// In-repo traits with a blanket impl (`impl<T: ..> Tr for T` / `for &T`): any type may have their methods.
|
|
496
|
+
// Filtered lazily: the generics reader is declared further down.
|
|
497
|
+
const blanketCands = [];
|
|
498
|
+
let blanketCache;
|
|
499
|
+
const blanketTraits = () => (blanketCache ??= [...new Set(blanketCands.filter((b) => declaredGenerics(b.impl).has(b.self)).map((b) => b.trait))]);
|
|
500
|
+
for (const impl of context.symbols.filter(isImpl)) {
|
|
501
|
+
if (impl.metadata?.implSelfType && implSelf(impl)?.t === "decl")
|
|
502
|
+
continue;
|
|
503
|
+
const tr = impl.metadata?.implTrait ? implTrait(impl) : undefined;
|
|
504
|
+
if (tr?.t === "decl" && tr.sym.kind === "interface" && impl.name.includes(" for "))
|
|
505
|
+
blanketCands.push({ impl, trait: tr.sym, self: impl.name.slice(impl.name.lastIndexOf(" for ") + 5).replace(/^&\s*('\w+\s+)?(mut\s+)?/, "").trim() });
|
|
506
|
+
for (const owner of [impl, ...(tr?.t === "decl" ? [tr.sym] : [])])
|
|
507
|
+
for (const m of childrenOf.get(owner.id) ?? [])
|
|
508
|
+
if (m.kind === "function")
|
|
509
|
+
foreignNames.add(m.name);
|
|
510
|
+
}
|
|
511
|
+
// ---- caller facts
|
|
512
|
+
/** `path` (`recv::f()`), `method` (`recv.f()`) or undefined, from the text right after the receiver. */
|
|
513
|
+
const shapeOf = (call, caller) => {
|
|
514
|
+
const rt = call.receiverText;
|
|
515
|
+
const off = offsetIn(caller, call.range.startLine, call.range.startColumn);
|
|
516
|
+
const tail = fileText(caller).text.slice(off, off + rt.length + 200);
|
|
517
|
+
if (!tail.startsWith(rt))
|
|
518
|
+
return "method"; // `<call>`-style marker: the receiver text is not in the source
|
|
519
|
+
const after = tail.slice(rt.length).trimStart();
|
|
520
|
+
return after.startsWith("::") ? "path" : after.startsWith(".") ? "method" : undefined;
|
|
521
|
+
};
|
|
522
|
+
/**
|
|
523
|
+
* Binding sites (the let / parameter / for / closure / arm / const node) of every name bound by parameters,
|
|
524
|
+
* `let`, closures, `for`, `if let`/`match` patterns anywhere in the caller, parsed from the caller's own source
|
|
525
|
+
* (offsets relative to its start). `*` = unparsable: every name may be shadowed.
|
|
526
|
+
*/
|
|
527
|
+
const bindingSites = (caller) => {
|
|
528
|
+
let c = PERSIST.bindings.get(caller.id);
|
|
529
|
+
if (c?.source === caller.source)
|
|
530
|
+
return c;
|
|
531
|
+
const sites = new Map();
|
|
532
|
+
const calls = new Map();
|
|
533
|
+
const add = (name, owner) => push(sites, name, owner);
|
|
534
|
+
const collect = (n, owner) => {
|
|
535
|
+
if (!n)
|
|
536
|
+
return;
|
|
537
|
+
if (n.type === "identifier" || n.type === "shorthand_field_identifier")
|
|
538
|
+
add(n.text, owner);
|
|
539
|
+
for (let i = 0; i < n.namedChildCount; i++) {
|
|
540
|
+
const c = n.namedChild(i);
|
|
541
|
+
const parentField = ["tuple_struct_pattern", "struct_pattern"].includes(n.type) && n.childForFieldName("type")?.id === c.id;
|
|
542
|
+
if (c.type === "scoped_identifier" || parentField)
|
|
543
|
+
continue;
|
|
544
|
+
collect(c, owner);
|
|
545
|
+
}
|
|
546
|
+
};
|
|
547
|
+
const visit = (n) => {
|
|
548
|
+
switch (n.type) {
|
|
549
|
+
case "macro_invocation": {
|
|
550
|
+
const args = macroArgs(n);
|
|
551
|
+
if (args) {
|
|
552
|
+
const outer = MACRO_SHIFT.get(n.tree) ?? ((i) => i);
|
|
553
|
+
MACRO_SHIFT.set(args.root.tree, (i) => outer(macroIndex(args, i)));
|
|
554
|
+
visit(args.root);
|
|
555
|
+
}
|
|
556
|
+
break;
|
|
557
|
+
}
|
|
558
|
+
case "call_expression":
|
|
559
|
+
calls.set(`${callerIndex(n)}:${callerIndex(n) + n.endIndex - n.startIndex}`, n);
|
|
560
|
+
break;
|
|
561
|
+
case "let_declaration":
|
|
562
|
+
case "for_expression":
|
|
563
|
+
case "let_condition":
|
|
564
|
+
case "parameter":
|
|
565
|
+
case "match_arm":
|
|
566
|
+
collect(n.childForFieldName("pattern"), n);
|
|
567
|
+
break;
|
|
568
|
+
case "closure_parameters":
|
|
569
|
+
collect(n, n);
|
|
570
|
+
break;
|
|
571
|
+
case "const_item":
|
|
572
|
+
case "static_item":
|
|
573
|
+
if (n.childForFieldName("name"))
|
|
574
|
+
add(n.childForFieldName("name").text, n);
|
|
575
|
+
break;
|
|
576
|
+
case "self_parameter":
|
|
577
|
+
add("self", n);
|
|
578
|
+
break;
|
|
579
|
+
}
|
|
580
|
+
for (const c of n.namedChildren)
|
|
581
|
+
if (c.type !== "token_tree")
|
|
582
|
+
visit(c);
|
|
583
|
+
};
|
|
584
|
+
let root;
|
|
585
|
+
try {
|
|
586
|
+
root = rustParser().parse((i) => caller.source.slice(i, i + 4_096)).rootNode;
|
|
587
|
+
visit(root);
|
|
588
|
+
}
|
|
589
|
+
catch {
|
|
590
|
+
sites.set("*", []);
|
|
591
|
+
}
|
|
592
|
+
PERSIST.bindings.set(caller.id, (c = { source: caller.source, sites, calls, root }));
|
|
593
|
+
return c;
|
|
594
|
+
};
|
|
595
|
+
const bindingsOf = (caller) => bindingSites(caller).sites;
|
|
596
|
+
/** Type/const parameter names declared by a fn / impl / trait, read from its syntax tree (any qualifiers). */
|
|
597
|
+
const declaredGenerics = (s) => {
|
|
598
|
+
const hit = PERSIST.generics.get(s.id);
|
|
599
|
+
if (hit?.source === s.source)
|
|
600
|
+
return hit.names;
|
|
601
|
+
const names = new Set();
|
|
602
|
+
try {
|
|
603
|
+
// Only the head matters: parsing the whole impl/trait body is wasted work.
|
|
604
|
+
const head = s.kind === "function" ? s.source : `${s.source.slice(0, Math.max(0, s.source.indexOf("{")))}{}`;
|
|
605
|
+
const item = rustParser().parse((i) => head.slice(i, i + 4_096)).rootNode.namedChild(0);
|
|
606
|
+
for (const p of (item ? field(item, "type_parameters") : null)?.namedChildren ?? []) {
|
|
607
|
+
const n = p.type === "type_identifier" ? p : (field(p, "left") ?? field(p, "name"));
|
|
608
|
+
if (n && (n.type === "type_identifier" || n.type === "identifier"))
|
|
609
|
+
names.add(n.text);
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
catch {
|
|
613
|
+
names.add("*"); // unparsable head: any name may be a type parameter
|
|
614
|
+
}
|
|
615
|
+
PERSIST.generics.set(s.id, { source: s.source, names });
|
|
616
|
+
return names;
|
|
617
|
+
};
|
|
618
|
+
/** Type parameters in scope for a caller: its own, its impl's or trait's. */
|
|
619
|
+
const isGenericParam = (caller, name) => {
|
|
620
|
+
const owner = enclosing(caller);
|
|
621
|
+
const has = (x) => declaredGenerics(x).has(name) || declaredGenerics(x).has("*");
|
|
622
|
+
return has(caller) || (!!owner && (isImpl(owner) || owner.kind === "interface") && has(owner));
|
|
623
|
+
};
|
|
624
|
+
const unresolved = (call, ev) => () => {
|
|
625
|
+
call.evidence = withBase(call, ev);
|
|
626
|
+
};
|
|
627
|
+
const ambiguous = (call, n, what) => unresolved(call, `ambiguous:${n} ${what}`);
|
|
628
|
+
/**
|
|
629
|
+
* Same-name fns in one scope of one file that differ only by `#[cfg(..)]` (spec §40: which one is active is
|
|
630
|
+
* not known): every alternative is attached (`runtimeTargetIds`), the first in source order as the target,
|
|
631
|
+
* `probable`. Any other same-name ambiguity stays `ambiguous:N`.
|
|
632
|
+
*/
|
|
633
|
+
const fnChoice = (call, fns, what, kind = "same-file") => {
|
|
634
|
+
const isCfg = (a) => /^#\[\s*cfg\s*\(/.test(a);
|
|
635
|
+
const rest = (s) => JSON.stringify((s.annotations ?? []).filter((a) => !isCfg(a)));
|
|
636
|
+
const gated = fns.every((f) => f.kind === "function" && f.filePath === fns[0].filePath && f.parentId === fns[0].parentId &&
|
|
637
|
+
(f.annotations ?? []).some(isCfg) && rest(f) === rest(fns[0]));
|
|
638
|
+
if (!gated)
|
|
639
|
+
return ambiguous(call, fns.length, what);
|
|
640
|
+
const sorted = [...fns].sort((a, b) => a.range.startLine - b.range.startLine);
|
|
641
|
+
const pick = settle(call, sorted[0], kind, `ambiguous:cfg ${fns.length} cfg-gated alternatives of ${sorted[0].qualifiedName}`, "probable");
|
|
642
|
+
return () => {
|
|
643
|
+
pick();
|
|
644
|
+
call.runtimeTargetIds = sorted.map((f) => f.id);
|
|
645
|
+
};
|
|
646
|
+
};
|
|
647
|
+
const settle = (call, target, kind, evidence, confidence = "exact") => () => {
|
|
648
|
+
call.declaredTargetId = target.id;
|
|
649
|
+
call.resolvedTargetId = target.id;
|
|
650
|
+
call.resolutionKind = kind;
|
|
651
|
+
call.confidence = confidence;
|
|
652
|
+
call.evidence = withBase(call, evidence);
|
|
653
|
+
};
|
|
654
|
+
const external = (call, pkg, exact, ev) => () => {
|
|
655
|
+
call.externalPackage = pkg;
|
|
656
|
+
call.resolutionKind = "external-package";
|
|
657
|
+
call.confidence = exact ? "exact" : "probable";
|
|
658
|
+
call.evidence = withBase(call, ev);
|
|
659
|
+
};
|
|
660
|
+
/** Methods/assoc fns named `name` on a resolved type: impls first, then traits the type implements. */
|
|
661
|
+
const memberOf = (call, decl, name, kind, extraTrait) => {
|
|
662
|
+
if (decl.kind === "interface")
|
|
663
|
+
return traitDecl(call, [decl], name);
|
|
664
|
+
const cands = (methodsByType.get(decl.id) ?? []).filter((m) => m.sym.name === name);
|
|
665
|
+
// A blanket impl of an in-scope trait declaring `name` competes with the type's own methods.
|
|
666
|
+
const asker = byId.get(call.callerId);
|
|
667
|
+
const blanket = asker
|
|
668
|
+
? blanketTraits().filter((t) => {
|
|
669
|
+
if (!traitMethods(t, name).length)
|
|
670
|
+
return false;
|
|
671
|
+
const seen = typeName(t.name, asker.filePath, asker);
|
|
672
|
+
return seen?.t === "decl" && seen.sym.id === t.id;
|
|
673
|
+
})
|
|
674
|
+
: [];
|
|
675
|
+
const own = [...(traitsByType.get(decl.id) ?? []), ...(extraTrait ? [extraTrait] : [])].filter((t) => !blanket.includes(t));
|
|
676
|
+
if (blanket.length && (cands.length || own.some((t) => traitMethods(t, name).length)))
|
|
677
|
+
return ambiguous(call, cands.length + blanket.length, `${decl.name}::${name} or blanket impl of ${blanket.map((t) => t.name).join(", ")}`);
|
|
678
|
+
if (cands.length === 1) {
|
|
679
|
+
const { sym, impl } = cands[0];
|
|
680
|
+
const traitName = impl.metadata?.implTrait;
|
|
681
|
+
if (traitName) {
|
|
682
|
+
// Derived / std impls are not indexed, so a unique in-repo trait impl is not unique in Rust when the
|
|
683
|
+
// type derives the same method; and a trait method is only a candidate when its trait is in scope.
|
|
684
|
+
const derived = DERIVE_TRAIT[name];
|
|
685
|
+
if (derived && (decl.annotations ?? []).some((a) => new RegExp(`derive\\s*\\([^)]*\\b${derived}\\b`).test(a)))
|
|
686
|
+
return ambiguous(call, 2, `derive(${derived}) and ${traitName} for ${decl.name}::${name}`);
|
|
687
|
+
const caller = byId.get(call.callerId);
|
|
688
|
+
const trait = [...(traitsByType.get(decl.id) ?? [])].find((t) => t.name === traitName && traitMethods(t, name).length > 0);
|
|
689
|
+
const seen = caller ? typeName(traitName, caller.filePath, caller) : undefined;
|
|
690
|
+
const owner = caller ? enclosing(caller) : undefined;
|
|
691
|
+
const ot = owner && isImpl(owner) ? implTrait(owner) : undefined;
|
|
692
|
+
const viaOwnImpl = !!ot && owner.metadata?.implTrait === traitName && (trait ? ot.t === "decl" && ot.sym.id === trait.id : ot.t === "external");
|
|
693
|
+
const visibleHere = viaOwnImpl || (trait ? seen?.t === "decl" && seen.sym.id === trait.id : seen?.t === "external");
|
|
694
|
+
if (!visibleHere)
|
|
695
|
+
return unresolved(call, `no-type:trait-not-in-scope ${traitName}`);
|
|
696
|
+
}
|
|
697
|
+
return settle(call, sym, kind, impl.metadata?.implTrait
|
|
698
|
+
? `trait:unique impl target ${impl.metadata.implTrait} for ${decl.name}::${name}`
|
|
699
|
+
: `inherent:unique target ${decl.name}::${name}`);
|
|
700
|
+
}
|
|
701
|
+
if (cands.length > 1)
|
|
702
|
+
return ambiguous(call, cands.length, `candidates for ${decl.name}::${name}`);
|
|
703
|
+
const traits = new Set(traitsByType.get(decl.id) ?? []);
|
|
704
|
+
if (extraTrait)
|
|
705
|
+
traits.add(extraTrait);
|
|
706
|
+
const viaTrait = [...traits].filter((t) => traitMethods(t, name).length);
|
|
707
|
+
if (viaTrait.length)
|
|
708
|
+
return traitDecl(call, viaTrait, name);
|
|
709
|
+
if (decl.kind === "enum" && /^[A-Z]/.test(name))
|
|
710
|
+
return unresolved(call, "no-symbol:variant");
|
|
711
|
+
return unresolved(call, `no-symbol:member ${decl.name}::${name}`);
|
|
712
|
+
};
|
|
713
|
+
const traitDecl = (call, traits, name) => {
|
|
714
|
+
const decls = traits.flatMap((t) => traitMethods(t, name));
|
|
715
|
+
if (decls.length === 1)
|
|
716
|
+
return settle(call, decls[0], "interface", `trait:declaration ${traits.find((t) => t.id === decls[0].parentId)?.name}::${name}`, "probable");
|
|
717
|
+
if (decls.length > 1)
|
|
718
|
+
return ambiguous(call, decls.length, `trait declarations of ${name}`);
|
|
719
|
+
return unresolved(call, `no-symbol:member trait::${name}`);
|
|
720
|
+
};
|
|
721
|
+
const typeOutcome = (call, res, name, kind, extraTrait) => {
|
|
722
|
+
if (!res)
|
|
723
|
+
return unresolved(call, "no-type:unknown-type");
|
|
724
|
+
if (res.t === "ambiguous")
|
|
725
|
+
return ambiguous(call, res.n, "types with that name");
|
|
726
|
+
if (res.t === "external")
|
|
727
|
+
return external(call, res.pkg, res.exact, `external type from ${res.pkg}`);
|
|
728
|
+
return memberOf(call, res.sym, name, kind, extraTrait);
|
|
729
|
+
};
|
|
730
|
+
const enclosingTypeRes = (caller) => {
|
|
731
|
+
const owner = enclosing(caller);
|
|
732
|
+
if (!owner)
|
|
733
|
+
return { res: undefined };
|
|
734
|
+
if (owner.kind === "interface")
|
|
735
|
+
return { res: { t: "decl", sym: owner }, owner };
|
|
736
|
+
if (!isImpl(owner))
|
|
737
|
+
return { res: undefined };
|
|
738
|
+
const tr = implTrait(owner);
|
|
739
|
+
return { res: implSelf(owner), owner, trait: tr?.t === "decl" && tr.sym.kind === "interface" ? tr.sym : undefined };
|
|
740
|
+
};
|
|
741
|
+
const WRAPPERS = new Set(["Box", "Arc", "Rc"]);
|
|
742
|
+
// Methods the wrapper itself has (or gets from std traits): never pushed through to the inner type.
|
|
743
|
+
const WRAPPER_METHODS = new Set([
|
|
744
|
+
"clone", "as_ref", "as_mut", "borrow", "borrow_mut", "deref", "deref_mut", "eq", "ne", "cmp", "partial_cmp",
|
|
745
|
+
"fmt", "hash", "to_string", "to_owned", "into", "try_into", "downcast", "downcast_ref", "downcast_mut",
|
|
746
|
+
]);
|
|
747
|
+
const UNWRAP = new Set(["unwrap", "expect", "unwrap_or_default", "unwrap_or", "unwrap_or_else"]);
|
|
748
|
+
const ITEM_NODES = {
|
|
749
|
+
function: ["function_item", "function_signature_item"], class: ["struct_item"], enum: ["enum_item"], type: ["impl_item", "type_item"], interface: ["trait_item"],
|
|
750
|
+
};
|
|
751
|
+
/** Syntax node of a declaration in its whole-file tree. */
|
|
752
|
+
const declNode = (s) => {
|
|
753
|
+
const off = offsetIn(s, s.range.startLine, s.range.startColumn);
|
|
754
|
+
for (let n = nodeAt(s, off); n; n = n.parent)
|
|
755
|
+
if (n.startIndex === off && ITEM_NODES[s.kind]?.includes(n.type))
|
|
756
|
+
return n;
|
|
757
|
+
return undefined;
|
|
758
|
+
};
|
|
759
|
+
// Items declared inside a fn body see block-local names we only model relative to the call: not typed.
|
|
760
|
+
const inFn = (s) => [...ancestors(s)].some((id) => id !== s.id && byId.get(id)?.kind === "function");
|
|
761
|
+
const ownerOf = (fn) => {
|
|
762
|
+
const o = enclosing(fn);
|
|
763
|
+
return o && (isImpl(o) || o.kind === "interface") ? o : undefined;
|
|
764
|
+
};
|
|
765
|
+
// A fn's signature scope: module-level names, never its own body's `use`/items (a distinct id hides them).
|
|
766
|
+
const sigFromCache = new Map();
|
|
767
|
+
const sigFrom = (fn) => sigFromCache.get(fn.id) ?? sigFromCache.set(fn.id, { ...fn, id: `${fn.id}#sig` }).get(fn.id);
|
|
768
|
+
const selfTy = (owner, bounded) => {
|
|
769
|
+
if (!owner || inFn(owner))
|
|
770
|
+
return undefined;
|
|
771
|
+
if (owner.kind === "interface")
|
|
772
|
+
return bounded ? { t: "bound", traits: [owner], ext: [], unknown: false } : undefined;
|
|
773
|
+
if (!isImpl(owner) || !owner.metadata?.implSelfType)
|
|
774
|
+
return undefined;
|
|
775
|
+
return tyOf(declNode(owner)?.childForFieldName("type"), { from: owner, owners: [owner], bounded });
|
|
776
|
+
};
|
|
777
|
+
const sigScope = (fn, bounded) => {
|
|
778
|
+
const owner = ownerOf(fn);
|
|
779
|
+
return { from: sigFrom(fn), owners: owner ? [fn, owner] : [fn], bounded, self: () => selfTy(owner, bounded) };
|
|
780
|
+
};
|
|
781
|
+
const bodyScope = (caller) => ({ ...sigScope(caller, true), from: caller });
|
|
782
|
+
/** Trait bounds written on generic `name` by an owner (inline and `where`), or undefined if it declares no such param. */
|
|
783
|
+
const boundNodes = (owner, name) => {
|
|
784
|
+
let found = false;
|
|
785
|
+
const out = [];
|
|
786
|
+
for (const p of field(owner, "type_parameters")?.namedChildren ?? []) {
|
|
787
|
+
const left = p.type === "type_identifier" ? p : (field(p, "left") ?? field(p, "name"));
|
|
788
|
+
if (left?.text !== name)
|
|
789
|
+
continue;
|
|
790
|
+
found = true;
|
|
791
|
+
out.push(...(field(p, "bounds")?.namedChildren ?? []));
|
|
792
|
+
}
|
|
793
|
+
for (const w of owner.namedChildren.find((c) => c.type === "where_clause")?.namedChildren ?? [])
|
|
794
|
+
if (w.type === "where_predicate" && field(w, "left")?.text === name)
|
|
795
|
+
out.push(...(field(w, "bounds")?.namedChildren ?? []));
|
|
796
|
+
return found ? out : undefined;
|
|
797
|
+
};
|
|
798
|
+
const boundTy = (nodes, sc) => {
|
|
799
|
+
const b = { t: "bound", traits: [], ext: [], unknown: false };
|
|
800
|
+
for (let n of nodes) {
|
|
801
|
+
if (n.type === "lifetime" || n.type === "removed_trait_bound")
|
|
802
|
+
continue;
|
|
803
|
+
if (n.type === "higher_ranked_trait_bound")
|
|
804
|
+
n = field(n, "type") ?? n;
|
|
805
|
+
if (n.type === "dynamic_type")
|
|
806
|
+
n = field(n, "trait") ?? n;
|
|
807
|
+
if (n.type === "function_type") { // `Fn(..)` sugar: a std trait
|
|
808
|
+
b.ext.push("std");
|
|
809
|
+
continue;
|
|
810
|
+
}
|
|
811
|
+
const t = tyOf(n, { ...sc, bounded: false });
|
|
812
|
+
if (t?.t === "decl" && t.sym.kind === "interface")
|
|
813
|
+
b.traits.push(t.sym);
|
|
814
|
+
else if (t?.t === "ext")
|
|
815
|
+
b.ext.push(t.pkg);
|
|
816
|
+
else
|
|
817
|
+
b.unknown = true;
|
|
818
|
+
}
|
|
819
|
+
return b;
|
|
820
|
+
};
|
|
821
|
+
/** A type alias `type X<P> = T;` expanded with its use-site arguments. */
|
|
822
|
+
const aliasTy = (alias, args, depth) => {
|
|
823
|
+
const node = inFn(alias) ? undefined : declNode(alias);
|
|
824
|
+
if (node?.type !== "type_item")
|
|
825
|
+
return undefined;
|
|
826
|
+
const params = [...typeParamNames(node)];
|
|
827
|
+
if (args.length && args.length !== params.length)
|
|
828
|
+
return undefined;
|
|
829
|
+
const subst = new Map(params.map((p, i) => [p, args[i]]));
|
|
830
|
+
return tyOf(field(node, "type"), { from: alias, owners: [], bounded: false, subst }, depth + 1);
|
|
831
|
+
};
|
|
832
|
+
const named = (res, name, args, depth) => {
|
|
833
|
+
if (!res)
|
|
834
|
+
return undefined;
|
|
835
|
+
if (res.t === "ambiguous")
|
|
836
|
+
return res;
|
|
837
|
+
if (res.t === "external")
|
|
838
|
+
return { t: "ext", pkg: res.pkg, exact: res.exact, name, args };
|
|
839
|
+
if (res.sym.kind === "type")
|
|
840
|
+
return aliasTy(res.sym, args, depth);
|
|
841
|
+
return { t: "decl", sym: res.sym, args };
|
|
842
|
+
};
|
|
843
|
+
/** Type written as syntax `node` in scope `sc`. */
|
|
844
|
+
const tyOf = (node, sc, depth = 0, args = []) => {
|
|
845
|
+
if (!node || depth > 8)
|
|
846
|
+
return undefined;
|
|
847
|
+
switch (node.type) {
|
|
848
|
+
case "reference_type":
|
|
849
|
+
case "pointer_type":
|
|
850
|
+
return tyOf(field(node, "type"), sc, depth + 1);
|
|
851
|
+
case "abstract_type":
|
|
852
|
+
case "dynamic_type": {
|
|
853
|
+
const tr = field(node, "trait");
|
|
854
|
+
return tr ? boundTy(tr.type === "bounded_type" ? tr.namedChildren : [tr], sc) : undefined;
|
|
855
|
+
}
|
|
856
|
+
case "bounded_type":
|
|
857
|
+
return boundTy(node.namedChildren, sc);
|
|
858
|
+
case "primitive_type":
|
|
859
|
+
return { t: "ext", pkg: "std", exact: true, name: node.text, args: [] };
|
|
860
|
+
case "generic_type": {
|
|
861
|
+
const list = (field(node, "type_arguments")?.namedChildren ?? []).filter((c) => c.type !== "lifetime" && !c.type.endsWith("comment"));
|
|
862
|
+
return tyOf(field(node, "type"), sc, depth + 1, list.map((a) => tyOf(a, sc, depth + 1)));
|
|
863
|
+
}
|
|
864
|
+
case "type_identifier": {
|
|
865
|
+
const name = node.text;
|
|
866
|
+
if (name === "Self")
|
|
867
|
+
return sc.self?.();
|
|
868
|
+
if (sc.subst?.has(name))
|
|
869
|
+
return sc.subst.get(name);
|
|
870
|
+
for (const o of sc.owners) {
|
|
871
|
+
const g = declaredGenerics(o);
|
|
872
|
+
if (g.has("*"))
|
|
873
|
+
return undefined;
|
|
874
|
+
if (!g.has(name))
|
|
875
|
+
continue;
|
|
876
|
+
const on = sc.bounded ? declNode(o) : undefined;
|
|
877
|
+
const bn = on && boundNodes(on, name);
|
|
878
|
+
return bn ? boundTy(bn, sc) : undefined;
|
|
879
|
+
}
|
|
880
|
+
return named(typeName(name, sc.from.filePath, sc.from), name, args, depth);
|
|
881
|
+
}
|
|
882
|
+
case "scoped_type_identifier": {
|
|
883
|
+
const segs = node.text.split("::").map((s) => s.trim());
|
|
884
|
+
// `T::Assoc` / `Self::Assoc` are associated types: unknown.
|
|
885
|
+
if (!segs.every((s) => IDENT.test(s)) || segs[0] === "Self" || sc.owners.some((o) => declaredGenerics(o).has(segs[0])))
|
|
886
|
+
return undefined;
|
|
887
|
+
return named(typePath(segs, sc.from.filePath, sc.from), segs.at(-1), args, depth);
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
return undefined;
|
|
891
|
+
};
|
|
892
|
+
/** `Result<T, _>` / `Option<T>` (std, or an alias of them) => T. */
|
|
893
|
+
const unwrapTy = (ty) => ty?.t === "ext" && STD_ROOTS.has(ty.pkg) && (ty.name === "Result" || ty.name === "Option") ? ty.args[0] : undefined;
|
|
894
|
+
const derefTy = (ty) => (ty?.t === "ext" && WRAPPERS.has(ty.name) && ty.args.length === 1 ? derefTy(ty.args[0]) : ty);
|
|
895
|
+
/** Type of field `name` of a struct-typed value (generic args substituted). */
|
|
896
|
+
const fieldTy = (base, name) => {
|
|
897
|
+
if (base?.t !== "decl" || base.sym.kind !== "class" || inFn(base.sym))
|
|
898
|
+
return undefined;
|
|
899
|
+
const node = declNode(base.sym);
|
|
900
|
+
const body = node && field(node, "body");
|
|
901
|
+
let tn;
|
|
902
|
+
if (body?.type === "field_declaration_list")
|
|
903
|
+
tn = body.namedChildren.find((f) => f.type === "field_declaration" && field(f, "name")?.text === name)?.childForFieldName("type");
|
|
904
|
+
else if (body && /^\d+$/.test(name))
|
|
905
|
+
tn = body.childrenForFieldName("type")[Number(name)];
|
|
906
|
+
if (!tn || !node)
|
|
907
|
+
return undefined;
|
|
908
|
+
const params = [...typeParamNames(node)];
|
|
909
|
+
const subst = new Map(params.map((p, i) => [p, base.args.length === params.length ? base.args[i] : undefined]));
|
|
910
|
+
return tyOf(tn, { from: base.sym, owners: [], bounded: false, subst, self: () => base });
|
|
911
|
+
};
|
|
912
|
+
/** Type of the local `name` used at `use` (caller-source offset): one binding site only, a plain `let`/parameter. */
|
|
913
|
+
const localTy = (caller, name, use) => {
|
|
914
|
+
const { sites } = bindingSites(caller);
|
|
915
|
+
// A match arm's binding is visible only inside that arm: arms not containing the use are not rivals.
|
|
916
|
+
// A binding written inside a macro's arguments: its scope is not modelled, never guessed.
|
|
917
|
+
if ((sites.get(name) ?? []).some((s) => MACRO_SHIFT.has(s.tree)))
|
|
918
|
+
return undefined;
|
|
919
|
+
const list = (sites.get(name) ?? []).filter((s) => s.type !== "match_arm" || (use >= s.startIndex && use < s.endIndex));
|
|
920
|
+
if (sites.has("*") || list.length !== 1)
|
|
921
|
+
return undefined; // shadowed / re-bound: never guessed
|
|
922
|
+
const site = list[0];
|
|
923
|
+
if (site.type === "match_arm")
|
|
924
|
+
return armTy(caller, site, name);
|
|
925
|
+
let pat = field(site, "pattern");
|
|
926
|
+
if (pat?.type === "mut_pattern")
|
|
927
|
+
pat = pat.namedChild(0);
|
|
928
|
+
if (pat?.type !== "identifier")
|
|
929
|
+
return undefined;
|
|
930
|
+
if (site.type === "parameter")
|
|
931
|
+
return tyOf(field(site, "type"), sigScope(caller, true));
|
|
932
|
+
if (site.type !== "let_declaration" || use < site.endIndex || !site.parent || use >= site.parent.endIndex)
|
|
933
|
+
return undefined;
|
|
934
|
+
const tn = field(site, "type");
|
|
935
|
+
return tn ? tyOf(tn, bodyScope(caller)) : exprTy(caller, field(site, "value"));
|
|
936
|
+
};
|
|
937
|
+
/**
|
|
938
|
+
* Type of `name` bound as a direct field of a tuple-variant arm pattern (`Get(cmd)` / `E::Get(a, b)`): the
|
|
939
|
+
* variant's declared payload type. The enum comes from the scrutinee's type, else from the pattern path;
|
|
940
|
+
* undefined unless the enum, the variant and the field position are each unique.
|
|
941
|
+
*/
|
|
942
|
+
const armTy = (caller, arm, name) => {
|
|
943
|
+
let pat = field(arm, "pattern");
|
|
944
|
+
if (pat?.type === "match_pattern")
|
|
945
|
+
pat = pat.namedChild(0);
|
|
946
|
+
if (pat?.type !== "tuple_struct_pattern")
|
|
947
|
+
return undefined;
|
|
948
|
+
const path = field(pat, "type");
|
|
949
|
+
const fields = pat.namedChildren.filter((c) => c.id !== path?.id && !c.type.endsWith("comment"));
|
|
950
|
+
const pos = fields.findIndex((c) => c.type === "identifier" && c.text === name);
|
|
951
|
+
if (!path || pos < 0 || fields.some((c) => c.type === "remaining_field_pattern"))
|
|
952
|
+
return undefined;
|
|
953
|
+
const segs = path.text.split("::").map((s) => s.trim());
|
|
954
|
+
const scrutinee = arm.parent?.parent?.type === "match_expression" ? field(arm.parent.parent, "value") : null;
|
|
955
|
+
let en = derefTy(exprTy(caller, scrutinee));
|
|
956
|
+
if (en?.t !== "decl" && segs.length > 1 && segs.slice(0, -1).every((s) => IDENT.test(s)))
|
|
957
|
+
en = named(typePath(segs.slice(0, -1), caller.filePath, caller), segs.at(-2), [], 0);
|
|
958
|
+
if (en?.t !== "decl" || en.sym.kind !== "enum" || inFn(en.sym))
|
|
959
|
+
return undefined;
|
|
960
|
+
const node = declNode(en.sym);
|
|
961
|
+
const variants = (node && field(node, "body")?.namedChildren.filter((v) => v.type === "enum_variant" && field(v, "name")?.text === segs.at(-1))) ?? [];
|
|
962
|
+
const body = variants.length === 1 ? field(variants[0], "body") : null;
|
|
963
|
+
if (body?.type !== "ordered_field_declaration_list")
|
|
964
|
+
return undefined;
|
|
965
|
+
const types = body.childrenForFieldName("type");
|
|
966
|
+
if (types.length !== fields.length || !node || typeParamNames(node).size)
|
|
967
|
+
return undefined;
|
|
968
|
+
return tyOf(types[pos], { from: en.sym, owners: [], bounded: false, self: () => en });
|
|
969
|
+
};
|
|
970
|
+
// Recursion only moves to strictly earlier / smaller nodes (receivers, a `let` value before its use), so it
|
|
971
|
+
// terminates; the guard only bounds the stack. A result computed under a cut is never memoized.
|
|
972
|
+
let exprDepth = 0;
|
|
973
|
+
let cuts = 0;
|
|
974
|
+
/** Type of expression `n` (a node of the caller's own source tree). */
|
|
975
|
+
const exprTy = (caller, n) => {
|
|
976
|
+
if (!n)
|
|
977
|
+
return undefined;
|
|
978
|
+
if (exprDepth > 200) {
|
|
979
|
+
cuts++;
|
|
980
|
+
return undefined;
|
|
981
|
+
}
|
|
982
|
+
exprDepth++;
|
|
983
|
+
try {
|
|
984
|
+
return exprTyOf(caller, n);
|
|
985
|
+
}
|
|
986
|
+
finally {
|
|
987
|
+
exprDepth--;
|
|
988
|
+
}
|
|
989
|
+
};
|
|
990
|
+
const exprTyOf = (caller, n) => {
|
|
991
|
+
switch (n.type) {
|
|
992
|
+
case "self":
|
|
993
|
+
return selfTy(ownerOf(caller), true);
|
|
994
|
+
case "identifier":
|
|
995
|
+
return localTy(caller, n.text, callerIndex(n));
|
|
996
|
+
case "parenthesized_expression":
|
|
997
|
+
case "reference_expression":
|
|
998
|
+
return exprTy(caller, n.namedChildren.at(-1));
|
|
999
|
+
case "field_expression":
|
|
1000
|
+
return fieldTy(derefTy(exprTy(caller, field(n, "value"))), field(n, "field")?.text ?? "");
|
|
1001
|
+
case "struct_expression":
|
|
1002
|
+
return tyOf(field(n, "name"), bodyScope(caller));
|
|
1003
|
+
case "try_expression":
|
|
1004
|
+
return unwrapTy(exprTy(caller, n.namedChild(0)));
|
|
1005
|
+
case "call_expression": {
|
|
1006
|
+
// Memoized per caller and node: each call of a chain is typed once, not once per outer call.
|
|
1007
|
+
const key = `${caller.id}:${n.startIndex}:${n.endIndex}`;
|
|
1008
|
+
if (callTyMemo.has(key))
|
|
1009
|
+
return callTyMemo.get(key);
|
|
1010
|
+
const before = cuts;
|
|
1011
|
+
const ty = callTy(caller, n);
|
|
1012
|
+
if (cuts === before)
|
|
1013
|
+
callTyMemo.set(key, ty);
|
|
1014
|
+
return ty;
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
return undefined;
|
|
1018
|
+
};
|
|
1019
|
+
const callTyMemo = new Map();
|
|
1020
|
+
/** Resolves a call found inside another call's receiver as if it were an edge (not recorded). */
|
|
1021
|
+
const probe = (caller, n) => {
|
|
1022
|
+
const edge = callEdge(n, caller.filePath, caller.id);
|
|
1023
|
+
// Positions of the caller-source tree are relative to the caller's start: shift them to the file.
|
|
1024
|
+
const at = (p) => ({
|
|
1025
|
+
line: caller.range.startLine + p.row,
|
|
1026
|
+
col: p.row === 0 ? caller.range.startColumn + p.column : p.column,
|
|
1027
|
+
});
|
|
1028
|
+
const s = at(n.startPosition);
|
|
1029
|
+
const e = at(n.endPosition);
|
|
1030
|
+
edge.range = { startLine: s.line, startColumn: s.col, endLine: e.line, endColumn: e.col };
|
|
1031
|
+
if (edge.evidence.some((x) => x.startsWith("macro:")))
|
|
1032
|
+
return edge;
|
|
1033
|
+
const saved = currentCall;
|
|
1034
|
+
currentCall = edge;
|
|
1035
|
+
try {
|
|
1036
|
+
resolveEdge(edge, caller, n)();
|
|
1037
|
+
}
|
|
1038
|
+
finally {
|
|
1039
|
+
currentCall = saved;
|
|
1040
|
+
}
|
|
1041
|
+
return edge;
|
|
1042
|
+
};
|
|
1043
|
+
/** Return type of a call: the exact in-repo target's declared return type, or std `unwrap`-style / `Default` rules. */
|
|
1044
|
+
const callTy = (caller, n) => {
|
|
1045
|
+
let fn = field(n, "function");
|
|
1046
|
+
if (fn?.type === "generic_function")
|
|
1047
|
+
fn = field(fn, "function");
|
|
1048
|
+
if (fn?.type === "field_expression" && UNWRAP.has(field(fn, "field")?.text ?? "")) {
|
|
1049
|
+
const inner = unwrapTy(exprTy(caller, field(fn, "value")));
|
|
1050
|
+
if (inner)
|
|
1051
|
+
return inner;
|
|
1052
|
+
}
|
|
1053
|
+
const edge = probe(caller, n);
|
|
1054
|
+
const target = edge.resolvedTargetId && edge.confidence === "exact" ? byId.get(edge.resolvedTargetId) : undefined;
|
|
1055
|
+
if (target?.kind === "class")
|
|
1056
|
+
return { t: "decl", sym: target, args: [] }; // tuple-struct constructor
|
|
1057
|
+
if (target?.kind === "function") {
|
|
1058
|
+
const rt = inFn(target) ? undefined : declNode(target)?.childForFieldName("return_type");
|
|
1059
|
+
return rt ? tyOf(rt, sigScope(target, false)) : undefined;
|
|
1060
|
+
}
|
|
1061
|
+
// `T::default()` is `Self` by the `Default` signature; `Enum::Variant(..)` is the enum.
|
|
1062
|
+
if (fn?.type !== "scoped_identifier" || !edge.evidence.some((e) => e.startsWith("no-symbol:")))
|
|
1063
|
+
return undefined;
|
|
1064
|
+
const path = field(fn, "path");
|
|
1065
|
+
const segs = (path?.type === "generic_type" ? field(path, "type") : path)?.text.split("::").map((s) => s.trim()) ?? [];
|
|
1066
|
+
if (!segs.length || !segs.every((s) => IDENT.test(s)) || isGenericParam(caller, segs[0]))
|
|
1067
|
+
return undefined;
|
|
1068
|
+
const t = segs.length === 1 && segs[0] === "Self" ? selfTy(ownerOf(caller), false) : named(typePath(segs, caller.filePath, caller), segs.at(-1), [], 0);
|
|
1069
|
+
if (t?.t !== "decl" || t.sym.kind === "interface")
|
|
1070
|
+
return undefined;
|
|
1071
|
+
const name = edge.calleeName;
|
|
1072
|
+
return (name === "default" && edge.argumentCount === 0) || (t.sym.kind === "enum" && /^[A-Z]/.test(name)) ? t : undefined;
|
|
1073
|
+
};
|
|
1074
|
+
/** `recv.m()` on a typed receiver. */
|
|
1075
|
+
const methodOn = (call, ty) => {
|
|
1076
|
+
const name = call.calleeName;
|
|
1077
|
+
const cands = methodCount.get(name) ?? 0;
|
|
1078
|
+
const nowhere = `method ${name} is defined by no project symbol`;
|
|
1079
|
+
if (!ty)
|
|
1080
|
+
return cands ? unresolved(call, `no-type:receiver-type-unknown candidates=${cands}`) : external(call, "std-or-dependency", false, nowhere);
|
|
1081
|
+
if (ty.t === "ambiguous")
|
|
1082
|
+
return ambiguous(call, ty.n, "types with that name");
|
|
1083
|
+
if (ty.t === "decl")
|
|
1084
|
+
return memberOf(call, ty.sym, name, "declared-type");
|
|
1085
|
+
if (ty.t === "bound") {
|
|
1086
|
+
const traits = ty.traits.filter((t) => traitMethods(t, name).length);
|
|
1087
|
+
if (traits.length)
|
|
1088
|
+
return traitDecl(call, traits, name);
|
|
1089
|
+
// Only external bounds (e.g. `I: Iterator`): the method is theirs unless an in-repo blanket/foreign impl adds one.
|
|
1090
|
+
if (!ty.unknown && !ty.traits.length && ty.ext.length && !foreignNames.has(name))
|
|
1091
|
+
return external(call, new Set(ty.ext).size === 1 ? ty.ext[0] : "std-or-dependency", false, `trait bound method ${name}`);
|
|
1092
|
+
return cands ? unresolved(call, `no-type:generic-param candidates=${cands}`) : external(call, "std-or-dependency", false, nowhere);
|
|
1093
|
+
}
|
|
1094
|
+
if (WRAPPERS.has(ty.name) && ty.args.length === 1) {
|
|
1095
|
+
// Nested wrappers (`Arc<Box<A>>`) recurse here, so the wrapper-method guard applies at every level.
|
|
1096
|
+
if (!cands)
|
|
1097
|
+
return external(call, ty.pkg, ty.exact, nowhere);
|
|
1098
|
+
if (WRAPPER_METHODS.has(name))
|
|
1099
|
+
return ambiguous(call, 2, `${ty.name}::${name} or the inner type's`);
|
|
1100
|
+
return methodOn(call, ty.args[0]);
|
|
1101
|
+
}
|
|
1102
|
+
if (!cands)
|
|
1103
|
+
return external(call, ty.pkg, ty.exact, `method on external type ${ty.name}`);
|
|
1104
|
+
// A project method name: an in-repo trait impl for this type, or a Deref to an in-repo generic argument, may win.
|
|
1105
|
+
if (foreignNames.has(name))
|
|
1106
|
+
return ambiguous(call, 2, `external ${ty.name}::${name} or an in-repo trait impl`);
|
|
1107
|
+
const local = (t) => !t || t.t !== "ext" || t.args.some(local);
|
|
1108
|
+
if (ty.args.some(local))
|
|
1109
|
+
return unresolved(call, `no-type:external-generic ${ty.name} candidates=${cands}`);
|
|
1110
|
+
return external(call, ty.pkg, ty.exact, `method on external type ${ty.name}`);
|
|
1111
|
+
};
|
|
1112
|
+
const methodCall = (call, caller, node) => {
|
|
1113
|
+
let n = node;
|
|
1114
|
+
if (!n) {
|
|
1115
|
+
const base = offsetIn(caller, caller.range.startLine, caller.range.startColumn);
|
|
1116
|
+
const rel = offsetIn(caller, call.range.startLine, call.range.startColumn) - base;
|
|
1117
|
+
n = bindingSites(caller).calls.get(`${rel}:${offsetIn(caller, call.range.endLine, call.range.endColumn) - base}`);
|
|
1118
|
+
}
|
|
1119
|
+
let fn = n && field(n, "function");
|
|
1120
|
+
if (fn?.type === "generic_function")
|
|
1121
|
+
fn = field(fn, "function");
|
|
1122
|
+
return methodOn(call, fn?.type === "field_expression" ? exprTy(caller, field(fn, "value")) : undefined);
|
|
1123
|
+
};
|
|
1124
|
+
// ---- rules
|
|
1125
|
+
const selfMethod = (call, caller) => {
|
|
1126
|
+
const { res, trait, owner } = enclosingTypeRes(caller);
|
|
1127
|
+
if (!owner)
|
|
1128
|
+
return unresolved(call, "no-type:self-outside-impl");
|
|
1129
|
+
if (!res)
|
|
1130
|
+
return unresolved(call, `no-type:self-type-unknown candidates=${methodCount.get(call.calleeName) ?? 0}`);
|
|
1131
|
+
if (res.t === "decl")
|
|
1132
|
+
return memberOf(call, res.sym, call.calleeName, "this-member", trait);
|
|
1133
|
+
if (res.t === "ambiguous")
|
|
1134
|
+
return ambiguous(call, res.n, "types with that name");
|
|
1135
|
+
return unresolved(call, `no-type:self-type-external candidates=${methodCount.get(call.calleeName) ?? 0}`);
|
|
1136
|
+
};
|
|
1137
|
+
const scopeFns = (caller, name) => {
|
|
1138
|
+
// Only local fns declared in a block that encloses the call are in scope.
|
|
1139
|
+
const fns = (list) => list.filter((s) => s.kind === "function" && s.name === name && (s.parentId === undefined || byId.get(s.parentId)?.kind !== "function" || declEnclosesCall(caller, s.range)));
|
|
1140
|
+
const own = fns(childrenOf.get(caller.id) ?? []);
|
|
1141
|
+
if (own.length)
|
|
1142
|
+
return own;
|
|
1143
|
+
for (let p = caller.parentId ? byId.get(caller.parentId) : undefined; p; p = p.parentId ? byId.get(p.parentId) : undefined) {
|
|
1144
|
+
if (p.kind !== "function" && p.kind !== "namespace")
|
|
1145
|
+
continue; // impl/trait bodies are not name scopes
|
|
1146
|
+
const f = fns(childrenOf.get(p.id) ?? []);
|
|
1147
|
+
if (f.length || p.kind === "namespace")
|
|
1148
|
+
return f;
|
|
1149
|
+
}
|
|
1150
|
+
return fns(topNamed.get(`${caller.filePath}\0${name}`) ?? []);
|
|
1151
|
+
};
|
|
1152
|
+
const bareViaImports = (call, name, recs) => {
|
|
1153
|
+
const cands = new Map();
|
|
1154
|
+
const ext = new Set();
|
|
1155
|
+
let unknown = false;
|
|
1156
|
+
for (const r of recs) {
|
|
1157
|
+
const t = importTarget(r);
|
|
1158
|
+
if (t.t === "ext")
|
|
1159
|
+
ext.add(t.pkg);
|
|
1160
|
+
else if (t.t === "unknown")
|
|
1161
|
+
unknown = true;
|
|
1162
|
+
else
|
|
1163
|
+
for (const s of itemsVia(t))
|
|
1164
|
+
if (s.kind === "function" || isTupleStruct(s))
|
|
1165
|
+
cands.set(s.id, s);
|
|
1166
|
+
}
|
|
1167
|
+
if (unknown)
|
|
1168
|
+
return unresolved(call, "no-symbol:import-target");
|
|
1169
|
+
if (ext.size === 1 && !cands.size)
|
|
1170
|
+
return external(call, [...ext][0], true, `imported from ${[...ext][0]}`);
|
|
1171
|
+
if (cands.size === 1 && !ext.size) {
|
|
1172
|
+
const s = [...cands.values()][0];
|
|
1173
|
+
const aliased = recs[0].localName !== recs[0].importedName;
|
|
1174
|
+
return settle(call, s, isTupleStruct(s) ? "constructor" : aliased ? "aliased-import" : "imported", `import ${recs[0].module}::${recs[0].importedName}`);
|
|
1175
|
+
}
|
|
1176
|
+
if (cands.size + ext.size > 1)
|
|
1177
|
+
return ambiguous(call, cands.size + ext.size, `imports named ${name}`);
|
|
1178
|
+
return unresolved(call, `no-symbol:imported ${name}`);
|
|
1179
|
+
};
|
|
1180
|
+
const bareCall = (call, caller) => {
|
|
1181
|
+
const name = call.calleeName;
|
|
1182
|
+
const file = caller.filePath;
|
|
1183
|
+
if (bindingsOf(caller).has(name) || bindingsOf(caller).has("*"))
|
|
1184
|
+
return unresolved(call, "no-type:local-binding");
|
|
1185
|
+
const li = localItems(caller, name);
|
|
1186
|
+
const localCtors = li.filter(isTupleStruct);
|
|
1187
|
+
if (localCtors.length === 1 && li.length === 1)
|
|
1188
|
+
return settle(call, localCtors[0], "constructor", `constructor ${localCtors[0].qualifiedName}`);
|
|
1189
|
+
if (li.some((x) => x.kind === "variable" || TYPE_KINDS.has(x.kind)))
|
|
1190
|
+
return unresolved(call, "no-type:local-item");
|
|
1191
|
+
const local = localImportsNamed(file, caller, name);
|
|
1192
|
+
// A local fn (declared in a fn body, enclosing the call) is as local as a `use`; the innermost block wins.
|
|
1193
|
+
const localFns = scopeFns(caller, name).filter((f) => f.parentId !== undefined && byId.get(f.parentId)?.kind === "function");
|
|
1194
|
+
const fnStart = localFns.length
|
|
1195
|
+
? Math.max(...localFns.map((f) => blockAt(caller, offsetIn(caller, f.range.startLine, f.range.startColumn))?.[0] ?? -1))
|
|
1196
|
+
: -1;
|
|
1197
|
+
const named = local.length ? Math.max(...local.map((r) => blockStartOf(caller, r))) : -1;
|
|
1198
|
+
const lg = globs(file, caller, name, true, Math.max(named, fnStart));
|
|
1199
|
+
if (lg.hit) {
|
|
1200
|
+
const lc = new Map();
|
|
1201
|
+
for (const s of [...lg.files.flatMap((x) => lookupItems(x.file, name, file)), ...lg.extra])
|
|
1202
|
+
if (s.kind === "function" || isTupleStruct(s))
|
|
1203
|
+
lc.set(s.id, s);
|
|
1204
|
+
if (lc.size > 1)
|
|
1205
|
+
return ambiguous(call, lc.size, `local glob imports providing ${name}`);
|
|
1206
|
+
if (lc.size === 1) {
|
|
1207
|
+
if (lg.unknown)
|
|
1208
|
+
return unresolved(call, "no-type:glob-unknown");
|
|
1209
|
+
const s = [...lc.values()][0];
|
|
1210
|
+
return settle(call, s, isTupleStruct(s) ? "constructor" : "imported", `glob import provides ${s.qualifiedName}`);
|
|
1211
|
+
}
|
|
1212
|
+
if (lg.unknown)
|
|
1213
|
+
return unresolved(call, "no-type:glob-unknown");
|
|
1214
|
+
}
|
|
1215
|
+
if (local.length)
|
|
1216
|
+
return bareViaImports(call, name, local); // fn-local `use` shadows module items
|
|
1217
|
+
const fns = scopeFns(caller, name);
|
|
1218
|
+
if (fns.length === 1)
|
|
1219
|
+
return settle(call, fns[0], "same-file", `same-file fn ${fns[0].qualifiedName}`);
|
|
1220
|
+
if (fns.length > 1)
|
|
1221
|
+
return fnChoice(call, fns, `same-scope fns named ${name}`);
|
|
1222
|
+
const ctors = declsHere(caller, name).filter(isTupleStruct);
|
|
1223
|
+
if (ctors.length === 1)
|
|
1224
|
+
return settle(call, ctors[0], "constructor", `constructor ${ctors[0].qualifiedName}`);
|
|
1225
|
+
if (ctors.length > 1)
|
|
1226
|
+
return ambiguous(call, ctors.length, `constructors named ${name}`);
|
|
1227
|
+
const recs = importsFor(file, caller).filter((r) => !r.wildcard && r.localName === name);
|
|
1228
|
+
if (recs.length)
|
|
1229
|
+
return bareViaImports(call, name, recs);
|
|
1230
|
+
const g = globs(file, caller, name);
|
|
1231
|
+
const gc = new Map();
|
|
1232
|
+
for (const s of [...g.files.flatMap((x) => lookupItems(x.file, name, file)), ...g.extra])
|
|
1233
|
+
if (s.kind === "function" || isTupleStruct(s))
|
|
1234
|
+
gc.set(s.id, s);
|
|
1235
|
+
if (gc.size > 1)
|
|
1236
|
+
return ambiguous(call, gc.size, `glob imports providing ${name}`);
|
|
1237
|
+
if (gc.size === 1) {
|
|
1238
|
+
if (g.unknown)
|
|
1239
|
+
return unresolved(call, "no-type:glob-unknown");
|
|
1240
|
+
const s = [...gc.values()][0];
|
|
1241
|
+
return settle(call, s, isTupleStruct(s) ? "constructor" : "imported", `glob import provides ${s.qualifiedName}`);
|
|
1242
|
+
}
|
|
1243
|
+
if (PRELUDE_FNS.has(name) && !g.unknown)
|
|
1244
|
+
return external(call, "std", false, `prelude ${name}`);
|
|
1245
|
+
return unresolved(call, g.unknown ? "no-type:glob-unknown" : "no-type:unknown-function");
|
|
1246
|
+
};
|
|
1247
|
+
/** Fn (or tuple-struct constructor) named `name` reached by `full` module path. */
|
|
1248
|
+
const moduleFn = (call, name, full, kind) => {
|
|
1249
|
+
const f = moduleFile(full.segs, full.fromFile);
|
|
1250
|
+
if (!f)
|
|
1251
|
+
return undefined;
|
|
1252
|
+
const cands = lookupItems(f, name, full.fromFile).filter((s) => s.kind === "function" || isTupleStruct(s));
|
|
1253
|
+
if (cands.length === 1) {
|
|
1254
|
+
const s = cands[0];
|
|
1255
|
+
return settle(call, s, isTupleStruct(s) ? "constructor" : kind, `module path ${full.segs.join("::")}::${name}`);
|
|
1256
|
+
}
|
|
1257
|
+
if (cands.length > 1)
|
|
1258
|
+
return fnChoice(call, cands, `fns named ${name} in ${full.segs.join("::")}`, kind);
|
|
1259
|
+
return unresolved(call, `no-symbol:member ${full.segs.join("::")}::${name}`);
|
|
1260
|
+
};
|
|
1261
|
+
/** Same-file inline `mod` descent: returns undefined when the path does not start in an inline mod scope. */
|
|
1262
|
+
const inlineFn = (call, caller, segs) => {
|
|
1263
|
+
let container = modContainer(caller);
|
|
1264
|
+
const rest = [...segs];
|
|
1265
|
+
while (rest[0] === "self" || rest[0] === "super") {
|
|
1266
|
+
if (rest[0] === "super") {
|
|
1267
|
+
if (!container)
|
|
1268
|
+
return undefined; // file-level: module files handle it
|
|
1269
|
+
container = modContainer(container);
|
|
1270
|
+
}
|
|
1271
|
+
rest.shift();
|
|
1272
|
+
}
|
|
1273
|
+
const scopeChildren = (c) => (c ? childrenOf.get(c.id) ?? [] : topByFile.get(caller.filePath) ?? []);
|
|
1274
|
+
for (const seg of rest) {
|
|
1275
|
+
const next = scopeChildren(container).filter((s) => s.kind === "namespace" && s.name === seg);
|
|
1276
|
+
if (next.length !== 1)
|
|
1277
|
+
return undefined;
|
|
1278
|
+
container = next[0];
|
|
1279
|
+
}
|
|
1280
|
+
const fns = scopeChildren(container).filter((s) => s.kind === "function" && s.name === call.calleeName);
|
|
1281
|
+
if (fns.length === 1)
|
|
1282
|
+
return settle(call, fns[0], "same-file", `same-file module fn ${fns[0].qualifiedName}`);
|
|
1283
|
+
if (fns.length > 1)
|
|
1284
|
+
return fnChoice(call, fns, `fns named ${call.calleeName} in module`);
|
|
1285
|
+
return undefined;
|
|
1286
|
+
};
|
|
1287
|
+
const pathCall = (call, caller, segs) => {
|
|
1288
|
+
const name = call.calleeName;
|
|
1289
|
+
const file = caller.filePath;
|
|
1290
|
+
const first = segs[0];
|
|
1291
|
+
if (first === "Self") {
|
|
1292
|
+
if (segs.length > 1)
|
|
1293
|
+
return unresolved(call, "no-type:unknown-type");
|
|
1294
|
+
const { res, trait } = enclosingTypeRes(caller);
|
|
1295
|
+
if (!res)
|
|
1296
|
+
return unresolved(call, "no-type:unknown-type");
|
|
1297
|
+
return typeOutcome(call, res, name, "static", trait);
|
|
1298
|
+
}
|
|
1299
|
+
const anchored = first === "crate" || first === "self" || first === "super";
|
|
1300
|
+
if (anchored && foreignRoot(file))
|
|
1301
|
+
return unresolved(call, "no-type:crate-root");
|
|
1302
|
+
// `T::f()` with a generic `T` in scope is a bound-based call (part B), never a same-named type.
|
|
1303
|
+
if (!anchored && isGenericParam(caller, first))
|
|
1304
|
+
return unresolved(call, "no-type:generic-param");
|
|
1305
|
+
const li = anchored ? [] : localItems(caller, first);
|
|
1306
|
+
if (li.some((x) => x.kind === "namespace"))
|
|
1307
|
+
return unresolved(call, "no-type:local-item");
|
|
1308
|
+
if (!anchored) {
|
|
1309
|
+
const inline = inlineFn(call, caller, segs);
|
|
1310
|
+
if (inline)
|
|
1311
|
+
return inline;
|
|
1312
|
+
}
|
|
1313
|
+
else if (modContainer(caller)) {
|
|
1314
|
+
return inlineFn(call, caller, segs) ?? unresolved(call, "no-symbol:module");
|
|
1315
|
+
}
|
|
1316
|
+
// A fn-local glob (innermost block) shadows file-level named imports: single segments go through the
|
|
1317
|
+
// type lookup, longer paths are left alone.
|
|
1318
|
+
if (!anchored) {
|
|
1319
|
+
const localNamed = localImportsNamed(file, caller, first);
|
|
1320
|
+
const lg = globs(file, caller, first, true, localNamed.length ? Math.max(...localNamed.map((r) => blockStartOf(caller, r))) : -1);
|
|
1321
|
+
if (lg.hit)
|
|
1322
|
+
return segs.length === 1 ? typeOutcome(call, typeName(first, file, caller), name, "static") : unresolved(call, "no-type:local-glob");
|
|
1323
|
+
}
|
|
1324
|
+
// A first segment that names a local type.
|
|
1325
|
+
if (!anchored && segs.length === 1 && (declsHere(caller, first).length > 0 || li.length > 0))
|
|
1326
|
+
return typeOutcome(call, typeName(first, file, caller), name, "static");
|
|
1327
|
+
const full = expand(segs, file, caller);
|
|
1328
|
+
if (!full)
|
|
1329
|
+
return unresolved(call, "no-type:unknown-type");
|
|
1330
|
+
if (full.t === "ext")
|
|
1331
|
+
return external(call, full.pkg, full.exact, `external path ${segs.join("::")}`);
|
|
1332
|
+
if (anchored || full.via || full.segs !== segs) {
|
|
1333
|
+
const kind = full.via ? (full.via.localName !== full.via.importedName ? "aliased-import" : "namespace-import") : "namespace-import";
|
|
1334
|
+
const viaModule = moduleFn(call, name, full, kind);
|
|
1335
|
+
if (viaModule && moduleFile(full.segs, full.fromFile)) {
|
|
1336
|
+
// A module file exists for the whole prefix: it wins over a type of the same name.
|
|
1337
|
+
return viaModule;
|
|
1338
|
+
}
|
|
1339
|
+
const tres = fromDecls(itemsVia({ t: "path", fromFile: full.fromFile, segs: full.segs }).filter((s) => TYPE_KINDS.has(s.kind)));
|
|
1340
|
+
if (tres)
|
|
1341
|
+
return typeOutcome(call, tres, name, "static");
|
|
1342
|
+
return unresolved(call, anchored ? "no-symbol:module" : "no-type:unknown-type");
|
|
1343
|
+
}
|
|
1344
|
+
// Non-anchored, no alias: local module file, else a type, else std/extern crate.
|
|
1345
|
+
const viaModule = moduleFn(call, name, full, "namespace-import");
|
|
1346
|
+
if (viaModule)
|
|
1347
|
+
return viaModule;
|
|
1348
|
+
if (segs.length === 1) {
|
|
1349
|
+
const t = typeName(first, file, caller);
|
|
1350
|
+
if (t)
|
|
1351
|
+
return typeOutcome(call, t, name, "static");
|
|
1352
|
+
}
|
|
1353
|
+
else {
|
|
1354
|
+
const t = typePath(segs, file, caller);
|
|
1355
|
+
if (t)
|
|
1356
|
+
return typeOutcome(call, t, name, "static");
|
|
1357
|
+
}
|
|
1358
|
+
const g = globs(file, caller, first);
|
|
1359
|
+
if (boundNames(file, first) || g.files.length || g.unknown)
|
|
1360
|
+
return unresolved(call, "no-type:unknown-type");
|
|
1361
|
+
if (STD_TYPES.has(first))
|
|
1362
|
+
return external(call, "std", false, `std type ${first}`);
|
|
1363
|
+
if (/^[a-z_]/.test(first))
|
|
1364
|
+
return external(call, first, false, `extern crate ${first}`);
|
|
1365
|
+
return unresolved(call, "no-type:unknown-type");
|
|
1366
|
+
};
|
|
1367
|
+
function resolveEdge(call, caller, node) {
|
|
1368
|
+
const rt = call.receiverText;
|
|
1369
|
+
if (call.evidence.includes("no-type:callee-expression"))
|
|
1370
|
+
return unresolved(call, "no-type:callee-expression");
|
|
1371
|
+
if (rt === undefined)
|
|
1372
|
+
return bareCall(call, caller);
|
|
1373
|
+
const shape = shapeOf(call, caller);
|
|
1374
|
+
if (rt === "self" && shape === "method")
|
|
1375
|
+
return selfMethod(call, caller);
|
|
1376
|
+
if (shape === "path") {
|
|
1377
|
+
const segs = rt.split("::");
|
|
1378
|
+
return rt.startsWith("<") || !segs.every((s) => IDENT.test(s)) ? unresolved(call, "no-type:qualified-path") : pathCall(call, caller, segs);
|
|
1379
|
+
}
|
|
1380
|
+
if (shape === "method")
|
|
1381
|
+
return methodCall(call, caller, node);
|
|
1382
|
+
return unresolved(call, "no-type:call-shape");
|
|
1383
|
+
}
|
|
1384
|
+
for (const call of context.calls) {
|
|
1385
|
+
const caller = byId.get(call.callerId);
|
|
1386
|
+
if (!caller)
|
|
1387
|
+
continue;
|
|
1388
|
+
// Recomputed from scratch each rebuild: clear a previous resolution.
|
|
1389
|
+
call.declaredTargetId = call.resolvedTargetId = call.externalPackage = call.runtimeTargetIds = undefined;
|
|
1390
|
+
call.resolutionKind = "unresolved";
|
|
1391
|
+
call.confidence = "unresolved";
|
|
1392
|
+
if (call.evidence.some((e) => e.startsWith("macro:")) && !isMacroArg(call))
|
|
1393
|
+
continue;
|
|
1394
|
+
currentCall = call;
|
|
1395
|
+
try {
|
|
1396
|
+
resolveEdge(call, caller)();
|
|
1397
|
+
if (isMacroArg(call) && call.confidence === "exact")
|
|
1398
|
+
call.confidence = "probable";
|
|
1399
|
+
}
|
|
1400
|
+
catch {
|
|
1401
|
+
// One bad edge (e.g. a file that vanished mid-rebuild) must not abort the rebuild for every language.
|
|
1402
|
+
leaveUnresolvedOnError(call);
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1405
|
+
}
|