astral-sh/ty/main 147k tokens More Tools
```
├── .editorconfig (omitted)
├── .gitattributes (omitted)
├── .github/
   ├── ISSUE_TEMPLATE/
      ├── 1_bug_report.yaml (300 tokens)
      ├── 2_question.yaml (100 tokens)
      ├── config.yml (100 tokens)
   ├── PULL_REQUEST_TEMPLATE.md (100 tokens)
   ├── actionlint.yaml (100 tokens)
   ├── renovate.json5 (500 tokens)
   ├── workflows/
      ├── build-binaries.yml (3.6k tokens)
      ├── build-docker.yml (3.1k tokens)
      ├── ci.yaml (800 tokens)
      ├── daily_property_tests.yml (500 tokens)
      ├── notify-dependents.yml (200 tokens)
      ├── publish-docs.yml (800 tokens)
      ├── publish-mirror.yml (500 tokens)
      ├── publish-pypi.yml (200 tokens)
      ├── publish-versions.yml (500 tokens)
      ├── release.yml (2.8k tokens)
   ├── zizmor.yml
├── .gitignore
├── .gitmodules
├── .markdownlint.yaml (100 tokens)
├── .pre-commit-config.yaml (700 tokens)
├── .python-version
├── AGENTS.md (100 tokens)
├── BENCHMARKS.md (6.8k tokens)
├── CHANGELOG.md (54.8k tokens)
├── CONTRIBUTING.md (2000 tokens)
├── Dockerfile (300 tokens)
├── LICENSE (omitted)
├── README.md (1000 tokens)
├── SECURITY.md (100 tokens)
├── _typos.toml
├── assets/
   ├── badge/
      ├── v0.json (100 tokens)
├── dist-workspace.toml (700 tokens)
├── docs/
   ├── .overrides/
      ├── main.html (300 tokens)
      ├── partials/
         ├── integrations/
            ├── analytics/
               ├── fathom.html
   ├── assets/
      ├── favicon.ico
      ├── logo-letter.svg (100 tokens)
      ├── ty-benchmark-cli-light.svg (1100 tokens)
      ├── ty-benchmark-cli.svg (1200 tokens)
   ├── coming-from-mypy-or-pyright.md (2000 tokens)
   ├── configuration.md (400 tokens)
   ├── editors.md (1000 tokens)
   ├── exclusions.md (700 tokens)
   ├── features/
      ├── diagnostics.md (400 tokens)
      ├── language-server.md (2.9k tokens)
      ├── screenshots/
         ├── code-completion.png
         ├── diagnostics1.dark.png
         ├── diagnostics1.light.png
         ├── diagnostics2.dark.png
         ├── diagnostics2.light.png
         ├── diagnostics3.dark.png
         ├── diagnostics3.light.png
         ├── diagnostics4.dark.png
         ├── diagnostics4.light.png
         ├── find-references.png
         ├── inlay-hints.png
         ├── inline-diagnostics.png
         ├── quick-fix.png
      ├── type-system.md (1400 tokens)
   ├── index.md (500 tokens)
   ├── installation.md (1000 tokens)
   ├── js/
      ├── extra.js (500 tokens)
   ├── modules.md (500 tokens)
   ├── python-version.md (400 tokens)
   ├── reference/
      ├── cli.md (2.6k tokens)
      ├── configuration.md (4.4k tokens)
      ├── editor-settings.md (2.8k tokens)
      ├── environment.md (500 tokens)
      ├── exit-codes.md (200 tokens)
      ├── rules.md (29.3k tokens)
      ├── typing-faq.md (3.2k tokens)
   ├── requirements.in
   ├── requirements.txt (700 tokens)
   ├── rules.md (500 tokens)
   ├── stylesheets/
      ├── extra.css (1400 tokens)
   ├── suppression.md (700 tokens)
   ├── type-checking.md (400 tokens)
├── mkdocs.yml (900 tokens)
├── pyproject.toml (700 tokens)
├── python/
   ├── ty/
      ├── __init__.py
      ├── __main__.py (100 tokens)
      ├── _find_ty.py (600 tokens)
      ├── py.typed
├── scripts/
   ├── autogenerate_files.sh (100 tokens)
   ├── release.sh (500 tokens)
   ├── transform_readme.py (500 tokens)
   ├── update_schemastore.py (1200 tokens)
├── uv.lock (omitted)
```


## /.github/ISSUE_TEMPLATE/1_bug_report.yaml

```yaml path="/.github/ISSUE_TEMPLATE/1_bug_report.yaml" 
name: Bug report
description: Report an error or unexpected behavior
body:
  - type: markdown
    attributes:
      value: |
        Thank you for taking the time to report an issue! We're glad to have you involved with ty.

        **Before reporting, please make sure to search through [existing issues](https://github.com/astral-sh/ty/issues?q=is:issue+is:open+label:bug) (including [closed](https://github.com/astral-sh/ty/issues?q=is:issue%20state:closed%20label:bug)).**<br>
        **In particular, check out the list of [frequently encountered problems](https://github.com/astral-sh/ty/issues/445).**

  - type: textarea
    attributes:
      label: Summary
      description: |
        A clear and concise description of the bug, including a minimal reproducible example.

        Be sure to include the command you invoked (e.g., `ty check`) and
        the current ty settings (e.g., relevant sections from your `pyproject.toml`).

        If possible, try to include the [playground](https://play.ty.dev) link that reproduces this issue.

    validations:
      required: true

  - type: input
    attributes:
      label: Version
      description: What version of ty are you using? (see `ty version`)
      placeholder: e.g., ty 0.0.1 (ff9000864 2025-05-06)
    validations:
      required: false

```

## /.github/ISSUE_TEMPLATE/2_question.yaml

```yaml path="/.github/ISSUE_TEMPLATE/2_question.yaml" 
name: Question
description: Ask a question about ty
labels: ["question"]
body:
  - type: textarea
    attributes:
      label: Question
      description: Describe your question in detail.
    validations:
      required: true

  - type: input
    attributes:
      label: Version
      description: What version of ty are you using? (see `ty version`)
      placeholder: e.g., ty 0.0.1 (ff9000864 2025-05-06)
    validations:
      required: false

```

## /.github/ISSUE_TEMPLATE/config.yml

```yml path="/.github/ISSUE_TEMPLATE/config.yml" 
blank_issues_enabled: true
contact_links:
  - name: Documentation
    url: https://github.com/astral-sh/ty/blob/main/README.md
    about: Please consult the documentation before creating an issue.
  - name: Community
    url: https://discord.com/invite/astral-sh
    about: Join our Discord community to ask questions and collaborate.

```

## /.github/PULL_REQUEST_TEMPLATE.md

<!--
Thank you for contributing to ty! To help us out with reviewing, please consider the following:

- Does this pull request include a summary of the change? (See below.)
- Does this pull request include a descriptive title?
- Does this pull request include references to any relevant issues?
-->

## Summary

<!-- What's the purpose of the change? What does it do, and why? -->

## Test Plan

<!-- How was it tested? -->


## /.github/actionlint.yaml

```yaml path="/.github/actionlint.yaml" 
# Configuration for the actionlint tool, which we run via prek
# to verify the correctness of the syntax in our GitHub Actions workflows.

self-hosted-runner:
  # Various runners we use that aren't recognized out-of-the-box by actionlint:
  labels:
    - depot-ubuntu-latest-8
    - depot-ubuntu-22.04-16
    - depot-ubuntu-22.04-32
    - github-windows-2025-x86_64-8
    - github-windows-2025-x86_64-16

```

## /.github/renovate.json5

```json5 path="/.github/renovate.json5" 
{
  $schema: "https://docs.renovatebot.com/renovate-schema.json",
  dependencyDashboard: true,
  suppressNotifications: ["prEditedNotification"],
  extends: ["github>astral-sh/renovate-config"],
  labels: ["internal"],
  schedule: ["before 4am on Wednesday"],
  semanticCommits: "disabled",
  separateMajorMinor: false,
  prHourlyLimit: 10,
  enabledManagers: ["github-actions", "pre-commit", "custom.regex"],
  "pre-commit": {
    enabled: true,
  },
  packageRules: [
    // Pin GitHub Actions to immutable SHAs.
    {
      matchDepTypes: ["action"],
      pinDigests: true,
    },
    // Annotate GitHub Actions SHAs with a SemVer version.
    {
      extends: ["helpers:pinGitHubActionDigests"],
      extractVersion: "^(?<version>v?\\d+\\.\\d+\\.\\d+){{contextString}}quot;,
      versioning: "regex:^v?(?<major>\\d+)(\\.(?<minor>\\d+)\\.(?<patch>\\d+))?{{contextString}}quot;,
    },
    {
      // Group upload/download artifact updates, the versions are dependent
      groupName: "Artifact GitHub Actions dependencies",
      matchManagers: ["github-actions"],
      matchDatasources: ["gitea-tags", "github-tags"],
      matchPackageNames: ["actions/upload-artifact", "actions/download-artifact"],
      description: "Weekly update of artifact-related GitHub Actions dependencies",
    },
    {
      // This package rule disables updates for GitHub runners:
      // we'd only pin them to a specific version
      // if there was a deliberate reason to do so
      groupName: "GitHub runners",
      matchManagers: ["github-actions"],
      matchDatasources: ["github-runners"],
      description: "Disable PRs updating GitHub runners (e.g. 'runs-on: macos-14')",
      enabled: false,
    },
    {
      groupName: "prek dependencies",
      matchManagers: ["pre-commit"],
      description: "Weekly update of prek dependencies",
    },
    {
      matchManagers: ["custom.regex"],
      matchDepNames: ["maturin"],
      commitMessageTopic: "maturin",
    },
  ],
  customManagers: [
    // Maturin version used in maturin-action
    {
      customType: "regex",
      managerFilePatterns: [
        "/.github/workflows/build-binaries\\.yml$/",
      ],
      matchStrings: ["maturin-version: (?<currentValue>v\\d+\\.\\d+\\.\\d+)"],
      depNameTemplate: "maturin",
      packageNameTemplate: "PyO3/maturin",
      datasourceTemplate: "github-releases",
    },
  ],
  vulnerabilityAlerts: {
    commitMessageSuffix: "",
    labels: ["internal", "security"],
  },
}

```

## /.github/workflows/build-binaries.yml

```yml path="/.github/workflows/build-binaries.yml" 
# Build ty on all platforms.
#
# Generates both wheels (for PyPI) and archived binaries (for GitHub releases).
#
# Assumed to run as a subworkflow of .github/workflows/release.yml; specifically, as a local
# artifacts job within `cargo-dist`.
name: "Build binaries"

on:
  workflow_call:
    inputs:
      plan:
        required: true
        type: string
  pull_request:
    paths:
      # When we change pyproject.toml, we want to ensure that the maturin builds still work.
      - pyproject.toml
      # And when we change this workflow itself...
      - .github/workflows/build-binaries.yml

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

permissions:
  contents: read

env:
  PACKAGE_NAME: ty
  MODULE_NAME: ty
  PYTHON_VERSION: "3.13"
  CARGO_INCREMENTAL: 0
  CARGO_NET_RETRY: 10
  CARGO_TERM_COLOR: always
  RUSTUP_MAX_RETRIES: 10

jobs:
  sdist:
    if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-build') }}
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
        with:
          submodules: recursive
          persist-credentials: false
      - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
        with:
          python-version: ${{ env.PYTHON_VERSION }}
      - name: "Prep README.md"
        run: python scripts/transform_readme.py
      - name: "Build sdist"
        uses: PyO3/maturin-action@e83996d129638aa358a18fbd1dfb82f0b0fb5d3b # v1.51.0
        with:
          maturin-version: v1.13.3
          command: sdist
          args: --out dist
      - name: "Test sdist"
        run: |
          pip install dist/"${PACKAGE_NAME}"-*.tar.gz --force-reinstall
          "${MODULE_NAME}" version
          "${MODULE_NAME}" --help
          python -m "${MODULE_NAME}" --help
      - name: "Upload sdist"
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: wheels-sdist
          path: dist

  macos-x86_64:
    if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-build') }}
    runs-on: macos-15
    steps:
      - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
        with:
          submodules: recursive
          persist-credentials: false
      - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
        with:
          python-version: ${{ env.PYTHON_VERSION }}
          architecture: x64
      - name: "Prep README.md"
        run: python scripts/transform_readme.py
      - name: "Build wheels - x86_64"
        uses: PyO3/maturin-action@e83996d129638aa358a18fbd1dfb82f0b0fb5d3b # v1.51.0
        with:
          maturin-version: v1.13.3
          target: x86_64
          args: --release --locked --out dist --compatibility pypi
      - name: "Upload wheels"
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: wheels-macos-x86_64
          path: dist
      - name: "Archive binary"
        run: |
          TARGET=x86_64-apple-darwin
          ARCHIVE_NAME=ty-$TARGET
          ARCHIVE_FILE=$ARCHIVE_NAME.tar.gz

          mkdir -p $ARCHIVE_NAME
          cp ruff/target/$TARGET/release/ty $ARCHIVE_NAME/ty
          tar czvf $ARCHIVE_FILE $ARCHIVE_NAME
          shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
      - name: "Upload binary"
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: artifacts-macos-x86_64
          path: |
            *.tar.gz
            *.sha256

  macos-aarch64:
    if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-build') }}
    runs-on: macos-15
    steps:
      - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
        with:
          submodules: recursive
          persist-credentials: false
      - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
        with:
          python-version: ${{ env.PYTHON_VERSION }}
          architecture: arm64
      - name: "Prep README.md"
        run: python scripts/transform_readme.py
      - name: "Build wheels - aarch64"
        uses: PyO3/maturin-action@e83996d129638aa358a18fbd1dfb82f0b0fb5d3b # v1.51.0
        with:
          maturin-version: v1.13.3
          target: aarch64
          args: --release --locked --out dist --compatibility pypi
      - name: "Test wheel - aarch64"
        run: |
          pip install dist/"${PACKAGE_NAME}"-*.whl --force-reinstall
          ty --help
          python -m ty --help
      - name: "Upload wheels"
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: wheels-aarch64-apple-darwin
          path: dist
      - name: "Archive binary"
        run: |
          TARGET=aarch64-apple-darwin
          ARCHIVE_NAME=ty-$TARGET
          ARCHIVE_FILE=$ARCHIVE_NAME.tar.gz

          mkdir -p $ARCHIVE_NAME
          cp ruff/target/$TARGET/release/ty $ARCHIVE_NAME/ty
          tar czvf $ARCHIVE_FILE $ARCHIVE_NAME
          shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
      - name: "Upload binary"
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: artifacts-aarch64-apple-darwin
          path: |
            *.tar.gz
            *.sha256

  windows:
    if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-build') }}
    runs-on: windows-latest
    strategy:
      matrix:
        platform:
          - target: x86_64-pc-windows-msvc
            arch: x64
          - target: i686-pc-windows-msvc
            arch: x86
          - target: aarch64-pc-windows-msvc
            arch: x64
    steps:
      - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
        with:
          submodules: recursive
          persist-credentials: false
      - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
        with:
          python-version: ${{ env.PYTHON_VERSION }}
          architecture: ${{ matrix.platform.arch }}
      - name: "Prep README.md"
        run: python scripts/transform_readme.py
      - name: "Build wheels"
        uses: PyO3/maturin-action@e83996d129638aa358a18fbd1dfb82f0b0fb5d3b # v1.51.0
        with:
          maturin-version: v1.13.3
          target: ${{ matrix.platform.target }}
          args: --release --locked --out dist --compatibility pypi
        env:
          # aarch64 build fails, see https://github.com/PyO3/maturin/issues/2110
          XWIN_VERSION: 16
      - name: "Test wheel"
        if: ${{ !startsWith(matrix.platform.target, 'aarch64') }}
        shell: bash
        run: |
          python -m pip install dist/"${PACKAGE_NAME}"-*.whl --force-reinstall
          "${MODULE_NAME}" version
          "${MODULE_NAME}" --help
          python -m "${MODULE_NAME}" --help
      - name: "Upload wheels"
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: wheels-${{ matrix.platform.target }}
          path: dist
      - name: "Archive binary"
        shell: bash
        run: |
          ARCHIVE_FILE=ty-${{ matrix.platform.target }}.zip
          7z a $ARCHIVE_FILE ./ruff/target/${{ matrix.platform.target }}/release/ty.exe
          sha256sum $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
      - name: "Upload binary"
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: artifacts-${{ matrix.platform.target }}
          path: |
            *.zip
            *.sha256

  linux:
    if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-build') }}
    runs-on: ubuntu-latest
    strategy:
      matrix:
        target:
          - x86_64-unknown-linux-gnu
          - i686-unknown-linux-gnu
    steps:
      - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
        with:
          submodules: recursive
          persist-credentials: false
      - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
        with:
          python-version: ${{ env.PYTHON_VERSION }}
          architecture: x64
      - name: "Prep README.md"
        run: python scripts/transform_readme.py
      - name: "Build wheels"
        uses: PyO3/maturin-action@e83996d129638aa358a18fbd1dfb82f0b0fb5d3b # v1.51.0
        with:
          maturin-version: v1.13.3
          target: ${{ matrix.target }}
          manylinux: 2_17
          args: --release --locked --out dist --compatibility pypi
      - name: "Test wheel"
        if: ${{ startsWith(matrix.target, 'x86_64') }}
        run: |
          pip install dist/"${PACKAGE_NAME}"-*.whl --force-reinstall
          "${MODULE_NAME}" version
          "${MODULE_NAME}" --help
          python -m "${MODULE_NAME}" --help
      - name: "Upload wheels"
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: wheels-${{ matrix.target }}
          path: dist
      - name: "Archive binary"
        shell: bash
        run: |
          set -euo pipefail

          TARGET=${{ matrix.target }}
          ARCHIVE_NAME=ty-$TARGET
          ARCHIVE_FILE=$ARCHIVE_NAME.tar.gz

          mkdir -p $ARCHIVE_NAME
          cp ruff/target/$TARGET/release/ty $ARCHIVE_NAME/ty
          tar czvf $ARCHIVE_FILE $ARCHIVE_NAME
          shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
      - name: "Upload binary"
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: artifacts-${{ matrix.target }}
          path: |
            *.tar.gz
            *.sha256

  linux-cross:
    if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-build') }}
    runs-on: ubuntu-latest
    strategy:
      matrix:
        platform:
          - target: aarch64-unknown-linux-gnu
            arch: aarch64
            manylinux: 2_17
            # see https://github.com/astral-sh/ruff/issues/3791
            # and https://github.com/gnzlbg/jemallocator/issues/170#issuecomment-1503228963
            maturin_docker_options: -e JEMALLOC_SYS_WITH_LG_PAGE=16
          - target: armv7-unknown-linux-gnueabihf
            arch: armv7
            manylinux: 2_17
          - target: s390x-unknown-linux-gnu
            arch: s390x
            manylinux: 2_17
          - target: powerpc64le-unknown-linux-gnu
            arch: ppc64le
            manylinux: 2_17
            # see https://github.com/astral-sh/ruff/issues/10073
            maturin_docker_options: -e JEMALLOC_SYS_WITH_LG_PAGE=16
          - target: riscv64gc-unknown-linux-gnu
            arch: riscv64
            # Minimum manylinux target for riscv64
            manylinux: 2_31
          - target: arm-unknown-linux-musleabihf
            # Use the cross container, but tag as `linux_armv6l`
            manylinux: auto
            arch: arm

    steps:
      - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
        with:
          submodules: recursive
          persist-credentials: false
      - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
        with:
          python-version: ${{ env.PYTHON_VERSION }}
      - name: "Prep README.md"
        run: python scripts/transform_readme.py
      - name: "Build wheels"
        uses: PyO3/maturin-action@e83996d129638aa358a18fbd1dfb82f0b0fb5d3b # v1.51.0
        with:
          maturin-version: v1.13.3
          target: ${{ matrix.platform.target }}
          manylinux: ${{ matrix.platform.manylinux }}
          docker-options: ${{ matrix.platform.maturin_docker_options }}
          args: --release --locked --out dist --compatibility pypi
      - uses: uraimo/run-on-arch-action@f9b26e3a1a408d5fd530d20c17b9f3f4428ff8d9 # v3.1.0
        if: ${{ matrix.platform.arch != 'ppc64le'}}
        name: Test wheel
        with:
          arch: ${{ matrix.platform.arch == 'arm' && 'armv6' || matrix.platform.arch }}
          distro: ${{ matrix.platform.arch == 'arm' && 'bullseye' || 'ubuntu20.04' }}
          githubToken: ${{ github.token }}
          install: |
            apt-get update
            apt-get install -y --no-install-recommends python3 python3-pip libatomic1
            pip3 install -U pip
          run: |
            pip3 install ${{ env.PACKAGE_NAME }} --no-index --find-links dist/ --force-reinstall
            ty --help
      - name: "Upload wheels"
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: wheels-${{ matrix.platform.target }}
          path: dist
      - name: "Archive binary"
        shell: bash
        run: |
          set -euo pipefail

          TARGET=${{ matrix.platform.target }}
          ARCHIVE_NAME=ty-$TARGET
          ARCHIVE_FILE=$ARCHIVE_NAME.tar.gz

          mkdir -p $ARCHIVE_NAME
          cp ruff/target/$TARGET/release/ty $ARCHIVE_NAME/ty
          tar czvf $ARCHIVE_FILE $ARCHIVE_NAME
          shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
      - name: "Upload binary"
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: artifacts-${{ matrix.platform.target }}
          path: |
            *.tar.gz
            *.sha256

  musllinux:
    if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-build') }}
    runs-on: ubuntu-latest
    strategy:
      matrix:
        target:
          - x86_64-unknown-linux-musl
          - i686-unknown-linux-musl
    steps:
      - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
        with:
          submodules: recursive
          persist-credentials: false
      - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
        with:
          python-version: ${{ env.PYTHON_VERSION }}
          architecture: x64
      - name: "Prep README.md"
        run: python scripts/transform_readme.py
      - name: "Build wheels"
        uses: PyO3/maturin-action@e83996d129638aa358a18fbd1dfb82f0b0fb5d3b # v1.51.0
        with:
          maturin-version: v1.13.3
          target: ${{ matrix.target }}
          manylinux: musllinux_1_2
          args: --release --locked --out dist --compatibility pypi
      - name: "Test wheel"
        if: matrix.target == 'x86_64-unknown-linux-musl'
        run: |
          docker run --rm -v ${{ github.workspace }}:/io -w /io --env MODULE_NAME --env PACKAGE_NAME alpine:latest sh -c "
            apk add python3;
            python -m venv .venv;
            .venv/bin/pip3 install ${PACKAGE_NAME} --no-index --find-links dist/ --force-reinstall;
            .venv/bin/${MODULE_NAME} --help;
            "
      - name: "Upload wheels"
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: wheels-${{ matrix.target }}
          path: dist
      - name: "Archive binary"
        shell: bash
        run: |
          set -euo pipefail

          TARGET=${{ matrix.target }}
          ARCHIVE_NAME=ty-$TARGET
          ARCHIVE_FILE=$ARCHIVE_NAME.tar.gz

          mkdir -p $ARCHIVE_NAME
          cp ruff/target/$TARGET/release/ty $ARCHIVE_NAME/ty
          tar czvf $ARCHIVE_FILE $ARCHIVE_NAME
          shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
      - name: "Upload binary"
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: artifacts-${{ matrix.target }}
          path: |
            *.tar.gz
            *.sha256

  musllinux-cross:
    if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-build') }}
    runs-on: ubuntu-latest
    strategy:
      matrix:
        platform:
          - target: aarch64-unknown-linux-musl
            arch: aarch64
            maturin_docker_options: -e JEMALLOC_SYS_WITH_LG_PAGE=16
          - target: armv7-unknown-linux-musleabihf
            arch: armv7

    steps:
      - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
        with:
          submodules: recursive
          persist-credentials: false
      - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
        with:
          python-version: ${{ env.PYTHON_VERSION }}
      - name: "Prep README.md"
        run: python scripts/transform_readme.py
      - name: "Build wheels"
        uses: PyO3/maturin-action@e83996d129638aa358a18fbd1dfb82f0b0fb5d3b # v1.51.0
        with:
          maturin-version: v1.13.3
          target: ${{ matrix.platform.target }}
          manylinux: musllinux_1_2
          args: --release --locked --out dist --compatibility pypi
          docker-options: ${{ matrix.platform.maturin_docker_options }}
      - uses: uraimo/run-on-arch-action@f9b26e3a1a408d5fd530d20c17b9f3f4428ff8d9 # v3.1.0
        name: Test wheel
        with:
          arch: ${{ matrix.platform.arch }}
          distro: alpine_latest
          githubToken: ${{ github.token }}
          install: |
            apk add python3
          run: |
            python -m venv .venv
            .venv/bin/pip3 install ${{ env.PACKAGE_NAME }} --no-index --find-links dist/ --force-reinstall
            .venv/bin/${{ env.MODULE_NAME }} --help
      - name: "Upload wheels"
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: wheels-${{ matrix.platform.target }}
          path: dist
      - name: "Archive binary"
        shell: bash
        run: |
          set -euo pipefail

          TARGET=${{ matrix.platform.target }}
          ARCHIVE_NAME=ty-$TARGET
          ARCHIVE_FILE=$ARCHIVE_NAME.tar.gz

          mkdir -p $ARCHIVE_NAME
          cp ruff/target/$TARGET/release/ty $ARCHIVE_NAME/ty
          tar czvf $ARCHIVE_FILE $ARCHIVE_NAME
          shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
      - name: "Upload binary"
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: artifacts-${{ matrix.platform.target }}
          path: |
            *.tar.gz
            *.sha256

```

## /.github/workflows/build-docker.yml

