Skip to content

Support qualified name queries in workspace/symbol#2715

Draft
rintaro wants to merge 1 commit into
swiftlang:mainfrom
rintaro:qualified-workspace-symbol-search
Draft

Support qualified name queries in workspace/symbol#2715
rintaro wants to merge 1 commit into
swiftlang:mainfrom
rintaro:qualified-workspace-symbol-search

Conversation

@rintaro

@rintaro rintaro commented Jul 8, 2026

Copy link
Copy Markdown
Member

workspace/symbol previously matched the query as a flat fuzzy search against bare symbol names, so you couldn't search by qualified name (e.g. Foo.bar or Foo::bar).

Queries are now split on ./:: into a parent chain + member. A qualified query resolves the container(s) named by the parent chain and returns their members whose name contains the member as a case-insensitive subsequence. An empty member (Foo.) lists all members. Both separators are accepted and can be mixed (Foo::Bar.baz), and a partial chain works (Inner.member resolves a nested type without naming its outer scope). Unqualified queries take the flat-search path unchanged.

Unlike the unqualified path, which filters out system symbols, a qualified query deliberately surfaces system SDK results — String.description finds the standard-library member. Those results point into the module's generated interface via a sourcekit-lsp://generated-swift-interface location (reusing the existing reference-document mechanism); only system namespaces (e.g. std) are skipped, to avoid enumerating an entire system scope.

Qualified results carry the fully-qualified name in name (e.g. Foo.bar()) with no containerName, so clients that filter against the label (e.g. VS Code) don't drop them.

No changes to the protocol, workspace/symbolNames/symbolInfo, or IndexStoreDB.

@ahoppen ahoppen left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very nice 🤩

Comment thread Sources/SourceKitLSP/QualifiedWorkspaceSymbolQuery.swift Outdated
Comment thread Sources/SourceKitLSP/QualifiedWorkspaceSymbolQuery.swift Outdated
Comment thread Sources/SourceKitLSP/QualifiedWorkspaceSymbolQuery.swift
Comment thread Sources/SourceKitLSP/SourceKitLSPServer.swift Outdated
Comment thread Sources/SourceKitLSP/SourceKitLSPServer.swift
Comment thread Sources/SourceKitLSP/SourceKitLSPServer.swift
Comment thread Sources/SourceKitLSP/SourceKitLSPServer.swift Outdated
Comment thread Sources/SourceKitLSP/SourceKitLSPServer.swift Outdated
Comment thread Tests/SourceKitLSPTests/WorkspaceSymbolsTests.swift Outdated
Comment thread Tests/SourceKitLSPTests/WorkspaceSymbolsTests.swift Outdated
@rintaro
rintaro force-pushed the qualified-workspace-symbol-search branch from d93ce01 to c620b5a Compare July 9, 2026 19:28

@ahoppen ahoppen left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One more test I would

current = ""
case ":":
// Must be a `::` separator; a lone `:` is invalid.
guard iterator.next() == ":" else { return nil }

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about if you want to search for String.components(separatedBy:). That contains a lone but shouldn’t be invalid.

in index: CheckedIndex
) throws -> [SymbolOccurrence] {
guard let containerName = chain.last else {
throw ResponseError.internalError("members(ofContainerChain:matching:in:) requires a non-empty chain")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
throw ResponseError.internalError("members(ofContainerChain:matching:in:) requires a non-empty chain")
throw ResponseError.internalError("\(#function) requires a non-empty chain")

Comment thread Sources/SourceKitLSP/SourceKitLSPServer.swift
`workspace/symbol` previously couldn't search for symbols by their
qualified name (e.g. `String.description` or `Foo::bar`). The handler
treated the query as a flat fuzzy match against bare symbol names, with
no notion of containing types or namespaces.

Add a small parser that splits queries on the trailing `.` or `::`
separator into a parent chain + member, then in the handler:

- Unqualified queries (no separator) take the existing flat-search path.
- Qualified queries resolve the container named by the parent chain,
  then enumerate its members, keeping those whose name matches the
  member. An empty member (e.g. the query `Foo.`) lists all members of
  the container. Members declared in extensions are included by also
  enumerating the containers reached through the type's `extendedBy`
  relations.

Both `.` and `::` are accepted and can be mixed in one query
(`Foo::Bar.baz`). Multi-level chains (`Outer.Inner.method`) are matched
by walking the `childOf` relation chain.

Unlike the flat unqualified path, which filters out system symbols,
qualified queries deliberately surface results from system SDK modules:
naming an explicit container is a request for its members regardless of
where they are defined, so `String.description` finds the standard
library member. Such SDK/stdlib results point into the module's
generated interface via a `sourcekit-lsp://generated-swift-interface`
location (reusing the reference-document mechanism); only system
*namespaces* (e.g. `std`) are skipped, to avoid enumerating an entire
system scope.

Qualified results are returned with the fully-qualified name in the
symbol's `name` (e.g. `Foo.bar()`) and no `containerName`, so clients
that filter workspace symbols by matching the query against the label
(e.g. VS Code) keep them.

No changes to `workspace/symbolNames` or `workspace/symbolInfo`, and no
changes to the protocol or to IndexStoreDB.
@rintaro
rintaro force-pushed the qualified-workspace-symbol-search branch from c620b5a to d7dc365 Compare July 25, 2026 07: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.

2 participants