diff --git a/.github/workflows/playground.yml b/.github/workflows/playground.yml
index 55932a4..4eabeeb 100644
--- a/.github/workflows/playground.yml
+++ b/.github/workflows/playground.yml
@@ -6,6 +6,7 @@ on:
- main
paths:
- "playground/**"
+ - ".github/workflows/playground.yml"
workflow_dispatch:
permissions:
@@ -36,6 +37,9 @@ jobs:
uses: actions/configure-pages@v6
- run: npm ci
+ - name: Build pinned WASM backends
+ run: npm run fetch-wasm
+ - run: npm run test:wasm
- run: npm run build
- uses: actions/upload-pages-artifact@v5
diff --git a/.github/workflows/python-tests.yml b/.github/workflows/python-tests.yml
index 5e48725..856d3a0 100644
--- a/.github/workflows/python-tests.yml
+++ b/.github/workflows/python-tests.yml
@@ -17,6 +17,10 @@ jobs:
fail-fast: false
matrix:
python-version: ["3.12", "3.13", "3.14"]
+ pyright-package: ["basedpyright", "pyright"]
+ env:
+ PYRIGHT_PACKAGE: ${{ matrix.pyright-package }}
+ UV_PYTHON: ${{ matrix.python-version }}
steps:
- uses: actions/checkout@v7
@@ -27,12 +31,12 @@ jobs:
- name: Set up Python ${{ matrix.python-version }}
run: uv python install ${{ matrix.python-version }}
- name: Install dependencies
- run: uv sync --all-extras
+ run: uv sync --all-extras --locked
- name: Setup Node.js environment
uses: actions/setup-node@v7.0.0
- - name: Install basedpyright globally
- run: npm install -g basedpyright
+ - name: Install Pyright distribution
+ run: npm install -g "$PYRIGHT_PACKAGE"
env:
NPM_CONFIG_PREFIX: ~/.npm-global
diff --git a/CLAUDE.md b/CLAUDE.md
index d7217ff..7ab6bcc 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -2,6 +2,12 @@
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
+## 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/`.
+
## Essential Commands
Always use `uv` for Python operations:
@@ -118,7 +124,7 @@ This is a minimal-dependency Python library providing typed LSP (Language Server
**Generation Process:**
1. `download_schemas.py`: Fetches latest schemas from upstream
2. `datamodel-codegen`: Converts JSON schema to TypedDict definitions, pinned to
- `--formatters black isort`, then ruff-formats its own output
+ `--formatters black isort --disable-timestamp`, then ruff-formats its own output
3. `generate.py`: Orchestrates final type file generation with utilities in `assets/scripts/utils/`
4. Every file in the Makefile's `GENERATED_FILES` is ruff-formatted and `--fix`ed so
regenerating produces no spurious diff
diff --git a/Makefile b/Makefile
index debcc32..f2c0dac 100644
--- a/Makefile
+++ b/Makefile
@@ -43,6 +43,7 @@ generate-lsp-schema:
--use-field-description \
--use-schema-description \
--use-double-quotes \
+ --disable-timestamp \
--formatters black isort
uvx ruff format assets/scripts/lsp_schema.py
@@ -56,6 +57,7 @@ generate-pyright-schema:
--use-field-description \
--use-schema-description \
--use-double-quotes \
+ --disable-timestamp \
--formatters black isort
uvx ruff format lsp_types/pyright/config_schema.py
diff --git a/README.md b/README.md
index 09dae41..faa5e44 100644
--- a/README.md
+++ b/README.md
@@ -5,192 +5,96 @@
[](https://github.com/Mazyod/lsp-python-types/actions/workflows/python-tests.yml)
[](https://opensource.org/licenses/MIT)
-_Publish the excellent work of [Sublime LSP](https://github.com/sublimelsp/lsp-python-types) as a PyPI package._
-
-
-
-
-__LSP Types__ is a Python package that aims to provide a fully typed interface to Language Server Protocol (LSP) interactions. It can be used to simply utilize the types, or to interact with an LSP server over stdio.
-
-The library has minimal dependencies (`tomlkit` for TOML config serialization).
-
-## Installation
+Typed Python interfaces for the Language Server Protocol, with async sessions
+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
+
+
+
+- **[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
```sh
-pip install lsp-types
-```
-
-## Usage
-
-Using the LSP types:
-
-```python
-import lsp_types
-
-# Use the types
+pip install "lsp-types[pyrefly]" # Or [ty] / [zuban]
```
-Using an LSP process through 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"
-])
+import asyncio
+from pathlib import Path
+from tempfile import TemporaryDirectory
-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.
-
-## LSPs
-
-The following LSPs are available out of the box:
-
-- [Pyright](https://github.com/microsoft/pyright)
-- [Pyrefly](https://github.com/facebook/pyrefly)
-- [ty](https://github.com/astral-sh/ty) - Astral's fast Python type checker
-- [Zuban](https://github.com/zubanls/zuban) - Rust-based type checker + LSP by the author of Jedi
-
-### Pyrefly CLI tools
-
-This library drives Pyrefly's LSP server (`pyrefly lsp`), but Pyrefly also ships a broader
-standalone CLI worth knowing about (verified with Pyrefly 1.2.0):
-
-| Command | What it does |
-|---------|--------------|
-| `pyrefly init` | Scaffold a `pyrefly.toml` (or `[tool.pyrefly]` in `pyproject.toml`), or **migrate an existing mypy/pyright config** to Pyrefly |
-| `pyrefly check` | Full type check of a file or project |
-| `pyrefly snippet ` | Type-check an inline code snippet |
-| `pyrefly infer` | Automatically add inferred type annotations to a file or directory |
-| `pyrefly coverage` | Type-coverage reporting commands |
-| `pyrefly suppress` | Add ignore comments for existing errors, or remove unused ignores |
-| `pyrefly stubgen` | Generate `.pyi` stub files from Python source |
-| `pyrefly dump-config` | Print Pyrefly's resolved configuration |
-| `pyrefly tsp` | Start a TSP (Type Server Protocol) server (new in 1.2.x) |
-
-Run `pyrefly --help` for details, or see the [Pyrefly docs](https://pyrefly.org/).
-
-## Feature Support Matrix
-
-### Legend
-
-| Symbol | Meaning |
-|--------|---------|
-| :white_check_mark: | Fully supported |
-| :warning: | Partial support (see notes) |
-| :x: | Not supported |
-| :grey_question: | Not tested / Not exposed in API |
-
-### Features by Backend
-
-> Last verified: Pyrefly 1.2.0, ty 0.0.75, Zuban 0.9.2 (basedpyright: CI only, unpinned `npm install -g basedpyright`)
-
-| Feature | Pyright | Pyrefly | ty | Zuban | Notes |
-|---------|:-------:|:-------:|:--:|:-----:|-------|
-| Diagnostics | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | |
-| Hover | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | ty shows type only, not variable name |
-| Completion | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | |
-| Completion Resolution | :white_check_mark: | :x: | :x: | :white_check_mark: | Pyrefly: no-op (returns item unchanged); ty: not supported (`-32601`) |
-| Signature Help | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | |
-| Rename | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | |
-| Semantic Tokens | :white_check_mark:\* | :white_check_mark:\*\* | :white_check_mark: | :white_check_mark: | \*basedpyright recommended; \*\*Pyrefly: legend not advertised (see docs) |
-| Go to Definition | :grey_question: | :grey_question: | :grey_question: | :grey_question: | Not exposed in Session API |
-| Find References | :grey_question: | :grey_question: | :grey_question: | :grey_question: | Not exposed in Session API |
-| Code Actions | :grey_question: | :grey_question: | :grey_question: | :grey_question: | Not exposed in Session API |
-| Formatting | :grey_question: | :grey_question: | :grey_question: | :grey_question: | Not exposed in Session API |
-
-> See [Feature Verification Guide](docs/FEATURE_VERIFICATION.md) for methodology on maintaining this table.
-
-For detailed documentation:
-- [Semantic Tokens Reference](docs/SEMANTIC_TOKENS.md) - Token types and modifiers for Monaco/editor integration
-- [Pyrefly Known Limitations](lsp_types/pyrefly/KNOWN_LIMITATIONS.md)
-- [ty Known Limitations](lsp_types/ty/KNOWN_LIMITATIONS.md)
-- [Zuban Known Limitations](lsp_types/zuban/KNOWN_LIMITATIONS.md)
-
-### Pyright Example
-
-```python
from lsp_types import Session
-from lsp_types.pyright.backend import PyrightBackend
-
-async def test_pyright_session():
- code = """\
-def greet(name: str) -> str:
- return 123
-"""
-
- session = await Session.create(PyrightBackend(), initial_code=code)
- diagnostics = await session.get_diagnostics()
+from lsp_types.pyrefly.backend import PyreflyBackend
+
+async def main():
+ with TemporaryDirectory() as workspace:
+ session = await Session.create(
+ PyreflyBackend(),
+ base_path=Path(workspace),
+ initial_code='answer: int = "oops"',
+ )
+ try:
+ print(await session.get_diagnostics())
+ await session.update_code("answer: int = 42")
+ print(await session.get_diagnostics()) # []
+ finally:
+ await session.shutdown()
+
+asyncio.run(main())
+```
- assert diagnostics != []
+Swap in `TyBackend`, `ZubanBackend`, or `PyrightBackend`. For Pyright, install
+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.
- code = """\
-def greet(name: str) -> str:
- return f"Hello, {name}"
-"""
+## What works here
- await session.update_code(code)
- diagnostics = await session.get_diagnostics()
- assert diagnostics == []
+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.
- await session.shutdown()
-```
+Verified **2026-09-11**: Pyright **1.1.414**, basedpyright **1.40.1**,
+Pyrefly **1.3.0**, ty **0.0.80**, Zuban **0.9.3**.
-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.)
+[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)
## Development
-- Requires Python 3.12+.
-- Requires `uv` for dev dependencies.
-
-Generate latest types in one go:
```sh
+uv sync --all-extras --locked
+npm install -g basedpyright
+uv run pytest
+uvx pyright --pythonpath .venv/bin/python
+uvx ruff check .
make generate-latest-types
```
-Download the latest json schema:
-```sh
-make download-schemas
-```
-
-Generate the types:
-```sh
-make generate-types
-```
-
-Copy the `lsp_types/types.py` file to your project.
-
-NOTE: Do not import types that begin with `__`. These types are internal types and are not meant to be used.
-
-### TODOs
-
-- Support server request handlers.
+The [runbook](docs/FEATURE_VERIFICATION.md) covers testing Microsoft Pyright
+separately, regenerating schemas and updating the browser playground.
diff --git a/assets/images/README.md b/assets/images/README.md
new file mode 100644
index 0000000..b7d9088
--- /dev/null
+++ b/assets/images/README.md
@@ -0,0 +1,9 @@
+# LSP party artwork
+
+`lsp-party.png` was generated with the built-in imagegen tool on 2026-09-11.
+The characters are editorial metaphors, not official mascots or benchmark scores.
+Left to right: Pyright the sentinel, Pyrefly the artificer, ty the scout, Zuban the diplomat.
+
+## Generation prompt
+
+Use case: stylized-concept. Asset type: compact GitHub README banner for a Python LSP library. Create a polished pixel-art adventuring party, exactly four equally prominent friendly fantasy characters in a single horizontal row, full bodies, crisp square pixel edges, carefully limited 16-bit palette, dark midnight-blue backdrop with subtle tiny stars and one shared ground line. Wide 3:1 composition, no cards, no grids, no stats, no title. Left to right: (1) blue-and-silver sentinel with shield and a small book, steady confident posture; (2) coral-and-gold artificer with goggles, tool belt and glowing tiny mechanical firefly; (3) mint-and-teal hooded scout with light boots, compass and flowing scarf, energetic posture; (4) purple-and-cream diplomat scholar holding two differently colored scrolls joined by a ribbon, warm expression. Give each a distinct silhouette. Pixel lettering below each, exact text: "Pyright", "Pyrefly", "ty", "Zuban". No other text, logos, watermark, rankings or health bars. Generous margins and strong legibility when displayed at 760px wide; charming, professional indie-game sprite artwork.
diff --git a/assets/images/lsp-party.png b/assets/images/lsp-party.png
new file mode 100644
index 0000000..4c9f6f9
Binary files /dev/null and b/assets/images/lsp-party.png differ
diff --git a/assets/lsprotocol/lsp.json b/assets/lsprotocol/lsp.json
index fc68d8d..85244c4 100644
--- a/assets/lsprotocol/lsp.json
+++ b/assets/lsprotocol/lsp.json
@@ -3899,6 +3899,10 @@
{
"kind": "reference",
"name": "WorkDoneProgressParams"
+ },
+ {
+ "kind": "reference",
+ "name": "PartialResultParams"
}
],
"documentation": "A parameter literal used in inline value requests.\n\n@since 3.17.0",
@@ -3950,6 +3954,10 @@
{
"kind": "reference",
"name": "WorkDoneProgressParams"
+ },
+ {
+ "kind": "reference",
+ "name": "PartialResultParams"
}
],
"documentation": "A parameter literal used in inlay hint requests.\n\n@since 3.17.0",
@@ -4358,6 +4366,10 @@
{
"kind": "reference",
"name": "WorkDoneProgressParams"
+ },
+ {
+ "kind": "reference",
+ "name": "PartialResultParams"
}
],
"documentation": "A parameter literal used in inline completion requests.\n\n@since 3.18.0",
diff --git a/assets/lsps/pyrefly-guide.md b/assets/lsps/pyrefly-guide.md
index d184c12..319752f 100644
--- a/assets/lsps/pyrefly-guide.md
+++ b/assets/lsps/pyrefly-guide.md
@@ -1,382 +1,69 @@
-# Pyrefly: A Developer's Guide to Programmatic Usage and LSP Integration
+# Pyrefly integration reference
-This guide documents practical insights gained from implementing a proof of concept with Pyrefly, Facebook's fast Python type checker and Language Server Protocol (LSP) server written in Rust.
-
-## Overview
-
-Pyrefly is a modern alternative to traditional Python type checkers like mypy, offering:
-
-- **Performance**: Written in Rust for speed on large codebases
-- **LSP Support**: Built-in Language Server Protocol implementation
-- **Editor Integration**: Works with VS Code, Neovim, Emacs, Vim, and other LSP-compatible editors
-- **Type Checking**: Advanced static analysis with comprehensive diagnostics
-
-## Command Line Interface
-
-### Basic Usage
-
-Pyrefly provides several commands accessible via its CLI:
-
-```bash
-# Type check a single file
-pyrefly check main.py
-
-# Type check entire project
-pyrefly check
-
-# Start LSP server
-pyrefly lsp
-
-# Check a code snippet
-pyrefly snippet "def hello(name: str) -> str: return f'Hello {name}'"
-
-# Initialize configuration
-pyrefly init
-
-# Display configuration
-pyrefly dump-config
-```
-
-### Key CLI Options
-
-- `--verbose`: Enable detailed logging
-- `--threads N`: Control parallelization (0 = auto)
-- `--color`: Control colored output (auto/always/never)
-
-## LSP Server Implementation
-
-### Starting the LSP Server
-
-```bash
-# Basic LSP server
-pyrefly lsp
-
-# With verbose logging
-pyrefly lsp --verbose
-
-# With specific indexing mode
-pyrefly lsp --indexing-mode lazy-non-blocking-background
-```
-
-### Indexing Modes
-
-Pyrefly supports different indexing strategies:
-
-- `none`: Disable indexing (disables find-refs, etc.)
-- `lazy-non-blocking-background`: Index in background thread (default)
-- `lazy-blocking`: Index in main thread (blocks IDE services)
-
-### LSP Communication Protocol
-
-The LSP server communicates via JSON-RPC over stdin/stdout. Here's the basic flow:
-
-#### 1. Initialize Connection
-
-```json
-{
- "jsonrpc": "2.0",
- "id": 1,
- "method": "initialize",
- "params": {
- "processId": 12345,
- "rootUri": "file:///path/to/project",
- "capabilities": {
- "textDocument": {
- "hover": {"contentFormat": ["markdown", "plaintext"]},
- "completion": {},
- "definition": {},
- "references": {}
- }
- }
- }
-}
-```
-
-#### 2. Send Initialized Notification
-
-```json
-{
- "jsonrpc": "2.0",
- "method": "initialized",
- "params": {}
-}
-```
-
-#### 3. Open Document
-
-```json
-{
- "jsonrpc": "2.0",
- "method": "textDocument/didOpen",
- "params": {
- "textDocument": {
- "uri": "file:///path/to/file.py",
- "languageId": "python",
- "version": 1,
- "text": "def hello(name: str) -> str:\n return f'Hello {name}'"
- }
- }
-}
-```
-
-#### 4. Request Features
-
-```json
-// Hover information
-{
- "jsonrpc": "2.0",
- "id": 2,
- "method": "textDocument/hover",
- "params": {
- "textDocument": {"uri": "file:///path/to/file.py"},
- "position": {"line": 0, "character": 4}
- }
-}
-
-// Code completion
-{
- "jsonrpc": "2.0",
- "id": 3,
- "method": "textDocument/completion",
- "params": {
- "textDocument": {"uri": "file:///path/to/file.py"},
- "position": {"line": 1, "character": 10}
- }
-}
-```
-
-### Python LSP Client Implementation
-
-Here's a minimal Python client for interacting with Pyrefly LSP:
-
-```python
-import subprocess
-import json
-import os
-
-class PyreflyLSPClient:
- def __init__(self, project_root: str):
- self.project_root = project_root
- self.process = None
-
- def start_server(self):
- """Start Pyrefly LSP server process."""
- self.process = subprocess.Popen(
- ["pyrefly", "lsp", "--verbose"],
- cwd=self.project_root,
- stdin=subprocess.PIPE,
- stdout=subprocess.PIPE,
- stderr=subprocess.PIPE,
- text=True,
- bufsize=0
- )
-
- def send_message(self, message: dict):
- """Send JSON-RPC message to LSP server."""
- content = json.dumps(message)
- content_length = len(content.encode('utf-8'))
-
- # LSP requires Content-Length header
- full_message = f"Content-Length: {content_length}\r\n\r\n{content}"
-
- self.process.stdin.write(full_message)
- self.process.stdin.flush()
-
- def initialize(self):
- """Initialize LSP connection."""
- init_msg = {
- "jsonrpc": "2.0",
- "id": 1,
- "method": "initialize",
- "params": {
- "processId": os.getpid(),
- "rootUri": f"file://{self.project_root}",
- "capabilities": {
- "textDocument": {
- "hover": {"contentFormat": ["markdown"]},
- "completion": {},
- "definition": {},
- "references": {}
- }
- }
- }
- }
-
- self.send_message(init_msg)
-
- # Send initialized notification
- self.send_message({
- "jsonrpc": "2.0",
- "method": "initialized",
- "params": {}
- })
-```
-
-## Programmatic Diagnostics
-
-### Running Type Checks Programmatically
-
-```python
-import subprocess
-import json
-
-def run_pyrefly_check(file_path: str) -> tuple[int, str, str]:
- """Run Pyrefly type check and return results."""
- result = subprocess.run(
- ["pyrefly", "check", file_path],
- capture_output=True,
- text=True
- )
-
- return result.returncode, result.stdout, result.stderr
-
-# Usage
-exit_code, stdout, stderr = run_pyrefly_check("main.py")
-if exit_code == 0:
- print("No type errors found")
-else:
- print(f"Type errors detected:\n{stderr}")
-```
-
-### Parsing Diagnostic Output
-
-Pyrefly outputs diagnostics in a structured format. Here's how to parse them:
-
-```python
-import re
-from typing import List, Dict, Any
-
-def parse_pyrefly_diagnostics(stderr: str) -> List[Dict[str, Any]]:
- """Parse Pyrefly diagnostic output into structured data."""
- diagnostics = []
-
- # Pattern for Pyrefly error messages
- error_pattern = r'(.+):(\d+):(\d+): (.+)'
-
- for line in stderr.strip().split('\n'):
- if 'ERROR' in line or 'WARN' in line:
- match = re.search(error_pattern, line)
- if match:
- diagnostics.append({
- 'file': match.group(1),
- 'line': int(match.group(2)),
- 'column': int(match.group(3)),
- 'message': match.group(4),
- 'severity': 'error' if 'ERROR' in line else 'warning'
- })
-
- return diagnostics
-```
+Verified with **Pyrefly 1.3.0 on 2026-09-11**. The backend's typed configuration
+is maintained in `lsp_types/pyrefly/config_schema.py`; this document is a short
+reference, not a generated or exhaustive upstream schema.
## Configuration
-### Project Configuration
-
-Pyrefly looks for configuration in `pyrefly.toml` at the project root. However, during testing, many standard configuration options weren't recognized, suggesting the configuration format is still evolving.
-
-### Working Configuration Approach
-
-Instead of relying on configuration files, use command-line options for consistent behavior:
-
-```bash
-# Recommended approach
-pyrefly lsp --verbose --threads 4
-```
-
-### Environment Variables
+Pyrefly reads `pyrefly.toml` or `[tool.pyrefly]` in `pyproject.toml`.
+`PyreflyBackend` writes `pyrefly.toml`, converting top-level Python snake_case
+keys to TOML kebab-case. Error-code keys inside `errors` should already use
+their upstream spelling. For example:
-Pyrefly respects these environment variables:
-
-- `PYREFLY_THREADS`: Number of threads for parallelization
-- `PYREFLY_COLOR`: Color output control
-- `PYREFLY_VERBOSE`: Enable verbose logging
-
-## Editor Integration Examples
-
-### VS Code
-
-Install the official Pyrefly extension from the marketplace, or configure manually:
-
-```json
-{
- "python.languageServer": "Pyrefly",
- "pyrefly.args": ["--verbose"]
-}
-```
-
-### Neovim (with nvim-lspconfig)
-
-```lua
-require('lspconfig').pyrefly.setup{
- cmd = {"pyrefly", "lsp"},
- settings = {
- pyrefly = {
- -- Add any Pyrefly-specific settings here
- }
- }
+```python
+from lsp_types import Session
+from lsp_types.pyrefly.backend import PyreflyBackend
+from lsp_types.pyrefly.config_schema import Model
+
+options: Model = {
+ "preset": "strict",
+ "python_version": "3.13",
+ "search_path": ["src"],
+ "check_unannotated_defs": True,
+ "infer_return_types": "checked",
+ "errors": {"regex": "error", "missing-attribute-patch-target": "warn"},
}
+# In an async function:
+# session = await Session.create(PyreflyBackend(), options=options)
```
-### Emacs (with Eglot)
-
-```elisp
-(add-to-list 'eglot-server-programs
- '(python-mode . ("pyrefly" "lsp")))
-```
+Errors accept severity strings (`error`, `warn`, `info`, `ignore`) and legacy
+booleans. `untyped_def_behavior` remains accepted but is deprecated upstream;
+use `check_unannotated_defs` and `infer_return_types` for new configurations.
+Plain dictionaries passed to `Session.create(options=...)` can carry options
+not yet represented in `Model`.
-## Performance Considerations
+An unconfigured CLI project may use the minimal `basic` preset or migrate
+nearby mypy/Pyright configuration in memory. Explicit configuration makes
+comparisons reproducible. `Session.create()` writes a configuration file,
+including an empty file when given no options, so its behavior need not match
+an unconfigured CLI invocation. See the
+[configuration reference](https://pyrefly.org/en/docs/configuration/) and
+[1.3.0 configuration implementation](https://github.com/facebook/pyrefly/blob/1.3.0/crates/pyrefly_config/src/base.rs).
-### Indexing Strategy
+## LSP and CLI
-Choose indexing mode based on your needs:
+The library starts `pyrefly lsp` and communicates over JSON-RPC stdio.
+The backend forwards `verbose`, `threads`, and `indexing_mode` as launch flags.
+Indexing modes are `none`, `lazy-non-blocking-background` (default), and
+`lazy-blocking` (useful for deterministic testing). Disabling indexing limits
+features such as references. Pyrefly supports virtual documents opened with
+`textDocument/didOpen`; files need not already exist on disk.
-- **Development**: Use `lazy-non-blocking-background` for responsiveness
-- **CI/Testing**: Use `lazy-blocking` for deterministic behavior
-- **Large Projects**: Consider `none` if find-references isn't critical
+The standalone CLI also offers configuration migration (`init`), annotation
+inference (`infer`), type coverage (`coverage`), ignore management (`suppress`),
+stub generation (`stubgen`), and an independent Type Server Protocol (`tsp`)
+server. These are not wrapped by this library's `Session` API.
-### Threading
-
-- Default (0) uses automatic thread detection
-- Set to 1 for sequential execution
-- Higher values can improve performance on large codebases
-
-## Troubleshooting
-
-### Common Issues
-
-1. **Module Import Errors**: Ensure Pyrefly can find your Python environment
-2. **LSP Connection Issues**: Check that stdin/stdout aren't being used by other processes
-3. **Configuration Warnings**: Many config options may not be supported yet
-4. **Performance**: Adjust indexing mode and thread count for your workflow
-
-### Debugging LSP Communication
-
-Enable verbose mode and monitor stderr for detailed protocol messages:
+Use structured output for programmatic CLI diagnostics:
```bash
-pyrefly lsp --verbose 2> lsp-debug.log
+pyrefly check --output-format=json main.py
+pyrefly dump-config main.py
+pyrefly lsp --help
```
-## Key Learnings
-
-1. **Fast Performance**: Pyrefly is noticeably faster than traditional Python type checkers
-2. **LSP-First Design**: Built from the ground up with LSP support in mind
-3. **Minimal Configuration**: Works well with minimal setup, unlike some alternatives
-4. **Active Development**: Configuration options and features are still evolving
-5. **Editor Agnostic**: Works with any LSP-compatible editor
-6. **Rust Performance**: Benefits from Rust's performance characteristics for large codebases
-
-## Best Practices
-
-1. Start with minimal configuration and add complexity as needed
-2. Use verbose mode during development and debugging
-3. Choose appropriate indexing mode for your workflow
-4. Monitor performance with different thread settings
-5. Keep the LSP server process isolated from your main application
-6. Use programmatic access for CI/CD integration
-
-## Resources
-
-- [Pyrefly GitHub Repository](https://github.com/facebook/pyrefly)
-- [Language Server Protocol Specification](https://microsoft.github.io/language-server-protocol/)
-- [LSP Client Libraries](https://microsoft.github.io/language-server-protocol/implementors/sdks/)
\ No newline at end of file
+See [IDE features](https://pyrefly.org/en/docs/IDE-features/),
+[integration limitations](../../lsp_types/pyrefly/KNOWN_LIMITATIONS.md), and
+[the 1.3.0 release](https://github.com/facebook/pyrefly/releases/tag/1.3.0).
diff --git a/assets/scripts/lsp_schema.py b/assets/scripts/lsp_schema.py
index 65c966c..ce93e1e 100644
--- a/assets/scripts/lsp_schema.py
+++ b/assets/scripts/lsp_schema.py
@@ -1,6 +1,5 @@
# generated by datamodel-codegen:
# filename: lsp.schema.json
-# timestamp: 2026-08-30T19:54:45+00:00
from __future__ import annotations
diff --git a/docs/FEATURE_VERIFICATION.md b/docs/FEATURE_VERIFICATION.md
index 2611d7e..72d38cd 100644
--- a/docs/FEATURE_VERIFICATION.md
+++ b/docs/FEATURE_VERIFICATION.md
@@ -1,101 +1,159 @@
-# Feature Verification Guide
+# Feature verification and maintenance runbook
-This document describes how to verify and update the feature support matrix in the README.
+Run this after backend releases, dependency updates or changes to `Session`.
+Keep the README to a short overview; put the evidence and qualifications here.
-## Verification Process
+## 1. Update and record versions
-### Step 1: Run the Test Suite
+Check live PyPI/npm metadata and tagged upstream release notes. Search indexes can
+lag a release. Prefer stable releases; distinguish an LSP binary from a CLI,
+editor extension, TSP server or browser WASM package.
-```bash
-uv run pytest tests/test_session.py -v
+```sh
+uv sync --all-extras --upgrade
+uv run pyrefly --version
+uv run ty --version
+uv run zuban --version
+npm view pyright version
+npm view basedpyright version
```
-Tests are parametrized across all backends (Pyright, Pyrefly, ty, Zuban). Key indicators:
-- **PASSED**: Feature works for that backend
-- **XFAIL**: Known limitation, documented with reason
-- **FAILED**: Regression or new issue
+Commit `uv.lock` with the dependency changes. Minimum supported versions in
+`pyproject.toml` are compatibility floors, not the versions verified in this run;
+raise them only if a change requires it. Generate schemas from upstream with:
-### Step 2: Review xfail Markers
-
-In `tests/`, search for `xfail` to find documented limitations:
-
-```python
-# Example from test_pool.py::test_session_warmup_on_recycle:
-if backend_name == "ty":
- pytest.xfail("ty hover doesn't include variable names in output")
+```sh
+make generate-latest-types
```
-Each xfail message explains why the feature is limited.
-
-### Step 3: Check Backend Capabilities
-
-Each backend declares its LSP capabilities in `get_lsp_capabilities()`:
-- `lsp_types/pyright/backend.py`
-- `lsp_types/pyrefly/backend.py`
-- `lsp_types/ty/backend.py`
-- `lsp_types/zuban/backend.py`
-
-Features declared here indicate what the client advertises to the server.
-
-### Step 4: Review Known Limitations
-
-- [Pyrefly Known Limitations](../lsp_types/pyrefly/KNOWN_LIMITATIONS.md)
-- [ty Known Limitations](../lsp_types/ty/KNOWN_LIMITATIONS.md)
-- [Zuban Known Limitations](../lsp_types/zuban/KNOWN_LIMITATIONS.md)
-
-## Updating the Feature Table
+If `make` is unavailable, execute its recipes directly (`uv run` / `uvx`).
+Review the schema and generated diff; don't edit generated types manually. Repeat
+generation to check reproducibility. The recipes disable timestamps and explicitly
+select Black/isort before Ruff formatting.
-### When to Update
+## 2. Test both Pyright distributions
-1. **After adding new Session API methods**: Add test, run it, update table
-2. **After upgrading backend versions**: Re-run tests, check for improvements, update version line
-3. **After backend releases announce new features**: Test and document
+`PyrightBackend` launches `pyright-langserver`. Both npm distributions provide
+that executable, so install them in **separate directories**:
-### Updating Version Numbers
-
-Update the "Last verified" line in README when re-testing:
-
-```bash
-# Check installed versions
-pyright --version # or basedpyright --version
-pyrefly --version
-ty --version
-zuban --version
+```sh
+npm install --prefix /tmp/lsp-basedpyright basedpyright@1.40.1
+npm install --prefix /tmp/lsp-pyright pyright@1.1.414
+PATH=/tmp/lsp-basedpyright/node_modules/.bin:$PATH uv run pytest tests -q
+PYRIGHT_PACKAGE=pyright PATH=/tmp/lsp-pyright/node_modules/.bin:$PATH \
+ uv run pytest tests -k Pyright -q
+uvx pyright --pythonpath .venv/bin/python .
+uvx ruff check .
```
-### Status Symbols
-
-| Symbol | Meaning | When to Use |
-|--------|---------|-------------|
-| :white_check_mark: | Fully supported | Test passes without xfail |
-| :warning: | Partial support | Test has xfail or conditional skip |
-| :x: | Not supported | Feature fails or is documented as unsupported |
-| :grey_question: | Unknown | Not exposed in Session API or not tested |
-
-### Adding Notes
+Update those exact npm versions on the next maintenance run. `PYRIGHT_PACKAGE`
+selects the tests' expected Microsoft behavior; it does not choose the executable.
+The default test expectation is basedpyright. CI tests both distributions on Python
+3.12, 3.13 and 3.14; npm versions float there, while Python installs use the lock.
+
+Review every failure, xfail and skip. A passing echo of a completion item does not
+prove resolution adds documentation. Similarly, client capabilities describe what
+**we advertise**, not what the server implements. Inspect `initialize` responses
+and send a real request with a control case when validating a claim.
+
+## Verified Session features
+
+Snapshot **2026-09-11**: Pyright 1.1.414, basedpyright 1.40.1, Pyrefly 1.3.0,
+ty 0.0.80, Zuban 0.9.3. “Yes” means the named integration test passes for its
+fixture, not comprehensive conformance for every Python program.
+
+| Feature | Pyright | basedpyright | Pyrefly | ty | Zuban |
+|---|---|---|---|---|---|
+| Diagnostics | Yes | Yes | Yes | Yes | Yes |
+| Hover | Yes | Yes | Yes | Type only | Yes |
+| Completion | Yes | Yes | Yes | Yes | Yes |
+| Completion resolution | Adds docs | Adds docs | Echo | Error -32601 | Adds docs |
+| Signature help | Yes | Yes | Yes | Yes | Yes |
+| Rename | Yes | Yes | Yes | Yes | Yes |
+| Semantic tokens | Error -32601 | Yes | Fallback legend | Yes | Yes |
+
+Evidence lives in `tests/test_session.py`: `test_session_diagnostics`,
+`test_session_hover`, `test_session_completion`, `test_session_signature_help`,
+`test_session_rename`, and the `test_session_semantic_tokens*` tests. Completion
+resolution checks actual added docstrings, the Pyrefly echo and ty's error.
+Microsoft Pyright omits optional `serverInfo` and semantic-token metadata;
+metadata/recycling tests explicitly cover that behavior.
+
+The single expected failure is `test_session_warmup_on_recycle[ty]`, whose
+variable-name assertion does not match ty's type-only hover. Normal hover works.
+
+Go to definition, references, code actions, formatting and broader upstream
+features are **not verified by this Session suite**. The low-level `LSPProcess`
+API exposes requests, but exposure alone does not establish server support.
+
+## 3. Recheck the integration boundaries
+
+```sh
+PATH=/tmp/lsp-basedpyright/node_modules/.bin:$PATH \
+ uv run python examples/extract_semantic_legends.py
+rg -n 'xfail|skip' tests
+```
-- Keep notes concise (under 50 characters)
-- Reference the specific limitation (e.g., "shows type only, not variable name")
-- Use semicolons to separate multiple backend notes
+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
+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).
+Check manual config schemas against tagged source/docs, including severity values,
+nested sections and renamed/deprecated settings. Keep historical evidence labeled.
+
+## 4. Refresh the browser playground
+
+From `playground/`:
+
+```sh
+npm update
+npm audit
+npm run fetch-wasm
+npm run test:wasm
+npm run build
+```
-## Evidence Mapping
+Review the pinned releases/checksums in `fetch-wasm.sh`; `npm update` alone cannot
+update WASM or the browser-basedpyright CDN worker. ty's source build needs a Rust
+toolchain, the WASM target and a native compiler. Pyrefly has a release WASM archive.
+Verify actual browser diagnostics, hover and edits for every engine; successful
+TypeScript compilation does not prove workers or WASM load. Test the configured
+GitHub Pages base path and confirm the build includes `dist/pyrefly` and `dist/ty` assets.
+
+For the optional browser regression check, install Playwright outside the project
+and run the production preview in another terminal:
+
+```sh
+npm install --prefix /tmp/lsp-browser-check playwright
+/tmp/lsp-browser-check/node_modules/.bin/playwright install chromium
+npm run preview -- --host 127.0.0.1
+# In another terminal, from playground/:
+PLAYWRIGHT_MODULE=/tmp/lsp-browser-check/node_modules/playwright/index.mjs \
+ node browser.test.mjs
+```
-| Feature | Test Function | What to Check |
-|---------|---------------|---------------|
-| Diagnostics | `test_session_diagnostics` | All backends pass; virtual documents work everywhere (ty since 0.0.16) |
-| Hover | `test_session_hover` | All backends pass; `if backend_name != "ty"` guards the variable-name assertion (ty shows type only) |
-| Completion | `test_session_completion` | All backends pass |
-| Completion Resolution | `test_session_completion` | `if backend_name != "ty"` skips resolution for ty only (it errors `-32601`). Pyrefly is invoked but only its echo is asserted, which is why the README still marks it unsupported |
-| Signature Help | `test_session_signature_help` | No xfails expected |
-| Rename | `test_session_rename` | All backends pass; Pyrefly rename was fixed in 1.1.1 (the former xfail is removed) |
-| Semantic Tokens | `test_session_semantic_tokens` | No xfails expected |
+The deterministic concurrency check uses Vite's dev modules to control completion
+order, including late diagnostics, edits during debounce and adapter disposal:
-## Untested Features
+```sh
+npm run dev -- --host 127.0.0.1
+# In another terminal, from playground/:
+PLAYWRIGHT_MODULE=/tmp/lsp-browser-check/node_modules/playwright/index.mjs \
+ node concurrency.test.mjs
+```
-Features declared in backend capabilities but not exposed in Session API:
-- Go to Definition (Pyrefly, ty, Zuban declare it)
-- Find References (Pyrefly, ty, Zuban declare it)
-- Code Actions
-- Formatting
+## 5. Publish the evidence in the docs
-To test these, use the low-level `LSPProcess` API directly.
+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.
diff --git a/docs/INTEGRATION_NOTES.md b/docs/INTEGRATION_NOTES.md
index bbd08a7..bac08bc 100644
--- a/docs/INTEGRATION_NOTES.md
+++ b/docs/INTEGRATION_NOTES.md
@@ -136,10 +136,18 @@ new monaco.lsp.MonacoLspClient(transport);
- 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):**
-Verification was documentary only: there is no npm on the machine, so nothing was
-installed or executed. Evidence came from the published npm artifacts (`monaco.d.ts`
+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 —
diff --git a/docs/MAINTENANCE_2026-09-11.md b/docs/MAINTENANCE_2026-09-11.md
new file mode 100644
index 0000000..7e95632
--- /dev/null
+++ b/docs/MAINTENANCE_2026-09-11.md
@@ -0,0 +1,99 @@
+# Maintenance — September 11, 2026
+
+## Release snapshot
+
+Checked live registries and tagged releases, rather than cached search results.
+Python lockfile: Pyrefly **1.2.0 → 1.3.0**, ty **0.0.75 → 0.0.80**,
+Zuban **0.9.2 → 0.9.3**. Separately installed Microsoft Pyright **1.1.414** and
+basedpyright **1.40.1**. Refreshed all resolvable Python dependencies, including
+datamodel-code-generator **0.76.0 → 0.79.0**, and declared its formatter extras.
+Compatibility floors remain unchanged; the lock records the tested versions.
+
+Browser packages: browser-basedpyright **1.28.1 → 1.40.1**, TypeScript **7.0.2**,
+Vite **8.3.0**, JSON-RPC **9.0.2**, LSP protocol **3.18.3**, DOMPurify **3.4.15**.
+Monaco remains **0.56.0**. Pyrefly WASM is pinned to **1.3.0** and ty's WASM source
+to **0.0.80**, both with SHA-256 checks. The browser UI correctly names basedpyright.
+
+Release sources and comparisons: [field guide](research/landscape.md),
+[Pyrefly investigation](research/pyrefly.md).
+
+## What the investigation changed
+
+- **Highlighting:** Pyrefly 1.3 emits five new string modifiers. Appended them to
+ 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).
+- **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
+ backend limitation documents with positive-control probes and explicitly
+ marked older evidence. Removed obsolete Pyrefly configuration advice.
+- **Pyright distinction:** Microsoft Pyright lacks semantic tokens and omits
+ optional server metadata that basedpyright supplies. Four old test assumptions
+ were specific to the fork. Tests now check the correct behavior for each;
+ CI explicitly selects both distributions and each Python interpreter.
+- **Protocol generation:** upstream adds partial-result tokens to inline values,
+ inlay hints and inline completion requests. Regenerated the public types and
+ removed generated timestamps so repeated builds produce identical files.
+- **Browser integration:** fixed WASM copying/URLs, repeated ty logger setup,
+ inconsistent Python targets and diagnostic races around edits/backend switches.
+ 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.
+
+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
+checks, not an overall winner. ty's incremental architecture and type narrowing,
+Zuban's Mypy migration/editor inference, and basedpyright's controls provide more
+useful personalities than invented performance or reliability scores.
+
+## Validation
+
+Host: Linux x86_64, Python **3.12.14**. Commands use `uv`; npm servers were installed
+in separate temporary prefixes, with the intended binary first in `PATH`.
+
+- Full suite with basedpyright: **251 passed, 1 xfailed**. The expected exception
+ is ty's variable-name hover assertion; ordinary hover passes.
+- Microsoft Pyright separately: **35 passed** (`PYRIGHT_PACKAGE=pyright`, `-k Pyright`).
+- `uvx pyright --pythonpath .venv/bin/python .`: **0 errors, warnings or information**.
+- Ruff lint and formatting checks: **passed**; `git diff --check`: **passed**.
+- `uv build`: wheel and sdist built successfully; no package version bump or publication.
+- Schema generation: all five generated files reproduced **byte-for-byte**.
+ `make` was unavailable, so the Makefile's recipes were run directly.
+- Backend probes confirmed completion-documentation enrichment with Pyright,
+ basedpyright and Zuban, Pyrefly's echo even after stripping metadata, and ty's
+ `-32601` rejection. The suite now checks these behaviors explicitly.
+- WASM tests: **2 passed**, exercising errors, hover and clearing after edits.
+- Playground production build: **passed**; `npm audit`: **0 vulnerabilities**;
+ `npm outdated`: **no entries**.
+- Chromium **153** / Playwright **1.63.0**: rendered diagnostics, hover and clearing
+ passed across basedpyright → Pyrefly → ty → Pyrefly → ty → basedpyright, including
+ repeated WASM initialization, with no console/page errors. Rapid backend
+ selection also preserved the final choice.
+- `concurrency.test.mjs`: **passed** controlled late-result, edit-during-debounce,
+ adapter-switch/detach, diagnostic-version, superseded-request, timeout and
+ disposal checks. Timed-out requests no longer return old cached diagnostics.
+
+The [runbook](FEATURE_VERIFICATION.md) contains reproducible commands, including
+the optional browser regression script without a new project dependency.
+
+## Limits and follow-up
+
+Local Python testing used 3.12; the revised CI covers 3.12, 3.13 and 3.14, but a
+remote CI run was not triggered. The artwork and browser assets were inspected
+locally; no deployment was performed. basedpyright's browser worker still loads
+from pinned jsDelivr URLs. ty WASM requires Rust and a native compiler; this run
+built it in an isolated Node 24 container. Its optimized WASM is about 18 MB,
+and Pyrefly's about 14 MB; generated modules stay ignored and CI rebuilds them.
+
+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)).
+
+These checks establish the tested integration behaviors, not full conformance or
+an independent speed leaderboard. CLI, LSP, TSP and browser WASM features remain
+separate in the evidence. No backend is labeled universally fastest or fragile.
diff --git a/docs/SEMANTIC_TOKENS.md b/docs/SEMANTIC_TOKENS.md
index 5ebb003..c451ba1 100644
--- a/docs/SEMANTIC_TOKENS.md
+++ b/docs/SEMANTIC_TOKENS.md
@@ -49,9 +49,13 @@ See `examples/extract_semantic_legends.py` for a complete working example.
## Token Legends by Backend
-### Pyright (basedpyright)
+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.
-> Last verified: basedpyright 1.36.2
+### basedpyright (through PyrightBackend)
+
+> Last verified: basedpyright 1.40.1 (2026-09-11)
#### Token Types
@@ -91,8 +95,8 @@ See `examples/extract_semantic_legends.py` for a complete working example.
### Pyrefly
-> Last verified: Pyrefly 1.2.0 (2026-08-30)
-> Legend source: [semantic_tokens.rs](https://github.com/facebook/pyrefly/blob/main/pyrefly/lib/state/semantic_tokens.rs)
+> 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.
@@ -139,12 +143,17 @@ Pyrefly does not advertise its legend via LSP initialization, but the token mapp
| 8 | `documentation` |
| 9 | `defaultLibrary` |
| 10 | `selfParameter` |
+| 11 | `byteString` |
+| 12 | `formatString` |
+| 13 | `rawString` |
+| 14 | `stringPrefix` |
+| 15 | `templateString` |
---
### ty
-> Last verified: ty 0.0.75 (2026-08-30)
+> Last verified: ty 0.0.80 (2026-09-11)
#### Token Types
@@ -165,6 +174,8 @@ Pyrefly does not advertise its legend via LSP initialization, but the token mapp
| 12 | `decorator` |
| 13 | `builtinConstant` |
| 14 | `typeParameter` |
+| 15 | `operator` |
+| 16 | `regexp` |
#### Token Modifiers
@@ -179,7 +190,7 @@ Pyrefly does not advertise its legend via LSP initialization, but the token mapp
### Zuban
-> Last verified: Zuban 0.9.2 (2026-08-30)
+> 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).
@@ -308,10 +319,11 @@ The canonical legend follows LSP standard ordering, with backend-specific tokens
- 23: label (LSP standard)
- 24-26: Backend-specific (selfParameter, clsParameter, builtinConstant)
-**Token Modifiers (bit 0-13):**
+**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
---
diff --git a/docs/USAGE.md b/docs/USAGE.md
new file mode 100644
index 0000000..896e915
--- /dev/null
+++ b/docs/USAGE.md
@@ -0,0 +1,50 @@
+# 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.
diff --git a/docs/research/landscape.md b/docs/research/landscape.md
new file mode 100644
index 0000000..09cb094
--- /dev/null
+++ b/docs/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/research/pyrefly.md b/docs/research/pyrefly.md
new file mode 100644
index 0000000..2b7c6ec
--- /dev/null
+++ b/docs/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/lsp_types/pyrefly/KNOWN_LIMITATIONS.md b/lsp_types/pyrefly/KNOWN_LIMITATIONS.md
index dd34b63..e061f06 100644
--- a/lsp_types/pyrefly/KNOWN_LIMITATIONS.md
+++ b/lsp_types/pyrefly/KNOWN_LIMITATIONS.md
@@ -1,49 +1,46 @@
-# Pyrefly Backend - Known Limitations
+# Pyrefly backend: known limitations
-This document describes known limitations and behavioral differences when using the Pyrefly backend compared to other LSP backends (Pyright, ty).
+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)
-## 1. Completion Item Resolution Is a No-op
+## Completion resolution is an echo
-**Limitation**: Pyrefly accepts the `completionItem/resolve` LSP request but returns the item unchanged.
+`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.
-**Behavior**: Calling `resolve_completion()` does not raise (unlike ty), but the resolved item carries no additional `detail`, `documentation`, or other metadata beyond what the initial completion already provided.
+Initial completions already include type details and documentation, so ordinary
+completion remains useful. Calling `resolve_completion()` succeeds but does
+not retrieve additional metadata.
-**Impact**: Completion items won't gain extended documentation from resolution. Basic completion works fine.
+## Semantic-token legend is not advertised
-## 2. Configuration Key Format
+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.
-**Note**: Pyrefly uses TOML configuration (`pyrefly.toml`) with kebab-case keys (e.g., `python-version`, `search-path`). The backend automatically converts snake_case Python keys to kebab-case when writing the config file.
+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).
----
+## Configuration and API boundaries
-## Previously Documented, Now Resolved
+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.
-- **Rename operations disabled for external files** (documented for Pyrefly 0.32.0): Earlier Pyrefly versions treated session files as "external" and returned no rename edits, so `get_rename_edits()` was marked `xfail`. As of Pyrefly 1.1.1 rename returns proper edits and the test is now a regular passing case.
+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
-## Version Information
-
-These limitations were last verified with Pyrefly 1.2.0 (verified 2026-08-30;
-1.2.0 is the newest stable release on PyPI, released 2026-08-01):
-
-- `completionItem/resolve` remains a no-op. The resolved item comes back
- byte-identical, and an item with `detail`/`documentation` stripped before the
- request comes back still stripped — a pure echo, not an already-complete
- result. Pyrefly nonetheless advertises `completionProvider.resolveProvider:
- true`. Little is lost in practice: Pyrefly front-loads `detail` and
- `documentation` into the initial completion items.
-- The semantic-tokens legend is still not advertised: `semanticTokensProvider`
- is absent from the initialize result entirely (not merely missing its
- `legend`), even though the server answers `textDocument/semanticTokens/full`.
- The hardcoded `PYREFLY_LEGEND` therefore remains required. ty and Zuban both
- advertise a legend.
-- Rename returns proper edits for the virtual session document and spans
- on-disk sibling modules; the fix has not regressed.
-
-**Forward-looking:** Pyrefly `main` (heading to 1.3.0) appends five token
-modifiers after `selfParameter` — `byteString`, `formatString`, `rawString`,
-`stringPrefix`, `templateString` (bits 11-15). Verified against 1.3.0.dev3:
-those bits are emitted and silently dropped by `normalize_tokens()` because
-they are absent from both `PYREFLY_LEGEND` and `CANONICAL_TOKEN_MODIFIERS`.
-Both lists must be extended when 1.3.0 ships. Token *types* are unchanged.
+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.
diff --git a/lsp_types/pyrefly/config_schema.py b/lsp_types/pyrefly/config_schema.py
index 72cef4b..b9df971 100644
--- a/lsp_types/pyrefly/config_schema.py
+++ b/lsp_types/pyrefly/config_schema.py
@@ -1,6 +1,7 @@
# Pyrefly configuration schema
# Based on official Pyrefly documentation: https://pyrefly.org/en/docs/configuration/
# CLI reference: https://github.com/facebook/pyrefly
+# Reviewed against Pyrefly 1.3.0 (2026-09-11).
#
# Note: Field names use snake_case (Python convention) but are automatically
# converted to kebab-case when written to pyrefly.toml (official format).
@@ -19,22 +20,21 @@
"skip-and-infer-return-any",
]
-# Error severity configuration (error-code -> enabled/disabled)
-ErrorConfig = dict[str, bool]
+ErrorSeverity = Literal["error", "warn", "info", "ignore"]
+# Boolean values remain accepted by Pyrefly for compatibility.
+ErrorConfig = dict[str, bool | ErrorSeverity]
class Model(TypedDict):
"""
Pyrefly Configuration Schema
- Comprehensive type definitions for all Pyrefly configuration options.
- Field names use snake_case following Python conventions. Pyrefly accepts
- both snake_case and kebab-case in TOML configuration files.
+ Common Pyrefly configuration options and backend launch settings.
+ Field names use snake_case; the backend writes kebab-case TOML keys.
All fields are NotRequired for maximum flexibility. For arbitrary fields
not yet in this schema:
- - Use cast(dict, config) to add extra keys
- - Or pass plain dict to write_config (accepts Mapping[str, Any])
+ pass a plain dictionary to Session.create(options=...).
Official Documentation: https://pyrefly.org/en/docs/configuration/
"""
@@ -94,8 +94,8 @@ class Model(TypedDict):
python_version: NotRequired[str]
"""Python version for sys.version checks, e.g. "3.13.0" (USER REQUESTED)"""
- python_platform: NotRequired[str]
- """Platform for sys.platform checks, e.g. "linux", "darwin", "win32" """
+ python_platform: NotRequired[str | list[str]]
+ """One platform, multiple platforms, or "all" for sys.platform checks."""
conda_environment: NotRequired[str]
"""Conda environment name for querying Python configuration"""
@@ -116,8 +116,23 @@ class Model(TypedDict):
typeshed_path: NotRequired[str]
"""Override bundled typeshed with custom path"""
+ preset: NotRequired[Literal["off", "basic", "legacy", "default", "strict", "all"]]
+ """Select the starting set of diagnostics and checking behavior."""
+
untyped_def_behavior: NotRequired[UntypedDefBehavior]
- """How to handle untyped function definitions (default: check-and-infer-return-type)"""
+ """Deprecated upstream; use check_unannotated_defs and infer_return_types."""
+
+ check_unannotated_defs: NotRequired[bool]
+ """Check unannotated function bodies (default: true with the default preset)."""
+
+ infer_return_types: NotRequired[Literal["never", "annotated", "checked"]]
+ """Infer returns for no functions, annotated functions, or all checked functions."""
+
+ treat_all_caps_as_final: NotRequired[bool]
+ """Reject reassignment of ALL_CAPS names (opt-in)."""
+
+ required_version: NotRequired[str]
+ """PEP 440 constraint on the Pyrefly version, e.g. ">=1.3,<1.4"."""
infer_with_first_use: NotRequired[bool]
"""Infer container types from first usage patterns (default: true)"""
@@ -138,6 +153,9 @@ class Model(TypedDict):
replace_imports_with_any: NotRequired[list[str]]
"""Module globs to unconditionally replace with typing.Any"""
+ replace_untyped_imports_with_any: NotRequired[list[str]]
+ """Replace matching installed packages lacking stubs or py.typed with Any."""
+
ignore_missing_imports: NotRequired[list[str]]
"""Module globs to replace with typing.Any when not found"""
@@ -149,4 +167,16 @@ class Model(TypedDict):
# ========================================================================
errors: NotRequired[ErrorConfig]
- """Error severity configuration: {"error-code": bool, ...}"""
+ """Error code to severity (or legacy enabled/disabled boolean)."""
+
+ baseline: NotRequired[str]
+ """Path to a baseline file of existing diagnostics."""
+
+ baseline_error_level: NotRequired[ErrorSeverity]
+ """Severity for diagnostics matching the baseline (default: ignore)."""
+
+ baseline_matching_mode: NotRequired[Literal["column", "concise-description"]]
+ """Match baseline entries by source column or concise diagnostic description."""
+
+ baseline_format: NotRequired[Literal["full", "minimal"]]
+ """Amount of metadata written to baseline entries."""
diff --git a/lsp_types/pyright/config_schema.py b/lsp_types/pyright/config_schema.py
index bbdbc81..92578b5 100644
--- a/lsp_types/pyright/config_schema.py
+++ b/lsp_types/pyright/config_schema.py
@@ -1,6 +1,5 @@
# generated by datamodel-codegen:
# filename: pyright.schema.json
-# timestamp: 2026-08-30T19:54:45+00:00
from __future__ import annotations
diff --git a/lsp_types/semantic_tokens.py b/lsp_types/semantic_tokens.py
index c82012f..dd4a2a5 100644
--- a/lsp_types/semantic_tokens.py
+++ b/lsp_types/semantic_tokens.py
@@ -55,6 +55,11 @@
"classMember", # bit 11 (pyright)
"parameter", # bit 12 (pyright - modifier, not to be confused with type)
"selfParameter", # bit 13 (pyrefly - modifier for self/cls parameters)
+ "byteString", # bit 14 (pyrefly 1.3+)
+ "formatString", # bit 15
+ "rawString", # bit 16
+ "stringPrefix", # bit 17
+ "templateString", # bit 18
]
# The canonical legend for Monaco/editor integration
@@ -72,7 +77,7 @@
}
# Pyrefly legend (server doesn't advertise it via LSP)
-# Source: https://github.com/facebook/pyrefly/blob/main/pyrefly/lib/state/semantic_tokens.rs
+# Source: https://github.com/facebook/pyrefly/blob/1.3.0/pyrefly/lib/state/semantic_tokens.rs
PYREFLY_LEGEND: types.SemanticTokensLegend = {
"tokenTypes": [
"namespace", # 0
@@ -111,6 +116,11 @@
"documentation", # bit 8
"defaultLibrary", # bit 9
"selfParameter", # bit 10 (for self/cls parameters)
+ "byteString", # bit 11 (1.3+; appended, compatible with older servers)
+ "formatString", # bit 12
+ "rawString", # bit 13
+ "stringPrefix", # bit 14
+ "templateString", # bit 15
],
}
diff --git a/lsp_types/ty/KNOWN_LIMITATIONS.md b/lsp_types/ty/KNOWN_LIMITATIONS.md
index 5fe5a86..e79c79a 100644
--- a/lsp_types/ty/KNOWN_LIMITATIONS.md
+++ b/lsp_types/ty/KNOWN_LIMITATIONS.md
@@ -21,13 +21,14 @@ 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 no `ty.toml` present;
+"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. What remains true for ty is that configuration cannot
-be changed *after* the session starts: to change it, create a new session.
+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
@@ -48,13 +49,15 @@ be changed *after* the session starts: to change it, create a new session.
**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. Two keys
-were verified to have a real effect there: `logLevel` (changes server log
+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`, `configuration-file`, `inlayHints`,
+`diagnosticMode`, `disableLanguageServices`, `inlayHints`,
`completions`, `pythonExtension`, `workspaceTrust`, `experimental`,
-`showSyntaxErrors`.
+`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
@@ -81,21 +84,17 @@ adjusts its warning to how much watching the client claims to support.
```
WARN Your LSP client doesn't support file watching: You may see stale results when files change outside the editor
```
-Advertising `workspace.didChangeWatchedFiles.dynamicRegistration` narrows this to
-"...doesn't support file watching **outside of project**: You may see stale results
-when **dependencies change**". Additionally advertising `relativePatternSupport`
-removes the warning entirely. This tiering is not new — it behaves identically at
-ty 0.0.70.
-
-**Why the warning is left in place**: silencing it by advertising those
-capabilities would be dishonest, not a fix. When `dynamicRegistration` is
-advertised, ty replies with a `client/registerCapability` *request*, and this
-client cannot answer it: the read loop tests `if "method" in payload` before
-`elif "id" in payload` (`lsp_types/process.py:480`), so a server-initiated
-request — which carries both — is routed to the notification listeners and never
-answered. Advertising a capability we do not implement and then leaving ty's
-request hanging is worse than the warning. A real fix needs server-request
-replies plus actual file watching.
+**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
@@ -111,7 +110,8 @@ are unaffected.
Unknown request: completionItem/resolve (-32601)
```
-**Impact**: Completion items won't have extended documentation or additional metadata that resolution typically provides. Basic completion works fine.
+**Impact**: Clients must use the initial completion response; they cannot fetch
+additional details through a separate resolution request. Basic completion works.
---
@@ -123,31 +123,26 @@ Unknown request: completionItem/resolve (-32601)
## Version Information
-These limitations were documented based on ty version 0.0.11 (January 2026), last
-verified with ty 0.0.75 (August 30, 2026), which is the newest release on PyPI.
-
-All six entries were probed directly against 0.0.75, each with a control case
-proving the probe could detect the opposite result. **None of the six was fixed.**
-The identical probe suite was then re-run against a pinned ty 0.0.70 in a throwaway
-virtualenv: behaviour was byte-identical on all six. Nothing regressed and nothing
-was fixed in 0.0.70..0.0.75, and the release notes for 0.0.71-0.0.75 contain no LSP
-change bearing on these entries. The `ty>=0.0.16` floor in `pyproject.toml` remains
-correct.
-
-Two entries read differently than their original wording suggests:
-
-- Limitation 4 is a gap in *this client*, not in ty. Supplying `workspaceFolders`
- in the initialize params (URI matching `base_path`) removes the warning with no
- loss of function: diagnostics stay correct and `ty.toml` is still applied.
- `Session.create()` does not currently send the field. A folder URI that is not a
- real directory is worse than sending none — ty falls back to default settings and
- reports no diagnostics at all.
-- Limitation 5 is tiered by client capability and should be left alone; see that
- section for why silencing it would be a regression in honesty, not a fix.
-
-Limitation 1 is narrower than originally written: the notification is still
-unhandled, but ty does accept configuration over LSP at initialization via
-`initializationOptions`. Configuration still cannot be changed after a session
-starts.
-
-Future versions may address some of these limitations.
+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.
diff --git a/lsp_types/ty/config_schema.py b/lsp_types/ty/config_schema.py
index 5333dcf..c079974 100644
--- a/lsp_types/ty/config_schema.py
+++ b/lsp_types/ty/config_schema.py
@@ -15,7 +15,7 @@
PythonPlatform = Literal["win32", "darwin", "android", "ios", "linux", "all"]
# Output format options
-OutputFormat = Literal["full", "concise"]
+OutputFormat = Literal["full", "concise", "github", "gitlab", "junit"]
class EnvironmentConfig(TypedDict, total=False):
@@ -61,6 +61,9 @@ class SrcConfig(TypedDict, total=False):
respect_ignore_files: NotRequired[bool]
"""Auto-exclude files listed in .gitignore. Default: true."""
+ exclude_scripts: NotRequired[bool]
+ """Exclude PEP 723 scripts unless explicitly passed to the CLI. Default: false."""
+
class AnalysisConfig(TypedDict, total=False):
"""
@@ -72,6 +75,18 @@ class AnalysisConfig(TypedDict, total=False):
respect_type_ignore_comments: NotRequired[bool]
"""Whether 'type: ignore' comments suppress errors. Default: true."""
+ allowed_unresolved_imports: NotRequired[list[str]]
+ """Module glob patterns exempt from unresolved-import diagnostics."""
+
+ replace_imports_with_any: NotRequired[list[str]]
+ """Module glob patterns whose imports become Any, even when resolvable."""
+
+ strict_equality_semantics: NotRequired[bool]
+ """Use strict equality inference and narrowing semantics. Default: false."""
+
+ strict_generic_narrowing: NotRequired[bool]
+ """Use strict narrowing for unspecialized generic classes. Default: false."""
+
class TerminalConfig(TypedDict, total=False):
"""
@@ -81,10 +96,10 @@ class TerminalConfig(TypedDict, total=False):
"""
error_on_warning: NotRequired[bool]
- """Exit with code 1 when warnings are emitted. Default: false."""
+ """Exit with code 1 when warnings are emitted. Default: true."""
output_format: NotRequired[OutputFormat]
- """Diagnostic message format: 'full' or 'concise'. Default: full."""
+ """Diagnostic output format for the CLI. Default: full."""
class OverrideConfig(TypedDict, total=False):
@@ -103,12 +118,15 @@ class OverrideConfig(TypedDict, total=False):
rules: NotRequired[dict[str, RuleSeverity]]
"""Rule overrides for matched files."""
+ analysis: NotRequired[AnalysisConfig]
+ """Analysis overrides for matched files."""
+
class Model(TypedDict, total=False):
"""
ty Configuration Schema
- Comprehensive type definitions for all ty configuration options.
+ Type definitions for documented ty configuration options.
Field names use snake_case following Python conventions but are
automatically converted to kebab-case when written to ty.toml.
diff --git a/lsp_types/types.py b/lsp_types/types.py
index f9df720..77c0075 100644
--- a/lsp_types/types.py
+++ b/lsp_types/types.py
@@ -1615,6 +1615,9 @@ class InlineValueParams(TypedDict):
requested."""
workDoneToken: NotRequired["ProgressToken"]
"""An optional token that a server can use to report work done progress."""
+ partialResultToken: NotRequired["ProgressToken"]
+ """An optional token that a server can use to report partial results (e.g. streaming) to
+ the client."""
class InlineValueRegistrationOptions(TypedDict):
@@ -1641,6 +1644,9 @@ class InlayHintParams(TypedDict):
"""The document range for which inlay hints should be computed."""
workDoneToken: NotRequired["ProgressToken"]
"""An optional token that a server can use to report work done progress."""
+ partialResultToken: NotRequired["ProgressToken"]
+ """An optional token that a server can use to report partial results (e.g. streaming) to
+ the client."""
class InlayHint(TypedDict):
@@ -1874,6 +1880,9 @@ class InlineCompletionParams(TypedDict):
"""The position inside the text document."""
workDoneToken: NotRequired["ProgressToken"]
"""An optional token that a server can use to report work done progress."""
+ partialResultToken: NotRequired["ProgressToken"]
+ """An optional token that a server can use to report partial results (e.g. streaming) to
+ the client."""
class InlineCompletionList(TypedDict):
diff --git a/lsp_types/zuban/KNOWN_LIMITATIONS.md b/lsp_types/zuban/KNOWN_LIMITATIONS.md
index 75ab736..7fb97ee 100644
--- a/lsp_types/zuban/KNOWN_LIMITATIONS.md
+++ b/lsp_types/zuban/KNOWN_LIMITATIONS.md
@@ -8,7 +8,7 @@ This document describes known limitations and behavioral differences when using
**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 is less capable.
+**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.
@@ -24,14 +24,14 @@ 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]` runs in the weaker Mypy-compatible mode, so
-skipping the write would silently downgrade it. Note this is observable only
+`[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.2, per the [features documentation](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)).
**Impact**: Code that accumulates stale `# type: ignore` comments will not be flagged when using this backend.
@@ -41,14 +41,14 @@ regardless of configuration, which makes the caveat easy to mis-verify.
*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.2.
+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. Verified at 0.9.2: `bad: int = "definitely not an int"` inside a
+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
@@ -80,12 +80,12 @@ callers can supply them via `Session.create`'s public `initialize_params`:
initialize_params={"initializationOptions": {"diagnosticMode": "workspace"}},
)
-Confirmed to change behavior at 0.9.2 by diffing the initialize response:
+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 sets
-`completionProvider: false`. An unknown key left the response identical to
-baseline, confirming these are read rather than ignored.
+`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"`,
@@ -99,29 +99,29 @@ client that gates requests on advertised capabilities sees a behavior change;
## Version Information
-These limitations were documented based on Zuban version 0.7.0 (April 2026), last
-verified on 2026-08-30 with Zuban 0.9.2 (released 2026-08-26, the newest release on
-PyPI). All four entries were re-probed directly against a live `zuban server`
-reporting `serverInfo {"name": "zuban", "version": "0.9.2"}`:
-
-- **1** — a pre-existing `pyproject.toml` containing `[project]`,
- `[project.scripts]`, `[build-system]`, `[tool.ruff]`, and `[tool.foo]` (with an
- inline table and a `[[tool.foo.item]]` array-of-tables) survived a `write_config`
- call with every parsed value intact; only `[tool.zuban]` was replaced. Since
- v0.22.1 the edit is format-preserving (`tomlkit`), so comments, inline tables,
- arrays-of-tables and whitespace elsewhere in the file also survive.
-- **2** — a file containing two unnecessary `# type: ignore` comments produced zero
- diagnostics, while a plain type error in a control file was reported.
-- **3** — a type error inside a value-constrained `TypeVar("T", str, bytes)` body
- (line 7) and inside a PEP 695 `[T: (str, bytes)]` body (line 2) went unreported,
- while the identical error in a plain function in the same file (lines 13 and 8)
- was caught. An upper-bounded `TypeVar("T", bound=str)` body (line 7) *was*
- checked.
-- **4** — `zuban server --help` still lists only `-h/--help`, and
- `zuban server --mode default` fails with `error: unexpected argument '--mode'
- found`. Zuban does honor LSP `initializationOptions`; see that section.
-
-Upstream still lists entries 2 and 3 verbatim under "Missing Features" in the
-[features documentation](https://docs.zubanls.com/en/latest/features.html), and the
-0.9.2 changelog records only bugfixes and conformance-test fixes. Future versions
-may address some of these limitations.
+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/lsp_types/zuban/config_schema.py b/lsp_types/zuban/config_schema.py
index b3c8dce..ecd19f0 100644
--- a/lsp_types/zuban/config_schema.py
+++ b/lsp_types/zuban/config_schema.py
@@ -16,8 +16,8 @@
from typing import Literal, NotRequired, TypedDict
-# Zuban's two operating modes. `default` is PyRight-like and recommended.
-ZubanMode = Literal["default", "mypy"]
+# `auto` selects native or Mypy-compatible behavior from project configuration.
+ZubanMode = Literal["default", "mypy", "auto"]
# Controls how Zuban infers untyped function return types.
# - `any`: behave like Mypy (return type is `Any`).
@@ -36,7 +36,7 @@ class Model(TypedDict, total=False):
"""
mode: NotRequired[ZubanMode]
- """Selects `default` (PyRight-like, recommended) or `mypy` (Mypy-compatible)."""
+ """Selects native `default`, Mypy-compatible `mypy`, or config-based `auto`."""
mypy_path: NotRequired[list[str]]
"""Additional import search paths (equivalent to Mypy's `mypy_path` / `MYPYPATH`)."""
diff --git a/playground/browser.test.mjs b/playground/browser.test.mjs
new file mode 100644
index 0000000..2fb4356
--- /dev/null
+++ b/playground/browser.test.mjs
@@ -0,0 +1,71 @@
+import assert from "node:assert/strict";
+// Keep Playwright outside the project's dependencies, e.g. install it under /tmp.
+// Run against "npm run preview" with PLAYWRIGHT_MODULE=/absolute/path/to/playwright/index.mjs.
+const { chromium } = await import(
+ process.env.PLAYWRIGHT_MODULE || "playwright"
+);
+const browser = await chromium.launch({ headless: true });
+const page = await browser.newPage();
+const errors = [];
+page.on("pageerror", (e) => errors.push(e.message));
+page.on("console", (m) => {
+ if (m.type() === "error") errors.push(m.text());
+});
+try {
+ await page.goto("http://127.0.0.1:4173/lsp-python-types/");
+ for (const name of [
+ "basedpyright",
+ "Pyrefly",
+ "ty",
+ "Pyrefly",
+ "ty",
+ "basedpyright",
+ ]) {
+ await page.getByRole("button", { name, exact: true }).click();
+ await page.waitForFunction(
+ (name) => document.querySelector("#status").textContent === name,
+ name,
+ );
+ await page.locator(".view-lines").click({ position: { x: 90, y: 12 } });
+ await page.keyboard.press("Control+KeyA");
+ await page.keyboard.insertText('value: int = "wrong"\n');
+ await page.waitForFunction(
+ () => document.querySelectorAll(".squiggly-error").length === 1,
+ );
+ await page.keyboard.press("Control+Home");
+ await page.keyboard.press("ArrowRight");
+ await page.keyboard.press("Control+KeyK");
+ await page.keyboard.press("Control+KeyI");
+ await page.locator(".monaco-hover:visible").waitFor({ state: "visible" });
+ assert.match(
+ await page.locator(".monaco-hover:visible").innerText(),
+ name === "ty" ? /Literal/ : /int/,
+ );
+ await page.keyboard.press("Escape");
+ await page.keyboard.press("Control+KeyA");
+ await page.keyboard.insertText("value: int = 42\n");
+ await page.waitForFunction(
+ () => document.querySelectorAll(".squiggly-error").length === 0,
+ );
+ console.log(`${name}: production markers, hover and clearing errors PASS`);
+ }
+ // Do not wait for initialization between selections: only the final choice may win.
+ await page.evaluate(() => {
+ for (const name of ["Pyrefly", "basedpyright", "ty"]) {
+ document.querySelector(`button[data-backend="${name}"]`).click();
+ }
+ });
+ await page.waitForFunction(
+ () => document.querySelector("#status").textContent === "ty",
+ );
+ await page.waitForTimeout(1500);
+ assert.equal(await page.locator("#status").textContent(), "ty");
+ assert.equal(
+ await page.locator("#backend-selector .active").textContent(),
+ "ty",
+ );
+ console.log("Rapid backend selections preserve the final choice: PASS");
+ assert.deepEqual(errors, []);
+} finally {
+ await browser.close();
+}
diff --git a/playground/concurrency.test.mjs b/playground/concurrency.test.mjs
new file mode 100644
index 0000000..b60d337
--- /dev/null
+++ b/playground/concurrency.test.mjs
@@ -0,0 +1,147 @@
+import assert from "node:assert/strict";
+// Run against "npm run dev"; use the same external Playwright module as browser.test.mjs.
+const { chromium } = await import(
+ process.env.PLAYWRIGHT_MODULE || "playwright"
+);
+const browser = await chromium.launch({ headless: true });
+try {
+ const page = await browser.newPage();
+ await page.goto("http://127.0.0.1:5173/lsp-python-types/");
+ await page.waitForFunction(
+ () => document.querySelector("#status").textContent === "basedpyright",
+ );
+ const snapshots = await page.evaluate(async () => {
+ const mainSource = await (await fetch("/lsp-python-types/src/main.ts")).text();
+ const editorUrl = mainSource.match(/from "([^"]*\/editor\.ts[^"]*)"/)[1];
+ const { setAdapter } = await import(editorUrl);
+ // Import Vite's exact bundled Monaco instance, including its cache query.
+ const editorSource = await (await fetch(editorUrl)).text();
+ const monaco = await import(
+ editorSource.match(/import \* as monaco from "([^"]+)"/)[1]
+ );
+ const model = monaco.editor.getModels()[0];
+ const nextTurn = () => new Promise((resolve) => setTimeout(resolve, 0));
+ const pending = [];
+ const adapter = (name) => ({
+ name,
+ updateCode: () => new Promise((resolve) => pending.push(resolve)),
+ getHover: async () => null,
+ dispose() {},
+ });
+ const diagnostic = (message) => [
+ {
+ startLineNumber: 1,
+ startColumn: 1,
+ endLineNumber: 1,
+ endColumn: 2,
+ message,
+ severity: 8,
+ },
+ ];
+ const markers = () =>
+ monaco.editor
+ .getModelMarkers({ resource: model.uri })
+ .map((marker) => ({ owner: marker.owner, message: marker.message }));
+ setAdapter(adapter("first"));
+ model.setValue("value: int = 42\n");
+ pending.shift()(diagnostic("before edit"));
+ await nextTurn();
+ const duringDebounce = markers();
+ setAdapter(adapter("old"));
+ const finishOld = pending.shift();
+ setAdapter(adapter("new"));
+ pending.shift()(diagnostic("new result"));
+ await nextTurn();
+ finishOld(diagnostic("old result"));
+ await nextTurn();
+ const afterSwitch = markers();
+ setAdapter(adapter("detached"));
+ setAdapter(null);
+ pending.shift()(diagnostic("after disposal"));
+ await nextTurn();
+ return { duringDebounce, afterSwitch, afterDetach: markers() };
+ });
+ assert.deepEqual(snapshots, {
+ duringDebounce: [],
+ afterSwitch: [{ owner: "new", message: "new result" }],
+ afterDetach: [],
+ });
+ console.log(
+ "Editor ignores results during debounce, after switching, and after detaching: PASS",
+ );
+
+ const diagnostics = await page.evaluate(async () => {
+ const { PyrightBackend } =
+ await import("/lsp-python-types/src/backends/pyright.ts");
+ const adapter = new PyrightBackend();
+ await adapter.initialize();
+ // Control the real transport's incoming publications without asking the server to analyze.
+ adapter.connection.sendNotification = async () => {};
+ const publish = (version, message) =>
+ adapter.workers[0].dispatchEvent(
+ new MessageEvent("message", {
+ data: {
+ jsonrpc: "2.0",
+ method: "textDocument/publishDiagnostics",
+ params: {
+ uri: "file:///src/main.py",
+ version,
+ diagnostics: [
+ {
+ range: {
+ start: { line: 0, character: 0 },
+ end: { line: 0, character: 1 },
+ },
+ message,
+ severity: 1,
+ },
+ ],
+ },
+ },
+ }),
+ );
+ try {
+ const first = adapter.updateCode("first");
+ let secondFinished = false;
+ const second = adapter.updateCode("second").then((result) => {
+ secondFinished = true;
+ return result;
+ });
+ publish(1, "stale");
+ await new Promise((resolve) => setTimeout(resolve, 0));
+ const ignoredOldVersion = !secondFinished;
+ publish(2, "current");
+ const results = {
+ first: await first,
+ second: (await second).map((item) => item.message),
+ ignoredOldVersion,
+ };
+ const originalTimeout = window.setTimeout;
+ window.setTimeout = (callback, delay, ...args) =>
+ originalTimeout(callback, delay === 10000 ? 0 : delay, ...args);
+ try {
+ results.timedOut = await adapter.updateCode("no response");
+ } finally {
+ window.setTimeout = originalTimeout;
+ }
+ const disposed = adapter.updateCode("disposed");
+ adapter.dispose();
+ results.disposed = await disposed;
+ return results;
+ } finally {
+ adapter.dispose();
+ }
+ });
+ assert.deepEqual(diagnostics, {
+ first: [],
+ second: ["current"],
+ ignoredOldVersion: true,
+ timedOut: [],
+ disposed: [],
+ });
+ console.log(
+ "basedpyright ignores stale versions and settles superseded, timed-out and disposed requests: PASS",
+ );
+} finally {
+ await browser.close();
+}
diff --git a/playground/fetch-wasm.sh b/playground/fetch-wasm.sh
index 1ef534f..4b48810 100755
--- a/playground/fetch-wasm.sh
+++ b/playground/fetch-wasm.sh
@@ -1,86 +1,61 @@
#!/usr/bin/env bash
-# Fetches pre-built WASM modules for Pyrefly and ty from their GitHub releases.
-# Run this before `npm run dev` if you want Pyrefly or ty backends.
+# Fetch Pyrefly's release WASM and build ty from its matching release source.
+# ty needs Rust (rustup) and a native compiler; wasm-pack runs through npm.
+# Update release versions and their SHA256 sums together during maintenance.
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
WASM_DIR="${SCRIPT_DIR}/wasm"
+PYREFLY_VERSION="1.3.0"
+TY_VERSION="0.0.80"
+WASM_PACK_VERSION="0.15.0"
+TMP_DIR=$(mktemp -d)
+trap 'rm -rf "$TMP_DIR"' EXIT
-# --- Pyrefly ---
-# Build from source using wasm-pack (requires Rust toolchain)
fetch_pyrefly() {
local dir="${WASM_DIR}/pyrefly"
- mkdir -p "$dir"
-
- if [ -f "$dir/pyrefly_wasm_bg.wasm" ]; then
- echo "Pyrefly WASM already exists, skipping."
+ if [ -f "$dir/pyrefly_wasm_bg.wasm" ] && [ -f "$dir/pyrefly_wasm.js" ] &&
+ [ "$(cat "$dir/.version" 2>/dev/null || true)" = "$PYREFLY_VERSION" ]; then
+ echo "Pyrefly ${PYREFLY_VERSION} WASM already exists."
return
fi
- echo "Building Pyrefly WASM from source..."
- local tmpdir
- tmpdir=$(mktemp -d)
- trap 'rm -rf "$tmpdir"' RETURN
-
- git clone --depth=1 https://github.com/facebook/pyrefly.git "$tmpdir/pyrefly"
- cd "$tmpdir/pyrefly/pyrefly_wasm"
-
- if ! command -v wasm-pack &>/dev/null; then
- echo "Installing wasm-pack..."
- cargo install wasm-pack
- fi
-
- wasm-pack build --target web --out-dir "$dir" --no-typescript
- echo "Pyrefly WASM built successfully."
+ echo "Downloading Pyrefly ${PYREFLY_VERSION} WASM..."
+ curl --fail --location --retry 3 \
+ "https://github.com/facebook/pyrefly/releases/download/${PYREFLY_VERSION}/pyrefly-wasm.tar.gz" \
+ -o "$TMP_DIR/pyrefly-wasm.tar.gz"
+ (cd "$TMP_DIR" && echo "6a70781b5e70ad505f18cade35bf64bfc83cfee14437e07fceafd374cc358fd0 pyrefly-wasm.tar.gz" | shasum -a 256 --check)
+ mkdir -p "$dir"
+ tar -xzf "$TMP_DIR/pyrefly-wasm.tar.gz" -C "$dir"
+ echo "$PYREFLY_VERSION" > "$dir/.version"
}
-# --- ty ---
-# Build from source using wasm-pack (requires Rust toolchain)
fetch_ty() {
local dir="${WASM_DIR}/ty"
- mkdir -p "$dir"
-
- if [ -f "$dir/ty_wasm_bg.wasm" ]; then
- echo "ty WASM already exists, skipping."
+ if [ -f "$dir/ty_wasm_bg.wasm" ] && [ -f "$dir/ty_wasm.js" ] &&
+ [ "$(cat "$dir/.version" 2>/dev/null || true)" = "$TY_VERSION" ]; then
+ echo "ty ${TY_VERSION} WASM already exists."
return
fi
- echo "Building ty WASM from source..."
- local tmpdir
- tmpdir=$(mktemp -d)
- trap 'rm -rf "$tmpdir"' RETURN
-
- git clone --depth=1 https://github.com/astral-sh/ruff.git "$tmpdir/ruff"
- cd "$tmpdir/ruff"
-
- if ! command -v wasm-pack &>/dev/null; then
- echo "Installing wasm-pack..."
- cargo install wasm-pack
- fi
-
- wasm-pack build --target web crates/ty_wasm --out-dir "$dir" --no-typescript
- echo "ty WASM built successfully."
+ command -v cargo >/dev/null || { echo "Install Rust with rustup before building ty WASM." >&2; exit 1; }
+ echo "Building ty ${TY_VERSION} WASM from release source..."
+ curl --fail --location --retry 3 \
+ "https://github.com/astral-sh/ty/releases/download/${TY_VERSION}/source.tar.gz" \
+ -o "$TMP_DIR/ty-source.tar.gz"
+ (cd "$TMP_DIR" && echo "a039d7e66d362e1707fc494f2c69dfdd2eb5333dc8fa9ad582edc125932fa5fa ty-source.tar.gz" | shasum -a 256 --check)
+ tar -xzf "$TMP_DIR/ty-source.tar.gz" -C "$TMP_DIR"
+ # The source archive contains the exact Ruff workspace used for this ty release.
+ (cd "$TMP_DIR/ruff" && npm exec --yes --package="wasm-pack@${WASM_PACK_VERSION}" -- \
+ wasm-pack build crates/ty_wasm --target web --out-dir "$dir" --no-typescript -- --locked)
+ echo "$TY_VERSION" > "$dir/.version"
}
-echo "=== Fetching WASM modules ==="
-echo ""
-
case "${1:-all}" in
- pyrefly)
- fetch_pyrefly
- ;;
- ty)
- fetch_ty
- ;;
- all)
- fetch_pyrefly
- fetch_ty
- ;;
- *)
- echo "Usage: $0 [pyrefly|ty|all]"
- exit 1
- ;;
+ pyrefly) fetch_pyrefly ;;
+ ty) fetch_ty ;;
+ all) fetch_pyrefly; fetch_ty ;;
+ *) echo "Usage: $0 [pyrefly|ty|all]" >&2; exit 1 ;;
esac
-echo ""
-echo "Done! WASM modules are in ${WASM_DIR}/"
+echo "WASM modules are in ${WASM_DIR}/"
diff --git a/playground/package-lock.json b/playground/package-lock.json
index fc149d8..a6ed84e 100644
--- a/playground/package-lock.json
+++ b/playground/package-lock.json
@@ -10,28 +10,28 @@
"dependencies": {
"monaco-editor": "^0.56.0",
"remote-web-worker": "^0.0.9",
- "vscode-jsonrpc": "^9.0.0",
- "vscode-languageserver-protocol": "^3.17.5"
+ "vscode-jsonrpc": "^9.0.2",
+ "vscode-languageserver-protocol": "^3.18.3"
},
"devDependencies": {
- "typescript": "^7.0.0",
- "vite": "^8.0.0"
+ "typescript": "^7.0.2",
+ "vite": "^8.3.0"
}
},
"node_modules/@oxc-project/types": {
- "version": "0.147.0",
- "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.147.0.tgz",
- "integrity": "sha512-IJ3s6ltHLp45S0bh7phkX+gJO7A1Wuz2EaqpAhb8WjqDwbzMiWKHhyyT42tskaWjEYXtHtVCPpnBJVT9+dcRLg==",
+ "version": "0.149.0",
+ "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.149.0.tgz",
+ "integrity": "sha512-Efcc+iF0j3Bf67YjEqIqWXbX5XddXoK/Mw4K1/JuXwRCZ8N16VR7iT23nlCc9XrveFVh/E5Rqs2StT0V8v9LdA==",
"dev": true,
"license": "MIT",
"funding": {
- "url": "https://github.com/sponsors/Boshen"
+ "url": "https://github.com/sponsors/oxc-project"
}
},
"node_modules/@rolldown/binding-android-arm-eabi": {
- "version": "1.2.6",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.6.tgz",
- "integrity": "sha512-b+jTcARdTiFLI6jB4a5XjTm0RWd6KcRfQj/I2356fxUZemiho9zQLxo0RtCuMDAyKcLo6cEltkgbQp6d1+sjjQ==",
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.8.tgz",
+ "integrity": "sha512-tN5aztYkKCte4i5SIrrz5yK/HMjEuCqCSCJa418jOV8tZ1cBY3YF2otxB1ktPxzsLA1BeTqwapK0bfjxNvHJVw==",
"cpu": [
"arm"
],
@@ -46,9 +46,9 @@
}
},
"node_modules/@rolldown/binding-android-arm64": {
- "version": "1.2.6",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.6.tgz",
- "integrity": "sha512-lkWU8ZJaRk9q3CIEY1Tc7vIFALp3Xw5NfGJo2hQg5oIqNgxWi1zI+IiDEK3r70BF5Dzol1tcXsnzsRc8NLhG+Q==",
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.8.tgz",
+ "integrity": "sha512-dIYTWl9XprMUiQFoc55KUyk/oS8SKYH3zFl0LTR7RT0Xj4hgSVyuJcroH8JUu8RcpF8fTB6E0aOwCkZoYPcDSQ==",
"cpu": [
"arm64"
],
@@ -63,9 +63,9 @@
}
},
"node_modules/@rolldown/binding-darwin-arm64": {
- "version": "1.2.6",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.6.tgz",
- "integrity": "sha512-dgR56NYnvAszm7Ob1B2/Vn0e8bUQYZH2UjVaMMtMVOCKFSfjhfLmuA/9+O+F+ajUdG6B/bSssrKW6JJYASa8jA==",
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.8.tgz",
+ "integrity": "sha512-PCSDQGXD2IyTEFrcgPyBM8jJuGmrbCMuoIOXdbEGVemruKACXoLQJrb+A45Z0L5t1RQkdfJprAYPkikbh7dzdA==",
"cpu": [
"arm64"
],
@@ -80,9 +80,9 @@
}
},
"node_modules/@rolldown/binding-darwin-x64": {
- "version": "1.2.6",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.6.tgz",
- "integrity": "sha512-vpVxFvUCFioJqug7OTvqptkc4yb8UX0AwfDmJpaR/0sWz+BUmqSVAf7c8JkUgnN8YLspb4a/N6NhTyMAmdyQ7Q==",
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.8.tgz",
+ "integrity": "sha512-Uk7lRsGhPFHVX/sAUC6D5H9Ol30dFHd6iquokll2th3LpdJ3F5CzQB+7DHn0Ri2mG+U7k2zXiPHDrwZenXhwSA==",
"cpu": [
"x64"
],
@@ -97,9 +97,9 @@
}
},
"node_modules/@rolldown/binding-freebsd-x64": {
- "version": "1.2.6",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.6.tgz",
- "integrity": "sha512-h1wG6Y6K3JlRswxsI64qQJqBAy4vrLuHgRbc8CZMGSWTOFRY6ghMApM1NKzB2I0n5xV1fjkE18SuVl2QpLeNpA==",
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.8.tgz",
+ "integrity": "sha512-DjszaTEVogPqA5bYzsEeqDCQxbcp2fexQwKcRspYji2yzR68fCf+e4fx6kBSRDwX5/brZaHw/hWS9+A/+/w9sQ==",
"cpu": [
"x64"
],
@@ -114,9 +114,9 @@
}
},
"node_modules/@rolldown/binding-linux-arm-gnueabihf": {
- "version": "1.2.6",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.6.tgz",
- "integrity": "sha512-tbCiqub0q2MVWJKgF5PoAlNWCtQydiOYSLIkd8sByqK/6MMYLJRcSXSYodqYtd0O+Fw7QaVmKKlS4oL94YRZ0w==",
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.8.tgz",
+ "integrity": "sha512-zmwa7FTmdzB6aaEEuuls18H6Ap5JmJPSoPTuXixeJZV6tG40SyLkApQtz1g8ptZtiEKqj9OM0oNLPh1AgvE31Q==",
"cpu": [
"arm"
],
@@ -131,16 +131,13 @@
}
},
"node_modules/@rolldown/binding-linux-arm64-gnu": {
- "version": "1.2.6",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.6.tgz",
- "integrity": "sha512-oxK9+baEBPhZG5HB4URY+uU04zJWeZlH6Tb9rB5DK4DF9XR1uXNLXt5Q5ZsugTKayNCNLhkcwz/ye74hRI98dg==",
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.8.tgz",
+ "integrity": "sha512-KdYQDPHwJVnbFwdTGMgxsI9SqblBlz6STGM+w1We/d5B8OWWidYH0MwkU/uA1wM5fIpO2MkOVxXrNzzuZhw9ew==",
"cpu": [
"arm64"
],
"dev": true,
- "libc": [
- "glibc"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -151,16 +148,13 @@
}
},
"node_modules/@rolldown/binding-linux-arm64-musl": {
- "version": "1.2.6",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.6.tgz",
- "integrity": "sha512-muWCk27FVBEZtv0MsK8gnfSmgczA8KQ0uRVJbTABKhkRfQc38aUrcb7fhi3BNiyseFmgcRsoMfQsSNJ+DbZdSw==",
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.8.tgz",
+ "integrity": "sha512-jFJTifHnNPY+yzOoNZQfSIysrVyXzEQPhPnOUjmD1bcQGHH6s7c8cViKWar8YplQImE5N9JRqMCLrM2CdxOrZA==",
"cpu": [
"arm64"
],
"dev": true,
- "libc": [
- "musl"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -171,16 +165,13 @@
}
},
"node_modules/@rolldown/binding-linux-ppc64-gnu": {
- "version": "1.2.6",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.6.tgz",
- "integrity": "sha512-eWDoSfU7Co2qj3vgB3Dt4lj1mG6CoWbcJQkRMP3XJplyCMtuaq3LHvPFjS9QIPvMGWVadJC04Xiy0IdcVPtnwQ==",
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.8.tgz",
+ "integrity": "sha512-FhiOziBDWPBjbcmRzfLyIJnaP7AVMFXT7YCXPjXxj7wKU3vx24RjrCNN/zjvVa+N2vVoHJwCoUBvsrN/DG3zIA==",
"cpu": [
"ppc64"
],
"dev": true,
- "libc": [
- "glibc"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -191,16 +182,13 @@
}
},
"node_modules/@rolldown/binding-linux-s390x-gnu": {
- "version": "1.2.6",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.6.tgz",
- "integrity": "sha512-2bWNjRSIayvupRKxXUY2tWG9fYdoUlTqWywHRvE8Eq3GvuQ+f2HeIkve697fIt+IQs/PV8yFsdWuhp1aJ1PdnA==",
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.8.tgz",
+ "integrity": "sha512-WnHfADMzOV2Y55wlx1hzzQnar/wDt/VdvWSD99r18Mz9ylNieIGOkRx3UV21h7m/eJvjySYJkO26VvGNFkwsIQ==",
"cpu": [
"s390x"
],
"dev": true,
- "libc": [
- "glibc"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -211,16 +199,13 @@
}
},
"node_modules/@rolldown/binding-linux-x64-gnu": {
- "version": "1.2.6",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.6.tgz",
- "integrity": "sha512-KekI0gS0wLxe1UBSQSjenBVwou/JkcQPDzBPICGZjxUv9k3RteHDPBQaiOicZUFKRIH2wKEimGwVpnJsbPzu7w==",
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.8.tgz",
+ "integrity": "sha512-H9tRr5ibfXFVLxbPOseVewewFpl28zcEdjRDt2FTUZU7odxP0gEv1ki4/kGmcGOh78oRwZuuQllGLZ9zTJp84g==",
"cpu": [
"x64"
],
"dev": true,
- "libc": [
- "glibc"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -231,16 +216,13 @@
}
},
"node_modules/@rolldown/binding-linux-x64-musl": {
- "version": "1.2.6",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.6.tgz",
- "integrity": "sha512-TvtPnfVr+HtyGiDmPK4VWmlNm7QhNNAcK5Q9A7aOXsI8545yCyaoMaicXrFZ72JzeYjaUVk7yT243zT0jzjFKQ==",
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.8.tgz",
+ "integrity": "sha512-UefiqfM3D6IVNlZ8tSGs9+Ejjud2T+oxO0IHADU45Y+lyEjD2dVFyZHbkfX0LUb5Zugo/oIv1eCO/KVYhgYJYA==",
"cpu": [
"x64"
],
"dev": true,
- "libc": [
- "musl"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -251,9 +233,9 @@
}
},
"node_modules/@rolldown/binding-openharmony-arm64": {
- "version": "1.2.6",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.6.tgz",
- "integrity": "sha512-iOo0VEay2XFhaCcH0sps5XIimkSuOnNaZrf6+ZkoSOQBJPKNU48RkmJv0/lSpipexu5P+ouFgafe5IGr/DiQfg==",
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.8.tgz",
+ "integrity": "sha512-637Ke4kWSy6rp9cxQ9gMOXlxPgIw/c1beASV4M//3+9I4uwBVOOl74G+e3zyU3u19U7RkRl/HuewixZ/Z6+Rjg==",
"cpu": [
"arm64"
],
@@ -268,9 +250,9 @@
}
},
"node_modules/@rolldown/binding-win32-arm64-msvc": {
- "version": "1.2.6",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.6.tgz",
- "integrity": "sha512-y5NTmmasMS455JlOCO4ZM9krIchv3Mvm1crL1iUPGOPgEzSkves9n0SdC5Sjz6+qWDFhd8/JpfWMH8NSWNHe+A==",
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.8.tgz",
+ "integrity": "sha512-xWBkPOF1Q9k/Gv1nQXnVdLxKu74jXppuOM4Z3mnypVUJJJwLsMl7hNJGRAUJoG8A5MgOI1ACKM+wBFxSJzKy4A==",
"cpu": [
"arm64"
],
@@ -285,9 +267,9 @@
}
},
"node_modules/@rolldown/binding-win32-x64-msvc": {
- "version": "1.2.6",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.6.tgz",
- "integrity": "sha512-np8iZSLfXlAD4kWhiyq/u0Yt8oZDtRQ8lGhQaCXo2rl37KNjeU0GjJuwr4P3oeZ++ROfofsKNBqR5LTO8aXyWQ==",
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.8.tgz",
+ "integrity": "sha512-uz2ZvfgXbxqNwijjjbxrnvALwpyODDcgc1T1N8N3rf/DXKQmaFwmB4LX4yyjggpwN2obdQLb2rgirX5ffCWYng==",
"cpu": [
"x64"
],
@@ -666,9 +648,9 @@
}
},
"node_modules/dompurify": {
- "version": "3.4.13",
- "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.13.tgz",
- "integrity": "sha512-2vmYIoqjze2d+kakP8S/nS5shfsl587kzwEjcGlTdiksUVgFHnFCsLYDVj/JNqJVOQZGSYBTmuycv0PodwmnMQ==",
+ "version": "3.4.15",
+ "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.15.tgz",
+ "integrity": "sha512-EUBjM+B+lkDE41iE82DDSCfkoPGfXx8IxFxPMjNzm/Uk4xDet77rTN9wqlxlVg71kK7XGuUMv6wUxJUwwv+Xyw==",
"license": "(MPL-2.0 OR Apache-2.0)",
"optionalDependencies": {
"@types/trusted-types": "^2.0.7"
@@ -850,9 +832,6 @@
"arm64"
],
"dev": true,
- "libc": [
- "glibc"
- ],
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -874,9 +853,6 @@
"arm64"
],
"dev": true,
- "libc": [
- "musl"
- ],
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -898,9 +874,6 @@
"x64"
],
"dev": true,
- "libc": [
- "glibc"
- ],
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -922,9 +895,6 @@
"x64"
],
"dev": true,
- "libc": [
- "musl"
- ],
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -1003,9 +973,9 @@
}
},
"node_modules/nanoid": {
- "version": "3.3.18",
- "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz",
- "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==",
+ "version": "3.3.19",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.19.tgz",
+ "integrity": "sha512-Y2tUNy4ouw6tq5oDSKeQYGOyhkUBhNOcGV/02KC+6kd9eDGqdZd++mjMiIDilrBYvjEnCYvVtsuHCuP+okSfug==",
"dev": true,
"funding": [
{
@@ -1029,9 +999,9 @@
"license": "ISC"
},
"node_modules/picomatch": {
- "version": "4.0.5",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz",
- "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==",
+ "version": "4.0.7",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz",
+ "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==",
"dev": true,
"license": "MIT",
"engines": {
@@ -1042,9 +1012,9 @@
}
},
"node_modules/postcss": {
- "version": "8.5.26",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.26.tgz",
- "integrity": "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==",
+ "version": "8.5.28",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.28.tgz",
+ "integrity": "sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==",
"dev": true,
"funding": [
{
@@ -1062,7 +1032,7 @@
],
"license": "MIT",
"dependencies": {
- "nanoid": "^3.3.17",
+ "nanoid": "^3.3.18",
"picocolors": "^1.1.1",
"source-map-js": "^1.2.1"
},
@@ -1077,13 +1047,13 @@
"license": "MIT"
},
"node_modules/rolldown": {
- "version": "1.2.6",
- "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.6.tgz",
- "integrity": "sha512-vMM4q3aixf46GiF1Kok8jDPFsEpXgFWGjUHXNkNHNm+Y2adXAG2dbX91jkti3i0ZRsOlcmbuzAz1poObSHCmUA==",
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.8.tgz",
+ "integrity": "sha512-Z67nTmhZe7anqnM/EjI392w5i/ANUinjip7QYsOyN37oayduxt3ksdX0hf5OOamkAd53BiIHfbfSzfUmzKFQqQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@oxc-project/types": "=0.147.0",
+ "@oxc-project/types": "=0.149.0",
"@rolldown/pluginutils": "^1.0.0"
},
"bin": {
@@ -1093,21 +1063,21 @@
"node": "^20.19.0 || >=22.12.0"
},
"optionalDependencies": {
- "@rolldown/binding-android-arm-eabi": "1.2.6",
- "@rolldown/binding-android-arm64": "1.2.6",
- "@rolldown/binding-darwin-arm64": "1.2.6",
- "@rolldown/binding-darwin-x64": "1.2.6",
- "@rolldown/binding-freebsd-x64": "1.2.6",
- "@rolldown/binding-linux-arm-gnueabihf": "1.2.6",
- "@rolldown/binding-linux-arm64-gnu": "1.2.6",
- "@rolldown/binding-linux-arm64-musl": "1.2.6",
- "@rolldown/binding-linux-ppc64-gnu": "1.2.6",
- "@rolldown/binding-linux-s390x-gnu": "1.2.6",
- "@rolldown/binding-linux-x64-gnu": "1.2.6",
- "@rolldown/binding-linux-x64-musl": "1.2.6",
- "@rolldown/binding-openharmony-arm64": "1.2.6",
- "@rolldown/binding-win32-arm64-msvc": "1.2.6",
- "@rolldown/binding-win32-x64-msvc": "1.2.6"
+ "@rolldown/binding-android-arm-eabi": "1.2.8",
+ "@rolldown/binding-android-arm64": "1.2.8",
+ "@rolldown/binding-darwin-arm64": "1.2.8",
+ "@rolldown/binding-darwin-x64": "1.2.8",
+ "@rolldown/binding-freebsd-x64": "1.2.8",
+ "@rolldown/binding-linux-arm-gnueabihf": "1.2.8",
+ "@rolldown/binding-linux-arm64-gnu": "1.2.8",
+ "@rolldown/binding-linux-arm64-musl": "1.2.8",
+ "@rolldown/binding-linux-ppc64-gnu": "1.2.8",
+ "@rolldown/binding-linux-s390x-gnu": "1.2.8",
+ "@rolldown/binding-linux-x64-gnu": "1.2.8",
+ "@rolldown/binding-linux-x64-musl": "1.2.8",
+ "@rolldown/binding-openharmony-arm64": "1.2.8",
+ "@rolldown/binding-win32-arm64-msvc": "1.2.8",
+ "@rolldown/binding-win32-x64-msvc": "1.2.8"
}
},
"node_modules/source-map-js": {
@@ -1173,16 +1143,16 @@
}
},
"node_modules/vite": {
- "version": "8.2.2",
- "resolved": "https://registry.npmjs.org/vite/-/vite-8.2.2.tgz",
- "integrity": "sha512-cFKLV/PRgAUlIRm5WjMjJ86jrftzpqcgH+Us+DS8mI3CDNiH30Whrz8uHL3+MOLPAgqbMBAqWdAHAphOAM+z/Q==",
+ "version": "8.3.0",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-8.3.0.tgz",
+ "integrity": "sha512-lhZBVvEHefgE+HQZC9O7EBJgCU/nVzFNl7vkS4RE0APtWLP02/8QVIkQtzBxPquh7lq5/78NHipTj7ODQ6XuyQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"lightningcss": "^1.33.0",
- "picomatch": "^4.0.5",
- "postcss": "^8.5.26",
- "rolldown": "~1.2.4",
+ "picomatch": "^4.0.7",
+ "postcss": "^8.5.28",
+ "rolldown": "~1.2.6",
"tinyglobby": "^0.2.17"
},
"bin": {
@@ -1199,7 +1169,7 @@
},
"peerDependencies": {
"@types/node": "^20.19.0 || >=22.12.0",
- "@vitejs/devtools": "^0.4.0 || ^0.5.0",
+ "@vitejs/devtools": "^0.7.1",
"esbuild": "^0.27.0 || ^0.28.0",
"jiti": ">=1.21.0",
"less": "^4.0.0",
@@ -1269,15 +1239,6 @@
"vscode-languageserver-types": "3.18.3"
}
},
- "node_modules/vscode-languageserver-protocol/node_modules/vscode-jsonrpc": {
- "version": "9.0.1",
- "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-9.0.1.tgz",
- "integrity": "sha512-rfuA6T75H6m5EkbhtEPzre9pT0HPcDI2MMy4+nPFIBks5J8JBAUHD4tRYSgaBOijIEC7SRkC1kKyXTLqbmh9jw==",
- "license": "MIT",
- "engines": {
- "node": ">=14.0.0"
- }
- },
"node_modules/vscode-languageserver-types": {
"version": "3.18.3",
"resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.18.3.tgz",
diff --git a/playground/package.json b/playground/package.json
index ae04f3d..461d80e 100644
--- a/playground/package.json
+++ b/playground/package.json
@@ -7,19 +7,20 @@
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview",
- "fetch-wasm": "bash fetch-wasm.sh"
+ "fetch-wasm": "bash fetch-wasm.sh",
+ "test:wasm": "node --test wasm.test.mjs"
},
"dependencies": {
"monaco-editor": "^0.56.0",
"remote-web-worker": "^0.0.9",
- "vscode-jsonrpc": "^9.0.0",
- "vscode-languageserver-protocol": "^3.17.5"
+ "vscode-jsonrpc": "^9.0.2",
+ "vscode-languageserver-protocol": "^3.18.3"
},
"devDependencies": {
- "typescript": "^7.0.0",
- "vite": "^8.0.0"
+ "typescript": "^7.0.2",
+ "vite": "^8.3.0"
},
"overrides": {
- "dompurify": "^3.4.11"
+ "dompurify": "^3.4.15"
}
}
diff --git a/playground/src/backends/pyrefly.ts b/playground/src/backends/pyrefly.ts
index 898a02d..896aa71 100644
--- a/playground/src/backends/pyrefly.ts
+++ b/playground/src/backends/pyrefly.ts
@@ -134,8 +134,11 @@ function mapPyreflySeverity(
async function loadPyreflyWasm(): Promise {
// Try loading from local static assets (built by CI or fetch-wasm.sh)
try {
- const baseUrl = new URL(/* @vite-ignore */ "../../wasm/pyrefly/", import.meta.url).href;
- const jsUrl = `${baseUrl}pyrefly_wasm.js`;
+ // Absolute URLs keep Vite's dev server from transforming public WASM glue.
+ const jsUrl = new URL(
+ `${import.meta.env.BASE_URL}pyrefly/pyrefly_wasm.js`,
+ window.location.origin,
+ ).href;
const mod = (await import(/* @vite-ignore */ jsUrl)) as PyreflyWasmModule;
await mod.default();
diff --git a/playground/src/backends/pyright.ts b/playground/src/backends/pyright.ts
index 86733c0..ff7f81f 100644
--- a/playground/src/backends/pyright.ts
+++ b/playground/src/backends/pyright.ts
@@ -16,7 +16,7 @@ import type { BackendAdapter, DiagnosticInfo, HoverInfo } from "./interface";
let MarkerSeverity: typeof import("monaco-editor").MarkerSeverity;
const PACKAGE = "browser-basedpyright";
-const VERSION = "1.28.1";
+const VERSION = "1.40.1";
const WORKER_URL = `https://cdn.jsdelivr.net/npm/${PACKAGE}@${VERSION}/dist/pyright.worker.js`;
const ROOT_PATH = "/src/";
@@ -33,11 +33,11 @@ const DEFAULT_CONFIG = JSON.stringify({
});
export class PyrightBackend implements BackendAdapter {
- readonly name = "Pyright";
+ readonly name = "basedpyright";
private connection: MessageConnection | null = null;
private workers: Worker[] = [];
private version = 0;
- private latestDiagnostics: Diagnostic[] = [];
+ private diagnosticsVersion = 0;
private diagnosticsResolve: ((diags: Diagnostic[]) => void) | null = null;
async initialize(): Promise {
@@ -84,8 +84,10 @@ export class PyrightBackend implements BackendAdapter {
"textDocument/publishDiagnostics",
(params: PublishDiagnosticsParams) => {
if (params.uri === FILE_URI) {
- this.latestDiagnostics = params.diagnostics;
- if (this.diagnosticsResolve) {
+ if (
+ this.diagnosticsResolve &&
+ (params.version === undefined || params.version >= this.diagnosticsVersion)
+ ) {
this.diagnosticsResolve(params.diagnostics);
this.diagnosticsResolve = null;
}
@@ -139,6 +141,8 @@ export class PyrightBackend implements BackendAdapter {
if (!this.connection) return [];
const version = ++this.version;
+ this.diagnosticsResolve?.([]);
+ this.diagnosticsVersion = version;
// Create a promise that resolves when we get diagnostics back
const diagnosticsPromise = new Promise((resolve) => {
@@ -147,7 +151,7 @@ export class PyrightBackend implements BackendAdapter {
setTimeout(() => {
if (this.diagnosticsResolve === resolve) {
this.diagnosticsResolve = null;
- resolve(this.latestDiagnostics);
+ resolve([]);
}
}, 10000);
});
@@ -200,6 +204,9 @@ export class PyrightBackend implements BackendAdapter {
}
dispose(): void {
+ this.version++;
+ this.diagnosticsResolve?.([]);
+ this.diagnosticsResolve = null;
if (this.connection) {
this.connection.dispose();
this.connection = null;
diff --git a/playground/src/backends/ty.ts b/playground/src/backends/ty.ts
index fcdf99b..9b48467 100644
--- a/playground/src/backends/ty.ts
+++ b/playground/src/backends/ty.ts
@@ -43,7 +43,6 @@ type TyPositionClass = TyPosition;
interface TyWasmModule {
default(): Promise;
- initLogging(level: number): void;
Workspace: new (
root: string,
encoding: number,
@@ -51,7 +50,6 @@ interface TyWasmModule {
) => TyWorkspace;
Position: new (line: number, column: number) => TyPositionClass;
PositionEncoding: { Utf16: number };
- LogLevel: { Info: number };
}
let MarkerSeverity: typeof import("monaco-editor").MarkerSeverity;
@@ -73,7 +71,7 @@ export class TyBackend implements BackendAdapter {
this.workspace = new this.tyModule.Workspace(
"/",
this.tyModule.PositionEncoding.Utf16,
- {},
+ { environment: { "python-version": "3.12" } },
);
this.fileHandle = this.workspace.openFile(FILENAME, "");
}
@@ -87,8 +85,7 @@ export class TyBackend implements BackendAdapter {
return diagnostics.map((d) => {
const range = d.toRange(this.workspace!);
return {
- // ty uses 0-based positions; Monaco uses 1-based — but ty's playground
- // shows range.start.line is already 1-based from toRange()
+ // ty's WASM Position is 1-based, like Monaco (unlike LSP positions).
startLineNumber: range?.start.line ?? 1,
startColumn: range?.start.column ?? 1,
endLineNumber: range?.end.line ?? 1,
@@ -153,16 +150,14 @@ function mapTySeverity(severity: number): monaco.MarkerSeverity {
async function loadTyWasm(): Promise {
try {
- const baseUrl = new URL(/* @vite-ignore */ "../../wasm/ty/", import.meta.url).href;
- const jsUrl = `${baseUrl}ty_wasm.js`;
+ // Absolute URLs keep Vite's dev server from transforming public WASM glue.
+ const jsUrl = new URL(
+ `${import.meta.env.BASE_URL}ty/ty_wasm.js`,
+ window.location.origin,
+ ).href;
const mod = (await import(/* @vite-ignore */ jsUrl)) as TyWasmModule;
await mod.default();
- try {
- mod.initLogging(mod.LogLevel.Info);
- } catch {
- // initLogging may fail if already initialized
- }
return mod;
} catch (e) {
throw new Error(
diff --git a/playground/src/editor.ts b/playground/src/editor.ts
index 18e3ca0..cf0d92b 100644
--- a/playground/src/editor.ts
+++ b/playground/src/editor.ts
@@ -49,6 +49,7 @@ export function createEditor(): monaco.editor.IStandaloneCodeEditor {
// Listen for content changes (debounced 500ms) — registered once
editor.onDidChangeModelContent(() => {
+ currentVersion++;
if (debounceTimer) clearTimeout(debounceTimer);
debounceTimer = setTimeout(() => runDiagnostics(), 500);
});
@@ -56,7 +57,9 @@ export function createEditor(): monaco.editor.IStandaloneCodeEditor {
return editor;
}
-export function setAdapter(adapter: BackendAdapter): void {
+export function setAdapter(adapter: BackendAdapter | null): void {
+ currentVersion++;
+ if (debounceTimer) clearTimeout(debounceTimer);
// Clear old markers and hover provider
if (currentAdapter) {
monaco.editor.setModelMarkers(
@@ -71,17 +74,17 @@ export function setAdapter(adapter: BackendAdapter): void {
}
currentAdapter = adapter;
- currentVersion = 0;
+ if (!adapter) return;
// Register hover provider
hoverDisposable = monaco.languages.registerHoverProvider("python", {
provideHover: async (_model, position) => {
- if (!currentAdapter) return null;
- const info = await currentAdapter.getHover(
+ const version = currentVersion;
+ const info = await adapter.getHover(
position.lineNumber,
position.column,
);
- if (!info) return null;
+ if (!info || adapter !== currentAdapter || version !== currentVersion) return null;
return {
range: info.range,
contents: [{ value: info.contents }],
@@ -94,17 +97,18 @@ export function setAdapter(adapter: BackendAdapter): void {
}
async function runDiagnostics(): Promise {
- if (!currentAdapter) return;
+ const adapter = currentAdapter;
+ if (!adapter) return;
const version = ++currentVersion;
const code = editor.getValue();
- const diagnostics = await currentAdapter.updateCode(code);
+ const diagnostics = await adapter.updateCode(code);
// Stale check — a newer version was triggered
- if (version !== currentVersion) return;
+ if (version !== currentVersion || adapter !== currentAdapter) return;
- setMarkers(currentAdapter.name, diagnostics);
+ setMarkers(adapter.name, diagnostics);
}
function setMarkers(owner: string, diagnostics: DiagnosticInfo[]): void {
diff --git a/playground/src/main.ts b/playground/src/main.ts
index a5993e6..629ded0 100644
--- a/playground/src/main.ts
+++ b/playground/src/main.ts
@@ -11,11 +11,12 @@ import {
import type { BackendAdapter } from "./backends/interface";
let currentAdapter: BackendAdapter | null = null;
+let switchVersion = 0;
const failedBackends = new Set();
function createBackend(name: BackendName): BackendAdapter {
switch (name) {
- case "Pyright":
+ case "basedpyright":
return new PyrightBackend();
case "Pyrefly":
return new PyreflyBackend();
@@ -26,6 +27,8 @@ function createBackend(name: BackendName): BackendAdapter {
async function switchBackend(name: BackendName): Promise {
if (failedBackends.has(name)) return;
+ const version = ++switchVersion;
+ setAdapter(null);
// Dispose current adapter
if (currentAdapter) {
@@ -35,13 +38,19 @@ async function switchBackend(name: BackendName): Promise {
setStatus(`Loading ${name}...`, "loading");
+ const adapter = createBackend(name);
try {
- const adapter = createBackend(name);
await adapter.initialize();
+ if (version !== switchVersion) {
+ adapter.dispose();
+ return;
+ }
currentAdapter = adapter;
setAdapter(adapter);
setStatus(name, "ready");
} catch (err) {
+ adapter.dispose();
+ if (version !== switchVersion) return;
console.error(`Failed to initialize ${name}:`, err);
failedBackends.add(name);
setBackendDisabled(name, true);
@@ -57,7 +66,7 @@ async function main(): Promise {
});
// Load default backend
- await switchBackend("Pyright");
+ await switchBackend("basedpyright");
}
main();
diff --git a/playground/src/ui.ts b/playground/src/ui.ts
index 4c90fac..4be9089 100644
--- a/playground/src/ui.ts
+++ b/playground/src/ui.ts
@@ -1,12 +1,12 @@
-export type BackendName = "Pyright" | "Pyrefly" | "ty";
+export type BackendName = "basedpyright" | "Pyrefly" | "ty";
-const ALL_BACKENDS: BackendName[] = ["Pyright", "Pyrefly", "ty"];
+const ALL_BACKENDS: BackendName[] = ["basedpyright", "Pyrefly", "ty"];
export interface UICallbacks {
onBackendSelect: (name: BackendName) => void;
}
-let currentBackend: BackendName = "Pyright";
+let currentBackend: BackendName = "basedpyright";
export function initUI(callbacks: UICallbacks): void {
const selector = document.getElementById("backend-selector")!;
diff --git a/playground/vite.config.ts b/playground/vite.config.ts
index e19664a..d1ae831 100644
--- a/playground/vite.config.ts
+++ b/playground/vite.config.ts
@@ -2,6 +2,7 @@ import { defineConfig } from "vite";
export default defineConfig({
base: "/lsp-python-types/",
+ publicDir: "wasm",
build: {
target: "es2022",
chunkSizeWarningLimit: 4000, // Monaco editor is large
diff --git a/playground/wasm.test.mjs b/playground/wasm.test.mjs
new file mode 100644
index 0000000..bfacad9
--- /dev/null
+++ b/playground/wasm.test.mjs
@@ -0,0 +1,62 @@
+import assert from "node:assert/strict";
+import { readFile } from "node:fs/promises";
+import test from "node:test";
+
+const invalid = 'value: int = "wrong"\n';
+const valid = "value: int = 42\n";
+
+async function load(name) {
+ const module = await import(`./wasm/${name}/${name}_wasm.js`);
+ await module.default({
+ module_or_path: await readFile(
+ new URL(`./wasm/${name}/${name}_wasm_bg.wasm`, import.meta.url),
+ ),
+ });
+ return module;
+}
+
+test("Pyrefly release WASM diagnoses edits and supplies hover", async () => {
+ const { State } = await load("pyrefly");
+ const state = new State("3.12");
+ try {
+ state.updateSandboxFiles({ "main.py": "" }, true);
+ state.setActiveFile("main.py");
+ state.updateSingleFile("main.py", invalid);
+ const errors = state.getErrors();
+ assert.ok(
+ errors.some(
+ (error) => error.startLineNumber === 1 && error.severity === 8,
+ ),
+ );
+ assert.match(JSON.stringify(state.hover(1, 2)), /int/);
+ state.updateSingleFile("main.py", valid);
+ assert.equal(state.getErrors().length, 0);
+ } finally {
+ state.free();
+ }
+});
+
+test("ty release WASM diagnoses edits and supplies 1-based hover", async () => {
+ const { Workspace, Position, PositionEncoding } = await load("ty");
+ const workspace = new Workspace("/", PositionEncoding.Utf16, {
+ environment: { "python-version": "3.12" },
+ });
+ const file = workspace.openFile("main.py", "");
+ try {
+ workspace.updateFile(file, invalid);
+ const diagnostics = workspace.checkFile(file);
+ assert.ok(diagnostics.some((diagnostic) => diagnostic.severity() === 2));
+ const range = diagnostics[0].toRange(workspace);
+ assert.equal(range.start.line, 1);
+ assert.ok(range.start.column >= 1);
+ for (const diagnostic of diagnostics) diagnostic.free();
+ const hover = workspace.hover(file, new Position(1, 2));
+ assert.match(hover.markdown, /Literal\["wrong"\]/);
+ assert.equal(hover.range.start.line, 1);
+ workspace.updateFile(file, valid);
+ assert.equal(workspace.checkFile(file).length, 0);
+ } finally {
+ workspace.closeFile(file);
+ workspace.free();
+ }
+});
diff --git a/pyproject.toml b/pyproject.toml
index d1dff2b..47e68df 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -50,7 +50,7 @@ dev = [
"pytest>=9.0.0,<10",
"pytest-cov>=7.0.0,<8",
"pytest-asyncio>=1.0.0",
- "datamodel-code-generator>=0.53.0",
+ "datamodel-code-generator[black,isort]>=0.53.0",
"httpx>=0.28.1",
"rich>=14.2.0",
]
diff --git a/tests/conftest.py b/tests/conftest.py
index 1debc75..9018735 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -2,6 +2,8 @@
from __future__ import annotations
+import os
+
import pytest
from lsp_types.pyrefly.backend import PyreflyBackend
@@ -30,3 +32,9 @@ def lsp_backend(request: pytest.FixtureRequest) -> LSPBackend:
def backend_name(lsp_backend: LSPBackend) -> str:
"""Helper fixture to get the backend name for test identification."""
return lsp_backend.__class__.__name__.replace("Backend", "").lower()
+
+
+@pytest.fixture
+def microsoft_pyright(backend_name: str) -> bool:
+ """CI runs the shared adapter against both npm distributions explicitly."""
+ return backend_name == "pyright" and os.environ.get("PYRIGHT_PACKAGE") == "pyright"
diff --git a/tests/test_semantic_tokens.py b/tests/test_semantic_tokens.py
new file mode 100644
index 0000000..894ec31
--- /dev/null
+++ b/tests/test_semantic_tokens.py
@@ -0,0 +1,60 @@
+"""Regression coverage for Pyrefly 1.3 string highlighting."""
+
+from pathlib import Path
+
+from lsp_types import Session, types
+from lsp_types.pyrefly.backend import PyreflyBackend
+from lsp_types.semantic_tokens import (
+ CANONICAL_LEGEND,
+ PYREFLY_LEGEND,
+ build_modifier_mapping,
+ build_type_mapping,
+ normalize_tokens,
+)
+
+
+def test_pyrefly_string_modifier_bits_survive_normalization():
+ # Check each wire bit separately as well as the combination, so swapped
+ # mappings cannot hide behind an identical aggregate mask.
+ for mask in (1, 2, 4, 8, 16, 31):
+ raw: types.SemanticTokens = {
+ "data": [0, 4, 3, 18, (mask << 11) | 4],
+ "resultId": "strings",
+ }
+ normalized = normalize_tokens(
+ raw,
+ build_type_mapping(PYREFLY_LEGEND),
+ build_modifier_mapping(PYREFLY_LEGEND),
+ )
+ assert normalized == {
+ "data": [0, 4, 3, 18, (mask << 14) | 4],
+ "resultId": "strings",
+ }
+ assert raw["data"][-1] == (mask << 11) | 4
+
+
+async def test_pyrefly_live_string_highlighting(tmp_path: Path):
+ session = await Session.create(
+ PyreflyBackend(),
+ base_path=tmp_path,
+ initial_code='a = b"bytes"\nb = r"raw"\nc = f"{a}"\nd = t"{a}"\n',
+ options={"python_version": "3.14"},
+ )
+ try:
+ tokens = await session.get_semantic_tokens(normalize=True)
+ assert tokens is not None
+ modifiers = {
+ name
+ for mask in tokens["data"][4::5]
+ for bit, name in enumerate(CANONICAL_LEGEND["tokenModifiers"])
+ if mask & (1 << bit)
+ }
+ assert {
+ "byteString",
+ "rawString",
+ "formatString",
+ "stringPrefix",
+ "templateString",
+ } <= modifiers
+ finally:
+ await session.shutdown()
diff --git a/tests/test_session.py b/tests/test_session.py
index 73a9493..c1e9d28 100644
--- a/tests/test_session.py
+++ b/tests/test_session.py
@@ -9,7 +9,7 @@
import lsp_types
from lsp_types import session as session_module
from lsp_types.pool import LSPProcessPool
-from lsp_types.process import LSPProcess, ProcessLaunchInfo
+from lsp_types.process import Error, LSPProcess, ProcessLaunchInfo
from lsp_types.pyrefly.backend import PyreflyBackend
from lsp_types.pyrefly.config_schema import Model as PyreflyConfig
from lsp_types.pyright.backend import PyrightBackend
@@ -370,7 +370,7 @@ async def test_session_completion(lsp_backend, backend_name, tmp_path: Path):
code = """\
class MyClass:
def my_method(self) -> None:
- pass
+ 'Method documentation.'
obj = MyClass()
obj.
@@ -399,11 +399,21 @@ def my_method(self) -> None:
resolved = await session.resolve_completion(method_completion)
assert resolved is not None
assert resolved.get("label") == "my_method"
+ if backend_name == "pyrefly":
+ assert resolved == method_completion
+ else:
+ assert "Method documentation." in str(resolved.get("documentation"))
+ assert resolved.get("documentation") != method_completion.get(
+ "documentation"
+ )
+ else:
+ with pytest.raises(Error, match="-32601"):
+ await session.resolve_completion(method_items[0])
await session.shutdown()
-async def test_session_semantic_tokens(lsp_backend, tmp_path: Path):
+async def test_session_semantic_tokens(lsp_backend, microsoft_pyright, tmp_path: Path):
"""Test semantic token retrieval"""
code = """\
def greet(name: str) -> str:
@@ -416,6 +426,13 @@ def greet(name: str) -> str:
)
# Get semantic tokens
+ if microsoft_pyright:
+ try:
+ with pytest.raises(Error, match="-32601"):
+ await session.get_semantic_tokens()
+ finally:
+ await session.shutdown()
+ return
tokens = await session.get_semantic_tokens()
assert tokens is not None
token_data = tokens.get("data", [])
@@ -426,7 +443,9 @@ def greet(name: str) -> str:
await session.shutdown()
-async def test_session_semantic_tokens_normalized(lsp_backend, tmp_path: Path):
+async def test_session_semantic_tokens_normalized(
+ lsp_backend, microsoft_pyright, tmp_path: Path
+):
"""Test normalized semantic token retrieval with canonical legend"""
code = """\
def greet(name: str) -> str:
@@ -446,6 +465,14 @@ def greet(name: str) -> str:
# Check that backend_legend is captured (Pyrefly uses hardcoded, others use server)
backend_legend = session.backend_legend
+ if microsoft_pyright:
+ try:
+ assert backend_legend is None
+ with pytest.raises(Error, match="-32601"):
+ await session.get_semantic_tokens(normalize=True)
+ finally:
+ await session.shutdown()
+ return
assert backend_legend is not None
# Get raw tokens
@@ -498,13 +525,21 @@ async def test_session_semantic_tokens_canonical_legend_consistency(
await session.shutdown()
-async def test_session_server_info(lsp_backend, backend_name, tmp_path: Path):
+async def test_session_server_info(
+ lsp_backend, backend_name, microsoft_pyright, tmp_path: Path
+):
"""Test that serverInfo from the initialize response is exposed on the session"""
session = await lsp_types.Session.create(
lsp_backend, base_path=tmp_path, initial_code="x = 1"
)
server_info = session.server_info
+ if microsoft_pyright:
+ try:
+ assert server_info is None # Optional in LSP; Microsoft omits it.
+ finally:
+ await session.shutdown()
+ return
assert server_info is not None, (
f"{backend_name} should report serverInfo in initialize response"
)
@@ -518,7 +553,7 @@ async def test_session_server_info(lsp_backend, backend_name, tmp_path: Path):
await session.shutdown()
-async def test_session_recycling_basic(lsp_backend, tmp_path: Path):
+async def test_session_recycling_basic(lsp_backend, microsoft_pyright, tmp_path: Path):
"""Test basic session recycling functionality"""
pool = LSPProcessPool(max_size=2)
@@ -532,8 +567,12 @@ async def test_session_recycling_basic(lsp_backend, tmp_path: Path):
)
first_server_info = session1.server_info
first_backend_legend = session1.backend_legend
- assert first_server_info is not None
- assert first_backend_legend is not None
+ if microsoft_pyright:
+ assert first_server_info is None
+ assert first_backend_legend is None
+ else:
+ assert first_server_info is not None
+ assert first_backend_legend is not None
# Verify it works
hover_info = await session1.get_hover_info(
diff --git a/tests/test_zuban_config.py b/tests/test_zuban_config.py
index e37a091..3aa2aff 100644
--- a/tests/test_zuban_config.py
+++ b/tests/test_zuban_config.py
@@ -45,7 +45,7 @@ def test_zuban_backend_write_config_creates_pyproject_toml(tmp_path: Path):
backend = ZubanBackend()
options: ZubanConfig = {
- "mode": "default",
+ "mode": "auto",
"untyped_strict_optional": True,
}
backend.write_config(tmp_path, options)
@@ -56,7 +56,7 @@ def test_zuban_backend_write_config_creates_pyproject_toml(tmp_path: Path):
parsed = tomllib.loads(config_path.read_text())
assert "tool" in parsed
assert "zuban" in parsed["tool"]
- assert parsed["tool"]["zuban"]["mode"] == "default"
+ assert parsed["tool"]["zuban"]["mode"] == "auto"
assert parsed["tool"]["zuban"]["untyped_strict_optional"] is True
@@ -291,9 +291,9 @@ def test_zuban_backend_write_config_empty_options_still_writes_table(tmp_path: P
"""An empty [tool.zuban] must still be written -- it selects Zuban's mode.
The table's *presence* puts Zuban in its `default` (PyRight-like) mode. A
- project carrying [tool.mypy] but no [tool.zuban] is driven into the weaker
- Mypy-compatible mode, so skipping the write when `options` is empty would
- silently downgrade those projects. See KNOWN_LIMITATIONS.md entry 1.
+ project carrying [tool.mypy] but no [tool.zuban] uses Mypy-compatible
+ behavior, so skipping the write when `options` is empty would silently
+ change the chosen mode. See KNOWN_LIMITATIONS.md entry 1.
"""
from lsp_types.zuban.backend import ZubanBackend
diff --git a/uv.lock b/uv.lock
index 8e720d4..60c003e 100644
--- a/uv.lock
+++ b/uv.lock
@@ -17,15 +17,15 @@ wheels = [
[[package]]
name = "anyio"
-version = "4.14.2"
+version = "4.15.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "idna" },
- { name = "typing-extensions", marker = "python_full_version < '3.13'" },
+ { name = "typing-extensions", marker = "python_full_version < '3.15'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/61/cc/a381afa6efea9f496eff839d4a6a1aed3bfafc7b3ab4b0d1b243a12573dd/anyio-4.14.2.tar.gz", hash = "sha256:cfa139f3ed1a23ee8f88a145ddb5ac7605b8bbfd8592baacd7ce3d8bb4313c7f", size = 260176, upload-time = "2026-07-12T20:29:07.082Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/a9/d2/f4d173e22df740bc37b1db102b386ba719b66e95b0f0d751f556b387e6d2/anyio-4.15.1.tar.gz", hash = "sha256:9f28306018cbd6d329e64a36d58256edff76dd996fe423bc957326e578b82a94", size = 276966, upload-time = "2026-09-05T10:42:39.44Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/da/35/f2287558c17e29fafc8ef3daf819bb9834061cfa43bff8014f7df7f63bdc/anyio-4.14.2-py3-none-any.whl", hash = "sha256:9f505dda5ac9f0c8309b5e8bd445a8c2bf7246f3ce950121e45ea15bc41d1494", size = 125813, upload-time = "2026-07-12T20:29:05.763Z" },
+ { url = "https://files.pythonhosted.org/packages/12/b8/4bd346e22b28902df4d651910f5242c28d84e4a5c2435ca5c3f797ed7e2e/anyio-4.15.1-py3-none-any.whl", hash = "sha256:6152fdbbf9a77fdec97731721bebf7c4c44f7c29b424b0065826173efc7ed101", size = 132079, upload-time = "2026-09-05T10:42:37.923Z" },
]
[[package]]
@@ -197,7 +197,7 @@ wheels = [
[[package]]
name = "datamodel-code-generator"
-version = "0.76.0"
+version = "0.79.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "argcomplete" },
@@ -209,9 +209,17 @@ dependencies = [
{ name = "pydantic" },
{ name = "pyyaml" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/5c/65/27983214b172cf5463209ef55b68f7bb180eaeb114fbca477f709e2ca721/datamodel_code_generator-0.76.0.tar.gz", hash = "sha256:782ad3d17ea53f3a2300347d4058e281ba749ff57821712464f88c1ce75876c8", size = 2184419, upload-time = "2026-08-29T02:04:09.432Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/d8/98/4b35f12e551d078e4297422e98cbc679f4792475bcf7da2e008308695d97/datamodel_code_generator-0.79.0.tar.gz", hash = "sha256:d1e4297c946cccb9c2f7f662167b5876d5554ce3884ac145042256af08f23924", size = 2661584, upload-time = "2026-09-10T17:56:30.318Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/57/72/801a441c9d3717c9fab3399a382ed2d146cc79df12b8ea45427ce26d55a3/datamodel_code_generator-0.76.0-py3-none-any.whl", hash = "sha256:cff9d19faa9072cfc19a04d11ac5e30d0e1a846bd1f843f05f37841941fe3e62", size = 643099, upload-time = "2026-08-29T02:04:07.519Z" },
+ { url = "https://files.pythonhosted.org/packages/80/62/c4ab81535f7fd6d46040908c535248ef96fa53d90c7379913256ddcd3f6c/datamodel_code_generator-0.79.0-py3-none-any.whl", hash = "sha256:0aa827080096844a56135eb990e69d622e79f74bad2e5f006ce797bd74477d1f", size = 695513, upload-time = "2026-09-10T17:56:27.845Z" },
+]
+
+[package.optional-dependencies]
+black = [
+ { name = "black", marker = "sys_platform != 'emscripten'" },
+]
+isort = [
+ { name = "isort", marker = "sys_platform != 'emscripten'" },
]
[[package]]
@@ -333,7 +341,7 @@ zuban = [
[package.dev-dependencies]
dev = [
- { name = "datamodel-code-generator" },
+ { name = "datamodel-code-generator", extra = ["black", "isort"] },
{ name = "httpx" },
{ name = "pytest" },
{ name = "pytest-asyncio" },
@@ -352,7 +360,7 @@ provides-extras = ["pyrefly", "ty", "zuban"]
[package.metadata.requires-dev]
dev = [
- { name = "datamodel-code-generator", specifier = ">=0.53.0" },
+ { name = "datamodel-code-generator", extras = ["black", "isort"], specifier = ">=0.53.0" },
{ name = "httpx", specifier = ">=0.28.1" },
{ name = "pytest", specifier = ">=9.0.0,<10" },
{ name = "pytest-asyncio", specifier = ">=1.0.0" },
@@ -482,11 +490,11 @@ wheels = [
[[package]]
name = "platformdirs"
-version = "4.11.5"
+version = "4.11.8"
source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/ea/06/cf1564dcc2e2261c8c8c6c05628dc8b418943bdae2a4e58640ceb2f770fa/platformdirs-4.11.5.tar.gz", hash = "sha256:e8b31f4f8bcbbedef91a6b57a706255e4f148d2a4e01648382a0a47342539173", size = 34823, upload-time = "2026-08-27T21:36:37.46Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/53/18/f3bb8ef0d3b930692343da8aa4d3cbcd6749477c053959395ac81965a6e9/platformdirs-4.11.8.tar.gz", hash = "sha256:f23abafea7dd4276d1f29104b83598d7dcc567cafd07c9c951e66665645437fc", size = 37182, upload-time = "2026-09-08T22:20:42.866Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/c7/12/6f3fcd5067a9cbf4f8664b32957973498da8b083455203c8d9cab83a725c/platformdirs-4.11.5-py3-none-any.whl", hash = "sha256:89f8d42695853b89c7170bd49bc3dc593f98a71e695ede88e06a3b247bc4563b", size = 23900, upload-time = "2026-08-27T21:36:36.227Z" },
+ { url = "https://files.pythonhosted.org/packages/f4/e1/5b7b8bbb55084d1425bcb9bc823ff519e1b2be05f6ebb0089e2eacc38413/platformdirs-4.11.8-py3-none-any.whl", hash = "sha256:52f2f181bbfde907966932cc8312d967d02976422d66d537ea16092b8e291081", size = 24027, upload-time = "2026-09-08T22:20:41.537Z" },
]
[[package]]
@@ -599,21 +607,21 @@ wheels = [
[[package]]
name = "pyrefly"
-version = "1.2.0"
+version = "1.3.0"
source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/89/01/a86e9f24722b095c3f88e3616132b75a21b0df53804bdc6a45314dd4d93c/pyrefly-1.2.0.tar.gz", hash = "sha256:5485f960fc2481617068c918335c39ab1507ef90b6b5bd35bf57726e60e73185", size = 6243654, upload-time = "2026-08-01T02:56:27.592Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/7d/82/df301034ff7705b51ab212039958910434e1e9bd4765cde435538221fc35/pyrefly-1.3.0.tar.gz", hash = "sha256:e96a0bf3abdc41cf40f7d2ef274bb610f4e6e4b387b366aea102902839073388", size = 6678024, upload-time = "2026-09-11T00:41:37.196Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/7d/9d/3c0ef1d4843987b22f996ed381ec9cf5a3b1273e29804db276252e4c95eb/pyrefly-1.2.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:7f46d983ac49ddd2b043694960a01dc6a19a5cfd8eec609d6bd9c42866f91b4e", size = 14026305, upload-time = "2026-08-01T02:56:02.611Z" },
- { url = "https://files.pythonhosted.org/packages/0a/06/03bbb78fbea54cdc65b626619f3597d5611aca4fdef11e72a4e8360e7e63/pyrefly-1.2.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:756f669b5555090f5c1a4fef30db1785fabe657764f7e4e6dc88994dfb8ca82d", size = 13463880, upload-time = "2026-08-01T02:56:04.93Z" },
- { url = "https://files.pythonhosted.org/packages/13/5a/7d8bc00a38e93bbc9c3e7bd14d305f7948717e667c9bcddeab9dd42fd255/pyrefly-1.2.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e3465812ce5ef4781fb592edbf2724547296f0a3124be115d73c7e8b2401862d", size = 13907329, upload-time = "2026-08-01T02:56:07.104Z" },
- { url = "https://files.pythonhosted.org/packages/be/94/9e08b4bf799d0b8f36b55a2783c7ba5f51730cf0632a85a67b5b5ed876cd/pyrefly-1.2.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5de7b2ad2bba5c8055181681a84b74143eac2234a48ba5d1b7ed7e7a722b02bd", size = 15039020, upload-time = "2026-08-01T02:56:09.208Z" },
- { url = "https://files.pythonhosted.org/packages/5b/bd/bca5fd0c80f4daf8ee6903a29df9f3de1feb05ff0946b8f35ec8c5096b13/pyrefly-1.2.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:25822ea9505f589ea8a725e4268b475132fb89e038fbf092e446510443ac142a", size = 14986199, upload-time = "2026-08-01T02:56:11.924Z" },
- { url = "https://files.pythonhosted.org/packages/97/f7/f07087f3d185ad2eced0c56cef89ca5474dfb4ff25f146cd50a861c97553/pyrefly-1.2.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90efe75e17491ef5d636e10469e9278d7d0256b3b4c5e1f4750069bf3ae0f5d1", size = 14393715, upload-time = "2026-08-01T02:56:14.143Z" },
- { url = "https://files.pythonhosted.org/packages/d3/70/0d142c320e284b9e3ce35e9b1e58b8ce2ee1f578f2a7234bc30e5022b94f/pyrefly-1.2.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:368aaf7eee4f511ddc0f8e564cf14e01ab2f10b0db9105c6d5b153bf498d07bf", size = 13933008, upload-time = "2026-08-01T02:56:16.525Z" },
- { url = "https://files.pythonhosted.org/packages/5d/e8/e84f11b6e1f63fd453ad3654213b9a0f6f4de8cef6b58038eef2d0d5955d/pyrefly-1.2.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:d52d5da7bc65fb7675fbaa80eda879d4f8787c494f04cac21603330d3abbdbbe", size = 14431827, upload-time = "2026-08-01T02:56:18.645Z" },
- { url = "https://files.pythonhosted.org/packages/0f/06/810d31380f66c75e1c0779a408d3b16117b1b368b57894f6aa66bef21686/pyrefly-1.2.0-py3-none-win32.whl", hash = "sha256:8c90751de8506d938e8f802659c74cf35bd7a0036510ee6c634a38eebb280bfa", size = 13229447, upload-time = "2026-08-01T02:56:20.921Z" },
- { url = "https://files.pythonhosted.org/packages/ed/98/4dafa3c7a1caed2dc8cc708dde09ba27963c7736508f55b626fff3024113/pyrefly-1.2.0-py3-none-win_amd64.whl", hash = "sha256:8a8964c224ccc4882730130955815de21ff443c1ac3f0b90685b19bf63848170", size = 14087387, upload-time = "2026-08-01T02:56:23.188Z" },
- { url = "https://files.pythonhosted.org/packages/1b/1c/df3cb0a2e5591660ded7a1836cd2f29dc48c91adb1c0a3a700a96f6d09e1/pyrefly-1.2.0-py3-none-win_arm64.whl", hash = "sha256:3a90bb8df39dfbac74b1f3b2e9d7c526b8f80568884c3944d955023a73ebf61e", size = 13430873, upload-time = "2026-08-01T02:56:25.425Z" },
+ { url = "https://files.pythonhosted.org/packages/36/98/47bf19b6caef77852c7e1c03d71ef48a5f452bf041503d883e0bca6fc4c8/pyrefly-1.3.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:dd6c61e4c307eaaf3cf25fb329c4e7c9ff0376c42d5900362be2fd3333578a69", size = 15088089, upload-time = "2026-09-11T00:41:11.046Z" },
+ { url = "https://files.pythonhosted.org/packages/4c/77/454615aa5db04903c99ced321295e32242c4b188be1a08f40b617ed72984/pyrefly-1.3.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:14c56defaa804747f18e03d11b539e71b71c8251a2b9f58e73dea80e2f6adfd9", size = 14431422, upload-time = "2026-09-11T00:41:13.61Z" },
+ { url = "https://files.pythonhosted.org/packages/01/f2/142e190b9432f802682b0b0794f71e2010e936de0541492cc43678d647af/pyrefly-1.3.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:64285b1261681933f97cb11f3ba2e4445ff02a266707e4e2b7a279db0424c9f2", size = 14880659, upload-time = "2026-09-11T00:41:15.947Z" },
+ { url = "https://files.pythonhosted.org/packages/d9/2f/60598fe38f29f22d11d9f4b5c7fee1cbbb0d5b0cb7db6b373761d57f5f75/pyrefly-1.3.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:46a90d6e48415dfa5d8d9c94ea5b9b61d7149dab87348952f2e000cf45145664", size = 16116073, upload-time = "2026-09-11T00:41:18.547Z" },
+ { url = "https://files.pythonhosted.org/packages/c2/0a/33e49756fe3341dbb13789622cb8c9b6350d2710ceb47b8b0521f134ceea/pyrefly-1.3.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e0cc33b35204b8e1b09396313024580d25a041de2f2f527e6496fc099282b967", size = 16049648, upload-time = "2026-09-11T00:41:21.255Z" },
+ { url = "https://files.pythonhosted.org/packages/b0/fe/be59429726af4c0c3585951a937232d957182364cd2a621f74f4298a176e/pyrefly-1.3.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e94ff692911596d257462320a2ef1435c93c20c80d631404961282f4cc22a6cc", size = 15439725, upload-time = "2026-09-11T00:41:23.572Z" },
+ { url = "https://files.pythonhosted.org/packages/2b/73/bb834e88afea6c0de6960be2102c0cc1d817193c1efc3f6db9b450df191c/pyrefly-1.3.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:5705bec5438aa4e3b65b0d2ea7613f6bdbdc57f8848bd4b7940bbab37f230d8c", size = 14900386, upload-time = "2026-09-11T00:41:25.717Z" },
+ { url = "https://files.pythonhosted.org/packages/c7/64/1adbc6ece9090bbd2e4ed1300918ab709166119426a21102639912ea65bb/pyrefly-1.3.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:fe60cb6d7cdf6c9bb236c0a7efc4b9839cfe7ef834b07d637862f95a642699ef", size = 15478065, upload-time = "2026-09-11T00:41:28.049Z" },
+ { url = "https://files.pythonhosted.org/packages/d0/5c/e1351611d2e205718394237381220ab70f0e21cb297db024eafead19acf9/pyrefly-1.3.0-py3-none-win32.whl", hash = "sha256:4fb4cd5e5007e99208e44f749edaf37a03c0a56c98da7422c2dd60fde0f294b0", size = 14130445, upload-time = "2026-09-11T00:41:30.401Z" },
+ { url = "https://files.pythonhosted.org/packages/3c/92/1d78e2ab1f9f1055ea43164d336903c95e608bede06ed182dc4b4f2c5390/pyrefly-1.3.0-py3-none-win_amd64.whl", hash = "sha256:f809fa5b447012adb8861efeb8f6435146788a39d9251d79dc38731a5d241893", size = 15067598, upload-time = "2026-09-11T00:41:32.582Z" },
+ { url = "https://files.pythonhosted.org/packages/b3/6f/459f56dc345c32f8597acef13675e7e67679fb56bc3fd8f0a280eaeb8c02/pyrefly-1.3.0-py3-none-win_arm64.whl", hash = "sha256:3a3fb7c07dfb9b43d4205d8e85a83aaefc85377d0437dc8469c132836a8a7e5d", size = 14347403, upload-time = "2026-09-11T00:41:35.01Z" },
]
[[package]]
@@ -758,27 +766,27 @@ wheels = [
[[package]]
name = "ty"
-version = "0.0.75"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/81/d0/d0c96f898d6974a4a3569ab3efdf9512c04ad99f9203effb55f72497fe97/ty-0.0.75.tar.gz", hash = "sha256:4c5eead33dfbf6e2ebb4f400f74b51ffc9bab702a6f23ddb648a1cbb740387e3", size = 6868326, upload-time = "2026-08-26T20:23:40.399Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/cb/6c/b12d03505f17581f0cfa3c12273fe34c1d67b36dfda1bc561a6bdc16512b/ty-0.0.75-py3-none-linux_armv6l.whl", hash = "sha256:e5409f50db2246fd4bd039d93d261e0cfa1daa554a4fb77256f91072c570349a", size = 12972606, upload-time = "2026-08-26T20:22:59.716Z" },
- { url = "https://files.pythonhosted.org/packages/d1/aa/30f11eecd9215a9f87e8fe8baaf48f3ce905f5d75b8e4aac70f0091f130c/ty-0.0.75-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:5e7b8b3472fb9bb2eeab314984b265df08a7a9d518867a9e6020eebc06570be2", size = 12527158, upload-time = "2026-08-26T20:23:02.767Z" },
- { url = "https://files.pythonhosted.org/packages/f2/11/7fd7001b0b5c6610bfbad7357e47d5fe6f82d4e84e94c53776a478f5e9f8/ty-0.0.75-py3-none-macosx_11_0_arm64.whl", hash = "sha256:c6ccf34169821fe0d23e3360deeef981d217963412f1d087b9bdd32ec57f7a57", size = 12400533, upload-time = "2026-08-26T20:23:04.965Z" },
- { url = "https://files.pythonhosted.org/packages/fd/7f/1e284ea3d348d7be02f12d83bc22ed9ef193033f863f05b64db99027f141/ty-0.0.75-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:842ebb41e9c6c334b40768704e20b1a69d5c6b08805b289d5e0e2565f49f2de1", size = 12420592, upload-time = "2026-08-26T20:23:07.427Z" },
- { url = "https://files.pythonhosted.org/packages/2d/ab/d813271543370c47fd74b5118f2066ab32b0983e907b1821f3f9a6d0fa7f/ty-0.0.75-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cf7a5a723c5f1e0fab4ffbfe9bd95123a526ed48f206e5f25cb2161ca294007a", size = 12739219, upload-time = "2026-08-26T20:23:09.809Z" },
- { url = "https://files.pythonhosted.org/packages/31/5b/95b49cc5570fd92a7bf63732f649b31906158721e03c7fcb1b5be74ee3bf/ty-0.0.75-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:54382f98e5da292fcd7104391afef5105c35bb2f312e29bea6f5fa419935255c", size = 13494046, upload-time = "2026-08-26T20:23:12.191Z" },
- { url = "https://files.pythonhosted.org/packages/2c/0d/502d2dd68173cf020e1ad2bdbab9544c86776de0b0e2ed15f8c2fe006e3d/ty-0.0.75-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ac13b180dc2aade2cd243f56b01650e78bf091a2e522ad3bc947245d7837c613", size = 13938899, upload-time = "2026-08-26T20:23:14.764Z" },
- { url = "https://files.pythonhosted.org/packages/20/5b/f3b12a25c07224456219fc2bd20db0ad7e40b304be0ff6aad728da0135f9/ty-0.0.75-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:752df7951a443219d7f1ff817e3723c85d428565ff449e08a7a93ba821661526", size = 13656711, upload-time = "2026-08-26T20:23:17.145Z" },
- { url = "https://files.pythonhosted.org/packages/51/7b/f090ad306e2b15a07b332d647138c5264b89d9758855ecce8b8a10bcb153/ty-0.0.75-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1fd399feedf7cee816563c1baec45fc1c0b3c89f1ea42364920b688004b5b7da", size = 13093499, upload-time = "2026-08-26T20:23:19.489Z" },
- { url = "https://files.pythonhosted.org/packages/f1/4b/f69b99aaaca0c7c65d5f114b186b26b21666f767b0c69eec99a2bdccc061/ty-0.0.75-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:d7625f6f56c7dc1e873579fdc9e432a0e21e302afe847ab60704d2303442a92e", size = 13520580, upload-time = "2026-08-26T20:23:21.789Z" },
- { url = "https://files.pythonhosted.org/packages/b6/e7/692c5f905c0345a15d2255fc74066d660f030254ae8dcdaf33f5a5c2f279/ty-0.0.75-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:89e7d527e95a2534b70cae29e94c104b84082760ea05927d23bb87280969c104", size = 12524095, upload-time = "2026-08-26T20:23:24.026Z" },
- { url = "https://files.pythonhosted.org/packages/ba/9a/f42b12cf265ea95344bf554764c4791cfb273bdd628aadd7c209af7cadc3/ty-0.0.75-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:0843f134440740706e01bee5f88f4cfc10e9b018bddb9e4ef4c12dc9fc0c9aef", size = 12756591, upload-time = "2026-08-26T20:23:26.126Z" },
- { url = "https://files.pythonhosted.org/packages/7c/5e/9b180c133cb9cce48179a7d2bf9e1802d992aa8176a918e0e05205760b42/ty-0.0.75-py3-none-musllinux_1_2_i686.whl", hash = "sha256:1bd0ec0e50ee1376875c88891efe6f549c3560fa5b2ddad79a425cd5a6218b9c", size = 12998754, upload-time = "2026-08-26T20:23:28.353Z" },
- { url = "https://files.pythonhosted.org/packages/39/f6/3c6ef5dd550103e29905121c67fb96a374564f31a2f44c6faa1af98c2d61/ty-0.0.75-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:1f9eafd561f90110d5e29f589ec3e956c4686e2f6631348d99276436f5cbe4d1", size = 13316474, upload-time = "2026-08-26T20:23:30.857Z" },
- { url = "https://files.pythonhosted.org/packages/bb/52/12776337874c821076bd5368e352ccd9e67174790abe3b856f749cb3524b/ty-0.0.75-py3-none-win32.whl", hash = "sha256:05063a6fafe2154b794a7f964515d148e51acd186d72d4a3acd347ee9fa19336", size = 12316315, upload-time = "2026-08-26T20:23:33.528Z" },
- { url = "https://files.pythonhosted.org/packages/53/e6/bb51e16af5c7138c9f52f8f3d0a401a371c6798d092e3b74926f186a9814/ty-0.0.75-py3-none-win_amd64.whl", hash = "sha256:81cf1ba5f6b7536ad56747865214255d9bc8e80533a689dbb9ddeaad464b09f1", size = 12917267, upload-time = "2026-08-26T20:23:35.978Z" },
- { url = "https://files.pythonhosted.org/packages/39/73/4542f829107468b5de4231af67f29927c093bfad11f3c1e5b2c08fb1206b/ty-0.0.75-py3-none-win_arm64.whl", hash = "sha256:541c9af5b7a0ad23d15ec315a7da81150833c359f48124ed3789ff25eacd6f42", size = 12711024, upload-time = "2026-08-26T20:23:38.159Z" },
+version = "0.0.80"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/54/b0/6d1b10e0d422736a3c439e487c950ae785f401d71ff879d5be68bccb6d90/ty-0.0.80.tar.gz", hash = "sha256:fe86bc91327e45ff5e3593b7e306e7f57a44bc0608f38d647b8d97bd99c96013", size = 7183998, upload-time = "2026-09-09T21:18:47.547Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/29/c8/3c93195eca282936ebb574d0ba98843e4b147ee324006f88464777012a92/ty-0.0.80-py3-none-linux_armv6l.whl", hash = "sha256:738d1cfca466c577aea24547c348629c00c63548cf7da2c46b497b3840f85dee", size = 13606509, upload-time = "2026-09-09T21:18:10.476Z" },
+ { url = "https://files.pythonhosted.org/packages/e7/48/bbb47f7001c97262a5109bcd92a45bcc8a2fbb21e994c214a9897630bfb7/ty-0.0.80-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:56060164bb8ee43770fa367524fbdba2611fc90f8923a02cc91f80eb37d5a1b0", size = 13203812, upload-time = "2026-09-09T21:18:12.796Z" },
+ { url = "https://files.pythonhosted.org/packages/db/8d/fd141160567047b742e466be8ed09a4c4ed80fa045d37f946eb4f4532fee/ty-0.0.80-py3-none-macosx_11_0_arm64.whl", hash = "sha256:da4062e0fbf3923d9b71688157c5753394f243348f00732e9edbb27498397169", size = 13021896, upload-time = "2026-09-09T21:18:15.186Z" },
+ { url = "https://files.pythonhosted.org/packages/1e/10/b4177faf9e71bc37bc4d08f512042269660a1ce0ec457c48f96e51fc91d7/ty-0.0.80-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9eb94b2659f506a3a07a9ea1415d5c18aaa1e554adc1612614d617a0ed320e1", size = 13083881, upload-time = "2026-09-09T21:18:17.616Z" },
+ { url = "https://files.pythonhosted.org/packages/7f/c2/192d48b9a9acbbe030fc9a4bd73af75671a5566e731949f77e912a68ce68/ty-0.0.80-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e2f5e39a87da48c1af1a3b3135f02be7a83a93da30b0d6a5f5d27806e7c747ce", size = 13354874, upload-time = "2026-09-09T21:18:19.744Z" },
+ { url = "https://files.pythonhosted.org/packages/0a/5b/736dfd31efd98bed9dbe018ff91676e5ea83485b1e24afb635fd0247728f/ty-0.0.80-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4e00149e7779c6b98f3ba12e10a631a861bd7ca1a42bbf064e2677ada7d2c0c2", size = 14204805, upload-time = "2026-09-09T21:18:21.999Z" },
+ { url = "https://files.pythonhosted.org/packages/fd/6f/557c726cb53401998e3589bb632c963a6887a70240e0a4386024f3731c81/ty-0.0.80-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a377268f359fb6e7a2cc9026a09223c764f58d020dec8e6baeaba9caba6ff829", size = 14630014, upload-time = "2026-09-09T21:18:24Z" },
+ { url = "https://files.pythonhosted.org/packages/e9/19/7a4b18fe27f6b6bd4ffa56b67c8b09ecb76bb4312d1c64b73f65417b4dbd/ty-0.0.80-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4511dbf1b266b9ce5b73e9b28d94468096c2b6da00e2fe205168c32d3b352ca7", size = 14326946, upload-time = "2026-09-09T21:18:26.679Z" },
+ { url = "https://files.pythonhosted.org/packages/f8/95/16dd90805fc7e53ec54ae9fdb1a8b74753fb159a55a170f60f49b9417824/ty-0.0.80-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe95feffa7156800c6f804195acb9fb5846a39671c7192c30fff23351aaf7c31", size = 13705988, upload-time = "2026-09-09T21:18:28.681Z" },
+ { url = "https://files.pythonhosted.org/packages/b1/09/4e87992c23ab8a742c7d4914ac5fe66fb9301c8464a69fd2ecbc0be3fbcd/ty-0.0.80-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:ba26b39f06bc8c3c2c5acd3a147239482b36620cdbca1b9d02e915294854f2cc", size = 14233729, upload-time = "2026-09-09T21:18:30.961Z" },
+ { url = "https://files.pythonhosted.org/packages/36/e9/b8c11fda8e66a1d1cc1a7160ed719a33f4dc0107b1cef6a14e2673965763/ty-0.0.80-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:2e6167320888c115a6fbe69893b63fd1c848f9121a454e75e5f612674528e3f3", size = 13173523, upload-time = "2026-09-09T21:18:33.266Z" },
+ { url = "https://files.pythonhosted.org/packages/73/a7/512083fa540c5be1ac8642658f03bfb5bfa6fd168ea4f55e3c0aabe04166/ty-0.0.80-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:7aecb62b4de70b479eab07d1779ea66da26dd8b068f50a9330867157312f103c", size = 13373014, upload-time = "2026-09-09T21:18:35.339Z" },
+ { url = "https://files.pythonhosted.org/packages/5e/63/cd2f0ca81fd9b8cafef93bbcf022bf636bc4de8ee7465c6aafeec1d4d52b/ty-0.0.80-py3-none-musllinux_1_2_i686.whl", hash = "sha256:4fed06adacd16b7e2d722f37449021b1419598d0440769701c0f4827faddde63", size = 13667827, upload-time = "2026-09-09T21:18:37.241Z" },
+ { url = "https://files.pythonhosted.org/packages/0d/5d/ebdfdcb2dc099ae74dc4b75511eef0dd398b2fa27006c543543974b4567f/ty-0.0.80-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:09329af6303ce611ec2cfffc995dc1ddf76c9f47194fc1a4d64a984759c25f5b", size = 13963866, upload-time = "2026-09-09T21:18:39.386Z" },
+ { url = "https://files.pythonhosted.org/packages/c4/f6/c286d5e1b560cfd16d6e91fdab683718aacf09fc88938ca06dcc9d1c8502/ty-0.0.80-py3-none-win32.whl", hash = "sha256:a81b3b512f7b4c68e42fbd1835633de658809666e9aafd5defa52bbc5197698d", size = 12881806, upload-time = "2026-09-09T21:18:41.507Z" },
+ { url = "https://files.pythonhosted.org/packages/fd/c8/3b3a8ac16d47a23ff34bf57c0d99a491860748aea96e0e886b4bfed54f9b/ty-0.0.80-py3-none-win_amd64.whl", hash = "sha256:8043f99878a2ae434781cb881c8a3840dff70c4a5b60bdc5ae84251f35ee063f", size = 13528883, upload-time = "2026-09-09T21:18:43.687Z" },
+ { url = "https://files.pythonhosted.org/packages/2e/71/a6c697930fca76596d7d17f8853120f41a298fd9ea632c901cfca8ca869b/ty-0.0.80-py3-none-win_arm64.whl", hash = "sha256:e277ef034331da5319efc839c064968d24f35715b71c70369cf97d36fe4dcbdb", size = 13370135, upload-time = "2026-09-09T21:18:45.758Z" },
]
[[package]]
@@ -816,19 +824,19 @@ wheels = [
[[package]]
name = "zuban"
-version = "0.9.2"
-source = { registry = "https://pypi.org/simple" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/a4/c0/1c395a08b2a7c48fbbb1398818d65813032ad0acfec5a841db0e7142a6ac/zuban-0.9.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:8922df80834c2d5cb1e2798192dc85d40b4dc3fa3149e7b2b89dccf3cdc110d8", size = 11377755, upload-time = "2026-08-26T00:19:21.022Z" },
- { url = "https://files.pythonhosted.org/packages/af/3c/b323802de4a86f71479b85d388294380205006362bca4414d3213469136a/zuban-0.9.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:112c9bd639ec417fe7881a7d99bc34e35ac6ba6e0afd4f33caf0774ea8ae84fc", size = 11101402, upload-time = "2026-08-26T00:19:24.548Z" },
- { url = "https://files.pythonhosted.org/packages/9f/3b/71ee867aebe9175de1db0208c2eeee7f294821545e6140c28e5ed9f21888/zuban-0.9.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8d65dafe086a8e73cbb8e6d7e3b234a7d2e64cf794d49c5ca0ca901a2880bea", size = 28451467, upload-time = "2026-08-26T00:19:28.898Z" },
- { url = "https://files.pythonhosted.org/packages/bc/93/7331f9f096b1c4a9d575ed6e86572cf6c5ac9eab5dc1c68d0d875e78d549/zuban-0.9.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d8431a65ac7815af24e015d6f6c935518625e015e028b061899677a3d43c6349", size = 28687477, upload-time = "2026-08-26T00:19:33.102Z" },
- { url = "https://files.pythonhosted.org/packages/f8/5a/de09de19a0ea8a4593632631c905751cf380cf8a92d1607632ad40c683ea/zuban-0.9.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6d271a27a56ad89e3bb5942580aa903354c3861bfab29e5c655d44007d1d15a2", size = 29736764, upload-time = "2026-08-26T00:19:38.73Z" },
- { url = "https://files.pythonhosted.org/packages/cb/71/2e32dfafa5ac302b7b9cc48bbcfbc9da6bca221ccfd03da5b9d2a28240ee/zuban-0.9.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:20a467e9d90f5dce53be3c760d9b65e768870a4e940a5256985f67155587eeb9", size = 31719649, upload-time = "2026-08-26T00:19:43.454Z" },
- { url = "https://files.pythonhosted.org/packages/b2/24/d437b54a088b2a35df948841d011a011ba5db59bdb4bad7c800dffcbbe2e/zuban-0.9.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:a2c29e501de2f39690eadffab6369c9ac949bdab82aaab4c9fcbbfda0794d704", size = 28630746, upload-time = "2026-08-26T00:19:47.888Z" },
- { url = "https://files.pythonhosted.org/packages/04/12/a132d0ad526d8986475344546b22acfea0bd6856bb293b70e691fa9bf5d5/zuban-0.9.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:23e34cd5d3c2aad476506fc2a0d3f4fd314e87bb03c873ec2c319497b06f16df", size = 29122179, upload-time = "2026-08-26T00:19:52.937Z" },
- { url = "https://files.pythonhosted.org/packages/70/4e/ebbec923b02c69eee955cc65d227c4a1b6f5c6f2b509b92c9df02ad67e2b/zuban-0.9.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:40f4737d3f7c5926b986ebb4cb44767b2b72788072e833c49d2d3ad2f69ecbdb", size = 29624765, upload-time = "2026-08-26T00:19:57.778Z" },
- { url = "https://files.pythonhosted.org/packages/db/7f/8baa60499029d57bc0dbc254d75469f7b7860654671d0bf3683bc6c56f05/zuban-0.9.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:26a83d3c1f12cd131774a4f89b3c0d750619e7035d02dcd4f668fb43e5ba2fea", size = 28964675, upload-time = "2026-08-26T00:20:02.558Z" },
- { url = "https://files.pythonhosted.org/packages/fa/d8/4a6f29958fa962c2b12ec32a0624708b9da93bec236ace0ac993632d05b1/zuban-0.9.2-py3-none-win32.whl", hash = "sha256:59d09caf2e488eb6d31a3bead1699349f66dafbfd595eb64bcd8e11b79d8e2d1", size = 10051150, upload-time = "2026-08-26T00:20:05.965Z" },
- { url = "https://files.pythonhosted.org/packages/af/c2/123e0f3054f688039290766fdbdd4cba96834537e6fd3f6c95ae460858bd/zuban-0.9.2-py3-none-win_amd64.whl", hash = "sha256:9bc429ef78d3f21a6aeec9cddffe8748235ef2374ef318a91f4aa089cafba75c", size = 10730401, upload-time = "2026-08-26T00:20:08.97Z" },
+version = "0.9.3"
+source = { registry = "https://pypi.org/simple" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/49/97/a0ba462d87300de7d0488d368c35fb204b39fc4f4b5f58e55a211b74be63/zuban-0.9.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:c479be7ec45c03c9a909a561f9c77475cb558b7b326626c7dccfc03006294c0e", size = 11391467, upload-time = "2026-09-02T15:28:23.533Z" },
+ { url = "https://files.pythonhosted.org/packages/4d/e5/fd0e96aa7d4a6d8aa8c892b1a96350e696c29577972409f94323faccafc9/zuban-0.9.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:7b24addd7f1fa95790381feaebef7cfc095b0c81a77f27457d0deaa4ba21f679", size = 11101353, upload-time = "2026-09-02T15:28:27.206Z" },
+ { url = "https://files.pythonhosted.org/packages/d1/5f/a64ce57bb76680028d39540f6fb3789509f5e718d44c1a6ee8451da8ec68/zuban-0.9.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d5c575915e3018f82976f45ee0f46f9f15eb458a30b3e9c4241a82419fa59f1", size = 28569052, upload-time = "2026-09-02T15:28:30.589Z" },
+ { url = "https://files.pythonhosted.org/packages/dc/83/3a9adab0eb8805ba25e95683a728ec9e67cb3d146c16a1b464b6e7b1d6bb/zuban-0.9.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ba146100486a899699ed61b4622eb7088e2c526f8d5fc1433647a3c50ebbf088", size = 28789112, upload-time = "2026-09-02T15:28:35.046Z" },
+ { url = "https://files.pythonhosted.org/packages/7d/02/391f917472321cf944548bb3f0435115a7a8cc6815fe0c0960662872403a/zuban-0.9.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:716e4bd45bcd444ea80c56603f7ecb302afb6155c5e8615754921d1cbfb09dbc", size = 29883166, upload-time = "2026-09-02T15:28:38.989Z" },
+ { url = "https://files.pythonhosted.org/packages/74/be/e3a79dfba2c6c204fe4d3c3102cd40ee2d45f04992d8ce8322938d6db400/zuban-0.9.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d1a99e797ceaccead3e6e5e4ba337dce822fedd9978579eb54a6d50364c962b5", size = 31934489, upload-time = "2026-09-02T15:28:43.526Z" },
+ { url = "https://files.pythonhosted.org/packages/3a/f6/3aea03d059b7d9f8eedd86ef8d77c992288a75d9ca254862e5a66712ced3/zuban-0.9.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:9351787b9034316678bdb075fbb63344d66c4a13fe5616101ca37fe204dfb3db", size = 28743782, upload-time = "2026-09-02T15:28:47.411Z" },
+ { url = "https://files.pythonhosted.org/packages/0c/69/62e3b373bc50dfe705c16b8593881e4c9f549d4a6d8140ade997dca12904/zuban-0.9.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:f2249f2363415e5fc5ff2b89fc35ff650bf2a6e4337ea04e53dbf23984104a3f", size = 29226151, upload-time = "2026-09-02T15:28:51.298Z" },
+ { url = "https://files.pythonhosted.org/packages/8b/73/00db674a103315370a68c601ac97a6e0818170fd8e4ce126689be7458496/zuban-0.9.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:8274b0df6bfa87fea376a6f089a1a442acc59a981af2c2cd0397ba1385eca851", size = 29772524, upload-time = "2026-09-02T15:28:55.563Z" },
+ { url = "https://files.pythonhosted.org/packages/44/6d/d8e43f95e7a06a4fe67a86d802eea755f8e1c478bfab60ef2bcfecdfb2ee/zuban-0.9.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:c72470afba2d6cbeb20626452015a5bee1488248b3db8849da0df7d090f70386", size = 29138520, upload-time = "2026-09-02T15:29:00.248Z" },
+ { url = "https://files.pythonhosted.org/packages/77/d8/59eeb2bfb1d215a1879d4b941a118937e4ac0010da67f21e4c0a5746b2bb/zuban-0.9.3-py3-none-win32.whl", hash = "sha256:7af0490c4e4b1bf6049a8313aa3dee286f12791d5e937d0af03fa1e49184aa84", size = 10030746, upload-time = "2026-09-02T15:29:03.853Z" },
+ { url = "https://files.pythonhosted.org/packages/0f/0f/b9051319c9c884a28eb4d53c9f1b022301b912a1b80554d2b0516fc9f174/zuban-0.9.3-py3-none-win_amd64.whl", hash = "sha256:c10097d0b8cbc7525ed12ebc9abea279b7e03c7aeee8037a0edbd14fd967e83d", size = 10745279, upload-time = "2026-09-02T15:29:06.758Z" },
]