```yml path="/.github/workflows/build-docker.yml" 
# Build and publish a Docker image.
#
# Assumed to run as a subworkflow of .github/workflows/release.yml; specifically, as a local
# artifacts job within `cargo-dist`.
#
# TODO(charlie): Ideally, the publish step would happen as a publish job within `cargo-dist`, but
# sharing the built image as an artifact between jobs is challenging.
name: "Build Docker image"

on:
  workflow_call:
    inputs:
      plan:
        required: true
        type: string
  pull_request:
    paths:
      - .github/workflows/build-docker.yml

env:
  TY_BASE_IMG: ghcr.io/${{ github.repository_owner }}/ty

permissions:
  contents: read
  # TODO(zanieb): Ideally, this would be `read` on dry-run but that will require
  # significant changes to the workflow.
  packages: write # zizmor: ignore[excessive-permissions]

jobs:
  docker-build:
    name: Build Docker image (ghcr.io/astral-sh/ty) for ${{ matrix.platform }}
    runs-on: ubuntu-latest
    environment:
      name: ${{ inputs.plan != '' && !fromJson(inputs.plan).announcement_tag_is_implicit && 'release' || '' }}
    strategy:
      fail-fast: false
      matrix:
        platform:
          - linux/amd64
          - linux/arm64
    steps:
      - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
        with:
          submodules: recursive
          persist-credentials: false

      - uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0

      - uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
        if: ${{ inputs.plan != '' && !fromJson(inputs.plan).announcement_tag_is_implicit }}
        with:
          registry: ghcr.io
          username: ${{ github.repository_owner }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Check tag consistency
        if: ${{ inputs.plan != '' && !fromJson(inputs.plan).announcement_tag_is_implicit }}
        env:
          TAG: ${{ inputs.plan != '' && fromJson(inputs.plan).announcement_tag || 'dry-run' }}
        run: |
          version=$(grep -m 1 "^version = " pyproject.toml | sed -e 's/version = "\(.*\)"/\1/g')
          if [ "${TAG}" != "${version}" ]; then
            echo "The input tag does not match the version from pyproject.toml:" >&2
            echo "${TAG}" >&2
            echo "${version}" >&2
            exit 1
          else
            echo "Releasing ${version}"
          fi

      - name: Extract metadata (tags, labels) for Docker
        id: meta
        uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # v6.1.0
        with:
          images: ${{ env.TY_BASE_IMG }}
          # Defining this makes sure the org.opencontainers.image.version OCI label becomes the actual release version and not the branch name
          tags: |
            type=raw,value=dry-run,enable=${{ inputs.plan == '' || fromJson(inputs.plan).announcement_tag_is_implicit }}
            type=pep440,pattern={{ version }},value=${{ inputs.plan != '' && fromJson(inputs.plan).announcement_tag || 'dry-run' }},enable=${{ inputs.plan != '' && !fromJson(inputs.plan).announcement_tag_is_implicit }}

      - name: Normalize Platform Pair (replace / with -)
        run: |
          platform=${{ matrix.platform }}
          echo "PLATFORM_TUPLE=${platform//\//-}" >> "$GITHUB_ENV"

      # Adapted from https://docs.docker.com/build/ci/github-actions/multi-platform/
      - name: Build and push by digest
        id: build
        uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0
        with:
          context: .
          platforms: ${{ matrix.platform }}
          cache-from: type=gha,scope=ty-${{ env.PLATFORM_TUPLE }}
          cache-to: type=gha,mode=min,scope=ty-${{ env.PLATFORM_TUPLE }}
          labels: ${{ steps.meta.outputs.labels }}
          outputs: type=image,name=${{ env.TY_BASE_IMG }},push-by-digest=true,name-canonical=true,push=${{ inputs.plan != '' && !fromJson(inputs.plan).announcement_tag_is_implicit }}

      - name: Export digests
        env:
          digest: ${{ steps.build.outputs.digest }}
        run: |
          mkdir -p /tmp/digests
          touch "/tmp/digests/${digest#sha256:}"

      - name: Upload digests
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: digests-${{ env.PLATFORM_TUPLE }}
          path: /tmp/digests/*
          if-no-files-found: error
          retention-days: 1

  docker-publish:
    name: Publish Docker image (ghcr.io/astral-sh/ty)
    runs-on: ubuntu-latest
    environment:
      name: release
    needs:
      - docker-build
    if: ${{ inputs.plan != '' && !fromJson(inputs.plan).announcement_tag_is_implicit }}
    permissions:
      attestations: write
      id-token: write
      packages: write
    steps:
      - name: Download digests
        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
        with:
          path: /tmp/digests
          pattern: digests-*
          merge-multiple: true

      - uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0

      - name: Extract metadata (tags, labels) for Docker
        id: meta
        uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # v6.1.0
        with:
          images: ${{ env.TY_BASE_IMG }}
          # Order is on purpose such that the label org.opencontainers.image.version has the first pattern with the full version
          tags: |
            type=pep440,pattern={{ version }},value=${{ fromJson(inputs.plan).announcement_tag }}
            type=pep440,pattern={{ major }}.{{ minor }},value=${{ fromJson(inputs.plan).announcement_tag }}

      - uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
        with:
          registry: ghcr.io
          username: ${{ github.repository_owner }}
          password: ${{ secrets.GITHUB_TOKEN }}

      # Adapted from https://docs.docker.com/build/ci/github-actions/multi-platform/
      - name: Create manifest list and push
        working-directory: /tmp/digests
        # The jq command expands the docker/metadata json "tags" array entry to `-t tag1 -t tag2 ...` for each tag in the array
        # The printf will expand the base image with the `<TY_BASE_IMG>@sha256:<sha256> ...` for each sha256 in the directory
        # The final command becomes `docker buildx imagetools create -t tag1 -t tag2 ... <TY_BASE_IMG>@sha256:<sha256_1> <TY_BASE_IMG>@sha256:<sha256_2> ...`
        run: |
          # shellcheck disable=SC2046
          docker buildx imagetools create \
            $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
            $(printf "${TY_BASE_IMG}@sha256:%s " *)

      - name: Export manifest digest
        id: manifest-digest
        env:
          IMAGE: ${{ env.TY_BASE_IMG }}
          VERSION: ${{ steps.meta.outputs.version }}
        run: |
          digest="$(
            docker buildx imagetools inspect \
              "${IMAGE}:${VERSION}" \
              --format '{{json .Manifest}}' \
            | jq -r '.digest'
          )"
          echo "digest=${digest}" >> "$GITHUB_OUTPUT"

      - name: Generate artifact attestation
        uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0
        with:
          subject-name: ${{ env.TY_BASE_IMG }}
          subject-digest: ${{ steps.manifest-digest.outputs.digest }}

  docker-publish-extra:
    name: Publish additional Docker image based on ${{ matrix.image-mapping }}
    runs-on: ubuntu-latest
    environment:
      name: release
    needs:
      - docker-publish
    if: ${{ inputs.plan != '' && !fromJson(inputs.plan).announcement_tag_is_implicit }}
    permissions:
      attestations: write
      id-token: write
      packages: write
    strategy:
      fail-fast: false
      matrix:
        # Mapping of base image followed by a comma followed by one or more base tags (comma separated)
        # Note, org.opencontainers.image.version label will use the first base tag (use the most specific tag first)
        image-mapping:
          - alpine:3.23,alpine3.23,alpine
          - debian:trixie-slim,trixie-slim,debian-slim
          - buildpack-deps:trixie,trixie,debian
    steps:
      - uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0

      - uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
        with:
          registry: ghcr.io
          username: ${{ github.repository_owner }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Generate Dynamic Dockerfile Tags
        shell: bash
        env:
          TAG_VALUE: ${{ fromJson(inputs.plan).announcement_tag }}
        run: |
          set -euo pipefail

          # Extract the image and tags from the matrix variable
          IFS=',' read -r BASE_IMAGE BASE_TAGS <<< "${{ matrix.image-mapping }}"

          # Generate Dockerfile content
          cat <<EOF > Dockerfile
          FROM ${BASE_IMAGE}
          COPY --from=${TY_BASE_IMG}:latest /ty /usr/local/bin/ty
          ENTRYPOINT []
          CMD ["/usr/local/bin/ty"]
          EOF

          # Initialize a variable to store all tag docker metadata patterns
          TAG_PATTERNS=""

          # Loop through all base tags and append its docker metadata pattern to the list
          # Order is on purpose such that the label org.opencontainers.image.version has the first pattern with the full version
          IFS=','; for TAG in ${BASE_TAGS}; do
            TAG_PATTERNS="${TAG_PATTERNS}type=pep440,pattern={{ version }},suffix=-${TAG},value=${TAG_VALUE}\n"
            TAG_PATTERNS="${TAG_PATTERNS}type=pep440,pattern={{ major }}.{{ minor }},suffix=-${TAG},value=${TAG_VALUE}\n"
            TAG_PATTERNS="${TAG_PATTERNS}type=raw,value=${TAG}\n"
          done

          # Remove the trailing newline from the pattern list
          TAG_PATTERNS="${TAG_PATTERNS%\\n}"

          # Export image cache name
          echo "IMAGE_REF=${BASE_IMAGE//:/-}" >> "$GITHUB_ENV"

          # Export tag patterns using the multiline env var syntax
          {
            echo "TAG_PATTERNS<<EOF"
            echo -e "${TAG_PATTERNS}"
            echo EOF
          } >> "$GITHUB_ENV"

      - name: Extract metadata (tags, labels) for Docker
        id: meta
        uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # v6.1.0
        # ghcr.io prefers index level annotations
        env:
          DOCKER_METADATA_ANNOTATIONS_LEVELS: index
        with:
          images: ${{ env.TY_BASE_IMG }}
          flavor: |
            latest=false
          tags: |
            ${{ env.TAG_PATTERNS }}

      - name: Build and push
        id: build-and-push
        uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0
        with:
          context: .
          platforms: linux/amd64,linux/arm64
          # We do not really need to cache here as the Dockerfile is tiny
          #cache-from: type=gha,scope=ty-${{ env.IMAGE_REF }}
          #cache-to: type=gha,mode=min,scope=ty-${{ env.IMAGE_REF }}
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          annotations: ${{ steps.meta.outputs.annotations }}

      - name: Generate artifact attestation
        uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0
        with:
          subject-name: ${{ env.TY_BASE_IMG }}
          subject-digest: ${{ steps.build-and-push.outputs.digest }}

  # This is effectively a duplicate of `docker-publish` to make https://github.com/astral-sh/ty/pkgs/container/ty
  # show the ty base image first since GitHub always shows the last updated image digests
  # This works by annotating the original digests (previously non-annotated) which triggers an update to ghcr.io
  docker-republish:
    name: Annotate Docker image (ghcr.io/astral-sh/ty)
    runs-on: ubuntu-latest
    environment:
      name: release
    needs:
      - docker-publish-extra
    if: ${{ inputs.plan != '' && !fromJson(inputs.plan).announcement_tag_is_implicit }}
    permissions:
      attestations: write
      id-token: write
      packages: write
    steps:
      - name: Download digests
        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
        with:
          path: /tmp/digests
          pattern: digests-*
          merge-multiple: true

      - uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0

      - name: Extract metadata (tags, labels) for Docker
        id: meta
        uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # v6.1.0
        env:
          DOCKER_METADATA_ANNOTATIONS_LEVELS: index
        with:
          images: ${{ env.TY_BASE_IMG }}
          # Order is on purpose such that the label org.opencontainers.image.version has the first pattern with the full version
          tags: |
            type=pep440,pattern={{ version }},value=${{ fromJson(inputs.plan).announcement_tag }}
            type=pep440,pattern={{ major }}.{{ minor }},value=${{ fromJson(inputs.plan).announcement_tag }}

      - uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
        with:
          registry: ghcr.io
          username: ${{ github.repository_owner }}
          password: ${{ secrets.GITHUB_TOKEN }}

      # Adapted from https://docs.docker.com/build/ci/github-actions/multi-platform/
      - name: Create manifest list and push
        working-directory: /tmp/digests
        # The readarray part is used to make sure the quoting and special characters are preserved on expansion (e.g. spaces)
        # The jq command expands the docker/metadata json "tags" array entry to `-t tag1 -t tag2 ...` for each tag in the array
        # The printf will expand the base image with the `<TY_BASE_IMG>@sha256:<sha256> ...` for each sha256 in the directory
        # The final command becomes `docker buildx imagetools create -t tag1 -t tag2 ... <TY_BASE_IMG>@sha256:<sha256_1> <TY_BASE_IMG>@sha256:<sha256_2> ...`
        run: |
          readarray -t lines <<< "$DOCKER_METADATA_OUTPUT_ANNOTATIONS"; annotations=(); for line in "${lines[@]}"; do annotations+=(--annotation "$line"); done

          # shellcheck disable=SC2046
          docker buildx imagetools create \
            "${annotations[@]}" \
            $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
            $(printf "${TY_BASE_IMG}@sha256:%s " *)

      - name: Export manifest digest
        id: manifest-digest
        env:
          IMAGE: ${{ env.TY_BASE_IMG }}
          VERSION: ${{ steps.meta.outputs.version }}
        run: |
          digest="$(
            docker buildx imagetools inspect \
              "${IMAGE}:${VERSION}" \
              --format '{{json .Manifest}}' \
            | jq -r '.digest'
          )"
          echo "digest=${digest}" >> "$GITHUB_OUTPUT"

      - name: Generate artifact attestation
        uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0
        with:
          subject-name: ${{ env.TY_BASE_IMG }}
          subject-digest: ${{ steps.manifest-digest.outputs.digest }}

```

## /.github/workflows/ci.yaml

```yaml path="/.github/workflows/ci.yaml" 
name: CI

permissions:
  contents: read

on:
  push:
    branches: [main]
  pull_request:
  workflow_dispatch:

concurrency:
  group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.event.pull_request.number || github.sha }}
  cancel-in-progress: true

env:
  CARGO_INCREMENTAL: 0
  CARGO_NET_RETRY: 10
  CARGO_TERM_COLOR: always
  RUSTUP_MAX_RETRIES: 10
  PACKAGE_NAME: ty

jobs:
  python-package:
    name: "python package"
    runs-on: ubuntu-latest
    timeout-minutes: 20
    steps:
      - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
        with:
          persist-credentials: false
          submodules: recursive
      - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
        with:
          python-version: ${{ env.PYTHON_VERSION }}
          architecture: x64
      - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
      - name: "Build wheels"
        uses: PyO3/maturin-action@e83996d129638aa358a18fbd1dfb82f0b0fb5d3b # v1.51.0
        with:
          args: --out dist
      - name: "Test wheel"
        run: |
          pip install --force-reinstall --find-links dist "${PACKAGE_NAME}" --pre
          ty --help
          python -m ty --help
      - name: "Remove wheels from cache"
        run: rm -rf target/wheels

  prek:
    name: "prek"
    runs-on: depot-ubuntu-22.04-16
    timeout-minutes: 10
    steps:
      - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
        with:
          persist-credentials: false
      - uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
      - name: "Cache prek"
        uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
        with:
          path: ~/.cache/prek
          key: prek-${{ hashFiles('.pre-commit-config.yaml') }}
      - name: "Run prek"
        run: |
          echo '\`\`\`console' > "$GITHUB_STEP_SUMMARY"
          # Enable color output for prek and remove it for the summary
          # Use --hook-stage=manual to enable slower hooks that are skipped by default
          SKIP=cargo-fmt,clippy,dev-generate-all uvx prek run --all-files --show-diff-on-failure --color always --hook-stage manual | \
            tee >(sed -E 's/\x1B\[([0-9]{1,2}(;[0-9]{1,2})*)?[mGK]//g' >> "$GITHUB_STEP_SUMMARY") >&1
          exit_code="${PIPESTATUS[0]}"
          echo '\`\`\`' >> "$GITHUB_STEP_SUMMARY"
          exit "$exit_code"

  generated-check:
    name: "Check generated files unedited"
    runs-on: ubuntu-latest
    timeout-minutes: 5
    steps:
      - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
        with:
          persist-credentials: false
          submodules: recursive

      - uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0

      - name: "Run auto generation scripts"
        run: |
          ./scripts/autogenerate_files.sh

      - name: "Check for uncommitted changes"
        run: |
          if [[ -n "$(git status --porcelain)" ]]; then
            echo "Error: Auto-generated files were manually edited."
            echo "Files with changes:"
            git status --porcelain
            exit 1
          fi

  docs:
    timeout-minutes: 10
    name: "mkdocs"
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
        with:
          fetch-depth: 0
          persist-credentials: false
      - uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
      - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0

      - name: "Build docs"
        run: uvx --with-requirements docs/requirements.txt mkdocs build --strict -f mkdocs.yml

```

## /.github/workflows/daily_property_tests.yml

```yml path="/.github/workflows/daily_property_tests.yml" 
name: Daily property test run

on:
  workflow_dispatch:
  schedule:
    - cron: "0 12 * * *"
  pull_request:
    paths:
      - ".github/workflows/daily_property_tests.yml"

permissions:
  contents: read

concurrency:
  group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
  cancel-in-progress: true

env:
  CARGO_INCREMENTAL: 0
  CARGO_NET_RETRY: 10
  CARGO_TERM_COLOR: always
  RUSTUP_MAX_RETRIES: 10
  FORCE_COLOR: 1

jobs:
  property_tests:
    name: Property tests
    runs-on: ubuntu-latest
    timeout-minutes: 20
    # Don't run the cron job on forks:
    if: ${{ github.repository == 'astral-sh/ty' || github.event_name != 'schedule' }}
    steps:
      - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
        with:
          persist-credentials: false
          repository: astral-sh/ruff
      - name: "Install Rust toolchain"
        run: rustup show
      - name: "Install mold"
        uses: rui314/setup-mold@9c9c13bf4c3f1adef0cc596abc155580bcb04444 # v1
      - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
      - name: Build ty
        # A release build takes longer (2 min vs 1 min), but the property tests run much faster in release
        # mode (1.5 min vs 14 min), so the overall time is shorter with a release build.
        run: cargo build --locked --release --package ty_python_semantic --tests
      - name: Run property tests
        shell: bash
        run: |
          export QUICKCHECK_TESTS=100000
          for _ in {1..5}; do
            cargo test --locked --release --package ty_python_semantic -- --ignored list::property_tests
            cargo test --locked --release --package ty_python_semantic -- --ignored types::property_tests::stable
          done

  create-issue-on-failure:
    name: Create an issue if the daily property test run surfaced any bugs
    runs-on: ubuntu-latest
    needs: property_tests
    if: ${{ github.repository == 'astral-sh/ty' && always() && github.event_name == 'schedule' && needs.property_tests.result == 'failure' }}
    permissions:
      issues: write
    steps:
      - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}
          script: |
            await github.rest.issues.create({
              owner: "astral-sh",
              repo: "ty",
              title: `Daily property test run failed on ${new Date().toDateString()}`,
              body: "Run listed here: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}",
              labels: ["bug", "type properties"],
            })

```

## /.github/workflows/notify-dependents.yml

```yml path="/.github/workflows/notify-dependents.yml" 
# Notify downstream repositories of a new release.
#
# Assumed to run as a subworkflow of .github/workflows/release.yml; specifically, as a post-announce
# job within `cargo-dist`.
name: "[ty] Notify dependents"

on:
  workflow_call:
    inputs:
      plan:
        required: true
        type: string

jobs:
  update-dependents:
    name: Notify dependents
    environment:
      name: release
    runs-on: ubuntu-latest
    steps:
      - name: "Update pre-commit mirror"
        uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
        with:
          github-token: ${{ secrets.TY_PRE_COMMIT_PAT }}
          script: |
            github.rest.actions.createWorkflowDispatch({
              owner: 'astral-sh',
              repo: 'ty-pre-commit',
              workflow_id: 'main.yml',
              ref: 'main',
            })

```

## /.github/workflows/publish-docs.yml

```yml path="/.github/workflows/publish-docs.yml" 
# Publish the ty documentation.
#
# Assumed to run as a subworkflow of .github/workflows/release.yml; specifically, as a post-announce
# job within `cargo-dist`.
name: mkdocs

on:
  workflow_dispatch:
    inputs:
      ref:
        description: "The commit SHA, tag, or branch to publish. Uses the default branch if not specified."
        default: ""
        type: string
  workflow_call:
    inputs:
      plan:
        required: true
        type: string

permissions:
  contents: read

jobs:
  mkdocs:
    environment:
      name: release
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
        with:
          ref: ${{ inputs.ref }}
          persist-credentials: true

      - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
        with:
          python-version: 3.14

      - name: "Set docs version"
        env:
          version: ${{ (inputs.plan != '' && fromJson(inputs.plan).announcement_tag) || inputs.ref }}
        run: |
          # if version is missing, use 'latest'
          if [ -z "$version" ]; then
            echo "Using 'latest' as version"
            version="latest"
          fi

          # Use version as display name for now
          display_name="$version"

          echo "version=$version" >> "$GITHUB_ENV"
          echo "display_name=$display_name" >> "$GITHUB_ENV"

      - name: "Set branch name"
        run: |
          timestamp="$(date +%s)"

          # create branch_display_name from display_name by replacing all
          # characters disallowed in git branch names with hyphens
          branch_display_name="$(echo "${display_name}" | tr -c '[:alnum:]._' '-' | tr -s '-')"

          echo "branch_name=update-docs-$branch_display_name-$timestamp" >> "$GITHUB_ENV"
          echo "timestamp=$timestamp" >> "$GITHUB_ENV"

      - name: "Install dependencies"
        run: pip install -r docs/requirements.txt

      - name: "Build docs"
        run: mkdocs build --strict -f mkdocs.yml

      - name: "Clone docs repo"
        run: git clone https://${{ secrets.ASTRAL_DOCS_PAT }}@github.com/astral-sh/docs.git astral-docs

      - name: "Copy docs"
        run: rm -rf astral-docs/site/ty && mkdir -p astral-docs/site && cp -r site/ty astral-docs/site/

      - name: "Commit docs"
        working-directory: astral-docs
        run: |
          git config user.name "astral-docs-bot"
          git config user.email "176161322+astral-docs-bot@users.noreply.github.com"

          git checkout -b "${branch_name}"
          git add site/ty
          git commit -m "Update ty documentation for $version"

      - name: "Create Pull Request"
        working-directory: astral-docs
        env:
          GITHUB_TOKEN: ${{ secrets.ASTRAL_DOCS_PAT }}
        run: |
          # set the PR title
          pull_request_title="Update ty documentation for ${display_name}"

          # Delete any existing pull requests that are open for this version
          # by checking against pull_request_title because the new PR will
          # supersede the old one.
          gh pr list --state open --json title --jq '.[] | select(.title == "$pull_request_title") | .number' | \
            xargs -I {} gh pr close {}

          # push the branch to GitHub
          git push origin "${branch_name}"

          # create the PR
          gh pr create \
            --base=main \
            --head="${branch_name}" \
            --title="${pull_request_title}" \
            --body="Automated documentation update for ${display_name}" \
            --label="documentation"

      - name: "Merge Pull Request"
        if: ${{ inputs.plan != '' && !fromJson(inputs.plan).announcement_tag_is_implicit }}
        working-directory: astral-docs
        env:
          GITHUB_TOKEN: ${{ secrets.ASTRAL_DOCS_PAT }}
        run: |
          # auto-merge the PR if the build was triggered by a release. Manual builds should be reviewed by a human.
          # give the PR a few seconds to be created before trying to auto-merge it
          sleep 10
          gh pr merge --squash "${branch_name}"

```

## /.github/workflows/publish-mirror.yml

```yml path="/.github/workflows/publish-mirror.yml" 
# Publish ty releases to a mirror
#
# Assumed to run as a subworkflow of .github/workflows/release.yml as a custom publish job
name: publish-mirror

on:
  workflow_call:
    inputs:
      plan:
        required: true
        type: string

permissions: {}

jobs:
  publish-mirror:
    runs-on: ubuntu-latest
    environment:
      name: release
    env:
      VERSION: ${{ fromJson(inputs.plan).announcement_tag }}
    steps:
      - name: "Download GitHub Artifacts"
        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
        with:
          pattern: artifacts-*
          path: artifacts
          merge-multiple: true
      - name: "Upload to R2"
        env:
          AWS_ACCESS_KEY_ID: ${{ secrets.MIRROR_R2_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.MIRROR_R2_SECRET_ACCESS_KEY }}
          AWS_ENDPOINT_URL: https://${{ secrets.MIRROR_R2_CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com
          AWS_DEFAULT_REGION: auto
          R2_BUCKET: ${{ secrets.MIRROR_R2_BUCKET_NAME }}
          PROJECT: ty
        run: |
          aws s3 cp --recursive --output table --color on \
            --exclude '*' \
            --include '*.zip' --include '*.zip.sha256' \
            --include '*.tar.gz' --include '*.tar.gz.sha256' \
            --include sha256.sum --include '*.ps1' --include '*.sh' \
            --cache-control "public, max-age=31536000, immutable" \
            artifacts/ \
            "s3://${R2_BUCKET}/github/${PROJECT}/releases/download/${VERSION}/"
      - name: "Upload latest installers to R2"
        if: ${{ !fromJson(inputs.plan).announcement_is_prerelease }}
        env:
          AWS_ACCESS_KEY_ID: ${{ secrets.MIRROR_R2_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.MIRROR_R2_SECRET_ACCESS_KEY }}
          AWS_ENDPOINT_URL: https://${{ secrets.MIRROR_R2_CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com
          AWS_DEFAULT_REGION: auto
          R2_BUCKET: ${{ secrets.MIRROR_R2_BUCKET_NAME }}
        run: |
          for installer in ty-installer.sh ty-installer.ps1; do
            aws s3 cp --output table --color on \
              --cache-control "public, max-age=300" \
              "artifacts/${installer}" \
              "s3://${R2_BUCKET}/installers/ty/latest/${installer}"
          done

```

## /.github/workflows/publish-pypi.yml

```yml path="/.github/workflows/publish-pypi.yml" 
# Publish a release to PyPI.
#
# Assumed to run as a subworkflow of .github/workflows/release.yml; specifically, as a publish job
# within `cargo-dist`.
name: "Publish to PyPI"

on:
  workflow_call:
    inputs:
      plan:
        required: true
        type: string

jobs:
  pypi-publish:
    name: Upload to PyPI
    runs-on: ubuntu-latest
    environment:
      name: release
    permissions:
      # For PyPI's trusted publishing.
      id-token: write
    steps:
      - name: "Install uv"
        uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
      - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
        with:
          pattern: wheels-*
          path: wheels
          merge-multiple: true
      - name: Publish to PyPi
        run: uv publish -v wheels/*

```

## /.github/workflows/publish-versions.yml

```yml path="/.github/workflows/publish-versions.yml" 
# Publish ty version information to the versions repository.
#
# Assumed to run as a subworkflow of .github/workflows/release.yml; specifically, as a post-announce
# job within `cargo-dist`.
name: publish-versions

on:
  workflow_call:
    inputs:
      plan:
        required: true
        type: string

permissions: {}

jobs:
  publish-versions:
    runs-on: ubuntu-latest
    environment: release
    env:
      VERSION: ${{ fromJson(inputs.plan).announcement_tag }}
    steps:
      - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
        with:
          persist-credentials: false

      - name: "Set branch name"
        run: echo "BRANCH_NAME=update-versions-$VERSION-$(date +%s)" >> "$GITHUB_ENV"

      - name: "Clone versions repo"
        run: git clone https://${{ secrets.ASTRAL_VERSIONS_PAT }}@github.com/astral-sh/versions.git astral-versions

      - name: "Install uv"
        uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0

      - name: "Update versions"
        env:
          PLAN: ${{ inputs.plan }}
        run: printf '%s' "$PLAN" | uv run astral-versions/scripts/convert-cargo-dist-plan.py | uv run astral-versions/scripts/insert-versions.py --name ty

      - name: "Commit versions"
        working-directory: astral-versions
        run: |
          git config user.name "astral-versions-bot"
          git config user.email "176161322+astral-versions-bot@users.noreply.github.com"

          git checkout -b "$BRANCH_NAME"
          git add -A
          git commit -m "Update ty to version $VERSION"

      - name: "Create Pull Request"
        working-directory: astral-versions
        env:
          GITHUB_TOKEN: ${{ secrets.ASTRAL_VERSIONS_PAT }}
        run: |
          pull_request_title="Add ty $VERSION"

          gh pr list --state open --json title --jq ".[] | select(.title == \"$pull_request_title\") | .number" | \
            xargs -I {} gh pr close {}

          git push origin "$BRANCH_NAME"

          gh pr create --base main --head "$BRANCH_NAME" \
            --title "$pull_request_title" \
            --body "Automated versions update for $VERSION" \
            --label "automation"

      - name: "Merge Pull Request"
        if: ${{ !fromJson(inputs.plan).announcement_tag_is_implicit }}
        working-directory: astral-versions
        env:
          GITHUB_TOKEN: ${{ secrets.ASTRAL_VERSIONS_PAT }}
        run: |
          # Wait for PR to be created before merging
          sleep 10
          gh pr merge --squash "$BRANCH_NAME"

```

## /.github/workflows/release.yml

