fix(swift): a call through a type path lands on the type it names (#2118)

`API.PackageController.GetRoute.query(on:)` kept only `query` — the
receiver was a navigation_expression, not an identifier — and the bare
name exact-matched whichever type's `query` came first. On
SwiftPackageIndex-Server every route type has one, so health checks and
package-controller tests "called" the dependency controller (or a test's
own `query`).

Extraction (TS and the kernel's swift walker, byte-parity): a receiver that
is a path of capitalized type names, two segments or more, not led by
`Self.`, is kept: `API.PackageController.GetRoute.query`. Instance chains
(`self.store.load()`) and mixed paths (`URLSession.shared.data`) are
unchanged.

Resolution (swift-type-visibility.ts, called decisively from
matchReferenceInner so nothing falls through to name guessing): the member
on the type the path names — a method, an enum case with associated values,
or a nested type's initializer. A member's owner path is its qualified name
with the outermost extension's written path read back from its line
(`extension API.PackageController { enum ShowRoute … }` →
`API::PackageController::ShowRoute`), so twin `Feature::State`s declared in
`extension BasicsView.Feature` and `extension ObservableBasicsView.Feature`
are told apart. A path shortened inside its namespace or behind a module
qualifier fits; two owners, or none, leave the call unresolved. TCA's
`@Reducer enum Path { case detail }` generates `Path.State`/`Path.Action`,
so `Path.State.detail(…)` lands on the written case.

A/B vs main (edge diffs, every change classified): SPI 76 retargeted, all to
the type the line names, 7 dropped (Fluent `App.Version.query`, `Plot.Node.*`
— SDK members main pinned on project methods); TCA 61 retargeted, 1 dropped
(`State.StateReducer.scope` → an unrelated `Store::scope`); mastodon-ios 177
retargeted (`Mastodon.API.V2.Instance.instance`, `L10n.Plural.Count.vote`),
11 SDK drops; swift-nio 6 calls into a deprecated alias became instantiates
of `ChannelOptions.Types.*`; IceCubes 1/1, Alamofire 2 SDK drops.
Kernel/wasm parity 0 diffs over 2,752 files on six repos.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Colby Mchenry
2026-09-30 03:59:28 +00:00
committed by GitHub
parent e845b1cd10
commit b50c7c0a70
7 changed files with 314 additions and 2 deletions
+27 -1
View File
@@ -110,6 +110,24 @@ fn strip_js_ws(s: &str) -> String {
s.chars().filter(|c| !is_js_space(*c)).collect()
}
/// SWIFT_TYPE_PATH_RECEIVER (tree-sitter.ts): `API.PackageController.GetRoute` —
/// two segments or more, each an ASCII capital then `[A-Za-z0-9_]*` (JS `\w`),
/// not led by `Self.`.
fn is_type_path(s: &str) -> bool {
if s.starts_with("Self.") {
return false;
}
let mut segments = 0;
for seg in s.split('.') {
let b = seg.as_bytes();
if b.is_empty() || !b[0].is_ascii_uppercase() || !b[1..].iter().all(|c| c.is_ascii_alphanumeric() || *c == b'_') {
return false;
}
segments += 1;
}
segments >= 2
}
struct Scope {
row: u32,
kind: &'static str,
@@ -1061,8 +1079,16 @@ impl<'t> Walker<'t> {
} else {
method_name.to_string()
};
} else if let Some(path) = receiver
.filter(|r| r.kind() == "navigation_expression")
.map(|r| strip_js_ws(self.text(r)))
.filter(|p| is_type_path(p))
{
// A type path, `API.PackageController.GetRoute.query(on:)`:
// keep it for the resolver (tree-sitter.ts SWIFT_TYPE_PATH_RECEIVER).
callee_name = format!("{path}.{method_name}");
} else {
// self_expression / super_expression / inner nav /
// self_expression / super_expression / instance nav /
// postfix / multi_line_string_literal → bare method name.
callee_name = method_name.to_string();
}