Files
codegraph/docs/design/swift-kernel-port-checklist.md
T
09e301bbfa feat(kernel): R7b Swift walker — swift module, tree-sitter-swift 0.7.3 bump, swift default-routed (#1381)
Fifth R7b batch-3 port, checklist-first recipe
(docs/design/swift-kernel-port-checklist.md, 1,056 lines — the largest of the
arc, with a built-extractor-validated emission pin and a childForFieldName
truth table).

Grammar bump first, validated standalone: tree-sitter-wasms ^0.4.0 (ABI 13) →
crate 0.7.3 — with a provenance twist: the wasm is built from the CRATE
TARBALL's src/ (alex-pinkus keeps generated files off main and the
0.7.3-with-generated-files tag ships an older ABI-14 generation that can never
sha-match; grammar.json rules are JSON-equal; the tarball is byte-for-byte
what the kernel's cargo build compiles — table identity by construction).
Older crates evaluated and rejected: clean-parse shapes are byte-identical on
0.7.3 (53-line CST battery diff, all inert), so an older pin buys nothing and
loses the macro-era wins. Delta = error-set membership (63 old-error files
parse clean: swift-testing #expect, #Preview/#GET macros, package access,
typed throws — vapor 23.1%→9.3%; 21 NEW-only regressions in 3 probed
construct classes) + two gate-found categories: docstring boundaries near #if
directives (7 clean files, docstring-field-only — verified mechanically) and
array-literal-callee call refs (2 refs, 1 file). Every hunk classified via
the error-union rule + parked-ref↔edge ripple pairing.

Walker (the arc's biggest) centers on the #1020 DEDICATED property branch:
computed properties → property nodes with the getter walked under the
property (SwiftUI body), static let/var → constant/variable, stored → field,
decorator/type-annotation/@Siblings-attr-arg refs all attached to the
ENCLOSING TYPE, stored initializer calls attributed to the class. Preserved
bug-for-bug: the never-resolving 'parameter' field (zero param type refs,
zero signatures), present-false isAsync, open→internal visibility,
everything-is-extends inheritance (first type_identifier per specifier), no
instantiates refs ever, subscript reads as `calls arr`, `defer` as `calls
defer`, multi-case enum entries minting only the first case, /** */ block
docs ignored AND chain-breaking, init/deinit/subscript minting no nodes with
visitNode-routed bodies (calls → class, static reads → nothing), multi-
segment extension resolveName, sugar extension names, the #selector shapes,
and the value_argument label-forward skip. ONE fix found by the sweep (then
pinned in the fixture + checklist): the shared `assignment` shadow-prune case
is swift-live — declared-then-assigned `let X: T` prunes X as a value-ref
target.

Gates: sweeps 0-diff Alamofire 89/98, vapor 224/247, swift-nio 407/554
(--max-deferral 0.3 — swift error incidence is 9–27% on BOTH arms,
structural; every deferral count matches the survey's table exactly);
full-init dumps byte-identical ×3 (31.9k/20.7k/126.3k lines); the Alamofire
census reproduces property=348 (the #1020 number) on the kernel arm;
kernel-swift-parity suite (206-line torture + CRLF + the #if-between-enum-
cases defer fixture) + swift grammar-parity row; full suite 2,626 green ×2
under CODEGRAPH_KERNEL_EXPECT=1. DEFAULT_ROUTED += swift (14 langs).

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 17:09:12 -05:00

1085 lines
72 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Swift kernel port (R7b) — the bug-for-bug checklist
**Status: PORT COMPLETE (2026-07-20)** — walker `codegraph-kernel/src/swift.rs`,
all gates passed (bump validated standalone with the diff classified per the
error-union rule + ripple pairing + two gate-found categories 7/8 below;
parity sweeps 0-diff Alamofire 89/98 / vapor 224/247 / swift-nio 407/554 at
--max-deferral 0.3 with exactly the predicted deferral counts; dump gates
byte-identical ×3; the Alamofire census reproduced property=348 on the kernel
arm; kernel-swift-parity suite; DEFAULT_ROUTED += swift). **One survey
correction found by the swift-nio sweep: §Shadow prune understated the case
list — the shared `assignment` prune case (tree-sitter.ts:829) is ALSO
swift-live** (a declared-then-assigned `let X: T` + `X = …` branches bumps
per assignment via the directly_assignable_expression's simple_identifier,
pruning X — swift-nio's main.swift files pin it). Survey basis: every
TS-side branch a `.swift` file exercises, with file:line anchors as of
**`a6c62d7`** (HEAD at survey time, clean main). Every grammar-shape and
emission claim below was **probed against both the current production wasm
(tree-sitter-wasms 0.1.13 build of npm tree-sitter-swift ^0.4.0, ABI 13) and a
fresh crate-0.7.3 build (ABI 15)** — CST probes for shapes, **plus the built
`dist/` extractor run end-to-end on a 106-line torture file and on all 98
Alamofire files** (emissions, order, counts pinned from real output, not
inference). Probe scripts + raw outputs: session scratchpad `svy-swift/`
(§Probe artifacts). Read WITH `docs/design/rust-kernel-migration-plan.md`
(§0a recipe, §2 boundary, §5 gates) and the format precedents
(`rust-lang-`, `csharp-`, `ruby-`, `php-kernel-port-checklist.md` — php's
non-neutral-bump "enumerate + classify" gate is the template here).
**Blocking findings: none.** Three eyes-open findings that shape the port:
(1) the grammar bump is **NOT graph-neutral** — the old→new delta is confined
to *error-set membership* (65 files across the three gate repos parse
differently-erroneously per arm; three NEW-only regression construct classes
enumerated in §Grammar-bump deltas) while every clean-parse extractor-relevant
shape probed **byte-identical** (53-line CST diff across the whole battery,
all classified inert); the bump gate is php-style enumerate+classify, not
expect-zero. (2) **Swift error incidence is structurally high — 9.226.5% on
the NEW arm (both-arm reality, not a bump artifact)** — the parity-sweep
deferral guard MUST be raised for swift (§Deferral policy); a 0.1 guard fails
the sweep on all three gate repos. (3) The port's center of gravity is the
**dedicated in-class property branch in tree-sitter.ts (#1020)** — it is NOT
in languages/swift.ts, it attributes its refs to the *enclosing type* rather
than the property node, and Alamofire's 348 computed-property nodes (re-counted
at survey time: exactly 348) ride it (§Dedicated property branch).
## Grammar prep (NOT staged — land FIRST, before any walker exists)
swift is **not** in `VENDORED_WASM_LANGS` (grammars.ts:291) — production loads
`require.resolve('tree-sitter-wasms/out/tree-sitter-swift.wasm')` (mapping
`swift: 'tree-sitter-swift.wasm'` at grammars.ts:34; `.swift` → swift at
grammars.ts:105), a 2023/24-era **ABI-13** build of npm tree-sitter-swift
^0.4.0 (sha256 `41c4fdb2…`, 3,147,876 bytes; 540 node types / 45 fields).
- **Version: crate `tree-sitter-swift = "=0.7.3"`** (crates.io max_stable,
published 2026-06-01; repo alex-pinkus/tree-sitter-swift).
- **PROVENANCE TWIST — the crate tarball is the canonical generated-file
source, NOT the git tag.** alex-pinkus keeps generated files off `main`;
tag `0.7.3-with-generated-files` (commit
`31d17fe7e818a2048c808b5c6fdc2dc792f4f5b5`) ships an **ABI-14**
parser.c (LANGUAGE_VERSION 14, SYMBOL_COUNT 565 — an *older-generator* run,
plus `parser_abi13.c`/`parser_abi14.c` variants), while the crates.io
tarball ships an **ABI-15** regeneration (LANGUAGE_VERSION 15, SYMBOL_COUNT
558, STATE_COUNT 10321). The two are the SAME grammar — `grammar.json`
`rules` and `externals` are JSON-equal between tag and crate (probed) — but
the parser.c bytes differ, so a tag-side sha-match is impossible.
**Build the wasm from the CRATE TARBALL's `src/`** (that is literally what
the kernel's cargo build compiles → table-identity by construction, the
csharp-precedent verification posture). Never touch the tag's parser.c and
never run `tree-sitter generate`.
- crate `src/parser.c` sha256
`d3edff6effe31b9a507f496577407987343b101b23eb7bee7a9b050e8ab5d27a` (20,642,243 bytes — expect slow builds)
- crate `src/scanner.c` sha256
`f3d6271d64f58c39eed544104a70ca2cf9ecbf80c5d900620f1afd38836542cb`
(== the tag's scanner.c byte-for-byte). **External scanner: YES** — the
crate build compiles it automatically; the wasm build picks it up from `src/`.
- **Build (survey-verified commands):**
```
curl -sL https://crates.io/api/v1/crates/tree-sitter-swift/0.7.3/download -o ts-swift-0.7.3.crate
tar xzf ts-swift-0.7.3.crate && cd tree-sitter-swift-0.7.3
# crate tarball lacks tree-sitter.json (metadata only — no effect on tables);
# copy it from the 0.7.3-with-generated-files tag clone:
cp ../tag-clone/tree-sitter.json .
npx -y tree-sitter-cli@0.25.10 build --wasm -o tree-sitter-swift.wasm .
```
(brew emcc present; one benign scanner warning. Survey artifact: ABI 15,
3,726,622 bytes, sha256
`cc77a63b8487956270e2f385e29a03ba0773ba532a3c8a8844a26b4c98793843`, 563 node
types / 46 fields, at scratchpad `svy-swift/tree-sitter-swift.wasm`.)
- **Version-choice rationale (evaluated, not defaulted):** the feared
alex-pinkus shape churn did NOT materialize for extraction — the full
OLD-vs-NEW CST battery (types/funcs/props/calls/imports/statics/docs) diffs
in only 53 lines, all inert (§deltas). An older crate (0.5.0/0.6.0) was
considered as a closer-match alternative and REJECTED: clean-parse shapes are
already identical on 0.7.3, so an older pin buys no shape proximity and
loses the error-set wins that dominate the delta — swift-testing `#expect`
(Alamofire's whole test suite), `#Preview`/`#GET`-style freestanding macros
(vapor 57→23 error files), `package` access, and typed `throws(E)` all parse
ONLY on 0.7.x. 0.7.3's three regression construct classes (§deltas) are
net-smaller (21 files) than its fixes (63 files) on the gate repos.
- **Staging plan:** vendor to `src/extraction/wasm/tree-sitter-swift.wasm`;
add `'swift'` to `VENDORED_WASM_LANGS` (grammars.ts:291) with an R7b
comment noting the crate-tarball provenance (NOT tag-sha-matched — state
why); pin `tree-sitter-swift = "=0.7.3"` in codegraph-kernel/Cargo.toml
(crate + wasm move TOGETHER); add `'swift'` to `GRAMMAR_LANGUAGES` in
`__tests__/kernel-grammar-parity.test.ts:39`; kernel symbol
`tree_sitter_swift::LANGUAGE` in langs.rs `grammar_for` + the `LANGUAGES`
const (langs.rs:18, `[&str; 13]` → 14). MIT license. `copy-assets` already
globs `src/extraction/wasm/*.wasm`.
- **Bump lands FIRST, standalone, full suite green + the enumerate+classify
dump gate (§Gates)** — the diff is expected NON-empty and must be confined
to the classified categories.
## Grammar-bump deltas (old ^0.4.0 → 0.7.3), every one classified
**Clean-parse shapes: byte-identical.** The whole probe battery (class-family,
functions, properties incl. wrappers/observers/tuple-lets, every call shape,
imports, static reads, docstrings) produced a 53-line CST diff, fully
decomposed into:
**Inert (verified against every consuming branch):**
1. `#selector` token split: OLD one anon `#selector` token; NEW anon `#` +
`selector`. `selector_expression`'s NAMED children are unchanged →
`normalizeSpecial` (function-ref.ts:685) and the body walker see identical
trees. Inert.
2. `#warning(…)` internals: OLD = one `diagnostic` leaf swallowing the full
text; NEW = `diagnostic` with an anon `#` child. `diagnostic` matches no
type list on either arm; no named children either way; no stray sibling
call on NEW (probed clean-file). Inert.
3. ABI/table renumber (540→563 node kinds, 45→46 fields) — kind-id churn
only; every consumer keys on type STRINGS.
**Behavior-changing — ALL of it is error-set membership** (the wasm arm never
defers: production extracts from error-recovery trees, so files whose
error-status or recovery changes WILL diff in the bump gate's dump):
4. **OLD-error → NEW-clean (the bump working as intended), 63 files across
the gate repos.** Construct classes, each probed to a minimal repro:
freestanding macros — `#Preview { }` (OLD: ERROR + orphan lambda; NEW:
`macro_invocation` — a NEW-only node type, in NO type list → recursed, its
lambda's calls attribute to the enclosing scope), swift-testing `#expect(…)`
(all 4 OLD-only Alamofire error files are Tests using it), vapor's `#GET`
route macros (drives vapor 57→23); `package` access modifier; typed throws
`throws(ErrorType)` (swift-nio ByteBuffer-views). Node/edge/ref diffs in
these files are expected and accepted.
5. **NEW-only error regressions, 21 files (3 Alamofire / 1 vapor / 17
swift-nio), three construct classes probed to minimal repros:**
- `#if`/`#elseif` **between enum cases** (`enum E { case a\n#if DEBUG\n
case b\n#endif }`) → NEW ERROR, OLD clean. (`#if` at top level, between
CLASS members, around postfix `.modifier(x)` chains, and with `||`
conditions all stay clean on both.)
- **Parenthesized-compound directive conditions**
(`#if (compiler(<6.1) && !os(WASI)) || (compiler(>=6.1) && …)`) → NEW
ERROR (swift-nio's lock/NIOLock family).
- **Optional-subscript + cast + coalesce chain**
(`info?["k"] as? String ?? "d"`) → NEW ERROR (Alamofire HTTPHeaders).
- (vapor's one file combines a backtick-escaped `` `default` `` parameter
label with other syntax; the label alone parses clean on both.)
These files' dumps diff (different recovery trees). Accept as classified;
count them; they are the deferral set the kernel will hand to wasm anyway.
6. **BOTH-arm error files that diff anyway**: two grammars recover
differently from the SAME error (e.g. vapor Authenticator's
`if let c = try? await …` — errors on both, different trees). Any hunk in
a file that is in EITHER arm's error list falls under this category — the
gate's mechanical rule is per-file, not per-construct (below).
7. **(Found at bump-gate time, survey-missed.) Docstring boundaries near
`#if` directives on CLEAN files.** The NEW grammar's directive/comment
sibling structure changes what getPrecedingDocstring accumulates next to
`#if`/`#endif` lines: 7 clean-on-both files (5 Alamofire, 2 swift-nio
Mocking.swift) diff in the docstring field ONLY (verified mechanically:
every other node field byte-equal). Direction: NEW gains a docstring the
OLD chain dropped (`webSocketRequest` gains "Only Apple platforms…") or
extends it with directive-adjacent text (`_withWindowsPaths` gains a
leading "ENABLE_MOCKING" line). The walker reproduces this automatically
(same shared docstring code over the same NEW trees).
8. **(Found at bump-gate time, survey-missed.) Array-literal-callee call
refs.** One clean file (swift-nio IPv4Header.swift) emits 2 NEW-only
`calls` refs whose callee text is a whole multi-line array literal —
a call_expression whose namedChild(0) is the array literal on NEW where
OLD parsed the same bytes as separate expressions. Same garbage-but-
deterministic family as `m[i][j]`; the walker reproduces it via the raw
func-text rule.
**Bump-gate rule (php precedent, adapted):** run the old-wasm vs new-wasm
full-init dump diff on all three gate repos; **every diffing file must be in
the union of the two arms' `hasError` file lists** (compute both lists first —
`svy-swift/error-incidence.cjs` does exactly this); a diff in a clean-on-both
file blocks the bump. Expect RESOLUTION ripple beyond the erroring files
(recovered symbols re-resolve refs in OTHER files — php's category-3
mechanism); prove ripple hunks mechanically by ref↔edge pairing
(php checklist's ripple-proof.mjs pattern) instead of re-litigating per file.
### Error incidence (probed, all `.swift` ≤1 MiB, both arms)
| Repo | files | OLD (^0.4.0 ABI-13) | NEW (0.7.3) | OLD-only | NEW-only | both |
|---|---|---|---|---|---|---|
| Alamofire | 98 | 10 (10.20%) | 9 (9.18%) | 4 | 3 | 6 |
| vapor | 247 | 57 (23.08%) | **23 (9.31%)** | 35 | 1 | 22 |
| swift-nio | 554 | 154 (27.80%) | 147 (26.53%) | 24 | 17 | 130 |
### Deferral policy — swift needs a c/cpp-style exemption
Swift sits FAR outside the ts/java/py/go norm (00.42%): heavy `#if`
platform-conditionalization (swift-nio), `try? await` in conditions, and
`@storageRestrictions` init-accessors error on BOTH arms. Per-file
`has_error()` → `defer:` like every walker; **run parity sweeps with
`--max-deferral 0.3`** (covers swift-nio's 26.5% with headroom; Alamofire/vapor
sit under 10%). Double-digit deferral on swift is grammar reality, not a
walker bug — but a deferral-rate JUMP vs the table above is a walker bug.
Deferred files are served by wasm and byte-match by construction; the sweep's
compared set is the ~7391% that parse clean.
## Architecture decisions
1. **No preParse.** `swiftExtractor` has no `preParse` hook (languages/swift.ts
— whole file, no such key) → `preParsedSource` (kernel/index.ts:96) is a
no-op; both arms parse raw bytes. Swift's `#if` is grammar-native
(`directive` nodes, both branches kept as siblings — probed) — nothing to
blank, unlike C/C#.
2. **Framework detection decides decoded vs raw path per repo.** THREE swift
resolvers carry `extract()` hooks (parse-worker.ts:93-99 forces any
language with an applicable framework `extract()` onto the decoded
`extractFromSource` path): `swiftUIResolver` (frameworks/swift.ts:11,
detect = any .swift containing `import SwiftUI`, else any file path ending
`.xcodeproj`/`.xcworkspace` — note `getAllFiles()` returns indexed files,
so the xcodeproj leg rarely fires; the pbxproj inside doesn't end with
`.xcodeproj`), `uikitResolver` (:136, detect = any .swift containing
`import UIKit`/`UIViewController`/`UIView`), `vaporResolver` (:269, detect
= `Package.swift` containing `vapor`, else `import Vapor`). Also
`expoModulesResolver` (expo-modules.ts:154, languages swift+kotlin, has
extract) and `swiftObjcBridgeResolver` (swift-objc.ts:251 — resolve-only,
NO extract, doesn't force decode). Gate-repo reality (verified):
**Alamofire → decoded** (Example/ imports UIKit; watchOS example imports
SwiftUI), **vapor → decoded** (Package.swift), **swift-nio → RAW buffers
path** (no detector fires) — one gate repo per transport, keep all three.
3. **Framework extractors themselves need NO port** (regex-over-raw-source TS,
run in extractFromSource after either arm). Their input contract on the
walker: node names/kinds (`struct`/`class`/`component` resolution by name,
frameworks/swift.ts:432) and — for the closure-collection pass —
function/method node **extents** (§CC pass). Vapor's route refs carry
filePath+language but those are FRAMEWORK refs (TS-side, post-walk).
4. **No extraction ref carries filePath** — swift has no visitNode hook and no
framework-independent denormalized refs (probed: every ref in the
torture/Alamofire runs has filePath undefined). **The v2 REF_FLAG_FILE_PATH
wire slot (buffers.rs:125 / layout.ts:98) stays UNUSED for swift** — flag
0 on every ref, like rust/csharp.
5. **One walker module** (suggest `codegraph-kernel/src/swift.rs`), registered
in langs.rs (`grammar_for` → `tree_sitter_swift::LANGUAGE.into()`,
`LANGUAGES` += "swift"). Skeleton cribs: **java.rs** for the class-like
scope stack + static-member refs + decorators; **ruby.rs/php.rs** for
hook-heavy dispatch; **rustlang.rs** for the `node_ids` dedupe + import
hooks. The dedicated property branch has no precedent in any walker — it
is new logic, transcribe from §Dedicated property branch.
6. **`.swift` → swift only** (grammars.ts:105); no content sniffing, no
dialect. MAX_FILE_SIZE (1 MiB) and generated-file skips are
orchestrator/TS-side and shared.
7. **No POST_PASSES entry** (kernel/index.ts:81 — none for swift) →
`tryKernelExtractRaw` stays eligible.
## Extractor config (languages/swift.ts — 138 lines, read it whole)
Types: functionTypes=[`function_declaration`]; classTypes=[`class_declaration`];
methodTypes=[`function_declaration`] (== functionTypes — the 994 branch handles
both; the 1027 methodTypes branch NEVER fires for swift);
interfaceTypes=[`protocol_declaration`]; structTypes=[`struct_declaration`]
(**DEAD — node type absent from BOTH grammars**, probed against the kind
tables); enumTypes=[`enum_declaration`] (**DEAD** likewise);
enumMemberTypes=[`enum_entry`]; typeAliasTypes=[`typealias_declaration`];
importTypes=[`import_declaration`]; callTypes=[`call_expression`];
variableTypes=[`property_declaration`, `constant_declaration`]
(**`constant_declaration` DEAD** — absent from both grammars; swift `let`/`var`
at every scope is `property_declaration`). nameField=`name`, bodyField=`body`,
paramsField=`parameter`, returnField=`return_type`.
**THE FIELD-LOOKUP SUBTLETY (do not trust CST dumps here):**
`fieldNameForChild` labels a function's return type and a typealias's value
`name:` — but `childForFieldName` resolves MULTIPLE field names on those
children through hidden rules. Probed truth-table, IDENTICAL on both grammars
(`svy-swift/field-probe.cjs`):
| lookup | result |
|---|---|
| function_declaration `name` | the simple_identifier (function name) |
| function_declaration `parameter` | **NULL** (parameter children are unfielded) |
| function_declaration `return_type` | **the return type node** (user_type/optional_type/…) |
| function_declaration `body` | function_body |
| typealias_declaration `name` | type_identifier (alias name) |
| typealias_declaration `value` | **the aliased type node** |
| property_declaration `name` | the `pattern` node |
| property_declaration `value` | the initializer expression |
| property_declaration `computed_value` | computed_property |
| enum_entry `name` | the FIRST case-name simple_identifier only |
The Rust walker must reproduce these exact resolutions (tree-sitter's native
`child_by_field_name` has the same semantics — but pin them in the parity
fixture, this is the branch the whole type-ref story hangs on).
Hooks PRESENT (port each exactly):
- **getReturnType = extractSwiftReturnType (swift.ts:14)** — POSITIONAL scan of
namedChildren: skip everything until the first `simple_identifier` (the
name; modifiers/type_parameters before it are skipped via the `seenName`
latch); after it, `function_body` → undefined (body reached); `user_type` →
that node; `optional_type` → its first namedChild of type `user_type`
(`?? null`); first such hit wins: text = `getNodeText(typeNode).trim()
.replace(/<[^>]*>/g, '')`, last `.`-segment (`KF.Builder` → `Builder` — the
comment explains why NOT the first type_identifier), must match
`/^[A-Za-z_]\w*$/` and ≠ `'Void'` else undefined. Probed results:
`-> Widget` → `Widget`; `-> Foo?` → `Foo`; `-> KF.Builder` → `Builder`;
`-> Result<Foo, Err>` → `Result` (single-level generic strip); **nested
generics break like rust** — `Result<Vec<Foo>, E>` → strip leaves
`Result, E>` → regex fails → undefined; `-> Void`/`-> [Foo]`(array_type)/
`-> (Int, Foo)`(tuple_type)/`-> (Int) -> Foo`(function_type) → undefined.
Parameters never match (their node type is `parameter`, not user_type — the
scan does NOT descend).
- **resolveName (swift.ts:60)** — class_declaration ONLY, `name` field must be
a `user_type` with >1 `type_identifier` children → the LAST one's text
(`extension KF.Builder` → `Builder`, so extension members merge with the
extended type's simple name, #750). Single-segment (`extension Plain`) and
non-user_type names → undefined (default extraction).
- **getSignature (swift.ts:76)** — `getChildByField(node,'parameter')` is
**ALWAYS NULL** (table above) → `if (!params) return undefined` →
**signature is undefined on EVERY swift function/method** (probed: sig=null
across the torture file and Alamofire). The returnType concatenation is dead
code. The walker emits NO signature for swift, ever.
- **getVisibility (swift.ts:87)** — scan ALL children (anon included) for a
child of type `modifiers`; on its WHOLE text: `.includes('public')` →
'public', `.includes('private')` → 'private', `.includes('internal')` →
'internal', `.includes('fileprivate')` → 'private' (**dead arm** —
'fileprivate' contains 'private', the second check already caught it);
no modifiers / no match → **'internal'**. QUIRKS, PRESERVE: **`open` →
'internal'** (not checked — probed: `open class func classFunc` →
vis=internal); `public private(set)` → 'public' (first match);
substring-matching runs over ATTRIBUTE text inside modifiers too (an
attribute whose lowercase text contains 'private'/'public' would flip
visibility — not expressible with real-world wrappers, but the walker must
substring-match the same way).
- **isStatic (swift.ts:101)** — any `modifiers` child whose text includes
`'static'` OR `'class'` → true. `class func`/`class var` → static ✓
deliberate. Same substring hazard (an `@objc(myclassthing)` attribute text
would set isStatic — match bug-for-bug).
- **classifyClassNode (swift.ts:112)** — scan ALL children for a child of
TYPE `struct` → 'struct', TYPE `enum` → 'enum', else 'class'. The grammar
puts the keyword in the `declaration_kind:` anon child (`class`/`struct`/
`enum`/`actor`/`extension`) → **actor → 'class', extension → 'class'**.
- **isAsync (swift.ts:121)** — modifiers text includes 'async'. **Effectively
always FALSE** (rust-precedent dead code): the grammar puts `async` as an
anon child AFTER the parameter list, never inside `modifiers` (probed:
`func asyncy() async throws` → isAsync=false). The walker must reproduce
false — note extractFunction/extractMethod store `isAsync: false` (hook
present, returns false), NOT undefined.
- **extractImport (swift.ts:130)** — signature = trimmed full node text;
identifier = namedChildren `find(type === 'identifier')` → moduleName = its
FULL text (`Foundation`, `UIKit.UIView` — dotted kept). Kinded imports
(`import class Darwin.FILE` — anon `class` child) and `@testable import`
(modifiers first — find skips it) still find the identifier (probed). No
identifier → null → because the hook exists, tree-sitter.ts:3350 fires →
nothing emitted (no generic fallback; not constructible in valid swift).
`handledRefs` never set → generic `imports` ref {from file node, name =
moduleName, line/col of the import node} (tree-sitter.ts:3183-3194). None
of the language-gated binding emitters (TS/py/rust/php/ruby, :3197-3234)
match swift.
Hooks ABSENT (the walker must NOT do these): `preParse`, `isConst` (**but
kinds still split let/const — via the swift-specific branches, not the
hook**), `isExported` (undefined on every node except the file node's literal
`false` and extractVariable's `?? false`), `isStatic` IS present (above),
`getReceiverType` (**never diverts extractFunction at :1522; methods happen
ONLY via isInsideClassLikeNode; no receiver QN override, no :1799
owner-contains fallback**), `resolveBody`, `recoverMangledName`,
`isMisparsedFunction`, `classifyMethodNode`, `extractPropertyName`,
`propertyTypes`, `fieldTypes`, `extraClassNodeTypes`, `packageTypes`/
`extractPackage` (no namespace node — top-level QNs are bare),
`extractModifiers`, `synthesizeMembers`, `extractBareCall`, `visitNode` hook,
`skipBodilessClass` (**but extractStruct/extractEnum have their own bodiless
skip** — §class-family), `methodsAreTopLevel`, `interfaceKind` (**protocols
are kind `interface`, NOT `protocol`**), `resolveTypeAliasKind`.
Registration: `EXTRACTORS.swift` (languages/index.ts:54), `FN_REF_SPECS.swift`
(function-ref.ts:392).
## tree-sitter.ts branches (anchors as of `a6c62d7`)
### visitNode dispatch — what each swift node hits
| Node | Branch | Behavior |
|---|---|---|
| `function_declaration` (top level) | functionTypes:994 → extractFunction:1517 | no receiver hook → always extractFunction at file scope |
| `function_declaration` (inside class-like) | :995 → extractMethod:1737 | methodTypes includes it + isInsideClassLikeNode (nodeStack top kind ∈ class/struct/interface/trait/enum/module, :1486) |
| `class_declaration` (class/struct/enum/actor/extension — ALL of them) | classTypes:1005 → classify → extractClass:1679 / extractStruct:1869 / extractEnum:1914 | §class-family |
| `protocol_declaration` | interfaceTypes:1054 → extractInterface:1834 | kind `interface`; §class-family |
| `typealias_declaration` | typeAliasTypes:1071 → extractTypeAlias:2890 | §Type aliases. Returns false → children ALSO recursed (nothing matches — user_type/function_type have no branches) |
| `property_declaration` (top level) | variableTypes:1098 (`!isInsideClassLikeNode()`) → extractVariable:2538 swift branch :2851 | §extractVariable. skipChildren=true + scanFnRefSubtree — **top-level initializers are NEVER walked: no calls/instantiates from them** (probed: `var topVar = compute()` emits nothing) |
| `property_declaration` (inside class-like) | **DEDICATED swift branch :1121-1193** | §Dedicated property branch. The :1098 gate fails first (isClassScopeConstantAssignment:1508 needs node.type `assignment` → false) |
| `protocol_property_declaration` | same dedicated branch (:1123) | protocol pushed = interface = class-like ✓ |
| `constant_declaration` | variableTypes | DEAD — node type doesn't exist |
| `import_declaration` | importTypes:1209 → extractImport:3170 | hook (§Extractor config) |
| `call_expression` (top-level statement / class-body initializer descent) | callTypes:1248 → extractCall:3684 | §extractCall. skipChildren stays false → nested calls recursed |
| `init_declaration` / `deinit_declaration` / `subscript_declaration` | **NO branch** | recursed by the generic loop :1295. **No node minted.** Their body statements are visited by visitNode (NOT visitFunctionBody) with the CLASS still on the stack → their calls attribute to the CLASS node (probed: `init { setupMonitor() }` → `calls setupMonitor from=class`; deinit's cleanup, subscript accessor bodies same). Consequences of the visitNode route: fn-ref capture fires (:990) but **extractStaticMemberRef does NOT** (body-walker-only, :5218) — a `Color.red` read inside `init` emits NOTHING |
| `enum_entry` | (via extractEnum's body loop only) | §class-family |
| `associatedtype_declaration`, `protocol_function_declaration`, `macro_invocation` (NEW), `directive`, `diagnostic`, `operator_declaration`-family, `precedence_group_declaration` | no branch | recursed, nothing extracted. **Protocol method requirements mint NO nodes** (probed — `func didFinish(_:)`/`static func build()` in a protocol produce nothing; only protocol_property_declarations become nodes via the dedicated branch) |
| `navigation_expression` (top level / class body) | no branch in visitNode | recursed. Static-member refs come ONLY from the body walker + walkAttrArgs — a top-level `Type.self` statement emits nothing |
| INSTANTIATION_KINDS (:354) | — | **contains NO swift node types** → `Foo()` is a plain `calls Foo` ref via extractCall; NO instantiates refs exist for swift, ever (probed) |
### Node creation, IDs, order
- createNode (:1308): id = `generateNodeId(filePath, kind, name, startRow+1)`
= `` `${kind}:${sha256(`${filePath}:${kind}:${name}:${line}`).hex.slice(0,32)}` ``
(tree-sitter-helpers.ts:18). File node id = literal `file:${filePath}`
(:509), name = basename, qualifiedName = filePath, endLine =
`source.split('\n').length`, isExported false. Dedupe/self-checks compare
ID STRINGS (`node_ids` vec pattern) — same-(kind,name,line) collisions are
legal.
- endLine extension via resolveBody (:1329) — no hook → no-op.
- contains edge from nodeStack top for every created node (:1363).
- qualifiedName = nodeStack names joined `::` (buildQualifiedName:1447;
namespacePrefix always empty outside C/C++). No package node → top-level
QN = bare name; members `Session::request`. Extension members ride the
extension's class node name (`Builder::done` for `extension KF.Builder`).
- extractModifiers hook absent → no decorators-from-modifiers merge (:1355
no-op); the `decorators` node field stays unset (probed dec=null
everywhere) — decorator refs are separate unresolved refs.
- **Emission order** (pinned by the torture probe, `svy-swift/extract-probe.out`):
file node → source-order walk (per construct: node + contains edge → its
refs in extractor-internal order) → fn-ref refs (flushFnRefCandidates :538)
→ value-ref EDGES (flushValueRefs :539, appended to edges LAST). Per
class: class node → extends refs (one per inheritance_specifier, source
order) → (extractCsharpPrimaryCtorParamRefs no-op — needs `parameter_list`
child type, absent) → decorates refs of the class → body members in source
order. Per dedicated-branch property: property/field/constant/variable
node → decorates (owner) → type-annotation refs (owner) → walkAttrArgs
static-member refs (owner) → then (stored only) initializer-descent refs.
Per method: node → type-annotation (return-type) refs → decorates → body
refs.
### The class family — extractClass / extractStruct / extractEnum / extractInterface
classify → 'struct': **extractStruct (:1869)** — body = `getChildByField(node,
'body')` (`class_body`); **bodiless → NO node** (:1876; record_declaration
exemption is C#-only; a bodiless struct isn't valid swift anyway). Node kind
`struct` {docstring, visibility, isExported:undefined}. extractInheritance →
§below. **NO extractDecoratorsFor** — `@main struct MyApp` emits NO decorates
ref (probed; the asymmetry: class YES, struct/enum/interface NO). Body
namedChildren visited with the struct pushed.
classify → 'enum': **extractEnum (:1914)** — body field (`enum_class_body`);
bodiless → no node. Kind `enum` {docstring, visibility, isExported:undefined}.
extractInheritance (the raw-value type rides it — `enum Suit: String` →
`extends String`; extends refs have NO builtin filter, probed `extends Int`).
NO decorates. Body loop: `enum_entry` ∈ enumMemberTypes → extractEnumMembers
(:1958): **`name` field path — `getChildByField(node,'name')` returns the
FIRST case name → ONE `enum_member` node, positioned at the enum_entry, then
RETURN.** QUIRKS, PRESERVE (probed): multi-case `case put, delete` → ONLY
`put` (delete DROPPED — the identifier-scan fallback :1967 is dead for swift
since the field always exists); associated values (`case success(Data)` —
`data_contents: enum_type_parameters`) and raw values (`= "H"`) are never
walked → NO refs from them; `indirect case` same. Non-enum_entry children
(computed properties, methods, nested types) → visitNode with the enum pushed.
classify → 'class' (incl. actor + extension): **extractClass (:1679)** — no
skipBodilessClass → a bodiless class_declaration would still mint (not valid
swift). Kind `class` {docstring, visibility, isExported:undefined}.
extractInheritance; extractCsharpPrimaryCtorParamRefs no-op;
**extractDecoratorsFor DOES run** (`@Observable class Model` → `decorates
Observable`). Push, visit body namedChildren via visitNode, no
synthesizeMembers, pop. **Extensions:** name via resolveName (multi-segment)
else raw `name`-field text — `extension Point` → class `Point` (a SECOND
class node named like the original — same name, different line → distinct
id); `extension [ServerTrustEvaluating]` → class named literally
**`[ServerTrustEvaluating]`** (raw sugar text, probed); `extension Array
where Element: Equatable` → class `Array`, and the `type_constraints` where
clause emits NOTHING (inheritance_constraint is not an inheritance_specifier).
**extractInterface (:1834)** — protocol_declaration → kind `interface` (no
interfaceKind hook) {docstring, isExported:undefined — **NO visibility, NO
decorates**}. extractInheritance on the protocol node (protocol inheritance
`: AnyObject, Identifiable` → extends refs). Body (`protocol_body`)
namedChildren visited with the interface pushed: protocol_property_declaration
→ dedicated branch (§below); protocol_function_declaration /
init_declaration / associatedtype_declaration → recursed, nothing.
### Inheritance — extractInheritance (:5291), the swift case (:5619-5632)
Direct namedChildren scan of the declaration node; ONLY the
`inheritance_specifier` case matches swift (one specifier per conformance,
each `inherits_from: user_type`): userType = child.namedChildren
`find(type==='user_type')` → typeId = ITS namedChildren
`find(type==='type_identifier')` — the FIRST — → one **`extends`** ref
{from class/struct/enum/interface node, name = typeId text, line/col of the
TYPE IDENTIFIER}. QUIRKS, PRESERVE: **everything is `extends`** — protocol
conformance, protocol inheritance, raw-value types; swift emits NO
`implements` refs from extraction (resolution's supertype machinery handles
both kinds); a qualified base `: Module.Base` takes the FIRST segment
(`Module`) — the outer namespace, not the type. Where-clause constraints and
generic bounds (`type_parameter` `name:` bounds) emit nothing. None of the
other clause cases (extends_clause/base_class_clause/delegation_specifier/…)
match swift node types.
## THE DEDICATED PROPERTY BRANCH (tree-sitter.ts:1113-1193) — #1020, the port's core
Fires for `property_declaration` | `protocol_property_declaration` when
`language === 'swift'` AND isInsideClassLikeNode(). Position in the else-if
ladder: AFTER variableTypes (:1098 — which in-class properties fail, see
dispatch table), BEFORE importTypes. **Alamofire baseline (re-measured at
survey HEAD on the production path): 98 files → 3,988 nodes = method 1801 /
field 588 / class 379 / property 348 / enum_member 230 / import 183 / struct
111 / file 98 / constant 97 / enum 77 / type_alias 39 / interface 27 /
function 10; 16,426 refs.** The 348 `property` nodes are exactly this
branch's computed path — the #1020 number reproduced to the digit.
### swiftPropertyInfo (tree-sitter.ts:277-291) — transcribe exactly
```
pattern = childForFieldName('name')
?? namedChildren.find(t === 'value_binding_pattern' || t === 'pattern')
?? null
binding = namedChildren.find(t === 'value_binding_pattern') // DIRECT children only
isLet = binding != null && text(binding).trimStart().startsWith('let')
isComputed = namedChildren.some(t === 'computed_property' || t === 'protocol_property_requirements')
nameNode = firstSimpleIdentifier(pattern)
```
`firstSimpleIdentifier` (:261-273): BFS (shift from a queue) over
namedChildren, **guard: at most 40 nodes popped**; first `simple_identifier`
wins. Tuple pattern `let (a, b)` → `a` only (one node for the whole
declaration, PRESERVE).
Shape notes (probed, identical both grammars):
- class/struct/enum property_declaration: `value_binding_pattern`
(mutability let/var) and `name: pattern > bound_identifier:
simple_identifier` are SIBLING direct children → binding found → isLet real.
- **protocol_property_declaration NESTS the value_binding_pattern INSIDE the
`name: pattern`** → the direct-children `find` MISSES it → binding null →
**isLet always false for protocol requirements** (irrelevant today — they
are all computed — but transcribe the miss).
- `computed_value: computed_property` carries the getter; the code matches by
TYPE not field.
- **`willSet`/`didSet` observers are a `willset_didset_block`, NOT a
computed_property** → an observed stored property (`var observed: Int = 0
{ willSet {…} didSet {…} }`) is **isComputed=false → a `field` node**, and
(stored-path descent, below) the observer bodies' calls attribute to the
CLASS (probed: `calls prepare` / `calls react` from=class).
### The branch body (:1126-1193), step by step
1. `ownerId = nodeStack top` (the class/struct/enum/interface node).
2. If nameNode:
- **isComputed → `createNode('property', name, node, {visibility: hook,
isStatic: hook ?? false})`** — computed properties become `property`
nodes (the #1020 "var isCloudProxy: Bool" fix; SwiftUI `var body: some
View` is the canonical case). Note extras: NO docstring, NO signature,
NO returnType — visibility + isStatic ONLY (probed doc=null on all).
- else **stored: `isStatic = hook ?? false`; kind = isStatic ? (isLet ?
'constant' : 'variable') : 'field'`** — `static let``constant`,
`static var``variable` (`class var` → isStatic via 'class' substring
`variable`), instance stored (let OR var) → `field`. Same extras.
- Node position = the property_declaration (multi-line observed props span
the block). nameNode null (no simple_identifier in pattern) → NO node,
but steps 3-5 still run.
3. If ownerId: `extractDecoratorsFor(node, ownerId)` — **refs attach to the
ENCLOSING TYPE, not the property** (probed: `@Published private var wrapped`
`decorates Published from=class:Session`). §Decorators for mechanics.
4. If ownerId: `extractVariableTypeAnnotation(node, ownerId)` (:6074) — finds
the direct `type_annotation` child → extractTypeRefsFromSubtree (:6090) →
one `references` ref per `type_identifier` leaf not in BUILTIN_TYPES,
**from the OWNER** (probed: `let rootQueue: DispatchQueue``references
DispatchQueue from=class` at the leaf's position). BUILTIN_TYPES nuances
(:5768): `Int`/`String`/`Double`/`Float`/`Boolean`/`Long`/`Char` are
suppressed (Scala rows), **`Bool` is NOT** (→ real `references Bool`
noise ref, probed), `Void`/`Character`/`Self` NOT. Wrappers recurse:
`[Tag]``Tag`; `Session?``Session`; `some View` (opaque_type) →
`View`.
5. If ownerId: **walkAttrArgs** (:1165-1175) — find the `modifiers` direct
child; if present, recursively run `extractStaticMemberRef(n)` over EVERY
node of the modifiers subtree. This is the Vapor `@Siblings(through:
PivotModel.self, from: \.$left, …)` mechanism: the attribute-argument
`navigation_expression` with target `simple_identifier PivotModel` +
suffix `.self``references PivotModel` from the OWNER at the RECEIVER's
position. Keypath args (`\.$left` — target is `key_path_expression`) and
the wrapper's own `user_type` are self-filtered (not MEMBER_ACCESS_TYPES /
not an accepted recv type). §Static-member refs for the shared mechanics.
6. Computed only: getter = namedChildren find(`computed_property` |
`protocol_property_requirements`); if found: push property id,
`visitFunctionBody(getter, '')`, pop; **skipChildren = true**. The
getter's calls/reads attribute to the PROPERTY node (probed: `calls check
from=property:isCloudProxy`; SwiftUI body's `VStack`/`Text` subtree
becomes the property's callees). protocol_property_requirements contains
only getter/setter specifiers → walks emit nothing.
7. **Stored: skipChildren stays FALSE** → the generic loop (:1295) descends
into the property_declaration's children via visitNode: the initializer's
call_expressions hit callTypes → **`calls` refs from the CLASS** (`let
stored = Session()``calls Session from=class`; `lazy var expensive =
Cache.build()``calls Cache.build from=class`); willset/didset bodies
likewise; the modifiers subtree is re-walked harmlessly (attributes have
no visitNode branches; walkAttrArgs already emitted the static refs —
different ref kinds, no dupes). Value-position navigation reads (`let m =
Suit.hearts` as an in-class initializer) emit NOTHING here (no
static-member call in visitNode) — contrast bodies.
8. fn-ref capture: maybeCaptureFnRefs ran at :990 BEFORE the branch —
property_declaration ∈ SWIFT_SPEC dispatch (varinit field `value`) → a
stored `let cb: Handler = onFire` captures `onFire`; computed properties
have no `value` field → nothing.
## extractVariable — the swift top-level branch (tree-sitter.ts:2851-2862)
Top-level property_declaration only (dispatch table). `swiftPropertyInfo`
again: nameNode && !isComputed → `createNode(isLet ? 'constant' : 'variable',
name, NODE, {docstring, isExported})`**position = the whole declaration**,
extras: docstring (getPrecedingDocstring — top-level consts DO get
docstrings, unlike the in-class branch), isExported = `?? false` → literal
**false** (:2549; probed exp=false — the only swift nodes besides the file
node with a non-undefined isExported), **NO signature** (unlike the TS/Go
branches — no initializer capture), no visibility/isStatic. Computed
top-level (`var topComputed: Int { 9 }`) → **skipped entirely** (no node).
One node per declaration — tuple `let (a, b) = …``a` only. The :1098
dispatch sets skipChildren=true + scanFnRefSubtree (capture-only, halts at
functionTypes + `lambda_literal` :610) → **top-level initializers emit NO
calls/instantiates refs** (probed: `var topVar: Int = compute()` → nothing
but the node; no type-annotation refs either — extractVariableTypeAnnotation
is NOT called on this path, PRESERVE the asymmetry with the in-class branch).
## extractFunction / extractMethod for swift (:1517 / :1737)
- extractFunction: no getReceiverType → never diverts. Name via extractName
(:90): resolveName (class_declaration-gated → undefined for functions) →
nameField `name` → the simple_identifier. **Operator functions:** `func <+>
(lhs:…)` has `name: custom_operator` → extractName returns the raw operator
text → a function node named `<+>` (probed clean-parse on both arms).
`<anonymous>` never occurs (grammar requires a name; the arrow/function_
expression paths are TS-only).
- Node extras: docstring (§Docstrings), signature **undefined** (dead hook),
visibility (hook), isExported undefined, **isAsync false** (dead hook),
isStatic (hook — real for `static`/`class` modifiers), returnType (hook).
- Then extractTypeAnnotations (:1594) — §Type-annotation refs: **return-type
refs ONLY**. Then extractDecoratorsFor (:1599) — attributes inside
`modifiers` → real decorates refs (`@objc func attributed``decorates
objc`, position = the attribute node). Push, walk `body` field
(function_body) via visitFunctionBody, pop.
- extractMethod (function_declaration inside class-like): gate :1747 passes
via isInsideClassLikeNode. Same extras (visibility/isAsync-false/isStatic/
returnType, signature undefined). receiverType undefined → no QN override
(:1790 skipped), no owner-contains fallback (:1799). Bodiless is
impossible for function_declaration in valid swift (protocol requirements
are a different node type, unhandled).
- **Nested named function in a body** → visitFunctionBody:5245 →
functionTypes + named → extractFunction → `function` node contained by the
enclosing method/function (its QN prefixes the enclosing chain via
nodeStack). Closures (`lambda_literal`) are NOT functionTypes → no nodes;
their calls attribute to the enclosing symbol (probed: `rootQueue.async {
self.perform(req) }``calls perform from=method:request`).
- Body-level class/struct/enum/protocol declarations → visitForCallsAndStructure
:5255-5275 dispatches classTypes (with classify)/interfaceTypes → full
extraction, contained by the enclosing function.
## Type aliases — extractTypeAlias (:2890)
typealias_declaration → name field (alias name). No resolveTypeAliasKind →
plain `type_alias` node {docstring, isExported:undefined}. swift ∈
TYPE_ANNOTATION_LANGUAGES → value = `getChildByField(node,'value')` — **the
multi-field lookup WORKS** (table above) → extractTypeRefsFromSubtree over
the aliased type → refs per type_identifier leaf (probed: `typealias Handler
= (Data) -> Void``references Data` + `references Void`; `typealias
BuilderAlias = KF.Builder` → refs `KF` AND `Builder` — one per segment).
The TS-only member extraction (:2983) is gated away. Returns false →
children recursed (nothing matches).
## extractCall (:3684) — the swift paths
Swift never hits the vbnet/erlang/objc/php/java branches (no name+object
fields on call_expression). Generic else :4312: `func =
childForFieldName('function') ?? namedChild(0)` — swift has NO function field
**namedChild(0) always**. cpp operator recovery (:4324) is cpp-gated. The
full observed matrix (every row probed via the built extractor):
| Call shape | func node | Path | Emitted ref |
|---|---|---|---|
| `helper()` | simple_identifier | else :4518 raw text | `calls helper` |
| `Foo()` | simple_identifier | same | `calls Foo` (constructor = plain call; NO instantiates, ever) |
| `Foo.init(raw:)` | navigation_expression | :4364 member branch | `calls Foo.init` |
| `obj.method(1)` | navigation_expression | member | `calls obj.method` |
| `self.own()` | nav, target `self_expression` | receiver not identifier-typed → fallthrough | `calls own` (bare — swift `self` is its own node TYPE; SKIP_RECEIVERS:4400 {self,this,cls,super} is reached only for identifier-typed receivers, so the same net effect arrives via a different path than kotlin) |
| `super.parent()` | nav, target `super_expression` | same | `calls parent` |
| `a.b.deep()` | nav, target inner nav | fallthrough | `calls deep` (bare — 2-hop receivers drop) |
| `x?.optCall()` | nav, targets [simple_identifier x, anon `?`] | receiver = namedChild(0) = `x` | `calls x.optCall` (**optional chaining keeps the receiver** — the `?` is a second anon `target:` child, invisible to namedChild) |
| `y!.forced()` | nav, target postfix_expression | fallthrough | `calls forced` |
| `Foo.make().draw()` | nav, target call_expression | **#750 re-encode :4408-4442**: swift-gated; innerNav = receiver.namedChild(0) (`Foo.make` nav or bare identifier), text ws-stripped; `/^[A-Z]/` gate | `calls Foo.make().draw` + (recursion) `calls Foo.make` |
| `foo.bar().baz()` | same | innerCallee `foo.bar` lowercase → reencode false | `calls baz` + `calls foo.bar` |
| `"lit".upper()` | nav, target line_string_literal | LITERAL_RECEIVER_TYPES :4397 (`line_string_literal` ∈ set :377) | **NOTHING** |
| `"""…""".trimmed()` | nav, target multi_line_string_literal | **NOT in the literal set** | `calls trimmed` (bare) — PRESERVE |
| `5.times()` / `[1,2].reduce(…)` | integer_literal / array_literal | literal set | NOTHING |
| `["k":1].lookup()` | dictionary_literal | **NOT in the set** (`dictionary` is, `dictionary_literal` isn't) | `calls lookup` — PRESERVE |
| `(freestanding)()` | tuple_expression | else raw text `(freestanding)`**parenthesized-conversion regex :4530 FIRES** | `calls freestanding` |
| `closureTaking { }` / multi-trailing | simple_identifier (lambda in call_suffix) | else | `calls closureTaking` |
| `arr[0]` (subscript READ) | simple_identifier (bracket call_suffix) | else | **`calls arr`** — every subscript access parses as call_expression; huge-volume quirk, PRESERVE |
| `m[i][j]` | inner call_expression | receiver is call but func = call_expression not nav → else raw text | **`calls m[i]`** (garbage, PRESERVE) |
| `self.items[k] = 1` | nav self.items | member, self receiver | `calls items` |
| `defer { cleanup() }` | simple_identifier `defer` | else | **`calls defer`** + inner `calls cleanup` — defer parses as a trailing-closure call (both arms, probed) |
| `try f()` / `try? f()` / `await f()` | (wrapped in try_expression/await_expression) | recursion reaches the inner call_expression | normal ref, position = the CALL node (after the keyword) |
| `"count \(counter.next())"` | (interpolated_expression inside the literal) | recursion descends into string literals | `calls counter.next` — interpolation calls DO emit |
| `.make()` (implicit member call) | prefix_expression | else raw text | **`calls .make`** (leading dot, unresolvable — PRESERVE) |
| macro `#Preview { ContentView() }` (NEW) | (macro_invocation unhandled) | recursion | inner `calls ContentView` from the enclosing scope |
Post-processing: parenthesized-conversion (:4529) as noted; template-strip
(:4542) and cpp fn-ptr fan-out (:4556) are c/cpp-gated. Final ref: {callerId
= nodeStack top, name, line = call startRow+1, column = call startColumn
(UTF-16)}. Inner nav/call children are ALSO visited after extractCall
(callTypes doesn't skipChildren; body walker recurses) — chains emit inner
refs, and the callee-position navigation_expression is exempted from
static-member refs by the :4771 callee-of-call check.
## Static-member / value-read refs (:4750-4808) — swift IS in STATIC_MEMBER_LANGS (:345)
Called from the body walker ONLY (:5218) plus the property branch's
walkAttrArgs. `navigation_expression` ∈ MEMBER_ACCESS_TYPES (:326). Mechanics
for swift: callee-of-call skip (:4771-4779 — parent ∈ callTypes and
parent.namedChild(0).startIndex === node.startIndex → the callee nav of a
call emits nothing); recv = object/expression/scope fields (all null for
swift — nav's fields are `target`/`suffix`) → **namedChild(0)** = the target.
Accepted recv types (:4791): identifier/type_identifier/simple_identifier/
name/scoped_type_identifier — swift targets are `simple_identifier`
capitalized regex `^[A-Z][A-Za-z0-9_]*$``references <target>` from the
enclosing symbol at the RECEIVER's position. Probed rows: `Color.red`
`references Color`; `Suit.hearts.rawValue` → outer nav recv = inner nav →
nothing, inner nav → `references Suit`; `UserModel.self``references
UserModel`; `Deep.Nested.leaf``references Deep` (inner nav only);
`.implicitMember` → prefix_expression, not a nav → NOTHING; `lowercase.field`
→ nothing; keypath `\Foo.bar` → recv = key_path_expression → nothing (the
type_identifier inside the keypath is never read). Remember the visitNode
gap: class-level initializer reads and init/deinit/subscript bodies emit NO
static-member refs (dispatch table).
## Type-annotation references — swift ∈ TYPE_ANNOTATION_LANGUAGES (:5753)
For every function/method (extractTypeAnnotations :5788, called at
:1594/:1816): params = `getChildByField(node, 'parameter')` → **NULL → NO
parameter type refs, ever** (probed: `@escaping (Int) -> Void` param emits
nothing — `Void` would have shown). returnType = `getChildByField(node,
'return_type')`**WORKS** (field table) → extractTypeRefsFromSubtree over
the return node → one `references` per type_identifier leaf not in
BUILTIN_TYPES: `-> DataRequest` → ref; `-> KF.Builder` → refs `KF` AND
`Builder`; `-> Session?``Session`; `-> Result<Foo, Err>``Result`,
`Foo`, `Err` (leaves); builtins suppressed per the §property-branch nuance
list (`Bool` NOT suppressed). The trailing direct `type_annotation` find
(:5873) is null for function_declarations. extractVariableTypeAnnotation
(:6074): the dedicated property branch (owner-attributed) and the body
walker's `variable_declarator` gate (:5230-5236 — **dead for swift**, no such
node type; body-local `let x: Foo` property_declarations emit NO type refs —
PRESERVE). property_signature/method_signature (:1282) — TS-only types.
## Decorators — extractDecoratorsFor (:4897), swift = `attribute` nodes
consider() (:4898) accepts type `attribute`; the target scan (:4937) finds
the attribute's first namedChild of accepted types — **swift attribute →
`user_type`** (:4950) → name = user_type text, strip `<…>` generic suffix,
strip to last `.`/`::` segment, trim → `decorates` ref {from the decorated
node id (or the OWNER for the property branch), name, line/col of the
ATTRIBUTE node}. Attributes live INSIDE the declaration's `modifiers` child →
found via the modifiers descent (:4983); the preceding-sibling scan (:5013)
finds nothing for swift (attributes are never siblings) and stops at the
first non-decorator sibling. Coverage matrix (probed): function ✓ (`@objc`
`decorates objc`), method ✓, class ✓ (`@Observable`), dedicated-branch
property ✓ (owner-attributed) — **struct/enum/interface ✗ NO decorates**
(extractStruct/extractEnum/extractInterface never call it — `@main struct`,
`@objc enum`, attributed protocols emit nothing, probed). Attribute ARGUMENTS
are never walked here — only walkAttrArgs (property branch) reads them, and
only for static-member shapes. Parameter attributes (`@escaping` etc. inside
`parameter`/`parameter_modifiers`) are never reached by any pass.
## Docstrings (tree-sitter-helpers.ts:95)
`///` doc lines AND `//` plain comments are node type `comment` — accepted by
the sibling scan; consecutive runs accumulate (unshift → source order) and
`//` vs `///` are NOT distinguished (a plain comment right above a decl IS
its docstring). **`/** */` and `/* */` block comments are `multiline_comment`
— NOT in the accepted set (comment/line_comment/block_comment/
documentation_comment) → block docs are IGNORED and BREAK the accumulation
chain** (probed: `/** block doc */ func blockDoc` → doc=null). PRESERVE.
DOCSTRING_WRAPPER_TYPES: no swift wrappers → no climbing. Attributes do NOT
break the chain (they're inside the declaration node — `/// doc` +
`@objc func` keeps its doc, probed). cleanCommentMarkers (:77): swift hits
`^\/\/[/!]?\s?` (gm) and — only if a multiline_comment ever reached it, which
it can't — the block strips. **The `gm` per-line strips are the ONLY
CRLF-sensitive regexes on the swift path (#1329): use `js_multiline_strip`
in docstring.rs.** Docstrings attach to: functions, methods, classes
(incl. extensions/actors), structs, enums, interfaces, top-level
constants/variables (extractVariable), type_aliases. NOT to: dedicated-branch
properties/fields/constants (extras carry no docstring — probed), enum
members, import nodes.
## Value-reference edges (:398-931) — swift IS in VALUE_REF_LANGS (:401)
Port the full machinery (crib go.rs/java.rs): `CODEGRAPH_VALUE_REFS=0` kill;
MAX_VALUE_REF_NODES = 20,000 caps the prune DFS and each reader scan;
isGeneratedFile skip.
- **Targets** (captureValueRefScope :735): kind constant|variable, name ≥3
chars AND `/[A-Z_]/`, parent id prefix ∈ {file:, class:, module:,
**struct:, enum:**} — the struct:/enum: rows exist FOR swift's
static-let-namespacing idiom (`enum Constants { static let X }`, comment at
:748-750). Swift targets: top-level `let`/`var` (under file:) and in-class
`static let`/`static var` (constant/variable under class:/struct:/enum:).
`interface:` NOT accepted → protocol statics (hypothetical) excluded.
Instance `field`s and computed `property`s are NOT targets.
- **Reader scopes** (:764): function/method/constant/variable nodes —
**`property` and `field` are NOT readers** (probed: `isCloudProxy`'s read
of SHARED_MAX emits no edge; `method request`'s does).
- **Shadow prune** (:803-878): the declarator switch's
**`property_declaration` case (:856-869) RESOLVES for swift** (unlike
php): vd = find `variable_declaration` (Kotlin) — absent for swift → else
`firstSimpleIdentifier(childForField('name') ?? find(value_binding_pattern
| pattern))` — the swift shape → bump() counts it when the text is a
target name (bump accepts identifier|simple_identifier :807). EVERY
property_declaration in the tree bumps — targets' own declarations, body
locals (`let SHARED_MAX = 5` in a method — the shadow source), and
same-named instance properties. `declCount > fileScopeCount` → target
deleted. **guard-let/if-let bindings do NOT bump** (guard_statement/
if_statement hold bound_identifier directly, no property_declaration —
probed) → a `guard let X = …` shadow does NOT prune. PRESERVE both sides.
- **Emission** (:880-930): per reader scope DFS (bodies are children — the
Dart/Pascal sibling pull :891 is inert); match node type
`simple_identifier` (:908 — swift's every name reference) whose text maps
to a target, target ≠ self, name ≠ scope's own name, dedupe per
(scope,target) → EDGE {source: scopeId, target, kind:'references',
metadata:{valueRef:true}}, appended AFTER all other edges. Because every
`simple_identifier` matches — nav suffixes included — `Session.SHARED_MAX`
AND a bare `SHARED_MAX` both emit (probed: the single torture valueRef
edge method:request → constant:SHARED_MAX).
## Function-as-value capture (#756) — SWIFT_SPEC (function-ref.ts:288)
```
idTypes: { simple_identifier }
dispatch: value_arguments → args
assignment → rhs (field 'result')
array_literal → list
property_declaration → varinit (field 'value')
layers: value_argument → 'value' // FIELD 'value', not null
special: { selector_expression }
```
No unwrap/ungatedModes/addressOfOnly. Mechanics (function-ref.ts:408-597 +
:685-696), all probed:
- **args/list**: every namedChild → normalizeValue. Bare `simple_identifier`
→ candidate (NAME_STOPLIST drops self/true/nil/…). `reg2(onFire)`
`onFire` ✓; `[cbA, cbB]` → both ✓ (the array_literal's OWN dispatch —
varinit's normalizeValue over an array_literal yields [], the capture
happens when the walker/scan VISITS the array node).
- **value_argument layer** (:547-557): the **label-forward skip** — label =
childForField('name') (a `value_argument_label`), value =
childForField('value') ?? last namedChild; label text === value text →
DROPPED (`forward(value: value)` → nothing; the Alamofire A/B finding).
Else descend the `value` field.
- **rhs** (assignment, field `result`): param-storage skip — lhs =
left/lhs/**target** fields ?? namedChild(0) (swift's field is `target`, a
directly_assignable_expression); lhs text's last identifier === rhs text →
skip (`self.raw = raw` → nothing, probed). `o.cb = handler``handler` ✓.
- **varinit** (property_declaration, field `value`): destructuring check
reads childForField('name') — swift's is type `pattern`, NOT in
{object_pattern, array_pattern, tuple_pattern, struct_pattern} → never
skipped; tuple-let values are non-normalizable expressions anyway.
Computed properties have no `value` field → nothing.
- **selector_expression special** (:685-696): namedChild(0) is
identifier/simple_identifier → its text (`#selector(fire)``fire`); else
rightmost simple_identifier descendant (`#selector(Holder.fire)``fire`;
QUIRK: `#selector(onNote(_:))` → the rightmost simple_identifier is the
argument label `_` → candidate `_`, dropped by the gate in practice); else
trimmed inner text. explicitRef=true (not an idType).
- Capture fires from visitNode:990 (incl. BEFORE the dedicated property
branch and on init/deinit/subscript descents), visitFunctionBody:5137, and
scanFnRefSubtree (top-level initializer scan; halt list :606-612 includes
`lambda_literal` — closures halt the scan — and functionTypes).
- **Flush gate** (:639): definedHere (same-file function/method names)
importedNames. Swift import refs are module names — `Foundation` (SIMPLE)
and `UIKit.UIView` (QUALIFIED_IMPORT → last segment `UIView`) both land in
importedNames but almost never match a candidate → **the swift gate is
effectively "defined in this file"** (probed: `#selector(Holder.fire)`'s
`fire` dropped; `onFire`/`handler`/`cbA`/`cbB` survive as same-file
functions). Survivors dedupe on `${fromNodeId}|${name}`
{referenceKind:'function_ref'}, appended after all walk refs.
## Closure-collection pass & other synthesis consumers (no port — pin the contract)
- **swift IS in CC_LANGUAGES** (resolution/callback-synthesizer.ts:77, with
kotlin — the #1235 gate). The pass re-reads **function/method nodes'
filePath + startLine..endLine source slices** (callback-synthesizer.ts:270-
297) and regexes for `.forEach`/`.append(`/`.add(`/`.push(`/`.insert(`
dispatch/registration pairs. **Input contract on the walker: node kinds,
languages, and LINE EXTENTS byte-match** — a wrong endLine silently changes
synthesis. The dump gate covers this; nothing extra to build.
- swift-objc bridge (`swiftObjcBridgeResolver`) + the `sendEvent` scan
(callback-synthesizer.ts:1387-1438) read raw `.swift` file text — file-set
contract only.
- The :811 and :3507 language gates consume node kinds/names — covered by
parity.
## Frameworks (stay TS-side — behavior pinned in §Architecture #2)
swiftUIResolver / uikitResolver / vaporResolver (resolution/frameworks/
swift.ts:11/:136/:269): extract() hooks emit `component`/`class`/`route`
nodes with LITERAL ids (`view:${filePath}:${name}:${line}`,
`route:${filePath}:${line}:${METHOD}:${path}` — NOT hashed) + vapor handler
refs (which DO carry filePath+language — framework refs, not extraction
refs). resolve() consumes plain `references` names by suffix + kind + dir
conventions (`getNodesByName` — walker's node names/kinds are the contract).
Vapor's `@Siblings` metatype refs (§property branch step 5) are the
extraction-side feed that keeps pivot models un-orphaned on Fluent repos.
## Parity mechanics (all have bitten before)
- **Emission order** per §Node creation — file → source-order walk → fn-refs
→ value-ref edges. Refs interleave with nodes exactly as the TS call sites
do: extends before members; per-property decorates → type-refs → attr-arg
refs → initializer-descent refs; per-function/method the return-type refs
come FIRST (extractTypeAnnotations :1594/:1816 runs before
extractDecoratorsFor :1599/:1819), then decorates, then body refs.
- **generateNodeId inputs**: (filePath, kind, name, startRow+1) — name is the
bare bound identifier for properties (no pattern text), the LAST segment
for multi-segment extensions, raw sugar text (`[ServerTrustEvaluating]`)
for sugar extensions, `<+>` for operator functions, the full dotted module
(`UIKit.UIView`) for import nodes; line = declaration start (= the
`modifiers`/attribute start when attributes precede the keyword — the
declaration node INCLUDES its modifiers).
- **UTF-16 columns + slices** (textutil::col16/slice_utf16): every ref/node
column, `startIndex/endIndex` substrings (getNodeText — getVisibility/
isStatic read `child.text`, same substring), and the `.slice(0,100)`-class
truncations (unused on the swift path — no signature/initializer capture).
Swift sources are emoji/CJK-heavy in tests — the torture fixture needs a
non-ASCII line before a symbol.
- **CRLF**: the ONLY multiline regexes on the swift path are
cleanCommentMarkers' `gm` strips (§Docstrings) → `js_multiline_strip` in
docstring.rs. `trimStart()`/`startsWith('let')` in swiftPropertyInfo are
whitespace-semantics-identical in Rust (`trim_start`). CRLF fixture
variants derived in-memory, per the tsjs pattern.
- **Defer policy**: per-file `has_error()``defer:`; **expected incidence
927% (§table); sweep with `--max-deferral 0.3`** — the c/cpp-style
exemption, justified by both-arm measurement, NOT a walker allowance: a
JUMP vs the table is a walker bug.
- Refs carry NO filePath/language (§Architecture #4 — REF_FLAG_FILE_PATH
unused); the wire contract is exactly extractFromSource's return.
- No POST_PASS; no preParse; `sourceIsPreParsed` never set for swift.
## Gates (per plan §5, no exceptions)
- **Grammar bump lands FIRST, standalone** (php pattern): vendor wasm +
`=0.7.3` crate pin + VENDORED_WASM_LANGS + kernel-grammar-parity
`GRAMMAR_LANGUAGES += 'swift'` in one change, full suite green, before any
walker exists. Old-wasm vs new-wasm **full-init dump diff on all three gate
repos** (`scripts/dump-graph.mjs`, cmp): expected NON-empty; **every
diffing file must be in the union of the two arms' hasError lists**
(regenerate with `svy-swift/error-incidence.cjs`; categories in
§Grammar-bump deltas — macro/testing/package/typed-throws fixes, the three
NEW-regression classes, both-arm recovery drift); prove residual
resolution ripple mechanically by parked-ref↔edge pairing (php's
ripple-proof pattern). Any clean-on-both-arms file diffing blocks the bump.
- **Torture fixtures** per `## Fixtures to build` (+ CRLF variants derived
in-memory), exercised by the new parity suite.
- **Parity sweeps** (`scripts/kernel-parity.mjs <dir>`, order-sensitive
full-object, **`--max-deferral 0.3`**):
- `…/scratchpad/gate-repos/Alamofire` (small, 98 files — **the mandatory
#1020 gate**, decoded path)
- `…/scratchpad/gate-repos/vapor` (medium, 247 files, decoded path,
Fluent/@Siblings + route shapes)
- `…/scratchpad/gate-repos/swift-nio` (large, 554 files, **raw-buffers
path** — no framework detects)
(all three cloned at survey time; re-clone fresh if gone). Then **full-init
dump-diffs byte-identical** (kernel arm vs `CODEGRAPH_KERNEL=0`,
dump-graph.mjs, cmp) on the same three.
- **Alamofire #1020 spot-check** (belt to the dump gate's suspenders): after
a kernel-arm index, node-kind census must match the wasm arm EXACTLY —
survey baseline at `a6c62d7` + current Alamofire HEAD: **property = 348**
(the #1020 number, re-measured), field 588, method 1801, class 379
(extensions!), constant 97, struct 111, enum 77, enum_member 230,
interface 27, type_alias 39, function 10, import 183 (98 files, 3,988
nodes, 16,426 refs — `svy-swift/count-props.cjs`). A missing dedicated
branch shows up here as property→0 / field→0 before the dump diff even
runs.
- **Suite**: new `__tests__/kernel-swift-parity.test.ts` — torture + CRLF
variants + an intentionally-erroring defer fixture (use a NEW-only
regression construct — e.g. `#if DEBUG` between enum cases — asserting the
kernel defers and wasm output is served); full suite ×2 green with
`CODEGRAPH_KERNEL_EXPECT=1`.
- **`DEFAULT_ROUTED += 'swift'`** (kernel/index.ts:37) only after ALL of the
above; changelog rides the existing kernel entry.
- Post-route sanity: §Architecture #2 — Alamofire/vapor ride the decoded
path, swift-nio the raw transport; measure the parse loop accordingly.
## Fixtures to build
**`torture.swift`** (seed: the survey's `svy-swift/torture.swift`, already
validated against the built extractor — extend it with the rows below),
**a CRLF variant derived in-memory**, **one defer fixture** (a NEW-only
regression construct, above), and keep every line traceable to a branch:
- class family: plain class; `public final class C: Base, Proto1, Proto2`
(extends ×3, all 'extends'); struct + bodiless-skip N/A; enum backed
(`: String` → extends String) + `case a = "H"` + **multi-case `case put,
delete` (ONLY `put` minted)** + associated values (no refs) + indirect +
computed property + static method in enum; actor (→ class); `extension
Point: Hashable` (second class node, extends); `extension KF.Builder`
(resolveName → `Builder`); `extension Array where Element: Equatable`
(class `Array`, NO constraint refs); **`extension [Proto]` (class named
`[Proto]`)**; protocol with inheritance + var requirement {get set} (→
property node, isLet=false via the nested-binding miss) + static var
requirement + **func/init/associatedtype requirements (NO nodes)**.
- the dedicated branch, exhaustively: stored let with type+initializer
(field + owner type-ref + owner-attributed initializer call); untyped var;
`static let SHARED_MAX` (constant, value-ref target); `static var`
(variable); `class var` computed (property, isStatic via 'class');
`lazy var x: Cache = Cache.build()` (field + `calls Cache.build`
from class); `public private(set)` (→ 'public'); `@Published private var`
(decorates from OWNER + vis 'private'); **`@Siblings(through: Pivot.self,
from: \.$left) var siblings: [Tag]`** (decorates Siblings + walkAttrArgs
`references Pivot` + type-ref `Tag`, all from OWNER); computed
`var isCloudProxy: Bool { check() }` (property + `calls check` FROM the
property + `references Bool` — Bool not builtin); **SwiftUI-shaped
`var body: some View { VStack { Text(label) } }`** (property; VStack/Text
calls from the property; `references View`); observed `var x = 0 {
willSet{prepare(newValue)} didSet{react(oldValue)} }` (**field**, observer
calls from the CLASS); `weak var delegate: SessionDelegate?`; tuple
`let (a, b) = makePair()` (ONE node `a`, `calls makePair` from class);
`open` visibility (→ 'internal').
- functions/methods: free func with params+return (`sig=null`, return-type
ref, NO param refs — include a `Void`-bearing param type to prove the
negative); `-> KF.Builder` (returnType 'Builder' + refs KF AND Builder);
`-> Result<Foo, Err>` (returnType 'Result'); `-> Result<Vec<Foo>, E>`-shaped
nested generic (returnType undefined); `-> Foo?` / `-> [Foo]` / `-> (A,B)`
/ `-> (Int) -> Foo` / `-> Void`; `func f() async throws` (isAsync FALSE);
`static func` / `open class func` (isStatic true, vis 'internal');
operator `func <+> (lhs:…)` (node named `<+>`); nested named func in a
body; `@objc func` (decorates objc); `@main struct` + `@objc enum` +
attributed protocol (NO decorates).
- init/deinit/subscript: `init(raw:) { self.raw = raw; setupMonitor() }`
(no node; `calls setupMonitor` from class; NO fn-ref for the param-storage
assignment); `convenience init?` (`calls init` bare from class); `deinit`;
subscript with get/set (`calls store` ×2 from class).
- calls: every row of the §extractCall matrix, verbatim — bare, `Foo()`,
`Foo.init()`, member, self/super (bare), 2-hop (bare), `x?.m()`
(receiver kept), `y!.m()` (bare), `Foo.make().draw()` re-encode + inner,
lowercase chain (bare + inner), `"lit".upper()` (nothing),
`"""m""".t()` (bare t), `5.times()` / `[1,2].reduce` (nothing),
`["k":1].lookup()` (bare), `(f)()` (conv-regex → f), trailing + multi-
trailing closures, `arr[0]` (`calls arr`), `m[i][j]` (`calls m[i]`),
`defer {}` (`calls defer`), try/try?/await wraps, string-interpolation
call, implicit-member call `.make()` (`calls .make`), `Task { await f() }`.
- static-member reads (in a BODY): `Color.red`, `Suit.hearts.rawValue`
(`Suit` only), `UserModel.self`, `Deep.Nested.leaf` (`Deep` only),
`.implicit` (nothing), `lowercase.f` (nothing), keypath `\Foo.bar`
(nothing) — plus the SAME reads at class-initializer scope and inside
`init` (nothing — the visitNode gap).
- value refs: `static let SHARED_MAX` in a class/struct/enum read from a
method (edge) and from a computed property (NO edge — property not a
reader); a method-local `let SHARED_MAX = 5` in a SECOND fixture variant
(prune kills the target); a `guard let SHARED_MAX` variant (NO prune);
top-level `let TOP_LEVEL_MAX` + reader.
- fn-refs: `reg(onFire)`, `reg(cb: onFire)` (label≠value → survives),
`forward(value: value)` (label-forward skip), `o.cb = handler`,
`self.x = x` (param-storage skip), `let table = [cbA, cbB]`,
`#selector(fire)` / `#selector(Holder.fire)` / `#selector(onNote(_:))`
(the `_` gate-drop), an undefined-name arg (gate-drop).
- imports: `import Foundation`, dotted `import UIKit.UIView`, kinded
`import class Darwin.FILE`, `@testable import`.
- typealias: function-type value (refs Data+Void) + member-type value
(refs KF+Builder).
- docstrings: `///` run (joined), `//` plain (IS a docstring), `/** */`
(**ignored**), `/* */` between `///` and decl (breaks chain), doc over an
attributed decl (kept), doc on top-level const (kept) vs in-class property
(dropped).
- misc: `#if os(iOS)` around members (both branches extracted), `#warning`,
a macro_invocation file (NEW arm; on OLD this file is a defer candidate —
keep it in the DEFER fixture instead), a non-ASCII (UTF-16) line before a
symbol, guard/if-let/for-await bodies with calls.
## Probe artifacts (session scratchpad `svy-swift/`)
`probe-swift.cjs` + `shape-{OLD,NEW}.txt` + `shape.diff` (the 53-line
classified battery diff), `mini-probes.cjs` + `mini-probes.out` (node-type
inventory, per-construct error matrix, defer/subscript/#if/async-let/regex
shapes), `field-probe.cjs` (the childForFieldName truth table),
`regress-probe.cjs` (NEW-regression minimal repros), `error-incidence.cjs`
(the §incidence table + per-file lists), `error-diag.cjs` (first-ERROR
diagnosis used for the delta classification), `extract-probe.cjs` +
`torture.swift` + `extract-probe.out` (the built-extractor emission pin),
`sugar-probe.cjs` (extension sugar/decorates asymmetry/implicit-member call),
`count-props.cjs` (the Alamofire census), `tree-sitter-swift.wasm` (the
staged-candidate 0.7.3 build), `tree-sitter-swift-0.7.3/` (crate extract) +
`tag-clone/` (0.7.3-with-generated-files) with the sha evidence.