```yml path="/.github/workflows/release.yml" 
# This file was autogenerated by dist: https://axodotdev.github.io/cargo-dist
#
# Copyright 2022-2024, axodotdev
# SPDX-License-Identifier: MIT or Apache-2.0
#
# CI that:
#
# * checks for a Git Tag that looks like a release
# * builds artifacts with dist (archives, installers, hashes)
# * uploads those artifacts to temporary workflow zip
# * on success, uploads the artifacts to a GitHub Release
#
# Note that the GitHub Release will be created with a generated
# title/body based on your changelogs.

name: Release
permissions:
  "contents": "write"

# This task will run whenever you workflow_dispatch with a tag that looks like a version
# like "1.0.0", "v0.1.0-prerelease.1", "my-app/0.1.0", "releases/v1.0.0", etc.
# Various formats will be parsed into a VERSION and an optional PACKAGE_NAME, where
# PACKAGE_NAME must be the name of a Cargo package in your workspace, and VERSION
# must be a Cargo-style SemVer Version (must have at least major.minor.patch).
#
# If PACKAGE_NAME is specified, then the announcement will be for that
# package (erroring out if it doesn't have the given version or isn't dist-able).
#
# If PACKAGE_NAME isn't specified, then the announcement will be for all
# (dist-able) packages in the workspace with that version (this mode is
# intended for workspaces with only one dist-able package, or with all dist-able
# packages versioned/released in lockstep).
#
# If you push multiple tags at once, separate instances of this workflow will
# spin up, creating an independent announcement for each one. However, GitHub
# will hard limit this to 3 tags per commit, as it will assume more tags is a
# mistake.
#
# If there's a prerelease-style suffix to the version, then the release(s)
# will be marked as a prerelease.
on:
  pull_request:
  workflow_dispatch:
    inputs:
      tag:
        description: Release Tag
        required: true
        default: dry-run
        type: string

env:
  CARGO_DIST_VERSION: "0.31.0"
  CARGO_DIST_CHECKSUM: "cd355dab0b4c02fb59038fef87655550021d07f45f1d82f947a34ef98560abb8"

jobs:
  release-gate:
    # N.B. This name should not change, it is used for downstream checks.
    name: release-gate
    if: ${{ github.event_name == 'workflow_dispatch' && inputs.tag != 'dry-run' }}
    runs-on: ubuntu-latest
    # This environment requires a 2-factor approval, i.e., the workflow must be approved by another
    # team member. GitHub fires approval events on every job that deploys to an environment, so we
    # have a dedicated environment for this purpose instead of using the `release` environment.
    # We use a GitHub App with a deployment protection rule webhook to ensure that the `release`
    # environment is only approved when the `release-gate` job succeeds.
    environment:
      name: release-gate
      deployment: true
    steps:
      - run: echo "Release approved"

  # Run 'dist plan' (or host) to determine what tasks we need to do
  plan:
    runs-on: "depot-ubuntu-latest-4"
    outputs:
      val: ${{ steps.plan.outputs.manifest }}
      tag: ${{ (inputs.tag != 'dry-run' && inputs.tag) || '' }}
      tag-flag: ${{ inputs.tag && inputs.tag != 'dry-run' && format('--tag={0}', inputs.tag) || '' }}
      publishing: ${{ inputs.tag && inputs.tag != 'dry-run' }}
    env:
      GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
    steps:
      - uses: actions/checkout@0c366fd6a839edf440554fa01a7085ccba70ac98
        with:
          persist-credentials: false
          submodules: recursive
      - name: Install dist
        shell: bash
        run: |
          curl --proto '=https' --tlsv1.2 -LsSf "https://github.com/axodotdev/cargo-dist/releases/download/v${CARGO_DIST_VERSION}/cargo-dist-x86_64-unknown-linux-gnu.tar.xz" -o /tmp/cargo-dist.tar.xz
          echo "${CARGO_DIST_CHECKSUM}  /tmp/cargo-dist.tar.xz" | sha256sum -c -
          tar -xf /tmp/cargo-dist.tar.xz -C /tmp
          install /tmp/cargo-dist-x86_64-unknown-linux-gnu/dist ~/.cargo/bin/
      - name: Cache dist
        uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f
        with:
          name: cargo-dist-cache
          path: ~/.cargo/bin/dist
      # sure would be cool if github gave us proper conditionals...
      # so here's a doubly-nested ternary-via-truthiness to try to provide the best possible
      # functionality based on whether this is a pull_request, and whether it's from a fork.
      # (PRs run on the *source* but secrets are usually on the *target* -- that's *good*
      # but also really annoying to build CI around when it needs secrets to work right.)
      - id: plan
        run: |
          dist ${{ (inputs.tag && inputs.tag != 'dry-run' && format('host --steps=create --tag={0}', inputs.tag)) || 'plan' }} --output-format=json > plan-dist-manifest.json
          echo "dist ran successfully"
          cat plan-dist-manifest.json
          echo "manifest=$(jq -c "." plan-dist-manifest.json)" >> "$GITHUB_OUTPUT"
      - name: "Upload dist-manifest.json"
        uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f
        with:
          name: artifacts-plan-dist-manifest
          path: plan-dist-manifest.json

  custom-build-binaries:
    needs:
      - plan
    if: ${{ needs.plan.outputs.publishing == 'true' || fromJson(needs.plan.outputs.val).ci.github.pr_run_mode == 'upload' || inputs.tag == 'dry-run' }}
    uses: ./.github/workflows/build-binaries.yml
    with:
      plan: ${{ needs.plan.outputs.val }}
    secrets: inherit

  custom-build-docker:
    needs:
      - plan
      - release-gate
    if: ${{ always() && needs.plan.result == 'success' && (needs.release-gate.result == 'success' || needs.release-gate.result == 'skipped') && (needs.plan.outputs.publishing == 'true' || fromJson(needs.plan.outputs.val).ci.github.pr_run_mode == 'upload' || inputs.tag == 'dry-run') }}
    uses: ./.github/workflows/build-docker.yml
    with:
      plan: ${{ needs.plan.outputs.val }}
    secrets: inherit
    permissions:
      "attestations": "write"
      "contents": "read"
      "id-token": "write"
      "packages": "write"

  # Build and package all the platform-agnostic(ish) things
  build-global-artifacts:
    needs:
      - plan
      - custom-build-binaries
      - custom-build-docker
    runs-on: "depot-ubuntu-latest-4"
    env:
      GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      BUILD_MANIFEST_NAME: target/distrib/global-dist-manifest.json
    steps:
      - uses: actions/checkout@0c366fd6a839edf440554fa01a7085ccba70ac98
        with:
          persist-credentials: false
          submodules: recursive
      - name: Install cached dist
        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c
        with:
          name: cargo-dist-cache
          path: ~/.cargo/bin/
      - run: chmod +x ~/.cargo/bin/dist
      # Get all the local artifacts for the global tasks to use (for e.g. checksums)
      - name: Fetch local artifacts
        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c
        with:
          pattern: artifacts-*
          path: target/distrib/
          merge-multiple: true
      - id: cargo-dist
        shell: bash
        run: |
          dist build ${{ needs.plan.outputs.tag-flag }} --output-format=json "--artifacts=global" > dist-manifest.json
          echo "dist ran successfully"

          # Parse out what we just built and upload it to scratch storage
          echo "paths<<EOF" >> "$GITHUB_OUTPUT"
          jq --raw-output ".upload_files[]" dist-manifest.json >> "$GITHUB_OUTPUT"
          echo "EOF" >> "$GITHUB_OUTPUT"

          cp dist-manifest.json "$BUILD_MANIFEST_NAME"
      - name: "Upload artifacts"
        uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f
        with:
          name: artifacts-build-global
          path: |
            ${{ steps.cargo-dist.outputs.paths }}
            ${{ env.BUILD_MANIFEST_NAME }}
  # Determines if we should publish/announce
  host:
    needs:
      - plan
      - custom-build-binaries
      - custom-build-docker
      - build-global-artifacts
    # Only run if we're "publishing", and only if plan, local and global didn't fail (skipped is fine)
    if: ${{ always() && needs.plan.result == 'success' && needs.plan.outputs.publishing == 'true' && (needs.build-global-artifacts.result == 'skipped' || needs.build-global-artifacts.result == 'success') && (needs.custom-build-binaries.result == 'skipped' || needs.custom-build-binaries.result == 'success') && (needs.custom-build-docker.result == 'skipped' || needs.custom-build-docker.result == 'success') }}
    env:
      GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
    runs-on: "depot-ubuntu-latest-4"
    environment:
      name: release
    outputs:
      val: ${{ steps.host.outputs.manifest }}
    steps:
      - uses: actions/checkout@0c366fd6a839edf440554fa01a7085ccba70ac98
        with:
          persist-credentials: false
          submodules: recursive
      - name: Install cached dist
        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c
        with:
          name: cargo-dist-cache
          path: ~/.cargo/bin/
      - run: chmod +x ~/.cargo/bin/dist
      # Fetch artifacts from scratch-storage
      - name: Fetch artifacts
        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c
        with:
          pattern: artifacts-*
          path: target/distrib/
          merge-multiple: true
      # This is a harmless no-op for GitHub Releases, hosting for that happens in "announce"
      - id: host
        shell: bash
        run: |
          dist host ${{ needs.plan.outputs.tag-flag }} --steps=upload --steps=release --output-format=json > dist-manifest.json
          echo "artifacts uploaded and released successfully"
          cat dist-manifest.json
          echo "manifest=$(jq -c "." dist-manifest.json)" >> "$GITHUB_OUTPUT"
      - name: "Upload dist-manifest.json"
        uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f
        with:
          # Overwrite the previous copy
          name: artifacts-dist-manifest
          path: dist-manifest.json

  custom-publish-pypi:
    needs:
      - plan
      - host
      - release-gate
    if: ${{ !fromJson(needs.plan.outputs.val).announcement_is_prerelease || fromJson(needs.plan.outputs.val).publish_prereleases }}
    uses: ./.github/workflows/publish-pypi.yml
    with:
      plan: ${{ needs.plan.outputs.val }}
    secrets: inherit
    # publish jobs get escalated permissions
    permissions:
      "id-token": "write"
      "packages": "write"

  # Create a GitHub Release while uploading all files to it
  announce:
    needs:
      - plan
      - host
      - release-gate
      - custom-publish-pypi
    # use "always() && ..." to allow us to wait for all publish jobs while
    # still allowing individual publish jobs to skip themselves (for prereleases).
    # "host" however must run to completion, no skipping allowed!
    if: ${{ always() && needs.host.result == 'success' && needs.release-gate.result == 'success' && (needs.custom-publish-pypi.result == 'skipped' || needs.custom-publish-pypi.result == 'success') }}
    runs-on: "depot-ubuntu-latest-4"
    environment:
      name: release
    permissions:
      "attestations": "write"
      "contents": "write"
      "id-token": "write"
    env:
      GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
    steps:
      - uses: actions/checkout@0c366fd6a839edf440554fa01a7085ccba70ac98
        with:
          persist-credentials: false
          submodules: recursive
      # Create a GitHub Release while uploading all files to it
      - name: "Download GitHub Artifacts"
        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c
        with:
          pattern: artifacts-*
          path: artifacts
          merge-multiple: true
      - name: Cleanup
        run: |
          # Remove the granular manifests
          rm -f artifacts/*-dist-manifest.json
      - name: Attest
        uses: actions/attest-build-provenance@00014ed6ed5efc5b1ab7f7f34a39eb55d41aa4f8
        with:
          subject-path: |
            artifacts/*.json
            artifacts/*.sh
            artifacts/*.ps1
            artifacts/*.zip
            artifacts/*.tar.gz
      - name: Create GitHub Release
        env:
          PRERELEASE_FLAG: "${{ fromJson(needs.host.outputs.val).announcement_is_prerelease && '--prerelease' || '' }}"
          ANNOUNCEMENT_TITLE: "${{ fromJson(needs.host.outputs.val).announcement_title }}"
          ANNOUNCEMENT_BODY: "${{ fromJson(needs.host.outputs.val).announcement_github_body }}"
          RELEASE_COMMIT: "${{ github.sha }}"
        run: |
          # Write and read notes from a file to avoid quoting breaking things
          echo "$ANNOUNCEMENT_BODY" > $RUNNER_TEMP/notes.txt

          gh release create "${{ needs.plan.outputs.tag }}" --target "$RELEASE_COMMIT" $PRERELEASE_FLAG --title "$ANNOUNCEMENT_TITLE" --notes-file "$RUNNER_TEMP/notes.txt" artifacts/*

  custom-notify-dependents:
    needs:
      - plan
      - announce
    uses: ./.github/workflows/notify-dependents.yml
    with:
      plan: ${{ needs.plan.outputs.val }}
    secrets: inherit

  custom-publish-docs:
    needs:
      - plan
      - announce
    uses: ./.github/workflows/publish-docs.yml
    with:
      plan: ${{ needs.plan.outputs.val }}
    secrets: inherit

  custom-publish-versions:
    needs:
      - plan
      - announce
    uses: ./.github/workflows/publish-versions.yml
    with:
      plan: ${{ needs.plan.outputs.val }}
    secrets: inherit

  custom-publish-mirror:
    needs:
      - plan
      - announce
    uses: ./.github/workflows/publish-mirror.yml
    with:
      plan: ${{ needs.plan.outputs.val }}
    secrets: inherit
    permissions:
      "contents": "read"

```

## /.github/zizmor.yml

```yml path="/.github/zizmor.yml" 
rules:
  secrets-outside-env:
    ignore:
      # TODO: move these secrets to the release environment
      - publish-docs.yml
      - publish-versions.yml

```

## /.gitignore

```gitignore path="/.gitignore" 
# Python-generated files
__pycache__/
*.py[oc]
build/
dist/
wheels/
*.egg-info

# Virtual environments
.venv

```

## /.gitmodules

```gitmodules path="/.gitmodules" 
[submodule "ruff"]
	path = ruff
	url = https://github.com/astral-sh/ruff

```

## /.markdownlint.yaml

```yaml path="/.markdownlint.yaml" 
# default to true for all rules
default: true

# MD007/unordered-list-indent
MD007:
  indent: 4

# MD033/no-inline-html
MD033: false

# MD041/first-line-h1
MD041: false

# MD013/line-length
MD013: false

# MD014/commands-show-output
MD014: false

# MD024/no-duplicate-heading
MD024:
  # Allow when nested under different parents e.g. CHANGELOG.md
  siblings_only: true

# MD046/code-block-style
#
# Ignore this because it conflicts with the code block style used in content
# tabs of mkdocs-material which is to add a blank line after the content title.
#
# Ref: https://github.com/astral-sh/ruff/pull/15011#issuecomment-2544790854
MD046: false

# Link text should be descriptive
# Disallows link text like *here* which is annoying.
MD059: false

```

## /.pre-commit-config.yaml

```yaml path="/.pre-commit-config.yaml" 
fail_fast: false

exclude: |
  (?x)^(
    .github/workflows/release.yml|
    ruff/.*|
    docs/reference/(cli|configuration|rules|environment).md
  )$

repos:
  # Priority 0: Read-only hooks; hooks that modify disjoint file types.
  - repo: https://github.com/astral-sh/uv-pre-commit
    rev: 0.11.18
    hooks:
      - id: uv-lock
        priority: 0

  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v6.0.0
    hooks:
      - id: check-merge-conflict
        priority: 0

  - repo: https://github.com/astral-sh/ruff-pre-commit
    rev: v0.15.15
    hooks:
      - id: ruff-format
        priority: 0
      - id: ruff
        args: [--fix, --exit-non-zero-on-fix]
        types_or: [python, pyi]
        require_serial: true
        priority: 1

  - repo: https://github.com/abravalheri/validate-pyproject
    rev: v0.25
    hooks:
      - id: validate-pyproject
        priority: 0

  - repo: https://github.com/executablebooks/mdformat
    rev: 1.0.0
    hooks:
      - id: mdformat
        language: python # means renovate will also update `additional_dependencies`
        additional_dependencies:
          - mdformat-mkdocs==5.1.4
          - mdformat-footnote==0.1.3
        priority: 0

  - repo: https://github.com/igorshubovych/markdownlint-cli
    rev: v0.48.0
    hooks:
      - id: markdownlint-fix
        priority: 1

  - repo: https://github.com/crate-ci/typos
    rev: v1.47.0
    hooks:
      - id: typos
        priority: 0

  # Prettier
  - repo: https://github.com/rbubley/mirrors-prettier
    rev: v3.8.3
    hooks:
      - id: prettier
        types: [yaml]
        priority: 0

  # zizmor detects security vulnerabilities in GitHub Actions workflows.
  # Additional configuration for the tool is found in `.github/zizmor.yml`
  - repo: https://github.com/zizmorcore/zizmor-pre-commit
    rev: v1.25.2
    hooks:
      - id: zizmor
        priority: 0

  - repo: https://github.com/shellcheck-py/shellcheck-py
    rev: v0.11.0.1
    hooks:
      - id: shellcheck
        priority: 0

  - repo: https://github.com/python-jsonschema/check-jsonschema
    rev: 0.37.2
    hooks:
      - id: check-github-workflows
        priority: 0

  # `actionlint` hook, for verifying correct syntax in GitHub Actions workflows.
  # Some additional configuration for `actionlint` can be found in `.github/actionlint.yaml`.
  - repo: https://github.com/rhysd/actionlint
    rev: v1.7.12
    hooks:
      - id: actionlint
        stages:
          # This hook is disabled by default, since it's quite slow.
          # To run all hooks *including* this hook, use `uvx pre-commit run -a --hook-stage=manual`.
          # To run *just* this hook, use `uvx pre-commit run -a actionlint --hook-stage=manual`.
          - manual
        args:
          - "-ignore=SC2129" # ignorable stylistic lint from shellcheck
          - "-ignore=SC2016" # another shellcheck lint: seems to have false positives?
        language: golang # means renovate will also update `additional_dependencies`
        additional_dependencies:
          # actionlint has a shellcheck integration which extracts shell scripts in `run:` steps from GitHub Actions
          # and checks these with shellcheck. This is arguably its most useful feature,
          # but the integration only works if shellcheck is installed
          - "github.com/wasilibs/go-shellcheck/cmd/shellcheck@v0.11.1"
        priority: 0

```

## /.python-version

```python-version path="/.python-version" 
3.13

```

## /AGENTS.md

# ty Repository

## Development

Parts of `.github/workflows/release.yml` are generated by cargo-dist from `dist-workspace.toml`. Before editing the release workflow, check whether the relevant section is generated. Prefer changing `dist-workspace.toml` or the referenced reusable workflow instead of editing generated YAML. After modifying cargo-dist configuration, regenerate the workflow with the cargo-dist version pinned in `dist-workspace.toml` and inspect the resulting diff to ensure the change will survive future regenerations.


## /BENCHMARKS.md

# Benchmarks

All benchmarks were computed on macOS (Apple M4 Pro 14, 48 GB) with the following tool versions:

