diff --git a/CLAUDE.md b/CLAUDE.md index 7ab6bcc..3897b9e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,9 +4,23 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co ## Maintenance -Follow `docs/FEATURE_VERIFICATION.md` for release updates, including separate -Microsoft Pyright/basedpyright checks and browser WASM verification. Keep the README -concise; dated evidence and detailed comparisons belong in `docs/`. +Follow `docs/internal/FEATURE_VERIFICATION.md` for release updates, including +separate Microsoft Pyright/basedpyright checks and browser WASM verification. + +## Documentation voice and audience + +- Keep the README and every guide it links concise, direct and confident. State + what each backend does and give actionable instructions and concrete limits. +- Preserve the character banner, backend personalities and tasteful emojis. +- Keep run dates, tested-version inventories, commands/results, investigation + history and editorial feedback in `docs/internal/`. Public docs must not link + to those records or narrate internal verification and decision-making. +- Confidence must follow evidence: keep real limitations and experimental labels; + remove hedging, repetitive caveats and unsupported rankings. +- Write public guides for users, internal records for maintainers. Apply this + distinction to the full README link path, not just the README itself. + +The durable feedback record is `docs/internal/DOCUMENTATION.md`. ## Essential Commands diff --git a/README.md b/README.md index faa5e44..c593035 100644 --- a/README.md +++ b/README.md @@ -10,31 +10,21 @@ and process pooling for Python language servers. Built on [Sublime LSP’s generated types](https://github.com/sublimelsp/lsp-python-types). Python 3.12+; one runtime dependency, `tomlkit`. -## Meet your party - -Pixel-art party: Pyright the blue sentinel, Pyrefly the coral artificer, ty the green scout, and Zuban the purple diplomat. - -- **[Pyright — the sentinel](docs/research/landscape.md#pyright--the-veteran).** - Broad typing support and configurable execution environments. Equip the - **basedpyright** fork for extra diagnostics, baselines and semantic highlighting; - those extras are not part of Microsoft Pyright. -- **[Pyrefly — the artificer](docs/research/pyrefly.md).** - A growing toolbelt: configurable regex and `mock.patch` checks, framework knowledge, - and experimental tensor/DataFrame analysis. Some tools need explicit settings; - experimental APIs can change. -- **[ty — the scout](docs/research/landscape.md#ty--the-swift-scout).** - Built for quick incremental feedback, explanatory diagnostics and precise type - narrowing. In this adapter, hover favors the type alone and completion resolution - is unavailable. -- **[Zuban — the diplomat](docs/research/landscape.md#zuban--the-bridge-builder).** - Bridges Mypy workflows and editor inference for untyped code. Compatibility modes - are its specialty; value-constrained generic bodies and unused ignores remain - checking blind spots. - -These are personalities, not speed rankings. The linked field notes separate -upstream features from what this library actually tests. - -## Start a session +## 🧙 Meet your party + +Pixel-art party: Pyright the blue sentinel, Pyrefly the coral artificer, ty the green scout, and Zuban the purple diplomat. + +- 🛡️ **[Pyright — the sentinel](https://github.com/Mazyod/lsp-python-types/blob/main/docs/research/landscape.md#pyright--the-sentinel).** + Broad typing support and configurable execution environments. + Choose **basedpyright** for extra diagnostics, baselines and semantic highlighting. +- 🔧 **[Pyrefly — the artificer](https://github.com/Mazyod/lsp-python-types/blob/main/docs/research/pyrefly.md).** + Framework-aware analysis with opt-in regex and `mock.patch` checks. +- 🏹 **[ty — the scout](https://github.com/Mazyod/lsp-python-types/blob/main/docs/research/landscape.md#ty--the-scout).** + Incremental analysis, explanatory diagnostics and precise type narrowing. +- 🤝 **[Zuban — the diplomat](https://github.com/Mazyod/lsp-python-types/blob/main/docs/research/landscape.md#zuban--the-diplomat).** + Mypy-compatible configuration and editor inference for untyped code. + +## 🚀 Start a session ```sh pip install "lsp-types[pyrefly]" # Or [ty] / [zuban] @@ -70,31 +60,30 @@ Node.js and `npm install -g pyright` (or `basedpyright`) separately. Sessions write backend configuration into `base_path`; use a dedicated workspace as above. For types alone, `import lsp_types`; no server is needed. -## What works here +## ✨ Features -Diagnostics, hover, completion, signature help and rename pass across all four -backends. Semantic tokens work with **basedpyright**, Pyrefly, ty and Zuban; -Microsoft Pyright does not provide them. Completion resolution enriches results -with Pyright/basedpyright and Zuban; Pyrefly echoes the item, while ty rejects it. +All four backends support diagnostics, hover, completion, signature help and rename. -Verified **2026-09-11**: Pyright **1.1.414**, basedpyright **1.40.1**, -Pyrefly **1.3.0**, ty **0.0.80**, Zuban **0.9.3**. +| Feature | Pyright | basedpyright | Pyrefly | ty | Zuban | +|---|---|---|---|---|---| +| Semantic highlighting | ❌ | ✅ | ✅ | ✅ | ✅ | +| Completion documentation via resolve | ✅ | ✅ | ❌ | ❌ | ✅ | -[Feature evidence & maintenance runbook](docs/FEATURE_VERIFICATION.md) · -[Semantic tokens](docs/SEMANTIC_TOKENS.md) · -[Low-level API & lifecycle](docs/USAGE.md) · -[Maintenance results](docs/MAINTENANCE_2026-09-11.md) +Pyrefly returns completion items unchanged on resolve; ty does not support the +request. ty hover returns the type without the symbol name. -## Development +📖 [API & lifecycle](https://github.com/Mazyod/lsp-python-types/blob/main/docs/USAGE.md) · +🎨 [Semantic tokens](https://github.com/Mazyod/lsp-python-types/blob/main/docs/SEMANTIC_TOKENS.md) · +🧭 [Backend guide](https://github.com/Mazyod/lsp-python-types/blob/main/docs/research/landscape.md) · +🎮 [Browser playground](https://mazyod.com/lsp-python-types/) + +## 🛠️ Development ```sh uv sync --all-extras --locked npm install -g basedpyright -uv run pytest +uv run pytest tests uvx pyright --pythonpath .venv/bin/python uvx ruff check . make generate-latest-types ``` - -The [runbook](docs/FEATURE_VERIFICATION.md) covers testing Microsoft Pyright -separately, regenerating schemas and updating the browser playground. diff --git a/docs/INTEGRATION_NOTES.md b/docs/INTEGRATION_NOTES.md index bac08bc..a74a234 100644 --- a/docs/INTEGRATION_NOTES.md +++ b/docs/INTEGRATION_NOTES.md @@ -1,219 +1,54 @@ -# LSP Backend Integration Notes - -This document captures frictions and enhancement opportunities discovered while integrating new LSP backends into lsp-python-types. - -## ty Backend Integration (January 2026) - -### Frictions Encountered - -#### 1. Virtual Document Support (Resolved) - -**Issue**: ty (as integrated at 0.0.11) required files to exist on disk before it could provide diagnostics, completion, and other features. Pyright and Pyrefly work with "virtual documents" opened via `didOpen` without requiring the file to exist on disk. - -**Original workaround**: The `requires_file_on_disk()` flag was added to the `LSPBackend` protocol so `Session.create()`/`update_code()` could mirror the session code to disk for ty. - -**Resolution (August 2026)**: Bisecting PyPI releases showed ty supports virtual documents from 0.0.16 onward (diagnostics, completion, and rename all verified with no file on disk). `TyBackend.requires_file_on_disk()` now returns `False` and the package floor is `ty>=0.0.16`. The protocol flag remains for any future backend that needs it. - -#### 2. `workspace/didChangeConfiguration` Not Supported - -**Issue**: ty logs `Received notification workspace/didChangeConfiguration which does not have a handler.` The Session class sends this notification after initialization to apply workspace settings, unless the backend opts out. - -**Impact**: Runtime configuration changes via `didChangeConfiguration` don't work with ty. However, configuration written to `ty.toml` is respected. - -**Resolution**: The `LSPBackend` protocol gained `consumes_did_change_configuration()` (default `True`). `TyBackend` and `ZubanBackend` return `False`, so `Session.create()` skips the notification entirely for them. No functional loss — both read their config from disk. - -#### 3. Nested Configuration Structure - -**Issue**: ty uses nested TOML sections (`[environment]`, `[src]`, `[rules]`) unlike Pyrefly's flat structure. This required implementing recursive key conversion. - -**Solution**: Created `_convert_keys_to_kebab()` function in `lsp_types/ty/backend.py`: -```python -def _convert_keys_to_kebab(obj: t.Mapping[str, t.Any]) -> dict[str, t.Any]: - """Recursively convert dict keys from snake_case to kebab-case.""" - result: dict[str, t.Any] = {} - for key, value in obj.items(): - kebab_key = key.replace("_", "-") - if isinstance(value, dict): - result[kebab_key] = _convert_keys_to_kebab(value) - elif isinstance(value, list): - result[kebab_key] = [ - _convert_keys_to_kebab(v) if isinstance(v, dict) else v - for v in value - ] - else: - result[kebab_key] = value - return result -``` - -**Potential Enhancement**: Extract this utility to a shared module (`lsp_types/utils.py`) since Pyrefly also uses TOML with kebab-case keys (though currently with flat structure). - -#### 4. Hover Information Format Differences - -**Issue**: ty's hover response shows just the type (`str`) rather than `variable_name: type` format used by Pyright and Pyrefly. - -**Impact**: Test assertions checking for variable names in hover text fail for ty. - -**Workaround**: Added backend-specific assertion in `test_session_hover`: -```python -if backend_name != "ty": - assert "result" in hover_text -assert "str" in hover_text -``` - -#### 5. No CLI Flags for LSP Server - -**Issue**: Unlike Pyrefly which accepts `--verbose`, `--threads`, and `--indexing-mode` CLI flags, ty's `server` command accepts no configuration flags. - -**Impact**: Configuration reaches ty via `ty.toml` or via `initializationOptions` at -LSP initialization, not via the command line. See ty's KNOWN_LIMITATIONS entries 1 and 3. - -**Solution**: `create_process_launch_info()` simply returns `["ty", "server"]` without any conditional flag building. - ---- - -## Enhancement Opportunities - -### 1. Shared TOML Key Conversion Utility - -Both Pyrefly and ty use TOML with kebab-case keys but Python code uses snake_case. Consider creating: - -```python -# lsp_types/utils.py -def snake_to_kebab_recursive(obj: Mapping[str, Any]) -> dict[str, Any]: - """Recursively convert dict keys from snake_case to kebab-case.""" - # ... implementation -``` - -Then refactor both backends to use this shared utility. - -### 2. Backend Capability Flags (implemented) - -The `LSPBackend` protocol carries both as methods: -- `requires_file_on_disk() -> bool` — all four backends return `False` -- `consumes_did_change_configuration() -> bool` — `False` for ty and Zuban - -`Session.create()` branches on both. - -### 3. Common LSP Capabilities Base - -Create a helper function for shared capabilities: - -```python -def get_base_python_capabilities() -> types.ClientCapabilities: - """Common LSP capabilities for Python type checkers.""" - return { - "textDocument": { - "publishDiagnostics": {...}, - "hover": {...}, - "signatureHelp": {}, - } - } -``` - -Backends could extend this base instead of duplicating the boilerplate. - -### 4. Monaco Native LSP Client (`monaco.lsp`) - -Monaco Editor v0.55.0 (November 2025) introduced a built-in LSP client under `monaco.lsp` that could significantly simplify the playground. The current playground manually handles JSON-RPC, the LSP handshake, position conversion, diagnostics, and hover registration (~200 lines per backend). With `monaco.lsp`, this reduces to ~10-15 lines per backend. - -**What it provides:** -- `MonacoLspClient` — auto-registers 21 LSP features (completion, hover, diagnostics, semantic tokens, go-to-definition, rename, code actions, inlay hints, etc.) -- `WebSocketTransport` — connect via WebSocket -- `createTransportToWorker(worker)` — connect to a Web Worker -- `createTransportToIFrame(iframe)` — connect to an iframe - -**Example usage:** -```typescript -const worker = new Worker(PYRIGHT_WORKER_URL); -const transport = monaco.lsp.createTransportToWorker(worker); -new monaco.lsp.MonacoLspClient(transport); -// All features auto-registered, including semantic tokens -``` - -**What it would replace in the playground:** -- `BackendAdapter` interface (diagnostics, hover, updateCode) -- `typeConversions.ts` (LSP-to-Monaco position mapping) -- Per-backend implementations (~200 lines each) -- Dependencies: `vscode-languageserver-protocol`, `vscode-jsonrpc` - -**Current artifact check (2026-09-11):** installed `monaco-editor` is still -0.56.0. The public declaration still exposes only `constructor(transport)` and -no `dispose()`; the shipped LSP client hardcodes `rootUri: null`, omits -`initializationOptions`, and discards the feature disposable store. The two -migration blockers below remain. The playground's existing adapters were built -and smoke-tested in Chromium; the other issue statuses below are historical, -not freshly verified. - -**Blockers / caveats (re-verified against v0.56.0 — 2026-08-30):** - -The August 30 verification was documentary only: npm was unavailable on that -machine, so nothing was installed or executed in that earlier run. Evidence came from the published npm artifacts (`monaco.d.ts` -and the shipped `esm/external/monaco-lsp-client/out/index.js` for 0.55.1 and 0.56.0, -fetched via CDN), the `monaco-lsp-client/` source at `main`, and the issue trackers. -The decisive check: diffing the shipped LSP bundle 0.55.1 -> 0.56.0 yields 50 lines — -untrusted markdown, a stray `debugger;` removal, one import rename. The client is -functionally unchanged, so every blocker below still stands. - -- **API still unstable** — `monaco-lsp-client/README.md` (current): "This package is - in alpha stage and might contain many bugs." 0.56.0 added typings (0.55.1 shipped - no `.d.ts` at all) but made no stability declaration. -- **No custom initialization params — still the main blocker.** - `constructor(transport: IMessageTransport)` is the entire public API; the client - hardcodes `{ processId: null, capabilities, rootUri: null }` and never sends - `initializationOptions`. Identical in 0.55.1, 0.56.0 and `main`. -- **Registration is global in practice for our backends** (the original wording was - too broad). Providers register against - `toMonacoLanguageSelector(capability.documentSelector)`, which falls back to - `{ language: "*" }` only when that selector is missing or empty — so capabilities - registered dynamically via `client/registerCapability` *are* scoped per-language. - But options derived from static server capabilities carry no document selector, - and Pyright, Pyrefly, ty and Zuban all advertise statically. -- **No reconnection** — `WebSocketTransport`'s `socket.onclose` only flips transport - state to `closed`; nothing subscribes to that state, and `reconnect` appears zero - times in the shipped bundle. -- **No `dispose()`** (microsoft/monaco-editor#5340, open) — new since this note was - written, and disqualifying on its own here. `createFeatures()` builds a - `DisposableStore` that the constructor discards, and `MonacoLspClient` exposes no - `dispose()`, so provider registrations outlive the transport for the page lifetime. - `playground/src/main.ts` disposes the adapter on every backend switch, so each - switch would leak a full set of providers. -- **Other open bugs to watch** — microsoft/monaco-editor#5224 and #5239 (document - URIs are case-mangled during text-document synchronisation) and #5342 - (`textDocument/codeAction` drops the diagnostic `data`, `code` and `source` fields - servers need for quickfixes). - -**Recommendation:** Do not migrate on monaco-editor 0.56.0. Re-evaluate only once -both a `MonacoLspClient` constructor accepting initialization options and a proper -`dispose()` have landed; #5340 is a required lifecycle fix, not merely something to -monitor. Treat 0.57+ as a release horizon to re-check, not an expectation that it -will be usable. If both land, migrating the playground would eliminate significant -boilerplate and gain features (completion, semantic tokens, rename, etc.) for free. - -### 5. Backend Registry Pattern - -For easier discovery and testing: - -```python -_BACKENDS: dict[str, type[LSPBackend]] = {} - -def register_backend(name: str): - def decorator(cls): - _BACKENDS[name] = cls - return cls - return decorator - -@register_backend("ty") -class TyBackend(LSPBackend): - ... -``` - ---- - -## Summary - -The ty backend integration revealed that different LSP servers have varying requirements around file handling and configuration. Optional capability flags on backends have since shipped (`requires_file_on_disk()`, `consumes_did_change_configuration()`). The current abstraction works but could still benefit from: - -1. Shared utilities for common patterns (TOML conversion, base capabilities) -2. Better documentation of backend-specific behaviors - -The core `LSPBackend` protocol and `Session` class work well across all four backends (Pyright, Pyrefly, ty, Zuban) with minimal backend-specific handling needed in tests. +# 🔧 Backend integration + +`Session` provides one API for Pyright, basedpyright, Pyrefly, ty, and Zuban. +Each backend supplies its launch command, configuration, and capabilities. +All supported backends analyze virtual documents without writing Python files +to disk. + +## Configuration + +`Session.create(options=...)` writes configuration in `base_path` before +launching the server. The default `base_path` is the current directory. + +| Backend | Configuration file | Python option keys | +| --- | --- | --- | +| Pyright / basedpyright | `pyrightconfig.json` | Upstream camelCase | +| Pyrefly | `pyrefly.toml` | Top-level snake_case becomes kebab-case; nested keys keep their spelling | +| ty | `ty.toml` | Snake_case becomes kebab-case recursively | +| Zuban | `[tool.zuban]` in `pyproject.toml` | Snake_case, unchanged | + +Pyright, Pyrefly, and ty replace their configuration files. Zuban replaces only +`[tool.zuban]`, preserving other sections and their formatting. Use a dedicated +`base_path` to keep session configuration separate from an existing project. + +Pass server-specific LSP initialization settings through +`Session.create(initialize_params=...)`. These values override the corresponding +top-level initialization fields. ty and Zuban receive configuration at session +creation; recreate their sessions to apply changes. + +## Capabilities and results + +- **Semantic tokens:** basedpyright, Pyrefly, ty, and Zuban support them. + Microsoft Pyright does not. Use the [normalized token API](SEMANTIC_TOKENS.md) + for one editor legend across backends. +- **Hover:** content and formatting differ by server. Display the returned + content directly; ty's variable hover shows the type without the variable name. +- **Completion resolution:** Pyrefly returns the submitted item unchanged. + ty rejects resolution requests. Use their initial completion results. +- **External file changes:** the client does not watch files. Recreate a session + when files changed outside `update_code()` are not reflected in results. + +See the backend-specific guidance for [Pyrefly](../lsp_types/pyrefly/KNOWN_LIMITATIONS.md), +[ty](../lsp_types/ty/KNOWN_LIMITATIONS.md), and +[Zuban](../lsp_types/zuban/KNOWN_LIMITATIONS.md). + +## Adding a backend + +Implement `LSPBackend` with the server's configuration writer, launch command, +client capabilities, workspace settings, and semantic-token legend fallback. +Use `None` for the fallback when the server advertises its legend. + +Set `requires_file_on_disk()` to match the server's document requirements. +Set `consumes_did_change_configuration()` to `False` when the server does not +handle that notification. `Session` handles initialization, document updates, +requests, and shutdown. diff --git a/docs/SEMANTIC_TOKENS.md b/docs/SEMANTIC_TOKENS.md index c451ba1..55530a9 100644 --- a/docs/SEMANTIC_TOKENS.md +++ b/docs/SEMANTIC_TOKENS.md @@ -1,10 +1,69 @@ -# Semantic Tokens Reference +# 🎨 Semantic tokens -This document provides a reference for semantic token types and modifiers returned by each LSP backend. This is particularly useful when integrating with editors like Monaco that need to map token IDs to theme colors. +Semantic tokens give editors type-aware syntax highlighting. basedpyright, +Pyrefly, ty, and Zuban support them; Microsoft Pyright does not. -## Overview +## Normalized Semantic Tokens API + +Use `normalize=True` with `CANONICAL_LEGEND` to keep editor colors consistent +when switching backends. Raw indexes differ: + +| Token | basedpyright Index | Pyrefly Index | ty Index | Zuban Index | +|-------|---------------|---------------|----------|-------------| +| `namespace` | 0 | 0 | 0 | 0 | +| `class` | 2 | 2 | 1 | 2 | +| `variable` | 6 | 8 | 5 | 8 | +| `function` | 9 | 12 | 7 | 12 | + +Request normalized tokens and send `CANONICAL_LEGEND` to the editor: + +```python +from pathlib import Path +from tempfile import TemporaryDirectory + +from lsp_types import CANONICAL_LEGEND, Session +from lsp_types.ty import TyBackend + +with TemporaryDirectory() as workspace: + session = await Session.create( + TyBackend(), base_path=Path(workspace), initial_code="x = 1" + ) + try: + tokens = await session.get_semantic_tokens(normalize=True) + legend = CANONICAL_LEGEND + finally: + await session.shutdown() +``` + +Run this code inside an async function. It requires the ty extra +(`uv add "lsp-types[ty]"`). + +Normalization requires a backend legend. If no legend is available, the method +returns raw tokens unchanged; unsupported servers still reject the request. + +### Available Properties + +```python +session.canonical_legend # The canonical legend (fixed, same for all backends) +session.backend_legend # The original legend from the server/backend +``` + +### Canonical Legend Order + +The canonical legend follows LSP standard ordering, with backend-specific tokens appended: + +**Token Types (index 0-26):** + +- 0-22: LSP standard types (namespace, type, class, enum, interface, struct, typeParameter, parameter, variable, property, enumMember, event, function, method, macro, keyword, modifier, comment, string, number, regexp, operator, decorator) +- 23: label (LSP standard) +- 24-26: Backend-specific (selfParameter, clsParameter, builtinConstant) + +**Token Modifiers (bit 0-18):** -Semantic tokens provide richer syntax highlighting than traditional TextMate grammars by leveraging the language server's understanding of the code. The LSP protocol encodes tokens as a compact integer array where each token is represented by 5 values. +- 0-9: LSP standard modifiers (declaration, definition, readonly, static, deprecated, abstract, async, modification, documentation, defaultLibrary) +- 10-12: Backend-specific from basedpyright (builtin, classMember, parameter) +- 13: Backend-specific from Pyrefly (selfParameter) +- 14-18: Pyrefly string modifiers (byteString, formatString, rawString, stringPrefix, templateString) ## Token Encoding Format @@ -12,9 +71,9 @@ Each token in the `data` array consists of 5 consecutive integers: | Position | Field | Description | |----------|-------|-------------| -| 0 | `deltaLine` | Line offset from previous token (or 0 for first token) | +| 0 | `deltaLine` | Line offset from previous token (from line 0 for the first token) | | 1 | `deltaStart` | Column offset from previous token on same line (or from 0 if new line) | -| 2 | `length` | Token length in characters | +| 2 | `length` | Token length in the negotiated position encoding | | 3 | `tokenType` | Index into the legend's `tokenTypes` array | | 4 | `tokenModifiers` | Bitmask of modifiers from the legend's `tokenModifiers` array | @@ -29,33 +88,21 @@ def has_modifier(token_modifiers: int, modifier_index: int) -> bool: For example, if `tokenModifiers = 5` (binary `101`), modifiers at index 0 and 2 are active. -## How to Get the Legend - -The legend is provided by the server during initialization in `InitializeResult.capabilities.semanticTokensProvider.legend`. You can extract it using: +## Backend legend -```python -from lsp_types.process import LSPProcess - -async with LSPProcess(process_info) as process: - init_result = await process.send.initialize({...}) - legend = init_result["capabilities"]["semanticTokensProvider"]["legend"] - token_types = legend["tokenTypes"] # List of type names - token_modifiers = legend["tokenModifiers"] # List of modifier names -``` - -See `examples/extract_semantic_legends.py` for a complete working example. - ---- +`session.backend_legend` contains the server's legend or the backend's fallback. +For low-level clients, read +`InitializeResult.capabilities.semanticTokensProvider.legend` when present. +Pyrefly omits this capability; use `PYREFLY_LEGEND` from +`lsp_types.semantic_tokens` for its raw tokens. ## Token Legends by Backend -Microsoft Pyright 1.1.414 does **not** provide semantic tokens. The Pyright-family -legend below belongs to the separate basedpyright fork, which uses the same backend. -These are LSP legends, independent of the playground’s WASM APIs. +The tables below describe each server’s raw LSP indexes. Read +`session.backend_legend` for the running server’s ordering. These legends apply +to LSP sessions; browser WASM APIs have their own feature sets. -### basedpyright (through PyrightBackend) - -> Last verified: basedpyright 1.40.1 (2026-09-11) +### basedpyright 1.40.1 (through PyrightBackend) #### Token Types @@ -93,10 +140,9 @@ These are LSP legends, independent of the playground’s WASM APIs. --- -### Pyrefly +### Pyrefly 1.3.0 -> Last verified: Pyrefly 1.3.0 (2026-09-11) -> Legend source: [semantic_tokens.rs](https://github.com/facebook/pyrefly/blob/1.3.0/pyrefly/lib/state/semantic_tokens.rs) +Legend source: [semantic_tokens.rs](https://github.com/facebook/pyrefly/blob/1.3.0/pyrefly/lib/state/semantic_tokens.rs) Pyrefly does not advertise its legend via LSP initialization, but the token mappings are defined in source code. @@ -151,9 +197,7 @@ Pyrefly does not advertise its legend via LSP initialization, but the token mapp --- -### ty - -> Last verified: ty 0.0.80 (2026-09-11) +### ty 0.0.80 #### Token Types @@ -188,9 +232,7 @@ Pyrefly does not advertise its legend via LSP initialization, but the token mapp --- -### Zuban - -> Last verified: Zuban 0.9.3 (2026-09-11) +### Zuban 0.9.3 Zuban advertises its legend via LSP initialization (follows LSP 3.17 standard ordering for the 23 token types it emits). @@ -239,20 +281,18 @@ Zuban advertises its legend via LSP initialization (follows LSP 3.17 standard or ## Monaco Editor Integration -When integrating with Monaco, register a `DocumentSemanticTokensProvider` that: - -1. Requests tokens via `session.get_semantic_tokens()` -2. Returns the token data along with the legend +Register a `DocumentSemanticTokensProvider` with the legend paired to your +Python response. In this example, `canonicalLegend` is the JSON representation +of `CANONICAL_LEGEND`, and `requestSemanticTokens` calls +`session.get_semantic_tokens(normalize=True)` through your application’s transport. ```typescript // TypeScript example for Monaco monaco.languages.registerDocumentSemanticTokensProvider('python', { - getLegend: () => ({ - tokenTypes: ['namespace', 'type', 'class', ...], // From backend legend - tokenModifiers: ['declaration', 'definition', ...] - }), + getLegend: () => canonicalLegend, provideDocumentSemanticTokens: async (model) => { const tokens = await requestSemanticTokens(model.uri); + if (!tokens) return null; return { data: new Uint32Array(tokens.data), resultId: tokens.resultId @@ -262,77 +302,5 @@ monaco.languages.registerDocumentSemanticTokensProvider('python', { }); ``` -The token types and modifiers must be registered in the **exact same order** as the backend's legend for the indices to map correctly. - ---- - -## Normalized Semantic Tokens API - -The library provides a **normalized tokens API** that remaps token indices to a canonical legend. This allows Monaco/editors to use a single fixed legend regardless of which backend is active. - -### The Problem - -Each backend has different legend ordering: - -| Token | Pyright Index | Pyrefly Index | ty Index | Zuban Index | -|-------|---------------|---------------|----------|-------------| -| `namespace` | 0 | 0 | 0 | 0 | -| `class` | 2 | 2 | 1 | 2 | -| `variable` | 6 | 8 | 5 | 8 | -| `function` | 9 | 12 | 7 | 12 | - -A Monaco client configured with one legend breaks when switching backends. - -### The Solution - -Use the `normalize=True` parameter to get tokens with indices remapped to the canonical legend: - -```python -from lsp_types import Session, CANONICAL_LEGEND -from lsp_types.pyright.backend import PyrightBackend - -session = await Session.create(PyrightBackend(), initial_code="x = 1") - -# Original tokens (backend-specific indices) -raw = await session.get_semantic_tokens() - -# Normalized tokens (canonical indices matching CANONICAL_LEGEND) -normalized = await session.get_semantic_tokens(normalize=True) - -# Monaco uses one fixed legend for all backends -monaco_legend = CANONICAL_LEGEND -``` - -### Available Properties - -```python -session.canonical_legend # The canonical legend (fixed, same for all backends) -session.backend_legend # The original legend from the server/backend -``` - -### Canonical Legend Order - -The canonical legend follows LSP standard ordering, with backend-specific tokens appended: - -**Token Types (index 0-26):** -- 0-22: LSP standard types (namespace, type, class, enum, interface, struct, typeParameter, parameter, variable, property, enumMember, event, function, method, macro, keyword, modifier, comment, string, number, regexp, operator, decorator) -- 23: label (LSP standard) -- 24-26: Backend-specific (selfParameter, clsParameter, builtinConstant) - -**Token Modifiers (bit 0-18):** -- 0-9: LSP standard modifiers (declaration, definition, readonly, static, deprecated, abstract, async, modification, documentation, defaultLibrary) -- 10-12: Backend-specific from Pyright (builtin, classMember, parameter) -- 13: Backend-specific from Pyrefly (selfParameter) -- 14-18: Pyrefly 1.3 string modifiers (byteString, formatString, rawString, stringPrefix, templateString); appended so existing indices stay stable - ---- - -## Updating This Document - -Run the extraction script to get the latest legends: - -```bash -uv run python examples/extract_semantic_legends.py -``` - -Update the tables above with the script output when backend versions change. +Match the legend to the data: use `CANONICAL_LEGEND` for normalized tokens and +`session.backend_legend` for raw tokens. Preserve the legend’s exact ordering. diff --git a/docs/USAGE.md b/docs/USAGE.md index 896e915..02ebd59 100644 --- a/docs/USAGE.md +++ b/docs/USAGE.md @@ -1,50 +1,68 @@ -# Usage details +# 🔌 Low-level API & lifecycle + +`Session` handles initialization and document management. Use `LSPProcess` for +direct access to typed LSP requests and notifications. ## Low-level stdio -> [!TIP] -> Recommend using [basedpyright](https://github.com/DetachHead/basedpyright) for extended features. +This example requires `pyright-langserver` on your `PATH`. ```python +import asyncio +from pathlib import Path + from lsp_types.process import LSPProcess, ProcessLaunchInfo -process_info = ProcessLaunchInfo(cmd=[ - "pyright-langserver", "--stdio" -]) -async with LSPProcess(process_info) as process: - # Initialize the process - ... +async def main(): + root = Path.cwd() + process_info = ProcessLaunchInfo(cmd=["pyright-langserver", "--stdio"]) + + async with LSPProcess(process_info) as process: + await process.send.initialize({ + "processId": None, + "rootUri": root.as_uri(), + "capabilities": {}, + }) + await process.notify.initialized({}) - # Grab a typed listener - diagnostics_listener = process.notify.on_publish_diagnostics(timeout=1.0) + # Register before opening the document so the response is captured. + listener = process.notify.on_publish_diagnostics(timeout=10.0) + await process.notify.did_open_text_document({ + "textDocument": { + "uri": (root / "example.py").as_uri(), + "languageId": "python", + "version": 1, + "text": "value: int = 'hello'\n", + } + }) + print(await listener) - # Send a notification (`await` is optional. It ensures messages have been drained) - await process.notify.did_open_text_document(...) - # Wait for diagnostics to come in - diagnostics = await diagnostics_listener +asyncio.run(main()) ``` -`LSPProcess.stop()` is terminal — including the implicit stop() when the `async with` -block exits. Calling `start()` on a stopped process raises `RuntimeError` instead -of relaunching the server, and requests and notifications sent through it raise -`RuntimeError` too (notifications are no longer dropped with a warning). The -messages name the state they came from (`LSP process has been stopped` vs. `LSP -process has not been started`). Construct a new `LSPProcess` when you need to -restart a server. +Requests require `await`. Notifications queue immediately; awaiting one also +waits for its bytes to drain to the server. +## Process lifecycle + +`LSPProcess.stop()` permanently closes the process. Exiting its `async with` +block calls `stop()` automatically. Starting a stopped process, or sending +requests or notifications through it, raises `RuntimeError`. Create a new +`LSPProcess` to restart a server. ## Session lifecycle -After `shutdown()`, a session's operational methods raise `RuntimeError`; its -captured server and semantic-token metadata remain readable. Calling -`shutdown()` while other operations are in flight is safe: it waits up to five -seconds for them to finish, and if any are still running it stops the language -server process instead of returning it to the pool, keeping stale operations -out of the next session's protocol stream. (One narrow exception: cancelling -an operation ends its in-flight accounting even if a notification write it -already queued is still being flushed.) +Always call `await session.shutdown()` in a `finally` block. Shutdown rejects +new operations immediately and gives active operations up to five seconds to +finish. If any remain, it stops the server process. Otherwise, it releases the +process to the pool, or stops it when no reusable pool was supplied. + +After shutdown, operational methods raise `RuntimeError`. Captured server +information and semantic-token legends remain readable. +Await document updates before shutting down. Cancelling an operation can leave +an already-queued notification flushing after the operation ends. -Internal generated types whose names start with `__` are not public API. +Generated types whose names start with `__` are internal and outside the public API. diff --git a/docs/internal/DOCUMENTATION.md b/docs/internal/DOCUMENTATION.md new file mode 100644 index 0000000..e018f47 --- /dev/null +++ b/docs/internal/DOCUMENTATION.md @@ -0,0 +1,44 @@ +# Documentation policy and feedback + +## Standing policy + +The README and linked user guides explain the product: what it does, how to use +it, and its concrete limitations. Write short, confident statements. Keep the +pixel-art party, backend characters and tasteful emojis. Personality is welcome; +meandering explanations and process narration are not. + +Evidence remains public in `docs/internal/`, but outside the user documentation +path. Store runbooks, dated validation, release close-outs, investigation details +and this feedback here. Do not link them from the README or user guides. + +State known limits directly. Keep version requirements and experimental labels +when they affect users. Never replace uncertainty with an unsupported claim; +resolve it through evidence or narrow the claim. Avoid ritual disclaimers about +how the work was checked and repeated explanations of what a comparison is not. + +## Feedback ledger — 2026-09-11 + +The maintainer praised the README banner and its character classes for giving +the project personality. They rejected verbose, hesitant prose and prominent +links to exact run dates, verification logs and internal deliberation. The same +standard applies to every document directly linked from the README. They asked +for concise, assertive, useful writing with emojis, and authorized keeping this +feedback publicly in a tucked-away maintainer folder. + +This policy belongs in project instructions and the release runbook so future +maintenance preserves the editorial standard. Historical evidence is retained; +public guides carry the conclusions and user actions. + +## Documentation refresh validation + +- Full suite with basedpyright: 251 passed, 1 existing expected failure (ty hover). +- Microsoft Pyright suite: 35 passed. +- Pyright type check: zero errors, warnings or information; Ruff lint passed. +- Wheel and source distribution built successfully with the revised README. +- Local documentation links and heading anchors passed; README links and artwork + use absolute URLs for GitHub and PyPI. +- Parallel reviews covered backend claims, API guidance and publication steps. +- The low-level stdio example returned the expected assignment diagnostic; the + normalized ty example returned token data and shut down successfully. + +No library behavior, dependency versions or playground code changed. diff --git a/docs/FEATURE_VERIFICATION.md b/docs/internal/FEATURE_VERIFICATION.md similarity index 76% rename from docs/FEATURE_VERIFICATION.md rename to docs/internal/FEATURE_VERIFICATION.md index 72d38cd..77f79bf 100644 --- a/docs/FEATURE_VERIFICATION.md +++ b/docs/internal/FEATURE_VERIFICATION.md @@ -1,7 +1,8 @@ # Feature verification and maintenance runbook Run this after backend releases, dependency updates or changes to `Session`. -Keep the README to a short overview; put the evidence and qualifications here. +Follow [the documentation policy](DOCUMENTATION.md). Keep evidence here and in +dated internal records; publish concise capabilities and practical limits in user guides. ## 1. Update and record versions @@ -94,7 +95,7 @@ PATH=/tmp/lsp-basedpyright/node_modules/.bin:$PATH \ rg -n 'xfail|skip' tests ``` -Compare legends with [the token reference](SEMANTIC_TOKENS.md) and tagged source. +Compare legends with [the token reference](../SEMANTIC_TOKENS.md) and tagged source. Pyrefly 1.3 still omits its provider from initialization; it needs the fallback legend. `tests/test_semantic_tokens.py` checks that its five new string modifiers survive normalization, including a live server fixture. Append canonical entries @@ -102,9 +103,9 @@ so existing editor indices stay stable. ty 0.0.80 appends `operator` and `regexp token types, already covered by the canonical legend. Reprobe versioned limitations with positive controls before advancing their dates: -[Pyrefly](../lsp_types/pyrefly/KNOWN_LIMITATIONS.md), -[ty](../lsp_types/ty/KNOWN_LIMITATIONS.md), -[Zuban](../lsp_types/zuban/KNOWN_LIMITATIONS.md). +[Pyrefly](../../lsp_types/pyrefly/KNOWN_LIMITATIONS.md), +[ty](../../lsp_types/ty/KNOWN_LIMITATIONS.md), +[Zuban](../../lsp_types/zuban/KNOWN_LIMITATIONS.md). Check manual config schemas against tagged source/docs, including severity values, nested sections and renamed/deprecated settings. Keep historical evidence labeled. @@ -149,11 +150,27 @@ PLAYWRIGHT_MODULE=/tmp/lsp-browser-check/node_modules/playwright/index.mjs \ node concurrency.test.mjs ``` -## 5. Publish the evidence in the docs - -Update the README versions and short profiles, this feature snapshot, semantic -legends and limitation dates. Save a dated maintenance report with commands, -results, unresolved issues and environment details. Keep broader capabilities, -release sources and benchmark caveats in the [field guide](research/landscape.md) -and [Pyrefly notes](research/pyrefly.md). Do not turn upstream marketing benchmarks -or test-suite elapsed time into a speed leaderboard. +## 5. Update public guidance and internal evidence + +Update the public feature descriptions, semantic legends and actionable +limitations when behavior changes. Use direct statements, preserve the character +banner and use emojis sparingly. Keep compatibility versions that affect usage; +keep verification dates and tested-version inventories here. + +Save commands, results, release sources, unresolved issues and environment details +in a dated maintenance record under `docs/internal/`. Keep detailed research in +`docs/internal/research/`. Public pages must not link to these records or this +runbook. Do not turn upstream benchmarks or test elapsed time into a speed ranking. + +Before publishing, follow the README links and read each destination as a user. +Remove investigation narrative, indecisive language and repeated qualifications; +retain real limitations, experimental status and useful upstream references. +Check local links and anchors after moving documents. Use absolute GitHub links +and a raw image URL in the README so the PyPI description renders correctly. + +For documentation releases, run the required tests, type check and lint; merge +only after CI passes. Use the existing `publish.yml` workflow with a patch bump +when the PyPI README needs updating. Deploy the playground through its existing +workflow when requested or when its assets change; no dependency or schema refresh +is needed for prose-only changes. Record publication and deployment results in the +internal ledger, never in the README. diff --git a/docs/MAINTENANCE_2026-09-11.md b/docs/internal/MAINTENANCE_2026-09-11.md similarity index 96% rename from docs/MAINTENANCE_2026-09-11.md rename to docs/internal/MAINTENANCE_2026-09-11.md index 5693f29..42b1b4d 100644 --- a/docs/MAINTENANCE_2026-09-11.md +++ b/docs/internal/MAINTENANCE_2026-09-11.md @@ -36,7 +36,7 @@ Release sources and comparisons: [field guide](research/landscape.md), its fallback legend and the canonical legend, preserving existing indices. A wire-bit regression check and live server test cover all five. ty now advertises `operator` and `regexp` token types; the canonical legend already - handles both. Updated the [token reference](SEMANTIC_TOKENS.md). + handles both. Updated the [token reference](../SEMANTIC_TOKENS.md). - **Configuration:** updated Pyrefly severities, presets, inference, multi-platform settings and baselines; ty import-analysis controls, strictness, per-file overrides, script exclusion and output formats; Zuban `auto` mode. Refreshed @@ -54,7 +54,7 @@ Release sources and comparisons: [field guide](research/landscape.md), CI now fetches/builds and smoke-tests pinned WASM before producing the site. - **Presentation:** replaced the long README with a ~425-word overview and one pixel-art party banner. Usage details and the feature matrix moved into docs. - [Artwork and generation prompt](../assets/images/README.md) are in the repo. + [Artwork and generation prompt](../../assets/images/README.md) are in the repo. Pyrefly's configured regex/mock-target checks caught two errors that strict Pyright did not in the same CLI probe. This is evidence for specific extra @@ -106,7 +106,7 @@ The existing Monaco dynamic-import bundler warning remains harmless. The code generator also emits a formatter deprecation warning even with the extras explicitly declared; generated output and validation succeed. Monaco's native LSP client still lacks configurable initialization and disposal in the installed -0.56.0 artifact, so migration remains deferred (see [integration notes](INTEGRATION_NOTES.md)). +0.56.0 artifact, so migration remains deferred (see [archived integration notes](REFERENCE_HISTORY_2026-09-11.md)). These checks establish the tested integration behaviors, not full conformance or an independent speed leaderboard. CLI, LSP, TSP and browser WASM features remain diff --git a/docs/internal/REFERENCE_HISTORY_2026-09-11.md b/docs/internal/REFERENCE_HISTORY_2026-09-11.md new file mode 100644 index 0000000..be4c737 --- /dev/null +++ b/docs/internal/REFERENCE_HISTORY_2026-09-11.md @@ -0,0 +1,961 @@ +# Archived reference notes — September 11, 2026 + +Historical documentation preserved during the public-docs cleanup. These notes contain dated observations, superseded wording, and unimplemented proposals; use the public guides for current API guidance. + +--- + +Source: `docs/USAGE.md` + +# Usage details + +## Low-level stdio + +> [!TIP] +> Recommend using [basedpyright](https://github.com/DetachHead/basedpyright) for extended features. + +```python +from lsp_types.process import LSPProcess, ProcessLaunchInfo + +process_info = ProcessLaunchInfo(cmd=[ + "pyright-langserver", "--stdio" +]) + +async with LSPProcess(process_info) as process: + # Initialize the process + ... + + # Grab a typed listener + diagnostics_listener = process.notify.on_publish_diagnostics(timeout=1.0) + + # Send a notification (`await` is optional. It ensures messages have been drained) + await process.notify.did_open_text_document(...) + + # Wait for diagnostics to come in + diagnostics = await diagnostics_listener +``` + +`LSPProcess.stop()` is terminal — including the implicit stop() when the `async with` +block exits. Calling `start()` on a stopped process raises `RuntimeError` instead +of relaunching the server, and requests and notifications sent through it raise +`RuntimeError` too (notifications are no longer dropped with a warning). The +messages name the state they came from (`LSP process has been stopped` vs. `LSP +process has not been started`). Construct a new `LSPProcess` when you need to +restart a server. + + +## Session lifecycle + +After `shutdown()`, a session's operational methods raise `RuntimeError`; its +captured server and semantic-token metadata remain readable. Calling +`shutdown()` while other operations are in flight is safe: it waits up to five +seconds for them to finish, and if any are still running it stops the language +server process instead of returning it to the pool, keeping stale operations +out of the next session's protocol stream. (One narrow exception: cancelling +an operation ends its in-flight accounting even if a notification write it +already queued is still being flushed.) + + +Internal generated types whose names start with `__` are not public API. + +--- + +Source: `docs/SEMANTIC_TOKENS.md` + +# Semantic Tokens Reference + +This document provides a reference for semantic token types and modifiers returned by each LSP backend. This is particularly useful when integrating with editors like Monaco that need to map token IDs to theme colors. + +## Overview + +Semantic tokens provide richer syntax highlighting than traditional TextMate grammars by leveraging the language server's understanding of the code. The LSP protocol encodes tokens as a compact integer array where each token is represented by 5 values. + +## Token Encoding Format + +Each token in the `data` array consists of 5 consecutive integers: + +| Position | Field | Description | +|----------|-------|-------------| +| 0 | `deltaLine` | Line offset from previous token (or 0 for first token) | +| 1 | `deltaStart` | Column offset from previous token on same line (or from 0 if new line) | +| 2 | `length` | Token length in characters | +| 3 | `tokenType` | Index into the legend's `tokenTypes` array | +| 4 | `tokenModifiers` | Bitmask of modifiers from the legend's `tokenModifiers` array | + +### Decoding Token Modifiers + +The `tokenModifiers` value is a bitmask. To check if a modifier applies: + +```python +def has_modifier(token_modifiers: int, modifier_index: int) -> bool: + return (token_modifiers & (1 << modifier_index)) != 0 +``` + +For example, if `tokenModifiers = 5` (binary `101`), modifiers at index 0 and 2 are active. + +## How to Get the Legend + +The legend is provided by the server during initialization in `InitializeResult.capabilities.semanticTokensProvider.legend`. You can extract it using: + +```python +from lsp_types.process import LSPProcess + +async with LSPProcess(process_info) as process: + init_result = await process.send.initialize({...}) + legend = init_result["capabilities"]["semanticTokensProvider"]["legend"] + token_types = legend["tokenTypes"] # List of type names + token_modifiers = legend["tokenModifiers"] # List of modifier names +``` + +See `examples/extract_semantic_legends.py` for a complete working example. + +--- + +## Token Legends by Backend + +Microsoft Pyright 1.1.414 does **not** provide semantic tokens. The Pyright-family +legend below belongs to the separate basedpyright fork, which uses the same backend. +These are LSP legends, independent of the playground’s WASM APIs. + +### basedpyright (through PyrightBackend) + +> Last verified: basedpyright 1.40.1 (2026-09-11) + +#### Token Types + +| Index | Token Type | +|------:|------------| +| 0 | `namespace` | +| 1 | `type` | +| 2 | `class` | +| 3 | `enum` | +| 4 | `typeParameter` | +| 5 | `parameter` | +| 6 | `variable` | +| 7 | `property` | +| 8 | `enumMember` | +| 9 | `function` | +| 10 | `method` | +| 11 | `keyword` | +| 12 | `decorator` | +| 13 | `selfParameter` | +| 14 | `clsParameter` | + +#### Token Modifiers + +| Bit | Modifier | +|----:|----------| +| 0 | `declaration` | +| 1 | `definition` | +| 2 | `readonly` | +| 3 | `static` | +| 4 | `async` | +| 5 | `defaultLibrary` | +| 6 | `builtin` | +| 7 | `classMember` | +| 8 | `parameter` | + +--- + +### Pyrefly + +> Last verified: Pyrefly 1.3.0 (2026-09-11) +> Legend source: [semantic_tokens.rs](https://github.com/facebook/pyrefly/blob/1.3.0/pyrefly/lib/state/semantic_tokens.rs) + +Pyrefly does not advertise its legend via LSP initialization, but the token mappings are defined in source code. + +#### Token Types + +| Index | Token Type | +|------:|------------| +| 0 | `namespace` | +| 1 | `type` | +| 2 | `class` | +| 3 | `enum` | +| 4 | `interface` | +| 5 | `struct` | +| 6 | `typeParameter` | +| 7 | `parameter` | +| 8 | `variable` | +| 9 | `property` | +| 10 | `enumMember` | +| 11 | `event` | +| 12 | `function` | +| 13 | `method` | +| 14 | `macro` | +| 15 | `keyword` | +| 16 | `modifier` | +| 17 | `comment` | +| 18 | `string` | +| 19 | `number` | +| 20 | `regexp` | +| 21 | `operator` | +| 22 | `decorator` | + +#### Token Modifiers + +| Bit | Modifier | +|----:|----------| +| 0 | `declaration` | +| 1 | `definition` | +| 2 | `readonly` | +| 3 | `static` | +| 4 | `deprecated` | +| 5 | `abstract` | +| 6 | `async` | +| 7 | `modification` | +| 8 | `documentation` | +| 9 | `defaultLibrary` | +| 10 | `selfParameter` | +| 11 | `byteString` | +| 12 | `formatString` | +| 13 | `rawString` | +| 14 | `stringPrefix` | +| 15 | `templateString` | + +--- + +### ty + +> Last verified: ty 0.0.80 (2026-09-11) + +#### Token Types + +| Index | Token Type | +|------:|------------| +| 0 | `namespace` | +| 1 | `class` | +| 2 | `parameter` | +| 3 | `selfParameter` | +| 4 | `clsParameter` | +| 5 | `variable` | +| 6 | `property` | +| 7 | `function` | +| 8 | `method` | +| 9 | `keyword` | +| 10 | `string` | +| 11 | `number` | +| 12 | `decorator` | +| 13 | `builtinConstant` | +| 14 | `typeParameter` | +| 15 | `operator` | +| 16 | `regexp` | + +#### Token Modifiers + +| Bit | Modifier | +|----:|----------| +| 0 | `definition` | +| 1 | `readonly` | +| 2 | `async` | +| 3 | `documentation` | + +--- + +### Zuban + +> Last verified: Zuban 0.9.3 (2026-09-11) + +Zuban advertises its legend via LSP initialization (follows LSP 3.17 standard ordering for the 23 token types it emits). + +#### Token Types + +| Index | Token Type | +|------:|------------| +| 0 | `namespace` | +| 1 | `type` | +| 2 | `class` | +| 3 | `enum` | +| 4 | `interface` | +| 5 | `struct` | +| 6 | `typeParameter` | +| 7 | `parameter` | +| 8 | `variable` | +| 9 | `property` | +| 10 | `enumMember` | +| 11 | `event` | +| 12 | `function` | +| 13 | `method` | +| 14 | `macro` | +| 15 | `keyword` | +| 16 | `modifier` | +| 17 | `comment` | +| 18 | `string` | +| 19 | `number` | +| 20 | `regexp` | +| 21 | `operator` | +| 22 | `decorator` | + +#### Token Modifiers + +| Bit | Modifier | +|----:|----------| +| 0 | `declaration` | +| 1 | `definition` | +| 2 | `readonly` | +| 3 | `static` | +| 4 | `deprecated` | +| 5 | `abstract` | +| 6 | `async` | +| 7 | `defaultLibrary` | + +--- + +## Monaco Editor Integration + +When integrating with Monaco, register a `DocumentSemanticTokensProvider` that: + +1. Requests tokens via `session.get_semantic_tokens()` +2. Returns the token data along with the legend + +```typescript +// TypeScript example for Monaco +monaco.languages.registerDocumentSemanticTokensProvider('python', { + getLegend: () => ({ + tokenTypes: ['namespace', 'type', 'class', ...], // From backend legend + tokenModifiers: ['declaration', 'definition', ...] + }), + provideDocumentSemanticTokens: async (model) => { + const tokens = await requestSemanticTokens(model.uri); + return { + data: new Uint32Array(tokens.data), + resultId: tokens.resultId + }; + }, + releaseDocumentSemanticTokens: () => {} +}); +``` + +The token types and modifiers must be registered in the **exact same order** as the backend's legend for the indices to map correctly. + +--- + +## Normalized Semantic Tokens API + +The library provides a **normalized tokens API** that remaps token indices to a canonical legend. This allows Monaco/editors to use a single fixed legend regardless of which backend is active. + +### The Problem + +Each backend has different legend ordering: + +| Token | Pyright Index | Pyrefly Index | ty Index | Zuban Index | +|-------|---------------|---------------|----------|-------------| +| `namespace` | 0 | 0 | 0 | 0 | +| `class` | 2 | 2 | 1 | 2 | +| `variable` | 6 | 8 | 5 | 8 | +| `function` | 9 | 12 | 7 | 12 | + +A Monaco client configured with one legend breaks when switching backends. + +### The Solution + +Use the `normalize=True` parameter to get tokens with indices remapped to the canonical legend: + +```python +from lsp_types import Session, CANONICAL_LEGEND +from lsp_types.pyright.backend import PyrightBackend + +session = await Session.create(PyrightBackend(), initial_code="x = 1") + +# Original tokens (backend-specific indices) +raw = await session.get_semantic_tokens() + +# Normalized tokens (canonical indices matching CANONICAL_LEGEND) +normalized = await session.get_semantic_tokens(normalize=True) + +# Monaco uses one fixed legend for all backends +monaco_legend = CANONICAL_LEGEND +``` + +### Available Properties + +```python +session.canonical_legend # The canonical legend (fixed, same for all backends) +session.backend_legend # The original legend from the server/backend +``` + +### Canonical Legend Order + +The canonical legend follows LSP standard ordering, with backend-specific tokens appended: + +**Token Types (index 0-26):** +- 0-22: LSP standard types (namespace, type, class, enum, interface, struct, typeParameter, parameter, variable, property, enumMember, event, function, method, macro, keyword, modifier, comment, string, number, regexp, operator, decorator) +- 23: label (LSP standard) +- 24-26: Backend-specific (selfParameter, clsParameter, builtinConstant) + +**Token Modifiers (bit 0-18):** +- 0-9: LSP standard modifiers (declaration, definition, readonly, static, deprecated, abstract, async, modification, documentation, defaultLibrary) +- 10-12: Backend-specific from Pyright (builtin, classMember, parameter) +- 13: Backend-specific from Pyrefly (selfParameter) +- 14-18: Pyrefly 1.3 string modifiers (byteString, formatString, rawString, stringPrefix, templateString); appended so existing indices stay stable + +--- + +## Updating This Document + +Run the extraction script to get the latest legends: + +```bash +uv run python examples/extract_semantic_legends.py +``` + +Update the tables above with the script output when backend versions change. + +--- + +Source: `docs/INTEGRATION_NOTES.md` + +# LSP Backend Integration Notes + +This document captures frictions and enhancement opportunities discovered while integrating new LSP backends into lsp-python-types. + +## ty Backend Integration (January 2026) + +### Frictions Encountered + +#### 1. Virtual Document Support (Resolved) + +**Issue**: ty (as integrated at 0.0.11) required files to exist on disk before it could provide diagnostics, completion, and other features. Pyright and Pyrefly work with "virtual documents" opened via `didOpen` without requiring the file to exist on disk. + +**Original workaround**: The `requires_file_on_disk()` flag was added to the `LSPBackend` protocol so `Session.create()`/`update_code()` could mirror the session code to disk for ty. + +**Resolution (August 2026)**: Bisecting PyPI releases showed ty supports virtual documents from 0.0.16 onward (diagnostics, completion, and rename all verified with no file on disk). `TyBackend.requires_file_on_disk()` now returns `False` and the package floor is `ty>=0.0.16`. The protocol flag remains for any future backend that needs it. + +#### 2. `workspace/didChangeConfiguration` Not Supported + +**Issue**: ty logs `Received notification workspace/didChangeConfiguration which does not have a handler.` The Session class sends this notification after initialization to apply workspace settings, unless the backend opts out. + +**Impact**: Runtime configuration changes via `didChangeConfiguration` don't work with ty. However, configuration written to `ty.toml` is respected. + +**Resolution**: The `LSPBackend` protocol gained `consumes_did_change_configuration()` (default `True`). `TyBackend` and `ZubanBackend` return `False`, so `Session.create()` skips the notification entirely for them. No functional loss — both read their config from disk. + +#### 3. Nested Configuration Structure + +**Issue**: ty uses nested TOML sections (`[environment]`, `[src]`, `[rules]`) unlike Pyrefly's flat structure. This required implementing recursive key conversion. + +**Solution**: Created `_convert_keys_to_kebab()` function in `lsp_types/ty/backend.py`: +```python +def _convert_keys_to_kebab(obj: t.Mapping[str, t.Any]) -> dict[str, t.Any]: + """Recursively convert dict keys from snake_case to kebab-case.""" + result: dict[str, t.Any] = {} + for key, value in obj.items(): + kebab_key = key.replace("_", "-") + if isinstance(value, dict): + result[kebab_key] = _convert_keys_to_kebab(value) + elif isinstance(value, list): + result[kebab_key] = [ + _convert_keys_to_kebab(v) if isinstance(v, dict) else v + for v in value + ] + else: + result[kebab_key] = value + return result +``` + +**Potential Enhancement**: Extract this utility to a shared module (`lsp_types/utils.py`) since Pyrefly also uses TOML with kebab-case keys (though currently with flat structure). + +#### 4. Hover Information Format Differences + +**Issue**: ty's hover response shows just the type (`str`) rather than `variable_name: type` format used by Pyright and Pyrefly. + +**Impact**: Test assertions checking for variable names in hover text fail for ty. + +**Workaround**: Added backend-specific assertion in `test_session_hover`: +```python +if backend_name != "ty": + assert "result" in hover_text +assert "str" in hover_text +``` + +#### 5. No CLI Flags for LSP Server + +**Issue**: Unlike Pyrefly which accepts `--verbose`, `--threads`, and `--indexing-mode` CLI flags, ty's `server` command accepts no configuration flags. + +**Impact**: Configuration reaches ty via `ty.toml` or via `initializationOptions` at +LSP initialization, not via the command line. See ty's KNOWN_LIMITATIONS entries 1 and 3. + +**Solution**: `create_process_launch_info()` simply returns `["ty", "server"]` without any conditional flag building. + +--- + +## Enhancement Opportunities + +### 1. Shared TOML Key Conversion Utility + +Both Pyrefly and ty use TOML with kebab-case keys but Python code uses snake_case. Consider creating: + +```python +# lsp_types/utils.py +def snake_to_kebab_recursive(obj: Mapping[str, Any]) -> dict[str, Any]: + """Recursively convert dict keys from snake_case to kebab-case.""" + # ... implementation +``` + +Then refactor both backends to use this shared utility. + +### 2. Backend Capability Flags (implemented) + +The `LSPBackend` protocol carries both as methods: +- `requires_file_on_disk() -> bool` — all four backends return `False` +- `consumes_did_change_configuration() -> bool` — `False` for ty and Zuban + +`Session.create()` branches on both. + +### 3. Common LSP Capabilities Base + +Create a helper function for shared capabilities: + +```python +def get_base_python_capabilities() -> types.ClientCapabilities: + """Common LSP capabilities for Python type checkers.""" + return { + "textDocument": { + "publishDiagnostics": {...}, + "hover": {...}, + "signatureHelp": {}, + } + } +``` + +Backends could extend this base instead of duplicating the boilerplate. + +### 4. Monaco Native LSP Client (`monaco.lsp`) + +Monaco Editor v0.55.0 (November 2025) introduced a built-in LSP client under `monaco.lsp` that could significantly simplify the playground. The current playground manually handles JSON-RPC, the LSP handshake, position conversion, diagnostics, and hover registration (~200 lines per backend). With `monaco.lsp`, this reduces to ~10-15 lines per backend. + +**What it provides:** +- `MonacoLspClient` — auto-registers 21 LSP features (completion, hover, diagnostics, semantic tokens, go-to-definition, rename, code actions, inlay hints, etc.) +- `WebSocketTransport` — connect via WebSocket +- `createTransportToWorker(worker)` — connect to a Web Worker +- `createTransportToIFrame(iframe)` — connect to an iframe + +**Example usage:** +```typescript +const worker = new Worker(PYRIGHT_WORKER_URL); +const transport = monaco.lsp.createTransportToWorker(worker); +new monaco.lsp.MonacoLspClient(transport); +// All features auto-registered, including semantic tokens +``` + +**What it would replace in the playground:** +- `BackendAdapter` interface (diagnostics, hover, updateCode) +- `typeConversions.ts` (LSP-to-Monaco position mapping) +- Per-backend implementations (~200 lines each) +- Dependencies: `vscode-languageserver-protocol`, `vscode-jsonrpc` + +**Current artifact check (2026-09-11):** installed `monaco-editor` is still +0.56.0. The public declaration still exposes only `constructor(transport)` and +no `dispose()`; the shipped LSP client hardcodes `rootUri: null`, omits +`initializationOptions`, and discards the feature disposable store. The two +migration blockers below remain. The playground's existing adapters were built +and smoke-tested in Chromium; the other issue statuses below are historical, +not freshly verified. + +**Blockers / caveats (re-verified against v0.56.0 — 2026-08-30):** + +The August 30 verification was documentary only: npm was unavailable on that +machine, so nothing was installed or executed in that earlier run. Evidence came from the published npm artifacts (`monaco.d.ts` +and the shipped `esm/external/monaco-lsp-client/out/index.js` for 0.55.1 and 0.56.0, +fetched via CDN), the `monaco-lsp-client/` source at `main`, and the issue trackers. +The decisive check: diffing the shipped LSP bundle 0.55.1 -> 0.56.0 yields 50 lines — +untrusted markdown, a stray `debugger;` removal, one import rename. The client is +functionally unchanged, so every blocker below still stands. + +- **API still unstable** — `monaco-lsp-client/README.md` (current): "This package is + in alpha stage and might contain many bugs." 0.56.0 added typings (0.55.1 shipped + no `.d.ts` at all) but made no stability declaration. +- **No custom initialization params — still the main blocker.** + `constructor(transport: IMessageTransport)` is the entire public API; the client + hardcodes `{ processId: null, capabilities, rootUri: null }` and never sends + `initializationOptions`. Identical in 0.55.1, 0.56.0 and `main`. +- **Registration is global in practice for our backends** (the original wording was + too broad). Providers register against + `toMonacoLanguageSelector(capability.documentSelector)`, which falls back to + `{ language: "*" }` only when that selector is missing or empty — so capabilities + registered dynamically via `client/registerCapability` *are* scoped per-language. + But options derived from static server capabilities carry no document selector, + and Pyright, Pyrefly, ty and Zuban all advertise statically. +- **No reconnection** — `WebSocketTransport`'s `socket.onclose` only flips transport + state to `closed`; nothing subscribes to that state, and `reconnect` appears zero + times in the shipped bundle. +- **No `dispose()`** (microsoft/monaco-editor#5340, open) — new since this note was + written, and disqualifying on its own here. `createFeatures()` builds a + `DisposableStore` that the constructor discards, and `MonacoLspClient` exposes no + `dispose()`, so provider registrations outlive the transport for the page lifetime. + `playground/src/main.ts` disposes the adapter on every backend switch, so each + switch would leak a full set of providers. +- **Other open bugs to watch** — microsoft/monaco-editor#5224 and #5239 (document + URIs are case-mangled during text-document synchronisation) and #5342 + (`textDocument/codeAction` drops the diagnostic `data`, `code` and `source` fields + servers need for quickfixes). + +**Recommendation:** Do not migrate on monaco-editor 0.56.0. Re-evaluate only once +both a `MonacoLspClient` constructor accepting initialization options and a proper +`dispose()` have landed; #5340 is a required lifecycle fix, not merely something to +monitor. Treat 0.57+ as a release horizon to re-check, not an expectation that it +will be usable. If both land, migrating the playground would eliminate significant +boilerplate and gain features (completion, semantic tokens, rename, etc.) for free. + +### 5. Backend Registry Pattern + +For easier discovery and testing: + +```python +_BACKENDS: dict[str, type[LSPBackend]] = {} + +def register_backend(name: str): + def decorator(cls): + _BACKENDS[name] = cls + return cls + return decorator + +@register_backend("ty") +class TyBackend(LSPBackend): + ... +``` + +--- + +## Summary + +The ty backend integration revealed that different LSP servers have varying requirements around file handling and configuration. Optional capability flags on backends have since shipped (`requires_file_on_disk()`, `consumes_did_change_configuration()`). The current abstraction works but could still benefit from: + +1. Shared utilities for common patterns (TOML conversion, base capabilities) +2. Better documentation of backend-specific behaviors + +The core `LSPBackend` protocol and `Session` class work well across all four backends (Pyright, Pyrefly, ty, Zuban) with minimal backend-specific handling needed in tests. + +--- + +Source: `lsp_types/pyrefly/KNOWN_LIMITATIONS.md` + +# Pyrefly backend: known limitations + +Verified with **Pyrefly 1.3.0 on 2026-09-11** using this library's LSP client. +Release notes are dated September 10; PyPI and GitHub publication occurred +September 11 UTC. [Release](https://github.com/facebook/pyrefly/releases/tag/1.3.0) + +## Completion resolution is an echo + +`completionItem/resolve` returns the submitted item unchanged, despite +advertising `completionProvider.resolveProvider: true`. A live probe resolved +a method completion both normally and with `detail`/`documentation` removed; +both responses exactly matched their respective inputs. + +Initial completions already include type details and documentation, so ordinary +completion remains useful. Calling `resolve_completion()` succeeds but does +not retrieve additional metadata. + +## Semantic-token legend is not advertised + +The initialize response omits `semanticTokensProvider` entirely, while +`textDocument/semanticTokens/full` still returns tokens. The backend therefore +supplies `PYREFLY_LEGEND` instead of discovering a legend from the server. + +Pyrefly 1.3.0 adds five string modifiers: `byteString`, `formatString`, +`rawString`, `stringPrefix`, and `templateString` (bits 11–15). This maintenance +updates both the fallback legend and canonical modifiers so normalization +preserves those bits. Token types are unchanged. See +[semantic-token documentation](../SEMANTIC_TOKENS.md). + +## Configuration and API boundaries + +The backend writes kebab-case TOML keys from top-level snake_case Python keys. +Nested error-code keys should use the upstream names (`bad-assignment`, etc.). +The typed schema covers common options; a plain `Session.create(options=...)` +dictionary can carry other upstream settings. + +Upstream CLI tools, TSP, and editor refactorings extend beyond the high-level +`Session` API. Their availability upstream does not imply a matching Session +method. Tensor-shape and DataFrame schema extensions remain experimental. + +## Previously resolved + +Rename previously failed for session files classified as external. It has +worked since 1.1.1 and remains covered by the regular rename integration test; +the former expected failure is gone. Virtual documents work without on-disk +mirroring. + +--- + +Source: `lsp_types/ty/KNOWN_LIMITATIONS.md` + +# ty Backend - Known Limitations + +This document describes known limitations and behavioral differences when using the ty backend compared to other LSP backends (Pyright, Pyrefly). + +## 1. `workspace/didChangeConfiguration` Not Supported + +**Limitation**: ty does not handle the `workspace/didChangeConfiguration` notification. + +**Behavior**: ty would log a warning if the notification were sent: +``` +WARN Received notification workspace/didChangeConfiguration which does not have a handler. +``` + +**Impact**: Runtime configuration changes via LSP notifications are ignored. However, configuration written to `ty.toml` before session creation is respected. + +**Resolution**: `TyBackend` implements `consumes_did_change_configuration() -> False`, +so `Session.create()` skips the notification entirely — the warning above no longer +appears in stderr. This remains the right call: the notification is genuinely +unhandled, so sending it produces a warning and no effect. + +Configuration does not have to live in `ty.toml`, however. ty also reads +`initializationOptions.configuration` at initialization, which accepts inline ty +config using kebab-case keys (`{"configuration": {"rules": {"unresolved-import": +"ignore"}}}` was verified to suppress that diagnostic with an empty `ty.toml`; +the snake_case spelling is silently ignored). Pass it through the public +`Session.create(..., initialize_params={"initializationOptions": {...}})` +parameter. This mirrors Zuban, which honors its own `initializationOptions` — +several backends in this repo accept LSP-time configuration, so "file-based only" +is the wrong mental model. This integration applies configuration when creating +the session. Recreate the session to change it reliably; its default null +configuration replies and lack of file watching do not provide settings updates. + +## 2. Hover Format Differs + +**Limitation**: ty's hover information shows only the type, not the variable name. + +**Behavior**: +- Pyright/Pyrefly hover: `result: str` or `(variable) result: str` +- ty hover: `str` + +**Impact**: Code that parses hover text expecting variable names will not find them with ty. + +## 3. No CLI Configuration Flags + +**Limitation**: The `ty server` command accepts no configuration flags. + +**Behavior**: Unlike Pyrefly which supports `--verbose`, `--threads`, etc., +`ty server --help` lists only `-h, --help`. + +**Impact**: None on functionality. Configuration reaches ty through two channels +rather than the command line: `ty.toml` (what `TyBackend.write_config()` writes), +and `initializationOptions` at LSP initialization — see limitation 1. The August +30 probe verified two keys with a real effect: `logLevel` (changes server log +verbosity) and `configuration` (applies inline ty config). Others are accepted +without a warning, but their effect was not confirmed and should not be assumed: +`diagnosticMode`, `disableLanguageServices`, `inlayHints`, +`completions`, `pythonExtension`, `workspaceTrust`, `experimental`, +`showSyntaxErrors`. The current documented setting for an explicit TOML path is +`configurationFile`; the older probe used `configuration-file` and did not +establish its behavior. See the [editor settings reference](https://docs.astral.sh/ty/reference/editor-settings/). + +ty warns loudly on unrecognized *top-level* initialization-option keys, so a typo +there is visible. That does not extend to nested keys: a misspelled rule name +inside `configuration` is silently ignored, as limitation 1 records for the +snake_case spelling of `unresolved-import`. + +## 4. Workspace Folders Warning + +**Limitation**: ty expects `workspaceFolders` in the initialization parameters. + +**Behavior**: ty logs a warning when workspaceFolders is not provided: +``` +WARN No workspace(s) were provided during initialization. Using the current working directory from the fallback system as a default workspace +``` + +**Impact**: ty falls back to using the working directory. This typically works correctly but may affect multi-root workspace scenarios. + +## 5. File Watching Not Supported by Client + +**Limitation**: This library's LSP client does not implement file watching, and ty +adjusts its warning to how much watching the client claims to support. + +**Behavior**: With the capabilities `TyBackend` currently advertises, ty logs: +``` +WARN Your LSP client doesn't support file watching: You may see stale results when files change outside the editor +``` +**Historical probe (0.0.70 and 0.0.75, August 30):** Advertising +`workspace.didChangeWatchedFiles.dynamicRegistration` narrowed the warning to +watching outside the project; also advertising `relativePatternSupport` removed +it. These capability variants were not rerun in the September maintenance. + +**Why the warning is left in place:** this library has no file watchers. The +process now answers server requests: its default handler returns null +configuration entries, acknowledges registration requests, and replies +`-32601` for unknown methods. That prevents protocol stalls, but acknowledging a +registration does not install a watcher. Advertising watching support would +still promise behavior this client does not provide. + +**Impact**: Files modified outside the LSP session (by external tools, a build +step, or a dependency install) may not be picked up until the session is +recreated. Sessions that only ever mutate the document through `update_code()` +are unaffected. + +## 6. Completion Item Resolution Not Supported + +**Limitation**: ty does not support the `completionItem/resolve` LSP request. + +**Behavior**: Calling `resolve_completion()` will raise an error: +``` +Unknown request: completionItem/resolve (-32601) +``` + +**Impact**: Clients must use the initial completion response; they cannot fetch +additional details through a separate resolution request. Basic completion works. + +--- + +## Previously Documented, Now Resolved + +- **Files must exist on disk** (documented for ty 0.0.11): Early ty versions returned empty diagnostics, limited completions, and failing renames for "virtual documents" opened via `didOpen` without a corresponding file on disk, so `TyBackend` mirrored the session code to disk (`requires_file_on_disk() -> True`). Bisecting PyPI releases shows virtual documents work from ty 0.0.16 onward (diagnostics on `didOpen` and `didChange`, completion, and rename all verified with no `.py` file on disk). The backend no longer writes files to disk, and the package floor is now `ty>=0.0.16`. + +--- + +## Version Information + +The September 11, 2026 maintenance used **ty 0.0.80**. Fresh temporary +LSP sessions confirmed: + +- `workspace/didChangeConfiguration` still logs an unhandled-notification + warning. An unresolved import remained after sending a suppressing setting + and editing the document; inline initialization with the correct kebab-case + rule suppressed it, while an unrelated assignment error remained. The + snake_case rule spelling did not suppress it. +- Hover over `result: str = "ok"` returned `Literal["ok"]`, without its name. +- `ty server --help` lists only `-h/--help`. +- The default initialization emits both missing-workspace and missing-watcher + warnings. Supplying a valid `workspaceFolders` removed the former while + retaining correct diagnostics. Session does not currently supply this field. +- Resolving a real completion item returned `-32601`. +- All diagnostic and hover probes used virtual documents, with no `.py` file on + disk. New analysis configuration serialized and loaded successfully; an + `allowed_unresolved_imports` entry suppressed its matching unresolved import + without suppressing the assignment control. + +Historical August 30 probes compared 0.0.70 and 0.0.75 and found identical behavior +for the six entries. That run also checked log-level changes, invalid workspace +paths and watcher-capability variants. Those details remain historical evidence, +not claims that every variant was repeated at 0.0.80. + +--- + +Source: `lsp_types/zuban/KNOWN_LIMITATIONS.md` + +# Zuban Backend - Known Limitations + +This document describes known limitations and behavioral differences when using the Zuban backend compared to other LSP backends (Pyright, Pyrefly, ty). + +## 1. Config Written to `pyproject.toml` + +**Behavior**: `ZubanBackend.write_config` adds or updates `[tool.zuban]` inside `pyproject.toml`, preserving any existing `[project]` metadata and other `[tool.*]` sections. It does not read, merge, or write `mypy.ini`, `.mypy.ini`, or `setup.cfg`. + +**Why `pyproject.toml` and not a dedicated file**: Unlike Pyright (`pyrightconfig.json`), Pyrefly (`pyrefly.toml`), and ty (`ty.toml`), Zuban has no dedicated config file in its native "default" mode. Selecting Zuban's PyRight-like mode is done via `pyproject.toml`'s `[tool.zuban]` table (some settings can also be passed as LSP `initializationOptions` — see entry 4). + +**Why `[tool.zuban]` and not `[tool.mypy]`**: Presence of `[tool.zuban]` puts Zuban into its recommended `default` mode (PyRight-like). `[tool.mypy]` would force the Mypy-compatible mode, which preserves different Mypy-compatible defaults. + +**Impact**: Re-invoking `write_config` replaces the previous `[tool.zuban]` table +in place; every other parsed value and section is preserved. + +**Formatting is preserved**: `write_config` is a format-preserving `tomlkit` +edit. Only the `[tool.zuban]` table is added or replaced; comments, inline +tables, arrays-of-tables, key ordering, whitespace and line endings elsewhere in +the file survive. Comments that `tomlkit` associates with an existing +`[tool.zuban]` go with it, since that table is replaced wholesale. This matters because `Session.create()` defaults to +`base_path=Path(".")`, so the naive call operates on the caller's real +`pyproject.toml`. (Until v0.22.1 this was a `tomllib` -> `tomli_w` round-trip +that preserved values but stripped every comment.) + +**The table is written even when `options` is empty**, and must be: its +*presence* is what selects Zuban's `default` mode. A project carrying +`[tool.mypy]` but no `[tool.zuban]` uses Mypy-compatible defaults, so +skipping the write would silently change its mode. Note this is observable only +through `zuban server` — the `zuban check` subcommand pins `default` mode +regardless of configuration, which makes the caveat easy to mis-verify. + +## 2. Unused `# type: ignore` Comments Not Reported + +**Limitation**: Zuban does not yet report unused `# type: ignore` comments (upstream limitation still present as of Zuban 0.9.3, per the [features documentation](https://docs.zubanls.com/en/latest/features.html)). + +**Impact**: Code that accumulates stale `# type: ignore` comments will not be flagged when using this backend. + +## 3. Value-Constrained `TypeVar` Function Bodies Not Type-Checked + +**Limitation**: Zuban does not type-check function bodies parameterized by a +*value-constrained* `TypeVar` — `TypeVar("T", str, bytes)` or the PEP 695 form +`[T: (str, bytes)]`. Upstream lists this under "Missing Features" in the +[features documentation](https://docs.zubanls.com/en/latest/features.html), still +present as of Zuban 0.9.3. + +**Not affected**: *upper-bounded* TypeVars — `TypeVar("T", bound=str)` — are +checked normally. The distinction is constraints (a tuple of alternatives) +versus a bound (a single upper limit); only the former disables body checking. + +**Impact**: Type errors inside value-constrained generic functions do not surface +via diagnostics. Historical August 30 probe at 0.9.2: `bad: int = "definitely not an int"` inside a +`TypeVar("T", str, bytes)` body (line 7) and inside a `[T: (str, bytes)]` body +(line 2) produced no diagnostic, while the identical statement in a plain +`def plain(x: str) -> str` in the same file (lines 13 and 8 respectively) was +reported as `[assignment] Incompatible types in assignment (expression has type +"str", variable has type "int")`. The same statement inside a +`TypeVar("T", bound=str)` body (line 7) was reported. + +## 4. No CLI Configuration Flags on `zuban server` + +**Limitation**: `zuban server` accepts no configuration flags. `zuban server --help` +lists only `-h, --help`; a configuration argument is rejected outright, e.g. +`zuban server --mode default` -> `error: unexpected argument '--mode' found`. +(By contrast `zuban check` exposes a large flag surface including `--mode`, +`--untyped-function-return-mode`, and `--python-executable`.) + +**Impact**: No functional impact on this backend — `ZubanBackend` configures Zuban +via `pyproject.toml`. Unlike the Pyrefly backend (which exposes `--verbose`, +`--threads`, `--indexing-mode` through `ProcessLaunchInfo`), +`ZubanBackend.create_process_launch_info` returns a fixed `["zuban", "server"]`. + +**Configuration is not exclusively file-based.** Zuban also reads LSP +`initializationOptions` sent with `initialize`. Upstream's changelog adds these in +0.8.1 (`typeCheckingMode`, `disableLanguageServices`, `diagnosticMode`, +`pythonExecutable`) and 0.9.1 (`inlayHintMode`). `ZubanBackend` sends none, but +callers can supply them via `Session.create`'s public `initialize_params`: + + await Session.create( + ZubanBackend(), + initialize_params={"initializationOptions": {"diagnosticMode": "workspace"}}, + ) + +Reconfirmed at 0.9.3 by diffing the initialize response: +`diagnosticMode="workspace"` flips `diagnosticProvider.workspaceDiagnostics` to +`true`; `typeCheckingMode="off"` drops `diagnosticProvider` entirely; +`disableLanguageServices=true` drops `hoverProvider` and removes +`completionProvider`. The historical 0.9.2 probe also found that an unknown key +left the response identical to baseline; that control was not repeated at 0.9.3. + +**Caveat**: for the two options probed this way, the change was to what Zuban +*advertises*, not to what it *answers*. With `typeCheckingMode="off"`, +`textDocument/diagnostic` still returned the same diagnostic; with +`disableLanguageServices=true`, hover and completion still returned results. A +client that gates requests on advertised capabilities sees a behavior change; +`Session`, which sends requests unconditionally, does not. The remaining options +(`diagnosticMode`, `pythonExecutable`, `inlayHintMode`) were not probed this way. + +--- + +## Version Information + +The September 11, 2026 maintenance used **Zuban 0.9.3**, released September 2. +Fresh temporary LSP sessions reporting that version confirmed: + +- The typed `mode="auto"` setting serialized and loaded successfully. +- One virtual document contained assignment errors in four functions. Errors + were reported in the upper-bounded TypeVar and plain functions, but not in + either value-constrained TypeVar syntax. Its unnecessary `# type: ignore` + was not reported either. +- `zuban server --help` lists only `-h/--help`. +- `diagnosticMode="workspace"` changed advertised workspace diagnostics to true; + `typeCheckingMode="off"` removed the diagnostic provider; + `disableLanguageServices=true` removed hover and completion providers. + Nevertheless, direct diagnostic, hover and completion requests still returned + results with the corresponding advertised services disabled. +- The 18 config tests, including preservation of comments, other sections, + inline tables and CRLF, passed. This is library behavior verified separately + from upstream capability claims. + +The detailed line-numbered 0.9.2 examples above are historical August 30 evidence. +The newer combined probe reached the same constrained-versus-bounded conclusion. +No fresh behavioral probe was made for `pythonExecutable` or `inlayHintMode`. + +Upstream continues to list unused-ignore reporting and constrained generic +bodies under [missing features](https://docs.zubanls.com/en/latest/features.html). +The [0.9.3 changelog](https://docs.zubanls.com/en/latest/changelog.html) records +deterministic file processing and a fix for quadratic literal handling. diff --git a/docs/internal/research/landscape.md b/docs/internal/research/landscape.md new file mode 100644 index 0000000..cf366b5 --- /dev/null +++ b/docs/internal/research/landscape.md @@ -0,0 +1,150 @@ +# Python language servers: a field guide + +Research snapshot: **2026-09-11**. These are upstream capabilities and design +choices, not a claim that every feature is exposed by this library's `Session` +API. See [feature verification](../FEATURE_VERIFICATION.md) for local evidence. +The README's characters are playful descriptions of priorities, not performance +ratings or predictions of reliability. + +## Release snapshot + +- **Pyright 1.1.414**, September 9: typing fixes, type-equality optimization, + and publication of `pyright-typeserver` to npm. + [Release notes](https://github.com/microsoft/pyright/releases/tag/1.1.414). +- **basedpyright 1.40.1**, September 10: merges Pyright 1.1.414 and improves + multiline builtin docstring display. + [Release notes](https://github.com/DetachHead/basedpyright/releases/tag/v1.40.1). +- **ty 0.0.80**, September 9: fixes hangs during bursts of inlay-hint requests, + gives autofixes more descriptive names, and improves typing and memory usage. + Since the previous local 0.0.75 snapshot, 0.0.76 also added a preview rule for + missing direct dependencies and improved PEP 723 script environments. + [0.0.80](https://github.com/astral-sh/ty/releases/tag/0.0.80), + [0.0.76](https://github.com/astral-sh/ty/releases/tag/0.0.76). +- **Zuban 0.9.3**, September 2: deterministic file processing and a fix for + quadratic behavior involving literals. + [Release](https://github.com/zubanls/zuban/releases/tag/v0.9.3), + [changelog](https://docs.zubanls.com/en/latest/changelog.html). + +The versions above were checked against live package registries and explicit +release tags. Cached GitHub `/releases/latest` pages returned older versions for +Zuban and basedpyright during this run; search-result dates alone are insufficient. + +## Pyright — the veteran + +Pyright remains a broad, configurable type checker. Its execution environments +let different project subdirectories target different Python versions, platforms, +and import paths. Its language server includes call hierarchy, navigation, +rename, completions and stub generation. These make a useful established +reference without implying that it wins every comparison. +[Pyright features at 1.1.414](https://github.com/microsoft/pyright/blob/1.1.414/docs/features.md). + +**Tradeoff:** Pyright, Pylance and basedpyright are different products. In +particular, semantic highlighting, inlay hints and import quick fixes are among +the Pylance features independently implemented by basedpyright; a successful +basedpyright test does not establish vanilla Pyright support. +[basedpyright's LSP additions](https://docs.basedpyright.com/latest/benefits-over-pyright/pylance-features/). + +## basedpyright — the guardian with adjustable armor + +This fork adds stricter defaults and finer diagnostic controls to the Pyright +family. Its recommended mode enables all diagnostic rules, uses warnings for +some checks, and checks all platforms by default. Expect adoption to surface +more issues unless the project config relaxes those defaults. +[Defaults](https://docs.basedpyright.com/latest/benefits-over-pyright/better-defaults/). + +Its baseline records existing errors so new work can face stricter checks +without first fixing the whole codebase. Baselines work in both CLI and LSP; +matching is imperfect when code moves. Editor additions include enum and +non-string literal completions, automatic `@override` insertion, and configurable +hint severity. These are concrete customization strengths, not just parity. +[Baseline](https://docs.basedpyright.com/latest/benefits-over-pyright/baseline/), +[editor improvements](https://docs.basedpyright.com/latest/benefits-over-pyright/language-server-improvements/). + +## ty — the swift scout + +ty's architecture prioritizes fine-grained incremental analysis: edits should +invalidate only the computations that depend on them. Its diagnostics emphasize +context and explanations. That makes quick feedback and understandable errors +a more useful identity than simply “written in Rust.” +[Astral's design and benchmark discussion](https://astral.sh/blog/ty). + +The type system also has its own character: it permits variable redeclarations, +uses intersections for narrowing, and reasons about reachability using inferred +types. Its `hasattr` analysis accounts for subclasses adding an attribute. +Explicit `ty_extensions.Intersection` annotations are ty-specific and currently +available only during type checking; the internal inference benefits do not +require adopting those annotations. +[Type-system examples](https://docs.astral.sh/ty/features/type-system/). + +Configuration offers per-file rule and analysis overrides, selective treatment +of third-party imports, and opt-in stricter equality and generic narrowing. +The last two default to false: extra theoretical precision can produce types +that are less convenient for everyday code. +[Configuration reference](https://docs.astral.sh/ty/reference/configuration/). + +**Tradeoff:** this integration has its own documented completion-resolution, +hover-format and file-watching constraints. A missing client feature is not a +general verdict on ty's editor support. Consult the +[locally verified limitations](../../../lsp_types/ty/KNOWN_LIMITATIONS.md). + +## Zuban — the bridge builder + +Zuban's distinctive combination is Mypy migration and help with untyped code. +Its Mypy mode preserves familiar configuration and behavior, while native mode +checks untyped functions and infers their returns. The LSP's automatic mode +selection is influenced by project configuration; this library writes +`[tool.zuban]`, which selects native mode unless explicitly overridden. +[Modes and configuration](https://docs.zubanls.com/en/latest/usage.html#modes). + +Since 0.8.0, editor heuristics follow call sites to improve completion, hover, +navigation and signatures even where the checker still sees `Any`. This is an +explicit distinction between editor assistance and type-checking semantics. +Earlier releases added Django model support, notebook support and completion +documentation resolution. +[Changelog](https://docs.zubanls.com/en/latest/changelog.html). + +**Tradeoff:** upstream still documents unchecked bodies of functions with +value-constrained TypeVars and missing unused-ignore diagnostics. General Mypy +plugin compatibility is not planned; targeted library support is different. +Zuban currently uses one CPU core and targets low memory consumption. Call/type +hierarchy and file-rename import updates are absent from its documented LSP +capabilities. These are more actionable limits than calling it “less capable.” +[Capabilities and missing features](https://docs.zubanls.com/en/latest/features.html). + +## Reading performance claims fairly + +Astral's December 2025 announcement reports separate cold CLI and incremental +LSP measurements: Home Assistant checking without cache, and diagnostic +recomputation after an edit in PyTorch on an M4. Its striking multipliers describe +those workloads and historical versions; they are not current universal rankings. +[Benchmark context](https://astral.sh/blog/ty). + +Zuban's homepage claims substantial speedups over Mypy and lower CPU/memory than +ty and Pyrefly. These are upstream claims, not measurements made by this project. +Likewise, passing over 95% of the *relevant Mypy tests* is a specific compatibility +measure, not a score for LSP quality or all Python programs. +[Zuban's overview](https://docs.zubanls.com/en/latest/). + +A useful local comparison must fix versions, interpreter, config and workload; +separate process startup, cold project checking and warm edits; check equivalent +diagnostic work; and report repeated measurements with machine details. The +integration suite establishes functionality, not a speed leaderboard. No README +character implies “fastest,” “slow,” or “breaks easily.” + +## Maintenance findings + +The upstream review led to these maintenance changes: + +- ty's typed config now includes analysis import controls and strictness options, + per-file analysis overrides and script exclusions. Its output-format choices + and warning-exit default were refreshed against the + [reference](https://docs.astral.sh/ty/reference/configuration/). +- Zuban's typed mode choices now include `auto`, added in 0.9.0. Fresh live probes + confirmed that its constrained-TypeVar and unused-ignore limitations remain. + [Changelog](https://docs.zubanls.com/en/latest/changelog.html). +- Vanilla Pyright and basedpyright now have independent test runs. Editor + extension features are no longer credited to vanilla Pyright through a shared + backend name. +- TSP is separate from this library's LSP integration. Pyright's newest release + also publishes a type server, so that category is not exclusive to Pyrefly. + [Pyright 1.1.414](https://github.com/microsoft/pyright/releases/tag/1.1.414). diff --git a/docs/internal/research/pyrefly.md b/docs/internal/research/pyrefly.md new file mode 100644 index 0000000..2b7c6ec --- /dev/null +++ b/docs/internal/research/pyrefly.md @@ -0,0 +1,91 @@ +# Pyrefly: the artificer with a growing toolkit + +Research checked **2026-09-11**. Character metaphor describes specialization, +not a performance ranking or a claim about reliability. + +## Release and direction + +**1.3.0** is the current stable release. Its notes say September 10; GitHub +published it September 11 at 00:42 UTC, following the first PyPI wheel at +00:41 UTC. Cached search results still showed 1.2.0, so the live +[PyPI metadata](https://pypi.org/pypi/pyrefly/json) and +[GitHub release metadata](https://api.github.com/repos/facebook/pyrefly/releases/latest) +were checked directly. + +The distinctive direction is broader code understanding. Version 1.3 adds +literal-regex and `mock.patch` validation, same-file Django reverse relations, +SQLAlchemy update checks, and PyTorch registered attributes. Its LSP adds Change +Signature, wider workspace symbol search, and cross-file hierarchy/reference +support without opening each file. Tensor-shape checking for JAX/NumPy/PyTorch +and DataFrame schema checking remain **experimental**; the previous +`@shaped_array` API was removed. These are upstream release claims, not all +features independently tested here. [1.3.0 release notes](https://github.com/facebook/pyrefly/releases/tag/1.3.0) + +## A small, reproducible difference + +This CLI probe was run with Pyrefly **1.3.0** and Pyright **1.1.414**, using +temporary files and explicit configurations: + +```python +import re +from unittest.mock import patch +re.compile("[") +patch("math.nonexistent_symbol") +``` + +With the following `pyrefly.toml`, `pyrefly check probe.py` reported both an +invalid regex and a missing patch target: + +```toml +[errors] +regex = "error" +missing-attribute-patch-target = "error" +``` + +`pyright --project . probe.py`, with `{"typeCheckingMode": "strict"}` in +`pyrightconfig.json`, reported **0 errors and 0 warnings**. This demonstrates +two particular checks, not comparative correctness across Python. An +unconfigured Pyrefly snippet reported no errors: enable the intended checks +explicitly when reproducing the comparison. These results concern CLI +diagnostics; the same fixture was not tested through LSP in this research. + +## Keep the interfaces separate + +The installed `pyrefly --help` confirms `infer`, `coverage`, `suppress`, +`stubgen`, configuration migration via `init`, and `tsp` alongside `lsp`. +This library starts **`pyrefly lsp`**; it does not wrap those CLI commands or +TSP. Upstream advertises richer editor refactorings and navigation than this +library's high-level `Session` methods currently expose; low-level LSP access +is a separate path. [IDE feature documentation](https://pyrefly.org/en/docs/IDE-features/) + +The release's striking TSP speedups measure repeated requests in a captured +Pylance session. They are neither Pyrefly-versus-Pyright timings nor this +library's LSP latency. Do not turn them into README speed multipliers. +[Performance notes](https://github.com/facebook/pyrefly/releases/tag/1.3.0) + +## Upgrade findings for this integration + +- This maintenance extends the hardcoded semantic-token and canonical modifier lists with + `byteString`, `formatString`, `rawString`, `stringPrefix`, `templateString`; + the previous run already detected these in 1.3 prereleases. A fresh 1.3.0 + LSP probe confirmed the missing advertised legend and completion-resolution + echo, including when detail and documentation were stripped first. +- The typed configuration now includes current severity strings in + `ErrorConfig`, `preset`, `check_unannotated_defs`, `infer_return_types`, + `replace_untyped_imports_with_any`, and useful baseline settings. + `untyped_def_behavior` remains for compatibility but is marked deprecated. The tagged + implementation explicitly names its replacements. + [1.3.0 base configuration](https://github.com/facebook/pyrefly/blob/1.3.0/crates/pyrefly_config/src/base.rs), + [baseline configuration](https://github.com/facebook/pyrefly/blob/1.3.0/crates/pyrefly_config/src/config.rs), + [severity definitions](https://github.com/facebook/pyrefly/blob/1.3.0/crates/pyrefly_config/src/error_kind.rs) +- The historical `assets/lsps/pyrefly-guide.md` advice that config files + are unreliable has been replaced. The supported files are `pyrefly.toml` and + `[tool.pyrefly]` in `pyproject.toml`; CLI JSON output also makes its regex + stderr parser unnecessary. More inference can analyze more dependency + modules, so depth has a cost. [Configuration documentation](https://pyrefly.org/en/docs/configuration/) + +## Character choice + +Pyrefly's artificer carries a toolbelt and glowing firefly: a metaphor for broader +code analysis and specialized tools, with experimental features clearly marked. +It is not a score for speed, robustness or overall correctness. diff --git a/docs/research/landscape.md b/docs/research/landscape.md index 09cb094..7cb0896 100644 --- a/docs/research/landscape.md +++ b/docs/research/landscape.md @@ -1,150 +1,77 @@ -# Python language servers: a field guide - -Research snapshot: **2026-09-11**. These are upstream capabilities and design -choices, not a claim that every feature is exposed by this library's `Session` -API. See [feature verification](../FEATURE_VERIFICATION.md) for local evidence. -The README's characters are playful descriptions of priorities, not performance -ratings or predictions of reliability. - -## Release snapshot - -- **Pyright 1.1.414**, September 9: typing fixes, type-equality optimization, - and publication of `pyright-typeserver` to npm. - [Release notes](https://github.com/microsoft/pyright/releases/tag/1.1.414). -- **basedpyright 1.40.1**, September 10: merges Pyright 1.1.414 and improves - multiline builtin docstring display. - [Release notes](https://github.com/DetachHead/basedpyright/releases/tag/v1.40.1). -- **ty 0.0.80**, September 9: fixes hangs during bursts of inlay-hint requests, - gives autofixes more descriptive names, and improves typing and memory usage. - Since the previous local 0.0.75 snapshot, 0.0.76 also added a preview rule for - missing direct dependencies and improved PEP 723 script environments. - [0.0.80](https://github.com/astral-sh/ty/releases/tag/0.0.80), - [0.0.76](https://github.com/astral-sh/ty/releases/tag/0.0.76). -- **Zuban 0.9.3**, September 2: deterministic file processing and a fix for - quadratic behavior involving literals. - [Release](https://github.com/zubanls/zuban/releases/tag/v0.9.3), - [changelog](https://docs.zubanls.com/en/latest/changelog.html). - -The versions above were checked against live package registries and explicit -release tags. Cached GitHub `/releases/latest` pages returned older versions for -Zuban and basedpyright during this run; search-result dates alone are insufficient. - -## Pyright — the veteran - -Pyright remains a broad, configurable type checker. Its execution environments -let different project subdirectories target different Python versions, platforms, -and import paths. Its language server includes call hierarchy, navigation, -rename, completions and stub generation. These make a useful established -reference without implying that it wins every comparison. -[Pyright features at 1.1.414](https://github.com/microsoft/pyright/blob/1.1.414/docs/features.md). - -**Tradeoff:** Pyright, Pylance and basedpyright are different products. In -particular, semantic highlighting, inlay hints and import quick fixes are among -the Pylance features independently implemented by basedpyright; a successful -basedpyright test does not establish vanilla Pyright support. -[basedpyright's LSP additions](https://docs.basedpyright.com/latest/benefits-over-pyright/pylance-features/). - -## basedpyright — the guardian with adjustable armor - -This fork adds stricter defaults and finer diagnostic controls to the Pyright -family. Its recommended mode enables all diagnostic rules, uses warnings for -some checks, and checks all platforms by default. Expect adoption to surface -more issues unless the project config relaxes those defaults. -[Defaults](https://docs.basedpyright.com/latest/benefits-over-pyright/better-defaults/). - -Its baseline records existing errors so new work can face stricter checks -without first fixing the whole codebase. Baselines work in both CLI and LSP; -matching is imperfect when code moves. Editor additions include enum and -non-string literal completions, automatic `@override` insertion, and configurable -hint severity. These are concrete customization strengths, not just parity. -[Baseline](https://docs.basedpyright.com/latest/benefits-over-pyright/baseline/), -[editor improvements](https://docs.basedpyright.com/latest/benefits-over-pyright/language-server-improvements/). - -## ty — the swift scout - -ty's architecture prioritizes fine-grained incremental analysis: edits should -invalidate only the computations that depend on them. Its diagnostics emphasize -context and explanations. That makes quick feedback and understandable errors -a more useful identity than simply “written in Rust.” -[Astral's design and benchmark discussion](https://astral.sh/blog/ty). - -The type system also has its own character: it permits variable redeclarations, -uses intersections for narrowing, and reasons about reachability using inferred -types. Its `hasattr` analysis accounts for subclasses adding an attribute. -Explicit `ty_extensions.Intersection` annotations are ty-specific and currently -available only during type checking; the internal inference benefits do not -require adopting those annotations. -[Type-system examples](https://docs.astral.sh/ty/features/type-system/). - -Configuration offers per-file rule and analysis overrides, selective treatment -of third-party imports, and opt-in stricter equality and generic narrowing. -The last two default to false: extra theoretical precision can produce types -that are less convenient for everyday code. +# Choose your language server + +Each member of the party brings a different strength. All use the same +[`Session` API](../USAGE.md). + +| Backend | Reach for it when you want… | +|---|---| +| 🛡️ Pyright | Broad typing support and separate environments within one project | +| ⚔️ basedpyright | Stricter diagnostics, baselines and richer editor features | +| 🔧 [Pyrefly](pyrefly.md) | Framework-aware analysis and specialized checks | +| 🏹 ty | Incremental analysis and explanatory diagnostics | +| 🤝 Zuban | Mypy migration and editor assistance for untyped code | + +## Pyright — the sentinel + +Pyright combines broad typing support with configurable execution environments. +Different project directories can target different Python versions, platforms +and import paths. Its language server provides completion, navigation, rename +and call hierarchy. [Pyright features](https://github.com/microsoft/pyright/blob/1.1.414/docs/features.md). + +Microsoft Pyright does not provide semantic tokens. Choose **basedpyright** for +semantic highlighting and additional editor features. Both work with +`PyrightBackend`; install the distribution you want to run. + +## basedpyright — the guardian + +basedpyright adds stricter defaults, finer diagnostic controls and a baseline +for existing errors. Use the baseline to enforce checks on new work while +addressing older errors gradually. Its recommended mode enables all diagnostic +rules, assigns warnings to some checks and checks all platforms by default. +[Defaults](https://docs.basedpyright.com/latest/benefits-over-pyright/better-defaults/), +[baselines](https://docs.basedpyright.com/latest/benefits-over-pyright/baseline/). + +Editor additions include semantic highlighting, inlay hints, import quick fixes, +enum and literal completions, and automatic `@override` insertion. +[Editor features](https://docs.basedpyright.com/latest/benefits-over-pyright/pylance-features/), +[completion improvements](https://docs.basedpyright.com/latest/benefits-over-pyright/language-server-improvements/). + +## ty — the scout + +ty uses fine-grained incremental analysis to recompute work affected by an edit. +Its diagnostics explain errors with context, and its type system uses +intersections and reachability analysis to narrow types. +[Astral's design](https://astral.sh/blog/ty), +[type system](https://docs.astral.sh/ty/features/type-system/). + +Configuration supports per-file overrides and controls for third-party import +analysis. Stricter equality and generic narrowing are opt-in settings. [Configuration reference](https://docs.astral.sh/ty/reference/configuration/). -**Tradeoff:** this integration has its own documented completion-resolution, -hover-format and file-watching constraints. A missing client feature is not a -general verdict on ty's editor support. Consult the -[locally verified limitations](../../lsp_types/ty/KNOWN_LIMITATIONS.md). - -## Zuban — the bridge builder - -Zuban's distinctive combination is Mypy migration and help with untyped code. -Its Mypy mode preserves familiar configuration and behavior, while native mode -checks untyped functions and infers their returns. The LSP's automatic mode -selection is influenced by project configuration; this library writes -`[tool.zuban]`, which selects native mode unless explicitly overridden. -[Modes and configuration](https://docs.zubanls.com/en/latest/usage.html#modes). - -Since 0.8.0, editor heuristics follow call sites to improve completion, hover, -navigation and signatures even where the checker still sees `Any`. This is an -explicit distinction between editor assistance and type-checking semantics. -Earlier releases added Django model support, notebook support and completion -documentation resolution. -[Changelog](https://docs.zubanls.com/en/latest/changelog.html). - -**Tradeoff:** upstream still documents unchecked bodies of functions with -value-constrained TypeVars and missing unused-ignore diagnostics. General Mypy -plugin compatibility is not planned; targeted library support is different. -Zuban currently uses one CPU core and targets low memory consumption. Call/type -hierarchy and file-rename import updates are absent from its documented LSP -capabilities. These are more actionable limits than calling it “less capable.” -[Capabilities and missing features](https://docs.zubanls.com/en/latest/features.html). - -## Reading performance claims fairly - -Astral's December 2025 announcement reports separate cold CLI and incremental -LSP measurements: Home Assistant checking without cache, and diagnostic -recomputation after an edit in PyTorch on an M4. Its striking multipliers describe -those workloads and historical versions; they are not current universal rankings. -[Benchmark context](https://astral.sh/blog/ty). - -Zuban's homepage claims substantial speedups over Mypy and lower CPU/memory than -ty and Pyrefly. These are upstream claims, not measurements made by this project. -Likewise, passing over 95% of the *relevant Mypy tests* is a specific compatibility -measure, not a score for LSP quality or all Python programs. -[Zuban's overview](https://docs.zubanls.com/en/latest/). - -A useful local comparison must fix versions, interpreter, config and workload; -separate process startup, cold project checking and warm edits; check equivalent -diagnostic work; and report repeated measurements with machine details. The -integration suite establishes functionality, not a speed leaderboard. No README -character implies “fastest,” “slow,” or “breaks easily.” - -## Maintenance findings - -The upstream review led to these maintenance changes: - -- ty's typed config now includes analysis import controls and strictness options, - per-file analysis overrides and script exclusions. Its output-format choices - and warning-exit default were refreshed against the - [reference](https://docs.astral.sh/ty/reference/configuration/). -- Zuban's typed mode choices now include `auto`, added in 0.9.0. Fresh live probes - confirmed that its constrained-TypeVar and unused-ignore limitations remain. - [Changelog](https://docs.zubanls.com/en/latest/changelog.html). -- Vanilla Pyright and basedpyright now have independent test runs. Editor - extension features are no longer credited to vanilla Pyright through a shared - backend name. -- TSP is separate from this library's LSP integration. Pyright's newest release - also publishes a type server, so that category is not exclusive to Pyrefly. - [Pyright 1.1.414](https://github.com/microsoft/pyright/releases/tag/1.1.414). +In `Session`, ty hover returns the type without the variable name. Completion +works; `resolve_completion()` raises `-32601`. Recreate the session after +configuration changes or external file edits. +[Integration details](../../lsp_types/ty/KNOWN_LIMITATIONS.md). + +## Zuban — the diplomat + +Zuban bridges Mypy workflows and editor assistance for untyped code. Mypy mode +preserves familiar defaults; native mode checks untyped functions and infers +return types. `ZubanBackend` writes `[tool.zuban]`, selecting native mode unless +you override it. [Modes and configuration](https://docs.zubanls.com/en/latest/usage.html#modes). + +Its editor heuristics use call sites to improve completion, hover, navigation +and signatures in untyped code. It also supports Django models and notebooks. +[Zuban features](https://docs.zubanls.com/en/latest/features.html). + +Zuban does not check value-constrained `TypeVar` function bodies or report unused +`# type: ignore` comments. Upper-bounded `TypeVar` bodies are checked. General +Mypy plugin compatibility is not supported. +[Integration details](../../lsp_types/zuban/KNOWN_LIMITATIONS.md). + +## Using these features + +`Session` provides diagnostics, hover, completion, signature help, rename and +semantic tokens where supported. Use the typed +[`LSPProcess` API](../USAGE.md#low-level-stdio) for additional protocol requests +such as navigation and call hierarchy. diff --git a/docs/research/pyrefly.md b/docs/research/pyrefly.md index 2b7c6ec..97f4cbf 100644 --- a/docs/research/pyrefly.md +++ b/docs/research/pyrefly.md @@ -1,91 +1,60 @@ -# Pyrefly: the artificer with a growing toolkit +# Pyrefly — the artificer 🔧 -Research checked **2026-09-11**. Character metaphor describes specialization, -not a performance ranking or a claim about reliability. +Pyrefly brings specialized checks and framework knowledge to Python analysis. +It understands same-file Django reverse relations, SQLAlchemy updates and +PyTorch registered attributes, alongside configurable validation for literal regexes and +`mock.patch` targets. [Feature release](https://github.com/facebook/pyrefly/releases/tag/1.3.0). -## Release and direction +## Enable specialized checks -**1.3.0** is the current stable release. Its notes say September 10; GitHub -published it September 11 at 00:42 UTC, following the first PyPI wheel at -00:41 UTC. Cached search results still showed 1.2.0, so the live -[PyPI metadata](https://pypi.org/pypi/pyrefly/json) and -[GitHub release metadata](https://api.github.com/repos/facebook/pyrefly/releases/latest) -were checked directly. +Enable these diagnostics in `pyrefly.toml`: -The distinctive direction is broader code understanding. Version 1.3 adds -literal-regex and `mock.patch` validation, same-file Django reverse relations, -SQLAlchemy update checks, and PyTorch registered attributes. Its LSP adds Change -Signature, wider workspace symbol search, and cross-file hierarchy/reference -support without opening each file. Tensor-shape checking for JAX/NumPy/PyTorch -and DataFrame schema checking remain **experimental**; the previous -`@shaped_array` API was removed. These are upstream release claims, not all -features independently tested here. [1.3.0 release notes](https://github.com/facebook/pyrefly/releases/tag/1.3.0) - -## A small, reproducible difference +```toml +[errors] +regex = "error" +missing-attribute-patch-target = "error" +``` -This CLI probe was run with Pyrefly **1.3.0** and Pyright **1.1.414**, using -temporary files and explicit configurations: +Run `uv run pyrefly check probe.py` on this file to report an invalid regex +and a missing patch target: ```python import re from unittest.mock import patch + re.compile("[") patch("math.nonexistent_symbol") ``` -With the following `pyrefly.toml`, `pyrefly check probe.py` reported both an -invalid regex and a missing patch target: - -```toml -[errors] -regex = "error" -missing-attribute-patch-target = "error" -``` - -`pyright --project . probe.py`, with `{"typeCheckingMode": "strict"}` in -`pyrightconfig.json`, reported **0 errors and 0 warnings**. This demonstrates -two particular checks, not comparative correctness across Python. An -unconfigured Pyrefly snippet reported no errors: enable the intended checks -explicitly when reproducing the comparison. These results concern CLI -diagnostics; the same fixture was not tested through LSP in this research. +To apply the same settings in a session, pass +`options={"errors": {"regex": "error", "missing-attribute-patch-target": "error"}}` +to `Session.create()`. Pyrefly accepts `pyrefly.toml` and `[tool.pyrefly]` in +`pyproject.toml`; this backend writes `pyrefly.toml`. +[Configuration reference](https://pyrefly.org/en/docs/configuration/). -## Keep the interfaces separate +## Editor tools -The installed `pyrefly --help` confirms `infer`, `coverage`, `suppress`, -`stubgen`, configuration migration via `init`, and `tsp` alongside `lsp`. -This library starts **`pyrefly lsp`**; it does not wrap those CLI commands or -TSP. Upstream advertises richer editor refactorings and navigation than this -library's high-level `Session` methods currently expose; low-level LSP access -is a separate path. [IDE feature documentation](https://pyrefly.org/en/docs/IDE-features/) +Pyrefly provides diagnostics, hover, completion, signature help, rename and +semantic tokens through [`Session`](../USAGE.md). Initial completion items +include type details and documentation; `resolve_completion()` returns the +item unchanged. -The release's striking TSP speedups measure repeated requests in a captured -Pylance session. They are neither Pyrefly-versus-Pyright timings nor this -library's LSP latency. Do not turn them into README speed multipliers. -[Performance notes](https://github.com/facebook/pyrefly/releases/tag/1.3.0) +The library supplies Pyrefly's semantic-token legend automatically, including +its string modifiers. Use `get_semantic_tokens(normalize=True)` for the shared +[editor legend](../SEMANTIC_TOKENS.md). -## Upgrade findings for this integration +Use the [typed LSP API](../USAGE.md#low-level-stdio) for workspace symbol search, +call hierarchy and references. Pyrefly’s editor integration also provides +Change Signature. +[IDE features](https://pyrefly.org/en/docs/IDE-features/). -- This maintenance extends the hardcoded semantic-token and canonical modifier lists with - `byteString`, `formatString`, `rawString`, `stringPrefix`, `templateString`; - the previous run already detected these in 1.3 prereleases. A fresh 1.3.0 - LSP probe confirmed the missing advertised legend and completion-resolution - echo, including when detail and documentation were stripped first. -- The typed configuration now includes current severity strings in - `ErrorConfig`, `preset`, `check_unannotated_defs`, `infer_return_types`, - `replace_untyped_imports_with_any`, and useful baseline settings. - `untyped_def_behavior` remains for compatibility but is marked deprecated. The tagged - implementation explicitly names its replacements. - [1.3.0 base configuration](https://github.com/facebook/pyrefly/blob/1.3.0/crates/pyrefly_config/src/base.rs), - [baseline configuration](https://github.com/facebook/pyrefly/blob/1.3.0/crates/pyrefly_config/src/config.rs), - [severity definitions](https://github.com/facebook/pyrefly/blob/1.3.0/crates/pyrefly_config/src/error_kind.rs) -- The historical `assets/lsps/pyrefly-guide.md` advice that config files - are unreliable has been replaced. The supported files are `pyrefly.toml` and - `[tool.pyrefly]` in `pyproject.toml`; CLI JSON output also makes its regex - stderr parser unnecessary. More inference can analyze more dependency - modules, so depth has a cost. [Configuration documentation](https://pyrefly.org/en/docs/configuration/) +## Experimental tools 🧪 -## Character choice +Tensor-shape analysis for JAX, NumPy and PyTorch, and DataFrame schema checking +are experimental. Their APIs can change. The former `@shaped_array` API has +been removed. [Release details](https://github.com/facebook/pyrefly/releases/tag/1.3.0). -Pyrefly's artificer carries a toolbelt and glowing firefly: a metaphor for broader -code analysis and specialized tools, with experimental features clearly marked. -It is not a score for speed, robustness or overall correctness. +This backend runs `pyrefly lsp`. Pyrefly's CLI commands (`infer`, `coverage`, +`suppress`, `stubgen`, `init`) and TSP server are separate tools. +See [backend details](../../lsp_types/pyrefly/KNOWN_LIMITATIONS.md) for configuration +and completion behavior, or [compare the party](landscape.md) to choose a backend. diff --git a/lsp_types/pyrefly/KNOWN_LIMITATIONS.md b/lsp_types/pyrefly/KNOWN_LIMITATIONS.md index e061f06..028b37e 100644 --- a/lsp_types/pyrefly/KNOWN_LIMITATIONS.md +++ b/lsp_types/pyrefly/KNOWN_LIMITATIONS.md @@ -1,46 +1,28 @@ -# Pyrefly backend: known limitations +# 🪽 Pyrefly: behavior & limitations -Verified with **Pyrefly 1.3.0 on 2026-09-11** using this library's LSP client. -Release notes are dated September 10; PyPI and GitHub publication occurred -September 11 UTC. [Release](https://github.com/facebook/pyrefly/releases/tag/1.3.0) +## Completion resolution adds no details -## Completion resolution is an echo +`resolve_completion()` returns the submitted item unchanged. Use the type +details and documentation included in the initial completion response. -`completionItem/resolve` returns the submitted item unchanged, despite -advertising `completionProvider.resolveProvider: true`. A live probe resolved -a method completion both normally and with `detail`/`documentation` removed; -both responses exactly matched their respective inputs. +## Semantic-token legend uses a built-in fallback -Initial completions already include type details and documentation, so ordinary -completion remains useful. Calling `resolve_completion()` succeeds but does -not retrieve additional metadata. +Pyrefly returns semantic tokens without advertising a legend during +initialization. `PyreflyBackend` supplies `PYREFLY_LEGEND` automatically. -## Semantic-token legend is not advertised +Normalization preserves Pyrefly's string modifiers: `byteString`, +`formatString`, `rawString`, `stringPrefix`, and `templateString`. See the +[semantic-token guide](../../docs/SEMANTIC_TOKENS.md). -The initialize response omits `semanticTokensProvider` entirely, while -`textDocument/semanticTokens/full` still returns tokens. The backend therefore -supplies `PYREFLY_LEGEND` instead of discovering a legend from the server. +## Configuration keys -Pyrefly 1.3.0 adds five string modifiers: `byteString`, `formatString`, -`rawString`, `stringPrefix`, and `templateString` (bits 11–15). This maintenance -updates both the fallback legend and canonical modifiers so normalization -preserves those bits. Token types are unchanged. See -[semantic-token documentation](../../docs/SEMANTIC_TOKENS.md). +Top-level snake_case Python keys become kebab-case TOML keys. Nested keys keep +their spelling: use upstream error-code names such as `bad-assignment`. +The typed schema covers common options; `Session.create(options=...)` also +accepts a plain dictionary with other upstream settings. -## Configuration and API boundaries +## API scope -The backend writes kebab-case TOML keys from top-level snake_case Python keys. -Nested error-code keys should use the upstream names (`bad-assignment`, etc.). -The typed schema covers common options; a plain `Session.create(options=...)` -dictionary can carry other upstream settings. - -Upstream CLI tools, TSP, and editor refactorings extend beyond the high-level -`Session` API. Their availability upstream does not imply a matching Session -method. Tensor-shape and DataFrame schema extensions remain experimental. - -## Previously resolved - -Rename previously failed for session files classified as external. It has -worked since 1.1.1 and remains covered by the regular rename integration test; -the former expected failure is gone. Virtual documents work without on-disk -mirroring. +Virtual documents and rename work without on-disk mirroring. The high-level +`Session` API exposes the methods documented by this library; upstream CLI, +TSP, and editor-only features do not automatically become session methods. diff --git a/lsp_types/ty/KNOWN_LIMITATIONS.md b/lsp_types/ty/KNOWN_LIMITATIONS.md index e79c79a..45d6d81 100644 --- a/lsp_types/ty/KNOWN_LIMITATIONS.md +++ b/lsp_types/ty/KNOWN_LIMITATIONS.md @@ -1,148 +1,59 @@ -# ty Backend - Known Limitations +# ⚡ ty: behavior & limitations -This document describes known limitations and behavioral differences when using the ty backend compared to other LSP backends (Pyright, Pyrefly). +## Configuration applies at session creation -## 1. `workspace/didChangeConfiguration` Not Supported +`TyBackend` writes `options` to `ty.toml`, converting snake_case keys to +kebab-case recursively. ty does not handle `workspace/didChangeConfiguration`, +so the backend skips that notification. Recreate the session to change settings. -**Limitation**: ty does not handle the `workspace/didChangeConfiguration` notification. +Pass inline configuration through `Session.create(initialize_params=...)`, +using upstream kebab-case keys: -**Behavior**: ty would log a warning if the notification were sent: +```python +initialize_params = { + "initializationOptions": { + "configuration": {"rules": {"unresolved-import": "ignore"}} + } +} ``` -WARN Received notification workspace/didChangeConfiguration which does not have a handler. -``` - -**Impact**: Runtime configuration changes via LSP notifications are ignored. However, configuration written to `ty.toml` before session creation is respected. - -**Resolution**: `TyBackend` implements `consumes_did_change_configuration() -> False`, -so `Session.create()` skips the notification entirely — the warning above no longer -appears in stderr. This remains the right call: the notification is genuinely -unhandled, so sending it produces a warning and no effect. - -Configuration does not have to live in `ty.toml`, however. ty also reads -`initializationOptions.configuration` at initialization, which accepts inline ty -config using kebab-case keys (`{"configuration": {"rules": {"unresolved-import": -"ignore"}}}` was verified to suppress that diagnostic with an empty `ty.toml`; -the snake_case spelling is silently ignored). Pass it through the public -`Session.create(..., initialize_params={"initializationOptions": {...}})` -parameter. This mirrors Zuban, which honors its own `initializationOptions` — -several backends in this repo accept LSP-time configuration, so "file-based only" -is the wrong mental model. This integration applies configuration when creating -the session. Recreate the session to change it reliably; its default null -configuration replies and lack of file watching do not provide settings updates. - -## 2. Hover Format Differs - -**Limitation**: ty's hover information shows only the type, not the variable name. - -**Behavior**: -- Pyright/Pyrefly hover: `result: str` or `(variable) result: str` -- ty hover: `str` - -**Impact**: Code that parses hover text expecting variable names will not find them with ty. - -## 3. No CLI Configuration Flags - -**Limitation**: The `ty server` command accepts no configuration flags. - -**Behavior**: Unlike Pyrefly which supports `--verbose`, `--threads`, etc., -`ty server --help` lists only `-h, --help`. - -**Impact**: None on functionality. Configuration reaches ty through two channels -rather than the command line: `ty.toml` (what `TyBackend.write_config()` writes), -and `initializationOptions` at LSP initialization — see limitation 1. The August -30 probe verified two keys with a real effect: `logLevel` (changes server log -verbosity) and `configuration` (applies inline ty config). Others are accepted -without a warning, but their effect was not confirmed and should not be assumed: -`diagnosticMode`, `disableLanguageServices`, `inlayHints`, -`completions`, `pythonExtension`, `workspaceTrust`, `experimental`, -`showSyntaxErrors`. The current documented setting for an explicit TOML path is -`configurationFile`; the older probe used `configuration-file` and did not -establish its behavior. See the [editor settings reference](https://docs.astral.sh/ty/reference/editor-settings/). - -ty warns loudly on unrecognized *top-level* initialization-option keys, so a typo -there is visible. That does not extend to nested keys: a misspelled rule name -inside `configuration` is silently ignored, as limitation 1 records for the -snake_case spelling of `unresolved-import`. -## 4. Workspace Folders Warning +Misspelled nested settings, including `unresolved_import` in this example, are +silently ignored. `ty server` accepts no configuration flags. Use `ty.toml` or +LSP initialization options, including `logLevel` and `configurationFile`. +See the [editor settings reference](https://docs.astral.sh/ty/reference/editor-settings/). -**Limitation**: ty expects `workspaceFolders` in the initialization parameters. +## Hover omits the variable name -**Behavior**: ty logs a warning when workspaceFolders is not provided: -``` -WARN No workspace(s) were provided during initialization. Using the current working directory from the fallback system as a default workspace -``` +Variable hover displays the type, such as `str` or `Literal["ok"]`, without the +variable name. Display the response directly rather than parsing it as +`name: type`. -**Impact**: ty falls back to using the working directory. This typically works correctly but may affect multi-root workspace scenarios. +## Workspace folders need explicit initialization -## 5. File Watching Not Supported by Client +Without `workspaceFolders`, ty uses the server's working directory and logs a +warning. `Session` does not populate this field automatically. Supply it through +`initialize_params` when you need explicit workspace roots: -**Limitation**: This library's LSP client does not implement file watching, and ty -adjusts its warning to how much watching the client claims to support. - -**Behavior**: With the capabilities `TyBackend` currently advertises, ty logs: -``` -WARN Your LSP client doesn't support file watching: You may see stale results when files change outside the editor +```python +initialize_params = { + "workspaceFolders": [{"uri": root.as_uri(), "name": root.name}] +} ``` -**Historical probe (0.0.70 and 0.0.75, August 30):** Advertising -`workspace.didChangeWatchedFiles.dynamicRegistration` narrowed the warning to -watching outside the project; also advertising `relativePatternSupport` removed -it. These capability variants were not rerun in the September maintenance. - -**Why the warning is left in place:** this library has no file watchers. The -process now answers server requests: its default handler returns null -configuration entries, acknowledges registration requests, and replies -`-32601` for unknown methods. That prevents protocol stalls, but acknowledging a -registration does not install a watcher. Advertising watching support would -still promise behavior this client does not provide. - -**Impact**: Files modified outside the LSP session (by external tools, a build -step, or a dependency install) may not be picked up until the session is -recreated. Sessions that only ever mutate the document through `update_code()` -are unaffected. - -## 6. Completion Item Resolution Not Supported - -**Limitation**: ty does not support the `completionItem/resolve` LSP request. - -**Behavior**: Calling `resolve_completion()` will raise an error: -``` -Unknown request: completionItem/resolve (-32601) -``` - -**Impact**: Clients must use the initial completion response; they cannot fetch -additional details through a separate resolution request. Basic completion works. - ---- -## Previously Documented, Now Resolved +Here, `root` is an absolute `pathlib.Path` to an existing workspace directory. -- **Files must exist on disk** (documented for ty 0.0.11): Early ty versions returned empty diagnostics, limited completions, and failing renames for "virtual documents" opened via `didOpen` without a corresponding file on disk, so `TyBackend` mirrored the session code to disk (`requires_file_on_disk() -> True`). Bisecting PyPI releases shows virtual documents work from ty 0.0.16 onward (diagnostics on `didOpen` and `didChange`, completion, and rename all verified with no `.py` file on disk). The backend no longer writes files to disk, and the package floor is now `ty>=0.0.16`. +## External files are not watched ---- +The client does not install file watchers. ty reports this with a warning; +files changed by external tools can remain stale until the session is recreated. +Document edits sent through `update_code()` work normally. -## Version Information +## Completion resolution is unsupported -The September 11, 2026 maintenance used **ty 0.0.80**. Fresh temporary -LSP sessions confirmed: +`resolve_completion()` raises `Unknown request: completionItem/resolve (-32601)`. +Use the initial completion response. -- `workspace/didChangeConfiguration` still logs an unhandled-notification - warning. An unresolved import remained after sending a suppressing setting - and editing the document; inline initialization with the correct kebab-case - rule suppressed it, while an unrelated assignment error remained. The - snake_case rule spelling did not suppress it. -- Hover over `result: str = "ok"` returned `Literal["ok"]`, without its name. -- `ty server --help` lists only `-h/--help`. -- The default initialization emits both missing-workspace and missing-watcher - warnings. Supplying a valid `workspaceFolders` removed the former while - retaining correct diagnostics. Session does not currently supply this field. -- Resolving a real completion item returned `-32601`. -- All diagnostic and hover probes used virtual documents, with no `.py` file on - disk. New analysis configuration serialized and loaded successfully; an - `allowed_unresolved_imports` entry suppressed its matching unresolved import - without suppressing the assignment control. +## Virtual documents are supported -Historical August 30 probes compared 0.0.70 and 0.0.75 and found identical behavior -for the six entries. That run also checked log-level changes, invalid workspace -paths and watcher-capability variants. Those details remain historical evidence, -not claims that every variant was repeated at 0.0.80. +Supported ty versions analyze documents opened through LSP without a matching +Python file on disk. The backend requires ty 0.0.16 or newer. diff --git a/lsp_types/zuban/KNOWN_LIMITATIONS.md b/lsp_types/zuban/KNOWN_LIMITATIONS.md index 7fb97ee..2e21fa6 100644 --- a/lsp_types/zuban/KNOWN_LIMITATIONS.md +++ b/lsp_types/zuban/KNOWN_LIMITATIONS.md @@ -1,127 +1,50 @@ -# Zuban Backend - Known Limitations +# 🛡️ Zuban: behavior & limitations -This document describes known limitations and behavioral differences when using the Zuban backend compared to other LSP backends (Pyright, Pyrefly, ty). +## Configuration replaces `[tool.zuban]` -## 1. Config Written to `pyproject.toml` +`ZubanBackend` adds or replaces `[tool.zuban]` in `pyproject.toml`, including when +`options` is empty. That table selects Zuban's native `default` mode; a project +with only `[tool.mypy]` uses Mypy-compatible defaults instead. -**Behavior**: `ZubanBackend.write_config` adds or updates `[tool.zuban]` inside `pyproject.toml`, preserving any existing `[project]` metadata and other `[tool.*]` sections. It does not read, merge, or write `mypy.ini`, `.mypy.ini`, or `setup.cfg`. +Other sections retain their values, comments, layout, and line endings. The +previous `[tool.zuban]` table and its associated comments are replaced. +`mypy.ini`, `.mypy.ini`, and `setup.cfg` are not read or edited by the backend's +configuration writer. -**Why `pyproject.toml` and not a dedicated file**: Unlike Pyright (`pyrightconfig.json`), Pyrefly (`pyrefly.toml`), and ty (`ty.toml`), Zuban has no dedicated config file in its native "default" mode. Selecting Zuban's PyRight-like mode is done via `pyproject.toml`'s `[tool.zuban]` table (some settings can also be passed as LSP `initializationOptions` — see entry 4). +`Session.create()` defaults to the current directory. Set `base_path` to a +dedicated directory to keep session settings separate from your project. -**Why `[tool.zuban]` and not `[tool.mypy]`**: Presence of `[tool.zuban]` puts Zuban into its recommended `default` mode (PyRight-like). `[tool.mypy]` would force the Mypy-compatible mode, which preserves different Mypy-compatible defaults. +## Unused ignores are not reported -**Impact**: Re-invoking `write_config` replaces the previous `[tool.zuban]` table -in place; every other parsed value and section is preserved. +Zuban does not flag unused `# type: ignore` comments. -**Formatting is preserved**: `write_config` is a format-preserving `tomlkit` -edit. Only the `[tool.zuban]` table is added or replaced; comments, inline -tables, arrays-of-tables, key ordering, whitespace and line endings elsewhere in -the file survive. Comments that `tomlkit` associates with an existing -`[tool.zuban]` go with it, since that table is replaced wholesale. This matters because `Session.create()` defaults to -`base_path=Path(".")`, so the naive call operates on the caller's real -`pyproject.toml`. (Until v0.22.1 this was a `tomllib` -> `tomli_w` round-trip -that preserved values but stripped every comment.) +## Value-constrained generic bodies are not checked -**The table is written even when `options` is empty**, and must be: its -*presence* is what selects Zuban's `default` mode. A project carrying -`[tool.mypy]` but no `[tool.zuban]` uses Mypy-compatible defaults, so -skipping the write would silently change its mode. Note this is observable only -through `zuban server` — the `zuban check` subcommand pins `default` mode -regardless of configuration, which makes the caveat easy to mis-verify. +Zuban skips type checking inside functions parameterized by +`TypeVar("T", str, bytes)` or `[T: (str, bytes)]`. Type errors in those bodies do +not appear in diagnostics. Upper-bounded TypeVars, such as +`TypeVar("T", bound=str)`, are checked normally. -## 2. Unused `# type: ignore` Comments Not Reported +Both gaps are listed in Zuban's +[missing features](https://docs.zubanls.com/en/latest/features.html). -**Limitation**: Zuban does not yet report unused `# type: ignore` comments (upstream limitation still present as of Zuban 0.9.3, per the [features documentation](https://docs.zubanls.com/en/latest/features.html)). +## Server settings use TOML or initialization options -**Impact**: Code that accumulates stale `# type: ignore` comments will not be flagged when using this backend. +`zuban server` accepts no configuration flags. Configure it through `options` +or pass LSP settings through `initialize_params`: -## 3. Value-Constrained `TypeVar` Function Bodies Not Type-Checked +```python +initialize_params = { + "initializationOptions": {"diagnosticMode": "workspace"} +} +``` -**Limitation**: Zuban does not type-check function bodies parameterized by a -*value-constrained* `TypeVar` — `TypeVar("T", str, bytes)` or the PEP 695 form -`[T: (str, bytes)]`. Upstream lists this under "Missing Features" in the -[features documentation](https://docs.zubanls.com/en/latest/features.html), still -present as of Zuban 0.9.3. +Zuban supports `typeCheckingMode`, `disableLanguageServices`, `diagnosticMode`, +`pythonExecutable`, and `inlayHintMode` initialization settings. Recreate the +session to change configuration; the backend skips +`workspace/didChangeConfiguration`. -**Not affected**: *upper-bounded* TypeVars — `TypeVar("T", bound=str)` — are -checked normally. The distinction is constraints (a tuple of alternatives) -versus a bound (a single upper limit); only the former disables body checking. - -**Impact**: Type errors inside value-constrained generic functions do not surface -via diagnostics. Historical August 30 probe at 0.9.2: `bad: int = "definitely not an int"` inside a -`TypeVar("T", str, bytes)` body (line 7) and inside a `[T: (str, bytes)]` body -(line 2) produced no diagnostic, while the identical statement in a plain -`def plain(x: str) -> str` in the same file (lines 13 and 8 respectively) was -reported as `[assignment] Incompatible types in assignment (expression has type -"str", variable has type "int")`. The same statement inside a -`TypeVar("T", bound=str)` body (line 7) was reported. - -## 4. No CLI Configuration Flags on `zuban server` - -**Limitation**: `zuban server` accepts no configuration flags. `zuban server --help` -lists only `-h, --help`; a configuration argument is rejected outright, e.g. -`zuban server --mode default` -> `error: unexpected argument '--mode' found`. -(By contrast `zuban check` exposes a large flag surface including `--mode`, -`--untyped-function-return-mode`, and `--python-executable`.) - -**Impact**: No functional impact on this backend — `ZubanBackend` configures Zuban -via `pyproject.toml`. Unlike the Pyrefly backend (which exposes `--verbose`, -`--threads`, `--indexing-mode` through `ProcessLaunchInfo`), -`ZubanBackend.create_process_launch_info` returns a fixed `["zuban", "server"]`. - -**Configuration is not exclusively file-based.** Zuban also reads LSP -`initializationOptions` sent with `initialize`. Upstream's changelog adds these in -0.8.1 (`typeCheckingMode`, `disableLanguageServices`, `diagnosticMode`, -`pythonExecutable`) and 0.9.1 (`inlayHintMode`). `ZubanBackend` sends none, but -callers can supply them via `Session.create`'s public `initialize_params`: - - await Session.create( - ZubanBackend(), - initialize_params={"initializationOptions": {"diagnosticMode": "workspace"}}, - ) - -Reconfirmed at 0.9.3 by diffing the initialize response: -`diagnosticMode="workspace"` flips `diagnosticProvider.workspaceDiagnostics` to -`true`; `typeCheckingMode="off"` drops `diagnosticProvider` entirely; -`disableLanguageServices=true` drops `hoverProvider` and removes -`completionProvider`. The historical 0.9.2 probe also found that an unknown key -left the response identical to baseline; that control was not repeated at 0.9.3. - -**Caveat**: for the two options probed this way, the change was to what Zuban -*advertises*, not to what it *answers*. With `typeCheckingMode="off"`, -`textDocument/diagnostic` still returned the same diagnostic; with -`disableLanguageServices=true`, hover and completion still returned results. A -client that gates requests on advertised capabilities sees a behavior change; -`Session`, which sends requests unconditionally, does not. The remaining options -(`diagnosticMode`, `pythonExecutable`, `inlayHintMode`) were not probed this way. - ---- - -## Version Information - -The September 11, 2026 maintenance used **Zuban 0.9.3**, released September 2. -Fresh temporary LSP sessions reporting that version confirmed: - -- The typed `mode="auto"` setting serialized and loaded successfully. -- One virtual document contained assignment errors in four functions. Errors - were reported in the upper-bounded TypeVar and plain functions, but not in - either value-constrained TypeVar syntax. Its unnecessary `# type: ignore` - was not reported either. -- `zuban server --help` lists only `-h/--help`. -- `diagnosticMode="workspace"` changed advertised workspace diagnostics to true; - `typeCheckingMode="off"` removed the diagnostic provider; - `disableLanguageServices=true` removed hover and completion providers. - Nevertheless, direct diagnostic, hover and completion requests still returned - results with the corresponding advertised services disabled. -- The 18 config tests, including preservation of comments, other sections, - inline tables and CRLF, passed. This is library behavior verified separately - from upstream capability claims. - -The detailed line-numbered 0.9.2 examples above are historical August 30 evidence. -The newer combined probe reached the same constrained-versus-bounded conclusion. -No fresh behavioral probe was made for `pythonExecutable` or `inlayHintMode`. - -Upstream continues to list unused-ignore reporting and constrained generic -bodies under [missing features](https://docs.zubanls.com/en/latest/features.html). -The [0.9.3 changelog](https://docs.zubanls.com/en/latest/changelog.html) records -deterministic file processing and a fix for quadratic literal handling. +`typeCheckingMode="off"` and `disableLanguageServices=True` remove the +corresponding advertised capabilities, but direct diagnostic, hover, and +completion requests still return results. `Session` sends these requests +unconditionally. Enforce disabled features in your application.