Skip to content

Swift support, and the two silent LSP bugs it exposed - #791

Merged
yogthos merged 2 commits into
mainfrom
swift-support
Aug 15, 2026
Merged

Swift support, and the two silent LSP bugs it exposed#791
yogthos merged 2 commits into
mainfrom
swift-support

Conversation

@yogthos

@yogthos yogthos commented Aug 15, 2026

Copy link
Copy Markdown
Collaborator

Closes #778.

sourcekit-lsp ships with the toolchain, so the server half is a table entry. Adding it and then running it against a real SwiftPM package is what turned this into three fixes.

Swift

  • swift LSP descriptor claiming .swift, rooted at Package.swift / compile_commands.json, launched as sourcekit-lsp (macOS has a /usr/bin shim onto the active Xcode; Linux toolchains put the real binary on PATH). .swift only — clangd claims the C-family extensions and is listed first, so a mixed target still sends .m to clangd.
  • swift build / test / run, swiftlint, swift-format auto-allowed, same trust model as the cargo and go entries. Subcommands are named rather than allowing swift **, because swift foo.swift and swift repl run arbitrary code — the reason bare python is excluded — and swift package resolve fetches.
  • claim_gate::segment_kind learned swift. Without it swift test matched the verifier's generic test token while the claim gate returned None, so a model told to verify, which verified, would be told it had not. That is dirge-hwk9.3 again, in a language neither recogniser knew about.

The two bugs it exposed, neither Swift-specific

A language server needs entries in three tables that don't reference each other: who claims the extension (builtin_servers), how to launch it (default_commands), and the languageId sent in didOpen (LANGUAGES). Miss the third and nothing errors anywhere — the server spawns, accepts the document as plaintext, and answers every query with nothing. Swift had that gap; so did clangd's .m/.mm and ruby-lsp's .rake/.gemspec, both already shipped. every_served_extension_has_a_language_id derives the check from the server registry, so claiming an extension now requires saying what language it is.

classify() identified a server→client request by its id's TYPE. Only numeric ids were accepted, so a request with a string id fell through to the notification arm and was dropped in silence. sourcekit-lsp sends client/registerCapability with a UUID and blocks until it is answered — so the server started, the file opened, and documentSymbol never came back. A request is identified by carrying both an id and a method; the ack now echoes the id verbatim. Found by teeing the LSP transport, not by reading the code.

Verification

Against a real SwiftPM package with the Xcode toolchain:

  • documentSymbol returns the type tree (was (no results))
  • definition on a call in the test target resolves into Sources/Greeter/Greeter.swift
  • swift test runs with no prompt
  • documentSymbol on this repo's own src/lsp/rpc.rs still returns rust-analyzer's symbols — the RPC change is not a regression

Full suite 5609 pass, four clippy configs and fmt clean.

Follow-ups filed

  • dirge-3cfq — no tree-sitter-swift adapter, so the pre-write syntax gate doesn't cover .swift and the tree-sitter-backed find_definition / list_symbols return nothing for Swift (the lsp tool answers the same query correctly). There is precedent for LSP-without-adapter (cmake), so this is scope, not a defect in what shipped.
  • A human-wait test flake seen twice in full-suite runs, unrelated to this change: anyone_waiting() is a process global, three production sites raise it, and only some tests take TEST_GATE. Diagnosed and filed rather than folded in.

Yogthos added 2 commits August 15, 2026 11:49
GH #778. sourcekit-lsp ships with the toolchain, so the server half is a table
entry: a `swift` descriptor claiming `.swift` with a Package.swift/
compile_commands.json root, and `sourcekit-lsp` as the launch command (macOS has
a /usr/bin shim, Linux toolchains put it on PATH).

Adding it and then actually running it against a real package found two bugs
that had nothing to do with Swift.

A language server needs entries in THREE tables that don't reference each other:
who claims the extension, how to launch it, and the languageId sent in didOpen.
Miss the third and nothing errors — the server spawns, accepts the document as
`plaintext` and answers every query with nothing. Swift had that, and so did
clangd's .m/.mm and ruby-lsp's .rake/.gemspec, both shipped. The new test derives
the check from the server registry, so claiming an extension now requires saying
what language it is.

Then documentSymbol still came back empty, and the wire capture showed why:
classify() only accepted numeric ids, so a server→client REQUEST with a string id
fell through to the notification arm and was dropped in silence. sourcekit-lsp
sends client/registerCapability with a UUID and blocks until it is answered, so
every request after it hung. A request is identified by having both an id and a
method, never by the id's type; the ack now echoes the id verbatim.

Also: swift build/test/run are auto-allowed like cargo's and go's (naming
subcommands, since `swift foo.swift` and `swift repl` run arbitrary code), and
claim_gate learned `swift` — without it `swift test` matched the verifier's
generic `test` token while the claim gate returned None, which is dirge-hwk9.3
again in a language neither recogniser knew.

Verified against a real SwiftPM package: documentSymbol returns the type tree,
definition resolves across files, `swift test` runs unprompted, and rust-analyzer
is unaffected.
@yogthos
yogthos merged commit 7733aad into main Aug 15, 2026
15 checks passed
@yogthos
yogthos deleted the swift-support branch August 15, 2026 16:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Swift support

1 participant