fix(swift): a call written after try! links what it calls on Windows (#2683)

The Windows kernels read the `!` of every `try!` as a logical not, so
`try! Message2(data: d)` became a call of `! Message2` that never resolves,
and a call through a receiver lost it: `try! Settings.Defaults.load()` was a
bare `load`.

The cause is the grammar's C, not the walker. tree-sitter-swift 0.7.3's
external scanner steps aside for the `!` of `try!` through a 64-bit mask
whose entry is built as `1UL << FAKE_TRY_BANG`, bit 32. `unsigned long` is 32
bits on Windows and MSVC folds the overflowing shift to 0 (warning C4293),
so the scanner never stepped aside: `try` parsed alone, then a prefix `!` on
the callee. macOS and Linux have 64-bit longs, and clang's wasm32 build folds
the shift to bit 31 (`_directive_endif`, valid at every expression start),
so the wasm and the other kernels have always read `try!` right.

The kernel now compiles the crate tarball's src/ itself, from
codegraph-kernel/grammars/swift, with upstream 0.7.4's one-line fix (`1ULL`),
as it already does for the Kotlin, Lua, Scala and Dart grammars. The patch and
its provenance are in docs/grammars/tree-sitter-swift.patch and .md. parser.c
is unchanged, so the node-kind and field tables still match the wasm, which
is untouched.

TortureTry.swift pins calls after try!, try? and try on both engines.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Colby Mchenry
2026-10-19 21:49:17 +00:00
committed by GitHub
co-authored by Claude Opus 5.5
parent 22e9f7b337
commit c58f0417c6
17 changed files with 617105 additions and 26 deletions
@@ -43,6 +43,12 @@ at survey time: exactly 348) ride it (§Dedicated property branch).
## Grammar prep (NOT staged — land FIRST, before any walker exists)
> **Update 2026-10-19:** the kernel no longer depends on the crate. It
> compiles the same tarball's `src/` from `codegraph-kernel/grammars/swift/`
> with a one-line scanner patch, because the crate's MSVC build misread every
> `try!` on Windows (`docs/grammars/tree-sitter-swift.md`). The wasm below is
> unchanged.
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
+60
View File
@@ -0,0 +1,60 @@
# tree-sitter-swift — provenance & the kernel's scanner patch
The kernel's vendored C (`codegraph-kernel/grammars/swift/`) is the `src/` of
the [alex-pinkus/tree-sitter-swift](https://github.com/alex-pinkus/tree-sitter-swift)
(MIT) **0.7.3 crates.io tarball** — the same files
`src/extraction/wasm/tree-sitter-swift.wasm` is built from (see
`docs/design/swift-kernel-port-checklist.md` §Grammar prep for why the
tarball and not the tag) — with the patch in `tree-sitter-swift.patch`
applied to `src/scanner.c`. The wasm is built from the unpatched tarball.
| file | upstream sha256 | patched sha256 |
|---|---|---|
| `src/parser.c` | `d3edff6effe31b9a507f496577407987343b101b23eb7bee7a9b050e8ab5d27a` | unchanged |
| `src/scanner.c` | `f3d6271d64f58c39eed544104a70ca2cf9ecbf80c5d900620f1afd38836542cb` | `8acafef098ea4a52ebfc4cdf2fe26706113a22ac8ba2faf7d952755ff350cad6` |
## What the patch fixes
The external scanner produces its own `!` token, and steps aside for a `!`
written straight after `try` so the parser can read `try!`. It finds those
positions through a 64-bit mask whose entry for `!` is built as
`1UL << FAKE_TRY_BANG`, bit 32. Where `unsigned long` is 32 bits that shift
overflows, and each compiler folds it differently:
- **MSVC** — both Windows kernels (`x86_64`/`aarch64-pc-windows-msvc`) — folds
it to 0 (warning C4293). The scanner never steps aside, so
`try! Message2(data: d)` parsed as `try` followed by a call of `!Message2`:
the call was named `! Message2` and linked nothing, and a call through a
type, `try! Settings.Defaults.load()`, lost its receiver.
- **clang for wasm32** (the wasm) folds it to bit 31, `_directive_endif`,
which is valid at every expression start — right after `try` too — so the
wasm reads `try!` correctly. Its one other effect: a prefix `!` at an
expression start is lexed by the parser's own lexer, which leaves an
anonymous `!` token under `bang` where the scanner's token is hidden. No
walker reads it.
- **macOS and Linux** kernels (64-bit `unsigned long`) were always correct.
The patch is upstream 0.7.4's one-line fix, `1ULL`. Measured on 2026-10-09
over 1,172 Swift files (Alamofire, SDWebImage, protobuf, realm-swift,
swift-nio, vapor and six smaller projects): the patched native build parses
every error-free file to the same tree as the wasm, apart from that anonymous
`!`. The 8 files that differ have parse errors on both, and the kernel defers
a file with parse errors to the wasm. The unpatched MSVC build misparsed 157
error-free files, every one at a `try!`.
## Rebuild
```
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
git apply /path/to/codegraph/docs/grammars/tree-sitter-swift.patch
cp src/parser.c src/scanner.c /path/to/codegraph/codegraph-kernel/grammars/swift/
cp src/tree_sitter/*.h /path/to/codegraph/codegraph-kernel/grammars/swift/tree_sitter/
```
Then rebuild the kernel (`npm run build:kernel`) and run
`__tests__/kernel-grammar-parity.test.ts` and `scripts/kernel-parity.mjs
--lang swift` on a Swift repo: the wasm and the kernel must parse the same.
A bump to a release that carries the fix (0.7.4 or later) can drop the patch
and the vendored copy: pin the crate in `codegraph-kernel/Cargo.toml` again
and rebuild the wasm from the same tarball, together.
+13
View File
@@ -0,0 +1,13 @@
--- a/src/scanner.c 2026-10-09 00:00:00
+++ b/src/scanner.c 2026-10-09 00:00:00
@@ -128,7 +128,9 @@
0, // EQ_EQ,
0, // PLUS_THEN_WS,
0, // MINUS_THEN_WS,
- 1UL << FAKE_TRY_BANG, // BANG,
+ // 1ULL, as upstream has it from 0.7.4: 1UL << 32 overflows where unsigned
+ // long is 32 bits (MSVC), and `try!` lost its `!`. (codegraph patch)
+ 1ULL << FAKE_TRY_BANG, // BANG,
0, // THROWS_KEYWORD,
0, // RETHROWS_KEYWORD,
0, // DEFAULT_KEYWORD,