mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-10-28 06:22:22 +00:00
perf(scope): gate namespace-call lookups and cheapen per-site drop bookkeeping
- `namespaceCallReturnType` runs for every two-part dotted binding during the chain follow. Skip the scope-chain lookup unless the head is a local name some namespace import binds (built once per run from the finalized import edges), and parse the name with one anchored match instead of split + per-part regex. - The receiver drop gate checks the cheap hidden-by-earlier-site condition first and evaluates the namespace-callable test last, only for sites that would otherwise not be recorded. - Per-site duplicate tracking keeps one map entry per position key (a Set only when distinct calls share a key) instead of two Sets and a concatenated key per site. - Type `CALLABLE_TYPES` against the definition type; key the Zig namespace handle cache on the tree type. Behavior is unchanged: the full lang-resolution corpus emits the identical 2,869 CALLS/ACCESSES edges, the receiver bench matches its baseline, and the resolver and scope-resolution suites pass (3,549 tests). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
603137d7dc
commit
c686065afd
3 changed files with 74 additions and 26 deletions
|
|
@ -82,7 +82,7 @@ export function zigImportRootOf(value: SyntaxNode | null): SyntaxNode | null {
|
|||
/** Module-level `const ns = @import("ns.zig")` handles on a lowercase file: a
|
||||
* namespace module, not a file-struct (`@import("Page.zig")`) whose methods
|
||||
* live in its struct scope. Cached per tree. */
|
||||
const zigNamespaceHandlesByTree = new WeakMap<object, ReadonlySet<string>>();
|
||||
const zigNamespaceHandlesByTree = new WeakMap<SyntaxNode['tree'], ReadonlySet<string>>();
|
||||
function zigNamespaceHandles(node: SyntaxNode): ReadonlySet<string> {
|
||||
const cached = zigNamespaceHandlesByTree.get(node.tree);
|
||||
if (cached !== undefined) return cached;
|
||||
|
|
|
|||
|
|
@ -115,7 +115,30 @@ function producerTypeRef(defId: string, scopes: ScopeResolutionIndexes): TypeRef
|
|||
*/
|
||||
const RECHAIN_MAX_DEPTH = 8;
|
||||
|
||||
const IDENTIFIER = /^[A-Za-z_$][\w$]*$/;
|
||||
/** `head.member`, both plain identifiers. */
|
||||
const QUALIFIED_MEMBER_RE = /^([A-Za-z_$][\w$]*)\.([A-Za-z_$][\w$]*)$/;
|
||||
|
||||
/**
|
||||
* Every local name a namespace import binds anywhere in the workspace. Most
|
||||
* dotted bindings are member chains (`user.address`) whose head is no such
|
||||
* name, so they skip the scope-chain lookup entirely. Import edges are fixed
|
||||
* once finalize has run.
|
||||
*/
|
||||
const namespaceLocalNamesByIndexes = new WeakMap<ScopeResolutionIndexes, ReadonlySet<string>>();
|
||||
function namespaceLocalNames(scopes: ScopeResolutionIndexes): ReadonlySet<string> {
|
||||
let names = namespaceLocalNamesByIndexes.get(scopes);
|
||||
if (names === undefined) {
|
||||
const collected = new Set<string>();
|
||||
for (const scope of scopes.scopeTree.byId?.values() ?? []) {
|
||||
for (const edge of scopes.imports?.get(scope.id) ?? scope.imports ?? []) {
|
||||
if (edge.kind === 'namespace') collected.add(edge.localName);
|
||||
}
|
||||
}
|
||||
names = collected;
|
||||
namespaceLocalNamesByIndexes.set(scopes, names);
|
||||
}
|
||||
return names;
|
||||
}
|
||||
|
||||
/**
|
||||
* `ns.f` where `ns` is a namespace import and `f` has a return annotation in
|
||||
|
|
@ -129,9 +152,10 @@ export function namespaceCallReturnType(
|
|||
scopes: ScopeResolutionIndexes,
|
||||
position?: TypeRef['lookupPosition'],
|
||||
): TypeRef | undefined {
|
||||
const parts = rawName.split('.');
|
||||
if (parts.length !== 2 || !parts.every((part) => IDENTIFIER.test(part))) return undefined;
|
||||
const [head, member] = parts as [string, string];
|
||||
const [, head, member] = QUALIFIED_MEMBER_RE.exec(rawName) ?? [];
|
||||
if (head === undefined || member === undefined || !namespaceLocalNames(scopes).has(head)) {
|
||||
return undefined;
|
||||
}
|
||||
const claim = lookupNameClaim(inScope, head, scopes, { position, purpose: 'value' });
|
||||
if (claim.bindings.some((binding) => binding.origin === 'local')) return undefined;
|
||||
const targets = new Set<ScopeId>();
|
||||
|
|
|
|||
|
|
@ -213,7 +213,10 @@ type ReceiverBoundProviderSubset = Pick<
|
|||
|
||||
/** A bare, undecorated identifier and nothing else — see {@link isBareTypeName}. */
|
||||
const BARE_TYPE_NAME_RE = /^[A-Za-z_$][\w$]*$/;
|
||||
const CALLABLE_TYPES: ReadonlySet<string> = new Set(['Function', 'Method']);
|
||||
const CALLABLE_TYPES: ReadonlySet<SymbolDefinition['type']> = new Set<SymbolDefinition['type']>([
|
||||
'Function',
|
||||
'Method',
|
||||
]);
|
||||
|
||||
/**
|
||||
* A type name a built-in test may be asked about: a bare, undecorated
|
||||
|
|
@ -487,11 +490,12 @@ export function emitReceiverBoundCalls(
|
|||
typeRef: TypeRef,
|
||||
namespaceTargets: ReadonlyMap<string, readonly string[]>,
|
||||
): boolean => {
|
||||
const [head, member, ...rest] = typeRef.rawName.split('.');
|
||||
if (head === undefined || member === undefined || rest.length > 0) return false;
|
||||
return (namespaceTargets.get(head) ?? []).some((file) =>
|
||||
CALLABLE_TYPES.has(lookupNamespaceMember(file, member)?.type ?? ''),
|
||||
);
|
||||
const [, head, member] = TWO_PART_RECEIVER_RE.exec(typeRef.rawName) ?? [];
|
||||
if (head === undefined || member === undefined) return false;
|
||||
return (namespaceTargets.get(head) ?? []).some((file) => {
|
||||
const def = lookupNamespaceMember(file, member);
|
||||
return def !== undefined && CALLABLE_TYPES.has(def.type);
|
||||
});
|
||||
};
|
||||
// A class-like def NESTED in `owner` (`A.Item` inside `A`): its qualified
|
||||
// name is the owner's plus the segment — the identity the structure phase
|
||||
|
|
@ -1162,8 +1166,7 @@ export function emitReceiverBoundCalls(
|
|||
// Duplicate captures of one site share a key and a member name. Distinct
|
||||
// calls can share only the key (`ns.make().m()` starts where `ns.make()`
|
||||
// does), so one's resolution must not hide the other's drop (#3498).
|
||||
const keysSeen = new Set<string>();
|
||||
const sitesSeen = new Set<string>();
|
||||
const memberNamesByKey = new Map<string, string | Set<string>>();
|
||||
for (const site of parsed.referenceSites) {
|
||||
if (site.kind !== 'call' && site.kind !== 'read' && site.kind !== 'write') continue;
|
||||
if (site.explicitReceiver === undefined) continue;
|
||||
|
|
@ -1206,10 +1209,7 @@ export function emitReceiverBoundCalls(
|
|||
const receiverName = site.explicitReceiver.name;
|
||||
const memberName = site.name;
|
||||
const siteKey = `${parsed.filePath}:${site.atRange.startLine}:${site.atRange.startCol}`;
|
||||
const sameKeyEarlier = keysSeen.has(siteKey);
|
||||
const siteSeenEarlier = sitesSeen.has(`${siteKey}\0${memberName}`);
|
||||
keysSeen.add(siteKey);
|
||||
sitesSeen.add(`${siteKey}\0${memberName}`);
|
||||
const earlierAtKey = recordSiteMember(memberNamesByKey, siteKey, memberName);
|
||||
|
||||
if (provider.suppressReceiverLookup !== undefined) {
|
||||
const baseName =
|
||||
|
|
@ -3175,21 +3175,19 @@ export function emitReceiverBoundCalls(
|
|||
// keyed by the MEMBER name, which is the only thing still known about a
|
||||
// dropped site (its callee is unknown by definition, so the drop cannot
|
||||
// be attributed to any target symbol).
|
||||
// #3498: a plain receiver bound to a namespace call the analyzer could
|
||||
// not type (`x = ns.untyped()`) is a drop too, not an exact miss.
|
||||
const typedReceiverUnresolved =
|
||||
typeRef !== undefined &&
|
||||
BARE_TYPE_NAME_RE.test(receiverName) &&
|
||||
bindsNamespaceCallable(typeRef, namespaceTargets);
|
||||
// Every resolving case `continue`s, so a handled key here came from an
|
||||
// earlier site or pre-seeding; it hides this drop only when that site
|
||||
// was a duplicate capture of this one.
|
||||
const hiddenByEarlierSite = handledSites.has(siteKey) && (siteSeenEarlier || !sameKeyEarlier);
|
||||
const hiddenByEarlierSite = handledSites.has(siteKey) && earlierAtKey !== 'shared-key';
|
||||
if (
|
||||
!hiddenByEarlierSite &&
|
||||
(compoundReceiverUnresolved ||
|
||||
receiverNamespace !== undefined ||
|
||||
typedReceiverUnresolved) &&
|
||||
!hiddenByEarlierSite
|
||||
// #3498: a plain receiver bound to a namespace call the analyzer
|
||||
// could not type (`x = ns.untyped()`) is a drop too, not an exact miss.
|
||||
(typeRef !== undefined &&
|
||||
BARE_TYPE_NAME_RE.test(receiverName) &&
|
||||
bindsNamespaceCallable(typeRef, namespaceTargets)))
|
||||
) {
|
||||
// Decoded once: both the shape census and the origin classifier read the
|
||||
// same chain, and this is inside the drop guard so a resolved site pays
|
||||
|
|
@ -3279,6 +3277,32 @@ function splitClassLevelMemberReceiver(
|
|||
/** `Holder.repo` and nothing looser — see {@link splitClassLevelMemberReceiver}. */
|
||||
const TWO_PART_RECEIVER_RE = /^([A-Za-z_$][\w$]*)\.([A-Za-z_$][\w$]*)$/;
|
||||
|
||||
/** How an earlier site in the same file relates to this one's position key. */
|
||||
type EarlierSiteAtKey = 'new-key' | 'shared-key' | 'repeated-site';
|
||||
|
||||
/** Record `memberName` at `siteKey` and say what was there before: nothing, a
|
||||
* different call starting at the same position, or this same member (a
|
||||
* duplicate capture). One map entry per key; a Set only on a collision. */
|
||||
function recordSiteMember(
|
||||
memberNamesByKey: Map<string, string | Set<string>>,
|
||||
siteKey: string,
|
||||
memberName: string,
|
||||
): EarlierSiteAtKey {
|
||||
const seen = memberNamesByKey.get(siteKey);
|
||||
if (seen === undefined) {
|
||||
memberNamesByKey.set(siteKey, memberName);
|
||||
return 'new-key';
|
||||
}
|
||||
if (typeof seen === 'string') {
|
||||
if (seen === memberName) return 'repeated-site';
|
||||
memberNamesByKey.set(siteKey, new Set([seen, memberName]));
|
||||
return 'shared-key';
|
||||
}
|
||||
if (seen.has(memberName)) return 'repeated-site';
|
||||
seen.add(memberName);
|
||||
return 'shared-key';
|
||||
}
|
||||
|
||||
/** Resolve a member by name on a class def, narrowing by argument
|
||||
* types when multiple overloads share the name. Falls back to the
|
||||
* first-seen def (legacy `findOwnedMember` semantics) when there's
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue