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
package/core/check.js
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
'use strict';
|
|
13
13
|
|
|
14
14
|
const { diffImpact, composeAccount, callNotResolvedEntries } = require('./analysis');
|
|
15
|
+
const { isValueDefinition } = require('./shared');
|
|
15
16
|
|
|
16
17
|
function summarizeAccount(account) {
|
|
17
18
|
if (!account) {
|
|
@@ -123,13 +124,22 @@ function check(index, options = {}) {
|
|
|
123
124
|
for (const fn of changed) {
|
|
124
125
|
const filePath = fn.relativePath || fn.file || '';
|
|
125
126
|
if (fn._nonCallable) {
|
|
126
|
-
const dependency = fn.impact?.typeReferences || fn.impact?.propertyAccesses;
|
|
127
|
+
const dependency = fn.impact?.typeReferences || fn.impact?.propertyAccesses || fn.impact?.valueAccesses;
|
|
128
|
+
// A value declaration (field, constant, module variable) is read
|
|
129
|
+
// and written through its value-access sites (fix #420): its
|
|
130
|
+
// change is reviewed there, as a function body change is through
|
|
131
|
+
// its callers. A type's shape and inheritance are not checked.
|
|
132
|
+
const valueDeclaration = isValueDefinition(fn);
|
|
127
133
|
items.push({
|
|
128
134
|
name: fn.name, file: filePath, line: fn.startLine, kind: fn._kind,
|
|
129
135
|
symbolType: fn.type, requiresToolchainValidation: true,
|
|
136
|
+
...(valueDeclaration && { valueDeclaration: true }),
|
|
130
137
|
callerCount: fn.impact?.totalCallSites || 0,
|
|
131
138
|
unverifiedCallerCount: fn.impact?.unverifiedSites?.length || 0,
|
|
132
|
-
|
|
139
|
+
// Non-call sites: a value band also lists its call tokens.
|
|
140
|
+
dependencyCount: valueDeclaration && Number.isInteger(fn.impact?.totalDependencySites)
|
|
141
|
+
? Math.max(0, fn.impact.totalDependencySites - (fn.impact.totalCallSites || 0))
|
|
142
|
+
: dependency?.confirmedCount || 0,
|
|
133
143
|
unverifiedDependencyCount: (dependency?.unverifiedCount || 0) + (fn.remainingReferences?.length || 0),
|
|
134
144
|
signatureMismatches: 0,
|
|
135
145
|
account: summarizeAccount(fn.impact?.account),
|
|
@@ -237,7 +247,13 @@ function check(index, options = {}) {
|
|
|
237
247
|
// Action items
|
|
238
248
|
const actions = [];
|
|
239
249
|
for (const it of items) {
|
|
240
|
-
if (it.requiresToolchainValidation) {
|
|
250
|
+
if (it.requiresToolchainValidation && it.valueDeclaration && it.kind !== 'deleted') {
|
|
251
|
+
actions.push({
|
|
252
|
+
severity: 'warn',
|
|
253
|
+
kind: 'value_change',
|
|
254
|
+
message: `${it.name}: ${it.kind} ${it.symbolType}; review its ${it.dependencyCount + it.callerCount} value access site(s) and run the compiler/type checker and tests`,
|
|
255
|
+
});
|
|
256
|
+
} else if (it.requiresToolchainValidation) {
|
|
241
257
|
actions.push({
|
|
242
258
|
severity: it.kind === 'added' ? 'warn' : 'error',
|
|
243
259
|
kind: 'declaration_change',
|
|
@@ -308,10 +324,11 @@ function check(index, options = {}) {
|
|
|
308
324
|
const signatureMismatches = items.reduce((sum, it) => sum + (it.signatureMismatches || 0), 0);
|
|
309
325
|
const filteredEdges = items.reduce((sum, it) => sum + (it.account ? it.account.filtered || 0 : 0), 0);
|
|
310
326
|
const usageReviewSymbols = items.filter(it => it.account && it.account.requiresUsageReview).length;
|
|
311
|
-
const unvalidatedDeclarations = items.filter(it => it.requiresToolchainValidation && it.kind !== 'added'
|
|
327
|
+
const unvalidatedDeclarations = items.filter(it => it.requiresToolchainValidation && it.kind !== 'added' &&
|
|
328
|
+
!(it.valueDeclaration && it.kind === 'modified')).length;
|
|
312
329
|
const reviewRequired = incompleteAccounts > 0 || unverifiedCallSites > 0 ||
|
|
313
330
|
signatureMismatches > 0 || filteredEdges > 0 || usageReviewSymbols > 0 ||
|
|
314
|
-
actions.some(a => a.kind === 'orphan_new' || a.kind === 'declaration_change') ||
|
|
331
|
+
actions.some(a => a.kind === 'orphan_new' || a.kind === 'declaration_change' || a.kind === 'value_change') ||
|
|
315
332
|
!!(limit && allChanged.length > limit);
|
|
316
333
|
|
|
317
334
|
return {
|
package/core/class-identity.js
CHANGED
|
@@ -33,6 +33,7 @@ const path = require('path');
|
|
|
33
33
|
const { langTraits } = require('../languages');
|
|
34
34
|
const { splitParentList } = require('./graph-build');
|
|
35
35
|
const { genericArityOf } = require('../languages/utils');
|
|
36
|
+
const { symbolsSpanning } = require('./shared');
|
|
36
37
|
|
|
37
38
|
const OWNER_CLASS_KINDS = new Set([
|
|
38
39
|
'class', 'struct', 'interface', 'trait', 'enum', 'record', 'union',
|
|
@@ -40,6 +41,32 @@ const OWNER_CLASS_KINDS = new Set([
|
|
|
40
41
|
const TRANSITIVE_IMPORT_DEPTH = 4;
|
|
41
42
|
const TRANSITIVE_IMPORT_FILE_CAP = 2000;
|
|
42
43
|
|
|
44
|
+
/** Complete a plain C-family declaration in the consuming translation unit.
|
|
45
|
+
* A declaration's name alone never selects a definition in another program. */
|
|
46
|
+
function completeForwardDeclaration(index, declaration, useFile) {
|
|
47
|
+
if (!declaration?.forwardDeclaration || !useFile ||
|
|
48
|
+
!langTraits(index.files.get(declaration.file)?.language)?.textualIncludes ||
|
|
49
|
+
declaration.anonymousNamespace || declaration.ppBranch ||
|
|
50
|
+
declaration.generics?.length || declaration.specialization ||
|
|
51
|
+
declaration.lexicalScopeStartLine || declaration.typedefName) return declaration;
|
|
52
|
+
const { includeClosure, effectiveNamespace } = require('./cpp-scope');
|
|
53
|
+
const visible = includeClosure(index, useFile);
|
|
54
|
+
if (declaration.file !== useFile && !visible.has(declaration.file)) return declaration;
|
|
55
|
+
const namespace = effectiveNamespace(index, declaration);
|
|
56
|
+
const recordKind = kind => ['class', 'struct'].includes(kind) ? 'record' : kind;
|
|
57
|
+
const providers = (index.symbols.get(declaration.name) || []).filter(candidate =>
|
|
58
|
+
OWNER_CLASS_KINDS.has(candidate.type) && !candidate.forwardDeclaration &&
|
|
59
|
+
!candidate.aliasOf && !candidate.typedefName && !candidate.anonymousNamespace &&
|
|
60
|
+
!candidate.ppBranch && !candidate.generics?.length && !candidate.specialization &&
|
|
61
|
+
!candidate.lexicalScopeStartLine &&
|
|
62
|
+
langTraits(index.files.get(candidate.file)?.language)?.textualIncludes &&
|
|
63
|
+
recordKind(candidate.type) === recordKind(declaration.type) &&
|
|
64
|
+
(candidate.enclosingType || '') === (declaration.enclosingType || '') &&
|
|
65
|
+
effectiveNamespace(index, candidate) === namespace &&
|
|
66
|
+
(candidate.file === useFile || visible.has(candidate.file)));
|
|
67
|
+
return providers.length === 1 ? providers[0] : declaration;
|
|
68
|
+
}
|
|
69
|
+
|
|
43
70
|
function _state(index) {
|
|
44
71
|
let state = index._classIdentity;
|
|
45
72
|
if (!state) {
|
|
@@ -47,12 +74,15 @@ function _state(index) {
|
|
|
47
74
|
named: new Map(),
|
|
48
75
|
classKeys: new WeakMap(),
|
|
49
76
|
owners: new WeakMap(),
|
|
77
|
+
lexicalOwners: new WeakMap(),
|
|
78
|
+
lexicalTypes: new WeakMap(),
|
|
50
79
|
resolved: new Map(),
|
|
51
80
|
parents: new Map(),
|
|
52
81
|
modules: new Map(),
|
|
53
82
|
// fix #365: target-independent derivations repeated for every
|
|
54
83
|
// same-name definition a caller query pins (stats --hot / repo).
|
|
55
84
|
declaring: new WeakMap(), // definitions array -> Map<refKey|mode, result>
|
|
85
|
+
linearizations: new Map(),
|
|
56
86
|
descendants: new Map(), // class key -> descendant key Set (default cap)
|
|
57
87
|
// fix #389: certain owner definitions, same-file namesake counts,
|
|
58
88
|
// names with function-local class definitions.
|
|
@@ -62,6 +92,8 @@ function _state(index) {
|
|
|
62
92
|
// fix #390: class name -> Set of files declaring it without a
|
|
63
93
|
// possible base elsewhere (see fileDeclaresClass).
|
|
64
94
|
declaringFiles: new Map(),
|
|
95
|
+
// fix #427: forward declaration -> the class it declares.
|
|
96
|
+
forwardProviders: new WeakMap(),
|
|
65
97
|
};
|
|
66
98
|
index._classIdentity = state;
|
|
67
99
|
}
|
|
@@ -85,11 +117,12 @@ function ownerNameOf(member) {
|
|
|
85
117
|
* file (another def whose range strictly contains it), 0 at module level.
|
|
86
118
|
*/
|
|
87
119
|
function _enclosingScopeStart(index, def) {
|
|
88
|
-
const
|
|
89
|
-
const symbols = fileEntry?.symbols;
|
|
120
|
+
const symbols = require('./provenance-overload').containingDeclarations(index, def);
|
|
90
121
|
if (!Array.isArray(symbols)) return 0;
|
|
91
122
|
let best = null;
|
|
92
|
-
|
|
123
|
+
// Only declarations holding the definition's first line can enclose it.
|
|
124
|
+
const around = def.endLine >= def.startLine ? symbolsSpanning(symbols, def.startLine) : symbols;
|
|
125
|
+
for (const s of around) {
|
|
93
126
|
if (s === def || s.startLine == null || s.endLine == null) continue;
|
|
94
127
|
if (s.startLine > def.startLine) continue;
|
|
95
128
|
if (s.endLine < def.endLine) continue;
|
|
@@ -99,10 +132,57 @@ function _enclosingScopeStart(index, def) {
|
|
|
99
132
|
return best ? best.startLine : 0;
|
|
100
133
|
}
|
|
101
134
|
|
|
135
|
+
/**
|
|
136
|
+
* The class a C/C++ forward declaration (`class State;`) declares, when the
|
|
137
|
+
* project defines exactly one class identity of its qualified name and kind
|
|
138
|
+
* (fix #427): every declaration of `Y::State` in a program names one class,
|
|
139
|
+
* and a member access through it compiles only where it is complete.
|
|
140
|
+
* Several definitions (separate programs, a vendored copy) leave it apart.
|
|
141
|
+
*/
|
|
142
|
+
function forwardDeclarationProvider(index, declaration) {
|
|
143
|
+
if (!declaration?.forwardDeclaration || declaration.anonymousNamespace ||
|
|
144
|
+
declaration.specialization || declaration.lexicalScopeStartLine || declaration.typedefName ||
|
|
145
|
+
!langTraits(index.files.get(declaration.file)?.language)?.textualIncludes) return null;
|
|
146
|
+
const state = _state(index);
|
|
147
|
+
if (state.forwardProviders.has(declaration)) return state.forwardProviders.get(declaration);
|
|
148
|
+
const { effectiveNamespace } = require('./cpp-scope');
|
|
149
|
+
const namespace = effectiveNamespace(index, declaration);
|
|
150
|
+
const recordKind = kind => ['class', 'struct'].includes(kind) ? 'record' : kind;
|
|
151
|
+
// Template parameter count (`generics` is the written `<T, U>` list or an array).
|
|
152
|
+
const arity = definition => {
|
|
153
|
+
const generics = definition.generics;
|
|
154
|
+
if (!generics) return 0;
|
|
155
|
+
if (Array.isArray(generics)) return generics.length;
|
|
156
|
+
const inner = String(generics).trim().replace(/^<|>$/g, '').trim();
|
|
157
|
+
return inner ? inner.split(',').length : 0;
|
|
158
|
+
};
|
|
159
|
+
const providers = (index.symbols.get(declaration.name) || []).filter(candidate =>
|
|
160
|
+
candidate !== declaration && OWNER_CLASS_KINDS.has(candidate.type) && !candidate.forwardDeclaration &&
|
|
161
|
+
!candidate.aliasOf && !candidate.typedefName && !candidate.anonymousNamespace &&
|
|
162
|
+
!candidate.specialization && !candidate.lexicalScopeStartLine &&
|
|
163
|
+
langTraits(index.files.get(candidate.file)?.language)?.textualIncludes &&
|
|
164
|
+
recordKind(candidate.type) === recordKind(declaration.type) &&
|
|
165
|
+
(candidate.enclosingType || '') === (declaration.enclosingType || '') &&
|
|
166
|
+
arity(candidate) === arity(declaration) &&
|
|
167
|
+
effectiveNamespace(index, candidate) === namespace);
|
|
168
|
+
// `#if` alternatives of one definition share its key.
|
|
169
|
+
const provider = providers.length > 0 &&
|
|
170
|
+
new Set(providers.map(candidate => classKeyOf(index, candidate))).size === 1 ? providers[0] : null;
|
|
171
|
+
state.forwardProviders.set(declaration, provider);
|
|
172
|
+
return provider;
|
|
173
|
+
}
|
|
174
|
+
|
|
102
175
|
/** Identity key of one class definition. */
|
|
103
176
|
function classKeyOf(index, def) {
|
|
104
177
|
const state = _state(index);
|
|
105
178
|
let key = state.classKeys.get(def);
|
|
179
|
+
if (key === undefined && def?.forwardDeclaration) {
|
|
180
|
+
const provider = forwardDeclarationProvider(index, def);
|
|
181
|
+
if (provider) {
|
|
182
|
+
key = classKeyOf(index, provider);
|
|
183
|
+
state.classKeys.set(def, key);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
106
186
|
if (key === undefined) {
|
|
107
187
|
// A `partial` type (C#) is ONE class declared across files: its
|
|
108
188
|
// identity is the namespace + enclosing type + name, never the file.
|
|
@@ -110,9 +190,13 @@ function classKeyOf(index, def) {
|
|
|
110
190
|
// the same type-parameter count merge (`partial class P<T>` never
|
|
111
191
|
// joins `partial class P`).
|
|
112
192
|
const arity = def.typeArity ? `|${def.typeArity}` : '';
|
|
193
|
+
const lexicalScope = _enclosingScopeStart(index, def);
|
|
194
|
+
// A nested declaration can share every source line with its
|
|
195
|
+
// enclosing type. The recorded owner still distinguishes its scope.
|
|
196
|
+
const scope = def.enclosingType ? `${lexicalScope}:${def.enclosingType}` : lexicalScope;
|
|
113
197
|
key = Array.isArray(def.modifiers) && def.modifiers.includes('partial')
|
|
114
198
|
? `partial|${def.namespace || ''}|${def.enclosingType || ''}|${def.name}${arity}`
|
|
115
|
-
: `${def.file}|${def.namespace || ''}|${
|
|
199
|
+
: `${def.file}|${def.namespace || ''}|${scope}|${def.name}${arity}`;
|
|
116
200
|
state.classKeys.set(def, key);
|
|
117
201
|
}
|
|
118
202
|
return key;
|
|
@@ -132,6 +216,12 @@ function classDefsNamed(index, name) {
|
|
|
132
216
|
let entries = defs.length <= 1
|
|
133
217
|
? defs.map(def => ({ def, key: `name:${name}` }))
|
|
134
218
|
: defs.map(def => ({ def, key: classKeyOf(index, def) }));
|
|
219
|
+
// A forward declaration of a defined class shares its key (fix #427);
|
|
220
|
+
// the definition represents the class, never the declaration.
|
|
221
|
+
if (entries.length > 1 && entries.some(entry => entry.def.forwardDeclaration)) {
|
|
222
|
+
entries = [...entries.filter(entry => !entry.def.forwardDeclaration),
|
|
223
|
+
...entries.filter(entry => entry.def.forwardDeclaration)];
|
|
224
|
+
}
|
|
135
225
|
const unique = new Set(entries.map(e => e.key)).size <= 1;
|
|
136
226
|
// One identity (a single def, or the parts of one partial/merged type):
|
|
137
227
|
// every consumer uses the name key, so comparisons stay consistent.
|
|
@@ -292,11 +382,22 @@ function _resolveAmong(index, entries, contextFile, namespace) {
|
|
|
292
382
|
const traits = langTraits(language);
|
|
293
383
|
// 2. Directory package scope (Go): every file of the package sees it.
|
|
294
384
|
if (traits?.packageScope === 'directory') {
|
|
295
|
-
|
|
296
|
-
|
|
385
|
+
// The file's own package (fix #425): `package x_test` is another
|
|
386
|
+
// package of the directory, which sees x's names through a dot import.
|
|
387
|
+
const { goBareNameReaches } = require('./go-packages');
|
|
388
|
+
const samePkg = entries.filter(e => goBareNameReaches(index, contextFile, e.def));
|
|
297
389
|
if (samePkg.length > 0) return _single(samePkg);
|
|
298
390
|
}
|
|
299
|
-
// 3.
|
|
391
|
+
// 3. A C# namespace's own members precede the types its using
|
|
392
|
+
// directives import (fix #430): `Post` inside namespace
|
|
393
|
+
// `Dapper.Tests.Performance` is that namespace's Post whatever
|
|
394
|
+
// `using Dapper.Tests.Performance.Dashing;` brings.
|
|
395
|
+
if (traits?.typeNameScoping === 'namespace-usings') {
|
|
396
|
+
const own = _contextNamespace(index, contextFile, namespace);
|
|
397
|
+
const declared = own ? entries.filter(e => e.def.namespace === own) : [];
|
|
398
|
+
if (declared.length > 0) return _single(declared);
|
|
399
|
+
}
|
|
400
|
+
// 4. Direct imports.
|
|
300
401
|
const imports = index.importGraph.get(contextFile);
|
|
301
402
|
if (imports && imports.size > 0) {
|
|
302
403
|
const imported = entries.filter(e => imports.has(e.def.file));
|
|
@@ -317,7 +418,7 @@ function _resolveAmong(index, entries, contextFile, namespace) {
|
|
|
317
418
|
}
|
|
318
419
|
if (imported.length > 0) return _single(imported);
|
|
319
420
|
}
|
|
320
|
-
//
|
|
421
|
+
// 5. Same or enclosing namespace/package (Java packages, C#/C++
|
|
321
422
|
// namespaces), nearest first.
|
|
322
423
|
const ns = _contextNamespace(index, contextFile, namespace);
|
|
323
424
|
if (ns) {
|
|
@@ -329,7 +430,7 @@ function _resolveAmong(index, entries, contextFile, namespace) {
|
|
|
329
430
|
return _single(visible.filter(e => e.def.namespace.length === longest));
|
|
330
431
|
}
|
|
331
432
|
}
|
|
332
|
-
//
|
|
433
|
+
// 6. Re-exports: the nearest transitive import reach, when unique.
|
|
333
434
|
return _transitiveImportMatch(index, contextFile, entries);
|
|
334
435
|
}
|
|
335
436
|
|
|
@@ -374,6 +475,41 @@ function ownerRefOf(index, member) {
|
|
|
374
475
|
return ref;
|
|
375
476
|
}
|
|
376
477
|
|
|
478
|
+
/**
|
|
479
|
+
* A nested callable's lexical type for simple-name lookup (fix #412).
|
|
480
|
+
* This is not member ownership: a local function stays a function, while
|
|
481
|
+
* names in its body can reach the surrounding type's members. A nested
|
|
482
|
+
* type's enclosing type is found by the same containment rule.
|
|
483
|
+
*/
|
|
484
|
+
function lexicalClassRefOf(index, definition) {
|
|
485
|
+
if (ownerNameOf(definition)) return ownerRefOf(index, definition);
|
|
486
|
+
if (!definition?.file || (!definition.isNested && !definition.enclosingType)) return null;
|
|
487
|
+
const entry = index.files.get(definition.file);
|
|
488
|
+
if (!entry || !langTraits(entry.language)?.bareCallReachesMethods) return null;
|
|
489
|
+
const state = _state(index);
|
|
490
|
+
if (state.lexicalOwners.has(definition)) return state.lexicalOwners.get(definition);
|
|
491
|
+
let types = state.lexicalTypes.get(entry);
|
|
492
|
+
if (!types) {
|
|
493
|
+
types = (entry.symbols || []).filter(symbol =>
|
|
494
|
+
OWNER_CLASS_KINDS.has(symbol.type) && !symbol.aliasOf);
|
|
495
|
+
state.lexicalTypes.set(entry, types);
|
|
496
|
+
}
|
|
497
|
+
const containing = types.filter(type => type !== definition &&
|
|
498
|
+
type.startLine <= definition.startLine &&
|
|
499
|
+
type.endLine >= (definition.endLine ?? definition.startLine));
|
|
500
|
+
const width = type => type.endLine - type.startLine;
|
|
501
|
+
const narrowest = Math.min(...containing.map(width));
|
|
502
|
+
const candidates = containing.filter(type => width(type) === narrowest).map(type =>
|
|
503
|
+
classDefsNamed(index, type.name).entries.find(entry => entry.def === type)).filter(Boolean);
|
|
504
|
+
const chosen = candidates.length === 1 ? candidates[0] : null;
|
|
505
|
+
// Equal line ranges can belong to different same-line type bodies.
|
|
506
|
+
// Keep that ambiguity distinct from an out-of-line type declaration.
|
|
507
|
+
const ref = chosen ? { name: chosen.def.name, key: chosen.key, def: chosen.def }
|
|
508
|
+
: containing.length ? { name: null, key: null, def: null } : null;
|
|
509
|
+
state.lexicalOwners.set(definition, ref);
|
|
510
|
+
return ref;
|
|
511
|
+
}
|
|
512
|
+
|
|
377
513
|
/**
|
|
378
514
|
* The class definition a member is declared in, when the language's own
|
|
379
515
|
* scoping makes that certain (fix #389): the innermost same-name type body
|
|
@@ -389,7 +525,9 @@ function ownerDefinitionOf(index, member) {
|
|
|
389
525
|
const state = _state(index);
|
|
390
526
|
if (state.ownerDefs.has(member)) return state.ownerDefs.get(member);
|
|
391
527
|
const defs = (index.symbols.get(name) || []).filter(d => d && d !== member &&
|
|
392
|
-
d.name === name && OWNER_CLASS_KINDS.has(d.type) && d.file === member.file
|
|
528
|
+
d.name === name && OWNER_CLASS_KINDS.has(d.type) && d.file === member.file &&
|
|
529
|
+
(member.namespace == null || (d.namespace || null) === member.namespace) &&
|
|
530
|
+
(member.enclosingType == null || (d.enclosingType || null) === member.enclosingType));
|
|
393
531
|
const span = d => d.endLine - d.startLine;
|
|
394
532
|
const containing = (list, line, endLine = line) => list.filter(d =>
|
|
395
533
|
d.startLine <= line && d.endLine >= endLine);
|
|
@@ -515,7 +653,8 @@ function parentRefsOf(index, ref) {
|
|
|
515
653
|
const out = [];
|
|
516
654
|
const seen = new Set();
|
|
517
655
|
const push = parent => {
|
|
518
|
-
const k = parent.key || `?${parent.name}${parent.external ? '!' : ''}
|
|
656
|
+
const k = parent.key || `?${parent.name}${parent.external ? '!' : ''}` +
|
|
657
|
+
(parent.alternatives ? `#${parent.alternatives.map(alternative => alternative.key).join('#')}` : '');
|
|
519
658
|
if (seen.has(k)) return;
|
|
520
659
|
seen.add(k);
|
|
521
660
|
out.push(parent);
|
|
@@ -541,8 +680,32 @@ function parentRefsOf(index, ref) {
|
|
|
541
680
|
// language's own disambiguation. Re-read it from the declared clause.
|
|
542
681
|
const rawParents = decl.extends ? splitParentList(decl.extends) : [];
|
|
543
682
|
const declRef = { name: ref.name, key: ref.key, def: decl };
|
|
683
|
+
if (langTraits(index.files.get(contextFile)?.language)?.memberLookupOrder === 'c3') {
|
|
684
|
+
// Python's base order is semantic. Resolving imported bases as
|
|
685
|
+
// a separate group would move them ahead of earlier bases.
|
|
686
|
+
for (const written of rawParents) {
|
|
687
|
+
const imported = _importBoundParents(index, contextFile, [written], ref);
|
|
688
|
+
if (imported.refs.length) { imported.refs.forEach(push); continue; }
|
|
689
|
+
if (written.includes('.')) {
|
|
690
|
+
push(_resolveQualifiedParent(index, written, contextFile, declRef));
|
|
691
|
+
} else {
|
|
692
|
+
push(externalParentDenotation(index, contextFile, written, decl.startLine) ||
|
|
693
|
+
resolveClassRef(index, written, contextFile, {
|
|
694
|
+
excludeKey: written === ref.name ? ref.key : null,
|
|
695
|
+
line: decl.startLine,
|
|
696
|
+
}));
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
continue;
|
|
700
|
+
}
|
|
701
|
+
// A base the module binds by import where names bind per runtime
|
|
702
|
+
// configuration (Python, fix #424): the import's own chase decides
|
|
703
|
+
// which class it is (`from .auto import tqdm as base`, where auto
|
|
704
|
+
// binds `tqdm` to a class or another module's value per branch).
|
|
705
|
+
const imported = _importBoundParents(index, contextFile, rawParents, ref);
|
|
706
|
+
for (const parent of imported.refs) push(parent);
|
|
544
707
|
for (const parentName of names || []) {
|
|
545
|
-
if (!parentName) continue;
|
|
708
|
+
if (!parentName || imported.names.has(parentName)) continue;
|
|
546
709
|
const qualified = parentName.includes('::') || parentName.includes('.');
|
|
547
710
|
if (qualified && classDefsNamed(index, parentName).entries.length === 0) {
|
|
548
711
|
push(_resolveQualifiedParent(index, parentName, contextFile, declRef));
|
|
@@ -580,6 +743,134 @@ function parentRefsOf(index, ref) {
|
|
|
580
743
|
return out;
|
|
581
744
|
}
|
|
582
745
|
|
|
746
|
+
/**
|
|
747
|
+
* Bases a Python declaration spells by a name its module binds by a
|
|
748
|
+
* module-level import (fix #424). Each candidate class definition of the
|
|
749
|
+
* imported name is asked whether the import's module chase reaches it under
|
|
750
|
+
* that name: one definite hit is the base; a hit through one configuration
|
|
751
|
+
* alternative, a module value or no project class leave it unresolved
|
|
752
|
+
* (key null). A chase the index cannot follow keeps the name lookup.
|
|
753
|
+
* Returns the resolved refs and the inheritance-graph names they cover.
|
|
754
|
+
*/
|
|
755
|
+
function _importBoundParents(index, contextFile, rawParents, childRef) {
|
|
756
|
+
const out = { refs: [], names: new Set() };
|
|
757
|
+
const fileEntry = contextFile ? index.files.get(contextFile) : null;
|
|
758
|
+
if (!fileEntry || langTraits(fileEntry.language)?.conditionalDefinitions !== 'branch' ||
|
|
759
|
+
rawParents.length === 0) return out;
|
|
760
|
+
const { _nameBindingReaches } = require('./callers');
|
|
761
|
+
for (const written of rawParents) {
|
|
762
|
+
if (!/^[A-Za-z_]\w*$/.test(written)) continue;
|
|
763
|
+
const bindings = (fileEntry.importBindings || []).filter(b => b.module != null && b.name !== '*' &&
|
|
764
|
+
b.kind !== 'import' && (b.alias || b.name) === written &&
|
|
765
|
+
!(Number.isInteger(b.line) && index.findEnclosingFunction(contextFile, b.line, true)));
|
|
766
|
+
if (bindings.length !== 1) continue;
|
|
767
|
+
const binding = bindings[0];
|
|
768
|
+
const rel = fileEntry.moduleResolved?.[binding.module];
|
|
769
|
+
if (!rel) continue;
|
|
770
|
+
const moduleFile = path.isAbsolute(rel) ? rel : path.join(index.root, rel);
|
|
771
|
+
let entries = classDefsNamed(index, binding.name).entries.filter(e => !e.def.lexicalScopeStartLine);
|
|
772
|
+
if (binding.name === childRef.name && childRef.key) entries = entries.filter(e => e.key !== childRef.key);
|
|
773
|
+
if (entries.length === 0) continue;
|
|
774
|
+
const verdicts = entries.map(e => _nameBindingReaches(index, moduleFile, binding.name, new Set([e.def.file]), 4,
|
|
775
|
+
{ targetName: binding.name, configurationVerdict: true, valueVerdict: true }));
|
|
776
|
+
if (verdicts.includes('unknown')) continue;
|
|
777
|
+
const hits = entries.filter((e, i) => verdicts[i] === 'yes');
|
|
778
|
+
out.names.add(binding.name);
|
|
779
|
+
if (hits.length === 1 && verdicts.filter(v => v !== 'no').length === 1) {
|
|
780
|
+
out.refs.push({ name: binding.name, key: hits[0].key, def: hits[0].def });
|
|
781
|
+
continue;
|
|
782
|
+
}
|
|
783
|
+
// One class per runtime configuration (fix #434): members every
|
|
784
|
+
// alternative declares through one definition are known by name.
|
|
785
|
+
const alternatives = _pythonClassDenotations(index, moduleFile, binding.name, 0);
|
|
786
|
+
if (alternatives?.length === 1 && hits.length === 0) {
|
|
787
|
+
out.refs.push(_refOfDefinition(index, alternatives[0]));
|
|
788
|
+
continue;
|
|
789
|
+
}
|
|
790
|
+
out.refs.push({ name: binding.name, key: null, def: null, importUnresolved: true,
|
|
791
|
+
...(alternatives?.length > 1 && { alternatives: alternatives.map(def => _refOfDefinition(index, def)) }) });
|
|
792
|
+
}
|
|
793
|
+
return out;
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
function _refOfDefinition(index, def) {
|
|
797
|
+
const entry = classDefsNamed(index, def.name).entries.find(candidate => candidate.def === def);
|
|
798
|
+
return { name: def.name, key: entry?.key || null, def };
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
/**
|
|
802
|
+
* The class definitions a Python module-scope name denotes, one per runtime
|
|
803
|
+
* configuration (fix #434): a class statement, a value naming a class, an
|
|
804
|
+
* import of a class, or a configuration item whose every alternative is
|
|
805
|
+
* one of those. Null when any alternative is no class or cannot be followed.
|
|
806
|
+
*/
|
|
807
|
+
function _pythonClassDenotations(index, file, name, depth) {
|
|
808
|
+
const entry = index.files.get(file);
|
|
809
|
+
if (!entry || depth > 6 || !/^[A-Za-z_]\w*$/.test(String(name))) return null;
|
|
810
|
+
const classAt = line => {
|
|
811
|
+
const found = classDefsNamed(index, name).entries.filter(candidate => candidate.def.file === file &&
|
|
812
|
+
!candidate.def.className && candidate.def.lexicalScopeStartLine == null &&
|
|
813
|
+
(line == null || candidate.def.startLine === line || candidate.def.nameLine === line));
|
|
814
|
+
return found.length === 1 ? found[0].def : null;
|
|
815
|
+
};
|
|
816
|
+
const imported = (target, line = null) => {
|
|
817
|
+
const bindings = (entry.importBindings || []).filter(binding => binding.module != null &&
|
|
818
|
+
binding.name !== '*' && binding.kind !== 'import' && (binding.alias || binding.name) === target &&
|
|
819
|
+
(line == null || binding.line === line));
|
|
820
|
+
if (bindings.length !== 1) return null;
|
|
821
|
+
const rel = entry.moduleResolved?.[bindings[0].module];
|
|
822
|
+
if (!rel) return null;
|
|
823
|
+
return _pythonClassDenotations(index, path.isAbsolute(rel) ? rel : path.join(index.root, rel),
|
|
824
|
+
bindings[0].name, depth + 1);
|
|
825
|
+
};
|
|
826
|
+
const { moduleItemNamed } = require('./configuration-items');
|
|
827
|
+
const item = moduleItemNamed(index, file, name);
|
|
828
|
+
if (item) {
|
|
829
|
+
const out = [];
|
|
830
|
+
for (const site of item.sites) {
|
|
831
|
+
let defs = null;
|
|
832
|
+
if (site.kind === 'class') {
|
|
833
|
+
const def = classAt(site.line);
|
|
834
|
+
defs = def ? [def] : null;
|
|
835
|
+
} else if (site.kind === 'value' && site.target) {
|
|
836
|
+
defs = site.target === name ? null : _pythonClassDenotations(index, file, site.target, depth + 1);
|
|
837
|
+
} else if (site.kind === 'import') {
|
|
838
|
+
defs = imported(name, site.line);
|
|
839
|
+
}
|
|
840
|
+
if (!defs) return null;
|
|
841
|
+
for (const def of defs) if (!out.includes(def)) out.push(def);
|
|
842
|
+
}
|
|
843
|
+
return out.length > 0 ? out : null;
|
|
844
|
+
}
|
|
845
|
+
const own = classAt(null);
|
|
846
|
+
if (own) return [own];
|
|
847
|
+
if ((entry.symbols || []).some(def => def.name === name && !def.className && def.lexicalScopeStartLine == null &&
|
|
848
|
+
def.type !== 'state')) return null;
|
|
849
|
+
const alias = (entry.moduleValueAliases || []).filter(value => value.name === name);
|
|
850
|
+
if (alias.length === 1 && alias[0].target && !alias[0].alternatives?.length && alias[0].target !== name &&
|
|
851
|
+
!alias[0].target.includes('.')) return _pythonClassDenotations(index, file, alias[0].target, depth + 1);
|
|
852
|
+
if (alias.length > 0) return null;
|
|
853
|
+
return imported(name);
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
/**
|
|
857
|
+
* The member lookup every configuration alternative of a base agrees on
|
|
858
|
+
* (fix #434): the declaring class and members when each alternative finds
|
|
859
|
+
* the same definitions certainly, else null.
|
|
860
|
+
*/
|
|
861
|
+
function alternativesDeclare(index, ref, definitions, opts = {}) {
|
|
862
|
+
if (!ref?.alternatives?.length) return null;
|
|
863
|
+
let agreed = null;
|
|
864
|
+
for (const alternative of ref.alternatives) {
|
|
865
|
+
const found = _findDeclaringClass(index, alternative, definitions, { ...opts, parentsOnly: false });
|
|
866
|
+
if (!found || found.uncertain) return null;
|
|
867
|
+
if (!agreed) { agreed = found; continue; }
|
|
868
|
+
if (found.members.length !== agreed.members.length ||
|
|
869
|
+
found.members.some(member => !agreed.members.includes(member))) return null;
|
|
870
|
+
}
|
|
871
|
+
return agreed;
|
|
872
|
+
}
|
|
873
|
+
|
|
583
874
|
/**
|
|
584
875
|
* An unqualified base name the declaring file binds to an out-of-project
|
|
585
876
|
* type (fix #390): an external reference (or an unresolved one when the
|
|
@@ -819,6 +1110,54 @@ function _findDeclaringClass(index, startRef, definitions, opts) {
|
|
|
819
1110
|
}
|
|
820
1111
|
return members.length > 0 ? { ref, uncertain, members } : null;
|
|
821
1112
|
};
|
|
1113
|
+
if (langTraits(index.files.get(startRef.def?.file)?.language)?.memberLookupOrder === 'c3') {
|
|
1114
|
+
if (!opts.parentsOnly) { const own = check(startRef); if (own) return own; }
|
|
1115
|
+
const order = c3Order(index, startRef);
|
|
1116
|
+
if (order) {
|
|
1117
|
+
for (const ref of order.slice(opts.parentsOnly ? 1 : 0)) {
|
|
1118
|
+
const hit = check(ref);
|
|
1119
|
+
if (hit) return hit;
|
|
1120
|
+
}
|
|
1121
|
+
return null;
|
|
1122
|
+
}
|
|
1123
|
+
// Even an incomplete order starts with the class and its first
|
|
1124
|
+
// direct base. A single-base chain preserves that prefix. Once
|
|
1125
|
+
// branches merge, unknown ancestry can change which member wins.
|
|
1126
|
+
const seen = new Set();
|
|
1127
|
+
let ref = startRef, skip = opts.parentsOnly;
|
|
1128
|
+
while (ref?.key && ref.def && !ref.external && !seen.has(ref.key)) {
|
|
1129
|
+
seen.add(ref.key);
|
|
1130
|
+
if (!skip) { const hit = check(ref); if (hit) return hit; }
|
|
1131
|
+
skip = false;
|
|
1132
|
+
const parents = parentRefsOf(index, ref);
|
|
1133
|
+
if (parents.length === 1) { ref = parents[0]; continue; }
|
|
1134
|
+
if (parents.length > 1 && parents[0].key && parents[0].def && !parents[0].external) {
|
|
1135
|
+
const hit = check(parents[0]);
|
|
1136
|
+
if (hit) return hit;
|
|
1137
|
+
}
|
|
1138
|
+
break;
|
|
1139
|
+
}
|
|
1140
|
+
// A fully resolved project closure ending only in known external
|
|
1141
|
+
// bases cannot reach an unrelated project namesake. Keep that
|
|
1142
|
+
// negative identity proof; uncertainty in ORDER matters only when
|
|
1143
|
+
// a project member or an unresolved base can participate.
|
|
1144
|
+
const pending = opts.parentsOnly ? [...parentRefsOf(index, startRef)] : [startRef];
|
|
1145
|
+
const visited = new Set();
|
|
1146
|
+
let possible = false;
|
|
1147
|
+
while (pending.length) {
|
|
1148
|
+
const candidate = pending.shift();
|
|
1149
|
+
if (candidate.external || visited.has(_refVisitKey(candidate))) continue;
|
|
1150
|
+
visited.add(_refVisitKey(candidate));
|
|
1151
|
+
if (!candidate.key || !candidate.def || candidate.importUnresolved || check(candidate)) {
|
|
1152
|
+
possible = true;
|
|
1153
|
+
break;
|
|
1154
|
+
}
|
|
1155
|
+
pending.push(...parentRefsOf(index, candidate));
|
|
1156
|
+
}
|
|
1157
|
+
if (!possible) return null;
|
|
1158
|
+
return alternativesDeclare(index, ref, definitions, opts) ||
|
|
1159
|
+
{ ref: ref || startRef, uncertain: true, members: [] };
|
|
1160
|
+
}
|
|
822
1161
|
const visited = new Set([_refVisitKey(startRef)]);
|
|
823
1162
|
if (!opts.parentsOnly) {
|
|
824
1163
|
const own = check(startRef);
|
|
@@ -831,6 +1170,12 @@ function _findDeclaringClass(index, startRef, definitions, opts) {
|
|
|
831
1170
|
const vk = _refVisitKey(ref);
|
|
832
1171
|
if (visited.has(vk)) continue;
|
|
833
1172
|
visited.add(vk);
|
|
1173
|
+
// A base its import leaves undecided (fix #424) may declare the
|
|
1174
|
+
// member under any class name.
|
|
1175
|
+
if (ref.importUnresolved) {
|
|
1176
|
+
return alternativesDeclare(index, ref, definitions, opts) ||
|
|
1177
|
+
{ ref, uncertain: true, members: check(ref)?.members || [] };
|
|
1178
|
+
}
|
|
834
1179
|
const hit = check(ref);
|
|
835
1180
|
if (hit) {
|
|
836
1181
|
// An unresolved parent can only yield an uncertain match.
|
|
@@ -853,6 +1198,30 @@ function _findDeclaringClass(index, startRef, definitions, opts) {
|
|
|
853
1198
|
return null;
|
|
854
1199
|
}
|
|
855
1200
|
|
|
1201
|
+
/** Complete C3 order, or null when a base's ancestry is unknown. */
|
|
1202
|
+
function c3Order(index, ref, visiting = new Set()) {
|
|
1203
|
+
if (!ref?.key || !ref.def || ref.external || visiting.has(ref.key)) return null;
|
|
1204
|
+
const memo = _state(index).linearizations;
|
|
1205
|
+
if (memo.has(ref.key)) return memo.get(ref.key);
|
|
1206
|
+
visiting.add(ref.key);
|
|
1207
|
+
const parents = parentRefsOf(index, ref);
|
|
1208
|
+
const lists = parents.map(parent => c3Order(index, parent, visiting)?.slice());
|
|
1209
|
+
visiting.delete(ref.key);
|
|
1210
|
+
if (lists.some(list => !list)) { memo.set(ref.key, null); return null; }
|
|
1211
|
+
lists.push(parents.slice());
|
|
1212
|
+
const order = [ref];
|
|
1213
|
+
while (lists.some(list => list.length)) {
|
|
1214
|
+
const active = lists.filter(list => list.length);
|
|
1215
|
+
const head = active.map(list => list[0]).find(candidate =>
|
|
1216
|
+
!active.some(list => list.slice(1).some(item => item.key === candidate.key)));
|
|
1217
|
+
if (!head) { memo.set(ref.key, null); return null; }
|
|
1218
|
+
order.push(head);
|
|
1219
|
+
for (const list of active) if (list[0].key === head.key) list.shift();
|
|
1220
|
+
}
|
|
1221
|
+
memo.set(ref.key, order);
|
|
1222
|
+
return order;
|
|
1223
|
+
}
|
|
1224
|
+
|
|
856
1225
|
/**
|
|
857
1226
|
* Relationship of class reference `ref` to the owners of `targetDefs`:
|
|
858
1227
|
* 'same' (identical definition), 'ancestor' (ref is a transitive ancestor of
|
|
@@ -929,6 +1298,14 @@ function descendsFromTargets(index, ref, targetDefs) {
|
|
|
929
1298
|
if (visited.has(vk)) continue;
|
|
930
1299
|
visited.add(vk);
|
|
931
1300
|
if (matches(parent)) return 'yes';
|
|
1301
|
+
if (parent.alternatives?.length) {
|
|
1302
|
+
// A base per runtime configuration (fix #434) descends when
|
|
1303
|
+
// every alternative does.
|
|
1304
|
+
const verdicts = parent.alternatives.map(alternative => descendsFromTargets(index, alternative, targetDefs));
|
|
1305
|
+
if (verdicts.every(verdict => verdict === 'yes')) return 'yes';
|
|
1306
|
+
if (!verdicts.every(verdict => verdict === 'no')) maybe = true;
|
|
1307
|
+
continue;
|
|
1308
|
+
}
|
|
932
1309
|
if (parent.key || parent.def) {
|
|
933
1310
|
queue.push(...parentRefsOf(index, parent));
|
|
934
1311
|
} else {
|
|
@@ -996,6 +1373,11 @@ function shareDescendant(index, matchedRef, targetDefs) {
|
|
|
996
1373
|
for (const td of targetDefs) {
|
|
997
1374
|
const tRef = ownerRefOf(index, td);
|
|
998
1375
|
if (!tRef) continue;
|
|
1376
|
+
if (!tRef.key) return true;
|
|
1377
|
+
// A leaf has no proper descendant that can combine the two
|
|
1378
|
+
// owners. If it belongs to the target's descendants, the existing
|
|
1379
|
+
// below-target rule skips it; otherwise the sets cannot intersect.
|
|
1380
|
+
if (mine.size === 1) continue;
|
|
999
1381
|
const theirs = descendantKeys(index, tRef);
|
|
1000
1382
|
if (!theirs) return true;
|
|
1001
1383
|
if (theirs.has(matchedRef.key)) continue;
|
|
@@ -1006,13 +1388,18 @@ function shareDescendant(index, matchedRef, targetDefs) {
|
|
|
1006
1388
|
|
|
1007
1389
|
module.exports = {
|
|
1008
1390
|
OWNER_CLASS_KINDS,
|
|
1391
|
+
alternativesDeclare,
|
|
1392
|
+
pythonClassDenotations: _pythonClassDenotations,
|
|
1009
1393
|
resetClassIdentity,
|
|
1010
1394
|
ownerNameOf,
|
|
1011
1395
|
classKeyOf,
|
|
1012
1396
|
classDefsNamed,
|
|
1397
|
+
completeForwardDeclaration,
|
|
1398
|
+
forwardDeclarationProvider,
|
|
1013
1399
|
resolveClassRef,
|
|
1014
1400
|
resolveQualifiedClassRef: _resolveQualifiedParent,
|
|
1015
1401
|
ownerRefOf,
|
|
1402
|
+
lexicalClassRefOf,
|
|
1016
1403
|
ownerDefinitionOf,
|
|
1017
1404
|
distinctOwnerDefinitions,
|
|
1018
1405
|
hasFunctionLocalClass,
|