- [Pyrefly](https://pypi.org/project/pyrefly/) 0.45.2
- [Pyright](https://www.npmjs.com/package/pyright) 1.1.407
- [mypy](https://pypi.org/project/mypy/) \<=1.19.0
- [ty](https://pypi.org/project/ty/) 0.0.2

Benchmark performance may vary across operating systems, and from project to project. This document
includes benchmarks from a variety of projects to provide a representative example of real-world
usage: [Black](https://github.com/psf/black), [discord.py](https://github.com/Rapptz/discord.py),
[Home Assistant](https://github.com/home-assistant/core), [isort](https://github.com/pycqa/isort),
[Jinja](https://github.com/pallets/jinja), [pandas](https://github.com/pandas-dev/pandas),
[pandas-stubs](https://github.com/pandas-dev/pandas-stubs),
[Prefect](https://github.com/PrefectHQ/prefect), and [PyTorch](https://github.com/pytorch/pytorch).

For instructions on running the benchmarks, see
[`ty_benchmark/README.md`](https://github.com/astral-sh/ruff/blob/7f7485d608d2da19a0632a1238f2d4be551f612f/scripts/ty_benchmark/README.md).

## CLI

```text
black
-----

Benchmark 1: ty
  Time (mean ± σ):      53.8 ms ±   1.6 ms    [User: 344.9 ms, System: 36.3 ms]
  Range (min … max):    51.4 ms …  57.9 ms    49 runs

  Warning: Ignoring non-zero exit code.

Benchmark 2: Pyrefly
  Time (mean ± σ):     167.4 ms ±   5.2 ms    [User: 648.8 ms, System: 157.8 ms]
  Range (min … max):   159.3 ms … 177.0 ms    18 runs

  Warning: Ignoring non-zero exit code.

Benchmark 3: mypy
  Time (mean ± σ):      1.197 s ±  0.008 s    [User: 1.144 s, System: 0.051 s]
  Range (min … max):    1.186 s …  1.212 s    10 runs

Benchmark 4: Pyright
  Time (mean ± σ):      1.193 s ±  0.020 s    [User: 13.264 s, System: 0.808 s]
  Range (min … max):    1.173 s …  1.240 s    10 runs

  Warning: Ignoring non-zero exit code.

Summary
  ty ran
    3.11 ± 0.13 times faster than Pyrefly
   22.16 ± 0.74 times faster than Pyright
   22.25 ± 0.66 times faster than mypy

-------------------------------------------------------------------------------

discord.py
----------

Benchmark 1: ty
  Time (mean ± σ):     272.3 ms ±   1.4 ms    [User: 1436.3 ms, System: 99.3 ms]
  Range (min … max):   270.5 ms … 275.6 ms    10 runs

  Warning: Ignoring non-zero exit code.

Benchmark 2: Pyrefly
  Time (mean ± σ):     312.7 ms ±  23.9 ms    [User: 2585.5 ms, System: 244.1 ms]
  Range (min … max):   283.4 ms … 367.9 ms    10 runs

  Warning: Ignoring non-zero exit code.

Benchmark 3: mypy
  Time (mean ± σ):      6.617 s ±  0.212 s    [User: 6.521 s, System: 0.091 s]
  Range (min … max):    6.233 s …  6.852 s    10 runs

  Warning: Ignoring non-zero exit code.

Benchmark 4: Pyright
  Time (mean ± σ):      2.874 s ±  0.078 s    [User: 35.269 s, System: 1.710 s]
  Range (min … max):    2.753 s …  2.964 s    10 runs

  Warning: Ignoring non-zero exit code.

Summary
  ty ran
    1.15 ± 0.09 times faster than Pyrefly
   10.55 ± 0.29 times faster than Pyright
   24.30 ± 0.79 times faster than mypy

-------------------------------------------------------------------------------

homeassistant
-------------

Benchmark 1: ty
  Time (mean ± σ):      2.186 s ±  0.083 s    [User: 23.139 s, System: 2.920 s]
  Range (min … max):    2.067 s …  2.355 s    10 runs

  Warning: Ignoring non-zero exit code.

Benchmark 2: Pyrefly
  Time (mean ± σ):      5.320 s ±  0.020 s    [User: 25.054 s, System: 33.545 s]
  Range (min … max):    5.294 s …  5.365 s    10 runs

  Warning: Ignoring non-zero exit code.

Benchmark 3: mypy
  Time (mean ± σ):     45.662 s ±  0.224 s    [User: 43.882 s, System: 1.767 s]
  Range (min … max):   45.328 s … 46.009 s    10 runs

  Warning: Ignoring non-zero exit code.

Benchmark 4: Pyright
  Time (mean ± σ):     19.623 s ±  0.425 s    [User: 227.040 s, System: 21.942 s]
  Range (min … max):   19.255 s … 20.748 s    10 runs

  Warning: Ignoring non-zero exit code.

Summary
  ty ran
    2.43 ± 0.09 times faster than Pyrefly
    8.98 ± 0.39 times faster than Pyright
   20.89 ± 0.80 times faster than mypy

-------------------------------------------------------------------------------

isort
-----

Benchmark 1: ty
  Time (mean ± σ):      39.0 ms ±   1.1 ms    [User: 161.0 ms, System: 21.0 ms]
  Range (min … max):    36.2 ms …  41.2 ms    67 runs

  Warning: Ignoring non-zero exit code.

Benchmark 2: Pyrefly
  Time (mean ± σ):     138.5 ms ±   2.8 ms    [User: 462.0 ms, System: 82.2 ms]
  Range (min … max):   132.2 ms … 143.8 ms    21 runs

  Warning: Ignoring non-zero exit code.

Benchmark 3: mypy
  Time (mean ± σ):     581.2 ms ±   2.2 ms    [User: 547.8 ms, System: 31.2 ms]
  Range (min … max):   578.4 ms … 584.9 ms    10 runs

Benchmark 4: Pyright
  Time (mean ± σ):      2.453 s ±  0.032 s    [User: 13.608 s, System: 0.743 s]
  Range (min … max):    2.402 s …  2.504 s    10 runs

  Warning: Ignoring non-zero exit code.

Summary
  ty ran
    3.55 ± 0.13 times faster than Pyrefly
   14.91 ± 0.44 times faster than mypy
   62.90 ± 2.02 times faster than Pyright

-------------------------------------------------------------------------------

jinja
-----

Benchmark 1: ty
  Time (mean ± σ):     110.2 ms ±   3.3 ms    [User: 326.8 ms, System: 27.6 ms]
  Range (min … max):   107.0 ms … 119.0 ms    26 runs

  Warning: Ignoring non-zero exit code.

Benchmark 2: Pyrefly
  Time (mean ± σ):     134.5 ms ±   1.7 ms    [User: 444.8 ms, System: 87.5 ms]
  Range (min … max):   131.5 ms … 137.8 ms    21 runs

  Warning: Ignoring non-zero exit code.

Benchmark 3: mypy
  Time (mean ± σ):     700.9 ms ±  13.2 ms    [User: 665.5 ms, System: 33.0 ms]
  Range (min … max):   693.7 ms … 737.7 ms    10 runs

  Warning: Ignoring non-zero exit code.

Benchmark 4: Pyright
  Time (mean ± σ):      1.099 s ±  0.014 s    [User: 12.235 s, System: 0.736 s]
  Range (min … max):    1.081 s …  1.127 s    10 runs

  Warning: Ignoring non-zero exit code.

Summary
  ty ran
    1.22 ± 0.04 times faster than Pyrefly
    6.36 ± 0.23 times faster than mypy
    9.97 ± 0.33 times faster than Pyright

-------------------------------------------------------------------------------

pandas
------

Benchmark 1: ty
  Time (mean ± σ):     551.5 ms ±  56.7 ms    [User: 4906.6 ms, System: 222.6 ms]
  Range (min … max):   467.6 ms … 614.8 ms    10 runs

  Warning: Ignoring non-zero exit code.

Benchmark 2: Pyrefly
  Time (mean ± σ):      1.174 s ±  0.012 s    [User: 11.917 s, System: 1.035 s]
  Range (min … max):    1.149 s …  1.186 s    10 runs

  Warning: Ignoring non-zero exit code.

Benchmark 3: mypy
  Time (mean ± σ):     21.366 s ±  0.083 s    [User: 21.112 s, System: 0.247 s]
  Range (min … max):   21.234 s … 21.539 s    10 runs

Benchmark 4: Pyright
  Time (mean ± σ):      6.878 s ±  0.082 s    [User: 80.281 s, System: 3.504 s]
  Range (min … max):    6.749 s …  7.001 s    10 runs

  Warning: Ignoring non-zero exit code.

Summary
  ty ran
    2.13 ± 0.22 times faster than Pyrefly
   12.47 ± 1.29 times faster than Pyright
   38.74 ± 3.99 times faster than mypy

-------------------------------------------------------------------------------

pandas-stubs
------------

Benchmark 1: ty
  Time (mean ± σ):      83.2 ms ±   2.8 ms    [User: 377.9 ms, System: 50.1 ms]
  Range (min … max):    75.3 ms …  87.1 ms    36 runs

  Warning: Ignoring non-zero exit code.

Benchmark 2: Pyrefly
  Time (mean ± σ):     253.6 ms ±   6.0 ms    [User: 853.5 ms, System: 322.0 ms]
  Range (min … max):   245.2 ms … 264.2 ms    11 runs

  Warning: Ignoring non-zero exit code.

Benchmark 3: mypy
  Time (mean ± σ):      5.290 s ±  0.028 s    [User: 5.150 s, System: 0.135 s]
  Range (min … max):    5.248 s …  5.332 s    10 runs

Benchmark 4: Pyright
  Time (mean ± σ):      1.792 s ±  0.030 s    [User: 17.300 s, System: 1.199 s]
  Range (min … max):    1.760 s …  1.843 s    10 runs

Summary
  ty ran
    3.05 ± 0.12 times faster than Pyrefly
   21.55 ± 0.80 times faster than Pyright
   63.61 ± 2.15 times faster than mypy

-------------------------------------------------------------------------------

prefect
-------

Benchmark 1: ty
  Time (mean ± σ):      92.4 ms ±   1.1 ms    [User: 524.8 ms, System: 70.1 ms]
  Range (min … max):    90.8 ms …  95.1 ms    30 runs

  Warning: Ignoring non-zero exit code.

Benchmark 2: Pyrefly
  Time (mean ± σ):     322.3 ms ±   7.9 ms    [User: 1061.7 ms, System: 691.8 ms]
  Range (min … max):   305.0 ms … 330.5 ms    10 runs

  Warning: Ignoring non-zero exit code.

Benchmark 3: mypy
  Time (mean ± σ):     742.5 ms ±   3.4 ms    [User: 702.0 ms, System: 38.1 ms]
  Range (min … max):   737.6 ms … 749.0 ms    10 runs

  Warning: Ignoring non-zero exit code.

Benchmark 4: Pyright
  Time (mean ± σ):      3.545 s ±  0.042 s    [User: 41.849 s, System: 2.252 s]
  Range (min … max):    3.507 s …  3.641 s    10 runs

  Warning: Ignoring non-zero exit code.

Summary
  ty ran
    3.49 ± 0.10 times faster than Pyrefly
    8.03 ± 0.10 times faster than mypy
   38.35 ± 0.64 times faster than Pyright

-------------------------------------------------------------------------------

pytorch
-------

Benchmark 1: ty
  Time (mean ± σ):      1.160 s ±  0.115 s    [User: 11.200 s, System: 1.344 s]
  Range (min … max):    1.034 s …  1.314 s    10 runs

  Warning: Ignoring non-zero exit code.

Benchmark 2: Pyrefly
  Time (mean ± σ):      2.084 s ±  0.029 s    [User: 19.660 s, System: 4.377 s]
  Range (min … max):    2.051 s …  2.143 s    10 runs

  Warning: Ignoring non-zero exit code.

Benchmark 3: mypy
  Time (mean ± σ):     30.157 s ±  0.155 s    [User: 29.776 s, System: 0.373 s]
  Range (min … max):   29.956 s … 30.454 s    10 runs

  Warning: Ignoring non-zero exit code.

Benchmark 4: Pyright
  Time (mean ± σ):     12.770 s ±  0.294 s    [User: 149.537 s, System: 8.990 s]
  Range (min … max):   12.357 s … 13.175 s    10 runs

  Warning: Ignoring non-zero exit code.

Summary
  ty ran
    1.80 ± 0.18 times faster than Pyrefly
   11.01 ± 1.12 times faster than Pyright
   26.01 ± 2.59 times faster than mypy
```

## LSP

### Incremental edit

```shell
----------------------------------------------------------------------------------------- benchmark 'black': 3 tests ----------------------------------------------------------------------------------------
Name (time in ms)                             Min                 Max                Mean            StdDev              Median               IQR            Outliers       OPS            Rounds  Iterations
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
test_incremental_edit[black-ty]            8.8965 (1.0)        9.4442 (1.0)        9.1480 (1.0)      0.1953 (1.0)        9.1324 (1.0)      0.3284 (1.0)           4;0  109.3131 (1.0)          10           1
test_incremental_edit[black-pyrefly]     181.7555 (20.43)    192.5020 (20.38)    186.0771 (20.34)    4.5209 (23.15)    183.9968 (20.15)    8.1744 (24.89)         2;0    5.3741 (0.05)         10           1
test_incremental_edit[black-pyright]     418.8096 (47.08)    436.1230 (46.18)    430.3303 (47.04)    5.7802 (29.60)    432.2938 (47.34)    7.9072 (24.08)         3;0    2.3238 (0.02)         10           1
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

----------------------------------------------------------------------------------------- benchmark 'discord.py': 3 tests -----------------------------------------------------------------------------------------
Name (time in ms)                                  Min                 Max                Mean             StdDev              Median                IQR            Outliers      OPS            Rounds  Iterations
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
test_incremental_edit[discord.py-ty]           11.0239 (1.0)       11.4890 (1.0)       11.2585 (1.0)       0.1475 (1.0)       11.3125 (1.0)       0.2244 (1.0)           4;0  88.8216 (1.0)          10           1
test_incremental_edit[discord.py-pyrefly]     404.7799 (36.72)    540.6511 (47.06)    480.9133 (42.72)    39.8237 (269.90)   486.0451 (42.97)    36.0575 (160.67)        3;1   2.0794 (0.02)         10           1
test_incremental_edit[discord.py-pyright]     438.0625 (39.74)    459.7234 (40.01)    454.7564 (40.39)     7.2835 (49.36)    458.2917 (40.51)     9.2910 (41.40)         2;0   2.1990 (0.02)         10           1
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

--------------------------------------------------------------------------------------------- benchmark 'homeassistant': 3 tests ---------------------------------------------------------------------------------------------
Name (time in ms)                                       Min                   Max                  Mean             StdDev                Median                IQR            Outliers      OPS            Rounds  Iterations
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
test_incremental_edit[homeassistant-ty]             26.2820 (1.0)         26.8146 (1.0)         26.5646 (1.0)       0.1786 (1.0)         26.5610 (1.0)       0.2392 (1.0)           4;0  37.6440 (1.0)          10           1
test_incremental_edit[homeassistant-pyright]       492.1233 (18.72)      513.8475 (19.16)      499.2279 (18.79)     6.6173 (37.05)      497.2621 (18.72)     6.0271 (25.20)         3;1   2.0031 (0.05)         10           1
test_incremental_edit[homeassistant-pyrefly]     1,858.6557 (70.72)    1,979.7762 (73.83)    1,923.3550 (72.40)    38.1201 (213.43)   1,917.0547 (72.18)    48.3089 (201.99)        4;0   0.5199 (0.01)         10           1
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

---------------------------------------------------------------------------------------- benchmark 'isort': 3 tests ----------------------------------------------------------------------------------------
Name (time in ms)                             Min                 Max                Mean            StdDev              Median               IQR            Outliers      OPS            Rounds  Iterations
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
test_incremental_edit[isort-ty]            9.9467 (1.0)       10.3084 (1.0)       10.1385 (1.0)      0.1117 (1.0)       10.1680 (1.0)      0.1633 (1.0)           3;0  98.6339 (1.0)          10           1
test_incremental_edit[isort-pyrefly]     116.1576 (11.68)    128.3192 (12.45)    122.6576 (12.10)    3.7490 (33.56)    123.0442 (12.10)    5.8752 (35.97)         3;0   8.1528 (0.08)         10           1
test_incremental_edit[isort-pyright]     383.3340 (38.54)    400.4724 (38.85)    390.9956 (38.57)    5.0222 (44.95)    389.3690 (38.29)    6.7132 (41.10)         3;0   2.5576 (0.03)         10           1
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

---------------------------------------------------------------------------------------- benchmark 'jinja': 3 tests ----------------------------------------------------------------------------------------
Name (time in ms)                             Min                 Max                Mean            StdDev              Median               IQR            Outliers      OPS            Rounds  Iterations
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
test_incremental_edit[jinja-ty]           43.7648 (1.0)       45.4928 (1.0)       44.3021 (1.0)      0.4960 (1.0)       44.2532 (1.0)      0.4335 (1.0)           2;1  22.5723 (1.0)          10           1
test_incremental_edit[jinja-pyrefly]     182.0870 (4.16)     194.4938 (4.28)     189.2674 (4.27)     4.0190 (8.10)     190.3916 (4.30)     2.8280 (6.52)          3;2   5.2835 (0.23)         10           1
test_incremental_edit[jinja-pyright]     424.4407 (9.70)     439.2709 (9.66)     432.3523 (9.76)     5.2093 (10.50)    432.5269 (9.77)     8.0710 (18.62)         4;0   2.3129 (0.10)         10           1
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

---------------------------------------------------------------------------------------------- benchmark 'pandas': 3 tests ----------------------------------------------------------------------------------------------
Name (time in ms)                                Min                   Max                  Mean              StdDev                Median                 IQR            Outliers      OPS            Rounds  Iterations
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
test_incremental_edit[pandas-ty]             69.6266 (1.0)         93.7763 (1.0)         81.3957 (1.0)        7.0073 (1.0)         81.2143 (1.0)        6.5060 (1.0)           3;0  12.2857 (1.0)          10           1
test_incremental_edit[pandas-pyright]       434.3018 (6.24)       540.0721 (5.76)       467.1337 (5.74)      34.8674 (4.98)       457.4263 (5.63)      46.5595 (7.16)          1;0   2.1407 (0.17)         10           1
test_incremental_edit[pandas-pyrefly]     3,756.2226 (53.95)    4,757.2635 (50.73)    4,377.4530 (53.78)    367.8468 (52.49)    4,486.5002 (55.24)    714.0047 (109.74)        5;0   0.2284 (0.02)         10           1
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

----------------------------------------------------------------------------------------------- benchmark 'prefect': 3 tests ----------------------------------------------------------------------------------------------
Name (time in ms)                                 Min                   Max                  Mean              StdDev                Median                 IQR            Outliers       OPS            Rounds  Iterations
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
test_incremental_edit[prefect-ty]              4.7899 (1.0)          5.3654 (1.0)          5.0291 (1.0)        0.1827 (1.0)          4.9726 (1.0)        0.2836 (1.0)           3;0  198.8408 (1.0)          10           1
test_incremental_edit[prefect-pyright]       537.4210 (112.20)     555.0903 (103.46)     543.9572 (108.16)     5.3413 (29.24)      543.2359 (109.25)     7.0621 (24.90)         4;0    1.8384 (0.01)         10           1
test_incremental_edit[prefect-pyrefly]     2,486.7581 (519.17)   3,972.1852 (740.34)   3,280.6660 (652.33)   500.2204 (>1000.0)  3,231.6573 (649.90)   706.5563 (>1000.0)       3;0    0.3048 (0.00)         10           1
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

----------------------------------------------------------------------------------------------- benchmark 'pytorch': 3 tests ----------------------------------------------------------------------------------------------
Name (time in ms)                                 Min                   Max                  Mean              StdDev                Median                 IQR            Outliers       OPS            Rounds  Iterations
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
test_incremental_edit[pytorch-ty]              4.3493 (1.0)          4.6291 (1.0)          4.4956 (1.0)        0.0940 (1.0)          4.4831 (1.0)        0.1323 (1.0)           4;0  222.4377 (1.0)          10           1
test_incremental_edit[pytorch-pyright]       367.6819 (84.54)      374.5961 (80.92)      370.4936 (82.41)      2.2878 (24.35)      370.5413 (82.65)      3.5865 (27.12)         2;0    2.6991 (0.01)         10           1
test_incremental_edit[pytorch-pyrefly]     2,333.3450 (536.48)   2,889.9276 (624.30)   2,604.7441 (579.39)   202.1562 (>1000.0)  2,582.8069 (576.12)   380.2684 (>1000.0)       4;0    0.3839 (0.00)         10           1
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
```

### Fetch diagnostics

```shell
----------------------------------------------------------------------------------------- benchmark 'black': 3 tests ----------------------------------------------------------------------------------------
Name (time in ms)                              Min                 Max                Mean            StdDev              Median               IQR            Outliers      OPS            Rounds  Iterations
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
test_fetch_diagnostics[black-ty]           40.5680 (1.0)       45.9828 (1.0)       43.4315 (1.0)      1.9415 (3.59)      43.4317 (1.0)      3.6550 (5.37)          5;0  23.0247 (1.0)          10           1
test_fetch_diagnostics[black-pyrefly]     131.4388 (3.24)     133.1042 (2.89)     132.3125 (3.05)     0.5407 (1.0)      132.2801 (3.05)     0.6808 (1.0)           3;0   7.5579 (0.33)         10           1
test_fetch_diagnostics[black-pyright]     234.5532 (5.78)     261.0507 (5.68)     241.2080 (5.55)     7.5489 (13.96)    238.8710 (5.50)     3.9942 (5.87)          1;1   4.1458 (0.18)         10           1
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

---------------------------------------------------------------------------------------- benchmark 'discord.py': 3 tests -----------------------------------------------------------------------------------------
Name (time in ms)                                   Min                 Max                Mean            StdDev              Median               IQR            Outliers      OPS            Rounds  Iterations
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
test_fetch_diagnostics[discord.py-ty]           88.1260 (1.0)       91.9354 (1.0)       89.9785 (1.0)      1.2617 (1.0)       89.8813 (1.0)      1.9953 (1.33)          3;0  11.1138 (1.0)          10           1
test_fetch_diagnostics[discord.py-pyrefly]     438.9406 (4.98)     462.2016 (5.03)     442.3538 (4.92)     7.0283 (5.57)     439.9846 (4.90)     1.4996 (1.0)           1;1   2.2606 (0.20)         10           1
test_fetch_diagnostics[discord.py-pyright]     488.1340 (5.54)     501.5540 (5.46)     493.6927 (5.49)     3.7678 (2.99)     493.7276 (5.49)     3.5742 (2.38)          3;1   2.0256 (0.18)         10           1
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

----------------------------------------------------------------------------------------- benchmark 'homeassistant': 3 tests ----------------------------------------------------------------------------------------
Name (time in ms)                                      Min                 Max                Mean             StdDev              Median               IQR            Outliers     OPS            Rounds  Iterations
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
test_fetch_diagnostics[homeassistant-ty]          109.6321 (1.0)      112.8913 (1.0)      111.4661 (1.0)       1.1174 (1.0)      111.4080 (1.0)      1.7967 (1.0)           5;0  8.9713 (1.0)          10           1
test_fetch_diagnostics[homeassistant-pyrefly]     218.1328 (1.99)     238.8827 (2.12)     222.4006 (2.00)      6.7033 (6.00)     219.6265 (1.97)     4.9113 (2.73)          2;1  4.4964 (0.50)         10           1
test_fetch_diagnostics[homeassistant-pyright]     888.3600 (8.10)     938.0845 (8.31)     901.1896 (8.08)     18.2062 (16.29)    893.4438 (8.02)     5.7174 (3.18)          2;2  1.1096 (0.12)         10           1
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

----------------------------------------------------------------------------------------- benchmark 'isort': 3 tests ----------------------------------------------------------------------------------------
Name (time in ms)                              Min                 Max                Mean            StdDev              Median               IQR            Outliers      OPS            Rounds  Iterations
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
test_fetch_diagnostics[isort-ty]           41.8193 (1.0)       47.1411 (1.0)       43.6068 (1.0)      1.7622 (1.0)       43.1409 (1.0)      2.2658 (1.0)           3;0  22.9322 (1.0)          10           1
test_fetch_diagnostics[isort-pyrefly]     103.6808 (2.48)     110.2636 (2.34)     105.9967 (2.43)     2.6978 (1.53)     104.5978 (2.42)     5.5226 (2.44)          3;0   9.4343 (0.41)         10           1
test_fetch_diagnostics[isort-pyright]     297.8888 (7.12)     327.5334 (6.95)     306.2138 (7.02)     8.7235 (4.95)     304.5766 (7.06)     6.0185 (2.66)          1;1   3.2657 (0.14)         10           1
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

----------------------------------------------------------------------------------------- benchmark 'jinja': 3 tests ----------------------------------------------------------------------------------------
Name (time in ms)                              Min                 Max                Mean            StdDev              Median                IQR            Outliers     OPS            Rounds  Iterations
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
test_fetch_diagnostics[jinja-ty]          114.6546 (1.0)      129.8110 (1.0)      120.6856 (1.0)      5.6731 (3.75)     119.8813 (1.0)       9.1737 (7.27)          3;0  8.2860 (1.0)          10           1
test_fetch_diagnostics[jinja-pyrefly]     134.1455 (1.17)     139.0428 (1.07)     136.2655 (1.13)     1.5126 (1.0)      135.7366 (1.13)      1.2612 (1.0)           3;1  7.3386 (0.89)         10           1
test_fetch_diagnostics[jinja-pyright]     280.1174 (2.44)     305.6963 (2.35)     287.5077 (2.38)     8.9505 (5.92)     283.8312 (2.37)     10.0400 (7.96)          2;0  3.4782 (0.42)         10           1
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

---------------------------------------------------------------------------------------- benchmark 'pandas': 3 tests ----------------------------------------------------------------------------------------
Name (time in ms)                               Min                 Max                Mean            StdDev              Median               IQR            Outliers     OPS            Rounds  Iterations
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
test_fetch_diagnostics[pandas-ty]          289.0480 (1.0)      294.6924 (1.0)      292.1341 (1.0)      1.6226 (1.0)      292.2783 (1.0)      2.4183 (2.01)          2;0  3.4231 (1.0)          10           1
test_fetch_diagnostics[pandas-pyrefly]     522.8750 (1.81)     529.1068 (1.80)     524.6167 (1.80)     1.9387 (1.19)     524.1335 (1.79)     1.2023 (1.0)           2;2  1.9062 (0.56)         10           1
test_fetch_diagnostics[pandas-pyright]     928.4370 (3.21)     949.3941 (3.22)     941.8812 (3.22)     5.6550 (3.49)     943.2703 (3.23)     4.2351 (3.52)          2;1  1.0617 (0.31)         10           1
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

----------------------------------------------------------------------------------------- benchmark 'prefect': 3 tests ----------------------------------------------------------------------------------------
Name (time in ms)                                Min                 Max                Mean             StdDev              Median               IQR            Outliers     OPS            Rounds  Iterations
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
test_fetch_diagnostics[prefect-ty]          123.5941 (1.0)      127.0129 (1.0)      125.3534 (1.0)       1.0498 (1.02)     125.1861 (1.0)      1.4461 (1.10)          3;0  7.9774 (1.0)          10           1
test_fetch_diagnostics[prefect-pyrefly]     437.5048 (3.54)     441.0088 (3.47)     438.7041 (3.50)      1.0287 (1.0)      438.4152 (3.50)     1.3126 (1.0)           2;0  2.2794 (0.29)         10           1
test_fetch_diagnostics[prefect-pyright]     828.5065 (6.70)     884.9894 (6.97)     845.3657 (6.74)     14.9587 (14.54)    842.5912 (6.73)     8.1088 (6.18)          2;1  1.1829 (0.15)         10           1
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

----------------------------------------------------------------------------------------- benchmark 'pytorch': 3 tests -----------------------------------------------------------------------------------------
Name (time in ms)                                Min                 Max                Mean            StdDev              Median                IQR            Outliers      OPS            Rounds  Iterations
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
test_fetch_diagnostics[pytorch-ty]           51.7146 (1.0)       59.2463 (1.0)       54.3685 (1.0)      2.2254 (2.06)      54.3844 (1.0)       3.0549 (2.12)          2;0  18.3930 (1.0)          10           1
test_fetch_diagnostics[pytorch-pyrefly]     174.7057 (3.38)     178.1138 (3.01)     175.7364 (3.23)     1.0824 (1.0)      175.5052 (3.23)      1.4398 (1.0)           1;0   5.6903 (0.31)         10           1
test_fetch_diagnostics[pytorch-pyright]     447.4101 (8.65)     474.9993 (8.02)     460.5399 (8.47)     9.8641 (9.11)     461.9636 (8.49)     18.7404 (13.02)         3;0   2.1714 (0.12)         10           1
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
```


## /CONTRIBUTING.md

# Contributing

> [!IMPORTANT]
> If you want to contribute changes to ty's core, please check out the
> [dedicated `ty` contributing guide](https://github.com/astral-sh/ruff/blob/main/crates/ty/CONTRIBUTING.md).
> Keep reading if you want to contribute to ty's documentation or release process instead.

## Repository structure

This repository contains ty's documentation and release infrastructure. The core of ty's Rust codebase is
located in the [Ruff](https://github.com/astral-sh/ruff) repository. While the relationship between these
two projects will evolve over time, they currently share foundational crates and it's easiest to use a single
repository for the Rust development.

ty's command-line help text, and part of `docs/reference/`, are auto-generated from Rust code in the Ruff
repository, using generation scripts that live in
[`crates/ruff_dev/src/`](https://github.com/astral-sh/ruff/blob/main/crates/ruff_dev/src/):

- [Configuration options](https://docs.astral.sh/ty/reference/configuration/), from
    [ruff/crates/ty_project/src/metadata/options.rs](https://github.com/astral-sh/ruff/blob/main/crates/ty_project/src/metadata/options.rs)
- [Rules](https://docs.astral.sh/ty/reference/rules/), from
    [ruff/crates/ty_python_semantic/src/](https://github.com/astral-sh/ruff/blob/main/crates/ty_python_semantic/src/)
- [Command-line interface reference](https://docs.astral.sh/ty/reference/cli/), from
    [ruff/crates/ty/src/args.rs](https://github.com/astral-sh/ruff/blob/main/crates/ty/src/args.rs)

The Ruff repository is included as a submodule inside this repository to allow ty's release tags to reflect
an exact snapshot of the Ruff project. The submodule is only updated on release. To see the latest development
code, check out the `main` branch of the Ruff repository.

## Getting started with the ty repository

Clone the repository:

```shell
git clone https://github.com/astral-sh/ty.git
```

Then, ensure the submodule is initialized:

```shell
git submodule update --init --recursive
```

### Prerequisites

You'll need [uv](https://docs.astral.sh/uv/getting-started/installation/) (or `pipx` and `pip`) to
run Python utility commands.

You can optionally install prek hooks to automatically run the validation checks
when making a commit:

```shell
uv tool install prek
prek install
```

## Building the Python package

The Python package can be built with any Python build frontend (Maturin is used as a backend), e.g.:

```shell
uv build
```

## Updating the Ruff commit

To update the Ruff submodule to the latest commit:

```shell
git -C ruff pull origin main
```

Or, to update the Ruff submodule to a specific commit:

```shell
git -C ruff checkout <commit>
```

To commit the changes:

```shell
commit=$(git -C ruff rev-parse --short HEAD)
git switch -c "sync/ruff-${commit}"
git add ruff
git commit -m "Update ruff submodule to https://github.com/astral-sh/ruff/commit/${commit}"
```

To restore the Ruff submodule to a clean-state, reset, then update the submodule:

```shell
git -C ruff reset --hard
git submodule update
```

To restore the Ruff submodule to the commit from `main`:

```shell
git -C ruff reset --hard $(git ls-tree main -- ruff | awk '{print $3}')
git add ruff
```

## Documentation

To preview any changes to the documentation locally run the development server with:

```shell
uvx --with-requirements docs/requirements.txt -- mkdocs serve -f mkdocs.yml
```

The documentation should then be available locally at
[http://127.0.0.1:8000/ty/](http://127.0.0.1:8000/ty/).

To update the documentation dependencies, edit `docs/requirements.in`, then run:

```shell
uv pip compile docs/requirements.in -o docs/requirements.txt --universal -p 3.12
```

Documentation is deployed automatically on release by publishing to the
[Astral documentation](https://github.com/astral-sh/docs) repository, which itself deploys via
Cloudflare Pages.

After making changes to the documentation, format the markdown files with:

```shell
npx prettier --prose-wrap always --write "**/*.md"
```

## Releasing ty

Releases can only be performed by Astral team members.

Preparation for the release is automated.

1. Install the prek hooks as described above, if you haven't already.

1. Checkout the `main` branch and run `git pull origin main --recurse-submodules --tags`.

1. Create and checkout a new branch for the release.

1. Run `./scripts/release.sh`.

    The release script will:

    - Update the Ruff submodule to the latest commit on `main` upstream
    - Generate changelog entries based on pull requests here, and in Ruff
    - Bump the versions in the `pyproject.toml` and `dist-workspace.toml`
    - Update the generated reference documentation in `docs/reference`

1. Editorialize the `CHANGELOG.md` file to ensure entries are consistently styled.

    This usually involves simple edits, like consistent capitalization and leading verbs like
    "Add ...".

1. Create a pull request with the changelog and version changes

    The pull requests are usually titled as: `Bump version to <version>`.

    Binary builds will automatically be tested for the release.

1. Merge the pull request.

1. Run the [release workflow](https://github.com/astral-sh/ty/actions/workflows/release.yml) with
    the version tag.

    **Do not include a leading `v`**.

    When running the release workflow for pre-release versions, use the Cargo version format (not PEP
    440), e.g. `0.0.1-alpha.5` (not `0.0.1a5`). For stable releases, these formats are identical.

1. Request a deployment approval from another team member.

    The release workflow will pause at the `release-gate` job until this approval is granted.

    The release will automatically be created on GitHub after the distributions are published.

1. Run `uv run --no-project ./scripts/update_schemastore.py`

    This script will prepare a branch to update the `ty.json` schema in the `schemastore`
    repository.

    Follow the link in the script's output to submit the pull request.

    The script is a no-op if there are no schema changes.

1. Update and release `ty-vscode`.

    The instructions are [in the `ty-vscode`
    repository](https://github.com/astral-sh/ty-vscode/blob/main/CONTRIBUTING.md#release).

## Updating ty's conformance results upstream

One way in which type checkers can be evaluated is by how well they do on the [typing conformance test suite](https://github.com/python/typing/tree/main/conformance),
which contains a number of assertions to test whether type checkers adhere to the rules laid out in
the typing spec. ty's results are uploaded to the upstream `python/typing` repo so that users can
compare ty's conformance score with other type checkers. Updating these results is partially, but
not fully, automated.

To update ty's conformance results upstream after a release:

1. Clone <https://github.com/python/typing> and checkout a new branch
1. Run `cd conformance`
1. Run `uv sync --update-package=ty`
1. Run `uv run python src/main.py`. This step may update generated fields in TOML files (see below) and/or the `results.html` file.
1. Check to see if any manual changes are required and apply them as necessary (see below for details).
1. If you had to make any manual changes as part of the previous step, run `uv run python src/main.py` again. This second run should not update any further TOML files, but is necessary for regenerating `results.html`.
1. Make a PR to the upstream repo.

### Manual changes that may be required to `.toml` files

The TOML files that contain the results for each type checker are partially generated.

The `conformance_automated`, `output` and `errors_diff` fields are all generated; these should never be altered:

- `output` is the raw output of the type checker on that file.
- `errors_diff` provides one line of output for every Python line where a diagnostic was expected from ty but didn't occur, and one line of output for every Python line where a diagnostic was not expected from ty but did occur.
- `conformance_automated`: this will always either be "Pass" (if `errors_diff` is an empty string) or "Fail" (if it is a non-empty string).

The other fields are manually entered and may need to be updated if any of the generated fields in a TOML file are altered by running `uv run python src/main.py`:

- `conformant`: This should be one of three values:
    - "Unsupported": none of the major features in the Python file are supported by ty
    - "Partial": some, but not all, of the features and assertions in the Python file are supported by ty. If this is given as the current status, a non-empty `notes` field should be provided that describes which features are currently supported and which are not.
    - "Pass": this should generally only be used if ty passes all assertions in the Python file and the generated `errors_diff` field is an empty string. In some rare occasions, it *may* be appropriate to label ty as conformant even if there is a non-empty `errors_diff` field, but you will generally need to provide a non-empty `ignore_errors` field if so.
- `notes`: if the score is given as "Partial" in the `conformant` field, this field should describe which assertions fail and which features are currently unsupported.
- `ignore_errors`: this is only required if the generated `errors_diff` field is nonempty but the score is nonetheless given as "Conformant". It should be a list of strings. Each string should be an error message that appears in the `errors_diff` field but should nonetheless be ignored when considering whether ty is conformant or not. If ty's score is given as "Conformant" with a non-empty `errors_diff` field, a verification check in CI in the `python/typing` repository will fail unless each error message listed in `errors_diff` is listed in that TOML file's `ignore_errors` field.


## /Dockerfile

``` path="/Dockerfile" 
FROM --platform=$BUILDPLATFORM ubuntu AS build
ENV HOME="/root"
WORKDIR $HOME

RUN apt update && apt install -y build-essential curl python3-venv

# Setup zig as cross compiling linker
RUN python3 -m venv $HOME/.venv
RUN .venv/bin/pip install cargo-zigbuild
ENV PATH="$HOME/.venv/bin:$PATH"

# Change to the ruff directory, which is the root for our build
WORKDIR $HOME/ruff

# Install rust
ARG TARGETPLATFORM
RUN case "$TARGETPLATFORM" in \
    "linux/arm64") echo "aarch64-unknown-linux-musl" > rust_target.txt ;; \
    "linux/amd64") echo "x86_64-unknown-linux-musl" > rust_target.txt ;; \
    *) exit 1 ;; \
    esac
# Update rustup whenever we bump the rust version
COPY ruff/rust-toolchain.toml rust-toolchain.toml
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --target $(cat rust_target.txt) --profile minimal --default-toolchain none
ENV PATH="$HOME/.cargo/bin:$PATH"
# Installs the correct toolchain version from rust-toolchain.toml and then the musl target
RUN rustup target add $(cat rust_target.txt)

# Build
COPY ruff/crates crates
COPY ruff/Cargo.toml Cargo.toml
COPY ruff/Cargo.lock Cargo.lock
COPY dist-workspace.toml ../dist-workspace.toml
RUN cargo zigbuild --bin ty --target $(cat rust_target.txt) --release
RUN cp target/$(cat rust_target.txt)/release/ty /ty
# TODO: Optimize binary size, with a version that also works when cross compiling
# RUN strip --strip-all /ty

FROM scratch
COPY --from=build /ty /ty
WORKDIR /io
ENTRYPOINT ["/ty"]

```

## /README.md

# ty

[![ty](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ty/main/assets/badge/v0.json)](https://github.com/astral-sh/ty)
[![PyPI](https://img.shields.io/pypi/v/ty.svg)](https://pypi.python.org/pypi/ty)
[![Discord](https://img.shields.io/badge/Discord-%235865F2.svg?logo=discord&logoColor=white)](https://discord.com/invite/astral-sh)

An extremely fast Python type checker and language server, written in Rust.

<br />

<p align="center">
  <img alt="Shows a bar chart with benchmark results." width="500px" src="https://raw.githubusercontent.com/astral-sh/ty/main/docs/assets/ty-benchmark-cli.svg">
</p>

<p align="center">
  <i>Type checking the <a href="https://github.com/home-assistant/core">home-assistant</a> project without caching.</i>
</p>

<br />

ty is backed by [Astral](https://astral.sh), the creators of
[uv](https://github.com/astral-sh/uv) and [Ruff](https://github.com/astral-sh/ruff).

ty is currently in [beta](#version-policy).

## Highlights

- 10x - 100x faster than mypy and Pyright
- Comprehensive [diagnostics](https://docs.astral.sh/ty/features/diagnostics/) with rich contextual information
- Configurable [rule levels](https://docs.astral.sh/ty/rules/), [per-file overrides](https://docs.astral.sh/ty/reference/configuration/#overrides), [suppression comments](https://docs.astral.sh/ty/suppression/), and first-class project support
- Designed for adoption, with support for [redeclarations](https://docs.astral.sh/ty/features/type-system/#redeclarations) and [partially typed code](https://docs.astral.sh/ty/features/type-system/#gradual-guarantee)
- [Language server](https://docs.astral.sh/ty/features/language-server/) with code navigation, completions, code actions, auto-import, inlay hints, on-hover help, etc.
- Fine-grained [incremental analysis](https://docs.astral.sh/ty/features/language-server/#fine-grained-incrementality) designed for fast updates when editing files in an IDE
- Editor integrations for [VS Code](https://docs.astral.sh/ty/editors/#vs-code), [PyCharm](https://docs.astral.sh/ty/editors/#pycharm), [Neovim](https://docs.astral.sh/ty/editors/#neovim) and more
- Advanced typing features like first-class [intersection types](https://docs.astral.sh/ty/features/type-system/#intersection-types), advanced [type narrowing](https://docs.astral.sh/ty/features/type-system/#top-and-bottom-materializations), and
    [sophisticated reachability analysis](https://docs.astral.sh/ty/features/type-system/#reachability-based-on-types)

## Getting started

Run ty with [uvx](https://docs.astral.sh/uv/guides/tools/#running-tools) to get started quickly:

```shell
uvx ty check
```

Or, check out the [ty playground](https://play.ty.dev) to try it out in your browser.

To learn more about using ty, see the [documentation](https://docs.astral.sh/ty/).

## Installation

To install ty, see the [installation](https://docs.astral.sh/ty/installation/) documentation.

To add the ty language server to your editor, see the [editor integration](https://docs.astral.sh/ty/editors/) guide.

## Getting help

If you have questions or want to report a bug, please open an
[issue](https://github.com/astral-sh/ty/issues) in this repository.

You may also join our [Discord server](https://discord.com/invite/astral-sh).

## Contributing

Development of this project takes place in the [Ruff](https://github.com/astral-sh/ruff) repository
at this time. Please [open pull requests](https://github.com/astral-sh/ruff/pulls) there for changes
to anything in the `ruff` submodule (which includes all of the Rust source code).

See the
[contributing guide](./CONTRIBUTING.md) for more details.

## Version policy

ty uses `0.0.x` versioning. ty does not yet have a stable API; breaking changes, including changes
to diagnostics, may occur between any two versions. See the [type system support](https://github.com/astral-sh/ty/issues/1889)
tracking issue for a detailed overview of currently supported features.

## FAQ

<!-- We intentionally use smaller headings for the FAQ items -->

<!-- markdownlint-disable MD001 -->

#### Why is ty doing \_\_\_\_\_?

See our [typing FAQ](https://docs.astral.sh/ty/reference/typing-faq).

#### How do you pronounce ty?

It's pronounced as "tee - why" ([`/tiː waɪ/`](https://en.wikipedia.org/wiki/Help:IPA/English#Key))

#### How should I stylize ty?

Just "ty", please.

<!-- markdownlint-enable MD001 -->

## License

ty is licensed under the MIT license ([LICENSE](LICENSE) or
<https://opensource.org/licenses/MIT>).

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in ty
by you, as defined in the MIT license, shall be licensed as above, without any additional terms or
conditions.

<div align="center">
  <a target="_blank" href="https://astral.sh" style="background:none">
    <img src="https://raw.githubusercontent.com/astral-sh/uv/main/assets/svg/Astral.svg" alt="Made by Astral">
  </a>
</div>


## /SECURITY.md

# Security policy

## Reporting a vulnerability

If you have found a possible vulnerability, please email `security at astral dot sh`.

## Bug bounties

While we sincerely appreciate and encourage reports of suspected security problems, please note that
Astral does not currently run any bug bounty programs.

## Vulnerability disclosures

Critical vulnerabilities will be disclosed via GitHub's
[security advisory](https://github.com/astral-sh/ty/security) system.


## /_typos.toml

```toml path="/_typos.toml" 
[files]
extend-exclude = []

[default.extend-words]

[default]
extend-ignore-re = [
    # Line ignore with trailing "spellchecker:disable-line"
    "(?Rm)^.*#\\s*spellchecker:disable-line{{contextString}}quot;,
    "LICENSEs",
    "ntBre"
]

```

## /assets/badge/v0.json

```json path="/assets/badge/v0.json" 
{
  "label": "",
  "message": "ty",
  "logoSvg": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"48\" height=\"48\"><path d=\"M48 7.7H27.8V0h-24v7.7H0v18.2h3.8v14.3c0 4.3 3.5 7.8 7.8 7.8H48V29.8H27.8v-3.9h12.4c4.3 0 7.8-3.5 7.8-7.8V7.7Z\" fill=\"#46ebe1\"/></svg>",
  "logoWidth": 10,
  "labelColor": "grey",
  "color": "#261230"
}

```

## /dist-workspace.toml

```toml path="/dist-workspace.toml" 
[workspace]
members = ["cargo:./ruff"]
packages = ["ty"]
version = "0.0.48"

# Config for 'dist'
[dist]
allow-dirty = ["ci"]
# The preferred dist version to use in CI (Cargo.toml SemVer syntax)
cargo-dist-version = "0.31.0"
# Whether to consider the binaries in a package for distribution (defaults true)
dist = false
# CI backends to support
ci = "github"
# The installers to generate for each app
installers = ["shell", "powershell"]
# The archive format to use for windows builds (defaults .zip)
windows-archive = ".zip"
# The archive format to use for non-windows builds (defaults .tar.xz)
unix-archive = ".tar.gz"
# Target platforms to build apps for (Rust target-triple syntax)
targets = [
    "aarch64-apple-darwin",
    "aarch64-unknown-linux-gnu",
    "aarch64-unknown-linux-musl",
    "aarch64-pc-windows-msvc",
    "arm-unknown-linux-musleabihf",
    "armv7-unknown-linux-gnueabihf",
    "armv7-unknown-linux-musleabihf",
    "x86_64-apple-darwin",
    "powerpc64-unknown-linux-gnu",
    "powerpc64le-unknown-linux-gnu",
    "riscv64gc-unknown-linux-gnu",
    "s390x-unknown-linux-gnu",
    "x86_64-unknown-linux-gnu",
    "x86_64-unknown-linux-musl",
    "x86_64-pc-windows-msvc",
    "i686-unknown-linux-gnu",
    "i686-unknown-linux-musl",
    "i686-pc-windows-msvc",
]
# Whether to auto-include files like READMEs, LICENSEs, and CHANGELOGs (default true)
auto-includes = false
# Whether dist should create a Github Release or use an existing draft
create-release = true
# Which actions to run on pull requests
pr-run-mode = "plan"
# Whether to publish prereleases to package managers
publish-prereleases = true
# Whether CI should trigger releases with dispatches instead of tag pushes
dispatch-releases = true
# Which phase dist should use to create the GitHub release
github-release = "announce"
# Whether to enable GitHub Attestations
github-attestations = true
# When to generate GitHub Attestations
github-attestations-phase = "announce"
# Patterns to attest when creating attestations for release artifacts
github-attestations-filters = ["*.json", "*.sh", "*.ps1", "*.zip", "*.tar.gz"]
# Whether CI should include auto-generated code to build local artifacts
build-local-artifacts = false
# Local artifacts jobs to run in CI
local-artifacts-jobs = ["./build-binaries", "./build-docker"]
# Publish jobs to run in CI
publish-jobs = ["./publish-pypi"]
# Post-announce jobs to run in CI
post-announce-jobs = ["./notify-dependents", "./publish-docs", "./publish-versions", "./publish-mirror"]
# Custom permissions for GitHub Jobs
github-custom-job-permissions = { "build-docker" = { packages = "write", contents = "read", id-token = "write", attestations = "write" }, "publish-mirror" = { contents = "read" } }
# Whether to install an updater program
install-updater = false
# Path that installers should place binaries in
install-path = ["$XDG_BIN_HOME/", "$XDG_DATA_HOME/../bin", "~/.local/bin"]
# Whether source tarballs should include submodules
recursive-tarball = true
# Prefer simple hosting for downloads, falling back to GitHub releases
hosting = ["simple", "github"]
simple-download-url = "https://releases.astral.sh/github/ty/releases/download/{tag}"

[dist.github-custom-runners]
global = "depot-ubuntu-latest-4"

[dist.github-action-commits]
"actions/checkout" = "0c366fd6a839edf440554fa01a7085ccba70ac98"                # v6.0.2
"actions/upload-artifact" = "bbbca2ddaa5d8feaa63e36b76fdaad77386f024f"         # v7.0.0
"actions/download-artifact" = "3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c"       # v8.0.1
"actions/attest-build-provenance" = "00014ed6ed5efc5b1ab7f7f34a39eb55d41aa4f8" # v3.1.0

```

## /docs/.overrides/main.html

```html path="/docs/.overrides/main.html" 
{% extends "base.html" %}

{% block htmltitle %}
{% if page.meta and page.meta.title %}
<title>{{ page.meta.title }} | {{ config.site_name }}</title>
{% elif page.title and not page.is_homepage %}
<title>{{ page.title | striptags }} | {{ config.site_name }}</title>
{% else %}
<title>{{ config.site_name }}</title>
{% endif %}
{% endblock %}

{% block extrahead %}
<link rel="apple-touch-icon" sizes="180x180" href="https://docs.astral.sh/static/apple-touch-icon.png"/>
<link rel="icon" type="image/png" sizes="32x32" href="https://docs.astral.sh/static/favicon-32x32.png"/>
<link rel="icon" type="image/png" sizes="16x16" href="https://docs.astral.sh/static/favicon-16x16.png"/>
<link rel="manifest" href="https://docs.astral.sh/static/site.webmanifest"/>
<link rel="mask-icon" href="https://docs.astral.sh/static/safari-pinned-tab.svg" color="#2e183d"/>
<meta name="msapplication-TileColor" content="#d7ff64"/>
<meta name="theme-color" content="#ffffff"/>
<meta name="robots" content="index,follow"/>
<script type="application/ld+json">
    {
        {% if page and page.meta.git_revision_date_localized_raw_iso_datetime %}
        "datePublished": "{{ page.meta.git_revision_date_localized_raw_iso_datetime }}Z",
        "dateModified": "{{ page.meta.git_revision_date_localized_raw_iso_datetime  }}Z",
        {% endif %}
        "@context": "https://schema.org",
        "@type": "WebSite",
        "name": "Astral Docs",
        "url": "https://docs.astral.sh"
    }
</script>
{% endblock %}

```

## /docs/.overrides/partials/integrations/analytics/fathom.html

```html path="/docs/.overrides/partials/integrations/analytics/fathom.html" 
<script src="https://cdn.usefathom.com/script.js" data-site="ASBGCSTT" defer></script>

```

## /docs/assets/favicon.ico

Binary file available at https://raw.githubusercontent.com/astral-sh/ty/refs/heads/main/docs/assets/favicon.ico

## /docs/assets/logo-letter.svg

```svg path="/docs/assets/logo-letter.svg" 
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M48 7.68H27.84V0H3.84V7.68H0V25.92H3.84V40.2136C3.84 44.5139 7.32607 48 11.6264 48H48V29.76H27.84V25.92H40.2136C44.5139 25.92 48 22.4339 48 18.1336V7.68Z" fill="#46EBE1"/>
</svg>

```

## /docs/assets/ty-benchmark-cli-light.svg

```svg path="/docs/assets/ty-benchmark-cli-light.svg" 
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 430 117">
  <style type="text/css">
    .benchmarkBar{
      fill: #6340AC;
    }
    .benchmarkLabel{
      fill: #333333;
    }
  </style>
  <g fill="none" stroke-miterlimit="10" transform="translate(61,0)"><g class="mark-group role-frame root" role="graphics-object" aria-roledescription="group mark container"><g transform="translate(0,0)"><path class="background" aria-hidden="true" d="M0,0h350v100h-350Z"/><g><g class="mark-group role-axis" aria-hidden="true"><g transform="translate(0.5,100.5)"><path class="background" aria-hidden="true" d="M0,0h0v0h0Z" pointer-events="none"/><g><g class="mark-rule role-axis-grid" pointer-events="none"><line transform="translate(0,0)" x2="0" y2="-100" stroke="rgba(127,127,127,0.25)" stroke-width="1" opacity="1"/><line transform="translate(140,0)" x2="0" y2="-100" stroke="rgba(127,127,127,0.25)" stroke-width="1" opacity="1"/><line transform="translate(280,0)" x2="0" y2="-100" stroke="rgba(127,127,127,0.25)" stroke-width="1" opacity="1"/></g></g><path class="foreground" aria-hidden="true" d="" pointer-events="none" display="none"/></g></g><g class="mark-group role-axis" role="graphics-symbol" aria-roledescription="axis" aria-label="X-axis for a linear scale with values from 0 to 50"><g transform="translate(0.5,100.5)"><path class="background" aria-hidden="true" d="M0,0h0v0h0Z" pointer-events="none"/><g><g class="mark-rule role-axis-tick" pointer-events="none"><line transform="translate(0,0)" x2="0" y2="0" stroke="rgba(127,127,127,0.25)" stroke-width="1" opacity="1"/><line transform="translate(140,0)" x2="0" y2="0" stroke="rgba(127,127,127,0.25)" stroke-width="1" opacity="1"/><line transform="translate(280,0)" x2="0" y2="0" stroke="rgba(127,127,127,0.25)" stroke-width="1" opacity="1"/></g><g class="mark-text role-axis-label" pointer-events="none"><text text-anchor="middle" transform="translate(0,15)" font-family="Roboto Mono,monospace" font-size="12px" class="benchmarkLabel" opacity="1">0s</text><text text-anchor="middle" transform="translate(140,15)" font-family="Roboto Mono,monospace" font-size="12px" class="benchmarkLabel" opacity="1">20s</text><text text-anchor="middle" transform="translate(280,15)" font-family="Roboto Mono,monospace" font-size="12px" class="benchmarkLabel" opacity="1">40s</text></g></g><path class="foreground" aria-hidden="true" d="" pointer-events="none" display="none"/></g></g><g class="mark-group role-axis" role="graphics-symbol" aria-roledescription="axis" aria-label="Y-axis for a discrete scale with 4 values: ty, Pyrefly, Pyright, mypy"><g transform="translate(0.5,0.5)"><path class="background" aria-hidden="true" d="M0,0h0v0h0Z" pointer-events="none"/><g><g class="mark-text role-axis-label" pointer-events="none"><text text-anchor="end" transform="translate(-10,16)" font-family="Roboto Mono,monospace" font-size="12px" class="benchmarkLabel" opacity="1" font-weight="bold">ty</text><text text-anchor="end" transform="translate(-10,41)" font-family="Roboto Mono,monospace" font-size="12px" class="benchmarkLabel" opacity="1">Pyrefly</text><text text-anchor="end" transform="translate(-10,66)" font-family="Roboto Mono,monospace" font-size="12px" class="benchmarkLabel" opacity="1">Pyright</text><text text-anchor="end" transform="translate(-10,91)" font-family="Roboto Mono,monospace" font-size="12px" class="benchmarkLabel" opacity="1">mypy</text></g></g><path class="foreground" aria-hidden="true" d="" pointer-events="none" display="none"/></g></g><g class="mark-rect role-mark layer_0_marks" role="graphics-object" aria-roledescription="rect mark container"><path aria-label="Sum of time: 2.186; tool: ty" role="graphics-symbol" aria-roledescription="bar" d="M0,6h15.302000000000001v13h-15.302000000000001Z" class="benchmarkBar"/><path aria-label="Sum of time: 5.32; tool: Pyrefly" role="graphics-symbol" aria-roledescription="bar" d="M0,31h37.24v13h-37.24Z" class="benchmarkBar"/><path aria-label="Sum of time: 19.623; tool: Pyright" role="graphics-symbol" aria-roledescription="bar" d="M0,56h137.36100000000002v13h-137.36100000000002Z" class="benchmarkBar"/><path aria-label="Sum of time: 45.662; tool: mypy" role="graphics-symbol" aria-roledescription="bar" d="M0,81h319.63399999999996v13h-319.63399999999996Z" class="benchmarkBar"/></g><g class="mark-text role-mark layer_1_marks" role="graphics-object" aria-roledescription="text mark container"><text aria-label="Sum of time: 5.32; tool: Pyrefly; timeFormat: 5.32s" role="graphics-symbol" aria-roledescription="text mark" text-anchor="start" transform="translate(43.24,41.5)" font-family="Roboto Mono,monospace" font-size="12px" class="benchmarkLabel">5.32s</text><text aria-label="Sum of time: 19.623; tool: Pyright; timeFormat: 19.62s" role="graphics-symbol" aria-roledescription="text mark" text-anchor="start" transform="translate(143.36100000000002,66.5)" font-family="Roboto Mono,monospace" font-size="12px" class="benchmarkLabel">19.62s</text><text aria-label="Sum of time: 45.662; tool: mypy; timeFormat: 45.66s" role="graphics-symbol" aria-roledescription="text mark" text-anchor="start" transform="translate(325.63399999999996,91.5)" font-family="Roboto Mono,monospace" font-size="12px" class="benchmarkLabel">45.66s</text></g><g class="mark-text role-mark layer_2_marks" role="graphics-object" aria-roledescription="text mark container"><text aria-label="Sum of time: 2.186; tool: ty; timeFormat: 2.19s" role="graphics-symbol" aria-roledescription="text mark" text-anchor="start" transform="translate(21.302,16.5)" font-family="Roboto Mono,monospace" font-size="12px" font-weight="bold" class="benchmarkLabel">2.19s</text></g></g><path class="foreground" aria-hidden="true" d="" display="none"/></g></g></g></svg>

```

## /docs/assets/ty-benchmark-cli.svg

```svg path="/docs/assets/ty-benchmark-cli.svg" 
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 430 117">
  <style type="text/css">
    .benchmarkBar{
      fill: #6340AC;
    }
    .benchmarkLabel{
      fill: #333333;
    }

    @media (prefers-color-scheme: dark) {
      .benchmarkBar{
        fill: #6340AC;
      }
      .benchmarkLabel{
        fill: #FFFFFF;
      }
    }
  </style>
  <g fill="none" stroke-miterlimit="10" transform="translate(61,0)"><g class="mark-group role-frame root" role="graphics-object" aria-roledescription="group mark container"><g transform="translate(0,0)"><path class="background" aria-hidden="true" d="M0,0h350v100h-350Z"/><g><g class="mark-group role-axis" aria-hidden="true"><g transform="translate(0.5,100.5)"><path class="background" aria-hidden="true" d="M0,0h0v0h0Z" pointer-events="none"/><g><g class="mark-rule role-axis-grid" pointer-events="none"><line transform="translate(0,0)" x2="0" y2="-100" stroke="rgba(127,127,127,0.25)" stroke-width="1" opacity="1"/><line transform="translate(140,0)" x2="0" y2="-100" stroke="rgba(127,127,127,0.25)" stroke-width="1" opacity="1"/><line transform="translate(280,0)" x2="0" y2="-100" stroke="rgba(127,127,127,0.25)" stroke-width="1" opacity="1"/></g></g><path class="foreground" aria-hidden="true" d="" pointer-events="none" display="none"/></g></g><g class="mark-group role-axis" role="graphics-symbol" aria-roledescription="axis" aria-label="X-axis for a linear scale with values from 0 to 50"><g transform="translate(0.5,100.5)"><path class="background" aria-hidden="true" d="M0,0h0v0h0Z" pointer-events="none"/><g><g class="mark-rule role-axis-tick" pointer-events="none"><line transform="translate(0,0)" x2="0" y2="0" stroke="rgba(127,127,127,0.25)" stroke-width="1" opacity="1"/><line transform="translate(140,0)" x2="0" y2="0" stroke="rgba(127,127,127,0.25)" stroke-width="1" opacity="1"/><line transform="translate(280,0)" x2="0" y2="0" stroke="rgba(127,127,127,0.25)" stroke-width="1" opacity="1"/></g><g class="mark-text role-axis-label" pointer-events="none"><text text-anchor="middle" transform="translate(0,15)" font-family="Roboto Mono,monospace" font-size="12px" class="benchmarkLabel" opacity="1">0s</text><text text-anchor="middle" transform="translate(140,15)" font-family="Roboto Mono,monospace" font-size="12px" class="benchmarkLabel" opacity="1">20s</text><text text-anchor="middle" transform="translate(280,15)" font-family="Roboto Mono,monospace" font-size="12px" class="benchmarkLabel" opacity="1">40s</text></g></g><path class="foreground" aria-hidden="true" d="" pointer-events="none" display="none"/></g></g><g class="mark-group role-axis" role="graphics-symbol" aria-roledescription="axis" aria-label="Y-axis for a discrete scale with 4 values: ty, Pyrefly, Pyright, mypy"><g transform="translate(0.5,0.5)"><path class="background" aria-hidden="true" d="M0,0h0v0h0Z" pointer-events="none"/><g><g class="mark-text role-axis-label" pointer-events="none"><text text-anchor="end" transform="translate(-10,16)" font-family="Roboto Mono,monospace" font-size="12px" class="benchmarkLabel" opacity="1" font-weight="bold">ty</text><text text-anchor="end" transform="translate(-10,41)" font-family="Roboto Mono,monospace" font-size="12px" class="benchmarkLabel" opacity="1">Pyrefly</text><text text-anchor="end" transform="translate(-10,66)" font-family="Roboto Mono,monospace" font-size="12px" class="benchmarkLabel" opacity="1">Pyright</text><text text-anchor="end" transform="translate(-10,91)" font-family="Roboto Mono,monospace" font-size="12px" class="benchmarkLabel" opacity="1">mypy</text></g></g><path class="foreground" aria-hidden="true" d="" pointer-events="none" display="none"/></g></g><g class="mark-rect role-mark layer_0_marks" role="graphics-object" aria-roledescription="rect mark container"><path aria-label="Sum of time: 2.186; tool: ty" role="graphics-symbol" aria-roledescription="bar" d="M0,6h15.302000000000001v13h-15.302000000000001Z" class="benchmarkBar"/><path aria-label="Sum of time: 5.32; tool: Pyrefly" role="graphics-symbol" aria-roledescription="bar" d="M0,31h37.24v13h-37.24Z" class="benchmarkBar"/><path aria-label="Sum of time: 19.623; tool: Pyright" role="graphics-symbol" aria-roledescription="bar" d="M0,56h137.36100000000002v13h-137.36100000000002Z" class="benchmarkBar"/><path aria-label="Sum of time: 45.662; tool: mypy" role="graphics-symbol" aria-roledescription="bar" d="M0,81h319.63399999999996v13h-319.63399999999996Z" class="benchmarkBar"/></g><g class="mark-text role-mark layer_1_marks" role="graphics-object" aria-roledescription="text mark container"><text aria-label="Sum of time: 5.32; tool: Pyrefly; timeFormat: 5.32s" role="graphics-symbol" aria-roledescription="text mark" text-anchor="start" transform="translate(43.24,41.5)" font-family="Roboto Mono,monospace" font-size="12px" class="benchmarkLabel">5.32s</text><text aria-label="Sum of time: 19.623; tool: Pyright; timeFormat: 19.62s" role="graphics-symbol" aria-roledescription="text mark" text-anchor="start" transform="translate(143.36100000000002,66.5)" font-family="Roboto Mono,monospace" font-size="12px" class="benchmarkLabel">19.62s</text><text aria-label="Sum of time: 45.662; tool: mypy; timeFormat: 45.66s" role="graphics-symbol" aria-roledescription="text mark" text-anchor="start" transform="translate(325.63399999999996,91.5)" font-family="Roboto Mono,monospace" font-size="12px" class="benchmarkLabel">45.66s</text></g><g class="mark-text role-mark layer_2_marks" role="graphics-object" aria-roledescription="text mark container"><text aria-label="Sum of time: 2.186; tool: ty; timeFormat: 2.19s" role="graphics-symbol" aria-roledescription="text mark" text-anchor="start" transform="translate(21.302,16.5)" font-family="Roboto Mono,monospace" font-size="12px" font-weight="bold" class="benchmarkLabel">2.19s</text></g></g><path class="foreground" aria-hidden="true" d="" display="none"/></g></g></g></svg>

```

## /docs/coming-from-mypy-or-pyright.md

# Coming from mypy or pyright

This guide helps you migrate a project from
[mypy](https://mypy.readthedocs.io/en/stable/) or
[pyright](https://microsoft.github.io/pyright/) to ty.

## Migration tips

- mypy disables an error code with `# type: ignore[code]`; pyright suppresses a single line with
    `# pyright: ignore[reportXyz]`; ty's equivalent is `# ty: ignore[rule]`.
    See [this page](suppression.md) for more information about suppression comments.
- mypy's `disable_error_code` and pyright's `reportXyz = "none"` both correspond to setting
    `<rule> = "ignore"` under `[tool.ty.rules]`. See [this section](reference/configuration.md#rules) for
    details.
- Severities in ty are `ignore`, `warn`, `error`. Pyright's `"information"` and `"hint"` levels have
    no direct ty equivalent — use `warn` for both.
- If you are looking for the equivalent of `disallow_untyped_defs` / `no-untyped-def` (mypy) or `reportMissingParameterType`,
    `reportUnknownParameterType` (pyright), check out this
    [FAQ entry](reference/typing-faq.md#why-doesnt-ty-warn-about-missing-type-annotations).

## How to read this table

- **ty rule**: the canonical name, as listed in [Rules](reference/rules.md). Configure under
    `[tool.ty.rules]`.
- **mypy error code**: the value passed to `# type: ignore[<code>]` or `disable_error_code`. Some ty
    rules surface as one of mypy's catch-all codes (`misc`, `assignment`, `valid-type`); these
    mappings are deliberately broad.
- **pyright diagnostic**: the `report*` setting in `pyrightconfig.json` or `[tool.pyright]`.

A blank cell means no direct equivalent exists in that checker (the diagnostic is either not
emitted, or is folded into a broader category that already appears for another ty rule).

## Mapping table

| ty rule                         | mypy error code                                         | pyright diagnostic                                           |
| ------------------------------- | ------------------------------------------------------- | ------------------------------------------------------------ |
| `call-abstract-method`          |                                                         | `reportAbstractUsage`                                        |
| `call-non-callable`             | `operator`                                              | `reportCallIssue`                                            |
| `conflicting-declarations`      | `no-redef`                                              | `reportRedeclaration`                                        |
| `conflicting-metaclass`         | `metaclass`                                             | `reportGeneralTypeIssues`                                    |
| `cyclic-class-definition`       | `misc`                                                  | `reportGeneralTypeIssues`                                    |
| `deprecated`                    | `deprecated`                                            | `reportDeprecated`                                           |
| `division-by-zero`              |                                                         |                                                              |
| `duplicate-base`                | `misc`                                                  | `reportGeneralTypeIssues`                                    |
| `empty-body`                    | `empty-body`                                            |                                                              |
| `inconsistent-mro`              | `misc`                                                  | `reportGeneralTypeIssues`                                    |
| `index-out-of-bounds`           | `misc`                                                  | `reportGeneralTypeIssues`                                    |
| `invalid-argument-type`         | `arg-type`<br>`index`<br>`type-var`<br>`typeddict-item` | `reportArgumentType`<br>`reportAssignmentType`               |
| `invalid-assignment`            | `assignment`                                            | `reportAssignmentType`                                       |
| `invalid-attribute-access`      | `misc`                                                  | `reportAttributeAccessIssue`                                 |
| `invalid-await`                 | `misc`                                                  | `reportGeneralTypeIssues`                                    |
| `invalid-base`                  | `valid-type`                                            | `reportGeneralTypeIssues`                                    |
| `invalid-context-manager`       | `misc`<br>`attr-defined`                                | `reportGeneralTypeIssues`                                    |
| `invalid-exception-caught`      | `misc`                                                  | `reportGeneralTypeIssues`                                    |
| `invalid-key`                   | `typeddict-item`<br>`typeddict-unknown-key`             | `reportAssignmentType`                                       |
| `invalid-metaclass`             | `metaclass`                                             |                                                              |
| `invalid-method-override`       | `override`                                              | `reportIncompatibleMethodOverride`                           |
| `invalid-overload`              | `no-overload-impl`                                      | `reportNoOverloadImplementation`                             |
| `invalid-parameter-default`     | `assignment`                                            | `reportArgumentType`                                         |
| `invalid-raise`                 | `misc`                                                  | `reportGeneralTypeIssues`                                    |
| `invalid-return-type`           | `return-value`                                          | `reportReturnType`                                           |
| `invalid-type-arguments`        | `misc`<br>`type-var`                                    | `reportInvalidTypeArguments`                                 |
| `invalid-type-form`             | `valid-type`                                            | `reportInvalidTypeForm`                                      |
| `missing-argument`              | `call-arg`                                              | `reportCallIssue`                                            |
| `missing-override-decorator`    | `explicit-override`                                     | `reportImplicitOverride`                                     |
| `missing-typed-dict-key`        | `typeddict-item`                                        | `reportAssignmentType`                                       |
| `no-matching-overload`          | `call-overload`                                         | `reportCallIssue`                                            |
| `not-iterable`                  | `misc`<br>`attr-defined`                                | `reportGeneralTypeIssues`                                    |
| `not-subscriptable`             | `index`                                                 | `reportIndexIssue`                                           |
| `parameter-already-assigned`    | `misc`<br>`call-arg`                                    | `reportCallIssue`                                            |
| `possibly-missing-attribute`    | `union-attr`                                            | `reportOptionalMemberAccess`<br>`reportAttributeAccessIssue` |
| `possibly-unresolved-reference` | `possibly-undefined`                                    | `reportPossiblyUnboundVariable`                              |
| `redundant-cast`                | `redundant-cast`                                        | `reportUnnecessaryCast`                                      |
| `too-many-positional-arguments` | `call-arg`                                              | `reportCallIssue`                                            |
| `type-assertion-failure`        | `assert-type`                                           | `reportAssertTypeFailure`                                    |
| `undefined-reveal`              | `unimported-reveal`                                     |                                                              |
| `unknown-argument`              | `call-arg`                                              | `reportCallIssue`                                            |
| `unresolved-attribute`          | `attr-defined`                                          | `reportAttributeAccessIssue`                                 |
| `unresolved-import`             | `import-not-found`                                      | `reportMissingImports`                                       |
| `unresolved-reference`          | `name-defined`                                          | `reportUndefinedVariable`                                    |
| `unsupported-operator`          | `operator`                                              | `reportOperatorIssue`                                        |
| `unused-awaitable`              | `unused-coroutine`<br>`unused-awaitable`                | `reportUnusedCoroutine`                                      |
| `unused-ignore-comment`         | `unused-ignore`                                         | `reportUnnecessaryTypeIgnoreComment`                         |

The full list of ty rules — including those without a direct equivalent above — is in
[Rules](reference/rules.md). Contributions to extend this mapping are welcome via pull request to the
[`ty` repository](https://github.com/astral-sh/ty); see issue
[#2111](https://github.com/astral-sh/ty/issues/2111) for context.


## /docs/configuration.md

# Configuration

## Configuration files

ty supports persistent configuration files at both the project- and user-level.

Specifically, ty will search for a `pyproject.toml` or `ty.toml` file in the current directory, or in the nearest parent directory.

If a `pyproject.toml` file is found, ty will read configuration from the `[tool.ty]` table. For example, to ignore the `index-out-of-bounds` rule, add the following to a `pyproject.toml`:

```toml title="pyproject.toml"
[tool.ty.rules]
index-out-of-bounds = "ignore"
```

!!! note

    If there is no `tool.ty` table, the `pyproject.toml` file will be ignored, and ty will continue
    searching in the directory hierarchy.

ty will also search for `ty.toml` files, which follow an identical structure, but omit the `[tool.ty]` prefix. For example:

```toml title="ty.toml"
[rules]
index-out-of-bounds = "ignore"
```

!!! important

    `ty.toml` files take precedence over `pyproject.toml` files, so if both `ty.toml` and `pyproject.toml` files are present in a directory, configuration will be read from `ty.toml`, and the `[tool.ty]` section in the accompanying `pyproject.toml` will be ignored.

ty will also discover user-level configuration at `~/.config/ty/ty.toml` (or `$XDG_CONFIG_HOME/ty/ty.toml`) on macOS and Linux, or `%APPDATA%\ty\ty.toml` on Windows. User-level configuration must use the `ty.toml` format, rather than the `pyproject.toml` format, as a `pyproject.toml` is intended to define a Python project.

If project- and user-level configuration files are found, the settings will be merged, with project-level configuration taking precedence over the user-level configuration.

For example, if a string, number, or boolean is present in both the project- and user-level configuration tables, the project-level value will be used, and the user-level value will be ignored. If an array is present in both tables, the arrays will be merged, with the project-level settings appearing later in the merged array.

Settings provided via command line take precedence over persistent configuration.

See the [configuration](./reference/configuration.md) reference for an enumeration of the available settings.


## /docs/editors.md

# Editor integration

ty can be integrated with various editors to provide a seamless development experience.

Learn more about ty's editor features in the [language server](./features/language-server.md)
documentation.

## VS Code

The Astral team maintains an official VS Code extension.

Install the [ty extension](https://marketplace.visualstudio.com/items?itemName=astral-sh.ty) from the VS Code Marketplace.

The extension automatically disables the language server from the [Python
extension](https://marketplace.visualstudio.com/items?itemName=ms-python.python)
to avoid running two Python language servers. This is done by setting
[`python.languageServer`](https://code.visualstudio.com/docs/python/settings-reference#_intellisense-engine-settings)
to `"None"` as a default configuration.

If you prefer to use ty only for type checking and want to use another language
server for capabilities like hover, auto-completions, etc., you can override
this by explicitly setting [`python.languageServer`](https://code.visualstudio.com/docs/python/settings-reference#_intellisense-engine-settings) and
[`ty.disableLanguageServices`](./reference/editor-settings.md#disablelanguageservices)
in your [`settings.json`](https://code.visualstudio.com/docs/configure/settings#_settings-json-file):

```jsonc
{
  "python.languageServer": "Pylance",
  "ty.disableLanguageServices": true,
}
```

## Neovim

The [nvim-lspconfig](https://github.com/neovim/nvim-lspconfig) extension is the
recommended way of using ty with Neovim (if you prefer not to install the extension,
you can copy [the ty configuration](https://github.com/neovim/nvim-lspconfig/blob/master/lsp/ty.lua)
manually instead). After installing the nvim-lspconfig extension, you need to enable
the language server (and can optionally configure additional settings). For Neovim >=0.11,
you can add the following snippet to your config file:

```lua
-- Optional: Only required if you need to update the language server settings
vim.lsp.config('ty', {
  settings = {
    ty = {
      -- ty language server settings go here
    }
  }
})

-- Required: Enable the language server
vim.lsp.enable('ty')
```

For Neovim \<0.11, you would use the configuration below instead (note that [you might need
to install an older version of nvim-lspconfig](https://github.com/neovim/nvim-lspconfig?tab=readme-ov-file#important-%EF%B8%8F)):

```lua
require('lspconfig').ty.setup({
  settings = {
    ty = {
      -- ty language server settings go here
    }
  }
})
```

## Zed

ty is included with Zed out of the box (no extension required), although the default primary LSP for Python is basedpyright.

You can enable ty and disable basedpyright by adding this to your `settings.json` file:

```json
{
  "languages": {
    "Python": {
      "language_servers": [
        // Enable ty and ruff,
        // Other built-in servers (basedpyright, pyright, pylsp)
        // are disabled by being omitted from this list.
        "ty",
        "ruff"
      ]
    }
  }
}
```

You can override the `ty` executable Zed uses by setting `lsp.ty.binary`:

```json
{
  "lsp": {
    "ty": {
      "binary": {
        "path": "/home/user/.local/bin/ty",
        "arguments": ["server"]
      }
    }
  }
}
```

More information in [Zed's documentation](https://zed.dev/docs/languages/python#configure-python-language-servers-in-zed).

## PyCharm

Starting with version 2025.3, PyCharm users can enable native ty support in the settings:

1. Go to **Python | Tools | ty** in the Settings dialog.

1. Select the **Enable** checkbox.

1. In the Execution mode setting, select how PyCharm should search for the executable:

    **Interpreter** mode: PyCharm searches for an executable installed in your interpreter. To install the ty package for the selected interpreter, click _Install ty_.

    **Path** mode: PyCharm searches for an executable in `$PATH`. If the executable is not found, you can specify the path by clicking the Browse... icon.

1. Select which options should be enabled.

For more information, refer to [PyCharm documentation](https://www.jetbrains.com/help/pycharm/lsp-tools.html#ty).

## Emacs

ty can be utilized as a language server via the built-in [Eglot](https://www.gnu.org/software/emacs/manual/html_node/eglot/index.html) client, which is part of Emacs since version 29.

```elisp
(with-eval-after-load 'eglot
  (add-to-list 'eglot-server-programs
               '((python-base-mode :language-id "python") . ("ty" "server"))))

(add-hook 'python-base-mode-hook 'eglot-ensure)
```

If you prefer to view ty's diagnostics through [Flycheck](https://www.flycheck.org/), the
[flycheck-eglot](https://github.com/flycheck/flycheck-eglot) package bridges Eglot's diagnostics
to Flycheck.

## Other editors

ty can be used with any editor that supports the [language server
protocol](https://microsoft.github.io/language-server-protocol/).

To start the language server, use the `server` subcommand:

```shell
ty server
```

Refer to your editor's documentation to learn how to connect to an LSP server.

## Settings

See the [editor settings reference](./reference/editor-settings.md) for more details on configuring the language
server.


## /docs/exclusions.md

# Excluding files

ty automatically discovers all Python files in your project. You can customize where ty searches by using the [`src.include`](./reference/configuration.md#include) and [`src.exclude`](./reference/configuration.md#exclude) settings.

For example, with the following configuration, ty checks all Python files in the `src` and `tests` directories except those in the `src/generated` directory:

=== "pyproject.toml"

    ```toml
    [tool.ty.src]
    include = ["src", "tests"]
    exclude = ["src/generated"]
    ```

=== "ty.toml"

    ```toml
    [src]
    include = ["src", "tests"]
    exclude = ["src/generated"]
    ```

## Default exclusions

By default, ty excludes a [variety of commonly ignored directories](./reference/configuration.md#exclude_1). If you want to include one of these directories, you can do so by adding a negative `exclude` using a leading `!`:

=== "pyproject.toml"

    ```toml
    [tool.ty.src]
    # Remove `build` from the excluded directories.
    exclude = ["!**/build/"]
    ```

=== "ty.toml"

    ```toml
    [src]
    # Remove `build` from the excluded directories.
    exclude = ["!**/build/"]
    ```

By default, ty ignores files listed in an `.ignore` or `.gitignore` file. To disable this functionality, set [`respect-ignore-files`](./reference/configuration.md#respect-ignore-files) to `false`.

## Explicit targets

You may explicitly pass the paths that ty should check, e.g.:

```shell
ty check src scripts/benchmark.py
```

Paths that are passed as positional arguments to `ty check` are included even if they would otherwise be ignored through `exclude` filters or an ignore-file.

## Include and exclude syntax

Both `include` and `exclude` support gitignore like glob patterns:

- `src/` matches a directory (including its contents) named `src`.
- `src` matches a file or directory (including its contents) named `src`.
- `*` matches any (possibly empty) sequence of characters (except `/`).
- `**` matches zero or more path components.
    This sequence **must** form a single path component, so both `./**a` and `./b**/` are invalid and will result in an error.
    A sequence of more than two consecutive `*` characters is also invalid.
- `?` matches any single character except `/`
- `[abc]` matches any character inside the brackets. Character sequences can also specify ranges of characters, as ordered by Unicode,
    so e.g. `[0-9]` specifies any character between `0` and `9` inclusive. An unclosed bracket is invalid.

All patterns are anchored: The pattern `src` only includes `<project_root>/src` but not something like `<project_root>/test/src`. To include any directory named `src`, use the prefix match `**/src`. The same applies for exclude patterns where `src` only excludes `<project_root>/src` but not something like `<project_root>/test/src`.

!!! warning

    A prefix include pattern like `**/src` can notably slow down the Python file discovery.

All fields accepting patterns use the reduced portable glob syntax from [PEP 639](https://peps.python.org/pep-0639/#add-license-FILES-key), with the addition that characters can be escaped with a backslash.

## Excluding files from virtual environments

In Python 3.13+, the `venv` module will add a `.gitignore` file to the virtual environment root and
ty will not emit diagnostics for the contained files. However, when using an older version of
Python, ty may include diagnostics for files in the virtual environment.

You can resolve this by adding a `.gitignore` to the environment, e.g., for a virtual environment
named `.venv`:

```shell
echo "*" > .venv/.gitignore
```

Or by adding your virtual environment to your `.gitignore` or `.ignore` file.


## /docs/features/diagnostics.md

# Diagnostics

ty provides diagnostics that include snippets of your source code, annotations and helpful
explanations. It will sometimes also provide suggestions on how to fix the reported issues.

## Example: Typed dictionaries

In this first example, ty detected an invalid assignment to a `TypedDict` key. Notice how the
diagnostic includes both the context around the line with the error, as well as the reference to the
`age` item in the `TypedDict` definition:

![TypedDict invalid-assignment diagnostic](screenshots/diagnostics1.dark.png#only-dark)
![TypedDict invalid-assignment diagnostic](screenshots/diagnostics1.light.png#only-light)

When a `TypedDict` key is misspelled, ty will suggest the correct spelling. If you are using an
editor with language server support, you can also apply this suggestion as a quick fix:

![TypedDict misspelled-key diagnostic](screenshots/diagnostics2.dark.png#only-dark)
![TypedDict misspelled-key diagnostic](screenshots/diagnostics2.light.png#only-light)

## Example: Invalid arguments

Here, a file has been opened for writing in text mode, but we are trying to write bytes to it.
The diagnostic points out the type mismatch and also includes the corresponding parameter in the
function definition for `write`:

![Invalid arguments diagnostic](screenshots/diagnostics3.dark.png#only-dark)
![Invalid arguments diagnostic](screenshots/diagnostics3.light.png#only-light)

## Example: Backwards compatibility

Instead of just telling you that `tomllib` can not be found, ty will tell you *why* it is not
available for your project. In this case, your project is targeting Python 3.10, but `tomllib` was
only added in Python 3.11:

![Missing import diagnostic](screenshots/diagnostics4.dark.png#only-dark)
![Missing import diagnostic](screenshots/diagnostics4.light.png#only-light)


## /docs/features/language-server.md

<!-- Note for maintainers: the screenshots referenced in this document were taken using
     the "Atom One Light" theme in VS Code. -->

# Language server

You can generally expect ty to be a fully-featured [language server] for Python.
This page describes some of the key features provided by ty's IDE integration and includes
a reference table of supported LSP features at the end.
See the [editor integration](../editors.md) guide for instructions on how to set up ty
with your editor.

## Diagnostics

<figure markdown="span">
  ![](screenshots/inline-diagnostics.png)
  <figcaption>Example of an inline diagnostic with code-span annotations</figcaption>
</figure>

ty reports type errors and other [diagnostics](./diagnostics.md) directly in your editor. Diagnostics
are updated as you type. You can use the
[`diagnosticMode`](../reference/editor-settings.md#diagnosticmode) setting to control if you want to
see diagnostics for open files only, or for your entire workspace.

!!! info

    ty supports both the "pull" and "push" diagnostic models. Most modern editors will use the "pull" model
    for better performance, where diagnostics are fetched on demand rather than pushed after every
    change.

## Code navigation

<figure markdown="span">
  ![](screenshots/find-references.png)
  <figcaption>"Find references" shows usages across the entire workspace</figcaption>
</figure>

ty powers several language server features that allow you to navigate a Python codebase:

- **Go to Definition**: Jump to where a symbol is defined. ty resolves imports, function calls,
    class references, and more.
- **Go to Declaration**: Navigate to the declaration site of a symbol, which can differ from its
    definition (could be in a stub file).
- **Go to Type Definition**: Navigate to the type of a symbol. For example, this takes you to the class `Person`
    when invoked on a variable `user: Person`.
- **Find all references**: Find every usage of a function, class, or variable across your entire workspace.
- **Document and workspace symbols**: See an outline of symbols in the current file, or search through symbols across your entire workspace.

## Code completions

<figure markdown="span">
  ![Code completion example](screenshots/code-completion.png){ width="500" }
  <figcaption>Accepting this completion will automatically add a <code>subprocess</code> import at the top of the file.</figcaption>
</figure>

ty provides intelligent code completions as you type, offering suggestions for variables, functions, classes, and modules that are in scope.
For symbols that are not yet imported, ty suggests auto-import actions to add the necessary `import` statements.

## Code actions and refactorings

<figure markdown="span">
  ![Code action example](screenshots/quick-fix.png){ width="700" }
  <figcaption>ty offers to remove the unused suppression comment</figcaption>
</figure>

ty offers quick fixes and other code actions to help you resolve issues:

- **Add import**: Automatically add missing import statements
- **Quick fixes**: Some diagnostics come with quick fix suggestions to resolve the issue
- **Rename symbol**: Safely rename symbols across your entire codebase
- **Selection range**: Expand or shrink the text selection in your editor based on ty's understanding of Python syntax

## Contextual information

<figure markdown="span">
  ![Inlay hints](screenshots/inlay-hints.png)
  <figcaption>Gray inlay hints and on-hover information (signature, docstring)</figcaption>
</figure>

ty surfaces useful contextual information as you code:

- **Hover**: Hover over any symbol to see its type, documentation, function signatures, and other
    useful information like the variance of type parameters.
- **Inlay hints**: Display inline type hints for variables and parameters without explicit
    annotations, as well as parameter names at call sites. These hints can also be double-clicked
    to insert the type annotations into your source code. You can also click on parts of the inlay
    hints for go-to-definition navigation.
- **Signature help**: When calling a function, ty displays the function's parameters and their
    types. This appears automatically when you type `(` and updates as you navigate between arguments.
- **Document highlight**: When the cursor is on a symbol, ty highlights all occurrences of that
    symbol in the current file.
- **Semantic highlighting**: Syntax highlighting based on the underlying semantics and types.

## Code folding

ty provides Python specific code folding ranges to LSP clients upon request. This includes tagging
docstrings as comments, which supports editor actions like "fold all comment blocks."

## Notebook support

ty supports Jupyter notebooks (`.ipynb` files) with language server features. Each cell is
analyzed in context, with diagnostics, completions, and other features working across cells.

## Fine-grained incrementality

ty's architecture is designed for low-latency updates of diagnostics and other language server features.
When you make a change in your editor, ty incrementally updates only the affected parts of the
codebase, rather than re-analyzing everything from scratch. This happens at a fine-grained level,
down to individual definitions. This incrementality means that you get instant feedback as you type, i.e.,
within a few milliseconds, even on large projects.

!!! info

    Fine-grained dependencies also allow ty to skip large parts of 3rd-party dependencies when they are not relevant
    to your codebase.

## Feature reference

<!-- markdownlint-disable MD060 -->

| Feature                                               | Status           | Notes                                                         |
| ----------------------------------------------------- | ---------------- | ------------------------------------------------------------- |
| [`callHierarchy/*`][callhierarchy]                    | ✅ Supported     |                                                               |
| [`notebookDocument/*`][notebookdocument]              | ✅ Supported     |                                                               |
| [`textDocument/codeAction`][codeaction]               | ✅ Supported     | Quick fixes                                                   |
| [`textDocument/codeLens`][codelens]                   | ❌ Not supported |                                                               |
| [`textDocument/completion`][completion]               | ✅ Supported     |                                                               |
| [`textDocument/declaration`][declaration]             | ✅ Supported     |                                                               |
| [`textDocument/definition`][definition]               | ✅ Supported     |                                                               |
| [`textDocument/diagnostic`][diagnostic]               | ✅ Supported     |                                                               |
| [`textDocument/documentColor`][documentcolor]         | ❌ Not supported |                                                               |
| [`textDocument/documentHighlight`][documenthighlight] | ✅ Supported     |                                                               |
| [`textDocument/documentLink`][documentlink]           | ❌ Not supported |                                                               |
| [`textDocument/documentSymbol`][documentsymbol]       | ✅ Supported     |                                                               |
| [`textDocument/foldingRange`][foldingrange]           | ✅ Supported     |                                                               |
| [`textDocument/formatting`][formatting]               | —                | Use [Ruff] for formatting                                     |
| [`textDocument/hover`][hover]                         | ✅ Supported     |                                                               |
| [`textDocument/implementation`][implementation]       | ❌ Not supported | [#3514]                                                       |
| [`textDocument/inlayHint`][inlayhint]                 | ✅ Supported     |                                                               |
| [`textDocument/onTypeFormatting`][ontypeformatting]   | —                | [Ruff #16829](https://github.com/astral-sh/ruff/issues/16829) |
| [`textDocument/prepareRename`][preparerename]         | ✅ Supported     |                                                               |
| [`textDocument/rangeFormatting`][rangeformatting]     | —                | Use [Ruff] for formatting                                     |
| [`textDocument/references`][references]               | ✅ Supported     |                                                               |
| [`textDocument/rename`][rename]                       | ✅ Supported     |                                                               |
| [`textDocument/selectionRange`][selectionrange]       | ✅ Supported     |                                                               |
| [`textDocument/semanticTokens`][semantictokens]       | ✅ Supported     |                                                               |
| [`textDocument/signatureHelp`][signaturehelp]         | ✅ Supported     |                                                               |
| [`textDocument/typeDefinition`][typedefinition]       | ✅ Supported     |                                                               |
| [`typeHierarchy/*`][typehierarchy]                    | ✅ Supported     |                                                               |
| [`workspace/diagnostic`][workspacediagnostic]         | ✅ Supported     |                                                               |
| [`workspace/symbol`][workspacesymbol]                 | ✅ Supported     |                                                               |
| [`workspace/willRenameFiles`][willrenamefiles]        | ❌ Not supported | [#1560]                                                       |

[#1560]: https://github.com/astral-sh/ty/issues/1560
[#3514]: https://github.com/astral-sh/ty/issues/3514
[callhierarchy]: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#callHierarchy_incomingCalls
[codeaction]: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_codeAction
[codelens]: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_codeLens
[completion]: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_completion
[declaration]: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_declaration
[definition]: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_definition
[diagnostic]: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_diagnostic
[documentcolor]: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_documentColor
[documenthighlight]: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_documentHighlight
[documentlink]: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_documentLink
[documentsymbol]: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_documentSymbol
[foldingrange]: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_foldingRange
[formatting]: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_formatting
[hover]: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_hover
[implementation]: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_implementation
[inlayhint]: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_inlayHint
[language server]: https://microsoft.github.io/language-server-protocol/
[notebookdocument]: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#notebookDocument_synchronization
[ontypeformatting]: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_onTypeFormatting
[preparerename]: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_prepareRename
[rangeformatting]: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_rangeFormatting
[references]: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_references
[rename]: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_rename
[ruff]: https://docs.astral.sh/ruff/
[selectionrange]: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_selectionRange
[semantictokens]: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_semanticTokens
[signaturehelp]: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_signatureHelp
[typedefinition]: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_typeDefinition
[typehierarchy]: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#typeHierarchy_supertypes
[willrenamefiles]: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#workspace_willRenameFiles
[workspacediagnostic]: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#workspace_diagnostic
[workspacesymbol]: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#workspace_symbol


## /docs/features/screenshots/code-completion.png

Binary file available at https://raw.githubusercontent.com/astral-sh/ty/refs/heads/main/docs/features/screenshots/code-completion.png

## /docs/features/screenshots/diagnostics1.dark.png

Binary file available at https://raw.githubusercontent.com/astral-sh/ty/refs/heads/main/docs/features/screenshots/diagnostics1.dark.png

## /docs/features/screenshots/diagnostics1.light.png

Binary file available at https://raw.githubusercontent.com/astral-sh/ty/refs/heads/main/docs/features/screenshots/diagnostics1.light.png

## /docs/features/screenshots/diagnostics2.dark.png

Binary file available at https://raw.githubusercontent.com/astral-sh/ty/refs/heads/main/docs/features/screenshots/diagnostics2.dark.png

## /docs/features/screenshots/diagnostics2.light.png

Binary file available at https://raw.githubusercontent.com/astral-sh/ty/refs/heads/main/docs/features/screenshots/diagnostics2.light.png

## /docs/features/screenshots/diagnostics3.dark.png

Binary file available at https://raw.githubusercontent.com/astral-sh/ty/refs/heads/main/docs/features/screenshots/diagnostics3.dark.png

## /docs/features/screenshots/diagnostics3.light.png

Binary file available at https://raw.githubusercontent.com/astral-sh/ty/refs/heads/main/docs/features/screenshots/diagnostics3.light.png

## /docs/features/screenshots/diagnostics4.dark.png

Binary file available at https://raw.githubusercontent.com/astral-sh/ty/refs/heads/main/docs/features/screenshots/diagnostics4.dark.png

## /docs/features/screenshots/diagnostics4.light.png

Binary file available at https://raw.githubusercontent.com/astral-sh/ty/refs/heads/main/docs/features/screenshots/diagnostics4.light.png

## /docs/features/screenshots/find-references.png

Binary file available at https://raw.githubusercontent.com/astral-sh/ty/refs/heads/main/docs/features/screenshots/find-references.png

## /docs/features/screenshots/inlay-hints.png

Binary file available at https://raw.githubusercontent.com/astral-sh/ty/refs/heads/main/docs/features/screenshots/inlay-hints.png

## /docs/features/screenshots/inline-diagnostics.png

Binary file available at https://raw.githubusercontent.com/astral-sh/ty/refs/heads/main/docs/features/screenshots/inline-diagnostics.png

## /docs/features/screenshots/quick-fix.png

Binary file available at https://raw.githubusercontent.com/astral-sh/ty/refs/heads/main/docs/features/screenshots/quick-fix.png

## /docs/features/type-system.md

# Type system

You can generally expect ty to support all typing features that are described and specified in the
[Python typing documentation] (for a detailed overview, please refer to the
[type system features tracking issue](https://github.com/astral-sh/ty/issues/1889)). This page
highlights some of the unique features that ty's type system provides.

## Redeclarations

ty allows you to reuse the same symbol with a different type. The following example shows how the
`paths` parameter is redeclared as a list of strings:

```py
def split_paths(paths: str) -> list[Path]:
    paths: list[str] = paths.split(":")
    return [Path(p) for p in paths]
```

(Full example in the [playground](https://play.ty.dev/80a74c95-a43e-4a3d-8c26-f88e879d7dcb))

## Intersection types

ty has first-class support for intersection types. In contrast to a union type `A | B`, which means
"*either* A *or* B", an intersection type `A & B` means "*both* A *and* B". Type narrowing in ty is
based on intersections. For example, notice how we can call `obj.serialize_json()` *and* access the
`.version` property in the following function:

```py
def output_as_json(obj: Serializable) -> str:
    if isinstance(obj, Versioned):
        reveal_type(obj)  # reveals: Serializable & Versioned

        return str({
            "data": obj.serialize_json(),
            "version": obj.version
        })
    else:
        return obj.serialize_json()
```

(Full example in the [playground](https://play.ty.dev/39241435-5e78-4ce9-817f-ce65be73a6ed))

Intersections can also be built using gradual types like `Any` or its implicit counterpart
`Unknown`. For example, imagine you call into untyped (third party) code that returns an object of
type `Unknown`. Narrowing the type of that object using `isinstance` will result in an intersection
type `Unknown & Iterable`. This type allows you to use `obj` as an iterable. But more importantly,
it still gives you access to attributes defined on the original unknown type (`.description`, in this
example):

```py
def print_content(data: bytes):
    obj = untyped_library.deserialize(data)

    if isinstance(obj, Iterable):
        print(obj.description)
        for part in obj:
            print("*", part.description)
    else:
        print(obj.description)
```

(Full example in the [playground](https://play.ty.dev/8f98820e-7306-4d69-b572-56d69a92b90f))

Intersection types are also used in `hasattr` narrowing. Take a look at the following example where
we narrow a type of `Person | Animal | None` using `hasattr(…, "name")`. `Person` is preserved in
the narrowed union type because it has a `name` attribute. `Animal` is intersected with a synthetic
protocol, accounting for the possibility of subclasses of `Animal` that add a `name` member.
`None` is excluded completely since it is a final type that has no `name` attribute:

```py
class Person:
    name: str

class Animal:
    species: str

def greet(being: Person | Animal | None):
    if hasattr(being, "name"):
        # `being` is now of type `Person | (Animal & <Protocol with members 'name'>)`

        print(f"Hello, {being.name}!")
    else:
        print("Hello there!")
```

(Full example in the [playground](https://play.ty.dev/31f2c718-516a-4a85-80e0-2a4682b818f1))

!!! info

    If you run into a situation like this and would like `Animal` to be excluded from the narrowed
    type as well, you can make `Animal` a `@final` class. This also allows ty to infer a more precise
    type for `being.name` (`str` instead of `object`).

If ty is the only type checker you use, you can also make direct use of intersection types in
annotations by importing `Intersection` from the special `ty_extensions` module that is (currently)
only available at type-checking time:

```py
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from ty_extensions import Intersection

    type SerializableVersioned = Intersection[Serializable, Versioned]

def output_as_json(obj: SerializableVersioned) -> str:
    ...
```

(Full example in the [playground](https://play.ty.dev/f003e901-0e45-4f45-9759-d6db9d5e5f66))

## Top and bottom materializations

Gradual types generally have two special [materializations]. The top materialization represents the
"largest" type that a gradual type can materialize to: the union of all possible materializations.
For example, the top materialization of `Any` is `object`, and the top materialization of
`Any & int` is `int`. For invariant generic classes, the top materialization cannot be expressed in
Python's type system, but it is a useful type that ty intersects with when `isinstance` checks
involve generic classes. For example, when checking `isinstance(…, list)`, ty intersects with the
top materialization of `list[Unknown]`:

```py
@final
class Item: ...

def process(items: Item | list[Item]):
    if isinstance(items, list):
        # reveals: list[Item]
        reveal_type(items)
```

(Full example in the [playground](https://play.ty.dev/f1306120-0b8d-4ed5-b832-1f2d379eae2b))

!!! info

    You might wonder why `Item` is declared `@final` here. If we remove the `@final` decorator, the
    inferred type in the `if` branch becomes `(Item & Top[list[Unknown]]) | list[Item]` instead.
    This accounts for the possibility of classes that inherit from both `Item` *and* `list`! If
    you run into this situation and want to rule out this case, you can also perform the `isinstance`
    check against `Item` instead. The `else` branch will then have a narrowed type of
    `list[Item] & ~Item`, which effectively acts like `list[Item]`.

## Reachability based on types

Reachability analysis in ty is based on type inference. This allows ty to detect unreachable
branches in many more situations compared to approaches which match on a few known patterns (e.g.
`sys.version_info >= (3, 10)` checks). This has useful practical applications. Consider a case
where you are writing code that needs to be compatible with two major versions of a dependency.
The following code can be successfully type-checked with either pydantic 1.x installed, or pydantic
2.x installed. In both cases, ty will only consider the corresponding branch to be reachable, and
will not emit any type errors for the other branch. This works because
`pydantic.__version__.startswith("2.")` can be evaluated to `True` or `False` at type-checking time:

```py
import pydantic
from pydantic import BaseModel

PYDANTIC_V2 = pydantic.__version__.startswith("2.")

class Person(BaseModel):
    name: str

def to_json(person: Person):
    if PYDANTIC_V2:
        return person.model_dump_json()  # no error here when checking with 1.x
    else:
        return person.json()
```

(Full example in the [playground](https://play.ty.dev/34a227bb-93d5-405e-86c3-72f57ec5642e))

[materializations]: https://typing.python.org/en/latest/spec/concepts.html#materialization
[python typing documentation]: https://typing.python.org/en/latest/spec/index.html


## /docs/index.md

# ty

An extremely fast Python type checker and language server, written in Rust.

<p align="center">
  <img alt="Shows a bar chart with benchmark results." width="500px" src="./assets/ty-benchmark-cli-light.svg#only-light">
</p>

<p align="center">
  <img alt="Shows a bar chart with benchmark results." width="500px" src="./assets/ty-benchmark-cli.svg#only-dark">
</p>

<p align="center">
  <i>Type checking the <a href="https://github.com/home-assistant/core">home-assistant</a> project without caching.</i>
</p>

ty is backed by [Astral](https://astral.sh), the creators of
[uv](https://github.com/astral-sh/uv) and [Ruff](https://github.com/astral-sh/ruff).

## Highlights

- 10x - 100x faster than mypy and Pyright
- Comprehensive [diagnostics](./features/diagnostics.md) with rich contextual information
- Configurable [rule levels](./rules.md), [per-file overrides](./reference/configuration.md#overrides), [suppression comments](./suppression.md), and first-class project support
- Designed for adoption, with support for [redeclarations](./features/type-system.md#redeclarations) and [partially typed code](./features/type-system.md#gradual-guarantee)
- [Language server](./features/language-server.md) with code navigation, completions, code actions, auto-import, inlay hints, on-hover help, etc.
- Fine-grained [incremental analysis](./features/language-server.md#fine-grained-incrementality) designed for fast updates when editing files in an IDE
- Editor integrations for [VS Code](./editors.md#vs-code), [PyCharm](./editors.md#pycharm), [Neovim](./editors.md#neovim) and more
- Advanced typing features like first-class [intersection types](./features/type-system.md#intersection-types), advanced [type narrowing](./features/type-system.md#top-and-bottom-materializations), and
    [sophisticated reachability analysis](./features/type-system.md#reachability-based-on-types)

## Getting started

Run ty with [uvx](https://docs.astral.sh/uv/guides/tools/#running-tools) to get started quickly:

```shell
uvx ty check
```

ty will check all Python files in the working directory or project by default.

See the [type checking](./type-checking.md) documentation for more details.

## Installation

To install ty, see the [installation](./installation.md) documentation.

To add the ty language server to your editor, see the [editor integration](./editors.md) guide.

## Playground

ty has an [online playground](https://play.ty.dev) you can use to try it out on snippets or small
projects.

!!! tip

    The playground is a great way to share snippets with other people, e.g., when sharing a bug
    report.


## /docs/installation.md

# Installing ty

## Running ty without installation

Use [uvx](https://docs.astral.sh/uv/guides/tools/) to quickly get started with ty:

```shell
uvx ty
```

## Installation methods

### Adding ty to your project

!!! tip

    Adding ty as a dependency ensures that all developers on the project are using the same version
    of ty.

Use [uv](https://github.com/astral-sh/uv) (or your project manager of choice) to add ty as a
development dependency:

```shell
uv add --dev ty
```

Then, use `uv run` to invoke ty:

```shell
uv run ty
```

To update ty, use `--upgrade-package`:

```shell
uv lock --upgrade-package ty
```

### Installing globally with uv

Install ty globally with uv:

```shell
uv tool install ty@latest
```

To update ty, use `uv tool upgrade`:

```shell
uv tool upgrade ty
```

### Installing with the standalone installer

ty includes a standalone installer.

=== "macOS and Linux"

    Use `curl` to download the script and execute it with `sh`:

    ```console
    $ curl -LsSf https://astral.sh/ty/install.sh | sh
    ```

    If your system doesn't have `curl`, you can use `wget`:

    ```console
    $ wget -qO- https://astral.sh/ty/install.sh | sh
    ```

    Request a specific version by including it in the URL:

    ```console
    $ curl -LsSf https://astral.sh/ty/0.0.48/install.sh | sh
    ```

=== "Windows"

    Use `irm` to download the script and execute it with `iex`:

    ```pwsh-session
    PS> powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/ty/install.ps1 | iex"
    ```

    Changing the [execution policy](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_execution_policies?view=powershell-7.4#powershell-execution-policies) allows running a script from the internet.

    Request a specific version by including it in the URL:

    ```pwsh-session
    PS> powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/ty/0.0.48/install.ps1 | iex"
    ```

!!! tip

    The installation script may be inspected before use:

    === "macOS and Linux"

        ```console
        $ curl -LsSf https://astral.sh/ty/install.sh | less
        ```

    === "Windows"

        ```pwsh-session
        PS> powershell -c "irm https://astral.sh/ty/install.ps1 | more"
        ```

    Alternatively, the installer or binaries can be downloaded directly from [GitHub](#installing-from-github-releases).

### Installing from GitHub Releases

ty release artifacts can be downloaded directly from
[GitHub Releases](https://github.com/astral-sh/ty/releases).

Each release page includes binaries for all supported platforms as well as instructions for using
the standalone installer via `github.com` instead of `astral.sh`.

### Installing globally with pipx

Install ty globally with pipx:

```shell
pipx install ty
```

To update ty, use `pipx upgrade`:

```shell
pipx upgrade ty
```

### Installing with pip

Install ty into your current Python environment with pip:

```shell
pip install ty
```

### Installing globally with mise

Install ty globally with with [mise](https://github.com/jdx/mise):

```shell
mise install ty
```

To set it globally:

```shell
mise use --global ty
```

### Installing in Docker

Install ty in Docker by copying the binary from the official image:

```dockerfile title="Dockerfile"
COPY --from=ghcr.io/astral-sh/ty:latest /ty /bin/
```

The following tags are available:

- `ghcr.io/astral-sh/ty:latest`
- `ghcr.io/astral-sh/ty:{major}.{minor}.{patch}`, e.g., `ghcr.io/astral-sh/ty:0.0.48`
- `ghcr.io/astral-sh/ty:{major}.{minor}`, e.g., `ghcr.io/astral-sh/ty:0.0` (the latest patch
    version)

### Using ty with Bazel

[`aspect_rules_lint`](https://registry.bazel.build/docs/aspect_rules_lint#function-lint_ty_aspect)
provides a Bazel lint aspect that runs ty. See its documentation for setup instructions.

## Adding ty to your editor

See the [editor integration](./editors.md) guide to add ty to your editor.

## Shell autocompletion

!!! tip

    You can run `echo $SHELL` to help you determine your shell.

To enable shell autocompletion for ty commands, run one of the following:

=== "Bash"

    ```bash
    echo 'eval "$(ty generate-shell-completion bash)"' >> ~/.bashrc
    ```

=== "Zsh"

    ```bash
    echo 'eval "$(ty generate-shell-completion zsh)"' >> ~/.zshrc
    ```

=== "fish"

    ```bash
    echo 'ty generate-shell-completion fish | source' > ~/.config/fish/completions/ty.fish
    ```

=== "Elvish"

    ```bash
    echo 'eval (ty generate-shell-completion elvish | slurp)' >> ~/.elvish/rc.elv
    ```

=== "PowerShell / pwsh"

    ```powershell
    if (!(Test-Path -Path $PROFILE)) {
      New-Item -ItemType File -Path $PROFILE -Force
    }
    Add-Content -Path $PROFILE -Value '(& ty generate-shell-completion powershell) | Out-String | Invoke-Expression'
    ```

Then restart the shell or source the shell config file.


## /docs/js/extra.js

```js path="/docs/js/extra.js" 
function cleanupClipboardText(targetSelector) {
  const targetElement = document.querySelector(targetSelector);

  // exclude "Generic Prompt" and "Generic Output" spans from copy
  const excludedClasses = ["gp", "go"];

  const clipboardText = Array.from(targetElement.childNodes)
    .filter(
      (node) =>
        !excludedClasses.some((className) =>
          node?.classList?.contains(className)
        )
    )
    .map((node) => node.textContent)
    .filter((s) => s != "");
  return clipboardText.join("").trim();
}

// Sets copy text to attributes lazily using an Intersection Observer.
function setCopyText() {
  // The `data-clipboard-text` attribute allows for customized content in the copy
  // See: https://www.npmjs.com/package/clipboard#copy-text-from-attribute
  const attr = "clipboardText";
  // all "copy" buttons whose target selector is a <code> element
  const elements = document.querySelectorAll(
    'button[data-clipboard-target$="code"]'
  );
  const observer = new IntersectionObserver((entries) => {
    entries.forEach((entry) => {
      // target in the viewport that have not been patched
      if (
        entry.intersectionRatio > 0 &&
        entry.target.dataset[attr] === undefined
      ) {
        entry.target.dataset[attr] = cleanupClipboardText(
          entry.target.dataset.clipboardTarget
        );
      }
    });
  });

  elements.forEach((elt) => {
    observer.observe(elt);
  });
}

// Using the document$ observable is particularly important if you are using instant loading since
// it will not result in a page refresh in the browser
// See `How to integrate with third-party JavaScript libraries` guideline:
// https://squidfunk.github.io/mkdocs-material/customization/?h=javascript#additional-javascript
document$.subscribe(function () {
  setCopyText();
});

// Use client-side redirects for anchors that have moved.
// Other redirects should use `redirect_maps` in the `mkdocs.yml` file instead.
(function () {
  // (there are no redirects yet)
  let redirect_maps = {};

  // The prefix for the site, see `site_dir` in `mkdocs.yml`
  let site_dir = "ty";

  function get_path() {
    var path = window.location.pathname;

    // Trim the site prefix
    if (path.startsWith("/" + site_dir + "/")) {
      path = path.slice(site_dir.length + 2);
    }

    // Always include a trailing `/`
    if (!path.endsWith("/")) {
      path = path + "/";
    }

    // Check for an anchor
    var anchor = window.location.hash.substring(1);
    if (!anchor) {
      return path;
    }

    return path + "#" + anchor;
  }

  let path = get_path();
  if (path && redirect_maps.hasOwnProperty(path)) {
    window.location.replace("/" + site_dir + "/" + redirect_maps[path]);
  }
})();

```

## /docs/modules.md

# Module discovery

## First-party modules

First-party modules are Python files that are part of your project source code.

By default, ty searches for first-party modules in the project's root directory or the `src`
directory, if present.

If your project uses a different layout, configure the project's
[`environment.root`](./reference/configuration.md#root) in your `pyproject.toml` or `ty.toml`. For example,
if your project's code is in an `app/` directory:

```text
example-pkg
├── README.md
├── pyproject.toml
└── app
    └── example_pkg
        └── __init__.py
```

then set [`environment.root`](./reference/configuration.md#root) in your `pyproject.toml` to `["./app"]`:

=== "pyproject.toml"

    ```toml
    [tool.ty.environment]
    root = ["./app"]
    ```

=== "ty.toml"

    ```toml
    [environment]
    root = ["./app"]
    ```

Note that a `./python` folder is automatically added to the project `root` if it exists,
and is not itself a package (i.e. does not contain an `__init__.py` file or an
`__init__.pyi` file).

## Third-party modules

Third-party modules are Python packages that are not part of your project or the standard library.
These are usually declared as dependencies in a `pyproject.toml` or `requirements.txt` file
and installed using a package manager like uv or pip. Examples of popular third-party
modules are `requests`, `numpy` and `django`.

ty searches for third-party modules in the configured [Python environment](#python-environment).

## Python environment

The Python environment is used for discovery of third-party modules.

By default, ty will attempt to discover a virtual environment.

First, ty checks for an active virtual environment using the `VIRTUAL_ENV` environment variable. If
not set, ty will search for a `.venv` directory in the project root or working directory. ty only
supports discovery of virtual environments at this time.

!!! note

    When using project management tools, such as uv or Poetry, the `run` command usually automatically
    activates the virtual environment and will be detected by ty.

The Python environment may be explicitly configured using the
[`environment.python`](./reference/configuration.md#python) setting or
[`--python`](./reference/cli.md#ty-check--python) flag.

When setting the environment explicitly, non-virtual environments can be provided.


## /docs/python-version.md

# Python version

The Python version affects allowed syntax, type definitions of the standard library, and type
definitions of first- and third-party modules that are conditional on the Python version.

For example, Python 3.10 introduced support for `match` statements and added the
`sys.stdlib_module_names` symbol to the standard library. Syntactic features always
need to be available in the lowest supported Python version, but symbols may be used
in a `sys.version_info` conditional branch:

```python
import sys

# `invalid-syntax` error if `python-version` is set to 3.9 or lower:
match "echo hello".split():
    case ["echo", message]:
        print(message)
    case _:
        print("unknown command")

# `unresolved-attribute` error if `python-version` is set to 3.9 or lower:
print(sys.stdlib_module_names)

if sys.version_info >= (3, 10):
    # ok, because the usage is guarded by a version check:
    print(sys.stdlib_module_names)
```

By default, the lower bound of the project's [`requires-python`](https://packaging.python.org/en/latest/guides/writing-pyproject-toml/#python-requires) field (from the `pyproject.toml`) is
used as the target Python version, ensuring that features and symbols only available in newer Python
versions are not used.

If the `requires-python` field is not available but a virtual environment *has* been
configured or detected, ty will try to infer the Python version being used from the virtual
environment's metadata.

If no virtual environment is present or inferring the Python version from the metadata fails,
ty will fall back to the latest stable Python version supported by ty (currently 3.14).

The Python version may also be explicitly specified using the
[`python-version`](./reference/configuration.md#python-version) setting or the
[`--python-version`](./reference/cli.md#ty-check--python-version) flag.


## /docs/reference/cli.md

<!-- WARNING: This file is auto-generated (cargo dev generate-all). Edit the doc comments in 'crates/ty/src/args.rs' if you want to change anything here. -->

# CLI Reference

## ty

An extremely fast Python type checker.

<h3 class="cli-reference">Usage</h3>

```
ty <COMMAND>
```

<h3 class="cli-reference">Commands</h3>

<dl class="cli-reference"><dt><a href="#ty-check"><code>ty check</code></a></dt><dd><p>Check a project for type errors</p></dd>
<dt><a href="#ty-server"><code>ty server</code></a></dt><dd><p>Start the language server</p></dd>
<dt><a href="#ty-version"><code>ty version</code></a></dt><dd><p>Display ty's version</p></dd>
<dt><a href="#ty-explain"><code>ty explain</code></a></dt><dd><p>Explain rules and other parts of ty</p></dd>
<dt><a href="#ty-help"><code>ty help</code></a></dt><dd><p>Print this message or the help of the given subcommand(s)</p></dd>
</dl>

## ty check

Check a project for type errors

<h3 class="cli-reference">Usage</h3>

```
ty check [OPTIONS] [PATH]...
```

<h3 class="cli-reference">Arguments</h3>

<dl class="cli-reference"><dt id="ty-check--paths"><a href="#ty-check--paths"><code>PATHS</code></a></dt><dd><p>List of files or directories to check [default: the project root]</p>
</dd></dl>

<h3 class="cli-reference">Options</h3>

<dl class="cli-reference"><dt id="ty-check--add-ignore"><a href="#ty-check--add-ignore"><code>--add-ignore</code></a></dt><dd><p>Adds <code>ty: ignore</code> comments to suppress all rule diagnostics</p>
</dd><dt id="ty-check--color"><a href="#ty-check--color"><code>--color</code></a> <i>when</i></dt><dd><p>Control when colored output is used</p>
<p>Possible values:</p>
<ul>
<li><code>auto</code>:  Display colors if the output goes to an interactive terminal</li>
<li><code>always</code>:  Always display colors</li>
<li><code>never</code>:  Never display colors</li>
</ul></dd><dt id="ty-check--config"><a href="#ty-check--config"><code>--config</code></a>, <code>-c</code> <i>config-option</i></dt><dd><p>A TOML <code>&lt;KEY&gt; = &lt;VALUE&gt;</code> pair (such as you might find in a <code>ty.toml</code> configuration file)
overriding a specific configuration option.</p>
<p>Overrides of individual settings using this option always take precedence
over all configuration files.</p>
</dd><dt id="ty-check--config-file"><a href="#ty-check--config-file"><code>--config-file</code></a> <i>path</i></dt><dd><p>The path to a <code>ty.toml</code> file to use for configuration.</p>
<p>While ty configuration can be included in a <code>pyproject.toml</code> file, it is not allowed in this context.</p>
<p>May also be set with the <code>TY_CONFIG_FILE</code> environment variable.</p></dd><dt id="ty-check--error"><a href="#ty-check--error"><code>--error</code></a> <i>rule</i></dt><dd><p>Treat the given rule as having severity 'error'. Can be specified multiple times. Use 'all' to apply to all rules.</p>
</dd><dt id="ty-check--error-on-warning"><a href="#ty-check--error-on-warning"><code>--error-on-warning</code></a></dt><dd><p>Use exit code 1 if there are any warning-level diagnostics</p>
</dd><dt id="ty-check--exclude"><a href="#ty-check--exclude"><code>--exclude</code></a> <i>exclude</i></dt><dd><p>Glob patterns for files to exclude from type checking.</p>
<p>Uses gitignore-style syntax to exclude files and directories from type checking. Supports patterns like <code>tests/</code>, <code>*.tmp</code>, <code>**/__pycache__/**</code>.</p>
</dd><dt id="ty-check--exit-zero"><a href="#ty-check--exit-zero"><code>--exit-zero</code></a></dt><dd><p>Always use exit code 0, even when there are error-level diagnostics</p>
</dd><dt id="ty-check--extra-search-path"><a href="#ty-check--extra-search-path"><code>--extra-search-path</code></a> <i>path</i></dt><dd><p>Additional path to use as a module-resolution source (can be passed multiple times).</p>
<p>This is an advanced option that should usually only be used for first-party or third-party modules that are not installed into your Python environment in a conventional way. Use <code>--python</code> to point ty to your Python environment if it is in an unusual location.</p>
</dd><dt id="ty-check--fix"><a href="#ty-check--fix"><code>--fix</code></a></dt><dd><p>Apply fixes to resolve errors</p>
</dd><dt id="ty-check--force-exclude"><a href="#ty-check--force-exclude"><code>--force-exclude</code></a></dt><dd><p>Enforce exclusions, even for paths passed to ty directly on the command-line. Use <code>--no-force-exclude</code> to disable</p>
</dd><dt id="ty-check--help"><a href="#ty-check--help"><code>--help</code></a>, <code>-h</code></dt><dd><p>Print help (see a summary with '-h')</p>
</dd><dt id="ty-check--ignore"><a href="#ty-check--ignore"><code>--ignore</code></a> <i>rule</i></dt><dd><p>Disables the rule. Can be specified multiple times. Use 'all' to apply to all rules.</p>
</dd><dt id="ty-check--no-progress"><a href="#ty-check--no-progress"><code>--no-progress</code></a></dt><dd><p>Hide all progress outputs.</p>
<p>For example, spinners or progress bars.</p>
</dd><dt id="ty-check--output-format"><a href="#ty-check--output-format"><code>--output-format</code></a> <i>output-format</i></dt><dd><p>The format to use for printing diagnostic messages</p>
<p>May also be set with the <code>TY_OUTPUT_FORMAT</code> environment variable.</p><p>Possible values:</p>
<ul>
<li><code>full</code>:  Print diagnostics verbosely, with context and helpful hints (default)</li>
<li><code>concise</code>:  Print diagnostics concisely, one per line</li>
<li><code>gitlab</code>:  Print diagnostics in the JSON format expected by GitLab Code Quality reports</li>
<li><code>github</code>:  Print diagnostics in the format used by GitHub Actions workflow error annotations</li>
<li><code>junit</code>:  Print diagnostics as a JUnit-style XML report</li>
</ul></dd><dt id="ty-check--project"><a href="#ty-check--project"><code>--project</code></a> <i>project</i></dt><dd><p>Run the command within the given project directory.</p>
<p>All <code>pyproject.toml</code> files will be discovered by walking up the directory tree from the given project directory, as will the project's virtual environment (<code>.venv</code>) unless the <code>venv-path</code> option is set.</p>
<p>Other command-line arguments (such as relative paths) will be resolved relative to the current working directory.</p>
</dd><dt id="ty-check--python"><a href="#ty-check--python"><code>--python</code></a>, <code>--venv</code> <i>path</i></dt><dd><p>Path to your project's Python environment or interpreter.</p>
<p>ty uses your Python environment to resolve third-party imports in your code.</p>
<p>This can be a path to:</p>
<ul>
<li>A Python interpreter, e.g. <code>.venv/bin/python3</code> - A virtual environment directory, e.g. <code>.venv</code> - A system Python <a href="https://docs.python.org/3/library/sys.html#sys.prefix"><code>sys.prefix</code></a> directory, e.g. <code>/usr</code></li>
</ul>
<p>If you're using a project management tool such as uv or you have an activated Conda or virtual environment, you should not generally need to specify this option.</p>

</dd><dt id="ty-check--python-platform"><a href="#ty-check--python-platform"><code>--python-platform</code></a>, <code>--platform</code> <i>platform</i></dt><dd><p>Target platform to assume when resolving types.</p>
<p>This is used to specialize the type of <code>sys.platform</code> and will affect the visibility of platform-specific functions and attributes. If the value is set to <code>all</code>, no assumptions are made about the target platform. If unspecified, the current system's platform will be used.</p>
</dd><dt id="ty-check--python-version"><a href="#ty-check--python-version"><code>--python-version</code></a>, <code>--target-version</code> <i>version</i></dt><dd><p>Python version to assume when resolving types.</p>
<p>The Python version affects allowed syntax, type definitions of the standard library, and type definitions of first- and third-party modules that are conditional on the Python version.</p>
<p>If a version is not specified on the command line or in a configuration file, ty will try the following techniques in order of preference to determine a value: 1. Check for the <code>project.requires-python</code> setting in a <code>pyproject.toml</code> file and use the minimum version from the specified range 2. Check for an activated or configured Python environment and attempt to infer the Python version of that environment 3. Fall back to the latest stable Python version supported by ty (see <code>ty check --help</code> output)</p>
<p>Possible values:</p>
<ul>
<li><code>3.7</code></li>
<li><code>3.8</code></li>
<li><code>3.9</code></li>
<li><code>3.10</code></li>
<li><code>3.11</code></li>
<li><code>3.12</code></li>
<li><code>3.13</code></li>
<li><code>3.14</code></li>
<li><code>3.15</code></li>
</ul></dd><dt id="ty-check--quiet"><a href="#ty-check--quiet"><code>--quiet</code></a>, <code>-q</code></dt><dd><p>Use quiet output (or <code>-qq</code> for silent output)</p>
</dd><dt id="ty-check--respect-ignore-files"><a href="#ty-check--respect-ignore-files"><code>--respect-ignore-files</code></a></dt><dd><p>Respect file exclusions via <code>.gitignore</code> and other standard ignore files. Use <code>--no-respect-ignore-files</code> to disable</p>
</dd><dt id="ty-check--typeshed"><a href="#ty-check--typeshed"><code>--typeshed</code></a>, <code>--custom-typeshed-dir</code> <i>path</i></dt><dd><p>Custom directory to use for stdlib typeshed stubs</p>
</dd><dt id="ty-check--verbose"><a href="#ty-check--verbose"><code>--verbose</code></a>, <code>-v</code></dt><dd><p>Use verbose output (or <code>-vv</code> and <code>-vvv</code> for more verbose output)</p>
</dd><dt id="ty-check--warn"><a href="#ty-check--warn"><code>--warn</code></a> <i>rule</i></dt><dd><p>Treat the given rule as having severity 'warn'. Can be specified multiple times. Use 'all' to apply to all rules.</p>
</dd><dt id="ty-check--watch"><a href="#ty-check--watch"><code>--watch</code></a>, <code>-W</code></dt><dd><p>Watch files for changes and recheck files related to the changed files</p>
</dd></dl>

## ty server

Start the language server

<h3 class="cli-reference">Usage</h3>

```
ty server
```

<h3 class="cli-reference">Options</h3>

<dl class="cli-reference"><dt id="ty-server--help"><a href="#ty-server--help"><code>--help</code></a>, <code>-h</code></dt><dd><p>Print help</p>
</dd></dl>

## ty version

Display ty's version

<h3 class="cli-reference">Usage</h3>

```
ty version [OPTIONS]
```

<h3 class="cli-reference">Options</h3>

<dl class="cli-reference"><dt id="ty-version--help"><a href="#ty-version--help"><code>--help</code></a>, <code>-h</code></dt><dd><p>Print help</p>
</dd><dt id="ty-version--output-format"><a href="#ty-version--output-format"><code>--output-format</code></a> <i>output-format</i></dt><dd><p>The format in which to display the version information</p>
<p>[default: text]</p><p>Possible values:</p>
<ul>
<li><code>text</code></li>
<li><code>json</code></li>
</ul></dd></dl>

## ty generate-shell-completion

Generate shell completion

<h3 class="cli-reference">Usage</h3>

```
ty generate-shell-completion <SHELL>
```

<h3 class="cli-reference">Arguments</h3>

<dl class="cli-reference"><dt id="ty-generate-shell-completion--shell"><a href="#ty-generate-shell-completion--shell"><code>SHELL</code></a></dt></dl>

<h3 class="cli-reference">Options</h3>

<dl class="cli-reference"><dt id="ty-generate-shell-completion--help"><a href="#ty-generate-shell-completion--help"><code>--help</code></a>, <code>-h</code></dt><dd><p>Print help</p>
</dd></dl>

## ty explain

Explain rules and other parts of ty

<h3 class="cli-reference">Usage</h3>

```
ty explain <COMMAND>
```

<h3 class="cli-reference">Commands</h3>

<dl class="cli-reference"><dt><a href="#ty-explain-rule"><code>ty explain rule</code></a></dt><dd><p>Explain a rule (or all rules)</p></dd>
<dt><a href="#ty-explain-help"><code>ty explain help</code></a></dt><dd><p>Print this message or the help of the given subcommand(s)</p></dd>
</dl>

### ty explain rule

Explain a rule (or all rules)

<h3 class="cli-reference">Usage</h3>

```
ty explain rule [OPTIONS] [RULE]
```

<h3 class="cli-reference">Arguments</h3>

<dl class="cli-reference"><dt id="ty-explain-rule--rule"><a href="#ty-explain-rule--rule"><code>RULE</code></a></dt><dd><p>Rule to explain</p>
<p>Defaults to all rules if omitted.</p>
</dd></dl>

<h3 class="cli-reference">Options</h3>

<dl class="cli-reference"><dt id="ty-explain-rule--help"><a href="#ty-explain-rule--help"><code>--help</code></a>, <code>-h</code></dt><dd><p>Print help (see a summary with '-h')</p>
</dd><dt id="ty-explain-rule--output-format"><a href="#ty-explain-rule--output-format"><code>--output-format</code></a> <i>output-format</i></dt><dd><p>Output format</p>
<p>[default: text]</p><p>Possible values:</p>
<ul>
<li><code>text</code></li>
<li><code>json</code></li>
</ul></dd></dl>

### ty explain help

Print this message or the help of the given subcommand(s)

<h3 class="cli-reference">Usage</h3>

```
ty explain help [COMMAND]
```

## ty help

Print this message or the help of the given subcommand(s)

<h3 class="cli-reference">Usage</h3>

```
ty help [COMMAND]
```



## /docs/reference/configuration.md

<!-- WARNING: This file is auto-generated (cargo dev generate-all). Update the doc comments on the 'Options' struct in 'crates/ty_project/src/metadata/options.rs' if you want to change anything here. -->

# Configuration
## `rules`

Configures the enabled rules and their severity.

The keys are either rule names or `all` to set a default severity for all rules.
See [the rules documentation](https://ty.dev/rules) for a list of all available rules.

Valid severities are:

* `ignore`: Disable the rule.
* `warn`: Enable the rule and create a warning diagnostic.
* `error`: Enable the rule and create an error diagnostic.
  ty will exit with a non-zero code if any error diagnostics are emitted.

**Default value**: `{...}`

**Type**: `dict[RuleName | "all", "ignore" | "warn" | "error"]`

**Example usage**:

=== "pyproject.toml"

    ```toml
    [tool.ty.rules]
    possibly-unresolved-reference = "warn"
    division-by-zero = "ignore"
    ```

=== "ty.toml"

    ```toml
    [rules]
    possibly-unresolved-reference = "warn"
    division-by-zero = "ignore"
    ```

---

## `analysis`

### `allowed-unresolved-imports`

A list of module glob patterns for which `unresolved-import` diagnostics should be suppressed.

Details on supported glob patterns:
- `*` matches zero or more characters except `.`. For example, `foo.*` matches `foo.bar` but
  not `foo.bar.baz`; `foo*` matches `foo` and `foobar` but not `foo.bar` or `barfoo`; and `*foo`
  matches `foo` and `barfoo` but not `foo.bar` or `foobar`.
- `**` matches any number of module components (e.g., `foo.**` matches `foo`, `foo.bar`, etc.)
- Prefix a pattern with `!` to exclude matching modules

When multiple patterns match, later entries take precedence.

Glob patterns can be used in combinations with each other. For example, to suppress errors for
any module where the first component contains the substring `test`, use `*test*.**`.

**Default value**: `[]`

**Type**: `list[str]`

**Example usage**:

=== "pyproject.toml"

    ```toml
    [tool.ty.analysis]
    # Suppress errors for all `test` modules except `test.foo`
    allowed-unresolved-imports = ["test.**", "!test.foo"]
    ```

=== "ty.toml"

    ```toml
    [analysis]
    # Suppress errors for all `test` modules except `test.foo`
    allowed-unresolved-imports = ["test.**", "!test.foo"]
    ```

---

### `replace-imports-with-any`

A list of module glob patterns whose imports should be replaced with `typing.Any`.

Unlike `allowed-unresolved-imports`, this setting replaces the module's type information
with `typing.Any` even if the module can be resolved. Import diagnostics are
unconditionally suppressed for matching modules.

- Prefix a pattern with `!` to exclude matching modules

When multiple patterns match, later entries take precedence.

Glob patterns can be used in combinations with each other. For example, to suppress errors for
any module where the first component contains the substring `test`, use `*test*.**`.

When multiple patterns match, later entries take precedence.

**Default value**: `[]`

**Type**: `list[str]`

**Example usage**:

=== "pyproject.toml"

    ```toml
    [tool.ty.analysis]
    # Replace all pandas and numpy imports with Any
    replace-imports-with-any = ["pandas.**", "numpy.**"]
    ```

=== "ty.toml"

    ```toml
    [analysis]
    # Replace all pandas and numpy imports with Any
    replace-imports-with-any = ["pandas.**", "numpy.**"]
    ```

---

### `respect-type-ignore-comments`

Whether ty should respect `type: ignore` comments.

When set to `false`, `type: ignore` comments are treated like any other normal
comment and can't be used to suppress ty errors (you have to use `ty: ignore` instead).

Setting this option can be useful when using ty alongside other type checkers or when
you prefer using `ty: ignore` over `type: ignore`.

Defaults to `true`.

**Default value**: `true`

**Type**: `bool`

**Example usage**:

=== "pyproject.toml"

    ```toml
    [tool.ty.analysis]
    # Disable support for `type: ignore` comments
    respect-type-ignore-comments = false
    ```

=== "ty.toml"

    ```toml
    [analysis]
    # Disable support for `type: ignore` comments
    respect-type-ignore-comments = false
    ```

---

## `environment`

### `extra-paths`

User-provided paths that should take first priority in module resolution.

This is an advanced option that should usually only be used for first-party or third-party
modules that are not installed into your Python environment in a conventional way.
Use the `python` option to specify the location of your Python environment.

This option is similar to mypy's `MYPYPATH` environment variable and pyright's `stubPath`
configuration setting.

**Default value**: `[]`

**Type**: `list[str]`

**Example usage**:

=== "pyproject.toml"

    ```toml
    [tool.ty.environment]
    extra-paths = ["./shared/my-search-path"]
    ```

=== "ty.toml"

    ```toml
    [environment]
    extra-paths = ["./shared/my-search-path"]
    ```

---

### `python`

Path to your project's Python environment or interpreter.

ty uses the `site-packages` directory of your project's Python environment
to resolve third-party (and, in some cases, first-party) imports in your code.

This can be a path to:

- A Python interpreter, e.g. `.venv/bin/python3`
- A virtual environment directory, e.g. `.venv`
- A system Python [`sys.prefix`] directory, e.g. `/usr`

If you're using a project management tool such as uv, you should not generally need to
specify this option, as commands such as `uv run` will set the `VIRTUAL_ENV` environment
variable to point to your project's virtual environment. ty can also infer the location of
your environment from an activated Conda environment, and will look for a `.venv` directory
in the project root if none of the above apply. Failing that, ty will look for a `python3`
or `python` binary available in `PATH`.

[`sys.prefix`]: https://docs.python.org/3/library/sys.html#sys.prefix

**Default value**: `null`

**Type**: `str`

**Example usage**:

=== "pyproject.toml"

    ```toml
    [tool.ty.environment]
    python = "./custom-venv-location/.venv"
    ```

=== "ty.toml"

    ```toml
    [environment]
    python = "./custom-venv-location/.venv"
    ```

---

### `python-platform`

Specifies the target platform that will be used to analyze the source code.
If specified, ty will understand conditions based on comparisons with `sys.platform`, such
as are commonly found in typeshed to reflect the differing contents of the standard library across platforms.
If `all` is specified, ty will assume that the source code can run on any platform.

If no platform is specified, ty will use the current platform:
- `win32` for Windows
- `darwin` for macOS
- `android` for Android
- `ios` for iOS
- `linux` for everything else

**Default value**: `<current-platform>`

**Type**: `"win32" | "darwin" | "android" | "ios" | "linux" | "all" | str`

**Example usage**:

=== "pyproject.toml"

    ```toml
    [tool.ty.environment]
    # Tailor type stubs and conditionalized type definitions to windows.
    python-platform = "win32"
    ```

=== "ty.toml"

    ```toml
    [environment]
    # Tailor type stubs and conditionalized type definitions to windows.
    python-platform = "win32"
    ```

---

### `python-version`

Specifies the version of Python that will be used to analyze the source code.
The version should be specified as a string in the format `M.m` where `M` is the major version
and `m` is the minor (e.g. `"3.7"` or `"3.12"`).
If a version is provided, ty will generate errors if the source code makes use of language features
that are not supported in that version.

If a version is not specified, ty will try the following techniques in order of preference
to determine a value:
1. Check for the `project.requires-python` setting in a `pyproject.toml` file
   and use the minimum version from the specified range
2. Check for an activated or configured Python environment
   and attempt to infer the Python version of that environment
3. Fall back to the default value (see below)

For some language features, ty can also understand conditionals based on comparisons
with `sys.version_info`. These are commonly found in typeshed, for example,
to reflect the differing contents of the standard library across Python versions.

**Default value**: `"3.14"`

**Type**: `"3.7" | "3.8" | "3.9" | "3.10" | "3.11" | "3.12" | "3.13" | "3.14" | "3.15"`

**Example usage**:

=== "pyproject.toml"

    ```toml
    [tool.ty.environment]
    python-version = "3.12"
    ```

=== "ty.toml"

    ```toml
    [environment]
    python-version = "3.12"
    ```

---

### `root`

The root paths of the project, used for finding first-party modules.

Accepts a list of directory paths searched in priority order (first has highest priority).

If left unspecified, ty will try to detect common project layouts and initialize `root` accordingly.
The project root (`.`) is always included. Additionally, the following directories are included
if they exist and are not packages (i.e. they do not contain `__init__.py` or `__init__.pyi` files):

* `./src`
* `./<project-name>` (if a `./<project-name>/<project-name>` directory exists)
* `./python`

**Default value**: `null`

**Type**: `list[str]`

**Example usage**:

=== "pyproject.toml"

    ```toml
    [tool.ty.environment]
    # Multiple directories (priority order)
    root = ["./src", "./lib", "./vendor"]
    ```

=== "ty.toml"

    ```toml
    [environment]
    # Multiple directories (priority order)
    root = ["./src", "./lib", "./vendor"]
    ```

---

### `typeshed`

Optional path to a "typeshed" directory on disk for us to use for standard-library types.
If this is not provided, we will fallback to our vendored typeshed stubs for the stdlib,
bundled as a zip file in the binary

**Default value**: `null`

**Type**: `str`

**Example usage**:

=== "pyproject.toml"

    ```toml
    [tool.ty.environment]
    typeshed = "/path/to/custom/typeshed"
    ```

=== "ty.toml"

    ```toml
    [environment]
    typeshed = "/path/to/custom/typeshed"
    ```

---

## `overrides`

Configuration override that applies to specific files based on glob patterns.

An override allows you to apply different rule configurations to specific
files or directories. Multiple overrides can match the same file, with
later overrides take precedence. Override rules take precedence over global
rules for matching files.

For example, to relax enforcement of rules in test files:

```toml
[[tool.ty.overrides]]
include = ["tests/**", "**/test_*.py"]

[tool.ty.overrides.rules]
possibly-unresolved-reference = "warn"
```

Or, to ignore a rule in generated files but retain enforcement in an important file:

```toml
[[tool.ty.overrides]]
include = ["generated/**"]
exclude = ["generated/important.py"]

[tool.ty.overrides.rules]
possibly-unresolved-reference = "ignore"
```


### `exclude`

A list of file and directory patterns to exclude from this override.

Patterns follow a syntax similar to `.gitignore`.
Exclude patterns take precedence over include patterns within the same override.

If not specified, defaults to `[]` (excludes no files).

**Default value**: `null`

**Type**: `list[str]`

**Example usage**:

=== "pyproject.toml"

    ```toml
    [[tool.ty.overrides]]
    exclude = [
        "generated",
        "*.proto",
        "tests/fixtures/**",
        "!tests/fixtures/important.py"  # Include this one file
    ]
    ```

=== "ty.toml"

    ```toml
    [[overrides]]
    exclude = [
        "generated",
        "*.proto",
        "tests/fixtures/**",
        "!tests/fixtures/important.py"  # Include this one file
    ]
    ```

---

### `include`

A list of file and directory patterns to include for this override.

The `include` option follows a similar syntax to `.gitignore` but reversed:
Including a file or directory will make it so that it (and its contents)
are affected by this override.

If not specified, defaults to `["**"]` (matches all files).

**Default value**: `null`

**Type**: `list[str]`

**Example usage**:

=== "pyproject.toml"

    ```toml
    [[tool.ty.overrides]]
    include = [
        "src",
        "tests",
    ]
    ```

=== "ty.toml"

    ```toml
    [[overrides]]
    include = [
        "src",
        "tests",
    ]
    ```

---

### `rules`

Rule overrides for files matching the include/exclude patterns.

These rules will be merged with the global rules, with override rules
taking precedence for matching files. You can set rules to different
severity levels or disable them entirely.

**Default value**: `{...}`

**Type**: `dict[RuleName | "all", "ignore" | "warn" | "error"]`

**Example usage**:

=== "pyproject.toml"

    ```toml
    [[tool.ty.overrides]]
    include = ["src"]

    [tool.ty.overrides.rules]
    possibly-unresolved-reference = "ignore"
    ```

=== "ty.toml"

    ```toml
    [[overrides]]
    include = ["src"]

    [overrides.rules]
    possibly-unresolved-reference = "ignore"
    ```

---

## `overrides.analysis`

#### `allowed-unresolved-imports`

A list of module glob patterns for which `unresolved-import` diagnostics should be suppressed.

Details on supported glob patterns:
- `*` matches zero or more characters except `.`. For example, `foo.*` matches `foo.bar` but
  not `foo.bar.baz`; `foo*` matches `foo` and `foobar` but not `foo.bar` or `barfoo`; and `*foo`
  matches `foo` and `barfoo` but not `foo.bar` or `foobar`.
- `**` matches any number of module components (e.g., `foo.**` matches `foo`, `foo.bar`, etc.)
- Prefix a pattern with `!` to exclude matching modules

When multiple patterns match, later entries take precedence.

Glob patterns can be used in combinations with each other. For example, to suppress errors for
any module where the first component contains the substring `test`, use `*test*.**`.

**Default value**: `[]`

**Type**: `list[str]`

**Example usage**:

=== "pyproject.toml"

    ```toml
    [tool.ty.overrides.analysis]
    # Suppress errors for all `test` modules except `test.foo`
    allowed-unresolved-imports = ["test.**", "!test.foo"]
    ```

=== "ty.toml"

    ```toml
    [overrides.analysis]
    # Suppress errors for all `test` modules except `test.foo`
    allowed-unresolved-imports = ["test.**", "!test.foo"]
    ```

---

#### `replace-imports-with-any`

A list of module glob patterns whose imports should be replaced with `typing.Any`.

Unlike `allowed-unresolved-imports`, this setting replaces the module's type information
with `typing.Any` even if the module can be resolved. Import diagnostics are
unconditionally suppressed for matching modules.

- Prefix a pattern with `!` to exclude matching modules

When multiple patterns match, later entries take precedence.

Glob patterns can be used in combinations with each other. For example, to suppress errors for
any module where the first component contains the substring `test`, use `*test*.**`.

When multiple patterns match, later entries take precedence.

**Default value**: `[]`

**Type**: `list[str]`

**Example usage**:

=== "pyproject.toml"

    ```toml
    [tool.ty.overrides.analysis]
    # Replace all pandas and numpy imports with Any
    replace-imports-with-any = ["pandas.**", "numpy.**"]
    ```

=== "ty.toml"

    ```toml
    [overrides.analysis]
    # Replace all pandas and numpy imports with Any
    replace-imports-with-any = ["pandas.**", "numpy.**"]
    ```

---

#### `respect-type-ignore-comments`

Whether ty should respect `type: ignore` comments.

When set to `false`, `type: ignore` comments are treated like any other normal
comment and can't be used to suppress ty errors (you have to use `ty: ignore` instead).

Setting this option can be useful when using ty alongside other type checkers or when
you prefer using `ty: ignore` over `type: ignore`.

Defaults to `true`.

**Default value**: `true`

**Type**: `bool`

**Example usage**:

=== "pyproject.toml"

    ```toml
    [tool.ty.overrides.analysis]
    # Disable support for `type: ignore` comments
    respect-type-ignore-comments = false
    ```

=== "ty.toml"

    ```toml
    [overrides.analysis]
    # Disable support for `type: ignore` comments
    respect-type-ignore-comments = false
    ```

---

## `src`

### `exclude`

A list of file and directory patterns to exclude from type checking.

Patterns follow a syntax similar to `.gitignore`:

- `./src/` matches only a directory
- `./src` matches both files and directories
- `src` matches files or directories named `src`
- `*` matches any (possibly empty) sequence of characters (except `/`).
- `**` matches zero or more path components.
  This sequence **must** form a single path component, so both `**a` and `b**` are invalid and will result in an error.
  A sequence of more than two consecutive `*` characters is also invalid.
- `?` matches any single character except `/`
- `[abc]` matches any character inside the brackets. Character sequences can also specify ranges of characters, as ordered by Unicode,
  so e.g. `[0-9]` specifies any character between `0` and `9` inclusive. An unclosed bracket is invalid.
- `!pattern` negates a pattern (undoes the exclusion of files that would otherwise be excluded)

All paths are anchored relative to the project root (`src` only
matches `<project_root>/src` and not `<project_root>/test/src`).
To exclude any directory or file named `src`, use `**/src` instead.

By default, ty excludes commonly ignored directories:

- `**/.bzr/`
- `**/.direnv/`
- `**/.eggs/`
- `**/.git/`
- `**/.git-rewrite/`
- `**/.hg/`
- `**/.mypy_cache/`
- `**/.nox/`
- `**/.pants.d/`
- `**/.pytype/`
- `**/.ruff_cache/`
- `**/.svn/`
- `**/.tox/`
- `**/.venv/`
- `**/__pypackages__/`
- `**/_build/`
- `**/buck-out/`
- `**/dist/`
- `**/node_modules/`
- `**/venv/`

You can override any default exclude by using a negated pattern. For example,
to re-include `dist` use `exclude = ["!dist"]`

**Default value**: `null`

**Type**: `list[str]`

**Example usage**:

=== "pyproject.toml"

    ```toml
    [tool.ty.src]
    exclude = [
        "generated",
        "*.proto",
        "tests/fixtures/**",
        "!tests/fixtures/important.py"  # Include this one file
    ]
    ```

=== "ty.toml"

    ```toml
    [src]
    exclude = [
        "generated",
        "*.proto",
        "tests/fixtures/**",
        "!tests/fixtures/important.py"  # Include this one file
    ]
    ```

---

### `include`

A list of files and directories to check. The `include` option
follows a similar syntax to `.gitignore` but reversed:
Including a file or directory will make it so that it (and its contents)
are type checked.

- `./src/` matches only a directory
- `./src` matches both files and directories
- `src` matches a file or directory named `src`
- `*` matches any (possibly empty) sequence of characters (except `/`).
- `**` matches zero or more path components.
  This sequence **must** form a single path component, so both `**a` and `b**` are invalid and will result in an error.
  A sequence of more than two consecutive `*` characters is also invalid.
- `?` matches any single character except `/`
- `[abc]` matches any character inside the brackets. Character sequences can also specify ranges of characters, as ordered by Unicode,
  so e.g. `[0-9]` specifies any character between `0` and `9` inclusive. An unclosed bracket is invalid.

All paths are anchored relative to the project root (`src` only
matches `<project_root>/src` and not `<project_root>/test/src`).

`exclude` takes precedence over `include`.

**Default value**: `null`

**Type**: `list[str]`

**Example usage**:

=== "pyproject.toml"

    ```toml
    [tool.ty.src]
    include = [
        "src",
        "tests",
    ]
    ```

=== "ty.toml"

    ```toml
    [src]
    include = [
        "src",
        "tests",
    ]
    ```

---

### `respect-ignore-files`

Whether to automatically exclude files that are ignored by `.ignore`,
`.gitignore`, `.git/info/exclude`, and global `gitignore` files.
Enabled by default.

**Default value**: `true`

**Type**: `bool`

**Example usage**:

=== "pyproject.toml"

    ```toml
    [tool.ty.src]
    respect-ignore-files = false
    ```

=== "ty.toml"

    ```toml
    [src]
    respect-ignore-files = false
    ```

---

### `root`

!!! warning "Deprecated"
    This option has been deprecated. Use `environment.root` instead.

The root of the project, used for finding first-party modules.

If left unspecified, ty will try to detect common project layouts and initialize `src.root` accordingly.
The project root (`.`) is always included. Additionally, the following directories are included
if they exist and are not packages (i.e. they do not contain `__init__.py` or `__init__.pyi` files):

* `./src`
* `./<project-name>` (if a `./<project-name>/<project-name>` directory exists)
* `./python`

**Default value**: `null`

**Type**: `str`

**Example usage**:

=== "pyproject.toml"

    ```toml
    [tool.ty.src]
    root = "./app"
    ```

=== "ty.toml"

    ```toml
    [src]
    root = "./app"
    ```

---

## `terminal`

### `error-on-warning`

Use exit code 1 if there are any warning-level diagnostics.

Defaults to `false`.

**Default value**: `false`

**Type**: `bool`

**Example usage**:

=== "pyproject.toml"

    ```toml
    [tool.ty.terminal]
    # Error if ty emits any warning-level diagnostics.
    error-on-warning = true
    ```

=== "ty.toml"

    ```toml
    [terminal]
    # Error if ty emits any warning-level diagnostics.
    error-on-warning = true
    ```

---

### `output-format`

The format to use for printing diagnostic messages.

Defaults to `full`.

**Default value**: `full`

**Type**: `full | concise | github | gitlab | junit`

**Example usage**:

=== "pyproject.toml"

    ```toml
    [tool.ty.terminal]
    output-format = "concise"
    ```

=== "ty.toml"

    ```toml
    [terminal]
    output-format = "concise"
    ```

---



## /docs/reference/environment.md

# Environment variables

ty defines and respects the following environment variables:

### `TY_CONFIG_FILE`

Path to a `ty.toml` configuration file to use.

When set, ty will use this file for configuration instead of
discovering configuration files automatically.

Equivalent to the `--config-file` command-line argument.

### `TY_LOG`

If set, ty will use this value as the log level for its `--verbose` output.
Accepts any filter compatible with the `tracing_subscriber` crate.

For example:

- `TY_LOG=ty=debug` is the equivalent of `-vv` to the command line
- `TY_LOG=trace` will enable all trace-level logging.

See the [tracing documentation](https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html#example-syntax)
for more.

### `TY_LOG_PROFILE`

If set to `"1"` or `"true"`, ty will enable flamegraph profiling.
This creates a `tracing.folded` file that can be used to generate flame graphs
for performance analysis.

### `TY_MAX_PARALLELISM`

Specifies an upper limit for the number of tasks ty is allowed to run in parallel.

For example, how many files should be checked in parallel.
This isn't the same as a thread limit. ty may spawn additional threads
when necessary, e.g. to watch for file system changes or a dedicated UI thread.

### `TY_OUTPUT_FORMAT`

The format to use for printing diagnostic messages.

When set, ty will use this format for output instead of the default.

Accepts the same values as the `--output-format` command-line argument.

## Externally-defined variables

ty also reads the following externally defined environment variables:

### `CONDA_DEFAULT_ENV`

Used to determine the name of the active Conda environment.

### `CONDA_PREFIX`

Used to detect the path of an active Conda environment.
If both `VIRTUAL_ENV` and `CONDA_PREFIX` are present, `VIRTUAL_ENV` will be preferred.

### `PYTHONPATH`

Adds additional directories to ty's search paths.
The format is the same as the shell’s PATH:
one or more directory pathnames separated by os appropriate pathsep
(e.g. colons on Unix or semicolons on Windows).

### `RAYON_NUM_THREADS`

Specifies an upper limit for the number of threads ty uses when performing work in parallel.
Equivalent to `TY_MAX_PARALLELISM`.

This is a standard Rayon environment variable.

### `VIRTUAL_ENV`

Used to detect an activated virtual environment.

### `XDG_CONFIG_HOME`

Path to user-level configuration directory on Unix systems.

### `_CONDA_ROOT`

Used to determine the root install path of Conda.



## /docs/reference/exit-codes.md

# Exit codes

The ty command line interface uses the following exit codes:

| Exit code | Description                                              |
| :-------- | :------------------------------------------------------- |
| `0`       | no violations with severity `error` or higher were found |
| `1`       | violations with severity `error` or higher were found    |
| `2`       | invalid CLI options, invalid configuration, or IO errors |
| `101`     | internal error                                           |

ty supports two command line arguments that change how exit codes work:

- `--exit-zero`: ty will exit with `0` even if violations were found.
- `--error-on-warning`: ty will exit with `1` if it finds any violations with severity `warning` or
    higher.


## /docs/requirements.in

```in path="/docs/requirements.in" 
black>=23.10.0
mkdocs>=1.5.0
mkdocs-material>=9.7.0
mkdocs-redirects>=1.2.1
mdformat>=0.7.17
mdformat-mkdocs>=2.0.4
mdformat-admon>=2.0.2
mkdocs-redirects>=1.2.2
mkdocs-git-revision-date-localized-plugin>=1.3.0
mkdocs-llmstxt>=0.2.0

```

## /docs/requirements.txt

# This file was autogenerated by uv via the following command:
#    uv pip compile docs/requirements.in -o docs/requirements.txt --universal -p 3.12
babel==2.15.0
    # via
    #   mkdocs-git-revision-date-localized-plugin
    #   mkdocs-material
backrefs==6.1
    # via mkdocs-material
beautifulsoup4==4.13.4
    # via
    #   markdownify
    #   mkdocs-llmstxt
black==24.4.2
    # via -r docs/requirements.in
certifi==2024.7.4
    # via requests
charset-normalizer==3.3.2
    # via requests
click==8.1.7
    # via
    #   black
    #   mkdocs
colorama==0.4.6
    # via
    #   click
    #   mkdocs
    #   mkdocs-material
ghp-import==2.1.0
    # via mkdocs
gitdb==4.0.12
    # via gitpython
gitpython==3.1.44
    # via mkdocs-git-revision-date-localized-plugin
idna==3.7
    # via requests
jinja2==3.1.4
    # via
    #   mkdocs
    #   mkdocs-material
linkify-it-py==2.0.3
    # via markdown-it-py
markdown==3.6
    # via
    #   mkdocs
    #   mkdocs-material
    #   pymdown-extensions
markdown-it-py==3.0.0
    # via
    #   mdformat
    #   mdformat-gfm
    #   mdit-py-plugins
markdownify==1.1.0
    # via mkdocs-llmstxt
markupsafe==2.1.5
    # via
    #   jinja2
    #   mkdocs
mdformat==0.7.22
    # via
    #   -r docs/requirements.in
    #   mdformat-admon
    #   mdformat-gfm
    #   mdformat-mkdocs
    #   mdformat-tables
    #   mkdocs-llmstxt
mdformat-admon==2.0.6
    # via
    #   -r docs/requirements.in
    #   mdformat-mkdocs
mdformat-gfm==0.3.6
    # via mdformat-mkdocs
mdformat-mkdocs==3.0.0
    # via -r docs/requirements.in
mdformat-tables==0.4.1
    # via mdformat-gfm
mdit-py-plugins==0.4.1
    # via
    #   mdformat-admon
    #   mdformat-gfm
    #   mdformat-mkdocs
mdurl==0.1.2
    # via markdown-it-py
mergedeep==1.3.4
    # via
    #   mkdocs
    #   mkdocs-get-deps
mkdocs==1.6.0
    # via
    #   -r docs/requirements.in
    #   mkdocs-git-revision-date-localized-plugin
    #   mkdocs-material
    #   mkdocs-redirects
mkdocs-get-deps==0.2.0
    # via mkdocs
mkdocs-git-revision-date-localized-plugin==1.3.0
    # via -r docs/requirements.in
mkdocs-llmstxt==0.2.0
    # via -r docs/requirements.in
mkdocs-material==9.7.0
    # via -r docs/requirements.in
mkdocs-material-extensions==1.3.1
    # via mkdocs-material
mkdocs-redirects==1.2.2
    # via -r docs/requirements.in
more-itertools==10.3.0
    # via mdformat-mkdocs
mypy-extensions==1.0.0
    # via black
packaging==24.1
    # via
    #   black
    #   mkdocs
paginate==0.5.6
    # via mkdocs-material
pathspec==0.12.1
    # via
    #   black
    #   mkdocs
platformdirs==4.2.2
    # via
    #   black
    #   mkdocs-get-deps
pygments==2.18.0
    # via mkdocs-material
pymdown-extensions==10.8.1
    # via mkdocs-material
python-dateutil==2.9.0.post0
    # via ghp-import
pytz==2025.1
    # via mkdocs-git-revision-date-localized-plugin
pyyaml==6.0.1
    # via
    #   mkdocs
    #   mkdocs-get-deps
    #   pymdown-extensions
    #   pyyaml-env-tag
pyyaml-env-tag==0.1
    # via mkdocs
requests==2.32.3
    # via mkdocs-material
six==1.16.0
    # via
    #   markdownify
    #   python-dateutil
smmap==5.0.2
    # via gitdb
soupsieve==2.7
    # via beautifulsoup4
typing-extensions==4.14.0
    # via beautifulsoup4
uc-micro-py==1.0.3
    # via linkify-it-py
urllib3==2.2.2
    # via requests
watchdog==4.0.1
    # via mkdocs


## /python/ty/__init__.py

```py path="/python/ty/__init__.py" 
from __future__ import annotations

from ._find_ty import find_ty_bin

__all__ = ["find_ty_bin"]

```

## /python/ty/py.typed

```typed path="/python/ty/py.typed" 


```


The content has been capped at 50000 tokens. The user could consider applying other filters to refine the result. The better and more specific the context, the better the LLM can follow instructions. If the context seems verbose, the user can refine the filter using uithub. Thank you for using https://uithub.com - Perfect LLM context for any GitHub repo.
Copied!