* Create the resolution context
()
| 431 | * Create the resolution context |
| 432 | */ |
| 433 | private createContext(): ResolutionContext { |
| 434 | return { |
| 435 | getNodesInFile: (filePath: string) => { |
| 436 | if (!this.nodeCache.has(filePath)) { |
| 437 | this.nodeCache.set(filePath, this.queries.getNodesByFile(filePath)); |
| 438 | } |
| 439 | return this.nodeCache.get(filePath)!; |
| 440 | }, |
| 441 | |
| 442 | getNodesByName: (name: string) => { |
| 443 | const cached = this.nameCache.get(name); |
| 444 | if (cached !== undefined) return cached; |
| 445 | const result = this.queries.getNodesByName(name); |
| 446 | this.nameCache.set(name, result); |
| 447 | return result; |
| 448 | }, |
| 449 | |
| 450 | getMethodMatches: (typeName: string, methodName: string, language: Language) => { |
| 451 | const key = `${language} ${typeName}::${methodName}`; |
| 452 | const cached = this.methodMatchCache.get(key); |
| 453 | if (cached !== undefined) return cached; |
| 454 | let candidates = this.nameCache.get(methodName); |
| 455 | if (candidates === undefined) { |
| 456 | candidates = this.queries.getNodesByName(methodName); |
| 457 | this.nameCache.set(methodName, candidates); |
| 458 | } |
| 459 | const want = `${typeName}::${methodName}`; |
| 460 | let matches: Node[]; |
| 461 | if (typeName.includes('::') || methodName.includes(':')) { |
| 462 | // Legacy linear filter for the shapes the owner index below can't |
| 463 | // key exactly: a multi-segment typeName (the endsWith test then |
| 464 | // spans more than two `::` segments) and ObjC selectors (whose |
| 465 | // single/empty-keyword colons defeat the segment split). Tiny |
| 466 | // populations; the per-key memo above still amortizes them. |
| 467 | matches = []; |
| 468 | for (const m of candidates) { |
| 469 | if (m.kind !== 'method') continue; |
| 470 | if (m.language !== language) continue; |
| 471 | const qn = m.qualifiedName; |
| 472 | if (qn === want || qn.endsWith(`::${want}`)) matches.push(m); |
| 473 | } |
| 474 | } else { |
| 475 | // Owner index: the linear filter above is O(all same-named methods) |
| 476 | // per CACHE MISS, and on overload-heavy landscapes the distinct |
| 477 | // (type, method) key space is so large the per-key memo never |
| 478 | // amortizes — Swift's `init` has tens of thousands of candidates |
| 479 | // and the compiler repo measured 732µs per failing call, most of it |
| 480 | // this scan (re-entered once per supertype recursion level, too). |
| 481 | // Bucket each (language, methodName)'s candidates ONCE by the |
| 482 | // qualifiedName's last two `::` segments — exactly the span the |
| 483 | // `qn === want || qn.endsWith('::' + want)` predicate tests for a |
| 484 | // segment-clean typeName — then every query is a map lookup. |
| 485 | // Bucket insertion follows candidate order, so each bucket is |
| 486 | // byte-identical to what the linear filter produced. |
| 487 | const idxKey = `${language} ${methodName}`; |
| 488 | let ownerIndex = this.methodOwnerIndexCache.get(idxKey); |
| 489 | if (!ownerIndex) { |
| 490 | ownerIndex = new Map<string, Node[]>(); |
no test coverage detected