ucn 5.5.1 → 5.5.3
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/.claude/skills/ucn/SKILL.md +35 -9
- package/.claude/skills/ucn/references/commands.md +40 -18
- package/.claude/skills/ucn/references/resolution-rules.md +126 -20
- package/.claude/skills/ucn/references/trust-contract.md +5 -3
- package/README.md +32 -21
- package/cli/index.js +2 -1
- package/core/accessors.js +341 -54
- package/core/account.js +74 -20
- package/core/analysis.js +257 -125
- package/core/ast-analysis.js +12 -3
- package/core/bridge.js +15 -65
- package/core/brief.js +8 -4
- package/core/build-worker.js +2 -1
- package/core/cache.js +137 -6
- package/core/call-name-cache.js +134 -0
- package/core/callable-scope.js +21 -0
- package/core/callers.js +4726 -940
- package/core/check.js +22 -5
- package/core/class-identity.js +399 -12
- package/core/client-receivers.js +189 -0
- package/core/constructor-calls.js +831 -0
- package/core/constructor-hierarchy.js +147 -0
- package/core/constructor-protocol.js +196 -0
- package/core/contract-membership.js +346 -140
- package/core/cpp-conversion-constraints.js +123 -0
- package/core/cpp-conversion-syntax.js +216 -0
- package/core/cpp-conversions.js +414 -0
- package/core/cpp-scope.js +245 -44
- package/core/deadcode.js +61 -16
- package/core/decorator-binding.js +137 -3
- package/core/enum-reference-syntax.js +134 -0
- package/core/execute.js +17 -1
- package/core/external-macros.js +42 -10
- package/core/go-packages.js +171 -0
- package/core/graph-build.js +2 -0
- package/core/graph.js +75 -59
- package/core/ignore-rules.js +2 -1
- package/core/imports.js +145 -19
- package/core/index-ir.js +41 -4
- package/core/inprocess-clients.js +41 -15
- package/core/ir.js +27 -6
- package/core/macro-expansion.js +38 -2
- package/core/out-of-class-members.js +182 -0
- package/core/output/analysis-ext.js +4 -2
- package/core/output/analysis.js +89 -20
- package/core/output/graph.js +12 -3
- package/core/output/lines.js +4 -3
- package/core/output/public.js +2 -0
- package/core/output/reporting.js +12 -1
- package/core/output/shared.js +2 -0
- package/core/output/tracing.js +28 -22
- package/core/output-budget.js +2 -2
- package/core/parallel-build.js +52 -1
- package/core/project.js +217 -112
- package/core/provenance-constructor-base.js +161 -0
- package/core/provenance-facts.js +257 -27
- package/core/provenance-overload.js +458 -54
- package/core/provenance-type.js +487 -0
- package/core/provenance.js +234 -15
- package/core/public-command.js +17 -2
- package/core/query-bodies.js +159 -0
- package/core/reflection.js +270 -2
- package/core/rename-token-syntax.js +111 -0
- package/core/reporting.js +13 -7
- package/core/request-helpers.js +63 -7
- package/core/rust-macro-expansion.js +151 -19
- package/core/rust-modules.js +1 -1
- package/core/rust-names.js +607 -0
- package/core/search.js +70 -17
- package/core/shared.js +98 -1
- package/core/source-memo.js +201 -0
- package/core/text-usage-syntax.js +38 -0
- package/core/tracing.js +7 -1
- package/core/type-denotation.js +124 -37
- package/core/type-references.js +615 -246
- package/core/value-references.js +2777 -0
- package/core/verify.js +1097 -281
- package/languages/adapter.js +28 -4
- package/languages/c-family.js +1118 -164
- package/languages/constructor-bases.js +105 -0
- package/languages/cpp-platform.js +26 -0
- package/languages/cpp-site-syntax.js +83 -0
- package/languages/csharp-scope.js +439 -0
- package/languages/csharp.js +428 -68
- package/languages/go.js +385 -207
- package/languages/html.js +4 -2
- package/languages/index.js +397 -2
- package/languages/java-platform.js +226 -0
- package/languages/java-unicode.js +269 -0
- package/languages/java.js +461 -117
- package/languages/javascript.js +418 -66
- package/languages/lexical-scope.js +190 -23
- package/languages/python.js +404 -54
- package/languages/rust.js +510 -82
- package/languages/utils.js +67 -8
- package/mcp/server.js +8 -2
- package/package.json +3 -3
|
@@ -0,0 +1,607 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* core/rust-names.js - Rust name resolution by item scopes (fix #422).
|
|
3
|
+
*
|
|
4
|
+
* A Rust name resolves through the scopes that bind it, innermost first:
|
|
5
|
+
* the blocks around the site (their items and `use` declarations, recorded
|
|
6
|
+
* on call records as `scopeBinding` by the parser), then the module the
|
|
7
|
+
* site belongs to. A module binds a name by an item it declares or an
|
|
8
|
+
* explicit `use`, which shadow its glob imports; a module never sees its
|
|
9
|
+
* parent's names except through a `use`. Types, values and macros are
|
|
10
|
+
* separate namespaces: a `mod x` never hides a function `x`.
|
|
11
|
+
*
|
|
12
|
+
* `resolveRustName` answers with the project definitions a name denotes
|
|
13
|
+
* (`{ defs }`; configuration alternatives give several), `{ external }` for a
|
|
14
|
+
* name bound outside the project (a standard-library path, or a value no
|
|
15
|
+
* project scope binds: the prelude), or null when the module tree cannot
|
|
16
|
+
* decide (a module whose item macros or includes may declare names, an
|
|
17
|
+
* unresolvable path, a file the module tree cannot place). Callers keep
|
|
18
|
+
* their other evidence rules for null.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
'use strict';
|
|
22
|
+
|
|
23
|
+
const path = require('path');
|
|
24
|
+
const { moduleTree } = require('./rust-modules');
|
|
25
|
+
const { rustInlineChainAt, findCargoRoot, workspaceCrateRegistry } = require('./imports');
|
|
26
|
+
|
|
27
|
+
const FUNCTION_KINDS = new Set(['function', 'method', 'constructor', 'static']);
|
|
28
|
+
const TYPE_KINDS = new Set(['struct', 'enum', 'union', 'trait', 'type', 'module']);
|
|
29
|
+
const ITEM_KINDS = new Set(['function', 'state', 'struct', 'enum', 'union', 'trait', 'type', 'module']);
|
|
30
|
+
|
|
31
|
+
const UNKNOWN = Object.freeze({ unknown: true });
|
|
32
|
+
const ABSENT = Object.freeze({ absent: true });
|
|
33
|
+
const EXTERNAL = Object.freeze({ external: true });
|
|
34
|
+
|
|
35
|
+
function inNamespace(def, ns) {
|
|
36
|
+
if (def.className || def.receiver) return false;
|
|
37
|
+
if (ns === 'type') return TYPE_KINDS.has(def.type);
|
|
38
|
+
if (def.type === 'function' || def.type === 'state') return true;
|
|
39
|
+
return def.type === 'struct' && (def.valueShape === 'unit' || def.valueShape === 'tuple');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function stateOf(index) {
|
|
43
|
+
const memo = index._rustScopeMemo || (index._rustScopeMemo = new Map());
|
|
44
|
+
let state = memo.get('\x01rustNames');
|
|
45
|
+
if (!state) {
|
|
46
|
+
state = { lookups: new Map(), contents: new Map(), inProgress: new Set(), crates: new Map(),
|
|
47
|
+
properRoots: new Map(), standard: null, answers: new Map() };
|
|
48
|
+
memo.set('\x01rustNames', state);
|
|
49
|
+
}
|
|
50
|
+
return state;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function moduleObject(tree, root, segs) {
|
|
54
|
+
const key = `${root}\0${segs.join('::')}`;
|
|
55
|
+
const entries = tree.entries.get(key);
|
|
56
|
+
return entries ? { root, segs, key, entries } : null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** The module a source position belongs to. */
|
|
60
|
+
function moduleAt(index, file, line) {
|
|
61
|
+
const tree = moduleTree(index);
|
|
62
|
+
const own = tree.moduleOf(file);
|
|
63
|
+
return moduleObject(tree, own.root, [...own.segs, ...rustInlineChainAt(tree.inlineByFile.get(file) || [], line)]);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Is `root` a crate root a Cargo target loads (lib.rs/main.rs, a bin, test,
|
|
68
|
+
* example or bench file, build.rs, a manifest path)? A file the module tree
|
|
69
|
+
* leaves unloaded for any other reason (a `mod` inside a macro, an include)
|
|
70
|
+
* is not a crate: its names cannot be resolved.
|
|
71
|
+
*/
|
|
72
|
+
function isProperCrateRoot(index, root) {
|
|
73
|
+
const state = stateOf(index);
|
|
74
|
+
if (state.properRoots.has(root)) return state.properRoots.get(root);
|
|
75
|
+
let proper = false;
|
|
76
|
+
const cargo = findCargoRoot(path.dirname(root));
|
|
77
|
+
if (cargo) {
|
|
78
|
+
const rel = path.relative(cargo.root, root).split(path.sep);
|
|
79
|
+
const src = path.relative(cargo.srcDir, root).split(path.sep);
|
|
80
|
+
const base = rel[rel.length - 1];
|
|
81
|
+
if (rel.length === 1 && (base === 'build.rs' || base === 'lib.rs' || base === 'main.rs')) proper = true;
|
|
82
|
+
else if (src.length === 1 && (src[0] === 'lib.rs' || src[0] === 'main.rs')) proper = true;
|
|
83
|
+
else if (src[0] === 'bin' && (src.length === 2 || (src.length === 3 && src[2] === 'main.rs'))) proper = true;
|
|
84
|
+
else if (['tests', 'examples', 'benches'].includes(rel[0]) &&
|
|
85
|
+
(rel.length === 2 || (rel.length === 3 && base === 'main.rs'))) proper = true;
|
|
86
|
+
else if ((cargo.targetDirs || []).includes(path.dirname(root))) proper = true;
|
|
87
|
+
}
|
|
88
|
+
state.properRoots.set(root, proper);
|
|
89
|
+
return proper;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Crates every target links without a manifest entry.
|
|
93
|
+
const SYSROOT_CRATES = new Set(['std', 'core', 'alloc', 'proc_macro', 'test']);
|
|
94
|
+
|
|
95
|
+
/** The dependency entry a manifest gives `name`, workspace inheritance followed. */
|
|
96
|
+
function manifestDependency(cargo, name) {
|
|
97
|
+
let dep = cargo?.dependencies?.[name];
|
|
98
|
+
if (!dep?.workspace || dep.path) return dep || null;
|
|
99
|
+
for (let dir = path.dirname(cargo.root), seen = 0; seen < 16; seen++) {
|
|
100
|
+
const workspace = findCargoRoot(dir);
|
|
101
|
+
if (!workspace) break;
|
|
102
|
+
const inherited = workspace.dependencies?.[dep.package || name];
|
|
103
|
+
if (inherited && !inherited.workspace) return { ...inherited, ...(dep.package && { package: dep.package }) };
|
|
104
|
+
const up = path.dirname(workspace.root);
|
|
105
|
+
if (up === workspace.root) break;
|
|
106
|
+
dir = up;
|
|
107
|
+
}
|
|
108
|
+
return dep;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* What a crate name denotes from `fromFile` (fix #422): `{ module }` (the
|
|
113
|
+
* library crate root of the own package, a path dependency or a workspace
|
|
114
|
+
* member), `{ external }` (a registry dependency or a sysroot crate), or
|
|
115
|
+
* null.
|
|
116
|
+
*/
|
|
117
|
+
function crateRef(index, tree, fromFile, name) {
|
|
118
|
+
const state = stateOf(index);
|
|
119
|
+
const key = `${path.dirname(fromFile)}\0${name}`;
|
|
120
|
+
if (state.crates.has(key)) return state.crates.get(key);
|
|
121
|
+
let result = null;
|
|
122
|
+
const cargo = findCargoRoot(path.dirname(fromFile));
|
|
123
|
+
let srcDir = null;
|
|
124
|
+
if (cargo?.packageName === name) {
|
|
125
|
+
srcDir = cargo.srcDir;
|
|
126
|
+
} else {
|
|
127
|
+
const dep = manifestDependency(cargo, name);
|
|
128
|
+
if (dep?.path) {
|
|
129
|
+
const target = findCargoRoot(dep.path);
|
|
130
|
+
if (target && path.resolve(target.root) === path.resolve(dep.path)) srcDir = target.srcDir;
|
|
131
|
+
} else if (dep && !dep.workspace) {
|
|
132
|
+
result = EXTERNAL;
|
|
133
|
+
} else if (SYSROOT_CRATES.has(name)) {
|
|
134
|
+
result = EXTERNAL;
|
|
135
|
+
} else if (!dep) {
|
|
136
|
+
const member = workspaceCrateRegistry(index.root).get(name);
|
|
137
|
+
if (member) srcDir = member.srcDir;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
if (srcDir) {
|
|
141
|
+
const lib = path.join(srcDir, 'lib.rs');
|
|
142
|
+
if (index.files.get(lib)?.language === 'rust' && tree.moduleOf(lib).root === lib) {
|
|
143
|
+
result = { module: moduleObject(tree, lib, []) };
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
state.crates.set(key, result);
|
|
147
|
+
return result;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function sameChain(a, b) {
|
|
151
|
+
return a.length === b.length && a.every((part, i) => part === b[i]);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* The scopes a module's own declarations give: explicit `use` bindings by
|
|
156
|
+
* local name, glob imports, and whether an item macro or include may add
|
|
157
|
+
* names the index does not hold.
|
|
158
|
+
*/
|
|
159
|
+
function moduleContent(index, tree, mod) {
|
|
160
|
+
const state = stateOf(index);
|
|
161
|
+
let content = state.contents.get(mod.key);
|
|
162
|
+
if (content) return content;
|
|
163
|
+
const uses = new Map();
|
|
164
|
+
const globs = [];
|
|
165
|
+
let opaque = false;
|
|
166
|
+
// Words an item macro of the module is given: names its expansion may
|
|
167
|
+
// declare (a declared name shadows the module's globs).
|
|
168
|
+
const macroWords = new Set();
|
|
169
|
+
for (const entry of mod.entries) {
|
|
170
|
+
const fileEntry = index.files.get(entry.file);
|
|
171
|
+
if (!fileEntry) { opaque = true; continue; }
|
|
172
|
+
const inline = tree.inlineByFile.get(entry.file) || [];
|
|
173
|
+
const inModule = line => sameChain(rustInlineChainAt(inline, line), entry.chain);
|
|
174
|
+
// A parse error may hide items, unless it lies inside a function
|
|
175
|
+
// body (`&raw const x` an older grammar cannot read).
|
|
176
|
+
if ((fileEntry.parseErrorRegions || []).some(([start, end]) => !(fileEntry.symbols || []).some(symbol =>
|
|
177
|
+
FUNCTION_KINDS.has(symbol.type) && symbol.startLine <= start && symbol.endLine >= end))) opaque = true;
|
|
178
|
+
for (const detail of fileEntry.importDetails || []) {
|
|
179
|
+
if (detail.lexicalScopeStartLine != null || detail.line == null) continue;
|
|
180
|
+
if (detail.type !== 'use' && detail.type !== 'use-glob' && detail.type !== 'include') continue;
|
|
181
|
+
if (!inModule(detail.line)) continue;
|
|
182
|
+
if (detail.type === 'include') { opaque = true; continue; }
|
|
183
|
+
const site = { path: detail.module, file: entry.file, line: detail.line,
|
|
184
|
+
visibility: detail.visibility || null, isUse: true };
|
|
185
|
+
if (detail.type === 'use-glob') { globs.push(site); continue; }
|
|
186
|
+
for (const local of detail.names || []) {
|
|
187
|
+
if (!uses.has(local)) uses.set(local, []);
|
|
188
|
+
uses.get(local).push(site);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
for (const macro of fileEntry.itemMacros || []) {
|
|
192
|
+
if (!inModule(macro.line)) continue;
|
|
193
|
+
for (const word of macro.words || []) macroWords.add(word);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
content = { uses, globs, opaque, macroWords };
|
|
197
|
+
state.contents.set(mod.key, content);
|
|
198
|
+
return content;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/** Items named `name` declared directly in a module, in namespace `ns`. */
|
|
202
|
+
function moduleItems(index, tree, mod, name, ns) {
|
|
203
|
+
const out = [];
|
|
204
|
+
for (const def of index.symbols.get(name) || []) {
|
|
205
|
+
if (!ITEM_KINDS.has(def.type) || !inNamespace(def, ns) || def.lexicalScopeStartLine != null) continue;
|
|
206
|
+
const entry = mod.entries.find(candidate => candidate.file === def.file);
|
|
207
|
+
if (!entry) continue;
|
|
208
|
+
const inline = tree.inlineByFile.get(def.file) || [];
|
|
209
|
+
let chain = rustInlineChainAt(inline, def.startLine);
|
|
210
|
+
// An inline module's own lines lie inside it.
|
|
211
|
+
if (def.type === 'module' && inline.some(mod2 => mod2.name === def.name && mod2.startLine === def.startLine)) {
|
|
212
|
+
chain = chain.slice(0, -1);
|
|
213
|
+
}
|
|
214
|
+
if (!sameChain(chain, entry.chain)) continue;
|
|
215
|
+
const fileEntry = index.files.get(def.file);
|
|
216
|
+
if ((fileEntry?.symbols || []).some(symbol => symbol !== def && FUNCTION_KINDS.has(symbol.type) &&
|
|
217
|
+
encloses(symbol, def))) continue;
|
|
218
|
+
out.push(def);
|
|
219
|
+
}
|
|
220
|
+
return out;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Does function `outer` enclose item `inner`? By source spans (byte offsets;
|
|
225
|
+
* generated items carry ordered fractional offsets within their expansion),
|
|
226
|
+
* else by lines.
|
|
227
|
+
*/
|
|
228
|
+
function encloses(outer, inner) {
|
|
229
|
+
const a = outer.sourceSpan;
|
|
230
|
+
const b = inner.sourceSpan;
|
|
231
|
+
if (Number.isFinite(a?.start) && Number.isFinite(a?.end) && Number.isFinite(b?.start) && Number.isFinite(b?.end)) {
|
|
232
|
+
return a.start <= b.start && a.end >= b.end && (a.start !== b.start || a.end !== b.end);
|
|
233
|
+
}
|
|
234
|
+
return outer.startLine <= inner.startLine && outer.endLine >= inner.endLine &&
|
|
235
|
+
!(outer.startLine === inner.startLine && outer.name === inner.name);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function childModuleOf(tree, mod, def) {
|
|
239
|
+
return moduleObject(tree, mod.root, [...mod.segs, def.name]);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/** Can a glob in `site` (module position) import an item with `modifiers` declared at (file, line)? */
|
|
243
|
+
function visibleFrom(index, visibility, file, line, siteFile, siteLine) {
|
|
244
|
+
const { rustItemAccessible } = require('./rust-modules');
|
|
245
|
+
const mods = Array.isArray(visibility) ? visibility : visibility ? [visibility] : [];
|
|
246
|
+
const normalized = mods.map(m => String(m).replace(/\s+/g, ''));
|
|
247
|
+
if (normalized.some(m => m.startsWith('pub(in'))) return null;
|
|
248
|
+
return rustItemAccessible(index, normalized, file, line, siteFile, siteLine);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function merge(results) {
|
|
252
|
+
const defs = new Map();
|
|
253
|
+
let external = false;
|
|
254
|
+
let unknown = false;
|
|
255
|
+
let cut = false;
|
|
256
|
+
for (const result of results) {
|
|
257
|
+
if (result.cut) cut = true;
|
|
258
|
+
if (result.unknown) unknown = true;
|
|
259
|
+
else if (result.external) external = true;
|
|
260
|
+
for (const item of result.defs || []) {
|
|
261
|
+
defs.set(`${item.def.file}\0${item.def.startLine}\0${item.def.type}\0${item.def.name}`, item);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
return { defs: [...defs.values()], external, unknown, cut };
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function finish(state, key, result) {
|
|
268
|
+
if (!result.cut) state.lookups.set(key, result);
|
|
269
|
+
return result;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* What `name` denotes in namespace `ns` inside module `mod` (as a glob or a
|
|
274
|
+
* path of another module reaches it). `site` is the importing position for
|
|
275
|
+
* glob visibility, or null for a path (a compiling path names an item its
|
|
276
|
+
* site may access).
|
|
277
|
+
*/
|
|
278
|
+
function lookupInModule(index, tree, mod, name, ns, site, depth) {
|
|
279
|
+
const state = stateOf(index);
|
|
280
|
+
if (!isProperCrateRoot(index, mod.root)) return UNKNOWN;
|
|
281
|
+
const key = `${mod.key}\0${name}\0${ns}\0${site ? `${site.file}\0${site.line}` : ''}`;
|
|
282
|
+
const memo = state.lookups.get(key);
|
|
283
|
+
if (memo) return memo;
|
|
284
|
+
if (state.inProgress.has(key)) return { absent: true, cut: true };
|
|
285
|
+
if (depth > 16) return UNKNOWN;
|
|
286
|
+
state.inProgress.add(key);
|
|
287
|
+
try {
|
|
288
|
+
const content = moduleContent(index, tree, mod);
|
|
289
|
+
const explicit = [];
|
|
290
|
+
for (const def of moduleItems(index, tree, mod, name, ns)) {
|
|
291
|
+
if (site) {
|
|
292
|
+
const visible = visibleFrom(index, def.modifiers, def.file, def.startLine, site.file, site.line);
|
|
293
|
+
if (visible === null) { explicit.push(UNKNOWN); continue; }
|
|
294
|
+
if (!visible) continue;
|
|
295
|
+
}
|
|
296
|
+
explicit.push({ defs: [{ def, module: mod }] });
|
|
297
|
+
}
|
|
298
|
+
for (const use of content.uses.get(name) || []) {
|
|
299
|
+
if (site) {
|
|
300
|
+
const visible = visibleFrom(index, use.visibility, use.file, use.line, site.file, site.line);
|
|
301
|
+
if (visible === null) { explicit.push(UNKNOWN); continue; }
|
|
302
|
+
if (!visible) continue;
|
|
303
|
+
}
|
|
304
|
+
explicit.push(resolveUsePath(index, tree, mod, use, ns, depth + 1));
|
|
305
|
+
}
|
|
306
|
+
const found = explicit.filter(result => !result.absent);
|
|
307
|
+
const cutExplicit = explicit.some(result => result.cut);
|
|
308
|
+
if (found.length > 0) {
|
|
309
|
+
const merged = merge(found);
|
|
310
|
+
merged.cut = merged.cut || cutExplicit;
|
|
311
|
+
if (merged.unknown || (merged.external && merged.defs.length > 0)) {
|
|
312
|
+
return finish(state, key, { unknown: true, cut: merged.cut });
|
|
313
|
+
}
|
|
314
|
+
if (merged.defs.length > 0) return finish(state, key, { defs: merged.defs, cut: merged.cut });
|
|
315
|
+
return finish(state, key, { external: true, cut: merged.cut });
|
|
316
|
+
}
|
|
317
|
+
// Globs: an explicit name (an item a macro may still declare)
|
|
318
|
+
// shadows them.
|
|
319
|
+
const fromGlobs = [];
|
|
320
|
+
const opaque = content.opaque || content.macroWords.has(name);
|
|
321
|
+
let globUnknown = opaque;
|
|
322
|
+
let globExternal = false;
|
|
323
|
+
let cut = cutExplicit;
|
|
324
|
+
for (const glob of content.globs) {
|
|
325
|
+
// An outer glob reaches this module's glob imports only where
|
|
326
|
+
// the `use` is visible to it.
|
|
327
|
+
if (site) {
|
|
328
|
+
const visible = visibleFrom(index, glob.visibility, glob.file, glob.line, site.file, site.line);
|
|
329
|
+
if (visible === null) { globUnknown = true; continue; }
|
|
330
|
+
if (!visible) continue;
|
|
331
|
+
}
|
|
332
|
+
const result = resolveGlob(index, tree, mod, glob, name, ns, depth + 1);
|
|
333
|
+
if (result.cut) cut = true;
|
|
334
|
+
if (result.defs?.length > 0) fromGlobs.push(result);
|
|
335
|
+
else if (result.unknown) globUnknown = true;
|
|
336
|
+
else if (result.external) globExternal = true;
|
|
337
|
+
}
|
|
338
|
+
if (fromGlobs.length > 0 && !opaque) {
|
|
339
|
+
const merged = merge(fromGlobs);
|
|
340
|
+
return finish(state, key, { defs: merged.defs, cut: cut || merged.cut });
|
|
341
|
+
}
|
|
342
|
+
if (globUnknown) return finish(state, key, { unknown: true, cut });
|
|
343
|
+
if (globExternal) return finish(state, key, { external: true, cut });
|
|
344
|
+
return finish(state, key, cut ? { absent: true, cut: true } : ABSENT);
|
|
345
|
+
} finally {
|
|
346
|
+
state.inProgress.delete(key);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/** A glob import's contribution of `name`. */
|
|
351
|
+
function resolveGlob(index, tree, mod, glob, name, ns, depth) {
|
|
352
|
+
const target = resolvePathPrefix(index, tree, mod, glob, String(glob.path || '').split('::'), depth);
|
|
353
|
+
const cut = !!target.cut;
|
|
354
|
+
if (target.unknown) return cut ? { unknown: true, cut } : UNKNOWN;
|
|
355
|
+
if (target.external) return cut ? { external: true, cut } : EXTERNAL;
|
|
356
|
+
const site = { file: glob.file, line: glob.line };
|
|
357
|
+
if (target.module) {
|
|
358
|
+
const result = lookupInModule(index, tree, target.module, name, ns, site, depth);
|
|
359
|
+
return cut && !result.cut ? { ...result, cut } : result;
|
|
360
|
+
}
|
|
361
|
+
if (target.enumDef) {
|
|
362
|
+
const variants = (index.symbols.get(name) || []).filter(def => def.type === 'variant' &&
|
|
363
|
+
def.className === target.enumDef.name && def.file === target.enumDef.file);
|
|
364
|
+
return variants.length > 0 ? { defs: variants.map(def => ({ def, module: null })), cut }
|
|
365
|
+
: { absent: true, cut };
|
|
366
|
+
}
|
|
367
|
+
return { unknown: true, cut };
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* Resolve the module (or type) a path prefix names from `mod`. Returns `{
|
|
372
|
+
* module }`, `{ enumDef }`, `{ typeDefs }`, `{ external }` or `{ unknown }`,
|
|
373
|
+
* with `cut` when a lookup on the way met a glob cycle.
|
|
374
|
+
*/
|
|
375
|
+
function resolvePathPrefix(index, tree, mod, use, segments, depth) {
|
|
376
|
+
if (depth > 16 || segments.length === 0) return UNKNOWN;
|
|
377
|
+
let i = 0;
|
|
378
|
+
let current;
|
|
379
|
+
let cut = false;
|
|
380
|
+
const done = result => (cut ? { ...result, cut } : result);
|
|
381
|
+
const first = segments[0];
|
|
382
|
+
if (first === 'crate' || first === '$crate') {
|
|
383
|
+
current = moduleObject(tree, mod.root, []);
|
|
384
|
+
i = 1;
|
|
385
|
+
} else if (first === 'self' || first === 'super') {
|
|
386
|
+
let segs = mod.segs;
|
|
387
|
+
if (first === 'self') i = 1;
|
|
388
|
+
while (segments[i] === 'super') {
|
|
389
|
+
if (segs.length === 0) return UNKNOWN;
|
|
390
|
+
segs = segs.slice(0, -1);
|
|
391
|
+
i++;
|
|
392
|
+
}
|
|
393
|
+
current = moduleObject(tree, mod.root, segs);
|
|
394
|
+
} else if (first === '') {
|
|
395
|
+
// `::name` is an extern crate.
|
|
396
|
+
const crate = crateRef(index, tree, use.file, segments[1]);
|
|
397
|
+
if (!crate) return UNKNOWN;
|
|
398
|
+
if (crate.external) return EXTERNAL;
|
|
399
|
+
current = crate.module;
|
|
400
|
+
i = 2;
|
|
401
|
+
} else {
|
|
402
|
+
// A name in scope, else an extern crate (a name both would be is
|
|
403
|
+
// an ambiguity error). In the 2015 edition a `use` path starts at
|
|
404
|
+
// the crate root.
|
|
405
|
+
const scope = use.isUse && findCargoRoot(path.dirname(use.file))?.edition === '2015'
|
|
406
|
+
? moduleObject(tree, mod.root, []) || mod : mod;
|
|
407
|
+
const local = lookupInModule(index, tree, scope, first, 'type', null, depth + 1);
|
|
408
|
+
if (local.cut) cut = true;
|
|
409
|
+
if (local.defs?.length > 0) {
|
|
410
|
+
const step = stepInto(tree, local.defs, segments.length === 1);
|
|
411
|
+
if (!step) return done(UNKNOWN);
|
|
412
|
+
if (!step.module) return done(segments.length === 1 ? step : UNKNOWN);
|
|
413
|
+
current = step.module;
|
|
414
|
+
} else {
|
|
415
|
+
const crate = crateRef(index, tree, use.file, first);
|
|
416
|
+
if (crate?.external) return done(EXTERNAL);
|
|
417
|
+
if (!crate) return done(UNKNOWN);
|
|
418
|
+
current = crate.module;
|
|
419
|
+
}
|
|
420
|
+
i = 1;
|
|
421
|
+
}
|
|
422
|
+
if (!current) return done(UNKNOWN);
|
|
423
|
+
for (; i < segments.length; i++) {
|
|
424
|
+
const result = lookupInModule(index, tree, current, segments[i], 'type', null, depth + 1);
|
|
425
|
+
if (result.cut) cut = true;
|
|
426
|
+
if (result.external) return done(EXTERNAL);
|
|
427
|
+
if (!(result.defs?.length > 0)) return done(UNKNOWN);
|
|
428
|
+
const step = stepInto(tree, result.defs, i === segments.length - 1);
|
|
429
|
+
if (!step) return done(UNKNOWN);
|
|
430
|
+
if (!step.module) return done(i === segments.length - 1 ? step : UNKNOWN);
|
|
431
|
+
current = step.module;
|
|
432
|
+
}
|
|
433
|
+
return done({ module: current });
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/** The next path segment's scope from the definitions it names. */
|
|
437
|
+
function stepInto(tree, items, last) {
|
|
438
|
+
const modules = items.filter(item => item.def.type === 'module');
|
|
439
|
+
if (modules.length === items.length) {
|
|
440
|
+
const targets = modules.map(item => item.crate || (item.module && childModuleOf(tree, item.module, item.def)));
|
|
441
|
+
if (targets.some(target => !target)) return null;
|
|
442
|
+
const keys = new Set(targets.map(target => target.key));
|
|
443
|
+
return keys.size === 1 ? { module: targets[0] } : null;
|
|
444
|
+
}
|
|
445
|
+
if (modules.length > 0 || !last) return null;
|
|
446
|
+
const enums = items.filter(item => item.def.type === 'enum');
|
|
447
|
+
if (enums.length === items.length && enums.length === 1) return { enumDef: enums[0].def };
|
|
448
|
+
return { typeDefs: items.map(item => item.def) };
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/** What a `use` path binds in `ns`, resolved from the module that declares it. */
|
|
452
|
+
function resolveUsePath(index, tree, mod, use, ns, depth) {
|
|
453
|
+
const segments = String(use.path || '').split('::');
|
|
454
|
+
if (segments.length === 0 || segments.slice(1).some(segment => segment === '')) return UNKNOWN;
|
|
455
|
+
const last = segments[segments.length - 1];
|
|
456
|
+
if (segments.length === 1 || (segments.length === 2 && segments[0] === '')) {
|
|
457
|
+
// `use name;` binds a crate (type namespace) or an item in scope.
|
|
458
|
+
if (ns === 'type') {
|
|
459
|
+
const crate = crateRef(index, tree, use.file, last);
|
|
460
|
+
if (crate?.external) return EXTERNAL;
|
|
461
|
+
if (crate?.module) return { defs: [{ def: { type: 'module', name: last, file: crate.module.root, startLine: 0 },
|
|
462
|
+
module: null, crate: crate.module }] };
|
|
463
|
+
}
|
|
464
|
+
return UNKNOWN;
|
|
465
|
+
}
|
|
466
|
+
const target = resolvePathPrefix(index, tree, mod, use, segments.slice(0, -1), depth);
|
|
467
|
+
const cut = !!target.cut;
|
|
468
|
+
if (target.unknown) return cut ? { unknown: true, cut } : UNKNOWN;
|
|
469
|
+
if (target.external) return cut ? { external: true, cut } : EXTERNAL;
|
|
470
|
+
if (target.module) {
|
|
471
|
+
const result = lookupInModule(index, tree, target.module, last, ns, null, depth);
|
|
472
|
+
return cut && !result.cut ? { ...result, cut } : result;
|
|
473
|
+
}
|
|
474
|
+
if (target.enumDef) {
|
|
475
|
+
const variants = (index.symbols.get(last) || []).filter(def => def.type === 'variant' &&
|
|
476
|
+
def.className === target.enumDef.name && def.file === target.enumDef.file);
|
|
477
|
+
return variants.length > 0 ? { defs: variants.map(def => ({ def, module: null })), cut }
|
|
478
|
+
: { unknown: true, cut };
|
|
479
|
+
}
|
|
480
|
+
return { unknown: true, cut };
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
function publicResult(result) {
|
|
484
|
+
if (!result || result.unknown) return null;
|
|
485
|
+
if (result.defs?.length > 0) return { defs: result.defs.map(item => item.def) };
|
|
486
|
+
if (result.external || result.absent) return { external: true, ...(result.absent && { unbound: true }) };
|
|
487
|
+
return null;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
/**
|
|
491
|
+
* What a bare name written at (file, line) denotes in namespace `ns`
|
|
492
|
+
* ('value' or 'type'). `scopeBinding` is the parser's record of the
|
|
493
|
+
* enclosing blocks' binding of the name (fix #422), if any.
|
|
494
|
+
* @returns {{ defs: object[] } | { external: true, unbound?: true } | null}
|
|
495
|
+
*/
|
|
496
|
+
function resolveRustName(index, file, line, name, ns, scopeBinding = null) {
|
|
497
|
+
return answer(index, `n\0${file}\0${line}\0${name}\0${ns}\0${bindingKey(scopeBinding)}`,
|
|
498
|
+
() => resolveRustNameUncached(index, file, line, name, ns, scopeBinding));
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
/**
|
|
502
|
+
* One answer per question for the index's lifetime: a public answer is
|
|
503
|
+
* complete (no lookup of it is in progress when it starts), and every
|
|
504
|
+
* engine query asks the same names at the same sites again.
|
|
505
|
+
*/
|
|
506
|
+
function answer(index, key, compute) {
|
|
507
|
+
const answers = stateOf(index).answers;
|
|
508
|
+
if (answers.has(key)) return answers.get(key);
|
|
509
|
+
const result = compute();
|
|
510
|
+
answers.set(key, result);
|
|
511
|
+
return result;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
function bindingKey(binding) {
|
|
515
|
+
if (!binding) return '';
|
|
516
|
+
return `${binding.kind}\x02${binding.path || ''}\x02${binding.line ?? ''}\x02` +
|
|
517
|
+
`${(binding.innerGlobs || []).join(',')}\x02${(binding.paths || []).join(',')}`;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
function resolveRustNameUncached(index, file, line, name, ns, scopeBinding) {
|
|
521
|
+
const entry = index.files.get(file);
|
|
522
|
+
if (entry?.language !== 'rust' || !name) return null;
|
|
523
|
+
const tree = moduleTree(index);
|
|
524
|
+
const mod = moduleAt(index, file, line);
|
|
525
|
+
if (!mod || !isProperCrateRoot(index, mod.root)) return null;
|
|
526
|
+
if (scopeBinding) {
|
|
527
|
+
if (scopeBinding.innerGlobs) return null;
|
|
528
|
+
if (scopeBinding.kind === 'use') {
|
|
529
|
+
const use = { path: scopeBinding.path, file, line: scopeBinding.line, isUse: true };
|
|
530
|
+
const result = resolveUsePath(index, tree, mod, use, ns, 0);
|
|
531
|
+
if (result.absent) return null;
|
|
532
|
+
return publicResult(result);
|
|
533
|
+
}
|
|
534
|
+
if (scopeBinding.kind === 'item') {
|
|
535
|
+
const defs = (index.symbols.get(name) || []).filter(def => def.file === file &&
|
|
536
|
+
def.startLine === scopeBinding.line && inNamespace(def, ns));
|
|
537
|
+
return defs.length > 0 ? { defs } : null;
|
|
538
|
+
}
|
|
539
|
+
if (scopeBinding.kind === 'globs') {
|
|
540
|
+
const results = scopeBinding.paths.map(globPath =>
|
|
541
|
+
resolveGlob(index, tree, mod, { path: globPath, file, line, isUse: true }, name, ns, 0));
|
|
542
|
+
const found = results.filter(result => result.defs?.length > 0);
|
|
543
|
+
if (found.length > 0) return publicResult(merge(found));
|
|
544
|
+
if (results.some(result => result.unknown)) return null;
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
const result = lookupInModule(index, tree, mod, name, ns, null, 0);
|
|
548
|
+
if (result.absent) {
|
|
549
|
+
// No scope binds it: a value or a prelude type/trait is the
|
|
550
|
+
// prelude's; any other type name is unbound here.
|
|
551
|
+
const prelude = ns === 'value' ||
|
|
552
|
+
!!require('../languages').langTraits(entry.language)?.implicitPreludeNames?.has(name);
|
|
553
|
+
return prelude ? { external: true, unbound: true } : null;
|
|
554
|
+
}
|
|
555
|
+
return publicResult(result);
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
/**
|
|
559
|
+
* What the qualifier of a path written at (file, line) names in the type
|
|
560
|
+
* namespace: `{ defs }` (a trait, type or module), `{ external: true }`, or
|
|
561
|
+
* null. `receiverScopeBinding` is the parser's block binding of its first
|
|
562
|
+
* segment.
|
|
563
|
+
*/
|
|
564
|
+
function resolveRustPathQualifier(index, file, line, qualifier, receiverScopeBinding = null) {
|
|
565
|
+
return answer(index, `q\0${file}\0${line}\0${qualifier}\0${bindingKey(receiverScopeBinding)}`,
|
|
566
|
+
() => resolveRustPathQualifierUncached(index, file, line, qualifier, receiverScopeBinding));
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
function resolveRustPathQualifierUncached(index, file, line, qualifier, receiverScopeBinding) {
|
|
570
|
+
const entry = index.files.get(file);
|
|
571
|
+
if (entry?.language !== 'rust' || !qualifier) return null;
|
|
572
|
+
const segments = String(qualifier).split('::');
|
|
573
|
+
if (segments.some(segment => !/^[A-Za-z_][A-Za-z0-9_]*$/.test(segment) && segment !== '$crate')) return null;
|
|
574
|
+
const tree = moduleTree(index);
|
|
575
|
+
const mod = moduleAt(index, file, line);
|
|
576
|
+
if (!mod || !isProperCrateRoot(index, mod.root)) return null;
|
|
577
|
+
if (segments.length === 1) {
|
|
578
|
+
if (['Self', 'self', 'super', 'crate'].includes(segments[0])) return null;
|
|
579
|
+
return resolveRustName(index, file, line, segments[0], 'type', receiverScopeBinding);
|
|
580
|
+
}
|
|
581
|
+
if (receiverScopeBinding) return null;
|
|
582
|
+
const use = { path: qualifier, file, line };
|
|
583
|
+
const result = resolveUsePath(index, tree, mod, use, 'type', 0);
|
|
584
|
+
if (result.absent) return null;
|
|
585
|
+
return publicResult(result);
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
/**
|
|
589
|
+
* What a module-level `use` path written in `file` at `line` binds in `ns`
|
|
590
|
+
* (fix #422): `{ defs }`, `{ external: true }`, or null.
|
|
591
|
+
*/
|
|
592
|
+
function resolveRustUsePath(index, file, line, usePath, ns) {
|
|
593
|
+
return answer(index, `u\0${file}\0${line}\0${usePath}\0${ns}`,
|
|
594
|
+
() => resolveRustUsePathUncached(index, file, line, usePath, ns));
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
function resolveRustUsePathUncached(index, file, line, usePath, ns) {
|
|
598
|
+
if (index.files.get(file)?.language !== 'rust' || !usePath) return null;
|
|
599
|
+
const tree = moduleTree(index);
|
|
600
|
+
const mod = moduleAt(index, file, line);
|
|
601
|
+
if (!mod || !isProperCrateRoot(index, mod.root)) return null;
|
|
602
|
+
const result = resolveUsePath(index, tree, mod, { path: usePath, file, line, isUse: true }, ns, 0);
|
|
603
|
+
if (result.absent) return null;
|
|
604
|
+
return publicResult(result);
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
module.exports = { resolveRustName, resolveRustPathQualifier, resolveRustUsePath };
|