Swift support, and the two silent LSP bugs it exposed - #791
Merged
Conversation
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
swiftLSP descriptor claiming.swift, rooted atPackage.swift/compile_commands.json, launched assourcekit-lsp(macOS has a/usr/binshim onto the active Xcode; Linux toolchains put the real binary on PATH)..swiftonly —clangdclaims the C-family extensions and is listed first, so a mixed target still sends.mto clangd.swift build/test/run,swiftlint,swift-formatauto-allowed, same trust model as the cargo and go entries. Subcommands are named rather than allowingswift **, becauseswift foo.swiftandswift replrun arbitrary code — the reason barepythonis excluded — andswift package resolvefetches.claim_gate::segment_kindlearnedswift. Without itswift testmatched the verifier's generictesttoken while the claim gate returnedNone, 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 thelanguageIdsent indidOpen(LANGUAGES). Miss the third and nothing errors anywhere — the server spawns, accepts the document asplaintext, and answers every query with nothing. Swift had that gap; so did clangd's.m/.mmand ruby-lsp's.rake/.gemspec, both already shipped.every_served_extension_has_a_language_idderives 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 sendsclient/registerCapabilitywith a UUID and blocks until it is answered — so the server started, the file opened, anddocumentSymbolnever 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:
documentSymbolreturns the type tree (was(no results))definitionon a call in the test target resolves intoSources/Greeter/Greeter.swiftswift testruns with no promptdocumentSymbolon this repo's ownsrc/lsp/rpc.rsstill returns rust-analyzer's symbols — the RPC change is not a regressionFull suite 5609 pass, four clippy configs and fmt clean.
Follow-ups filed
dirge-3cfq— notree-sitter-swiftadapter, so the pre-write syntax gate doesn't cover.swiftand the tree-sitter-backedfind_definition/list_symbolsreturn nothing for Swift (thelsptool answers the same query correctly). There is precedent for LSP-without-adapter (cmake), so this is scope, not a defect in what shipped.anyone_waiting()is a process global, three production sites raise it, and only some tests takeTEST_GATE. Diagnosed and filed rather than folded in.