Type Server Protocol: в репозитории Pylance описан протокол анализа типов Python

Type Server Protocol: в репозитории Pylance описан протокол анализа типов Python

В репозитории microsoft/pylance-release лежит документ, описывающий Type Server Protocol (TSP): JSON-RPC-протокол, с помощью которого клиент запрашивает у сервера типов данные анализа Python. Сервер типов хранит состояние анализа типов для рабочей области и отвечает на запросы о версии протокола, снимках состояния, путях поиска Python, разрешении импортов и типах (вычисленных, объявленных и ожидаемых).

Протокол берёт из Language Server Protocol (LSP) структуры данных и соглашения там, где это удобно: Position и Range, URI в виде строк. Методы TSP имеют префикс typeServer/. Авторитетными определениями названы файлы в той же папке: typeServerProtocol.ts (интерфейсы и типы запросов на TypeScript), tsp.json (машиночитаемая модель протокола) и tsp.schema.json (JSON-схема этой модели). Версии записываются по семантическому версионированию: пока протокол в линейке 0.x, изменения минорной версии могут ломать совместимость, а изменения патч-версии остаются обратно совместимыми внутри той же минорной линии.

Клиент запускает сервер типов и общается с ним по основному JSON-RPC-соединению через поддерживаемый сервером транспорт, например stdio или Node IPC; при stdio в stdout допускаются только сообщения протокола. Обычно состояние рабочей области и документов передаётся через стандартные инициализацию и синхронизацию документов LSP, после чего на том же соединении отправляются запросы typeServer/*. Типичный сеанс: клиент запускает сервер или подключается к нему; стороны инициализируют общее состояние; клиент вызывает typeServer/getSupportedProtocolVersion и проверяет совместимость возвращённой версии; затем typeServer/getSnapshot даёт первый идентификатор снимка; далее идут запросы типов, разрешения импортов и путей поиска с текущим снимком; а когда прежние результаты перестают быть верными, сервер шлёт typeServer/snapshotChanged.

Снимок, неотрицательное целое число, обозначающее состояние анализа на момент времени. Типы, объявления, символы и другие возвращённые объекты действительны только для снимка, в котором получены. Сервер обязан продвигать снимок, когда ранее выданный результат мог устареть: при изменении открытых файлов, закрытии файлов, смене конфигурации, изменении отслеживаемых файлов или зависимостей, сбросе кэша. Идентификаторы снимков в течение сеанса не должны уменьшаться. Запрос с устаревшим снимком должен завершаться ошибкой JSON-RPC, после которой клиент может повторить его со свежим снимком; предпочтительный код, ServerCancelled из LSP (-32802).

Позиции следуют LSP: строки считаются с нуля, смещения символов, нулевые, в кодовых единицах UTF-16. Если сервер хранит координаты в другой кодировке, он должен преобразовывать их на границе протокола, иначе клиент может запросить данные не про тот узел синтаксического дерева. Три запроса типов различаются тем, какой тип они возвращают для одного и того же узла или объявления: getComputedType возвращает тип, выведенный из потока выполнения (после сужения a до int вычисленный тип b в b = a + 1, int); getDeclaredType, тип, явно записанный в коде (для def foo(a: int | str) объявленный тип a, int | str); getExpectedType, тип, которого ждёт окружающий контекст (для вызова foo(4) ожидаемый тип аргумента, int | str). Объект запроса, это узел (URI и диапазон в исходнике) либо объявление: обычное, привязанное к узлу AST, или синтезированное, созданное средством проверки типов без исходного узла. Значение InvalidHandle равно -1 и означает недействительный или недоступный дескриптор.

Результаты типов, JSON-объекты с дискриминатором. Поддерживаются встроенные типы, объявленные типы, функции, классы, объединения, модули, переменные типов, наборы перегрузок, синтезированные типы и ссылки на типы. У каждого объекта есть числовые kind, id и битовое поле flags. Идентификаторы типов действуют только внутри одного ответа и служат для связи объектов-ссылок с ранее сериализованными типами, чтобы не дублировать крупные или рекурсивные графы; сравнивать их между разными ответами не следует.

Отдельный необязательный режим, несколько соединений. Исходное соединение остаётся главным: на нём инициализация, состояние рабочей области, синхронизация документов, уведомления об изменениях и управление соединениями. Дополнительные соединения только для чтения и только для TSP; они нужны для высоконагруженных запросов типов или фоновых воркеров, которые не должны блокировать главное соединение. Режим согласуется при LSP initialize через capabilities.experimental.typeServerMultiConnection со списком supportedTransports; он включается, только если обе стороны заявили возможность и у них есть общий транспорт. Сейчас встроенный транспорт для дополнительных соединений один, ipc. Клиент открывает и закрывает канал запросом typeServer/connection на главном соединении (type: open или close, kind: ipc, args с конечными точками): для двунаправленного канала одна конечная точка, для однонаправленных две, вход и выход сервера. Сервер при успехе возвращает success: true, иначе success: false. Сервер обязан корректно работать и без этого режима, а допустимые на дополнительном соединении сообщения привязаны к согласованной версии TSP. Сервер должен отклонять там трафик LSP, уведомления об изменении состояния, typeServer/connection и недопустимые для версии сообщения.

В конце документа приведён перечень проверок совместимости сервера: версия в виде semver, корректная сериализация, неубывающие снимки, инвалидация снимков при изменении документов, конфигурации и отслеживаемых файлов, повторяемая ошибка при устаревшем снимке, позиции по правилам LSP, результаты разрешения импортов и запросов типов как корректные объекты Type или undefined. Текст на этом месте обрывается.

Ключевые факты

  • TSP, JSON-RPC-протокол, через который клиент спрашивает у сервера типов данные анализа Python: версию протокола, снимки, пути поиска, разрешение импортов, вычисленные, объявленные и ожидаемые типы.
  • Протокол заимствует из LSP структуры вроде Position и Range, методы имеют префикс typeServer/; пока он в линейке 0.x, изменения минорной версии могут ломать совместимость.
  • Результаты привязаны к снимку состояния анализа: идентификаторы не убывают, а запрос с устаревшим снимком должен падать с ошибкой (предпочтительно ServerCancelled, -32802), после которой клиент повторяет его.
  • Необязательный режим нескольких соединений: дополнительные соединения только для чтения, пока единственный транспорт, ipc; сервер обязан работать и в режиме одного соединения.
  • Определения протокола лежат в файлах typeServerProtocol.ts, tsp.json и tsp.schema.json.

Почему это важно

Документ фиксирует единый способ получать от анализатора Python сведения о типах: клиенту не нужно зависеть от внутреннего устройства конкретного сервера. Здесь такие данные вынесены в отдельный протокол с машиночитаемой моделью и схемой. Протокол опирается на привычные соглашения LSP, поэтому его проще встроить в уже существующие редакторы и инструменты. Важно и то, что он пока нестабилен: линейка 0.x допускает ломающие изменения.

Кому это важно

Разработчикам языковых серверов, расширений для редакторов и инструментов статического анализа Python, которым нужны данные о типах. Тем, кто пишет клиента или сервер типов, пригодятся требования к снимкам, позициям, транспорту и перечень проверок совместимости. Обычным пользователям редакторов документ напрямую не касается.

Как это применить

Разработчик клиента следует порядку сеанса: запускает сервер, инициализирует состояние через LSP, проверяет версию через typeServer/getSupportedProtocolVersion, получает снимок через typeServer/getSnapshot и передаёт его в запросах типов. Реализации сервера стоит свериться с файлами typeServerProtocol.ts, tsp.json и tsp.schema.json как с авторитетным источником и пройти перечень проверок совместимости. Режим нескольких соединений нужен только при высокой нагрузке или фоновой работе; начинать разумно с одного соединения. Цена и лицензия в доступном тексте не обсуждаются.

Можно ли доверять

Это документация в репозитории microsoft/pylance-release, то есть описание протокола, а не отзыв или оценка. В видимом тексте не названы авторы и дата, а также не сказано, какие клиенты и серверы, включая Pylance, реализуют протокол и насколько он стабилен. Таблицы сообщений в доступный текст не попали, и текст обрывается на перечне проверок, поэтому детали о конкретных сообщениях нужно смотреть в самом репозитории.

Риски и подводные камни

Пока протокол в линейке 0.x, обновление минорной версии может сломать совместимость, поэтому нужна проверка версии при старте. Объекты и идентификаторы типов действуют только для своего снимка, а идентификаторы типов ещё и только внутри одного ответа: их нельзя сравнивать между ответами. Неверное преобразование кодировки позиций (ожидается UTF-16) приведёт к запросу не того узла. При использовании stdio в stdout нельзя писать ничего, кроме сообщений протокола. Сервер должен отклонять на дополнительных соединениях трафик LSP и изменяющие состояние сообщения, а корректная работа не должна зависеть от многосоединённого режима.

«Поддержка нескольких соединений не должна быть обязательной для корректности протокола.»

— документ Type Server Protocol в репозитории microsoft/pylance-release