fix: deduplicate Swift extension class nodes in call resolution

When Swift extensions create multiple Class nodes with the same name
(e.g. Product.swift + ProductMatchableConformance.swift), the call
resolver gets multiple candidates and refuses to emit a CALLS edge.

Add dedup: when all candidates share the same type (Class/Struct) and
differ only by file, prefer the primary definition (shortest filepath).

Note: This fix is partial — some constructor calls inside function
bodies may still be consumed by the type-env constructor binding
scanner before reaching resolveCallTarget. Filed as known limitation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
marxo126 2026-03-21 10:25:33 +01:00
parent 561a54a154
commit dfe83f333e

View file

@ -893,7 +893,20 @@ const resolveCallTarget = (
if (disambiguated) return toResolveResult(disambiguated, tiered.tier);
}
if (filteredCandidates.length !== 1) return null;
if (filteredCandidates.length !== 1) {
// Deduplicate: Swift extensions create multiple Class nodes with the same name.
// When all candidates share the same type and differ only by file (extension vs
// primary definition), they represent the same symbol. Prefer the primary
// definition (shortest file path: Product.swift over ProductExtension.swift).
if (filteredCandidates.length > 1) {
const allSameType = filteredCandidates.every(c => c.type === filteredCandidates[0].type);
if (allSameType && (filteredCandidates[0].type === 'Class' || filteredCandidates[0].type === 'Struct')) {
const sorted = [...filteredCandidates].sort((a, b) => a.filePath.length - b.filePath.length);
return toResolveResult(sorted[0], tiered.tier);
}
}
return null;
}
return toResolveResult(filteredCandidates[0], tiered.tier);
};