fix(swift): read a call's shape before reaching a member by name (#2170)

Swift's extractor keeps one receiver level, so super.init(…), a longer
chain's removeAll() and a bare min(a, b) all arrive as bare names and
exact-matched whichever project member shared the name. A bare or self./
super. call now reaches only a member of the types around it and what they
inherit or conform to (source-read heads, UIKit ancestry, the standard
collection protocols), nearest first; a standard-named chain link needs a
receiver that names the owner, an outside owner, or an argument label only
the project's method takes; playground and test-target globals stay put.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Colby Mchenry
2026-09-30 13:43:36 +00:00
committed by GitHub
parent 791ae397e5
commit a39fd14e89
3 changed files with 406 additions and 1 deletions
+1
View File
@@ -14,6 +14,7 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
### Fixes
- In Swift, a call written with no receiver, or through `self.` or `super.`, now reaches only a member of the type around it or of what that type inherits or conforms to, and the nearest override wins. A standard-library name later in a chain (`axis.entries.removeAll()`, `names.contains(x)`) no longer lands on a project type's method, unless the receiver or the call's argument label points to it. On Charts, `min(a, b)` and `max(a, b)` used to land on a range type's `min` field and a `Sequence` extension, and `super.init(…)` on an Objective-C demo's `init` over 170 times. A global in a playground or a test target is no longer linked from the main code. Re-index after upgrading.
- A call on a variable declared with a type from outside the project (`List<String> list`, `var sb = new StringBuilder()`, `signer = TimestampSigner()`) no longer lands on a project type's method with the same name. On gson, `list.add(…)` used to land on a project list wrapper; on commons-lang, `s.length()` and `buffer.append(…)` on project classes. In Python, a keyword argument inside a call (`prefix=IPNetwork(…)`) is no longer taken for an assignment to that name.
- Kotlin classes whose primary constructor sits on the line after the class name, before a supertype list (`expect open class ByteString` / `internal constructor(…) : Comparable<ByteString>`, common in Kotlin Multiplatform code), no longer lose their members to loose functions. A call later in a chain to a Kotlin standard-library function (`apply`, `let`, `toString`, `map`, `contains`…) no longer lands on a project type's same-named method. On okhttp, `.apply { … }` used to land on `ConnectionSpec.apply`. Re-index after upgrading.
- In Rust and Go, a call later in a chain, like `sym.filename().map(…)` or `c.Flags().String(…)`, no longer lands on the project's own method of that name when the name is a standard-library one (`unwrap`, `clone`, `map`, `iter`, Go's `String` or `Error`). A plain call like `get(handler)` or `drop(x)` never lands on a method. On tokio, `.map(…)` on an `Option` used to land on `MutexGuard::map`; on axum, the router's `get(handler)` on a cookie jar's `get`; on ripgrep, `.clone()` on an error type.
+133
View File
@@ -0,0 +1,133 @@
/**
* Swift's extractor keeps one receiver level, so `super.reset()`,
* `axis.entries.removeAll()` and `min(a, b)` reach the resolver as bare
* names. How the call is written decides what it can mean:
*
* - bare or through `self.`: a member of a type around the call or of what it
* inherits or conforms to (UIKit ancestry and the standard collection
* protocols included), the nearest first — `super.`: the latter only.
* Charts' `min(a, b)` went to a range type's `min` field, `max(a, b)` to a
* `Sequence` extension, `super.init(…)` to an Objective-C demo's `init`;
* - down a longer chain, a name the standard types all carry is theirs unless
* the receiver names the owner or the call gives a label only the project's
* method takes (Kingfisher's `data.kf.contains(jpeg:)`);
* - a global `let` in a playground is nobody else's call target.
*/
import { describe, it, expect, afterAll, beforeAll } from 'vitest';
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import { CodeGraph } from '../src';
let root = '';
let cg: CodeGraph;
beforeAll(async () => {
root = fs.mkdtempSync(path.join(os.tmpdir(), 'cg-swift-shape-'));
const files: Record<string, string> = {
'Sources/Charts/Utils/Sequence+KeyPath.swift': `extension Sequence {
func max<T: Comparable>(by keyPath: KeyPath<Element, T>) -> Element? {
return nil
}
}
`,
'Sources/Charts/Renderers/Renderer.swift': `open class Renderer {
public struct XBounds {
public var min: Int = 0
}
}
`,
'Sources/Charts/Data/ChartData.swift': `open class ChartData {
open func removeAll() { }
}
`,
'Sources/Charts/Formatters/Formatter.swift': `open class Formatter {
open func reset() { }
}
`,
'Sources/Charts/Charts/ChartViewBase.swift': `open class ChartViewBase: NSObject {
open func highlightValue(_ h: Int) { }
open func reset() { }
}
`,
'Sources/Charts/Charts/BarChartView.swift': `open class BarChartView: ChartViewBase {
open override func highlightValue(_ h: Int) { }
}
`,
'Sources/Charts/Charts/BarLineChartViewBase.swift': `open class BarLineChartViewBase: ChartViewBase {
open func tapped(axis: Axis) {
highlightValue(1)
let widest = max(1, 2)
let narrowest = min(3, 4)
super.reset()
axis.entries.removeAll()
}
}
`,
'Sources/Charts/Wrapper/KingfisherWrapper.swift': `public struct KingfisherWrapper<Base> {
public let base: Base
}
extension KingfisherWrapper where Base == Data {
func contains(jpeg marker: Int) -> Bool {
return false
}
}
`,
'Sources/Charts/Wrapper/Decoder.swift': `final class Decoder {
func scan(data: Data) -> Bool {
return data.kf.contains(jpeg: 0xC2)
}
}
`,
'Demo/Extensions/UIViewController+Bar.swift': `extension UIViewController {
func setupOperationBar() { }
}
`,
'Demo/Controllers/ListController.swift': `class ListController: UICollectionViewController {
override func viewDidLoad() {
setupOperationBar()
}
}
`,
'Demo/Chart.playground/Contents.swift': `let min = 20.0
`,
};
for (const [rel, content] of Object.entries(files)) {
fs.mkdirSync(path.dirname(path.join(root, rel)), { recursive: true });
fs.writeFileSync(path.join(root, rel), content);
}
cg = await CodeGraph.init(root, { index: true });
});
afterAll(() => {
cg?.close();
if (root) fs.rmSync(root, { recursive: true, force: true });
});
/** `Owner::member` of every call edge out of a file. */
function callsFrom(file: string): string[] {
const ids = cg.getNodesInFile(file).map((n) => n.id);
return cg
.getOutgoingEdgesFrom(ids)
.filter((e) => e.kind === 'calls')
.map((e) => cg.getNode(e.target)!.qualifiedName)
.sort();
}
describe('Swift calls reached by name alone', () => {
it('reach the nearest inherited member, and nothing outside the hierarchy', () => {
expect(callsFrom('Sources/Charts/Charts/BarLineChartViewBase.swift')).toEqual([
'ChartViewBase::highlightValue',
'ChartViewBase::reset',
]);
});
it('keep a standard-named method the call labels as the project’s', () => {
expect(callsFrom('Sources/Charts/Wrapper/Decoder.swift')).toEqual(['KingfisherWrapper::contains']);
});
it('reach an extension of a UIKit ancestor', () => {
expect(callsFrom('Demo/Controllers/ListController.swift')).toEqual(['UIViewController::setupOperationBar']);
});
});
+272 -1
View File
@@ -1616,6 +1616,260 @@ function isKotlinStdChainTarget(n: Node, receiver: string): boolean {
return receiver === 'this' || (receiver !== '' && sharesReceiverWord(receiver, n));
}
/**
* How a Swift call is written: `bare` (implicit self, or a free function),
* through `self.` / `super.`, or `chained` on some other receiver. The
* extractor keeps one receiver level, so `super.init(…)`,
* `axis.entries.removeAll()` and `min(a, b)` all arrive as bare names.
*/
interface SwiftCallShape {
shape: 'bare' | 'self' | 'super' | 'chained';
/** A chained call's receiver, calls and subscripts dropped. */
receiver: string;
/** The first argument's label, if any. */
label: string;
/** `name[…]`: a subscript of a value, not a call. */
subscript: boolean;
}
function swiftCallShape(ref: UnresolvedRef, context: ResolutionContext): SwiftCallShape | null {
const line = context.getFileLines?.(ref.filePath)?.[ref.line - 1] ?? context.readFile(ref.filePath)?.split('\n')[ref.line - 1];
if (line === undefined) return null;
const name = ref.referenceName;
let start = line.startsWith(name, ref.column) && !/[\w$]/.test(line[ref.column - 1] ?? '') ? ref.column : -1;
if (start < 0) {
const m = new RegExp(`(?<![\\w$])${name}\\s*[({<[]`).exec(line);
start = m ? m.index : -1;
}
if (start < 0) return null;
const before = line.slice(0, start);
const after = line.slice(start + name.length);
const base = { receiver: '', label: '', subscript: /^\s*\[/.test(after) };
if (!/\.\s*$/.test(before)) return { ...base, shape: 'bare' };
// `self.m(` — not `self[i].m(` or `self.items.m(`, which are chains.
const plain = /(?:^|[^\w$.)\]])(self|Self|super)\s*[?!]?\s*\.\s*$/.exec(before)?.[1];
if (plain) return { ...base, shape: plain === 'super' ? 'super' : 'self' };
return {
...base,
shape: 'chained',
receiver: rustGoReceiverName(before.replace(/[?!]\s*\./g, '.')),
label: /^\s*\(\s*([A-Za-z_]\w*)\s*:(?!:)/.exec(after)?.[1] ?? '',
};
}
/** Argument labels the standard library's collection and string methods take. */
const SWIFT_STD_LABELS: ReadonlySet<string> = new Set([
'contentsOf', 'where', 'by', 'at', 'keepingCapacity', 'separator', 'forKey', 'of', 'into', 'in', 'options',
'maxSplits', 'omittingEmptySubsequences', 'with', 'after', 'before', 'upTo', 'through', 'offsetBy', 'default',
]);
/**
* Whether a Swift method's declaration takes the argument label a call gives
* first (`func contains(jpeg marker: JPEGMarker)` for `data.kf.contains(jpeg:
* .SOF2)`). Labels are part of a Swift method's name, so one the standard
* library's namesakes never take identifies the project's.
*/
function swiftDeclaresLabel(n: Node, label: string, context: ResolutionContext): boolean {
if (label === '' || SWIFT_STD_LABELS.has(label)) return false;
const lines = context.getFileLines?.(n.filePath) ?? context.readFile(n.filePath)?.split(/\r?\n/) ?? [];
const head = lines.slice(n.startLine - 1, n.startLine + 3).join(' ');
return new RegExp(`\\bfunc\\s+${n.name}\\s*(?:<[^>]*>)?\\s*\\(\\s*${label}\\b`).test(head);
}
/**
* What the standard library's collection types and protocols refine, so a
* project extension of `Collection` is in reach of a type that conforms to
* `RandomAccessCollection`. UIKit / AppKit ancestry comes from the
* Objective-C table.
*/
const SWIFT_STD_SUPERS: Readonly<Record<string, readonly string[]>> = {
RandomAccessCollection: ['BidirectionalCollection'], BidirectionalCollection: ['Collection'],
MutableCollection: ['Collection'], RangeReplaceableCollection: ['Collection'], Collection: ['Sequence'],
LazySequenceProtocol: ['Sequence'], LazyCollectionProtocol: ['Collection', 'LazySequenceProtocol'],
StringProtocol: ['BidirectionalCollection'],
Array: ['RandomAccessCollection', 'MutableCollection', 'RangeReplaceableCollection'],
ArraySlice: ['RandomAccessCollection', 'MutableCollection', 'RangeReplaceableCollection'],
ContiguousArray: ['RandomAccessCollection', 'MutableCollection', 'RangeReplaceableCollection'],
String: ['StringProtocol', 'RangeReplaceableCollection'], Substring: ['StringProtocol', 'RangeReplaceableCollection'],
Dictionary: ['Collection'], Set: ['Collection', 'SetAlgebra'], Range: ['RandomAccessCollection'],
ClosedRange: ['RandomAccessCollection'],
};
/** Swift declarations a member hangs off: types, protocols, and extensions (extracted as classes). */
const SWIFT_TYPE_KINDS: ReadonlySet<string> = new Set(['class', 'struct', 'enum', 'interface']);
const SWIFT_MEMBER_KINDS: ReadonlySet<string> = new Set(['method', 'property', 'field', 'enum_member']);
const SWIFT_DECLS = new WeakMap<ResolutionContext, Map<string, { supers: string[]; projectType: boolean }>>();
const SWIFT_HIERARCHIES = new WeakMap<ResolutionContext, WeakMap<UnresolvedRef, Map<string, number>>>();
/**
* What the project declares a Swift type as: the supertypes and protocols
* every declaration and extension of it names, and whether any of them is
* the type itself rather than an extension of an outside one
* (`extension Sequence`).
*/
function swiftDeclOf(typeName: string, context: ResolutionContext): { supers: string[]; projectType: boolean } {
let memo = SWIFT_DECLS.get(context);
if (!memo) {
memo = new Map();
SWIFT_DECLS.set(context, memo);
}
const hit = memo.get(typeName);
if (hit) return hit;
const system = OBJC_SYSTEM_SUPERS[typeName];
const info = { supers: [...(SWIFT_STD_SUPERS[typeName] ?? (system ? [system] : []))], projectType: false };
for (const decl of context.getNodesByName(typeName)) {
if (decl.language !== 'swift' || !SWIFT_TYPE_KINDS.has(decl.kind)) continue;
const head = swiftHeadOf(decl, context);
info.supers.push(...head.supers);
if (!head.extension) info.projectType = true;
}
memo.set(typeName, info);
return info;
}
/**
* A Swift declaration's head, read up to its body with comments, attribute
* arguments and generic parameters dropped: `@objc(ChartViewBase) open class
* ChartViewBase<T>: NSUIView, ChartDataProvider where T: Equatable {` →
* NSUIView, ChartDataProvider.
*/
function swiftHeadOf(decl: Node, context: ResolutionContext): { supers: string[]; extension: boolean } {
const lines = context.getFileLines?.(decl.filePath) ?? context.readFile(decl.filePath)?.split(/\r?\n/) ?? [];
const text = lines
.slice(decl.startLine - 1, Math.min(decl.endLine, decl.startLine + 20))
.join('\n')
.replace(/\/\*[\s\S]*?\*\//g, ' ')
.replace(/\/\/[^\n]*/g, ' ');
let depth = 0;
let flat = '';
for (const ch of text) {
if (ch === '<' || ch === '(') depth++;
else if (ch === '>' || ch === ')') depth = Math.max(0, depth - 1);
else if (depth === 0) {
if (ch === '{') break;
flat += ch;
}
}
const head = /\b(class|struct|enum|protocol|extension|actor)\s+[\w.]+\s*([\s\S]*)$/.exec(flat);
if (!head) return { supers: [], extension: false };
const clause = /^:([\s\S]*?)(?:\bwhere\b|$)/.exec(head[2]!.trim())?.[1] ?? '';
return {
supers: clause.split(',').map((item) => /([A-Za-z_]\w*)\s*$/.exec(item.trim())?.[1]).filter((w): w is string => !!w),
extension: head[1] === 'extension',
};
}
/** The Swift types around a call (depth 0) and what they inherit or conform to, by distance. */
function swiftHierarchyAt(ref: UnresolvedRef, context: ResolutionContext): Map<string, number> {
let memo = SWIFT_HIERARCHIES.get(context);
if (!memo) {
memo = new WeakMap();
SWIFT_HIERARCHIES.set(context, memo);
}
const hit = memo.get(ref);
if (hit) return hit;
const depths = new Map<string, number>();
const queue: Array<[string, number]> = context
.getNodesInFile(ref.filePath)
.filter((n) => SWIFT_TYPE_KINDS.has(n.kind) && n.startLine <= ref.line && n.endLine >= ref.line)
.map((n) => [n.name.split('.').pop()!, 0]);
while (queue.length > 0 && depths.size < 40) {
const [name, depth] = queue.shift()!;
if (depths.has(name)) continue;
depths.set(name, depth);
for (const sup of swiftDeclOf(name, context).supers) queue.push([sup, depth + 1]);
}
memo.set(ref, depths);
return depths;
}
/**
* Of the in-scope members a bare / `self.` / `super.` Swift call could mean,
* the nearest: the type's own, else its superclass's — Alamofire's
* `self.cancel()` in a DownloadRequest extension is DownloadRequest's
* override, not Request's.
*/
function nearestSwiftMembers(candidates: Node[], ref: UnresolvedRef, context: ResolutionContext): Node[] {
const hierarchy = swiftHierarchyAt(ref, context);
const depthOf = (n: Node): number | undefined => {
if (!SWIFT_MEMBER_KINDS.has(n.kind) && n.kind !== 'constant' && n.kind !== 'variable') return undefined;
const cut = n.qualifiedName.lastIndexOf('::');
return cut > 0 ? hierarchy.get(n.qualifiedName.slice(0, cut).split(/::|\./).pop()!) : undefined;
};
const depths = candidates.map(depthOf).filter((d): d is number => d !== undefined);
if (depths.length < 2) return candidates;
const nearest = Math.min(...depths);
return candidates.filter((n) => {
const d = depthOf(n);
return d === undefined || d === nearest;
});
}
/**
* Methods of Swift's standard collections, strings and optionals and of
* Foundation / UIKit views — names a project type rarely carries itself.
* Ones it often does (`cancel`, `resume`, `store`, `validate`, `load`) are
* left out: Alamofire's `request.resume()`, Kingfisher's `cache.store(…)`.
*/
const SWIFT_STD_METHODS: ReadonlySet<string> = new Set([
'append', 'insert', 'remove', 'removeAll', 'removeFirst', 'removeLast', 'removeValue', 'contains', 'map',
'compactMap', 'flatMap', 'filter', 'reduce', 'forEach', 'sorted', 'sort', 'first', 'last', 'min', 'max',
'firstIndex', 'lastIndex', 'index', 'enumerated', 'reversed', 'joined', 'split', 'prefix', 'suffix',
'dropFirst', 'dropLast', 'allSatisfy', 'randomElement', 'shuffled', 'popLast', 'replaceSubrange',
'replacingOccurrences', 'components', 'trimmingCharacters', 'hasPrefix', 'hasSuffix', 'lowercased',
'uppercased', 'appending', 'updateValue', 'merge', 'merging', 'union', 'intersection', 'subtracting',
'formUnion', 'isEqual', 'addSubview', 'removeFromSuperview', 'setNeedsDisplay', 'setNeedsLayout',
'layoutIfNeeded', 'addGestureRecognizer', 'addTarget', 'addObserver', 'removeObserver', 'eraseToAnyPublisher',
]);
/**
* Whether a Swift file's declarations are visible from another file as far as
* separate modules go: one under a `…Tests` directory (SwiftPM's
* `Tests/VaporTests`, Xcode's `KingfisherTests`) or in a playground only from
* inside it.
*/
function isSwiftTargetVisible(declFile: string, fromFile: string): boolean {
const segments = declFile.split('/').slice(0, -1);
const target = segments.findIndex((seg) => /Tests$|\.playground$/.test(seg));
if (target < 0) return true;
const prefix = segments.slice(0, target + 1).join('/') + '/';
return fromFile.startsWith(prefix);
}
/**
* Whether a Swift call of that shape can mean `n`. A member reached with no
* receiver or through `self.` belongs to a type around the call or to what
* it inherits or conforms to — `super.` to the latter only; top-level code
* has no implicit self. Charts' `min(a, b)` went to a range type's `min`
* field, `super.init(…)` to an Objective-C demo's `init` 170 times. A member
* reached down a longer chain keeps its match unless its name is one the
* standard types all carry: `axis.entries.removeAll()` is the array's, not
* a chart data class's (unless the receiver names the owner, or the owner
* is an outside type the project only extends).
*/
function isSwiftCallTarget(n: Node, shape: SwiftCallShape | null, ref: UnresolvedRef, context: ResolutionContext): boolean {
if (!shape) return true;
// A global `let` / `var` from another file is subscripted, not called —
// Charts' `min(a, b)` went to a playground page's `let min = 20.0`.
if ((n.kind === 'constant' || n.kind === 'variable') && n.filePath !== ref.filePath && !shape.subscript) return false;
// A test target imports the module it tests, never the reverse.
if (!isSwiftTargetVisible(n.filePath, ref.filePath)) return false;
const cut = n.qualifiedName.lastIndexOf('::');
const owner = cut > 0 ? n.qualifiedName.slice(0, cut).split(/::|\./).pop()! : null;
if (!SWIFT_MEMBER_KINDS.has(n.kind) && !(owner !== null && (n.kind === 'constant' || n.kind === 'variable'))) {
if (n.kind !== 'function') return true;
return shape.shape === 'bare' || owner !== null || (shape.shape === 'chained' && /^[A-Z]/.test(shape.receiver));
}
if (owner === null) return true;
if (shape.shape === 'chained') {
if (shape.receiver === '' || !SWIFT_STD_METHODS.has(n.name)) return true;
return sharesReceiverWord(shape.receiver.split('.').pop()!, n) || !swiftDeclOf(owner, context).projectType ||
swiftDeclaresLabel(n, shape.label, context);
}
const depth = swiftHierarchyAt(ref, context).get(owner);
return depth !== undefined && (shape.shape !== 'super' || depth > 0);
}
/**
* Methods of Rust's Option / Result / iterators / collections / strings /
* smart pointers — names a project type rarely carries itself. Ones it often
@@ -2026,6 +2280,14 @@ function isObjcSelfSendTarget(n: Node, ref: UnresolvedRef, context: ResolutionCo
const OBJC_SYSTEM_SUPERS: Readonly<Record<string, string>> = {
UIResponder: 'NSObject', UIView: 'UIResponder', UIViewController: 'UIResponder', UIWindow: 'UIView',
UIControl: 'UIView', UIButton: 'UIControl', UITextField: 'UIControl', UISwitch: 'UIControl', UISlider: 'UIControl',
UISegmentedControl: 'UIControl', UIStepper: 'UIControl', UIPageControl: 'UIControl', UIDatePicker: 'UIControl',
UIRefreshControl: 'UIControl', UIStackView: 'UIView', UINavigationBar: 'UIView', UIToolbar: 'UIView',
UITabBar: 'UIView', UISearchBar: 'UIView', UIVisualEffectView: 'UIView', UIActivityIndicatorView: 'UIView',
UIProgressView: 'UIView', UIPickerView: 'UIView', UITableViewHeaderFooterView: 'UIView',
UITableViewController: 'UIViewController', UICollectionViewController: 'UIViewController',
UINavigationController: 'UIViewController', UITabBarController: 'UIViewController',
UIPageViewController: 'UIViewController', UISplitViewController: 'UIViewController',
UIAlertController: 'UIViewController', UIHostingController: 'UIViewController',
UIImageView: 'UIView', UILabel: 'UIView', UIScrollView: 'UIView', UITableView: 'UIScrollView',
UICollectionView: 'UIScrollView', UITextView: 'UIScrollView', UITableViewCell: 'UIView',
UICollectionReusableView: 'UIView', UICollectionViewCell: 'UICollectionReusableView',
@@ -2798,9 +3060,12 @@ export function matchByExactName(
? rustGoCallShape(ref, context) : null;
const kotlinStdChain = ref.language === 'kotlin' && ref.referenceKind === 'calls' && KOTLIN_STD_METHODS.has(ref.referenceName)
? kotlinChainReceiver(ref, context) : null;
const swiftShape = ref.language === 'swift' && ref.referenceKind === 'calls' && /^[A-Za-z_]\w*$/.test(ref.referenceName)
? swiftCallShape(ref, context) : null;
const phpSelf = phpSelfReceiver(ref, context);
const filtered = sameName.filter((n) =>
!(kotlinStdChain !== null && !isKotlinStdChainTarget(n, kotlinStdChain)) &&
!(swiftShape && !isSwiftCallTarget(n, swiftShape, ref, context)) &&
!(rustGoShape && !isRustGoCallTarget(n, rustGoShape)) &&
!(scalaBare && !isScalaMemberInScope(n, ref, context)) &&
!(csharpBare && !isCsharpMemberInScope(n, ref, context)) &&
@@ -2857,7 +3122,8 @@ export function matchByExactName(
// file's symbol of that name, so a bare call has no cross-file candidate.
!(bareJs && n.filePath !== ref.filePath && isLocallyBoundJsName(ref.referenceName, ref.filePath, context))
);
const candidates = dartBare ? nearestDartMembers(filtered, ref, context) : filtered;
const candidates = dartBare ? nearestDartMembers(filtered, ref, context)
: swiftShape && swiftShape.shape !== 'chained' ? nearestSwiftMembers(filtered, ref, context) : filtered;
if (candidates.length === 0) {
return null;
@@ -3924,6 +4190,8 @@ export function clearNameMatcherMemos(context: ResolutionContext): void {
PHP_SUPERS.delete(context);
DART_SUPERS.delete(context);
DART_HIERARCHIES.delete(context);
SWIFT_DECLS.delete(context);
SWIFT_HIERARCHIES.delete(context);
KOTLIN_FILE_SCOPES.delete(context);
RUBY_ANCESTRY.delete(context);
CFML_CHAINS.delete(context);
@@ -6076,6 +6344,8 @@ export function matchFuzzy(
? rustGoCallShape(ref, context) : null;
const kotlinStdChain = ref.language === 'kotlin' && ref.referenceKind === 'calls' && KOTLIN_STD_METHODS.has(ref.referenceName)
? kotlinChainReceiver(ref, context) : null;
const swiftShape = ref.language === 'swift' && ref.referenceKind === 'calls' && /^[A-Za-z_]\w*$/.test(ref.referenceName)
? swiftCallShape(ref, context) : null;
const phpSelf = phpSelfReceiver(ref, context);
// Names are case-sensitive in every language but a handful: Rust's
// `Bytes` is not the method `bytes`, Python's builtin `dir(…)` not a class
@@ -6101,6 +6371,7 @@ export function matchFuzzy(
!(scalaBare && !isScalaMemberInScope(n, ref, context)) &&
!(rustGoShape && !isRustGoCallTarget(n, rustGoShape)) &&
!(kotlinStdChain !== null && !isKotlinStdChainTarget(n, kotlinStdChain)) &&
!(swiftShape && !isSwiftCallTarget(n, swiftShape, ref, context)) &&
!(phpSelf && (n.kind !== 'method' || !isPhpMethodInScope(n, ref, phpSelf, context))))
.filter((n) => (ref.referenceKind !== 'references' && ref.referenceKind !== 'function_ref') ||
sameLanguageFamily(n.language, ref.language));