* Interface/registry-dispatch announcement — #687 extended to GRAPH-visible * polymorphism (the body-scan can't see it: `nodeType.execute()` is textually * an ordinary call; the polymorphism lives in the `implements`/`extends` edges). * * A method the agent named that resolves to a large
(cg: CodeGraph, candidates: Array<{ token: string; family: Node[] }>, named: Map<string, Node>)
| 2223 | * TRUE graph-wide implementer count, NOT their frequency in the sample. |
| 2224 | */ |
| 2225 | private buildPolymorphicBoundaries(cg: CodeGraph, candidates: Array<{ token: string; family: Node[] }>, named: Map<string, Node>): string { |
| 2226 | const CLASSY = new Set(['class', 'struct', 'interface', 'trait', 'protocol', 'abstract']); |
| 2227 | const MIN_IMPL = 8; // a supertype needs >= this many implementers to count as "polymorphic" |
| 2228 | const MIN_SUPPORT = 2; // >= this many sampled definers must share the supertype (ties it to the token) |
| 2229 | const SAMPLE = 40; // family members inspected per token |
| 2230 | const MAX_NOTES = 3; |
| 2231 | const rel = (p: string) => p.replace(/\\/g, '/'); |
| 2232 | const containerOf = (m: Node): Node | null => { |
| 2233 | try { const ce = cg.getIncomingEdges(m.id).find((e) => e.kind === 'contains'); return ce ? cg.getNode(ce.source) : null; } |
| 2234 | catch { return null; } |
| 2235 | }; |
| 2236 | const notes: string[] = []; |
| 2237 | const seenSuper = new Set<string>(); |
| 2238 | for (const { token, family } of candidates) { |
| 2239 | if (notes.length >= MAX_NOTES) break; |
| 2240 | // supertype id → how many sampled definers share it + a few example definers |
| 2241 | const supers = new Map<string, { node: Node; count: number; targets: Node[] }>(); |
| 2242 | for (const m of family.slice(0, SAMPLE)) { |
| 2243 | const container = containerOf(m); |
| 2244 | if (!container || !CLASSY.has(container.kind)) continue; |
| 2245 | let sups: Node[] = []; |
| 2246 | try { |
| 2247 | sups = cg.getOutgoingEdges(container.id) |
| 2248 | .filter((e) => e.kind === 'implements' || e.kind === 'extends') |
| 2249 | .map((e) => { try { return cg.getNode(e.target); } catch { return null; } }) |
| 2250 | .filter((n): n is Node => !!n && CLASSY.has(n.kind) && (n.name?.length || 0) >= 3); |
| 2251 | } catch { /* no supertypes — free function or unresolved */ } |
| 2252 | for (const s of sups) { |
| 2253 | const e = supers.get(s.id) || { node: s, count: 0, targets: [] }; |
| 2254 | e.count++; |
| 2255 | if (e.targets.length < 6) e.targets.push(m); |
| 2256 | supers.set(s.id, e); |
| 2257 | } |
| 2258 | } |
| 2259 | // Pick the supertype with the most TRUE implementers (graph-wide), among |
| 2260 | // those genuinely shared by the token's definers. |
| 2261 | let best: { node: Node; impl: number; targets: Node[] } | null = null; |
| 2262 | for (const { node, count, targets } of supers.values()) { |
| 2263 | if (count < MIN_SUPPORT) continue; |
| 2264 | let impl = 0; |
| 2265 | try { impl = cg.getIncomingEdges(node.id).filter((e) => e.kind === 'implements' || e.kind === 'extends').length; } |
| 2266 | catch { /* leave 0 — gated out below */ } |
| 2267 | if (impl < MIN_IMPL) continue; |
| 2268 | if (!best || impl > best.impl) best = { node, impl, targets }; |
| 2269 | } |
| 2270 | if (!best || seenSuper.has(best.node.id)) continue; |
| 2271 | seenSuper.add(best.node.id); |
| 2272 | const namedNames = new Set([...named.values()].map((n) => n.name)); |
| 2273 | const eg = best.targets.slice(0, 4).map((m) => { |
| 2274 | const cont = containerOf(m); |
| 2275 | const disp = cont ? `${cont.name}.${m.name}` : (m.qualifiedName || m.name); |
| 2276 | const mark = cont && namedNames.has(cont.name) ? ' ← you named this' : ''; |
| 2277 | return `\`${disp}\` (${rel(m.filePath)}:${m.startLine})${mark}`; |
| 2278 | }); |
| 2279 | const more = best.impl > eg.length ? ` +${best.impl - eg.length} more` : ''; |
| 2280 | notes.push(`- \`${token}\` → runtime dispatch to **${best.impl}** types implementing \`${best.node.name}\` — the static path ends here, the target is chosen at runtime. e.g. ${eg.join(', ')}${more}`); |
| 2281 | } |
| 2282 | if (notes.length === 0) return ''; |
no test coverage detected