├── .editorconfig ├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ ├── 1_bug_report.yaml │ ├── 2_question.yaml │ └── config.yml ├── PULL_REQUEST_TEMPLATE.md ├── actionlint.yaml ├── renovate.json5 └── workflows │ ├── build-binaries.yml │ ├── build-docker.yml │ ├── ci.yaml │ ├── daily_property_tests.yml │ ├── publish-pypi.yml │ └── release.yml ├── .gitignore ├── .gitmodules ├── .markdownlint.yaml ├── .pre-commit-config.yaml ├── .python-version ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Dockerfile ├── LICENSE ├── README.md ├── SECURITY.md ├── _typos.toml ├── dist-workspace.toml ├── docs ├── README.md └── reference │ ├── cli.md │ ├── configuration.md │ ├── editor-settings.md │ ├── env.md │ └── rules.md ├── pyproject.toml ├── python └── ty │ ├── __init__.py │ ├── __main__.py │ └── py.typed ├── scripts ├── autogenerate_files.sh ├── release.sh ├── transform_readme.py └── update_schemastore.py └── uv.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | # Check http://editorconfig.org for more information 2 | # This is the main config file for this project: 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | end_of_line = lf 9 | indent_style = space 10 | insert_final_newline = true 11 | indent_size = 2 12 | 13 | [*.{rs,py,pyi,toml}] 14 | indent_size = 4 15 | 16 | [*.md] 17 | max_line_length = 100 18 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | 3 | docs/reference/cli.md linguist-generated=true 4 | docs/reference/configuration.md linguist-generated=true 5 | docs/reference/rules.md linguist-generated=true 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/1_bug_report.yaml: -------------------------------------------------------------------------------- 1 | name: Bug report 2 | description: Report an error or unexpected behavior 3 | body: 4 | - type: markdown 5 | attributes: 6 | value: | 7 | Thank you for taking the time to report an issue! We're glad to have you involved with ty. 8 | 9 | **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)).**
10 | **In particular, check out the list of [frequently encountered problems](https://github.com/astral-sh/ty/issues/445).** 11 | 12 | - type: textarea 13 | attributes: 14 | label: Summary 15 | description: | 16 | A clear and concise description of the bug, including a minimal reproducible example. 17 | 18 | Be sure to include the command you invoked (e.g., `ty check`) and 19 | the current ty settings (e.g., relevant sections from your `pyproject.toml`). 20 | 21 | If possible, try to include the [playground](https://play.ty.dev) link that reproduces this issue. 22 | 23 | validations: 24 | required: true 25 | 26 | - type: input 27 | attributes: 28 | label: Version 29 | description: What version of ty are you using? (see `ty version`) 30 | placeholder: e.g., ty 0.0.1 (ff9000864 2025-05-06) 31 | validations: 32 | required: false 33 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/2_question.yaml: -------------------------------------------------------------------------------- 1 | name: Question 2 | description: Ask a question about ty 3 | labels: ["question"] 4 | body: 5 | - type: textarea 6 | attributes: 7 | label: Question 8 | description: Describe your question in detail. 9 | validations: 10 | required: true 11 | 12 | - type: input 13 | attributes: 14 | label: Version 15 | description: What version of ty are you using? (see `ty version`) 16 | placeholder: e.g., ty 0.0.1 (ff9000864 2025-05-06) 17 | validations: 18 | required: false 19 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: true 2 | contact_links: 3 | - name: Documentation 4 | url: https://github.com/astral-sh/ty/blob/main/README.md 5 | about: Please consult the documentation before creating an issue. 6 | - name: Community 7 | url: https://discord.com/invite/astral-sh 8 | about: Join our Discord community to ask questions and collaborate. 9 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 8 | 9 | ## Summary 10 | 11 | 12 | 13 | ## Test Plan 14 | 15 | 16 | -------------------------------------------------------------------------------- /.github/actionlint.yaml: -------------------------------------------------------------------------------- 1 | # Configuration for the actionlint tool, which we run via pre-commit 2 | # to verify the correctness of the syntax in our GitHub Actions workflows. 3 | 4 | self-hosted-runner: 5 | # Various runners we use that aren't recognized out-of-the-box by actionlint: 6 | labels: 7 | - depot-ubuntu-latest-8 8 | - depot-ubuntu-22.04-16 9 | - depot-ubuntu-22.04-32 10 | - github-windows-2025-x86_64-8 11 | - github-windows-2025-x86_64-16 12 | -------------------------------------------------------------------------------- /.github/renovate.json5: -------------------------------------------------------------------------------- 1 | { 2 | $schema: "https://docs.renovatebot.com/renovate-schema.json", 3 | dependencyDashboard: true, 4 | suppressNotifications: ["prEditedNotification"], 5 | extends: ["config:recommended"], 6 | labels: ["internal"], 7 | schedule: ["before 4am on Monday"], 8 | semanticCommits: "disabled", 9 | separateMajorMinor: false, 10 | prHourlyLimit: 10, 11 | enabledManagers: ["github-actions", "pre-commit"], 12 | "pre-commit": { 13 | enabled: true, 14 | }, 15 | packageRules: [ 16 | // Pin GitHub Actions to immutable SHAs. 17 | { 18 | matchDepTypes: ["action"], 19 | pinDigests: true, 20 | }, 21 | // Annotate GitHub Actions SHAs with a SemVer version. 22 | { 23 | extends: ["helpers:pinGitHubActionDigests"], 24 | extractVersion: "^(?v?\\d+\\.\\d+\\.\\d+)$", 25 | versioning: "regex:^v?(?\\d+)(\\.(?\\d+)\\.(?\\d+))?$", 26 | }, 27 | { 28 | // Group upload/download artifact updates, the versions are dependent 29 | groupName: "Artifact GitHub Actions dependencies", 30 | matchManagers: ["github-actions"], 31 | matchDatasources: ["gitea-tags", "github-tags"], 32 | matchPackageNames: ["actions/.*-artifact"], 33 | description: "Weekly update of artifact-related GitHub Actions dependencies", 34 | }, 35 | { 36 | // This package rule disables updates for GitHub runners: 37 | // we'd only pin them to a specific version 38 | // if there was a deliberate reason to do so 39 | groupName: "GitHub runners", 40 | matchManagers: ["github-actions"], 41 | matchDatasources: ["github-runners"], 42 | description: "Disable PRs updating GitHub runners (e.g. 'runs-on: macos-14')", 43 | enabled: false, 44 | }, 45 | { 46 | groupName: "pre-commit dependencies", 47 | matchManagers: ["pre-commit"], 48 | description: "Weekly update of pre-commit dependencies", 49 | } 50 | ], 51 | vulnerabilityAlerts: { 52 | commitMessageSuffix: "", 53 | labels: ["internal", "security"], 54 | }, 55 | } 56 | -------------------------------------------------------------------------------- /.github/workflows/build-binaries.yml: -------------------------------------------------------------------------------- 1 | # Build ty on all platforms. 2 | # 3 | # Generates both wheels (for PyPI) and archived binaries (for GitHub releases). 4 | # 5 | # Assumed to run as a subworkflow of .github/workflows/release.yml; specifically, as a local 6 | # artifacts job within `cargo-dist`. 7 | name: "Build binaries" 8 | 9 | on: 10 | workflow_call: 11 | inputs: 12 | plan: 13 | required: true 14 | type: string 15 | pull_request: 16 | paths: 17 | # When we change pyproject.toml, we want to ensure that the maturin builds still work. 18 | - pyproject.toml 19 | # And when we change this workflow itself... 20 | - .github/workflows/build-binaries.yml 21 | 22 | concurrency: 23 | group: ${{ github.workflow }}-${{ github.ref }} 24 | cancel-in-progress: true 25 | 26 | permissions: 27 | contents: read 28 | 29 | env: 30 | PACKAGE_NAME: ty 31 | MODULE_NAME: ty 32 | PYTHON_VERSION: "3.13" 33 | CARGO_INCREMENTAL: 0 34 | CARGO_NET_RETRY: 10 35 | CARGO_TERM_COLOR: always 36 | RUSTUP_MAX_RETRIES: 10 37 | 38 | jobs: 39 | sdist: 40 | if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-build') }} 41 | runs-on: ubuntu-latest 42 | steps: 43 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 44 | with: 45 | submodules: recursive 46 | persist-credentials: false 47 | - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 48 | with: 49 | python-version: ${{ env.PYTHON_VERSION }} 50 | - name: "Prep README.md" 51 | run: python scripts/transform_readme.py 52 | - name: "Build sdist" 53 | uses: PyO3/maturin-action@aef21716ff3dcae8a1c301d23ec3e4446972a6e3 # v1.49.1 54 | with: 55 | command: sdist 56 | args: --out dist 57 | - name: "Test sdist" 58 | run: | 59 | pip install dist/"${PACKAGE_NAME}"-*.tar.gz --force-reinstall 60 | "${MODULE_NAME}" version 61 | "${MODULE_NAME}" --help 62 | python -m "${MODULE_NAME}" --help 63 | - name: "Upload sdist" 64 | uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 65 | with: 66 | name: wheels-sdist 67 | path: dist 68 | 69 | macos-x86_64: 70 | if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-build') }} 71 | runs-on: macos-14 72 | steps: 73 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 74 | with: 75 | submodules: recursive 76 | persist-credentials: false 77 | - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 78 | with: 79 | python-version: ${{ env.PYTHON_VERSION }} 80 | architecture: x64 81 | - name: "Prep README.md" 82 | run: python scripts/transform_readme.py 83 | - name: "Build wheels - x86_64" 84 | uses: PyO3/maturin-action@aef21716ff3dcae8a1c301d23ec3e4446972a6e3 # v1.49.1 85 | with: 86 | target: x86_64 87 | args: --release --locked --out dist 88 | - name: "Upload wheels" 89 | uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 90 | with: 91 | name: wheels-macos-x86_64 92 | path: dist 93 | - name: "Archive binary" 94 | run: | 95 | TARGET=x86_64-apple-darwin 96 | ARCHIVE_NAME=ty-$TARGET 97 | ARCHIVE_FILE=$ARCHIVE_NAME.tar.gz 98 | 99 | mkdir -p $ARCHIVE_NAME 100 | cp ruff/target/$TARGET/release/ty $ARCHIVE_NAME/ty 101 | tar czvf $ARCHIVE_FILE $ARCHIVE_NAME 102 | shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256 103 | - name: "Upload binary" 104 | uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 105 | with: 106 | name: artifacts-macos-x86_64 107 | path: | 108 | *.tar.gz 109 | *.sha256 110 | 111 | macos-aarch64: 112 | if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-build') }} 113 | runs-on: macos-14 114 | steps: 115 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 116 | with: 117 | submodules: recursive 118 | persist-credentials: false 119 | - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 120 | with: 121 | python-version: ${{ env.PYTHON_VERSION }} 122 | architecture: arm64 123 | - name: "Prep README.md" 124 | run: python scripts/transform_readme.py 125 | - name: "Build wheels - aarch64" 126 | uses: PyO3/maturin-action@aef21716ff3dcae8a1c301d23ec3e4446972a6e3 # v1.49.1 127 | with: 128 | target: aarch64 129 | args: --release --locked --out dist 130 | - name: "Test wheel - aarch64" 131 | run: | 132 | pip install dist/"${PACKAGE_NAME}"-*.whl --force-reinstall 133 | ty --help 134 | python -m ty --help 135 | - name: "Upload wheels" 136 | uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 137 | with: 138 | name: wheels-aarch64-apple-darwin 139 | path: dist 140 | - name: "Archive binary" 141 | run: | 142 | TARGET=aarch64-apple-darwin 143 | ARCHIVE_NAME=ty-$TARGET 144 | ARCHIVE_FILE=$ARCHIVE_NAME.tar.gz 145 | 146 | mkdir -p $ARCHIVE_NAME 147 | cp ruff/target/$TARGET/release/ty $ARCHIVE_NAME/ty 148 | tar czvf $ARCHIVE_FILE $ARCHIVE_NAME 149 | shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256 150 | - name: "Upload binary" 151 | uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 152 | with: 153 | name: artifacts-aarch64-apple-darwin 154 | path: | 155 | *.tar.gz 156 | *.sha256 157 | 158 | windows: 159 | if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-build') }} 160 | runs-on: windows-latest 161 | strategy: 162 | matrix: 163 | platform: 164 | - target: x86_64-pc-windows-msvc 165 | arch: x64 166 | - target: i686-pc-windows-msvc 167 | arch: x86 168 | - target: aarch64-pc-windows-msvc 169 | arch: x64 170 | steps: 171 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 172 | with: 173 | submodules: recursive 174 | persist-credentials: false 175 | - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 176 | with: 177 | python-version: ${{ env.PYTHON_VERSION }} 178 | architecture: ${{ matrix.platform.arch }} 179 | - name: "Prep README.md" 180 | run: python scripts/transform_readme.py 181 | - name: "Build wheels" 182 | uses: PyO3/maturin-action@aef21716ff3dcae8a1c301d23ec3e4446972a6e3 # v1.49.1 183 | with: 184 | target: ${{ matrix.platform.target }} 185 | args: --release --locked --out dist 186 | env: 187 | # aarch64 build fails, see https://github.com/PyO3/maturin/issues/2110 188 | XWIN_VERSION: 16 189 | - name: "Test wheel" 190 | if: ${{ !startsWith(matrix.platform.target, 'aarch64') }} 191 | shell: bash 192 | run: | 193 | python -m pip install dist/"${PACKAGE_NAME}"-*.whl --force-reinstall 194 | "${MODULE_NAME}" version 195 | "${MODULE_NAME}" --help 196 | python -m "${MODULE_NAME}" --help 197 | - name: "Upload wheels" 198 | uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 199 | with: 200 | name: wheels-${{ matrix.platform.target }} 201 | path: dist 202 | - name: "Archive binary" 203 | shell: bash 204 | run: | 205 | ARCHIVE_FILE=ty-${{ matrix.platform.target }}.zip 206 | 7z a $ARCHIVE_FILE ./ruff/target/${{ matrix.platform.target }}/release/ty.exe 207 | sha256sum $ARCHIVE_FILE > $ARCHIVE_FILE.sha256 208 | - name: "Upload binary" 209 | uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 210 | with: 211 | name: artifacts-${{ matrix.platform.target }} 212 | path: | 213 | *.zip 214 | *.sha256 215 | 216 | linux: 217 | if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-build') }} 218 | runs-on: ubuntu-latest 219 | strategy: 220 | matrix: 221 | target: 222 | - x86_64-unknown-linux-gnu 223 | - i686-unknown-linux-gnu 224 | steps: 225 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 226 | with: 227 | submodules: recursive 228 | persist-credentials: false 229 | - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 230 | with: 231 | python-version: ${{ env.PYTHON_VERSION }} 232 | architecture: x64 233 | - name: "Prep README.md" 234 | run: python scripts/transform_readme.py 235 | - name: "Build wheels" 236 | uses: PyO3/maturin-action@aef21716ff3dcae8a1c301d23ec3e4446972a6e3 # v1.49.1 237 | with: 238 | target: ${{ matrix.target }} 239 | manylinux: auto 240 | args: --release --locked --out dist 241 | - name: "Test wheel" 242 | if: ${{ startsWith(matrix.target, 'x86_64') }} 243 | run: | 244 | pip install dist/"${PACKAGE_NAME}"-*.whl --force-reinstall 245 | "${MODULE_NAME}" version 246 | "${MODULE_NAME}" --help 247 | python -m "${MODULE_NAME}" --help 248 | - name: "Upload wheels" 249 | uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 250 | with: 251 | name: wheels-${{ matrix.target }} 252 | path: dist 253 | - name: "Archive binary" 254 | shell: bash 255 | run: | 256 | set -euo pipefail 257 | 258 | TARGET=${{ matrix.target }} 259 | ARCHIVE_NAME=ty-$TARGET 260 | ARCHIVE_FILE=$ARCHIVE_NAME.tar.gz 261 | 262 | mkdir -p $ARCHIVE_NAME 263 | cp ruff/target/$TARGET/release/ty $ARCHIVE_NAME/ty 264 | tar czvf $ARCHIVE_FILE $ARCHIVE_NAME 265 | shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256 266 | - name: "Upload binary" 267 | uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 268 | with: 269 | name: artifacts-${{ matrix.target }} 270 | path: | 271 | *.tar.gz 272 | *.sha256 273 | 274 | linux-cross: 275 | if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-build') }} 276 | runs-on: ubuntu-latest 277 | strategy: 278 | matrix: 279 | platform: 280 | - target: aarch64-unknown-linux-gnu 281 | arch: aarch64 282 | # see https://github.com/astral-sh/ruff/issues/3791 283 | # and https://github.com/gnzlbg/jemallocator/issues/170#issuecomment-1503228963 284 | maturin_docker_options: -e JEMALLOC_SYS_WITH_LG_PAGE=16 285 | - target: armv7-unknown-linux-gnueabihf 286 | arch: armv7 287 | - target: s390x-unknown-linux-gnu 288 | arch: s390x 289 | - target: powerpc64le-unknown-linux-gnu 290 | arch: ppc64le 291 | # see https://github.com/astral-sh/ruff/issues/10073 292 | maturin_docker_options: -e JEMALLOC_SYS_WITH_LG_PAGE=16 293 | - target: powerpc64-unknown-linux-gnu 294 | arch: ppc64 295 | # see https://github.com/astral-sh/ruff/issues/10073 296 | maturin_docker_options: -e JEMALLOC_SYS_WITH_LG_PAGE=16 297 | - target: arm-unknown-linux-musleabihf 298 | arch: arm 299 | 300 | steps: 301 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 302 | with: 303 | submodules: recursive 304 | persist-credentials: false 305 | - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 306 | with: 307 | python-version: ${{ env.PYTHON_VERSION }} 308 | - name: "Prep README.md" 309 | run: python scripts/transform_readme.py 310 | - name: "Build wheels" 311 | uses: PyO3/maturin-action@aef21716ff3dcae8a1c301d23ec3e4446972a6e3 # v1.49.1 312 | with: 313 | target: ${{ matrix.platform.target }} 314 | manylinux: auto 315 | docker-options: ${{ matrix.platform.maturin_docker_options }} 316 | args: --release --locked --out dist 317 | - uses: uraimo/run-on-arch-action@ac33288c3728ca72563c97b8b88dda5a65a84448 # v2 318 | if: ${{ matrix.platform.arch != 'ppc64' && matrix.platform.arch != 'ppc64le'}} 319 | name: Test wheel 320 | with: 321 | arch: ${{ matrix.platform.arch == 'arm' && 'armv6' || matrix.platform.arch }} 322 | distro: ${{ matrix.platform.arch == 'arm' && 'bullseye' || 'ubuntu20.04' }} 323 | githubToken: ${{ github.token }} 324 | install: | 325 | apt-get update 326 | apt-get install -y --no-install-recommends python3 python3-pip 327 | pip3 install -U pip 328 | run: | 329 | pip3 install ${{ env.PACKAGE_NAME }} --no-index --find-links dist/ --force-reinstall 330 | ty --help 331 | - name: "Upload wheels" 332 | uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 333 | with: 334 | name: wheels-${{ matrix.platform.target }} 335 | path: dist 336 | - name: "Archive binary" 337 | shell: bash 338 | run: | 339 | set -euo pipefail 340 | 341 | TARGET=${{ matrix.platform.target }} 342 | ARCHIVE_NAME=ty-$TARGET 343 | ARCHIVE_FILE=$ARCHIVE_NAME.tar.gz 344 | 345 | mkdir -p $ARCHIVE_NAME 346 | cp ruff/target/$TARGET/release/ty $ARCHIVE_NAME/ty 347 | tar czvf $ARCHIVE_FILE $ARCHIVE_NAME 348 | shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256 349 | - name: "Upload binary" 350 | uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 351 | with: 352 | name: artifacts-${{ matrix.platform.target }} 353 | path: | 354 | *.tar.gz 355 | *.sha256 356 | 357 | musllinux: 358 | if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-build') }} 359 | runs-on: ubuntu-latest 360 | strategy: 361 | matrix: 362 | target: 363 | - x86_64-unknown-linux-musl 364 | - i686-unknown-linux-musl 365 | steps: 366 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 367 | with: 368 | submodules: recursive 369 | persist-credentials: false 370 | - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 371 | with: 372 | python-version: ${{ env.PYTHON_VERSION }} 373 | architecture: x64 374 | - name: "Prep README.md" 375 | run: python scripts/transform_readme.py 376 | - name: "Build wheels" 377 | uses: PyO3/maturin-action@aef21716ff3dcae8a1c301d23ec3e4446972a6e3 # v1.49.1 378 | with: 379 | target: ${{ matrix.target }} 380 | manylinux: musllinux_1_2 381 | args: --release --locked --out dist 382 | - name: "Test wheel" 383 | if: matrix.target == 'x86_64-unknown-linux-musl' 384 | uses: addnab/docker-run-action@4f65fabd2431ebc8d299f8e5a018d79a769ae185 # v3 385 | with: 386 | image: alpine:latest 387 | options: -v ${{ github.workspace }}:/io -w /io 388 | run: | 389 | apk add python3 390 | python -m venv .venv 391 | .venv/bin/pip3 install ${{ env.PACKAGE_NAME }} --no-index --find-links dist/ --force-reinstall 392 | .venv/bin/${{ env.MODULE_NAME }} --help 393 | - name: "Upload wheels" 394 | uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 395 | with: 396 | name: wheels-${{ matrix.target }} 397 | path: dist 398 | - name: "Archive binary" 399 | shell: bash 400 | run: | 401 | set -euo pipefail 402 | 403 | TARGET=${{ matrix.target }} 404 | ARCHIVE_NAME=ty-$TARGET 405 | ARCHIVE_FILE=$ARCHIVE_NAME.tar.gz 406 | 407 | mkdir -p $ARCHIVE_NAME 408 | cp ruff/target/$TARGET/release/ty $ARCHIVE_NAME/ty 409 | tar czvf $ARCHIVE_FILE $ARCHIVE_NAME 410 | shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256 411 | - name: "Upload binary" 412 | uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 413 | with: 414 | name: artifacts-${{ matrix.target }} 415 | path: | 416 | *.tar.gz 417 | *.sha256 418 | 419 | musllinux-cross: 420 | if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-build') }} 421 | runs-on: ubuntu-latest 422 | strategy: 423 | matrix: 424 | platform: 425 | - target: aarch64-unknown-linux-musl 426 | arch: aarch64 427 | maturin_docker_options: -e JEMALLOC_SYS_WITH_LG_PAGE=16 428 | - target: armv7-unknown-linux-musleabihf 429 | arch: armv7 430 | 431 | steps: 432 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 433 | with: 434 | submodules: recursive 435 | persist-credentials: false 436 | - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 437 | with: 438 | python-version: ${{ env.PYTHON_VERSION }} 439 | - name: "Prep README.md" 440 | run: python scripts/transform_readme.py 441 | - name: "Build wheels" 442 | uses: PyO3/maturin-action@aef21716ff3dcae8a1c301d23ec3e4446972a6e3 # v1.49.1 443 | with: 444 | target: ${{ matrix.platform.target }} 445 | manylinux: musllinux_1_2 446 | args: --release --locked --out dist 447 | docker-options: ${{ matrix.platform.maturin_docker_options }} 448 | - uses: uraimo/run-on-arch-action@ac33288c3728ca72563c97b8b88dda5a65a84448 # v2 449 | name: Test wheel 450 | with: 451 | arch: ${{ matrix.platform.arch }} 452 | distro: alpine_latest 453 | githubToken: ${{ github.token }} 454 | install: | 455 | apk add python3 456 | run: | 457 | python -m venv .venv 458 | .venv/bin/pip3 install ${{ env.PACKAGE_NAME }} --no-index --find-links dist/ --force-reinstall 459 | .venv/bin/${{ env.MODULE_NAME }} --help 460 | - name: "Upload wheels" 461 | uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 462 | with: 463 | name: wheels-${{ matrix.platform.target }} 464 | path: dist 465 | - name: "Archive binary" 466 | shell: bash 467 | run: | 468 | set -euo pipefail 469 | 470 | TARGET=${{ matrix.platform.target }} 471 | ARCHIVE_NAME=ty-$TARGET 472 | ARCHIVE_FILE=$ARCHIVE_NAME.tar.gz 473 | 474 | mkdir -p $ARCHIVE_NAME 475 | cp ruff/target/$TARGET/release/ty $ARCHIVE_NAME/ty 476 | tar czvf $ARCHIVE_FILE $ARCHIVE_NAME 477 | shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256 478 | - name: "Upload binary" 479 | uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 480 | with: 481 | name: artifacts-${{ matrix.platform.target }} 482 | path: | 483 | *.tar.gz 484 | *.sha256 485 | -------------------------------------------------------------------------------- /.github/workflows/build-docker.yml: -------------------------------------------------------------------------------- 1 | # Build and publish a Docker image. 2 | # 3 | # Assumed to run as a subworkflow of .github/workflows/release.yml; specifically, as a local 4 | # artifacts job within `cargo-dist`. 5 | # 6 | # TODO(charlie): Ideally, the publish step would happen as a publish job within `cargo-dist`, but 7 | # sharing the built image as an artifact between jobs is challenging. 8 | name: "Build Docker image" 9 | 10 | on: 11 | workflow_call: 12 | inputs: 13 | plan: 14 | required: true 15 | type: string 16 | pull_request: 17 | paths: 18 | - .github/workflows/build-docker.yml 19 | 20 | env: 21 | TY_BASE_IMG: ghcr.io/${{ github.repository_owner }}/ty 22 | 23 | permissions: 24 | contents: read 25 | # TODO(zanieb): Ideally, this would be `read` on dry-run but that will require 26 | # significant changes to the workflow. 27 | packages: write # zizmor: ignore[excessive-permissions] 28 | 29 | jobs: 30 | docker-build: 31 | name: Build Docker image (ghcr.io/astral-sh/ty) for ${{ matrix.platform }} 32 | runs-on: ubuntu-latest 33 | environment: 34 | name: release 35 | strategy: 36 | fail-fast: false 37 | matrix: 38 | platform: 39 | - linux/amd64 40 | - linux/arm64 41 | steps: 42 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 43 | with: 44 | submodules: recursive 45 | persist-credentials: false 46 | 47 | - uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3 48 | 49 | - uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3 50 | with: 51 | registry: ghcr.io 52 | username: ${{ github.repository_owner }} 53 | password: ${{ secrets.GITHUB_TOKEN }} 54 | 55 | - name: Check tag consistency 56 | if: ${{ inputs.plan != '' && !fromJson(inputs.plan).announcement_tag_is_implicit }} 57 | env: 58 | TAG: ${{ inputs.plan != '' && fromJson(inputs.plan).announcement_tag || 'dry-run' }} 59 | run: | 60 | version=$(grep -m 1 "^version = " dist-workspace.toml | sed -e 's/version = "\(.*\)"/\1/g') 61 | if [ "${TAG}" != "${version}" ]; then 62 | echo "The input tag does not match the version from dist-workspace.toml:" >&2 63 | echo "${TAG}" >&2 64 | echo "${version}" >&2 65 | exit 1 66 | else 67 | echo "Releasing ${version}" 68 | fi 69 | 70 | - name: Extract metadata (tags, labels) for Docker 71 | id: meta 72 | uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5 73 | with: 74 | images: ${{ env.TY_BASE_IMG }} 75 | # Defining this makes sure the org.opencontainers.image.version OCI label becomes the actual release version and not the branch name 76 | tags: | 77 | type=raw,value=dry-run,enable=${{ inputs.plan == '' || fromJson(inputs.plan).announcement_tag_is_implicit }} 78 | type=pep440,pattern={{ version }},value=${{ inputs.plan != '' && fromJson(inputs.plan).announcement_tag || 'dry-run' }},enable=${{ inputs.plan != '' && !fromJson(inputs.plan).announcement_tag_is_implicit }} 79 | 80 | - name: Normalize Platform Pair (replace / with -) 81 | run: | 82 | platform=${{ matrix.platform }} 83 | echo "PLATFORM_TUPLE=${platform//\//-}" >> "$GITHUB_ENV" 84 | 85 | # Adapted from https://docs.docker.com/build/ci/github-actions/multi-platform/ 86 | - name: Build and push by digest 87 | id: build 88 | uses: docker/build-push-action@14487ce63c7a62a4a324b0bfb37086795e31c6c1 # v6 89 | with: 90 | context: . 91 | platforms: ${{ matrix.platform }} 92 | cache-from: type=gha,scope=ty-${{ env.PLATFORM_TUPLE }} 93 | cache-to: type=gha,mode=min,scope=ty-${{ env.PLATFORM_TUPLE }} 94 | labels: ${{ steps.meta.outputs.labels }} 95 | 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 }} 96 | 97 | - name: Export digests 98 | env: 99 | digest: ${{ steps.build.outputs.digest }} 100 | run: | 101 | mkdir -p /tmp/digests 102 | touch "/tmp/digests/${digest#sha256:}" 103 | 104 | - name: Upload digests 105 | uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 106 | with: 107 | name: digests-${{ env.PLATFORM_TUPLE }} 108 | path: /tmp/digests/* 109 | if-no-files-found: error 110 | retention-days: 1 111 | 112 | docker-publish: 113 | name: Publish Docker image (ghcr.io/astral-sh/ty) 114 | runs-on: ubuntu-latest 115 | environment: 116 | name: release 117 | needs: 118 | - docker-build 119 | if: ${{ inputs.plan != '' && !fromJson(inputs.plan).announcement_tag_is_implicit }} 120 | steps: 121 | - name: Download digests 122 | uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 123 | with: 124 | path: /tmp/digests 125 | pattern: digests-* 126 | merge-multiple: true 127 | 128 | - uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3 129 | 130 | - name: Extract metadata (tags, labels) for Docker 131 | id: meta 132 | uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5 133 | with: 134 | images: ${{ env.TY_BASE_IMG }} 135 | # Order is on purpose such that the label org.opencontainers.image.version has the first pattern with the full version 136 | tags: | 137 | type=pep440,pattern={{ version }},value=${{ fromJson(inputs.plan).announcement_tag }} 138 | type=raw,value=${{ fromJson(inputs.plan).announcement_tag }} 139 | type=pep440,pattern={{ major }}.{{ minor }},value=${{ fromJson(inputs.plan).announcement_tag }} 140 | 141 | - uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3 142 | with: 143 | registry: ghcr.io 144 | username: ${{ github.repository_owner }} 145 | password: ${{ secrets.GITHUB_TOKEN }} 146 | 147 | # Adapted from https://docs.docker.com/build/ci/github-actions/multi-platform/ 148 | - name: Create manifest list and push 149 | working-directory: /tmp/digests 150 | # The jq command expands the docker/metadata json "tags" array entry to `-t tag1 -t tag2 ...` for each tag in the array 151 | # The printf will expand the base image with the `@sha256: ...` for each sha256 in the directory 152 | # The final command becomes `docker buildx imagetools create -t tag1 -t tag2 ... @sha256: @sha256: ...` 153 | run: | 154 | # shellcheck disable=SC2046 155 | docker buildx imagetools create \ 156 | $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ 157 | $(printf "${TY_BASE_IMG}@sha256:%s " *) 158 | 159 | docker-publish-extra: 160 | name: Publish additional Docker image based on ${{ matrix.image-mapping }} 161 | runs-on: ubuntu-latest 162 | environment: 163 | name: release 164 | needs: 165 | - docker-publish 166 | if: ${{ inputs.plan != '' && !fromJson(inputs.plan).announcement_tag_is_implicit }} 167 | strategy: 168 | fail-fast: false 169 | matrix: 170 | # Mapping of base image followed by a comma followed by one or more base tags (comma separated) 171 | # Note, org.opencontainers.image.version label will use the first base tag (use the most specific tag first) 172 | image-mapping: 173 | - alpine:3.21,alpine3.21,alpine 174 | - debian:bookworm-slim,bookworm-slim,debian-slim 175 | - buildpack-deps:bookworm,bookworm,debian 176 | steps: 177 | - uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3 178 | 179 | - uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3 180 | with: 181 | registry: ghcr.io 182 | username: ${{ github.repository_owner }} 183 | password: ${{ secrets.GITHUB_TOKEN }} 184 | 185 | - name: Generate Dynamic Dockerfile Tags 186 | shell: bash 187 | env: 188 | TAG_VALUE: ${{ fromJson(inputs.plan).announcement_tag }} 189 | run: | 190 | set -euo pipefail 191 | 192 | # Extract the image and tags from the matrix variable 193 | IFS=',' read -r BASE_IMAGE BASE_TAGS <<< "${{ matrix.image-mapping }}" 194 | 195 | # Generate Dockerfile content 196 | cat < Dockerfile 197 | FROM ${BASE_IMAGE} 198 | COPY --from=${TY_BASE_IMG}:${TAG_VALUE} /ty /usr/local/bin/ty 199 | ENTRYPOINT [] 200 | CMD ["/usr/local/bin/ty"] 201 | EOF 202 | 203 | # Initialize a variable to store all tag docker metadata patterns 204 | TAG_PATTERNS="" 205 | 206 | # Loop through all base tags and append its docker metadata pattern to the list 207 | # Order is on purpose such that the label org.opencontainers.image.version has the first pattern with the full version 208 | IFS=','; for TAG in ${BASE_TAGS}; do 209 | TAG_PATTERNS="${TAG_PATTERNS}type=pep440,pattern={{ version }},suffix=-${TAG},value=${TAG_VALUE}\n" 210 | TAG_PATTERNS="${TAG_PATTERNS}type=pep440,pattern={{ major }}.{{ minor }},suffix=-${TAG},value=${TAG_VALUE}\n" 211 | TAG_PATTERNS="${TAG_PATTERNS}type=raw,value=${TAG}\n" 212 | done 213 | 214 | # Remove the trailing newline from the pattern list 215 | TAG_PATTERNS="${TAG_PATTERNS%\\n}" 216 | 217 | # Export image cache name 218 | echo "IMAGE_REF=${BASE_IMAGE//:/-}" >> "$GITHUB_ENV" 219 | 220 | # Export tag patterns using the multiline env var syntax 221 | { 222 | echo "TAG_PATTERNS<> "$GITHUB_ENV" 226 | 227 | - name: Extract metadata (tags, labels) for Docker 228 | id: meta 229 | uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5 230 | # ghcr.io prefers index level annotations 231 | env: 232 | DOCKER_METADATA_ANNOTATIONS_LEVELS: index 233 | with: 234 | images: ${{ env.TY_BASE_IMG }} 235 | flavor: | 236 | latest=false 237 | tags: | 238 | ${{ env.TAG_PATTERNS }} 239 | 240 | - name: Build and push 241 | uses: docker/build-push-action@14487ce63c7a62a4a324b0bfb37086795e31c6c1 # v6 242 | with: 243 | context: . 244 | platforms: linux/amd64,linux/arm64 245 | # We do not really need to cache here as the Dockerfile is tiny 246 | #cache-from: type=gha,scope=ty-${{ env.IMAGE_REF }} 247 | #cache-to: type=gha,mode=min,scope=ty-${{ env.IMAGE_REF }} 248 | push: true 249 | tags: ${{ steps.meta.outputs.tags }} 250 | labels: ${{ steps.meta.outputs.labels }} 251 | annotations: ${{ steps.meta.outputs.annotations }} 252 | 253 | # This is effectively a duplicate of `docker-publish` to make https://github.com/astral-sh/ty/pkgs/container/ty 254 | # show the ty base image first since GitHub always shows the last updated image digests 255 | # This works by annotating the original digests (previously non-annotated) which triggers an update to ghcr.io 256 | docker-republish: 257 | name: Annotate Docker image (ghcr.io/astral-sh/ty) 258 | runs-on: ubuntu-latest 259 | environment: 260 | name: release 261 | needs: 262 | - docker-publish-extra 263 | if: ${{ inputs.plan != '' && !fromJson(inputs.plan).announcement_tag_is_implicit }} 264 | steps: 265 | - name: Download digests 266 | uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 267 | with: 268 | path: /tmp/digests 269 | pattern: digests-* 270 | merge-multiple: true 271 | 272 | - uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3 273 | 274 | - name: Extract metadata (tags, labels) for Docker 275 | id: meta 276 | uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5 277 | env: 278 | DOCKER_METADATA_ANNOTATIONS_LEVELS: index 279 | with: 280 | images: ${{ env.TY_BASE_IMG }} 281 | # Order is on purpose such that the label org.opencontainers.image.version has the first pattern with the full version 282 | tags: | 283 | type=pep440,pattern={{ version }},value=${{ fromJson(inputs.plan).announcement_tag }} 284 | type=raw,value=${{ fromJson(inputs.plan).announcement_tag }} 285 | type=pep440,pattern={{ major }}.{{ minor }},value=${{ fromJson(inputs.plan).announcement_tag }} 286 | 287 | - uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3 288 | with: 289 | registry: ghcr.io 290 | username: ${{ github.repository_owner }} 291 | password: ${{ secrets.GITHUB_TOKEN }} 292 | 293 | # Adapted from https://docs.docker.com/build/ci/github-actions/multi-platform/ 294 | - name: Create manifest list and push 295 | working-directory: /tmp/digests 296 | # The readarray part is used to make sure the quoting and special characters are preserved on expansion (e.g. spaces) 297 | # The jq command expands the docker/metadata json "tags" array entry to `-t tag1 -t tag2 ...` for each tag in the array 298 | # The printf will expand the base image with the `@sha256: ...` for each sha256 in the directory 299 | # The final command becomes `docker buildx imagetools create -t tag1 -t tag2 ... @sha256: @sha256: ...` 300 | run: | 301 | readarray -t lines <<< "$DOCKER_METADATA_OUTPUT_ANNOTATIONS"; annotations=(); for line in "${lines[@]}"; do annotations+=(--annotation "$line"); done 302 | 303 | # shellcheck disable=SC2046 304 | docker buildx imagetools create \ 305 | "${annotations[@]}" \ 306 | $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ 307 | $(printf "${TY_BASE_IMG}@sha256:%s " *) 308 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | permissions: 4 | contents: read 5 | 6 | on: 7 | push: 8 | branches: [main] 9 | pull_request: 10 | workflow_dispatch: 11 | 12 | concurrency: 13 | group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.event.pull_request.number || github.sha }} 14 | cancel-in-progress: true 15 | 16 | env: 17 | CARGO_INCREMENTAL: 0 18 | CARGO_NET_RETRY: 10 19 | CARGO_TERM_COLOR: always 20 | RUSTUP_MAX_RETRIES: 10 21 | PACKAGE_NAME: ty 22 | 23 | jobs: 24 | python-package: 25 | name: "python package" 26 | runs-on: ubuntu-latest 27 | timeout-minutes: 20 28 | steps: 29 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 30 | with: 31 | persist-credentials: false 32 | submodules: recursive 33 | - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 34 | with: 35 | python-version: ${{ env.PYTHON_VERSION }} 36 | architecture: x64 37 | - uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8 38 | - name: "Build wheels" 39 | uses: PyO3/maturin-action@aef21716ff3dcae8a1c301d23ec3e4446972a6e3 # v1.49.1 40 | with: 41 | args: --out dist 42 | - name: "Test wheel" 43 | run: | 44 | pip install --force-reinstall --find-links dist "${PACKAGE_NAME}" --pre 45 | ty --help 46 | python -m ty --help 47 | - name: "Remove wheels from cache" 48 | run: rm -rf target/wheels 49 | 50 | pre-commit: 51 | name: "pre-commit" 52 | runs-on: depot-ubuntu-22.04-16 53 | timeout-minutes: 10 54 | steps: 55 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 56 | with: 57 | persist-credentials: false 58 | - uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5.4.2 59 | - name: "Cache pre-commit" 60 | uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 61 | with: 62 | path: ~/.cache/pre-commit 63 | key: pre-commit-${{ hashFiles('.pre-commit-config.yaml') }} 64 | - name: "Run pre-commit" 65 | run: | 66 | echo '```console' > "$GITHUB_STEP_SUMMARY" 67 | # Enable color output for pre-commit and remove it for the summary 68 | # Use --hook-stage=manual to enable slower pre-commit hooks that are skipped by default 69 | SKIP=cargo-fmt,clippy,dev-generate-all uvx --python="${PYTHON_VERSION}" pre-commit run --all-files --show-diff-on-failure --color=always --hook-stage=manual | \ 70 | tee >(sed -E 's/\x1B\[([0-9]{1,2}(;[0-9]{1,2})*)?[mGK]//g' >> "$GITHUB_STEP_SUMMARY") >&1 71 | exit_code="${PIPESTATUS[0]}" 72 | echo '```' >> "$GITHUB_STEP_SUMMARY" 73 | exit "$exit_code" 74 | 75 | generated-check: 76 | name: "Check generated files unedited" 77 | runs-on: ubuntu-latest 78 | timeout-minutes: 5 79 | steps: 80 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 81 | with: 82 | persist-credentials: false 83 | submodules: recursive 84 | 85 | - uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5.4.2 86 | 87 | - name: "Run auto generation scripts" 88 | run: | 89 | ./scripts/autogenerate_files.sh 90 | 91 | - name: "Check for uncommitted changes" 92 | run: | 93 | if [[ -n "$(git status --porcelain)" ]]; then 94 | echo "Error: Auto-generated files were manually edited." 95 | echo "Files with changes:" 96 | git status --porcelain 97 | exit 1 98 | fi 99 | -------------------------------------------------------------------------------- /.github/workflows/daily_property_tests.yml: -------------------------------------------------------------------------------- 1 | name: Daily property test run 2 | 3 | on: 4 | workflow_dispatch: 5 | schedule: 6 | - cron: "0 12 * * *" 7 | pull_request: 8 | paths: 9 | - ".github/workflows/daily_property_tests.yml" 10 | 11 | permissions: 12 | contents: read 13 | 14 | concurrency: 15 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} 16 | cancel-in-progress: true 17 | 18 | env: 19 | CARGO_INCREMENTAL: 0 20 | CARGO_NET_RETRY: 10 21 | CARGO_TERM_COLOR: always 22 | RUSTUP_MAX_RETRIES: 10 23 | FORCE_COLOR: 1 24 | 25 | jobs: 26 | property_tests: 27 | name: Property tests 28 | runs-on: ubuntu-latest 29 | timeout-minutes: 20 30 | # Don't run the cron job on forks: 31 | if: ${{ github.repository == 'astral-sh/ty' || github.event_name != 'schedule' }} 32 | steps: 33 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 34 | with: 35 | persist-credentials: false 36 | repository: astral-sh/ruff 37 | - name: "Install Rust toolchain" 38 | run: rustup show 39 | - name: "Install mold" 40 | uses: rui314/setup-mold@e16410e7f8d9e167b74ad5697a9089a35126eb50 # v1 41 | - uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8 42 | - name: Build ty 43 | # A release build takes longer (2 min vs 1 min), but the property tests run much faster in release 44 | # mode (1.5 min vs 14 min), so the overall time is shorter with a release build. 45 | run: cargo build --locked --release --package ty_python_semantic --tests 46 | - name: Run property tests 47 | shell: bash 48 | run: | 49 | export QUICKCHECK_TESTS=100000 50 | for _ in {1..5}; do 51 | cargo test --locked --release --package ty_python_semantic -- --ignored list::property_tests 52 | cargo test --locked --release --package ty_python_semantic -- --ignored types::property_tests::stable 53 | done 54 | 55 | create-issue-on-failure: 56 | name: Create an issue if the daily property test run surfaced any bugs 57 | runs-on: ubuntu-latest 58 | needs: property_tests 59 | if: ${{ github.repository == 'astral-sh/ty' && always() && github.event_name == 'schedule' && needs.property_tests.result == 'failure' }} 60 | permissions: 61 | issues: write 62 | steps: 63 | - uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 64 | with: 65 | github-token: ${{ secrets.GITHUB_TOKEN }} 66 | script: | 67 | await github.rest.issues.create({ 68 | owner: "astral-sh", 69 | repo: "ty", 70 | title: `Daily property test run failed on ${new Date().toDateString()}`, 71 | body: "Run listed here: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}", 72 | labels: ["bug", "type properties"], 73 | }) 74 | -------------------------------------------------------------------------------- /.github/workflows/publish-pypi.yml: -------------------------------------------------------------------------------- 1 | # Publish a release to PyPI. 2 | # 3 | # Assumed to run as a subworkflow of .github/workflows/release.yml; specifically, as a publish job 4 | # within `cargo-dist`. 5 | name: "Publish to PyPI" 6 | 7 | on: 8 | workflow_call: 9 | inputs: 10 | plan: 11 | required: true 12 | type: string 13 | 14 | jobs: 15 | pypi-publish: 16 | name: Upload to PyPI 17 | runs-on: ubuntu-latest 18 | environment: 19 | name: release 20 | permissions: 21 | # For PyPI's trusted publishing. 22 | id-token: write 23 | steps: 24 | - name: "Install uv" 25 | uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5.4.2 26 | - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 27 | with: 28 | pattern: wheels-* 29 | path: wheels 30 | merge-multiple: true 31 | - name: Publish to PyPi 32 | run: uv publish -v wheels/* 33 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | # This file was autogenerated by dist: https://github.com/astral-sh/cargo-dist 2 | # 3 | # Copyright 2022-2024, axodotdev 4 | # Copyright 2025 Astral Software Inc. 5 | # SPDX-License-Identifier: MIT or Apache-2.0 6 | # 7 | # CI that: 8 | # 9 | # * checks for a Git Tag that looks like a release 10 | # * builds artifacts with dist (archives, installers, hashes) 11 | # * uploads those artifacts to temporary workflow zip 12 | # * on success, uploads the artifacts to a GitHub Release 13 | # 14 | # Note that the GitHub Release will be created with a generated 15 | # title/body based on your changelogs. 16 | 17 | name: Release 18 | permissions: 19 | "contents": "write" 20 | 21 | # This task will run whenever you workflow_dispatch with a tag that looks like a version 22 | # like "1.0.0", "v0.1.0-prerelease.1", "my-app/0.1.0", "releases/v1.0.0", etc. 23 | # Various formats will be parsed into a VERSION and an optional PACKAGE_NAME, where 24 | # PACKAGE_NAME must be the name of a Cargo package in your workspace, and VERSION 25 | # must be a Cargo-style SemVer Version (must have at least major.minor.patch). 26 | # 27 | # If PACKAGE_NAME is specified, then the announcement will be for that 28 | # package (erroring out if it doesn't have the given version or isn't dist-able). 29 | # 30 | # If PACKAGE_NAME isn't specified, then the announcement will be for all 31 | # (dist-able) packages in the workspace with that version (this mode is 32 | # intended for workspaces with only one dist-able package, or with all dist-able 33 | # packages versioned/released in lockstep). 34 | # 35 | # If you push multiple tags at once, separate instances of this workflow will 36 | # spin up, creating an independent announcement for each one. However, GitHub 37 | # will hard limit this to 3 tags per commit, as it will assume more tags is a 38 | # mistake. 39 | # 40 | # If there's a prerelease-style suffix to the version, then the release(s) 41 | # will be marked as a prerelease. 42 | on: 43 | pull_request: 44 | workflow_dispatch: 45 | inputs: 46 | tag: 47 | description: Release Tag 48 | required: true 49 | default: dry-run 50 | type: string 51 | 52 | jobs: 53 | # Run 'dist plan' (or host) to determine what tasks we need to do 54 | plan: 55 | runs-on: "depot-ubuntu-latest-4" 56 | outputs: 57 | val: ${{ steps.plan.outputs.manifest }} 58 | tag: ${{ (inputs.tag != 'dry-run' && inputs.tag) || '' }} 59 | tag-flag: ${{ inputs.tag && inputs.tag != 'dry-run' && format('--tag={0}', inputs.tag) || '' }} 60 | publishing: ${{ inputs.tag && inputs.tag != 'dry-run' }} 61 | env: 62 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 63 | steps: 64 | - uses: actions/checkout@85e6279cec87321a52edac9c87bce653a07cf6c2 65 | with: 66 | persist-credentials: false 67 | submodules: recursive 68 | - name: Install dist 69 | # we specify bash to get pipefail; it guards against the `curl` command 70 | # failing. otherwise `sh` won't catch that `curl` returned non-0 71 | shell: bash 72 | run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/astral-sh/cargo-dist/releases/download/v0.28.5-prerelease.3/cargo-dist-installer.sh | sh" 73 | - name: Cache dist 74 | uses: actions/upload-artifact@6027e3dd177782cd8ab9af838c04fd81a07f1d47 75 | with: 76 | name: cargo-dist-cache 77 | path: ~/.cargo/bin/dist 78 | # sure would be cool if github gave us proper conditionals... 79 | # so here's a doubly-nested ternary-via-truthiness to try to provide the best possible 80 | # functionality based on whether this is a pull_request, and whether it's from a fork. 81 | # (PRs run on the *source* but secrets are usually on the *target* -- that's *good* 82 | # but also really annoying to build CI around when it needs secrets to work right.) 83 | - id: plan 84 | run: | 85 | dist ${{ (inputs.tag && inputs.tag != 'dry-run' && format('host --steps=create --tag={0}', inputs.tag)) || 'plan' }} --output-format=json > plan-dist-manifest.json 86 | echo "dist ran successfully" 87 | cat plan-dist-manifest.json 88 | echo "manifest=$(jq -c "." plan-dist-manifest.json)" >> "$GITHUB_OUTPUT" 89 | - name: "Upload dist-manifest.json" 90 | uses: actions/upload-artifact@6027e3dd177782cd8ab9af838c04fd81a07f1d47 91 | with: 92 | name: artifacts-plan-dist-manifest 93 | path: plan-dist-manifest.json 94 | 95 | custom-build-binaries: 96 | needs: 97 | - plan 98 | if: ${{ needs.plan.outputs.publishing == 'true' || fromJson(needs.plan.outputs.val).ci.github.pr_run_mode == 'upload' || inputs.tag == 'dry-run' }} 99 | uses: ./.github/workflows/build-binaries.yml 100 | with: 101 | plan: ${{ needs.plan.outputs.val }} 102 | secrets: inherit 103 | 104 | custom-build-docker: 105 | needs: 106 | - plan 107 | if: ${{ needs.plan.outputs.publishing == 'true' || fromJson(needs.plan.outputs.val).ci.github.pr_run_mode == 'upload' || inputs.tag == 'dry-run' }} 108 | uses: ./.github/workflows/build-docker.yml 109 | with: 110 | plan: ${{ needs.plan.outputs.val }} 111 | secrets: inherit 112 | permissions: 113 | "contents": "read" 114 | "packages": "write" 115 | 116 | # Build and package all the platform-agnostic(ish) things 117 | build-global-artifacts: 118 | needs: 119 | - plan 120 | - custom-build-binaries 121 | - custom-build-docker 122 | runs-on: "depot-ubuntu-latest-4" 123 | env: 124 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 125 | BUILD_MANIFEST_NAME: target/distrib/global-dist-manifest.json 126 | steps: 127 | - uses: actions/checkout@85e6279cec87321a52edac9c87bce653a07cf6c2 128 | with: 129 | persist-credentials: false 130 | submodules: recursive 131 | - name: Install cached dist 132 | uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 133 | with: 134 | name: cargo-dist-cache 135 | path: ~/.cargo/bin/ 136 | - run: chmod +x ~/.cargo/bin/dist 137 | # Get all the local artifacts for the global tasks to use (for e.g. checksums) 138 | - name: Fetch local artifacts 139 | uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 140 | with: 141 | pattern: artifacts-* 142 | path: target/distrib/ 143 | merge-multiple: true 144 | - id: cargo-dist 145 | shell: bash 146 | run: | 147 | dist build ${{ needs.plan.outputs.tag-flag }} --output-format=json "--artifacts=global" > dist-manifest.json 148 | echo "dist ran successfully" 149 | 150 | # Parse out what we just built and upload it to scratch storage 151 | echo "paths<> "$GITHUB_OUTPUT" 152 | jq --raw-output ".upload_files[]" dist-manifest.json >> "$GITHUB_OUTPUT" 153 | echo "EOF" >> "$GITHUB_OUTPUT" 154 | 155 | cp dist-manifest.json "$BUILD_MANIFEST_NAME" 156 | - name: "Upload artifacts" 157 | uses: actions/upload-artifact@6027e3dd177782cd8ab9af838c04fd81a07f1d47 158 | with: 159 | name: artifacts-build-global 160 | path: | 161 | ${{ steps.cargo-dist.outputs.paths }} 162 | ${{ env.BUILD_MANIFEST_NAME }} 163 | # Determines if we should publish/announce 164 | host: 165 | needs: 166 | - plan 167 | - custom-build-binaries 168 | - custom-build-docker 169 | - build-global-artifacts 170 | # Only run if we're "publishing", and only if local and global didn't fail (skipped is fine) 171 | if: ${{ always() && 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') }} 172 | env: 173 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 174 | runs-on: "depot-ubuntu-latest-4" 175 | outputs: 176 | val: ${{ steps.host.outputs.manifest }} 177 | steps: 178 | - uses: actions/checkout@85e6279cec87321a52edac9c87bce653a07cf6c2 179 | with: 180 | persist-credentials: false 181 | submodules: recursive 182 | - name: Install cached dist 183 | uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 184 | with: 185 | name: cargo-dist-cache 186 | path: ~/.cargo/bin/ 187 | - run: chmod +x ~/.cargo/bin/dist 188 | # Fetch artifacts from scratch-storage 189 | - name: Fetch artifacts 190 | uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 191 | with: 192 | pattern: artifacts-* 193 | path: target/distrib/ 194 | merge-multiple: true 195 | # This is a harmless no-op for GitHub Releases, hosting for that happens in "announce" 196 | - id: host 197 | shell: bash 198 | run: | 199 | dist host ${{ needs.plan.outputs.tag-flag }} --steps=upload --steps=release --output-format=json > dist-manifest.json 200 | echo "artifacts uploaded and released successfully" 201 | cat dist-manifest.json 202 | echo "manifest=$(jq -c "." dist-manifest.json)" >> "$GITHUB_OUTPUT" 203 | - name: "Upload dist-manifest.json" 204 | uses: actions/upload-artifact@6027e3dd177782cd8ab9af838c04fd81a07f1d47 205 | with: 206 | # Overwrite the previous copy 207 | name: artifacts-dist-manifest 208 | path: dist-manifest.json 209 | 210 | custom-publish-pypi: 211 | needs: 212 | - plan 213 | - host 214 | if: ${{ !fromJson(needs.plan.outputs.val).announcement_is_prerelease || fromJson(needs.plan.outputs.val).publish_prereleases }} 215 | uses: ./.github/workflows/publish-pypi.yml 216 | with: 217 | plan: ${{ needs.plan.outputs.val }} 218 | secrets: inherit 219 | # publish jobs get escalated permissions 220 | permissions: 221 | "id-token": "write" 222 | "packages": "write" 223 | 224 | # Create a GitHub Release while uploading all files to it 225 | announce: 226 | needs: 227 | - plan 228 | - host 229 | - custom-publish-pypi 230 | # use "always() && ..." to allow us to wait for all publish jobs while 231 | # still allowing individual publish jobs to skip themselves (for prereleases). 232 | # "host" however must run to completion, no skipping allowed! 233 | if: ${{ always() && needs.host.result == 'success' && (needs.custom-publish-pypi.result == 'skipped' || needs.custom-publish-pypi.result == 'success') }} 234 | runs-on: "depot-ubuntu-latest-4" 235 | env: 236 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 237 | steps: 238 | - uses: actions/checkout@85e6279cec87321a52edac9c87bce653a07cf6c2 239 | with: 240 | persist-credentials: false 241 | submodules: recursive 242 | # Create a GitHub Release while uploading all files to it 243 | - name: "Download GitHub Artifacts" 244 | uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 245 | with: 246 | pattern: artifacts-* 247 | path: artifacts 248 | merge-multiple: true 249 | - name: Cleanup 250 | run: | 251 | # Remove the granular manifests 252 | rm -f artifacts/*-dist-manifest.json 253 | - name: Create GitHub Release 254 | env: 255 | PRERELEASE_FLAG: "${{ fromJson(needs.host.outputs.val).announcement_is_prerelease && '--prerelease' || '' }}" 256 | ANNOUNCEMENT_TITLE: "${{ fromJson(needs.host.outputs.val).announcement_title }}" 257 | ANNOUNCEMENT_BODY: "${{ fromJson(needs.host.outputs.val).announcement_github_body }}" 258 | RELEASE_COMMIT: "${{ github.sha }}" 259 | run: | 260 | # Write and read notes from a file to avoid quoting breaking things 261 | echo "$ANNOUNCEMENT_BODY" > $RUNNER_TEMP/notes.txt 262 | 263 | gh release create "${{ needs.plan.outputs.tag }}" --target "$RELEASE_COMMIT" $PRERELEASE_FLAG --title "$ANNOUNCEMENT_TITLE" --notes-file "$RUNNER_TEMP/notes.txt" artifacts/* 264 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Python-generated files 2 | __pycache__/ 3 | *.py[oc] 4 | build/ 5 | dist/ 6 | wheels/ 7 | *.egg-info 8 | 9 | # Virtual environments 10 | .venv 11 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "ruff"] 2 | path = ruff 3 | url = https://github.com/astral-sh/ruff 4 | -------------------------------------------------------------------------------- /.markdownlint.yaml: -------------------------------------------------------------------------------- 1 | # default to true for all rules 2 | default: true 3 | 4 | # MD007/unordered-list-indent 5 | MD007: 6 | indent: 4 7 | 8 | # MD033/no-inline-html 9 | MD033: false 10 | 11 | # MD041/first-line-h1 12 | MD041: false 13 | 14 | # MD013/line-length 15 | MD013: false 16 | 17 | # MD014/commands-show-output 18 | MD014: false 19 | 20 | # MD024/no-duplicate-heading 21 | MD024: 22 | # Allow when nested under different parents e.g. CHANGELOG.md 23 | siblings_only: true 24 | 25 | # MD046/code-block-style 26 | # 27 | # Ignore this because it conflicts with the code block style used in content 28 | # tabs of mkdocs-material which is to add a blank line after the content title. 29 | # 30 | # Ref: https://github.com/astral-sh/ruff/pull/15011#issuecomment-2544790854 31 | MD046: false 32 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | fail_fast: false 2 | 3 | exclude: | 4 | (?x)^( 5 | .github/workflows/release.yml 6 | | ruff/.* 7 | | docs/reference/(cli|configuration|rules).md 8 | )$ 9 | 10 | repos: 11 | - repo: https://github.com/astral-sh/uv-pre-commit 12 | rev: 0.7.3 13 | hooks: 14 | - id: uv-lock 15 | - repo: https://github.com/pre-commit/pre-commit-hooks 16 | rev: v5.0.0 17 | hooks: 18 | - id: check-merge-conflict 19 | 20 | - repo: https://github.com/astral-sh/ruff-pre-commit 21 | rev: v0.11.8 22 | hooks: 23 | - id: ruff-format 24 | - id: ruff 25 | args: [--fix, --exit-non-zero-on-fix] 26 | types_or: [python, pyi] 27 | require_serial: true 28 | 29 | - repo: https://github.com/abravalheri/validate-pyproject 30 | rev: v0.24.1 31 | hooks: 32 | - id: validate-pyproject 33 | 34 | - repo: https://github.com/executablebooks/mdformat 35 | rev: 0.7.22 36 | hooks: 37 | - id: mdformat 38 | additional_dependencies: 39 | - mdformat-mkdocs==4.0.0 40 | - mdformat-footnote==0.1.1 41 | 42 | - repo: https://github.com/igorshubovych/markdownlint-cli 43 | rev: v0.44.0 44 | hooks: 45 | - id: markdownlint-fix 46 | 47 | - repo: https://github.com/crate-ci/typos 48 | rev: v1.32.0 49 | hooks: 50 | - id: typos 51 | 52 | # Prettier 53 | - repo: https://github.com/rbubley/mirrors-prettier 54 | rev: v3.5.3 55 | hooks: 56 | - id: prettier 57 | types: [yaml] 58 | 59 | # zizmor detects security vulnerabilities in GitHub Actions workflows. 60 | # Additional configuration for the tool is found in `.github/zizmor.yml` 61 | - repo: https://github.com/woodruffw/zizmor-pre-commit 62 | rev: v1.6.0 63 | hooks: 64 | - id: zizmor 65 | 66 | - repo: https://github.com/python-jsonschema/check-jsonschema 67 | rev: 0.33.0 68 | hooks: 69 | - id: check-github-workflows 70 | 71 | # `actionlint` hook, for verifying correct syntax in GitHub Actions workflows. 72 | # Some additional configuration for `actionlint` can be found in `.github/actionlint.yaml`. 73 | - repo: https://github.com/rhysd/actionlint 74 | rev: v1.7.7 75 | hooks: 76 | - id: actionlint 77 | stages: 78 | # This hook is disabled by default, since it's quite slow. 79 | # To run all hooks *including* this hook, use `uvx pre-commit run -a --hook-stage=manual`. 80 | # To run *just* this hook, use `uvx pre-commit run -a actionlint --hook-stage=manual`. 81 | - manual 82 | args: 83 | - "-ignore=SC2129" # ignorable stylistic lint from shellcheck 84 | - "-ignore=SC2016" # another shellcheck lint: seems to have false positives? 85 | additional_dependencies: 86 | # actionlint has a shellcheck integration which extracts shell scripts in `run:` steps from GitHub Actions 87 | # and checks these with shellcheck. This is arguably its most useful feature, 88 | # but the integration only works if shellcheck is installed 89 | - "github.com/wasilibs/go-shellcheck/cmd/shellcheck@v0.10.0" 90 | -------------------------------------------------------------------------------- /.python-version: -------------------------------------------------------------------------------- 1 | 3.13 2 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 0.0.1-alpha.7 4 | 5 | ### Bug fixes 6 | 7 | - Implement Python's floor-division semantics for `Literal` `int`s ([#18249](https://github.com/astral-sh/ruff/pull/18249)) 8 | - Don't warn about a `yield` expression not being in a function if the `yield` expression is in a function ([#18008](https://github.com/astral-sh/ruff/pull/18008)) 9 | - Fix inference of attribute writes to unions/intersections that including module-literal types ([#18313](https://github.com/astral-sh/ruff/pull/18313)) 10 | - Fix false-positive diagnostics in binary comparison inference logic for intersection types ([#18266](https://github.com/astral-sh/ruff/pull/18266)) 11 | - Fix instance vs callable subtyping/assignability ([#18260](https://github.com/astral-sh/ruff/pull/18260)) 12 | - Ignore `ClassVar` declarations when resolving instance members ([#18241](https://github.com/astral-sh/ruff/pull/18241)) 13 | - Fix crash when hovering over a `ty_extensions.Intersection[A, B]` expression in an IDE context ([#18321](https://github.com/astral-sh/ruff/pull/18321)) 14 | - Respect `MRO_NO_OBJECT_FALLBACK` policy when looking up symbols on `type` instances ([#18312](https://github.com/astral-sh/ruff/pull/18312)) 15 | - `get_protocol_members` returns a frozenset, not a tuple ([#18284](https://github.com/astral-sh/ruff/pull/18284)) 16 | 17 | ### Typing semantics and features 18 | 19 | - Support `import ` and `from import module` ([#18137](https://github.com/astral-sh/ruff/pull/18137)) 20 | - Support frozen dataclasses ([#17974](https://github.com/astral-sh/ruff/pull/17974)) 21 | - Understand that the presence of a `__getattribute__` method indicates arbitrary members can exist on a type ([#18280](https://github.com/astral-sh/ruff/pull/18280)) 22 | - Add a subdiagnostic if `invalid-return-type` is emitted on a method with an empty body on a non-protocol subclass of a protocol class ([#18243](https://github.com/astral-sh/ruff/pull/18243)) 23 | - Improve `invalid-type-form` diagnostic where a module-literal type is used in a type expression and the module has a member which would be valid in a type expression ([#18244](https://github.com/astral-sh/ruff/pull/18244)) 24 | - Split `invalid-base` error code into two error codes ([#18245](https://github.com/astral-sh/ruff/pull/18245)) 25 | - Rename `call-possibly-unbound-method` to `possibly-unbound-implicit-call` ([#18017](https://github.com/astral-sh/ruff/pull/18017)) 26 | 27 | ### Configuration 28 | 29 | - Add `tests` to `src.root` by default if a `tests/` directory exists and is not a package ([#18286](https://github.com/astral-sh/ruff/pull/18286)) 30 | - Tell the user why we inferred the Python version we inferred ([#18082](https://github.com/astral-sh/ruff/pull/18082)) 31 | - Add support for detecting activated Conda and Pixi environments ([#18267](https://github.com/astral-sh/ruff/pull/18267)) 32 | - Move `respect-ignore-files` configuration setting under `src` section ([#18322](https://github.com/astral-sh/ruff/pull/18322)) 33 | 34 | ### Server 35 | 36 | - Fix server panic when calling `system_mut` ([#18252](https://github.com/astral-sh/ruff/pull/18252)) 37 | - Abort process if worker thread panics ([#18211](https://github.com/astral-sh/ruff/pull/18211)) 38 | - Gracefully handle salsa cancellations and panics in background request handlers ([#18254](https://github.com/astral-sh/ruff/pull/18254)) 39 | 40 | ### Contributors 41 | 42 | - [@felixscherz](https://github.com/felixscherz) 43 | - [@carljm](https://github.com/carljm) 44 | - [@j178](https://github.com/j178) 45 | - [@thejchap](https://github.com/thejchap) 46 | - [@brainwane](https://github.com/brainwane) 47 | - [@AlexWaygood](https://github.com/AlexWaygood) 48 | - [@lipefree](https://github.com/lipefree) 49 | - [@InSyncWithFoo](https://github.com/InSyncWithFoo) 50 | - [@brandtbucher](https://github.com/brandtbucher) 51 | - [@MichaReiser](https://github.com/MichaReiser) 52 | - [@maxmynter](https://github.com/maxmynter) 53 | - [@fabridamicelli](https://github.com/fabridamicelli) 54 | - [@sharkdp](https://github.com/sharkdp) 55 | 56 | ## 0.0.1-alpha.6 57 | 58 | ### Server 59 | 60 | - Add rule link to server diagnostics ([#18128](https://github.com/astral-sh/ruff/pull/18128)) 61 | - Avoid panicking when there are multiple workspaces ([#18151](https://github.com/astral-sh/ruff/pull/18151)) 62 | - Show related information in diagnostic ([#17359](https://github.com/astral-sh/ruff/pull/17359)) 63 | 64 | ### Configuration 65 | 66 | - Default `src.root` setting to `['.', '']` if an `src/` directory does not exist but a `/` directory does exist ([#18141](https://github.com/astral-sh/ruff/pull/18141)) 67 | 68 | ### Typing semantics and features 69 | 70 | - Consider a class with a dynamic element in its MRO assignable to any subtype of `type` ([#18205](https://github.com/astral-sh/ruff/pull/18205)) 71 | - Ensure that a function-literal type is always considered equivalent to itself ([#18227](https://github.com/astral-sh/ruff/pull/18227)) 72 | - Promote literals when inferring class specializations from constructors ([#18102](https://github.com/astral-sh/ruff/pull/18102)) 73 | - Support `typing.TypeAliasType` ([#18156](https://github.com/astral-sh/ruff/pull/18156)) 74 | - Infer function-call type variables in both directions ([#18155](https://github.com/astral-sh/ruff/pull/18155)) 75 | 76 | ### Improvements to modeling of runtime semantics 77 | 78 | - Integer indexing into `bytes` returns `int` ([#18218](https://github.com/astral-sh/ruff/pull/18218)) 79 | - Emit `invalid-exception-caught` diagnostics even when the caught exception is not bound to a variable ([#18202](https://github.com/astral-sh/ruff/pull/18202)) 80 | 81 | ### Usability improvements 82 | 83 | - Add hint to some diagnostics that [PEP 604](https://peps.python.org/pep-0604/) union syntax is only available on Python 3.10+ ([#18192](https://github.com/astral-sh/ruff/pull/18192)) 84 | - Add note to `unresolved-import` diagnostic hinting to users to configure their Python environment ([#18207](https://github.com/astral-sh/ruff/pull/18207)) 85 | - Make `division-by-zero` an opt-in diagnostic rather than opt-out ([#18220](https://github.com/astral-sh/ruff/pull/18220)) 86 | 87 | ### Import resolution improvements 88 | 89 | - Add support for PyPy virtual environments ([#18203](https://github.com/astral-sh/ruff/pull/18203)) 90 | 91 | ### Contributors 92 | 93 | - [@dhruvmanila](https://github.com/dhruvmanila) 94 | - [@InSyncWithFoo](https://github.com/InSyncWithFoo) 95 | - [@AlexWaygood](https://github.com/AlexWaygood) 96 | - [@MichaReiser](https://github.com/MichaReiser) 97 | - [@BradonZhang](https://github.com/BradonZhang) 98 | - [@dcreager](https://github.com/dcreager) 99 | - [@danielhollas](https://github.com/danielhollas) 100 | - [@esadek](https://github.com/esadek) 101 | - [@kiran-4444](https://github.com/kiran-4444) 102 | - [@Mathemmagician](https://github.com/Mathemmagician) 103 | - [@sharkdp](https://github.com/sharkdp) 104 | - [@felixscherz](https://github.com/felixscherz) 105 | - [@adamaaronson](https://github.com/adamaaronson) 106 | - [@carljm](https://github.com/carljm) 107 | 108 | ## 0.0.1-alpha.5 109 | 110 | ### Bug fixes 111 | 112 | - Fix assignability checks for invariant generics parameterized by gradual types ([#18138](https://github.com/astral-sh/ruff/pull/18138)) 113 | - Revert boolean expression control flow change which caused a performance regression ([#18150](https://github.com/astral-sh/ruff/pull/18150)) 114 | - Remove pyvenv.cfg validation check for lines with multiple `=` ([#18144](https://github.com/astral-sh/ruff/pull/18144)) 115 | 116 | ### Contributors 117 | 118 | - [@MatthewMckee4](https://github.com/MatthewMckee4) 119 | - [@AlexWaygood](https://github.com/AlexWaygood) 120 | 121 | ## 0.0.1-alpha.4 122 | 123 | ### Enhancements 124 | 125 | - Allow unions including `Any`/`Unknown` as bases ([#18094](https://github.com/astral-sh/ruff/pull/18094)) 126 | - Better control flow for boolean expressions that are inside if ([#18010](https://github.com/astral-sh/ruff/pull/18010)) 127 | - Improve invalid method calls for unmatched overloads ([#18122](https://github.com/astral-sh/ruff/pull/18122)) 128 | - Add support for `NamedTuple` 'fallback' attributes ([#18127](https://github.com/astral-sh/ruff/pull/18127)) 129 | - `type[…]` is always assignable to `type` ([#18121](https://github.com/astral-sh/ruff/pull/18121)) 130 | - Support accessing `__builtins__` global ([#18118](https://github.com/astral-sh/ruff/pull/18118)) 131 | 132 | ### Bug fixes 133 | 134 | - Fix relative imports in stub packages ([#18132](https://github.com/astral-sh/ruff/pull/18132)) 135 | 136 | ### Contributors 137 | 138 | - [@MatthewMckee4](https://github.com/MatthewMckee4) 139 | - [@felixscherz](https://github.com/felixscherz) 140 | - [@BurntSushi](https://github.com/BurntSushi) 141 | - [@maxmynter](https://github.com/maxmynter) 142 | - [@sharkdp](https://github.com/sharkdp) 143 | - [@TomerBin](https://github.com/TomerBin) 144 | - [@MichaReiser](https://github.com/MichaReiser) 145 | 146 | ## 0.0.1-alpha.3 147 | 148 | ### Enhancements 149 | 150 | - Include synthesized arguments in displayed counts for `too-many-positional-arguments` ([#18098](https://github.com/astral-sh/ruff/pull/18098)) 151 | 152 | ### Bug fixes 153 | 154 | - Fix `redundant-cast` false positives when casting to `Unknown` ([#18111](https://github.com/astral-sh/ruff/pull/18111)) 155 | - Fix normalization of unions containing instances parameterized with unions ([#18112](https://github.com/astral-sh/ruff/pull/18112)) 156 | - Make dataclass instances adhere to DataclassInstance ([#18115](https://github.com/astral-sh/ruff/pull/18115)) 157 | 158 | ### CLI 159 | 160 | - Change layout of extra verbose output and respect `--color` for verbose output ([#18089](https://github.com/astral-sh/ruff/pull/18089)) 161 | 162 | ### Documentation 163 | 164 | - Use Cargo-style versions in the changelog ([#397](https://github.com/astral-sh/ty/pull/397)) 165 | 166 | ### Contributors 167 | 168 | - [@zanieb](https://github.com/zanieb) 169 | - [@sharkdp](https://github.com/sharkdp) 170 | - [@AlexWaygood](https://github.com/AlexWaygood) 171 | - [@InSyncWithFoo](https://github.com/InSyncWithFoo) 172 | - [@MichaReiser](https://github.com/MichaReiser) 173 | 174 | ## 0.0.1-alpha.2 175 | 176 | ### Enhancements 177 | 178 | - Improve diagnostics for failure to call overloaded function ([#18073](https://github.com/astral-sh/ruff/pull/18073)) 179 | - Fix inconsistent casing in `invalid-return-type` diagnostic ([#18084](https://github.com/astral-sh/ruff/pull/18084)) 180 | - Add type-expression syntax link to `invalid-type-expression` diagnostics ([#18104](https://github.com/astral-sh/ruff/pull/18104)) 181 | 182 | ### Bug fixes 183 | 184 | - Add cycle handling for unpacking targets ([#18078](https://github.com/astral-sh/ruff/pull/18078)) 185 | - Do not look up `__init__` on instances ([#18092](https://github.com/astral-sh/ruff/pull/18092)) 186 | 187 | ### Typing 188 | 189 | - Infer parameter specializations of explicitly implemented generic protocols ([#18054](https://github.com/astral-sh/ruff/pull/18054)) 190 | - Check assignments to implicit global symbols are assignable to the types declared on `types.ModuleType` ([#18077](https://github.com/astral-sh/ruff/pull/18077)) 191 | - Fix various generics-related TODOs ([#18062](https://github.com/astral-sh/ruff/pull/18062)) 192 | 193 | ### Documentation 194 | 195 | - Fix rule link in the configuration description ([#381](https://github.com/astral-sh/ty/pull/381)) 196 | - Use `https://ty.dev/rules` when linking to the rules table ([#18072](https://github.com/astral-sh/ruff/pull/18072)) 197 | - Use `ty server` instead of `ty lsp` ([#360](https://github.com/astral-sh/ty/pull/360)) 198 | - Fix missing `>` in HTML anchor tags in CLI reference ([#18096](https://github.com/astral-sh/ruff/pull/18096)) 199 | - Fix link to rules docs ([#378](https://github.com/astral-sh/ty/pull/378)) 200 | - Fix repository in README transform script ([#361](https://github.com/astral-sh/ty/pull/361)) 201 | 202 | ### Contributors 203 | 204 | - [@dhruvmanila](https://github.com/dhruvmanila) 205 | - [@Usul-Dev](https://github.com/Usul-Dev) 206 | - [@dcreager](https://github.com/dcreager) 207 | - [@AlexWaygood](https://github.com/AlexWaygood) 208 | - [@BurntSushi](https://github.com/BurntSushi) 209 | - [@MichaReiser](https://github.com/MichaReiser) 210 | - [@frgfm](https://github.com/frgfm) 211 | - [@kiran-4444](https://github.com/kiran-4444) 212 | - [@sharkdp](https://github.com/sharkdp) 213 | - [@eruditmorina](https://github.com/eruditmorina) 214 | 215 | ## 0.0.1-alpha.1 216 | 217 | ### Enhancements 218 | 219 | - Add basic support for non-virtual Python environments ([#17991](https://github.com/astral-sh/ruff/pull/17991)) 220 | - Do not allow invalid virtual environments from discovered `.venv` or `VIRTUAL_ENV` ([#18003](https://github.com/astral-sh/ruff/pull/18003)) 221 | - Refine message for why a rule is enabled ([#18038](https://github.com/astral-sh/ruff/pull/18038)) 222 | - Update `--python` to accept paths to executables in environments ([#17954](https://github.com/astral-sh/ruff/pull/17954)) 223 | - Improve diagnostics for `assert_type` and `assert_never` ([#18050](https://github.com/astral-sh/ruff/pull/18050)) 224 | - Add a note to the diagnostic if a new builtin is used on an old Python version ([#18068](https://github.com/astral-sh/ruff/pull/18068)) 225 | 226 | ### Bug fixes 227 | 228 | - Fix infinite recursion bug in `is_disjoint_from` ([#18043](https://github.com/astral-sh/ruff/pull/18043)) 229 | - Recognize submodules in self-referential imports ([#18005](https://github.com/astral-sh/ruff/pull/18005)) 230 | 231 | ### Typing 232 | 233 | - Allow a class to inherit from an intersection if the intersection contains a dynamic type and the intersection is not disjoint from `type` ([#18055](https://github.com/astral-sh/ruff/pull/18055)) 234 | - Allow classes to inherit from `type[Any]` or `type[Unknown]` ([#18060](https://github.com/astral-sh/ruff/pull/18060)) 235 | - Apply function specialization to all overloads ([#18020](https://github.com/astral-sh/ruff/pull/18020)) 236 | - Implement `DataClassInstance` protocol for dataclasses ([#18018](https://github.com/astral-sh/ruff/pull/18018)) 237 | - Induct into instances and subclasses when finding and applying generics ([#18052](https://github.com/astral-sh/ruff/pull/18052)) 238 | - Infer parameter specializations of generic aliases ([#18021](https://github.com/astral-sh/ruff/pull/18021)) 239 | - Narrowing for `hasattr()` ([#18053](https://github.com/astral-sh/ruff/pull/18053)) 240 | - Silence false positives for PEP-695 ParamSpec annotations ([#18001](https://github.com/astral-sh/ruff/pull/18001)) 241 | - Understand homogeneous tuple annotations ([#17998](https://github.com/astral-sh/ruff/pull/17998)) 242 | - `__file__` is always a string inside a Python module ([#18071](https://github.com/astral-sh/ruff/pull/18071)) 243 | 244 | ### CLI 245 | 246 | - Avoid initializing progress bars early ([#18049](https://github.com/astral-sh/ruff/pull/18049)) 247 | 248 | ### Contributors 249 | 250 | - [@soof-golan](https://github.com/soof-golan) 251 | - [@ibraheemdev](https://github.com/ibraheemdev) 252 | - [@dhruvmanila](https://github.com/dhruvmanila) 253 | - [@charliermarsh](https://github.com/charliermarsh) 254 | - [@MichaReiser](https://github.com/MichaReiser) 255 | - [@carljm](https://github.com/carljm) 256 | - [@abhijeetbodas2001](https://github.com/abhijeetbodas2001) 257 | - [@zanieb](https://github.com/zanieb) 258 | - [@AlexWaygood](https://github.com/AlexWaygood) 259 | - [@dcreager](https://github.com/dcreager) 260 | - [@mtshiba](https://github.com/mtshiba) 261 | - [@sharkdp](https://github.com/sharkdp) 262 | 263 | ## 0.0.0-alpha.8 264 | 265 | ### Changes 266 | 267 | - Add `--config` CLI arg ([#17697](https://github.com/astral-sh/ruff/pull/17697)) 268 | - Add CLI documentation and update README ([#284](https://github.com/astral-sh/ty/pull/284)) 269 | - Add a warning about pre-release status to the CLI ([#17983](https://github.com/astral-sh/ruff/pull/17983)) 270 | - Add missing bitwise-operator branches for boolean and integer arithmetic ([#17949](https://github.com/astral-sh/ruff/pull/17949)) 271 | - Add progress bar for `ty check` ([#17965](https://github.com/astral-sh/ruff/pull/17965)) 272 | - Add CLI reference ([#17978](https://github.com/astral-sh/ruff/pull/17978)) 273 | - Change default severity for `unbound-reference` to `error` ([#17936](https://github.com/astral-sh/ruff/pull/17936)) 274 | - Change range of `revealed-type` diagnostic to be the range of the argument passed in, not the whole call ([#17980](https://github.com/astral-sh/ruff/pull/17980)) 275 | - Default to latest supported Python version ([#17938](https://github.com/astral-sh/ruff/pull/17938)) 276 | - Display "All checks passed!" message in green ([#17982](https://github.com/astral-sh/ruff/pull/17982)) 277 | - Document configuration schema ([#17950](https://github.com/astral-sh/ruff/pull/17950)) 278 | - Generate and add rules table ([#17953](https://github.com/astral-sh/ruff/pull/17953)) 279 | - Handle type variables that have other type variables as a default ([#17956](https://github.com/astral-sh/ruff/pull/17956)) 280 | - Ignore `possibly-unresolved-reference` by default ([#17934](https://github.com/astral-sh/ruff/pull/17934)) 281 | - Implement `global` handling and `load-before-global-declaration` syntax error ([#17637](https://github.com/astral-sh/ruff/pull/17637)) 282 | - Make `unused-ignore-comment` disabled by default for now ([#17955](https://github.com/astral-sh/ruff/pull/17955)) 283 | - Recognise functions containing `yield from` expressions as being generator functions ([#17930](https://github.com/astral-sh/ruff/pull/17930)) 284 | - Fix stack overflow on recursive protocols ([#17929](https://github.com/astral-sh/ruff/pull/17929)) 285 | - Report duplicate `Protocol` or `Generic` base classes with `[duplicate-base]`, not `[inconsistent-mro]` ([#17971](https://github.com/astral-sh/ruff/pull/17971)) 286 | - Respect the gradual guarantee when reporting errors in resolving MROs ([#17962](https://github.com/astral-sh/ruff/pull/17962)) 287 | - Support `typing.Self` in methods ([#17689](https://github.com/astral-sh/ruff/pull/17689)) 288 | - Support extending `__all__` from an imported module even when the module is not an `ExprName` node ([#17947](https://github.com/astral-sh/ruff/pull/17947)) 289 | - Support extending `__all__` with a literal tuple or set as well as a literal list ([#17948](https://github.com/astral-sh/ruff/pull/17948)) 290 | - Understand classes that inherit from subscripted `Protocol[]` as generic ([#17832](https://github.com/astral-sh/ruff/pull/17832)) 291 | - Update ty metadata ([#17943](https://github.com/astral-sh/ruff/pull/17943)) 292 | - Add `py.typed` ([#276](https://github.com/astral-sh/ty/pull/276)) 293 | - Bottom-up improvement of diagnostic messages for union type function calls ([#17984](https://github.com/astral-sh/ruff/pull/17984)) 294 | - Fix more ecosystem/fuzzer panics with fixpoint ([#17758](https://github.com/astral-sh/ruff/pull/17758)) 295 | - Remove `lint:` prefix from top-level diagnostic preamble ([#17987](https://github.com/astral-sh/ruff/pull/17987)) 296 | 297 | ### Contributors 298 | 299 | - [@Glyphack](https://github.com/Glyphack) 300 | - [@BurntSushi](https://github.com/BurntSushi) 301 | - [@paul-nameless](https://github.com/paul-nameless) 302 | - [@MichaReiser](https://github.com/MichaReiser) 303 | - [@ntbre](https://github.com/ntBre) 304 | - [@ibraheemdev](https://github.com/ibraheemdev) 305 | - [@sharkdp](https://github.com/sharkdp) 306 | - [@thejchap](https://github.com/thejchap) 307 | - [@carljm](https://github.com/carljm) 308 | - [@jorenham](https://github.com/jorenham) 309 | - [@AlexWaygood](https://github.com/AlexWaygood) 310 | - [@dcreager](https://github.com/dcreager) 311 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | - [Our Pledge](#our-pledge) 4 | - [Our Standards](#our-standards) 5 | - [Enforcement Responsibilities](#enforcement-responsibilities) 6 | - [Scope](#scope) 7 | - [Enforcement](#enforcement) 8 | - [Enforcement Guidelines](#enforcement-guidelines) 9 | - [1. Correction](#1-correction) 10 | - [2. Warning](#2-warning) 11 | - [3. Temporary Ban](#3-temporary-ban) 12 | - [4. Permanent Ban](#4-permanent-ban) 13 | - [Attribution](#attribution) 14 | 15 | ## Our Pledge 16 | 17 | We as members, contributors, and leaders pledge to make participation in our 18 | community a harassment-free experience for everyone, regardless of age, body 19 | size, visible or invisible disability, ethnicity, sex characteristics, gender 20 | identity and expression, level of experience, education, socio-economic status, 21 | nationality, personal appearance, race, religion, or sexual identity 22 | and orientation. 23 | 24 | We pledge to act and interact in ways that contribute to an open, welcoming, 25 | diverse, inclusive, and healthy community. 26 | 27 | ## Our Standards 28 | 29 | Examples of behavior that contributes to a positive environment for our 30 | community include: 31 | 32 | - Demonstrating empathy and kindness toward other people 33 | - Being respectful of differing opinions, viewpoints, and experiences 34 | - Giving and gracefully accepting constructive feedback 35 | - Accepting responsibility and apologizing to those affected by our mistakes, 36 | and learning from the experience 37 | - Focusing on what is best not just for us as individuals, but for the 38 | overall community 39 | 40 | Examples of unacceptable behavior include: 41 | 42 | - The use of sexualized language or imagery, and sexual attention or 43 | advances of any kind 44 | - Trolling, insulting or derogatory comments, and personal or political attacks 45 | - Public or private harassment 46 | - Publishing others' private information, such as a physical or email 47 | address, without their explicit permission 48 | - Other conduct which could reasonably be considered inappropriate in a 49 | professional setting 50 | 51 | ## Enforcement Responsibilities 52 | 53 | Community leaders are responsible for clarifying and enforcing our standards of 54 | acceptable behavior and will take appropriate and fair corrective action in 55 | response to any behavior that they deem inappropriate, threatening, offensive, 56 | or harmful. 57 | 58 | Community leaders have the right and responsibility to remove, edit, or reject 59 | comments, commits, code, wiki edits, issues, and other contributions that are 60 | not aligned to this Code of Conduct, and will communicate reasons for moderation 61 | decisions when appropriate. 62 | 63 | ## Scope 64 | 65 | This Code of Conduct applies within all community spaces, and also applies when 66 | an individual is officially representing the community in public spaces. 67 | Examples of representing our community include using an official e-mail address, 68 | posting via an official social media account, or acting as an appointed 69 | representative at an online or offline event. 70 | 71 | ## Enforcement 72 | 73 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 74 | reported to the community leaders responsible for enforcement at 75 | . 76 | All complaints will be reviewed and investigated promptly and fairly. 77 | 78 | All community leaders are obligated to respect the privacy and security of the 79 | reporter of any incident. 80 | 81 | ## Enforcement Guidelines 82 | 83 | Community leaders will follow these Community Impact Guidelines in determining 84 | the consequences for any action they deem in violation of this Code of Conduct: 85 | 86 | ### 1. Correction 87 | 88 | **Community Impact**: Use of inappropriate language or other behavior deemed 89 | unprofessional or unwelcome in the community. 90 | 91 | **Consequence**: A private, written warning from community leaders, providing 92 | clarity around the nature of the violation and an explanation of why the 93 | behavior was inappropriate. A public apology may be requested. 94 | 95 | ### 2. Warning 96 | 97 | **Community Impact**: A violation through a single incident or series 98 | of actions. 99 | 100 | **Consequence**: A warning with consequences for continued behavior. No 101 | interaction with the people involved, including unsolicited interaction with 102 | those enforcing the Code of Conduct, for a specified period of time. This 103 | includes avoiding interactions in community spaces as well as external channels 104 | like social media. Violating these terms may lead to a temporary or 105 | permanent ban. 106 | 107 | ### 3. Temporary Ban 108 | 109 | **Community Impact**: A serious violation of community standards, including 110 | sustained inappropriate behavior. 111 | 112 | **Consequence**: A temporary ban from any sort of interaction or public 113 | communication with the community for a specified period of time. No public or 114 | private interaction with the people involved, including unsolicited interaction 115 | with those enforcing the Code of Conduct, is allowed during this period. 116 | Violating these terms may lead to a permanent ban. 117 | 118 | ### 4. Permanent Ban 119 | 120 | **Community Impact**: Demonstrating a pattern of violation of community 121 | standards, including sustained inappropriate behavior, harassment of an 122 | individual, or aggression toward or disparagement of classes of individuals. 123 | 124 | **Consequence**: A permanent ban from any sort of public interaction within 125 | the community. 126 | 127 | ## Attribution 128 | 129 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 130 | version 2.0, available [here](https://www.contributor-covenant.org/version/2/0/code_of_conduct.html). 131 | 132 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 133 | enforcement ladder](https://github.com/mozilla/diversity). 134 | 135 | For answers to common questions about this code of conduct, see the [FAQ](https://www.contributor-covenant.org/faq). 136 | Translations are available [here](https://www.contributor-covenant.org/translations). 137 | 138 | [homepage]: https://www.contributor-covenant.org 139 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | > [!IMPORTANT] 4 | > If you want to contribute changes to ty's core, please check out the 5 | > [dedicated `ty` contributing guide](https://github.com/astral-sh/ruff/blob/main/crates/ty/CONTRIBUTING.md). 6 | > Keep reading if you want to contribute to ty's documentation or release process instead. 7 | 8 | ## Repository structure 9 | 10 | This repository contains ty's documentation and release infrastructure. The core of ty's Rust codebase is 11 | located in the [Ruff](https://github.com/astral-sh/ruff) repository. While the relationship between these 12 | two projects will evolve over time, they currently share foundational crates and it's easiest to use a single 13 | repository for the Rust development. 14 | 15 | ty's command-line help text, and part of `docs/reference/`, are auto-generated from Rust code in the Ruff 16 | repository, using generation scripts that live in 17 | [`crates/ruff_dev/src/`](https://github.com/astral-sh/ruff/blob/main/crates/ruff_dev/src/): 18 | 19 | - [Configuration options](docs/reference/configuration.md), from 20 | [ruff/crates/ty_project/src/metadata/options.rs](https://github.com/astral-sh/ruff/blob/main/crates/ty_project/src/metadata/options.rs) 21 | - [Rules](docs/reference/rules.md), from 22 | [ruff/crates/ty_python_semantic/src/](https://github.com/astral-sh/ruff/blob/main/crates/ty_python_semantic/src/) 23 | - [Command-line interface reference](docs/reference/cli.md), from 24 | [ruff/crates/ty/src/args.rs](https://github.com/astral-sh/ruff/blob/main/crates/ty/src/args.rs) 25 | 26 | The Ruff repository is included as a submodule inside this repository to allow ty's release tags to reflect 27 | an exact snapshot of the Ruff project. The submodule is only updated on release. To see the latest development 28 | code, check out the `main` branch of the Ruff repository. 29 | 30 | ## Getting started with the ty repository 31 | 32 | Clone the repository: 33 | 34 | ```shell 35 | git clone https://github.com/astral-sh/ty.git 36 | ``` 37 | 38 | Then, ensure the submodule is initialized: 39 | 40 | ```shell 41 | git submodule update --init --recursive 42 | ``` 43 | 44 | ### Prerequisites 45 | 46 | You'll need [uv](https://docs.astral.sh/uv/getting-started/installation/) (or `pipx` and `pip`) to 47 | run Python utility commands. 48 | 49 | You can optionally install pre-commit hooks to automatically run the validation checks 50 | when making a commit: 51 | 52 | ```shell 53 | uv tool install pre-commit 54 | pre-commit install 55 | ``` 56 | 57 | ## Building the Python package 58 | 59 | The Python package can be built with any Python build frontend (Maturin is used as a backend), e.g.: 60 | 61 | ```shell 62 | uv build 63 | ``` 64 | 65 | ## Updating the Ruff commit 66 | 67 | To update the Ruff submodule to the latest commit: 68 | 69 | ```shell 70 | git -C ruff pull origin main 71 | ``` 72 | 73 | Or, to update the Ruff submodule to a specific commit: 74 | 75 | ```shell 76 | git -C ruff checkout 77 | ``` 78 | 79 | To commit the changes: 80 | 81 | ```shell 82 | commit=$(git -C ruff rev-parse --short HEAD) 83 | git switch -c "sync/ruff-${commit}" 84 | git add ruff 85 | git commit -m "Update ruff submodule to https://github.com/astral-sh/ruff/commit/${commit}" 86 | ``` 87 | 88 | To restore the Ruff submodule to a clean-state, reset, then update the submodule: 89 | 90 | ```shell 91 | git -C ruff reset --hard 92 | git submodule update 93 | ``` 94 | 95 | To restore the Ruff submodule to the commit from `main`: 96 | 97 | ```shell 98 | git -C ruff reset --hard $(git ls-tree main -- ruff | awk '{print $3}') 99 | git add ruff 100 | ``` 101 | 102 | ## Releasing ty 103 | 104 | Releases can only be performed by Astral team members. 105 | 106 | Preparation for the release is automated. 107 | 108 | 1. Run `./scripts/release.sh`. 109 | 110 | The release script will: 111 | 112 | - Update the Ruff submodule to the latest commit on `main` upstream 113 | - Generate changelog entries based on pull requests here, and in Ruff 114 | - Bump the versions in the `pyproject.toml` and `dist-workspace.toml` 115 | - Update the generated reference documentation in `docs/reference` 116 | 117 | 1. Editorialize the `CHANGELOG.md` file to ensure entries are consistently styled. 118 | 119 | This usually involves simple edits, like consistent capitalization and leading verbs like 120 | "Add ...". 121 | 122 | 1. Create a pull request with the changelog and version changes 123 | 124 | The pull requests are usually titled as: `Bump version to `. 125 | 126 | Binary builds will automatically be tested for the release. 127 | 128 | 1. Merge the pull request. 129 | 130 | 1. Run the [release workflow](https://github.com/astral-sh/ty/actions/workflows/release.yml) with 131 | the version tag. 132 | 133 | **Do not include a leading `v`**. 134 | 135 | When running the release workflow for pre-release versions, use the Cargo version format (not PEP 136 | 440), e.g. `0.0.0-alpha.5` (not `0.0.0a5`). For stable releases, these formats are identical. 137 | 138 | The release will automatically be created on GitHub after the distributions are published. 139 | 140 | 1. Run `uv run --no-project ./scripts/update_schemastore.py` 141 | 142 | This script will prepare a branch to update the `ty.json` schema in the `schemastore` 143 | repository. 144 | 145 | Follow the link in the script's output to submit the pull request. 146 | 147 | The script is a no-op if there are no schema changes. 148 | 149 | 1. If necessary, update and release [`ty-vscode`](https://github.com/astral-sh/ty-vscode). 150 | 151 | The instructions are in the `ty-vscode` repository. 152 | 153 | Updating the extension bumps the bundled ty version, which is used if ty is not installed. 154 | 155 | Updating the extension is required for: 156 | 157 | - Minor releases 158 | - Patch releases, if a critical bug in `ty server` is fixed 159 | - When releasing new `ty server` features that require changes in `ty-vscode` 160 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM --platform=$BUILDPLATFORM ubuntu AS build 2 | ENV HOME="/root" 3 | WORKDIR $HOME 4 | 5 | RUN apt update && apt install -y build-essential curl python3-venv 6 | 7 | # Setup zig as cross compiling linker 8 | RUN python3 -m venv $HOME/.venv 9 | RUN .venv/bin/pip install cargo-zigbuild 10 | ENV PATH="$HOME/.venv/bin:$PATH" 11 | 12 | # Change to the ruff directory, which is the root for our build 13 | WORKDIR $HOME/ruff 14 | 15 | # Install rust 16 | ARG TARGETPLATFORM 17 | RUN case "$TARGETPLATFORM" in \ 18 | "linux/arm64") echo "aarch64-unknown-linux-musl" > rust_target.txt ;; \ 19 | "linux/amd64") echo "x86_64-unknown-linux-musl" > rust_target.txt ;; \ 20 | *) exit 1 ;; \ 21 | esac 22 | # Update rustup whenever we bump the rust version 23 | COPY ruff/rust-toolchain.toml rust-toolchain.toml 24 | RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --target $(cat rust_target.txt) --profile minimal --default-toolchain none 25 | ENV PATH="$HOME/.cargo/bin:$PATH" 26 | # Installs the correct toolchain version from rust-toolchain.toml and then the musl target 27 | RUN rustup target add $(cat rust_target.txt) 28 | 29 | # Build 30 | COPY ruff/crates crates 31 | COPY ruff/Cargo.toml Cargo.toml 32 | COPY ruff/Cargo.lock Cargo.lock 33 | RUN cargo zigbuild --bin ty --target $(cat rust_target.txt) --release 34 | RUN cp target/$(cat rust_target.txt)/release/ty /ty 35 | # TODO: Optimize binary size, with a version that also works when cross compiling 36 | # RUN strip --strip-all /ty 37 | 38 | FROM scratch 39 | COPY --from=build /ty /ty 40 | WORKDIR /io 41 | ENTRYPOINT ["/ty"] 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Astral Software Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ty 2 | 3 | [![PyPI](https://img.shields.io/pypi/v/ty.svg)](https://pypi.python.org/pypi/ty) 4 | [![Discord](https://img.shields.io/badge/Discord-%235865F2.svg?logo=discord&logoColor=white)](https://discord.com/invite/astral-sh) 5 | 6 | An extremely fast Python type checker and language server, written in Rust. 7 | 8 | > [!WARNING] 9 | > 10 | > ty is in preview and is not ready for production use. 11 | > 12 | > We're working hard to make ty stable and feature-complete, but until then, expect to encounter bugs, 13 | > missing features, and fatal errors. 14 | 15 | ## Getting started 16 | 17 | Try out the [online playground](https://play.ty.dev), or run ty with 18 | [uvx](https://docs.astral.sh/uv/guides/tools/#running-tools) to get started quickly: 19 | 20 | ```shell 21 | uvx ty 22 | ``` 23 | 24 | For other ways to install ty, see the [installation](./docs/README.md#installation) documentation. 25 | 26 | If you do not provide a subcommand, ty will list available commands — for detailed information about 27 | command-line options, see the [CLI reference](./docs/reference/cli.md). 28 | 29 | Use the `check` command to run the type checker: 30 | 31 | ```shell 32 | uvx ty check 33 | ``` 34 | 35 | ty will run on all Python files in the working directory and or subdirectories. If used from a 36 | project, ty will run on all Python files in the project (starting in the directory with the 37 | `pyproject.toml`) 38 | 39 | You can also provide specific paths to check: 40 | 41 | ```shell 42 | uvx ty check example.py 43 | ``` 44 | 45 | When type checking, ty will find installed packages in the active virtual environment (via 46 | `VIRTUAL_ENV`) or discover a virtual environment named `.venv` in the project root or working 47 | directory. It will not find packages in non-virtual environments without specifying the target path 48 | with `--python`. See the [module discovery](./docs/README.md#module-discovery) documentation for 49 | details. 50 | 51 | ## Learning more 52 | 53 | To learn more about using ty, see the [documentation](./docs/README.md). 54 | 55 | ## Getting involved 56 | 57 | If you have questions or want to report a bug, please open an 58 | [issue](https://github.com/astral-sh/ty/issues) in this repository. 59 | 60 | Development of this project takes place in the [Ruff](https://github.com/astral-sh/ruff) repository 61 | at this time. Please [open pull requests](https://github.com/astral-sh/ruff/pulls) there for changes 62 | to anything in the `ruff` submodule (which includes all of the Rust source code). 63 | 64 | See the 65 | [contributing guide](./CONTRIBUTING.md) for more details. 66 | 67 | ## License 68 | 69 | ty is licensed under the MIT license ([LICENSE](LICENSE) or 70 | ). 71 | 72 | Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in ty 73 | by you, as defined in the MIT license, shall be licensed as above, without any additional terms or 74 | conditions. 75 | 76 |
77 | 78 | Made by Astral 79 | 80 |
81 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security policy 2 | 3 | ## Reporting a vulnerability 4 | 5 | If you have found a possible vulnerability, please email `security at astral dot sh`. 6 | 7 | ## Bug bounties 8 | 9 | While we sincerely appreciate and encourage reports of suspected security problems, please note that 10 | Astral does not currently run any bug bounty programs. 11 | 12 | ## Vulnerability disclosures 13 | 14 | Critical vulnerabilities will be disclosed via GitHub's 15 | [security advisory](https://github.com/astral-sh/ty/security) system. 16 | -------------------------------------------------------------------------------- /_typos.toml: -------------------------------------------------------------------------------- 1 | [files] 2 | extend-exclude = [] 3 | 4 | [default.extend-words] 5 | 6 | [default] 7 | extend-ignore-re = [ 8 | # Line ignore with trailing "spellchecker:disable-line" 9 | "(?Rm)^.*#\\s*spellchecker:disable-line$", 10 | "LICENSEs", 11 | ] 12 | -------------------------------------------------------------------------------- /dist-workspace.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = ["cargo:./ruff"] 3 | packages = ["ty"] 4 | version = "0.0.1-alpha.7" 5 | 6 | # Config for 'dist' 7 | [dist] 8 | # The preferred dist version to use in CI (Cargo.toml SemVer syntax) 9 | cargo-dist-version = "0.28.5-prerelease.3" 10 | # Whether to consider the binaries in a package for distribution (defaults true) 11 | dist = false 12 | # CI backends to support 13 | ci = "github" 14 | # The installers to generate for each app 15 | installers = ["shell", "powershell"] 16 | # The archive format to use for windows builds (defaults .zip) 17 | windows-archive = ".zip" 18 | # The archive format to use for non-windows builds (defaults .tar.xz) 19 | unix-archive = ".tar.gz" 20 | # Target platforms to build apps for (Rust target-triple syntax) 21 | targets = [ 22 | "aarch64-apple-darwin", 23 | "aarch64-unknown-linux-gnu", 24 | "aarch64-unknown-linux-musl", 25 | "aarch64-pc-windows-msvc", 26 | "arm-unknown-linux-musleabihf", 27 | "armv7-unknown-linux-gnueabihf", 28 | "armv7-unknown-linux-musleabihf", 29 | "x86_64-apple-darwin", 30 | "powerpc64-unknown-linux-gnu", 31 | "powerpc64le-unknown-linux-gnu", 32 | "s390x-unknown-linux-gnu", 33 | "x86_64-unknown-linux-gnu", 34 | "x86_64-unknown-linux-musl", 35 | "x86_64-pc-windows-msvc", 36 | "i686-unknown-linux-gnu", 37 | "i686-unknown-linux-musl", 38 | "i686-pc-windows-msvc", 39 | ] 40 | # Whether to auto-include files like READMEs, LICENSEs, and CHANGELOGs (default true) 41 | auto-includes = false 42 | # Whether dist should create a Github Release or use an existing draft 43 | create-release = true 44 | # Which actions to run on pull requests 45 | pr-run-mode = "plan" 46 | # Whether to publish prereleases to package managers 47 | publish-prereleases = true 48 | # Whether CI should trigger releases with dispatches instead of tag pushes 49 | dispatch-releases = true 50 | # Which phase dist should use to create the GitHub release 51 | github-release = "announce" 52 | # Whether CI should include auto-generated code to build local artifacts 53 | build-local-artifacts = false 54 | # Local artifacts jobs to run in CI 55 | local-artifacts-jobs = ["./build-binaries", "./build-docker"] 56 | # Publish jobs to run in CI 57 | publish-jobs = ["./publish-pypi"] 58 | # Post-announce jobs to run in CI 59 | post-announce-jobs = [] 60 | # Custom permissions for GitHub Jobs 61 | github-custom-job-permissions = { "build-docker" = { packages = "write", contents = "read" } } 62 | # Whether to install an updater program 63 | install-updater = false 64 | # Path that installers should place binaries in 65 | install-path = ["$XDG_BIN_HOME/", "$XDG_DATA_HOME/../bin", "~/.local/bin"] 66 | 67 | [dist.github-custom-runners] 68 | global = "depot-ubuntu-latest-4" 69 | 70 | [dist.github-action-commits] 71 | "actions/checkout" = "85e6279cec87321a52edac9c87bce653a07cf6c2" # v4 72 | "actions/upload-artifact" = "6027e3dd177782cd8ab9af838c04fd81a07f1d47" # v4.6.2 73 | "actions/download-artifact" = "d3f86a106a0bac45b974a628896c90dbdf5c8093" # v4.3.0 74 | "actions/attest-build-provenance" = "c074443f1aee8d4aeeae555aebba3282517141b2" #v2.2.3 75 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # ty 2 | 3 | **[Installation](#installation)** | 4 | **[Usage](#usage)** | 5 | **[Module discovery](#module-discovery)** | 6 | **[Editor integration](#editor-integration)** | 7 | **[Rules](#rules)** | 8 | **[Suppressions](#suppressions)** | 9 | **[Exit codes](#exit-codes)** | 10 | **[Reference](#reference)** 11 | 12 | ## Getting started 13 | 14 | For a quick guide on getting started, see the top-level [README](../README.md#getting-started). 15 | 16 | ## Installation 17 | 18 | ### Adding ty to your project 19 | 20 | Use [uv](https://github.com/astral-sh/uv) (or your project manager of choice) to add ty as a 21 | development dependency: 22 | 23 | ```shell 24 | uv add --dev ty 25 | ``` 26 | 27 | Adding ty as a dependency ensures that all developers on the project are using the same version of 28 | ty. 29 | 30 | Then, use `uv run` to invoke ty: 31 | 32 | ```shell 33 | uv run ty 34 | ``` 35 | 36 | ### Installing globally 37 | 38 | Install ty globally with uv: 39 | 40 | ```shell 41 | uv tool install ty@latest 42 | ``` 43 | 44 | Or, pipx: 45 | 46 | ```shell 47 | pipx install ty 48 | ``` 49 | 50 | ### Installing with pip 51 | 52 | Install ty into your current Python environment with pip: 53 | 54 | ```shell 55 | pip install ty 56 | ``` 57 | 58 | ### Usage 59 | 60 | Run [`ty check`](./reference/cli.md#ty-check), in your project's top-level directory, 61 | to check the project for type errors using ty's default configuration. 62 | 63 | If this provokes a cascade of errors, and you are using the standard library `venv` module 64 | to provide your virtual environment, add the venv directory to your `.gitignore` 65 | or `.ignore` file and then retry. 66 | 67 | ## Module discovery 68 | 69 | ### First-party modules 70 | 71 | First-party modules are Python files that are part of your project source code. 72 | 73 | By default, ty searches for first-party modules in the project's root directory or the `src` 74 | directory, if present. 75 | 76 | If your project uses a different layout, configure the project's 77 | [`src.root`](./reference/configuration.md#root) in your `pyproject.toml` or `ty.toml`. For example, 78 | if your project's code is in an `app/` directory: 79 | 80 | ```text 81 | example-pkg 82 | ├── README.md 83 | ├── pyproject.toml 84 | └── app 85 | └── example_pkg 86 | └── __init__.py 87 | ``` 88 | 89 | then set [`src.root`](./reference/configuration.md#root) in your `pyproject.toml` to `./app`: 90 | 91 | ```toml 92 | [tool.ty.src] 93 | root = "./app" 94 | ``` 95 | 96 | ### Third-party modules 97 | 98 | Third-party modules are Python packages that are not part of your project or the standard library. 99 | These are usually declared as dependencies in a `pyproject.toml` or `requirements.txt` file 100 | and installed using a package manager like uv or pip. Examples of popular third-party 101 | modules are `requests`, `numpy` and `django`. 102 | 103 | ty searches for third-party modules in the configured [Python environment](#python-environment). 104 | 105 | ### Python environment 106 | 107 | The Python environment is used for discovery of third-party modules. 108 | 109 | By default, ty will attempt to discover a virtual environment. 110 | 111 | First, ty checks for an active virtual environment using the `VIRTUAL_ENV` environment variable. If 112 | not set, ty will search for a `.venv` directory in the project root or working directory. ty only 113 | supports discovery of virtual environments at this time. 114 | 115 | > [!NOTE] 116 | > 117 | > When using project management tools, such as uv or Poetry, the `run` command usually automatically 118 | > activates the virtual environment and will be detected by ty. 119 | 120 | The Python environment may be explicitly configured using the 121 | [`environment.python`](./reference/configuration.md#python) setting or 122 | [`--python`](./reference/cli.md#ty-check--python) flag. 123 | 124 | When setting the environment explicitly, non-virtual environments can be provided. 125 | 126 | ## Python version 127 | 128 | The Python version affects allowed syntax, type definitions of the standard library, and type 129 | definitions of first- and third-party modules that are conditional on the Python version. 130 | 131 | For example, Python 3.10 introduced support for `match` statements and added the 132 | `sys.stdlib_module_names` symbol to the standard library. Syntactic features always 133 | need to be available in the lowest supported Python version, but symbols may be used 134 | in a `sys.version_info` conditional branch: 135 | 136 | ```python 137 | import sys 138 | 139 | # `invalid-syntax` error if `python-version` is set to 3.9 or lower: 140 | match "echo hello".split(): 141 | case ["echo", message]: 142 | print(message) 143 | case _: 144 | print("unknown command") 145 | 146 | # `unresolved-attribute` error if `python-version` is set to 3.9 or lower: 147 | print(sys.stdlib_module_names) 148 | 149 | if sys.version_info >= (3, 10): 150 | # ok, because the usage is guarded by a version check: 151 | print(sys.stdlib_module_names) 152 | ``` 153 | 154 | 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 155 | used as the target Python version, ensuring that features and symbols only available in newer Python 156 | versions are not used. 157 | 158 | If the `requires-python` field is not available, the latest stable version supported by ty is used, 159 | which is currently 3.13. 160 | 161 | ty will not infer the Python version from the Python environment at this time. 162 | 163 | The Python version may be explicitly specified using the 164 | [`python-version`](./reference/configuration.md#python-version) setting or the 165 | [`--python-version`](./reference/cli.md#ty-check--python-version) flag. 166 | 167 | ## Excluding files 168 | 169 | By default, ty ignores files listed in an `.ignore` or `.gitignore` file. 170 | 171 | To disable this functionality, set [`respect-ignore-files`](./reference/configuration.md#respect-ignore-files) to `false`. 172 | 173 | You may also explicitly pass the paths that ty should check, e.g.: 174 | 175 | ```shell 176 | ty check src scripts/benchmark.py 177 | ``` 178 | 179 | We plan on adding dedicated options for including and excluding files in future releases. 180 | 181 | ## Editor integration 182 | 183 | ty can be integrated with various editors to provide a seamless development experience. 184 | 185 | ### VS Code 186 | 187 | The Astral team maintains an official VS Code extension. 188 | 189 | Install the [ty extension](https://marketplace.visualstudio.com/items?itemName=astral-sh.ty) from the VS Code Marketplace. 190 | 191 | See the extension's [README](https://github.com/astral-sh/ty-vscode) for more details on usage. 192 | 193 | ### Other editors 194 | 195 | ty can be used with any editor that supports the [language server 196 | protocol](https://microsoft.github.io/language-server-protocol/). 197 | 198 | To start the language server, use the `server` subcommand: 199 | 200 | ```shell 201 | ty server 202 | ``` 203 | 204 | Refer to your editor's documentation to learn how to connect to an LSP server. 205 | 206 | See the [editor settings](./reference/editor-settings.md) for more details on configuring the language 207 | server. 208 | 209 | #### Neovim 210 | 211 | For Neovim 0.10 or earlier (with [`nvim-lspconfig`](https://github.com/neovim/nvim-lspconfig)): 212 | 213 | ```lua 214 | require('lspconfig').ty.setup({ 215 | init_options = { 216 | settings = { 217 | -- ty language server settings go here 218 | } 219 | } 220 | }) 221 | ``` 222 | 223 | For Neovim 0.11+ (with [`vim.lsp.config`]()): 224 | 225 | ```lua 226 | -- Optional: Only required if you need to update the language server settings 227 | vim.lsp.config('ty', { 228 | init_options = { 229 | settings = { 230 | -- ty language server settings go here 231 | } 232 | } 233 | }) 234 | 235 | -- Required: Enable the language server 236 | vim.lsp.enable('ty') 237 | ``` 238 | 239 | ## Rules 240 | 241 | Rules are individual checks that ty performs to detect common issues in your code, such as 242 | incompatible assignments, missing imports, or invalid type annotations. Each rule focuses on a 243 | specific pattern and can be turned on or off depending on your project’s needs. 244 | 245 | See [rules](./reference/rules.md) for an enumeration of all supported rules. 246 | 247 | ### Rule levels 248 | 249 | Each rule has a configurable level: 250 | 251 | - `error`: violations are reported as errors and ty exits with an exit code of 1 if there's any. 252 | - `warn`: violations are reported as warnings. Depending on your configuration, ty exits with an exit code of 0 if there are only warning violations (default) or 1 when using `--error-on-warning`. 253 | - `ignore`: the rule is turned off 254 | 255 | You can configure the level for each rule on the command line using the `--warn`, `--error`, and 256 | `--ignore` flags. For example: 257 | 258 | ```shell 259 | 260 | ty check \ 261 | --warn unused-ignore-comment \ # Make `unused-ignore-comment` a warning 262 | --ignore redundant-cast \ # Disable `redundant-cast` 263 | --error possibly-unbound-attribute \ # Error on `possibly-unbound-attribute` 264 | --error possibly-unbound-import # Error on `possibly-unbound-import` 265 | ``` 266 | 267 | The options can be repeated. Subsequent options override earlier options. 268 | 269 | Rule levels can also be changed in the [`rules`](./reference/configuration.md#rules) section of a 270 | [configuration file](#configuration-files). 271 | 272 | For example, the following is equivalent to the command above: 273 | 274 | ```toml 275 | [tool.ty.rules] 276 | unused-ignore-comment = "warn" 277 | redundant-cast = "ignore" 278 | possibly-unbound-attribute = "error" 279 | possibly-unbound-import = "error" 280 | ``` 281 | 282 | ## Suppressions 283 | 284 | Rules can also be ignored in specific locations in your code (instead of disabling the rule 285 | entirely) to silence false positives or permissible violations. 286 | 287 | > [!NOTE] 288 | > 289 | > To disable a rule entirely, set it to the `ignore` level as described in [rule levels](#rule-levels). 290 | 291 | ### ty suppression comments 292 | 293 | To suppress a rule violation inline add a `# ty: ignore[]` comment at the end of the line: 294 | 295 | ```py 296 | a = 10 + "test" # ty: ignore[unsupported-operator] 297 | ``` 298 | 299 | Rule violations spanning multiple lines can be suppressed by adding the comment at the end of the 300 | violation's first or last line: 301 | 302 | ```py 303 | def add_three(a: int, b: int, c: int): ... 304 | 305 | # on the first line 306 | 307 | add_three( # ty: ignore[missing-argument] 308 | 3, 309 | 2 310 | ) 311 | 312 | # or, on the last line 313 | 314 | add_three( 315 | 3, 316 | 2 317 | ) # ty: ignore[missing-argument] 318 | ``` 319 | 320 | To suppress multiple violations on a single line, enumerate each rule separated by a comma: 321 | 322 | ```python 323 | add_three("one", 5) # ty: ignore[missing-argument, invalid-argument-type] 324 | ``` 325 | 326 | > [!NOTE] 327 | > 328 | > Enumerating rule names (e.g., `[rule1, rule2]`) is optional. However, we strongly recommend 329 | > including suppressing specific rules to avoid accidental suppression of other errors. 330 | 331 | ### Standard suppression comments 332 | 333 | ty supports the standard [`type:ignore`](https://typing.python.org/en/latest/spec/directives.html#type-ignore-comments) comment 334 | format introduced by PEP 484. 335 | 336 | ty handles these similarly to `ty: ignore` comments, but suppresses all violations on that line, 337 | even when `type: ignore[code]` is used. 338 | 339 | ```python 340 | # Ignore all typing errors on the next line 341 | add_three("one", 5) # type: ignore 342 | ``` 343 | 344 | ### Unused suppression comments 345 | 346 | If the [`unused-ignore-comment`](./reference/rules.md#unused-ignore-comment) rule is enabled, ty 347 | will report unused `ty: ignore` and `type: ignore` comments. 348 | 349 | `unused-ignore-comment` violations can only be suppressed using `# ty: ignore[unused-ignore-comment]`. 350 | They cannot be suppressed using `# ty: ignore` without a rule code or `# type: ignore`. 351 | 352 | ### `@no_type_check` directive 353 | 354 | ty supports the 355 | [`@no_type_check`](https://typing.python.org/en/latest/spec/directives.html#no-type-check) decorator 356 | to suppress all violations inside a function. 357 | 358 | ```python 359 | from typing import no_type_check 360 | 361 | def add_three(a: int, b: int, c: int): 362 | a + b + c 363 | 364 | @no_type_check 365 | def main(): 366 | add_three(3, 4) 367 | ``` 368 | 369 | Decorating a class with `@no_type_check` isn't supported. 370 | 371 | ## Configuration 372 | 373 | ### Configuration files 374 | 375 | ty supports persistent configuration files at both the project- and user-level. 376 | 377 | Specifically, ty will search for a `pyproject.toml` or `ty.toml` file in the current directory, or in the nearest parent directory. 378 | 379 | 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`: 380 | 381 | **`pyproject.toml`**: 382 | 383 | ```toml 384 | [tool.ty.rules] 385 | index-out-of-bounds = "ignore" 386 | ``` 387 | 388 | (If there is no `tool.ty` table, the `pyproject.toml` file will be ignored, and ty will continue searching in the directory hierarchy.) 389 | 390 | ty will also search for `ty.toml` files, which follow an identical structure, but omit the `[tool.ty]` prefix. For example: 391 | 392 | **`ty.toml`**: 393 | 394 | ```toml 395 | [rules] 396 | index-out-of-bounds = "ignore" 397 | ``` 398 | 399 | > [!NOTE] 400 | > `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. 401 | 402 | 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. 403 | 404 | 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. 405 | 406 | 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. 407 | 408 | Settings provided via command line take precedence over persistent configuration. 409 | 410 | See the [configuration](./reference/configuration.md) reference for an enumeration of the available settings. 411 | 412 | ## Exit codes 413 | 414 | - `0` if no violations with severity `error` or higher were found. 415 | - `1` if any violations with severity `error` were found. 416 | - `2` if ty terminates abnormally due to invalid CLI options. 417 | - `101` if ty terminates abnormally due to an internal error. 418 | 419 | ty supports two command line arguments that change how exit codes work: 420 | 421 | - `--exit-zero`: ty will exit with `0` even if violations were found. 422 | - `--error-on-warning`: ty will exit with `1` if it finds any violations with severity `warning` or higher. 423 | 424 | ## Reference 425 | 426 | For more details, see the reference documentation: 427 | 428 | - [Commands](./reference/cli.md) 429 | - [Rules](./reference/rules.md) 430 | - [Configuration](./reference/configuration.md) 431 | - [Environment variables](./reference/env.md) 432 | -------------------------------------------------------------------------------- /docs/reference/cli.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # CLI Reference 4 | 5 | ## ty 6 | 7 | An extremely fast Python type checker. 8 | 9 |

Usage

10 | 11 | ``` 12 | ty 13 | ``` 14 | 15 |

Commands

16 | 17 |
ty check

Check a project for type errors

18 |
ty server

Start the language server

19 |
ty version

Display ty's version

20 |
ty help

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

21 |
22 | 23 | ## ty check 24 | 25 | Check a project for type errors 26 | 27 |

Usage

28 | 29 | ``` 30 | ty check [OPTIONS] [PATH]... 31 | ``` 32 | 33 |

Arguments

34 | 35 |
PATHS

List of files or directories to check [default: the project root]

36 |
37 | 38 |

Options

39 | 40 |
--color when

Control when colored output is used

41 |

Possible values:

42 |
    43 |
  • auto: Display colors if the output goes to an interactive terminal
  • 44 |
  • always: Always display colors
  • 45 |
  • never: Never display colors
  • 46 |
--config, -c config-option

A TOML <KEY> = <VALUE> pair (such as you might find in a ty.toml configuration file) 47 | overriding a specific configuration option.

48 |

Overrides of individual settings using this option always take precedence 49 | over all configuration files.

50 |
--error rule

Treat the given rule as having severity 'error'. Can be specified multiple times.

51 |
--error-on-warning

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

52 |
--exit-zero

Always use exit code 0, even when there are error-level diagnostics

53 |
--extra-search-path path

Additional path to use as a module-resolution source (can be passed multiple times)

54 |
--help, -h

Print help (see a summary with '-h')

55 |
--ignore rule

Disables the rule. Can be specified multiple times.

56 |
--output-format output-format

The format to use for printing diagnostic messages

57 |

Possible values:

58 |
    59 |
  • full: Print diagnostics verbosely, with context and helpful hints [default]
  • 60 |
  • concise: Print diagnostics concisely, one per line
  • 61 |
--project project

Run the command within the given project directory.

62 |

All pyproject.toml files will be discovered by walking up the directory tree from the given project directory, as will the project's virtual environment (.venv) unless the venv-path option is set.

63 |

Other command-line arguments (such as relative paths) will be resolved relative to the current working directory.

64 |
--python path

Path to the Python environment.

65 |

ty uses the Python environment to resolve type information and third-party dependencies.

66 |

If not specified, ty will attempt to infer it from the VIRTUAL_ENV environment variable or discover a .venv directory in the project root or working directory.

67 |

If a path to a Python interpreter is provided, e.g., .venv/bin/python3, ty will attempt to find an environment two directories up from the interpreter's path, e.g., .venv. At this time, ty does not invoke the interpreter to determine the location of the environment. This means that ty will not resolve dynamic executables such as a shim.

68 |

ty will search in the resolved environments's site-packages directories for type information and third-party imports.

69 |
--python-platform, --platform platform

Target platform to assume when resolving types.

70 |

This is used to specialize the type of sys.platform and will affect the visibility of platform-specific functions and attributes. If the value is set to all, no assumptions are made about the target platform. If unspecified, the current system's platform will be used.

71 |
--python-version, --target-version version

Python version to assume when resolving types.

72 |

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.

73 |

By default, the Python version is inferred as the lower bound of the project's requires-python field from the pyproject.toml, if available. Otherwise, the latest stable version supported by ty is used, which is currently 3.13.

74 |

ty will not infer the Python version from the Python environment at this time.

75 |

Possible values:

76 |
    77 |
  • 3.7
  • 78 |
  • 3.8
  • 79 |
  • 3.9
  • 80 |
  • 3.10
  • 81 |
  • 3.11
  • 82 |
  • 3.12
  • 83 |
  • 3.13
  • 84 |
--respect-ignore-files

Respect file exclusions via .gitignore and other standard ignore files. Use --no-respect-gitignore to disable

85 |
--typeshed, --custom-typeshed-dir path

Custom directory to use for stdlib typeshed stubs

86 |
--verbose, -v

Use verbose output (or -vv and -vvv for more verbose output)

87 |
--warn rule

Treat the given rule as having severity 'warn'. Can be specified multiple times.

88 |
--watch, -W

Watch files for changes and recheck files related to the changed files

89 |
90 | 91 | ## ty server 92 | 93 | Start the language server 94 | 95 |

Usage

96 | 97 | ``` 98 | ty server 99 | ``` 100 | 101 |

Options

102 | 103 |
--help, -h

Print help

104 |
105 | 106 | ## ty version 107 | 108 | Display ty's version 109 | 110 |

Usage

111 | 112 | ``` 113 | ty version 114 | ``` 115 | 116 |

Options

117 | 118 |
--help, -h

Print help

119 |
120 | 121 | ## ty generate-shell-completion 122 | 123 | Generate shell completion 124 | 125 |

Usage

126 | 127 | ``` 128 | ty generate-shell-completion 129 | ``` 130 | 131 |

Arguments

132 | 133 |
SHELL
134 | 135 |

Options

136 | 137 |
--help, -h

Print help

138 |
139 | 140 | ## ty help 141 | 142 | Print this message or the help of the given subcommand(s) 143 | 144 |

Usage

145 | 146 | ``` 147 | ty help [COMMAND] 148 | ``` 149 | 150 | -------------------------------------------------------------------------------- /docs/reference/configuration.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Configuration 4 | #### `rules` 5 | 6 | Configures the enabled rules and their severity. 7 | 8 | See [the rules documentation](https://ty.dev/rules) for a list of all available rules. 9 | 10 | Valid severities are: 11 | 12 | * `ignore`: Disable the rule. 13 | * `warn`: Enable the rule and create a warning diagnostic. 14 | * `error`: Enable the rule and create an error diagnostic. 15 | ty will exit with a non-zero code if any error diagnostics are emitted. 16 | 17 | **Default value**: `{...}` 18 | 19 | **Type**: `dict[RuleName, "ignore" | "warn" | "error"]` 20 | 21 | **Example usage** (`pyproject.toml`): 22 | 23 | ```toml 24 | [tool.ty.rules] 25 | possibly-unresolved-reference = "warn" 26 | division-by-zero = "ignore" 27 | ``` 28 | 29 | --- 30 | 31 | ## `environment` 32 | 33 | #### `extra-paths` 34 | 35 | List of user-provided paths that should take first priority in the module resolution. 36 | Examples in other type checkers are mypy's `MYPYPATH` environment variable, 37 | or pyright's `stubPath` configuration setting. 38 | 39 | **Default value**: `[]` 40 | 41 | **Type**: `list[str]` 42 | 43 | **Example usage** (`pyproject.toml`): 44 | 45 | ```toml 46 | [tool.ty.environment] 47 | extra-paths = ["~/shared/my-search-path"] 48 | ``` 49 | 50 | --- 51 | 52 | #### `python` 53 | 54 | Path to the Python installation from which ty resolves type information and third-party dependencies. 55 | 56 | ty will search in the path's `site-packages` directories for type information and 57 | third-party imports. 58 | 59 | This option is commonly used to specify the path to a virtual environment. 60 | 61 | **Default value**: `null` 62 | 63 | **Type**: `str` 64 | 65 | **Example usage** (`pyproject.toml`): 66 | 67 | ```toml 68 | [tool.ty.environment] 69 | python = "./.venv" 70 | ``` 71 | 72 | --- 73 | 74 | #### `python-platform` 75 | 76 | Specifies the target platform that will be used to analyze the source code. 77 | If specified, ty will understand conditions based on comparisons with `sys.platform`, such 78 | as are commonly found in typeshed to reflect the differing contents of the standard library across platforms. 79 | 80 | If no platform is specified, ty will use the current platform: 81 | - `win32` for Windows 82 | - `darwin` for macOS 83 | - `android` for Android 84 | - `ios` for iOS 85 | - `linux` for everything else 86 | 87 | **Default value**: `` 88 | 89 | **Type**: `"win32" | "darwin" | "android" | "ios" | "linux" | str` 90 | 91 | **Example usage** (`pyproject.toml`): 92 | 93 | ```toml 94 | [tool.ty.environment] 95 | # Tailor type stubs and conditionalized type definitions to windows. 96 | python-platform = "win32" 97 | ``` 98 | 99 | --- 100 | 101 | #### `python-version` 102 | 103 | Specifies the version of Python that will be used to analyze the source code. 104 | The version should be specified as a string in the format `M.m` where `M` is the major version 105 | and `m` is the minor (e.g. `"3.0"` or `"3.6"`). 106 | If a version is provided, ty will generate errors if the source code makes use of language features 107 | that are not supported in that version. 108 | It will also understand conditionals based on comparisons with `sys.version_info`, such 109 | as are commonly found in typeshed to reflect the differing contents of the standard 110 | library across Python versions. 111 | 112 | **Default value**: `"3.13"` 113 | 114 | **Type**: `"3.7" | "3.8" | "3.9" | "3.10" | "3.11" | "3.12" | "3.13" | .` 115 | 116 | **Example usage** (`pyproject.toml`): 117 | 118 | ```toml 119 | [tool.ty.environment] 120 | python-version = "3.12" 121 | ``` 122 | 123 | --- 124 | 125 | #### `typeshed` 126 | 127 | Optional path to a "typeshed" directory on disk for us to use for standard-library types. 128 | If this is not provided, we will fallback to our vendored typeshed stubs for the stdlib, 129 | bundled as a zip file in the binary 130 | 131 | **Default value**: `null` 132 | 133 | **Type**: `str` 134 | 135 | **Example usage** (`pyproject.toml`): 136 | 137 | ```toml 138 | [tool.ty.environment] 139 | typeshed = "/path/to/custom/typeshed" 140 | ``` 141 | 142 | --- 143 | 144 | ## `src` 145 | 146 | #### `respect-ignore-files` 147 | 148 | Whether to automatically exclude files that are ignored by `.ignore`, 149 | `.gitignore`, `.git/info/exclude`, and global `gitignore` files. 150 | Enabled by default. 151 | 152 | **Default value**: `true` 153 | 154 | **Type**: `bool` 155 | 156 | **Example usage** (`pyproject.toml`): 157 | 158 | ```toml 159 | [tool.ty.src] 160 | respect-ignore-files = false 161 | ``` 162 | 163 | --- 164 | 165 | #### `root` 166 | 167 | The root of the project, used for finding first-party modules. 168 | 169 | If left unspecified, ty will try to detect common project layouts and initialize `src.root` accordingly: 170 | 171 | * if a `./src` directory exists, include `.` and `./src` in the first party search path (src layout or flat) 172 | * if a `.//` directory exists, include `.` and `./` in the first party search path 173 | * otherwise, default to `.` (flat layout) 174 | 175 | Besides, if a `./tests` directory exists and is not a package (i.e. it does not contain an `__init__.py` file), 176 | it will also be included in the first party search path. 177 | 178 | **Default value**: `null` 179 | 180 | **Type**: `str` 181 | 182 | **Example usage** (`pyproject.toml`): 183 | 184 | ```toml 185 | [tool.ty.src] 186 | root = "./app" 187 | ``` 188 | 189 | --- 190 | 191 | ## `terminal` 192 | 193 | #### `error-on-warning` 194 | 195 | Use exit code 1 if there are any warning-level diagnostics. 196 | 197 | Defaults to `false`. 198 | 199 | **Default value**: `false` 200 | 201 | **Type**: `bool` 202 | 203 | **Example usage** (`pyproject.toml`): 204 | 205 | ```toml 206 | [tool.ty.terminal] 207 | # Error if ty emits any warning-level diagnostics. 208 | error-on-warning = true 209 | ``` 210 | 211 | --- 212 | 213 | #### `output-format` 214 | 215 | The format to use for printing diagnostic messages. 216 | 217 | Defaults to `full`. 218 | 219 | **Default value**: `full` 220 | 221 | **Type**: `full | concise` 222 | 223 | **Example usage** (`pyproject.toml`): 224 | 225 | ```toml 226 | [tool.ty.terminal] 227 | output-format = "concise" 228 | ``` 229 | 230 | --- 231 | 232 | -------------------------------------------------------------------------------- /docs/reference/editor-settings.md: -------------------------------------------------------------------------------- 1 | # Editor settings 2 | 3 | The editor settings supported by ty's language server, as well as the settings specific to [ty's VS Code extension](https://github.com/astral-sh/ty-vscode/). 4 | 5 | ## `experimental` 6 | 7 | ### `completions.enable` 8 | 9 | Enables ty's experimental support for code completions. 10 | 11 | **Default value**: `false` 12 | 13 | **Type**: `boolean` 14 | 15 | **Example usage**: 16 | 17 | ```json 18 | { 19 | "ty.experimental.completions.enable": true 20 | } 21 | ``` 22 | 23 | ## `logFile` 24 | 25 | Path to the file to which the language server writes its log messages. By default, ty writes log messages to stderr. 26 | 27 | **Default value**: `null` 28 | 29 | **Type**: `string` 30 | 31 | **Example usage**: 32 | 33 | ```json 34 | { 35 | "ty.logFile": "~/path/to/ty.log" 36 | } 37 | ``` 38 | 39 | ## `logLevel` 40 | 41 | The log level to use for the language server. 42 | 43 | **Default value**: `"info"` 44 | 45 | **Type**: `"trace" | "debug" | "info" | "warn" | "error"` 46 | 47 | **Example usage**: 48 | 49 | ```json 50 | { 51 | "ty.logLevel": "debug" 52 | } 53 | ``` 54 | 55 | ### `trace.server` 56 | 57 | The detail level at which messages between the language server and the editor (client) are logged. Refer to the [LSP 58 | specification](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#traceValue) 59 | for more information. 60 | 61 | **Default value**: `"off"` 62 | 63 | **Type**: `"off" | "messages" | "verbose"` 64 | 65 | **Example usage**: 66 | 67 | ```json 68 | { 69 | "ty.trace.server": "messages" 70 | } 71 | ``` 72 | 73 | ## VS Code specific 74 | 75 | The following settings are specific to ty's VS Code extension. 76 | 77 | ### `importStrategy` 78 | 79 | Strategy for loading the `ty` executable. 80 | 81 | - `fromEnvironment` finds ty in the environment, falling back to the bundled version 82 | - `useBundled` uses the version bundled with the extension 83 | 84 | **Default value**: `"fromEnvironment"` 85 | 86 | **Type**: `"fromEnvironment" | "useBundled"` 87 | 88 | **Example usage**: 89 | 90 | ```json 91 | { 92 | "ty.importStrategy": "useBundled" 93 | } 94 | ``` 95 | 96 | ### `interpreter` 97 | 98 | A list of paths to Python interpreters. Even though this is a list, only the first interpreter is 99 | used. 100 | 101 | The interpreter path is used to find the `ty` executable when 102 | [`ty.importStrategy`](#importstrategy) is set to `fromEnvironment`. 103 | 104 | **Default value**: `[]` 105 | 106 | **Type**: `string[]` 107 | 108 | **Example usage**: 109 | 110 | ```json 111 | { 112 | "ty.interpreter": ["/home/user/.local/bin/python"] 113 | } 114 | ``` 115 | 116 | ### `path` 117 | 118 | A list of path to `ty` executables. 119 | 120 | The extension uses the first executable that exists. This setting takes precedence over the 121 | [`ty.importStrategy`](#importstrategy) setting. 122 | 123 | **Default value**: `[]` 124 | 125 | **Type**: `string[]` 126 | 127 | **Example usage**: 128 | 129 | ```json 130 | { 131 | "ty.path": ["/home/user/.local/bin/ty"] 132 | } 133 | ``` 134 | -------------------------------------------------------------------------------- /docs/reference/env.md: -------------------------------------------------------------------------------- 1 | # Environment variables 2 | 3 | ty defines and respects the following environment variables: 4 | 5 | ## `TY_LOG` 6 | 7 | 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: 8 | 9 | - `TY_LOG=uv=debug` is the equivalent of `-vv` to the command line 10 | - `TY_LOG=trace` will enable all trace-level logging. 11 | 12 | See the [tracing documentation](https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html#example-syntax) for more. 13 | 14 | ## `TY_MAX_PARALLELISM` 15 | 16 | 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. 17 | 18 | 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. 19 | 20 | ## Externally defined variables 21 | 22 | ty also reads the following externally defined environment variables: 23 | 24 | ### `RAYON_NUM_THREADS` 25 | 26 | Specifies an upper limit for the number of threads ty uses when performing work in parallel. Equivalent to `TY_MAX_PARALLELISM`. 27 | 28 | ### `VIRTUAL_ENV` 29 | 30 | Used to detect an activated virtual environment. 31 | 32 | ### `XDG_CONFIG_HOME` 33 | 34 | Path to user-level configuration directory on Unix systems. 35 | -------------------------------------------------------------------------------- /docs/reference/rules.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Rules 4 | 5 | ## `byte-string-type-annotation` 6 | 7 | **Default level**: error 8 | 9 |
10 | detects byte strings in type annotation positions 11 | 12 | ### What it does 13 | Checks for byte-strings in type annotation positions. 14 | 15 | ### Why is this bad? 16 | Static analysis tools like ty can't analyse type annotations that use byte-string notation. 17 | 18 | ### Examples 19 | ```python 20 | def test(): -> b"int": 21 | ... 22 | ``` 23 | 24 | Use instead: 25 | ```python 26 | def test(): -> "int": 27 | ... 28 | ``` 29 | 30 | ### Links 31 | * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20byte-string-type-annotation) 32 | * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fstring_annotation.rs#L36) 33 |
34 | 35 | ## `call-non-callable` 36 | 37 | **Default level**: error 38 | 39 |
40 | detects calls to non-callable objects 41 | 42 | ### What it does 43 | Checks for calls to non-callable objects. 44 | 45 | ### Why is this bad? 46 | Calling a non-callable object will raise a `TypeError` at runtime. 47 | 48 | ### Examples 49 | ```python 50 | 4() # TypeError: 'int' object is not callable 51 | ``` 52 | 53 | ### Links 54 | * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20call-non-callable) 55 | * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L91) 56 |
57 | 58 | ## `conflicting-argument-forms` 59 | 60 | **Default level**: error 61 | 62 |
63 | detects when an argument is used as both a value and a type form in a call 64 | 65 | ### What it does 66 | Checks whether an argument is used as both a value and a type form in a call. 67 | 68 | ### Why is this bad? 69 | Such calls have confusing semantics and often indicate a logic error. 70 | 71 | ### Examples 72 | ```python 73 | from typing import reveal_type 74 | from ty_extensions import is_fully_static 75 | 76 | if flag: 77 | f = repr # Expects a value 78 | else: 79 | f = is_fully_static # Expects a type form 80 | 81 | f(int) # error 82 | ``` 83 | 84 | ### Links 85 | * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-argument-forms) 86 | * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L135) 87 |
88 | 89 | ## `conflicting-declarations` 90 | 91 | **Default level**: error 92 | 93 |
94 | detects conflicting declarations 95 | 96 | ### What it does 97 | Checks whether a variable has been declared as two conflicting types. 98 | 99 | ### Why is this bad 100 | A variable with two conflicting declarations likely indicates a mistake. 101 | Moreover, it could lead to incorrect or ill-defined type inference for 102 | other code that relies on these variables. 103 | 104 | ### Examples 105 | ```python 106 | if b: 107 | a: int 108 | else: 109 | a: str 110 | 111 | a = 1 112 | ``` 113 | 114 | ### Links 115 | * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-declarations) 116 | * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L161) 117 |
118 | 119 | ## `conflicting-metaclass` 120 | 121 | **Default level**: error 122 | 123 |
124 | detects conflicting metaclasses 125 | 126 | ### What it does 127 | Checks for class definitions where the metaclass of the class 128 | being created would not be a subclass of the metaclasses of 129 | all the class's bases. 130 | 131 | ### Why is it bad? 132 | Such a class definition raises a `TypeError` at runtime. 133 | 134 | ### Examples 135 | ```python 136 | class M1(type): ... 137 | class M2(type): ... 138 | class A(metaclass=M1): ... 139 | class B(metaclass=M2): ... 140 | 141 | ## TypeError: metaclass conflict 142 | class C(A, B): ... 143 | ``` 144 | 145 | ### Links 146 | * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-metaclass) 147 | * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L186) 148 |
149 | 150 | ## `cyclic-class-definition` 151 | 152 | **Default level**: error 153 | 154 |
155 | detects cyclic class definitions 156 | 157 | ### What it does 158 | Checks for class definitions in stub files that inherit 159 | (directly or indirectly) from themselves. 160 | 161 | ### Why is it bad? 162 | Although forward references are natively supported in stub files, 163 | inheritance cycles are still disallowed, as it is impossible to 164 | resolve a consistent [method resolution order] for a class that 165 | inherits from itself. 166 | 167 | ### Examples 168 | ```python 169 | ## foo.pyi 170 | class A(B): ... 171 | class B(A): ... 172 | ``` 173 | 174 | [method resolution order]: https://docs.python.org/3/glossary.html#term-method-resolution-order 175 | 176 | ### Links 177 | * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20cyclic-class-definition) 178 | * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L212) 179 |
180 | 181 | ## `duplicate-base` 182 | 183 | **Default level**: error 184 | 185 |
186 | detects class definitions with duplicate bases 187 | 188 | ### What it does 189 | Checks for class definitions with duplicate bases. 190 | 191 | ### Why is this bad? 192 | Class definitions with duplicate bases raise `TypeError` at runtime. 193 | 194 | ### Examples 195 | ```python 196 | class A: ... 197 | 198 | ## TypeError: duplicate base class 199 | class B(A, A): ... 200 | ``` 201 | 202 | ### Links 203 | * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20duplicate-base) 204 | * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L256) 205 |
206 | 207 | ## `escape-character-in-forward-annotation` 208 | 209 | **Default level**: error 210 | 211 |
212 | detects forward type annotations with escape characters 213 | 214 | TODO #14889 215 | 216 | ### Links 217 | * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20escape-character-in-forward-annotation) 218 | * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fstring_annotation.rs#L120) 219 |
220 | 221 | ## `fstring-type-annotation` 222 | 223 | **Default level**: error 224 | 225 |
226 | detects F-strings in type annotation positions 227 | 228 | ### What it does 229 | Checks for f-strings in type annotation positions. 230 | 231 | ### Why is this bad? 232 | Static analysis tools like ty can't analyse type annotations that use f-string notation. 233 | 234 | ### Examples 235 | ```python 236 | def test(): -> f"int": 237 | ... 238 | ``` 239 | 240 | Use instead: 241 | ```python 242 | def test(): -> "int": 243 | ... 244 | ``` 245 | 246 | ### Links 247 | * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20fstring-type-annotation) 248 | * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fstring_annotation.rs#L11) 249 |
250 | 251 | ## `implicit-concatenated-string-type-annotation` 252 | 253 | **Default level**: error 254 | 255 |
256 | detects implicit concatenated strings in type annotations 257 | 258 | ### What it does 259 | Checks for implicit concatenated strings in type annotation positions. 260 | 261 | ### Why is this bad? 262 | Static analysis tools like ty can't analyse type annotations that use implicit concatenated strings. 263 | 264 | ### Examples 265 | ```python 266 | def test(): -> "Literal[" "5" "]": 267 | ... 268 | ``` 269 | 270 | Use instead: 271 | ```python 272 | def test(): -> "Literal[5]": 273 | ... 274 | ``` 275 | 276 | ### Links 277 | * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20implicit-concatenated-string-type-annotation) 278 | * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fstring_annotation.rs#L86) 279 |
280 | 281 | ## `incompatible-slots` 282 | 283 | **Default level**: error 284 | 285 |
286 | detects class definitions whose MRO has conflicting __slots__ 287 | 288 | ### What it does 289 | Checks for classes whose bases define incompatible `__slots__`. 290 | 291 | ### Why is this bad? 292 | Inheriting from bases with incompatible `__slots__`s 293 | will lead to a `TypeError` at runtime. 294 | 295 | Classes with no or empty `__slots__` are always compatible: 296 | 297 | ```python 298 | class A: ... 299 | class B: 300 | __slots__ = () 301 | class C: 302 | __slots__ = ("a", "b") 303 | 304 | ## fine 305 | class D(A, B, C): ... 306 | ``` 307 | 308 | Multiple inheritance from more than one different class 309 | defining non-empty `__slots__` is not allowed: 310 | 311 | ```python 312 | class A: 313 | __slots__ = ("a", "b") 314 | 315 | class B: 316 | __slots__ = ("a", "b") # Even if the values are the same 317 | 318 | ## TypeError: multiple bases have instance lay-out conflict 319 | class C(A, B): ... 320 | ``` 321 | 322 | ### Known problems 323 | Dynamic (not tuple or string literal) `__slots__` are not checked. 324 | Additionally, classes inheriting from built-in classes with implicit layouts 325 | like `str` or `int` are also not checked. 326 | 327 | ```pycon 328 | >>> hasattr(int, "__slots__") 329 | False 330 | >>> hasattr(str, "__slots__") 331 | False 332 | >>> class A(int, str): ... 333 | Traceback (most recent call last): 334 | File "", line 1, in 335 | class A(int, str): ... 336 | TypeError: multiple bases have instance lay-out conflict 337 | ``` 338 | 339 | ### Links 340 | * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20incompatible-slots) 341 | * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L277) 342 |
343 | 344 | ## `inconsistent-mro` 345 | 346 | **Default level**: error 347 | 348 |
349 | detects class definitions with an inconsistent MRO 350 | 351 | ### What it does 352 | Checks for classes with an inconsistent [method resolution order] (MRO). 353 | 354 | ### Why is this bad? 355 | Classes with an inconsistent MRO will raise a `TypeError` at runtime. 356 | 357 | ### Examples 358 | ```python 359 | class A: ... 360 | class B(A): ... 361 | 362 | ## TypeError: Cannot create a consistent method resolution order 363 | class C(A, B): ... 364 | ``` 365 | 366 | [method resolution order]: https://docs.python.org/3/glossary.html#term-method-resolution-order 367 | 368 | ### Links 369 | * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20inconsistent-mro) 370 | * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L363) 371 |
372 | 373 | ## `index-out-of-bounds` 374 | 375 | **Default level**: error 376 | 377 |
378 | detects index out of bounds errors 379 | 380 | ### What it does 381 | Checks for attempts to use an out of bounds index to get an item from 382 | a container. 383 | 384 | ### Why is this bad? 385 | Using an out of bounds index will raise an `IndexError` at runtime. 386 | 387 | ### Examples 388 | ```python 389 | t = (0, 1, 2) 390 | t[3] # IndexError: tuple index out of range 391 | ``` 392 | 393 | ### Links 394 | * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20index-out-of-bounds) 395 | * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L387) 396 |
397 | 398 | ## `invalid-argument-type` 399 | 400 | **Default level**: error 401 | 402 |
403 | detects call arguments whose type is not assignable to the corresponding typed parameter 404 | 405 | ### What it does 406 | Detects call arguments whose type is not assignable to the corresponding typed parameter. 407 | 408 | ### Why is this bad? 409 | Passing an argument of a type the function (or callable object) does not accept violates 410 | the expectations of the function author and may cause unexpected runtime errors within the 411 | body of the function. 412 | 413 | ### Examples 414 | ```python 415 | def func(x: int): ... 416 | func("foo") # error: [invalid-argument-type] 417 | ``` 418 | 419 | ### Links 420 | * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-argument-type) 421 | * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L407) 422 |
423 | 424 | ## `invalid-assignment` 425 | 426 | **Default level**: error 427 | 428 |
429 | detects invalid assignments 430 | 431 | ### What it does 432 | Checks for assignments where the type of the value 433 | is not [assignable to] the type of the assignee. 434 | 435 | ### Why is this bad? 436 | Such assignments break the rules of the type system and 437 | weaken a type checker's ability to accurately reason about your code. 438 | 439 | ### Examples 440 | ```python 441 | a: int = '' 442 | ``` 443 | 444 | [assignable to]: https://typing.python.org/en/latest/spec/glossary.html#term-assignable 445 | 446 | ### Links 447 | * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-assignment) 448 | * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L447) 449 |
450 | 451 | ## `invalid-attribute-access` 452 | 453 | **Default level**: error 454 | 455 |
456 | Invalid attribute access 457 | 458 | ### What it does 459 | Checks for assignments to class variables from instances 460 | and assignments to instance variables from its class. 461 | 462 | ### Why is this bad? 463 | Incorrect assignments break the rules of the type system and 464 | weaken a type checker's ability to accurately reason about your code. 465 | 466 | ### Examples 467 | ```python 468 | class C: 469 | class_var: ClassVar[int] = 1 470 | instance_var: int 471 | 472 | C.class_var = 3 # okay 473 | C().class_var = 3 # error: Cannot assign to class variable 474 | 475 | C().instance_var = 3 # okay 476 | C.instance_var = 3 # error: Cannot assign to instance variable 477 | ``` 478 | 479 | ### Links 480 | * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-attribute-access) 481 | * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1395) 482 |
483 | 484 | ## `invalid-base` 485 | 486 | **Default level**: error 487 | 488 |
489 | detects class bases that will cause the class definition to raise an exception at runtime 490 | 491 | ### What it does 492 | Checks for class definitions that have bases which are not instances of `type`. 493 | 494 | ### Why is this bad? 495 | Class definitions with bases like this will lead to `TypeError` being raised at runtime. 496 | 497 | ### Examples 498 | ```python 499 | class A(42): ... # error: [invalid-base] 500 | ``` 501 | 502 | ### Links 503 | * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-base) 504 | * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L469) 505 |
506 | 507 | ## `invalid-context-manager` 508 | 509 | **Default level**: error 510 | 511 |
512 | detects expressions used in with statements that don't implement the context manager protocol 513 | 514 | ### What it does 515 | Checks for expressions used in `with` statements 516 | that do not implement the context manager protocol. 517 | 518 | ### Why is this bad? 519 | Such a statement will raise `TypeError` at runtime. 520 | 521 | ### Examples 522 | ```python 523 | ## TypeError: 'int' object does not support the context manager protocol 524 | with 1: 525 | print(2) 526 | ``` 527 | 528 | ### Links 529 | * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-context-manager) 530 | * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L520) 531 |
532 | 533 | ## `invalid-declaration` 534 | 535 | **Default level**: error 536 | 537 |
538 | detects invalid declarations 539 | 540 | ### What it does 541 | Checks for declarations where the inferred type of an existing symbol 542 | is not [assignable to] its post-hoc declared type. 543 | 544 | ### Why is this bad? 545 | Such declarations break the rules of the type system and 546 | weaken a type checker's ability to accurately reason about your code. 547 | 548 | ### Examples 549 | ```python 550 | a = 1 551 | a: str 552 | ``` 553 | 554 | [assignable to]: https://typing.python.org/en/latest/spec/glossary.html#term-assignable 555 | 556 | ### Links 557 | * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-declaration) 558 | * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L541) 559 |
560 | 561 | ## `invalid-exception-caught` 562 | 563 | **Default level**: error 564 | 565 |
566 | detects exception handlers that catch classes that do not inherit from BaseException 567 | 568 | ### What it does 569 | Checks for exception handlers that catch non-exception classes. 570 | 571 | ### Why is this bad? 572 | Catching classes that do not inherit from `BaseException` will raise a TypeError at runtime. 573 | 574 | ### Example 575 | ```python 576 | try: 577 | 1 / 0 578 | except 1: 579 | ... 580 | ``` 581 | 582 | Use instead: 583 | ```python 584 | try: 585 | 1 / 0 586 | except ZeroDivisionError: 587 | ... 588 | ``` 589 | 590 | ### References 591 | - [Python documentation: except clause](https://docs.python.org/3/reference/compound_stmts.html#except-clause) 592 | - [Python documentation: Built-in Exceptions](https://docs.python.org/3/library/exceptions.html#built-in-exceptions) 593 | 594 | ### Ruff rule 595 | This rule corresponds to Ruff's [`except-with-non-exception-classes` (`B030`)](https://docs.astral.sh/ruff/rules/except-with-non-exception-classes) 596 | 597 | ### Links 598 | * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-exception-caught) 599 | * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L564) 600 |
601 | 602 | ## `invalid-generic-class` 603 | 604 | **Default level**: error 605 | 606 |
607 | detects invalid generic classes 608 | 609 | ### What it does 610 | Checks for the creation of invalid generic classes 611 | 612 | ### Why is this bad? 613 | There are several requirements that you must follow when defining a generic class. 614 | 615 | ### Examples 616 | ```python 617 | from typing import Generic, TypeVar 618 | 619 | T = TypeVar("T") # okay 620 | 621 | ## error: class uses both PEP-695 syntax and legacy syntax 622 | class C[U](Generic[T]): ... 623 | ``` 624 | 625 | ### References 626 | - [Typing spec: Generics](https://typing.python.org/en/latest/spec/generics.html#introduction) 627 | 628 | ### Links 629 | * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-generic-class) 630 | * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L600) 631 |
632 | 633 | ## `invalid-legacy-type-variable` 634 | 635 | **Default level**: error 636 | 637 |
638 | detects invalid legacy type variables 639 | 640 | ### What it does 641 | Checks for the creation of invalid legacy `TypeVar`s 642 | 643 | ### Why is this bad? 644 | There are several requirements that you must follow when creating a legacy `TypeVar`. 645 | 646 | ### Examples 647 | ```python 648 | from typing import TypeVar 649 | 650 | T = TypeVar("T") # okay 651 | Q = TypeVar("S") # error: TypeVar name must match the variable it's assigned to 652 | T = TypeVar("T") # error: TypeVars should not be redefined 653 | 654 | ## error: TypeVar must be immediately assigned to a variable 655 | def f(t: TypeVar("U")): ... 656 | ``` 657 | 658 | ### References 659 | - [Typing spec: Generics](https://typing.python.org/en/latest/spec/generics.html#introduction) 660 | 661 | ### Links 662 | * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-legacy-type-variable) 663 | * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L626) 664 |
665 | 666 | ## `invalid-metaclass` 667 | 668 | **Default level**: error 669 | 670 |
671 | detects invalid metaclass= arguments 672 | 673 | ### What it does 674 | Checks for arguments to `metaclass=` that are invalid. 675 | 676 | ### Why is this bad? 677 | Python allows arbitrary expressions to be used as the argument to `metaclass=`. 678 | These expressions, however, need to be callable and accept the same arguments 679 | as `type.__new__`. 680 | 681 | ### Example 682 | 683 | ```python 684 | def f(): ... 685 | 686 | ## TypeError: f() takes 0 positional arguments but 3 were given 687 | class B(metaclass=f): ... 688 | ``` 689 | 690 | ### References 691 | - [Python documentation: Metaclasses](https://docs.python.org/3/reference/datamodel.html#metaclasses) 692 | 693 | ### Links 694 | * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-metaclass) 695 | * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L675) 696 |
697 | 698 | ## `invalid-overload` 699 | 700 | **Default level**: error 701 | 702 |
703 | detects invalid @overload usages 704 | 705 | ### What it does 706 | Checks for various invalid `@overload` usages. 707 | 708 | ### Why is this bad? 709 | The `@overload` decorator is used to define functions and methods that accepts different 710 | combinations of arguments and return different types based on the arguments passed. This is 711 | mainly beneficial for type checkers. But, if the `@overload` usage is invalid, the type 712 | checker may not be able to provide correct type information. 713 | 714 | ### Example 715 | 716 | Defining only one overload: 717 | 718 | ```py 719 | from typing import overload 720 | 721 | @overload 722 | def foo(x: int) -> int: ... 723 | def foo(x: int | None) -> int | None: 724 | return x 725 | ``` 726 | 727 | Or, not providing an implementation for the overloaded definition: 728 | 729 | ```py 730 | from typing import overload 731 | 732 | @overload 733 | def foo() -> None: ... 734 | @overload 735 | def foo(x: int) -> int: ... 736 | ``` 737 | 738 | ### References 739 | - [Python documentation: `@overload`](https://docs.python.org/3/library/typing.html#typing.overload) 740 | 741 | ### Links 742 | * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-overload) 743 | * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L702) 744 |
745 | 746 | ## `invalid-parameter-default` 747 | 748 | **Default level**: error 749 | 750 |
751 | detects default values that can't be assigned to the parameter's annotated type 752 | 753 | ### What it does 754 | Checks for default values that can't be 755 | assigned to the parameter's annotated type. 756 | 757 | ### Why is this bad? 758 | This breaks the rules of the type system and 759 | weakens a type checker's ability to accurately reason about your code. 760 | 761 | ### Examples 762 | ```python 763 | def f(a: int = ''): ... 764 | ``` 765 | 766 | ### Links 767 | * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-parameter-default) 768 | * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L745) 769 |
770 | 771 | ## `invalid-protocol` 772 | 773 | **Default level**: error 774 | 775 |
776 | detects invalid protocol class definitions 777 | 778 | ### What it does 779 | Checks for invalidly defined protocol classes. 780 | 781 | ### Why is this bad? 782 | An invalidly defined protocol class may lead to the type checker inferring 783 | unexpected things. It may also lead to `TypeError`s at runtime. 784 | 785 | ### Examples 786 | A `Protocol` class cannot inherit from a non-`Protocol` class; 787 | this raises a `TypeError` at runtime: 788 | 789 | ```pycon 790 | >>> from typing import Protocol 791 | >>> class Foo(int, Protocol): ... 792 | ... 793 | Traceback (most recent call last): 794 | File "", line 1, in 795 | class Foo(int, Protocol): ... 796 | TypeError: Protocols can only inherit from other protocols, got 797 | ``` 798 | 799 | ### Links 800 | * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-protocol) 801 | * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L335) 802 |
803 | 804 | ## `invalid-raise` 805 | 806 | **Default level**: error 807 | 808 |
809 | detects raise statements that raise invalid exceptions or use invalid causes 810 | 811 | Checks for `raise` statements that raise non-exceptions or use invalid 812 | causes for their raised exceptions. 813 | 814 | ### Why is this bad? 815 | Only subclasses or instances of `BaseException` can be raised. 816 | For an exception's cause, the same rules apply, except that `None` is also 817 | permitted. Violating these rules results in a `TypeError` at runtime. 818 | 819 | ### Examples 820 | ```python 821 | def f(): 822 | try: 823 | something() 824 | except NameError: 825 | raise "oops!" from f 826 | 827 | def g(): 828 | raise NotImplemented from 42 829 | ``` 830 | 831 | Use instead: 832 | ```python 833 | def f(): 834 | try: 835 | something() 836 | except NameError as e: 837 | raise RuntimeError("oops!") from e 838 | 839 | def g(): 840 | raise NotImplementedError from None 841 | ``` 842 | 843 | ### References 844 | - [Python documentation: The `raise` statement](https://docs.python.org/3/reference/simple_stmts.html#raise) 845 | - [Python documentation: Built-in Exceptions](https://docs.python.org/3/library/exceptions.html#built-in-exceptions) 846 | 847 | ### Links 848 | * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-raise) 849 | * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L765) 850 |
851 | 852 | ## `invalid-return-type` 853 | 854 | **Default level**: error 855 | 856 |
857 | detects returned values that can't be assigned to the function's annotated return type 858 | 859 | ### What it does 860 | Detects returned values that can't be assigned to the function's annotated return type. 861 | 862 | ### Why is this bad? 863 | Returning an object of a type incompatible with the annotated return type may cause confusion to the user calling the function. 864 | 865 | ### Examples 866 | ```python 867 | def func() -> int: 868 | return "a" # error: [invalid-return-type] 869 | ``` 870 | 871 | ### Links 872 | * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-return-type) 873 | * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L428) 874 |
875 | 876 | ## `invalid-super-argument` 877 | 878 | **Default level**: error 879 | 880 |
881 | detects invalid arguments for super() 882 | 883 | ### What it does 884 | Detects `super()` calls where: 885 | - the first argument is not a valid class literal, or 886 | - the second argument is not an instance or subclass of the first argument. 887 | 888 | ### Why is this bad? 889 | `super(type, obj)` expects: 890 | - the first argument to be a class, 891 | - and the second argument to satisfy one of the following: 892 | - `isinstance(obj, type)` is `True` 893 | - `issubclass(obj, type)` is `True` 894 | 895 | Violating this relationship will raise a `TypeError` at runtime. 896 | 897 | ### Examples 898 | ```python 899 | class A: 900 | ... 901 | class B(A): 902 | ... 903 | 904 | super(A, B()) # it's okay! `A` satisfies `isinstance(B(), A)` 905 | 906 | super(A(), B()) # error: `A()` is not a class 907 | 908 | super(B, A()) # error: `A()` does not satisfy `isinstance(A(), B)` 909 | super(B, A) # error: `A` does not satisfy `issubclass(A, B)` 910 | ``` 911 | 912 | ### References 913 | - [Python documentation: super()](https://docs.python.org/3/library/functions.html#super) 914 | 915 | ### Links 916 | * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-super-argument) 917 | * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L808) 918 |
919 | 920 | ## `invalid-syntax-in-forward-annotation` 921 | 922 | **Default level**: error 923 | 924 |
925 | detects invalid syntax in forward annotations 926 | 927 | TODO #14889 928 | 929 | ### Links 930 | * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-syntax-in-forward-annotation) 931 | * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fstring_annotation.rs#L111) 932 |
933 | 934 | ## `invalid-type-alias-type` 935 | 936 | **Default level**: error 937 | 938 |
939 | detects invalid TypeAliasType definitions 940 | 941 | ### What it does 942 | Checks for the creation of invalid `TypeAliasType`s 943 | 944 | ### Why is this bad? 945 | There are several requirements that you must follow when creating a `TypeAliasType`. 946 | 947 | ### Examples 948 | ```python 949 | from typing import TypeAliasType 950 | 951 | IntOrStr = TypeAliasType("IntOrStr", int | str) # okay 952 | NewAlias = TypeAliasType(get_name(), int) # error: TypeAliasType name must be a string literal 953 | ``` 954 | 955 | ### Links 956 | * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-alias-type) 957 | * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L654) 958 |
959 | 960 | ## `invalid-type-checking-constant` 961 | 962 | **Default level**: error 963 | 964 |
965 | detects invalid TYPE_CHECKING constant assignments 966 | 967 | ### What it does 968 | Checks for a value other than `False` assigned to the `TYPE_CHECKING` variable, or an 969 | annotation not assignable from `bool`. 970 | 971 | ### Why is this bad? 972 | The name `TYPE_CHECKING` is reserved for a flag that can be used to provide conditional 973 | code seen only by the type checker, and not at runtime. Normally this flag is imported from 974 | `typing` or `typing_extensions`, but it can also be defined locally. If defined locally, it 975 | must be assigned the value `False` at runtime; the type checker will consider its value to 976 | be `True`. If annotated, it must be annotated as a type that can accept `bool` values. 977 | 978 | ### Examples 979 | ```python 980 | TYPE_CHECKING: str 981 | TYPE_CHECKING = '' 982 | ``` 983 | 984 | ### Links 985 | * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-checking-constant) 986 | * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L847) 987 |
988 | 989 | ## `invalid-type-form` 990 | 991 | **Default level**: error 992 | 993 |
994 | detects invalid type forms 995 | 996 | ### What it does 997 | Checks for expressions that are used as [type expressions] 998 | but cannot validly be interpreted as such. 999 | 1000 | ### Why is this bad? 1001 | Such expressions cannot be understood by ty. 1002 | In some cases, they might raise errors at runtime. 1003 | 1004 | ### Examples 1005 | ```python 1006 | from typing import Annotated 1007 | 1008 | a: type[1] # `1` is not a type 1009 | b: Annotated[int] # `Annotated` expects at least two arguments 1010 | ``` 1011 | [type expressions]: https://typing.python.org/en/latest/spec/annotations.html#type-and-annotation-expressions 1012 | 1013 | ### Links 1014 | * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-form) 1015 | * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L871) 1016 |
1017 | 1018 | ## `invalid-type-variable-constraints` 1019 | 1020 | **Default level**: error 1021 | 1022 |
1023 | detects invalid type variable constraints 1024 | 1025 | ### What it does 1026 | Checks for constrained [type variables] with only one constraint. 1027 | 1028 | ### Why is this bad? 1029 | A constrained type variable must have at least two constraints. 1030 | 1031 | ### Examples 1032 | ```python 1033 | from typing import TypeVar 1034 | 1035 | T = TypeVar('T', str) # invalid constrained TypeVar 1036 | ``` 1037 | 1038 | Use instead: 1039 | ```python 1040 | T = TypeVar('T', str, int) # valid constrained TypeVar 1041 | ## or 1042 | T = TypeVar('T', bound=str) # valid bound TypeVar 1043 | ``` 1044 | 1045 | [type variables]: https://docs.python.org/3/library/typing.html#typing.TypeVar 1046 | 1047 | ### Links 1048 | * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-variable-constraints) 1049 | * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L895) 1050 |
1051 | 1052 | ## `missing-argument` 1053 | 1054 | **Default level**: error 1055 | 1056 |
1057 | detects missing required arguments in a call 1058 | 1059 | ### What it does 1060 | Checks for missing required arguments in a call. 1061 | 1062 | ### Why is this bad? 1063 | Failing to provide a required argument will raise a `TypeError` at runtime. 1064 | 1065 | ### Examples 1066 | ```python 1067 | def func(x: int): ... 1068 | func() # TypeError: func() missing 1 required positional argument: 'x' 1069 | ``` 1070 | 1071 | ### Links 1072 | * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20missing-argument) 1073 | * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L924) 1074 |
1075 | 1076 | ## `no-matching-overload` 1077 | 1078 | **Default level**: error 1079 | 1080 |
1081 | detects calls that do not match any overload 1082 | 1083 | ### What it does 1084 | Checks for calls to an overloaded function that do not match any of the overloads. 1085 | 1086 | ### Why is this bad? 1087 | Failing to provide the correct arguments to one of the overloads will raise a `TypeError` 1088 | at runtime. 1089 | 1090 | ### Examples 1091 | ```python 1092 | @overload 1093 | def func(x: int): ... 1094 | @overload 1095 | def func(x: bool): ... 1096 | func("string") # error: [no-matching-overload] 1097 | ``` 1098 | 1099 | ### Links 1100 | * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20no-matching-overload) 1101 | * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L943) 1102 |
1103 | 1104 | ## `non-subscriptable` 1105 | 1106 | **Default level**: error 1107 | 1108 |
1109 | detects subscripting objects that do not support subscripting 1110 | 1111 | ### What it does 1112 | Checks for subscripting objects that do not support subscripting. 1113 | 1114 | ### Why is this bad? 1115 | Subscripting an object that does not support it will raise a `TypeError` at runtime. 1116 | 1117 | ### Examples 1118 | ```python 1119 | 4[1] # TypeError: 'int' object is not subscriptable 1120 | ``` 1121 | 1122 | ### Links 1123 | * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20non-subscriptable) 1124 | * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L966) 1125 |
1126 | 1127 | ## `not-iterable` 1128 | 1129 | **Default level**: error 1130 | 1131 |
1132 | detects iteration over an object that is not iterable 1133 | 1134 | ### What it does 1135 | Checks for objects that are not iterable but are used in a context that requires them to be. 1136 | 1137 | ### Why is this bad? 1138 | Iterating over an object that is not iterable will raise a `TypeError` at runtime. 1139 | 1140 | ### Examples 1141 | 1142 | ```python 1143 | for i in 34: # TypeError: 'int' object is not iterable 1144 | pass 1145 | ``` 1146 | 1147 | ### Links 1148 | * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20not-iterable) 1149 | * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L984) 1150 |
1151 | 1152 | ## `parameter-already-assigned` 1153 | 1154 | **Default level**: error 1155 | 1156 |
1157 | detects multiple arguments for the same parameter 1158 | 1159 | ### What it does 1160 | Checks for calls which provide more than one argument for a single parameter. 1161 | 1162 | ### Why is this bad? 1163 | Providing multiple values for a single parameter will raise a `TypeError` at runtime. 1164 | 1165 | ### Examples 1166 | 1167 | ```python 1168 | def f(x: int) -> int: ... 1169 | 1170 | f(1, x=2) # Error raised here 1171 | ``` 1172 | 1173 | ### Links 1174 | * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20parameter-already-assigned) 1175 | * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1035) 1176 |
1177 | 1178 | ## `raw-string-type-annotation` 1179 | 1180 | **Default level**: error 1181 | 1182 |
1183 | detects raw strings in type annotation positions 1184 | 1185 | ### What it does 1186 | Checks for raw-strings in type annotation positions. 1187 | 1188 | ### Why is this bad? 1189 | Static analysis tools like ty can't analyse type annotations that use raw-string notation. 1190 | 1191 | ### Examples 1192 | ```python 1193 | def test(): -> r"int": 1194 | ... 1195 | ``` 1196 | 1197 | Use instead: 1198 | ```python 1199 | def test(): -> "int": 1200 | ... 1201 | ``` 1202 | 1203 | ### Links 1204 | * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20raw-string-type-annotation) 1205 | * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fstring_annotation.rs#L61) 1206 |
1207 | 1208 | ## `static-assert-error` 1209 | 1210 | **Default level**: error 1211 | 1212 |
1213 | Failed static assertion 1214 | 1215 | ### What it does 1216 | Makes sure that the argument of `static_assert` is statically known to be true. 1217 | 1218 | ### Why is this bad? 1219 | A `static_assert` call represents an explicit request from the user 1220 | for the type checker to emit an error if the argument cannot be verified 1221 | to evaluate to `True` in a boolean context. 1222 | 1223 | ### Examples 1224 | ```python 1225 | from ty_extensions import static_assert 1226 | 1227 | static_assert(1 + 1 == 3) # error: evaluates to `False` 1228 | 1229 | static_assert(int(2.0 * 3.0) == 6) # error: does not have a statically known truthiness 1230 | ``` 1231 | 1232 | ### Links 1233 | * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20static-assert-error) 1234 | * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1371) 1235 |
1236 | 1237 | ## `subclass-of-final-class` 1238 | 1239 | **Default level**: error 1240 | 1241 |
1242 | detects subclasses of final classes 1243 | 1244 | ### What it does 1245 | Checks for classes that subclass final classes. 1246 | 1247 | ### Why is this bad? 1248 | Decorating a class with `@final` declares to the type checker that it should not be subclassed. 1249 | 1250 | ### Example 1251 | 1252 | ```python 1253 | from typing import final 1254 | 1255 | @final 1256 | class A: ... 1257 | class B(A): ... # Error raised here 1258 | ``` 1259 | 1260 | ### Links 1261 | * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20subclass-of-final-class) 1262 | * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1126) 1263 |
1264 | 1265 | ## `too-many-positional-arguments` 1266 | 1267 | **Default level**: error 1268 | 1269 |
1270 | detects calls passing too many positional arguments 1271 | 1272 | ### What it does 1273 | Checks for calls that pass more positional arguments than the callable can accept. 1274 | 1275 | ### Why is this bad? 1276 | Passing too many positional arguments will raise `TypeError` at runtime. 1277 | 1278 | ### Example 1279 | 1280 | ```python 1281 | def f(): ... 1282 | 1283 | f("foo") # Error raised here 1284 | ``` 1285 | 1286 | ### Links 1287 | * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20too-many-positional-arguments) 1288 | * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1171) 1289 |
1290 | 1291 | ## `type-assertion-failure` 1292 | 1293 | **Default level**: error 1294 | 1295 |
1296 | detects failed type assertions 1297 | 1298 | ### What it does 1299 | Checks for `assert_type()` and `assert_never()` calls where the actual type 1300 | is not the same as the asserted type. 1301 | 1302 | ### Why is this bad? 1303 | `assert_type()` allows confirming the inferred type of a certain value. 1304 | 1305 | ### Example 1306 | 1307 | ```python 1308 | def _(x: int): 1309 | assert_type(x, int) # fine 1310 | assert_type(x, str) # error: Actual type does not match asserted type 1311 | ``` 1312 | 1313 | ### Links 1314 | * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20type-assertion-failure) 1315 | * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1149) 1316 |
1317 | 1318 | ## `unavailable-implicit-super-arguments` 1319 | 1320 | **Default level**: error 1321 | 1322 |
1323 | detects invalid super() calls where implicit arguments are unavailable. 1324 | 1325 | ### What it does 1326 | Detects invalid `super()` calls where implicit arguments like the enclosing class or first method argument are unavailable. 1327 | 1328 | ### Why is this bad? 1329 | When `super()` is used without arguments, Python tries to find two things: 1330 | the nearest enclosing class and the first argument of the immediately enclosing function (typically self or cls). 1331 | If either of these is missing, the call will fail at runtime with a `RuntimeError`. 1332 | 1333 | ### Examples 1334 | ```python 1335 | super() # error: no enclosing class or function found 1336 | 1337 | def func(): 1338 | super() # error: no enclosing class or first argument exists 1339 | 1340 | class A: 1341 | f = super() # error: no enclosing function to provide the first argument 1342 | 1343 | def method(self): 1344 | def nested(): 1345 | super() # error: first argument does not exist in this nested function 1346 | 1347 | lambda: super() # error: first argument does not exist in this lambda 1348 | 1349 | (super() for _ in range(10)) # error: argument is not available in generator expression 1350 | 1351 | super() # okay! both enclosing class and first argument are available 1352 | ``` 1353 | 1354 | ### References 1355 | - [Python documentation: super()](https://docs.python.org/3/library/functions.html#super) 1356 | 1357 | ### Links 1358 | * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unavailable-implicit-super-arguments) 1359 | * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1192) 1360 |
1361 | 1362 | ## `unknown-argument` 1363 | 1364 | **Default level**: error 1365 | 1366 |
1367 | detects unknown keyword arguments in calls 1368 | 1369 | ### What it does 1370 | Checks for keyword arguments in calls that don't match any parameter of the callable. 1371 | 1372 | ### Why is this bad? 1373 | Providing an unknown keyword argument will raise `TypeError` at runtime. 1374 | 1375 | ### Example 1376 | 1377 | ```python 1378 | def f(x: int) -> int: ... 1379 | 1380 | f(x=1, y=2) # Error raised here 1381 | ``` 1382 | 1383 | ### Links 1384 | * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unknown-argument) 1385 | * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1249) 1386 |
1387 | 1388 | ## `unresolved-attribute` 1389 | 1390 | **Default level**: error 1391 | 1392 |
1393 | detects references to unresolved attributes 1394 | 1395 | ### What it does 1396 | Checks for unresolved attributes. 1397 | 1398 | ### Why is this bad? 1399 | Accessing an unbound attribute will raise an `AttributeError` at runtime. 1400 | An unresolved attribute is not guaranteed to exist from the type alone, 1401 | so this could also indicate that the object is not of the type that the user expects. 1402 | 1403 | ### Examples 1404 | ```python 1405 | class A: ... 1406 | 1407 | A().foo # AttributeError: 'A' object has no attribute 'foo' 1408 | ``` 1409 | 1410 | ### Links 1411 | * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-attribute) 1412 | * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1270) 1413 |
1414 | 1415 | ## `unresolved-import` 1416 | 1417 | **Default level**: error 1418 | 1419 |
1420 | detects unresolved imports 1421 | 1422 | ### What it does 1423 | Checks for import statements for which the module cannot be resolved. 1424 | 1425 | ### Why is this bad? 1426 | Importing a module that cannot be resolved will raise a `ModuleNotFoundError` 1427 | at runtime. 1428 | 1429 | ### Examples 1430 | ```python 1431 | import foo # ModuleNotFoundError: No module named 'foo' 1432 | ``` 1433 | 1434 | ### Links 1435 | * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-import) 1436 | * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1292) 1437 |
1438 | 1439 | ## `unresolved-reference` 1440 | 1441 | **Default level**: error 1442 | 1443 |
1444 | detects references to names that are not defined 1445 | 1446 | ### What it does 1447 | Checks for references to names that are not defined. 1448 | 1449 | ### Why is this bad? 1450 | Using an undefined variable will raise a `NameError` at runtime. 1451 | 1452 | ### Example 1453 | 1454 | ```python 1455 | print(x) # NameError: name 'x' is not defined 1456 | ``` 1457 | 1458 | ### Links 1459 | * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-reference) 1460 | * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1311) 1461 |
1462 | 1463 | ## `unsupported-bool-conversion` 1464 | 1465 | **Default level**: error 1466 | 1467 |
1468 | detects boolean conversion where the object incorrectly implements __bool__ 1469 | 1470 | ### What it does 1471 | Checks for bool conversions where the object doesn't correctly implement `__bool__`. 1472 | 1473 | ### Why is this bad? 1474 | If an exception is raised when you attempt to evaluate the truthiness of an object, 1475 | using the object in a boolean context will fail at runtime. 1476 | 1477 | ### Examples 1478 | 1479 | ```python 1480 | class NotBoolable: 1481 | __bool__ = None 1482 | 1483 | b1 = NotBoolable() 1484 | b2 = NotBoolable() 1485 | 1486 | if b1: # exception raised here 1487 | pass 1488 | 1489 | b1 and b2 # exception raised here 1490 | not b1 # exception raised here 1491 | b1 < b2 < b1 # exception raised here 1492 | ``` 1493 | 1494 | ### Links 1495 | * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-bool-conversion) 1496 | * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1004) 1497 |
1498 | 1499 | ## `unsupported-operator` 1500 | 1501 | **Default level**: error 1502 | 1503 |
1504 | detects binary, unary, or comparison expressions where the operands don't support the operator 1505 | 1506 | ### What it does 1507 | Checks for binary expressions, comparisons, and unary expressions where 1508 | the operands don't support the operator. 1509 | 1510 | ### Why is this bad? 1511 | Attempting to use an unsupported operator will raise a `TypeError` at 1512 | runtime. 1513 | 1514 | ### Examples 1515 | ```python 1516 | class A: ... 1517 | 1518 | A() + A() # TypeError: unsupported operand type(s) for +: 'A' and 'A' 1519 | ``` 1520 | 1521 | ### Links 1522 | * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-operator) 1523 | * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1330) 1524 |
1525 | 1526 | ## `zero-stepsize-in-slice` 1527 | 1528 | **Default level**: error 1529 | 1530 |
1531 | detects a slice step size of zero 1532 | 1533 | ### What it does 1534 | Checks for step size 0 in slices. 1535 | 1536 | ### Why is this bad? 1537 | A slice with a step size of zero will raise a `ValueError` at runtime. 1538 | 1539 | ### Examples 1540 | ```python 1541 | l = list(range(10)) 1542 | l[1:10:0] # ValueError: slice step cannot be zero 1543 | ``` 1544 | 1545 | ### Links 1546 | * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20zero-stepsize-in-slice) 1547 | * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1352) 1548 |
1549 | 1550 | ## `invalid-ignore-comment` 1551 | 1552 | **Default level**: warn 1553 | 1554 |
1555 | detects ignore comments that use invalid syntax 1556 | 1557 | ### What it does 1558 | Checks for `type: ignore` and `ty: ignore` comments that are syntactically incorrect. 1559 | 1560 | ### Why is this bad? 1561 | A syntactically incorrect ignore comment is probably a mistake and is useless. 1562 | 1563 | ### Examples 1564 | ```py 1565 | a = 20 / 0 # type: ignoree 1566 | ``` 1567 | 1568 | Use instead: 1569 | 1570 | ```py 1571 | a = 20 / 0 # type: ignore 1572 | ``` 1573 | 1574 | ### Links 1575 | * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-ignore-comment) 1576 | * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Fsuppression.rs#L65) 1577 |
1578 | 1579 | ## `possibly-unbound-attribute` 1580 | 1581 | **Default level**: warn 1582 | 1583 |
1584 | detects references to possibly unbound attributes 1585 | 1586 | ### What it does 1587 | Checks for possibly unbound attributes. 1588 | 1589 | ### Why is this bad? 1590 | Attempting to access an unbound attribute will raise an `AttributeError` at runtime. 1591 | 1592 | ### Examples 1593 | ```python 1594 | class A: 1595 | if b: 1596 | c = 0 1597 | 1598 | A.c # AttributeError: type object 'A' has no attribute 'c' 1599 | ``` 1600 | 1601 | ### Links 1602 | * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unbound-attribute) 1603 | * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1056) 1604 |
1605 | 1606 | ## `possibly-unbound-implicit-call` 1607 | 1608 | **Default level**: warn 1609 | 1610 |
1611 | detects implicit calls to possibly unbound methods 1612 | 1613 | ### What it does 1614 | Checks for implicit calls to possibly unbound methods. 1615 | 1616 | ### Why is this bad? 1617 | Expressions such as `x[y]` and `x * y` call methods 1618 | under the hood (`__getitem__` and `__mul__` respectively). 1619 | Calling an unbound method will raise an `AttributeError` at runtime. 1620 | 1621 | ### Examples 1622 | ```python 1623 | import datetime 1624 | 1625 | class A: 1626 | if datetime.date.today().weekday() != 6: 1627 | def __getitem__(self, v): ... 1628 | 1629 | A()[0] # TypeError: 'A' object is not subscriptable 1630 | ``` 1631 | 1632 | ### Links 1633 | * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unbound-implicit-call) 1634 | * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L109) 1635 |
1636 | 1637 | ## `possibly-unbound-import` 1638 | 1639 | **Default level**: warn 1640 | 1641 |
1642 | detects possibly unbound imports 1643 | 1644 | ### What it does 1645 | Checks for imports of symbols that may be unbound. 1646 | 1647 | ### Why is this bad? 1648 | Importing an unbound module or name will raise a `ModuleNotFoundError` 1649 | or `ImportError` at runtime. 1650 | 1651 | ### Examples 1652 | ```python 1653 | ## module.py 1654 | import datetime 1655 | 1656 | if datetime.date.today().weekday() != 6: 1657 | a = 1 1658 | 1659 | ## main.py 1660 | from module import a # ImportError: cannot import name 'a' from 'module' 1661 | ``` 1662 | 1663 | ### Links 1664 | * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unbound-import) 1665 | * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1078) 1666 |
1667 | 1668 | ## `redundant-cast` 1669 | 1670 | **Default level**: warn 1671 | 1672 |
1673 | detects redundant cast calls 1674 | 1675 | ### What it does 1676 | Detects redundant `cast` calls where the value already has the target type. 1677 | 1678 | ### Why is this bad? 1679 | These casts have no effect and can be removed. 1680 | 1681 | ### Example 1682 | ```python 1683 | def f() -> int: 1684 | return 10 1685 | 1686 | cast(int, f()) # Redundant 1687 | ``` 1688 | 1689 | ### Links 1690 | * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20redundant-cast) 1691 | * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1423) 1692 |
1693 | 1694 | ## `undefined-reveal` 1695 | 1696 | **Default level**: warn 1697 | 1698 |
1699 | detects usages of reveal_type without importing it 1700 | 1701 | ### What it does 1702 | Checks for calls to `reveal_type` without importing it. 1703 | 1704 | ### Why is this bad? 1705 | Using `reveal_type` without importing it will raise a `NameError` at runtime. 1706 | 1707 | ### Examples 1708 | ```python 1709 | reveal_type(1) # NameError: name 'reveal_type' is not defined 1710 | ``` 1711 | 1712 | ### Links 1713 | * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20undefined-reveal) 1714 | * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1231) 1715 |
1716 | 1717 | ## `unknown-rule` 1718 | 1719 | **Default level**: warn 1720 | 1721 |
1722 | detects ty: ignore comments that reference unknown rules 1723 | 1724 | ### What it does 1725 | Checks for `ty: ignore[code]` where `code` isn't a known lint rule. 1726 | 1727 | ### Why is this bad? 1728 | A `ty: ignore[code]` directive with a `code` that doesn't match 1729 | any known rule will not suppress any type errors, and is probably a mistake. 1730 | 1731 | ### Examples 1732 | ```py 1733 | a = 20 / 0 # ty: ignore[division-by-zer] 1734 | ``` 1735 | 1736 | Use instead: 1737 | 1738 | ```py 1739 | a = 20 / 0 # ty: ignore[division-by-zero] 1740 | ``` 1741 | 1742 | ### Links 1743 | * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unknown-rule) 1744 | * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Fsuppression.rs#L40) 1745 |
1746 | 1747 | ## `unsupported-base` 1748 | 1749 | **Default level**: warn 1750 | 1751 |
1752 | detects class bases that are unsupported as ty could not feasibly calculate the class's MRO 1753 | 1754 | ### What it does 1755 | Checks for class definitions that have bases which are unsupported by ty. 1756 | 1757 | ### Why is this bad? 1758 | If a class has a base that is an instance of a complex type such as a union type, 1759 | ty will not be able to resolve the [method resolution order] (MRO) for the class. 1760 | This will lead to an inferior understanding of your codebase and unpredictable 1761 | type-checking behavior. 1762 | 1763 | ### Examples 1764 | ```python 1765 | import datetime 1766 | 1767 | class A: ... 1768 | class B: ... 1769 | 1770 | if datetime.date.today().weekday() != 6: 1771 | C = A 1772 | else: 1773 | C = B 1774 | 1775 | class D(C): ... # error: [unsupported-base] 1776 | ``` 1777 | 1778 | [method resolution order]: https://docs.python.org/3/glossary.html#term-method-resolution-order 1779 | 1780 | ### Links 1781 | * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-base) 1782 | * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L487) 1783 |
1784 | 1785 | ## `division-by-zero` 1786 | 1787 | **Default level**: ignore 1788 | 1789 |
1790 | detects division by zero 1791 | 1792 | ### What it does 1793 | It detects division by zero. 1794 | 1795 | ### Why is this bad? 1796 | Dividing by zero raises a `ZeroDivisionError` at runtime. 1797 | 1798 | ### Examples 1799 | ```python 1800 | 5 / 0 1801 | ``` 1802 | 1803 | ### Links 1804 | * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20division-by-zero) 1805 | * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L238) 1806 |
1807 | 1808 | ## `possibly-unresolved-reference` 1809 | 1810 | **Default level**: ignore 1811 | 1812 |
1813 | detects references to possibly undefined names 1814 | 1815 | ### What it does 1816 | Checks for references to names that are possibly not defined. 1817 | 1818 | ### Why is this bad? 1819 | Using an undefined variable will raise a `NameError` at runtime. 1820 | 1821 | ### Example 1822 | 1823 | ```python 1824 | for i in range(0): 1825 | x = i 1826 | 1827 | print(x) # NameError: name 'x' is not defined 1828 | ``` 1829 | 1830 | ### Links 1831 | * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unresolved-reference) 1832 | * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1104) 1833 |
1834 | 1835 | ## `unused-ignore-comment` 1836 | 1837 | **Default level**: ignore 1838 | 1839 |
1840 | detects unused type: ignore comments 1841 | 1842 | ### What it does 1843 | Checks for `type: ignore` or `ty: ignore` directives that are no longer applicable. 1844 | 1845 | ### Why is this bad? 1846 | A `type: ignore` directive that no longer matches any diagnostic violations is likely 1847 | included by mistake, and should be removed to avoid confusion. 1848 | 1849 | ### Examples 1850 | ```py 1851 | a = 20 / 2 # ty: ignore[division-by-zero] 1852 | ``` 1853 | 1854 | Use instead: 1855 | 1856 | ```py 1857 | a = 20 / 2 1858 | ``` 1859 | 1860 | ### Links 1861 | * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unused-ignore-comment) 1862 | * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Fsuppression.rs#L15) 1863 |
1864 | 1865 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "ty" 3 | version = "0.0.1a7" 4 | requires-python = ">=3.8" 5 | dependencies = [] 6 | description = "An extremely fast Python type checker, written in Rust." 7 | readme = "README.md" 8 | authors = [{ name = "Astral Software Inc.", email = "hey@astral.sh" }] 9 | keywords = ["ty", "typing", "analysis", "check"] 10 | classifiers = [ 11 | "Development Status :: 3 - Alpha", 12 | "Environment :: Console", 13 | "Intended Audience :: Developers", 14 | "Operating System :: OS Independent", 15 | "License :: OSI Approved :: MIT License", 16 | "Programming Language :: Python", 17 | "Programming Language :: Python :: 3.8", 18 | "Programming Language :: Python :: 3.9", 19 | "Programming Language :: Python :: 3.10", 20 | "Programming Language :: Python :: 3.11", 21 | "Programming Language :: Python :: 3.12", 22 | "Programming Language :: Python :: 3.13", 23 | "Programming Language :: Python :: 3 :: Only", 24 | "Programming Language :: Rust", 25 | "Topic :: Software Development :: Quality Assurance", 26 | "Topic :: Software Development :: Testing", 27 | "Topic :: Software Development :: Libraries", 28 | "Topic :: Software Development :: Libraries :: Python Modules", 29 | ] 30 | 31 | [project.urls] 32 | Repository = "https://github.com/astral-sh/ty" 33 | Changelog = "https://github.com/astral-sh/ty/blob/main/CHANGELOG.md" 34 | Releases = "https://github.com/astral-sh/ty/releases" 35 | Discord = "https://discord.gg/astral-sh" 36 | 37 | [build-system] 38 | requires = ["maturin>=1.0,<2.0"] 39 | build-backend = "maturin" 40 | 41 | [dependency-groups] 42 | release = ["rooster-blue"] 43 | 44 | [tool.uv] 45 | cache-keys = [ 46 | { file = "pyproject.toml" }, 47 | { file = "dist-workspace.toml" }, 48 | { file = "ruff/Cargo.toml" }, 49 | { file = "ruff/Cargo.lock" }, 50 | { file = "**/*.rs" }, 51 | ] 52 | 53 | [tool.ruff] 54 | extend-exclude = ["ruff"] 55 | 56 | [tool.ruff.lint] 57 | select = [ 58 | "E", # pycodestyle (error) 59 | "F", # pyflakes 60 | "B", # bugbear 61 | "B9", 62 | "C4", # flake8-comprehensions 63 | "SIM", # flake8-simplify 64 | "I", # isort 65 | "UP", # pyupgrade 66 | "PIE", # flake8-pie 67 | "PGH", # pygrep-hooks 68 | "PYI", # flake8-pyi 69 | "RUF", 70 | ] 71 | 72 | ignore = [ 73 | # only relevant if you run a script with `python -0`, 74 | # which seems unlikely for any of the scripts in this repo 75 | "B011", 76 | # Leave it to the formatter to split long lines and 77 | # the judgement of all of us. 78 | "E501", 79 | ] 80 | 81 | [tool.ruff.lint.isort] 82 | required-imports = ["from __future__ import annotations"] 83 | 84 | 85 | [tool.uv.sources] 86 | rooster-blue = { git = "https://github.com/zanieb/rooster", rev = "cf27242" } 87 | 88 | [tool.maturin] 89 | bindings = "bin" 90 | manifest-path = "ruff/crates/ty/Cargo.toml" 91 | module-name = "ty" 92 | python-source = "python" 93 | strip = true 94 | include = [ 95 | { path = "dist-workspace.toml", format = [ 96 | "sdist", 97 | ] }, 98 | { path = "LICENSE", format = "sdist" }, 99 | ] 100 | 101 | [tool.rooster] 102 | version-files = [ 103 | "pyproject.toml", 104 | { path = "dist-workspace.toml", field = "workspace.version", format = "cargo" }, 105 | ] 106 | submodules = ["ruff"] 107 | require-labels = [{ submodule = "ruff", labels = ["ty"] }] 108 | ignore-labels = ["internal", "testing"] 109 | version-format = "cargo" 110 | default-bump-type = "pre" 111 | trim-title-prefixes = ["[ty]"] 112 | 113 | changelog_sections.breaking = "Breaking changes" 114 | changelog_sections.preview = "Preview features" 115 | changelog_sections.bug = "Bug fixes" 116 | changelog_sections.server = "Server" 117 | changelog_sections.cli = "CLI" 118 | changelog_sections.configuration = "Configuration" 119 | changelog_sections.documentation = "Documentation" 120 | -------------------------------------------------------------------------------- /python/ty/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/astral-sh/ty/afb20f6feb139cf7d01ff3dc80cbe02c8bf011d1/python/ty/__init__.py -------------------------------------------------------------------------------- /python/ty/__main__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import os 4 | import sys 5 | import sysconfig 6 | 7 | 8 | def find_ty_bin() -> str: 9 | """Return the ty binary path.""" 10 | 11 | ty_exe = "ty" + sysconfig.get_config_var("EXE") 12 | 13 | scripts_path = os.path.join(sysconfig.get_path("scripts"), ty_exe) 14 | if os.path.isfile(scripts_path): 15 | return scripts_path 16 | 17 | if sys.version_info >= (3, 10): 18 | user_scheme = sysconfig.get_preferred_scheme("user") 19 | elif os.name == "nt": 20 | user_scheme = "nt_user" 21 | elif sys.platform == "darwin" and sys._framework: 22 | user_scheme = "osx_framework_user" 23 | else: 24 | user_scheme = "posix_user" 25 | 26 | user_path = os.path.join(sysconfig.get_path("scripts", scheme=user_scheme), ty_exe) 27 | if os.path.isfile(user_path): 28 | return user_path 29 | 30 | # Search in `bin` adjacent to package root (as created by `pip install --target`). 31 | pkg_root = os.path.dirname(os.path.dirname(__file__)) 32 | target_path = os.path.join(pkg_root, "bin", ty_exe) 33 | if os.path.isfile(target_path): 34 | return target_path 35 | 36 | # Search for pip-specific build environments. 37 | # 38 | # Expect to find ty in /pip-build-env-/overlay/bin/ty 39 | # Expect to find a "normal" folder at /pip-build-env-/normal 40 | # 41 | # See: https://github.com/pypa/pip/blob/102d8187a1f5a4cd5de7a549fd8a9af34e89a54f/src/pip/_internal/build_env.py#L87 42 | paths = os.environ.get("PATH", "").split(os.pathsep) 43 | if len(paths) >= 2: 44 | 45 | def get_last_three_path_parts(path: str) -> list[str]: 46 | """Return a list of up to the last three parts of a path.""" 47 | parts = [] 48 | 49 | while len(parts) < 3: 50 | head, tail = os.path.split(path) 51 | if tail or head != path: 52 | parts.append(tail) 53 | path = head 54 | else: 55 | parts.append(path) 56 | break 57 | 58 | return parts 59 | 60 | maybe_overlay = get_last_three_path_parts(paths[0]) 61 | maybe_normal = get_last_three_path_parts(paths[1]) 62 | if ( 63 | len(maybe_normal) >= 3 64 | and maybe_normal[-1].startswith("pip-build-env-") 65 | and maybe_normal[-2] == "normal" 66 | and len(maybe_overlay) >= 3 67 | and maybe_overlay[-1].startswith("pip-build-env-") 68 | and maybe_overlay[-2] == "overlay" 69 | ): 70 | # The overlay must contain the ty binary. 71 | candidate = os.path.join(paths[0], ty_exe) 72 | if os.path.isfile(candidate): 73 | return candidate 74 | 75 | raise FileNotFoundError(scripts_path) 76 | 77 | 78 | if __name__ == "__main__": 79 | ty = os.fsdecode(find_ty_bin()) 80 | if sys.platform == "win32": 81 | import subprocess 82 | 83 | completed_process = subprocess.run([ty, *sys.argv[1:]]) 84 | sys.exit(completed_process.returncode) 85 | else: 86 | os.execvp(ty, [ty, *sys.argv[1:]]) 87 | -------------------------------------------------------------------------------- /python/ty/py.typed: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /scripts/autogenerate_files.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | # 3 | # Generate files and copy documentation from Ruff. 4 | # 5 | # Usage 6 | # 7 | # ./scripts/autogenerate-files.sh 8 | # 9 | set -eu 10 | 11 | script_root="$(realpath "$(dirname "$0")")" 12 | project_root="$(dirname "$script_root")" 13 | cd "$project_root" 14 | 15 | echo "Updating lockfile..." 16 | uv lock 17 | 18 | echo "Copying reference documentation from Ruff..." 19 | cp ruff/crates/ty/docs/cli.md ./docs/reference/ 20 | cp ruff/crates/ty/docs/configuration.md ./docs/reference/ 21 | cp ./ruff/crates/ty/docs/rules.md ./docs/reference/ 22 | 23 | echo "Documentation has been copied from Ruff submodule" 24 | -------------------------------------------------------------------------------- /scripts/release.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | # 3 | # Prepare a release. 4 | # 5 | # Usage 6 | # 7 | # ./scripts/release.sh [rooster-args ...] 8 | # 9 | set -eu 10 | 11 | echo "Checking Ruff submodule status..." 12 | if git -C ruff diff --quiet; then 13 | echo "Ruff submodule is clean; continuing..." 14 | else 15 | echo "Ruff submodule has uncommitted changes; aborting!" 16 | exit 1 17 | fi 18 | 19 | ruff_head=$(git -C ruff rev-parse --abbrev-ref HEAD) 20 | case "${ruff_head}" in 21 | "HEAD") 22 | echo "Ruff submodule has detached HEAD; switching to main..." 23 | git -C ruff checkout main > /dev/null 2>&1 24 | ;; 25 | "main") 26 | echo "Ruff submodule is on main branch; continuing..." 27 | ;; 28 | *) 29 | echo "Ruff submodule is on branch ${ruff_head} but must be on main; aborting!" 30 | exit 1 31 | ;; 32 | esac 33 | 34 | 35 | echo "Updating Ruff to the latest commit..." 36 | git -C ruff pull origin main 37 | git add ruff 38 | 39 | script_root="$(realpath "$(dirname "$0")")" 40 | project_root="$(dirname "$script_root")" 41 | 42 | echo "Running rooster..." 43 | cd "$project_root" 44 | 45 | # Generate the changelog and bump versions 46 | uv run --only-group release \ 47 | rooster release "$@" 48 | 49 | "${script_root}/autogenerate_files.sh" 50 | git add ./docs/reference 51 | -------------------------------------------------------------------------------- /scripts/transform_readme.py: -------------------------------------------------------------------------------- 1 | """Transform the README.md to support a specific deployment target. 2 | 3 | By default, we assume that our README.md will be rendered on GitHub. However, 4 | PyPI includes the README with different rendering. 5 | """ 6 | 7 | from __future__ import annotations 8 | 9 | import re 10 | import urllib.parse 11 | from pathlib import Path 12 | 13 | import tomllib 14 | 15 | 16 | def main() -> None: 17 | """Modify the README.md to support PyPI.""" 18 | # Read the current version from the `dist-workspace.toml`. 19 | with Path("dist-workspace.toml").open(mode="rb") as fp: 20 | # Parse the TOML. 21 | dist_workspace = tomllib.load(fp) 22 | if "workspace" in dist_workspace and "version" in dist_workspace["workspace"]: 23 | version = dist_workspace["workspace"]["version"] 24 | else: 25 | raise ValueError("Version not found in dist-workspace.toml") 26 | 27 | content = Path("README.md").read_text(encoding="utf8") 28 | 29 | # Replace any relative URLs (e.g., `[CONTRIBUTING.md`) with absolute URLs. 30 | def replace(match: re.Match) -> str: 31 | url = match.group(1) 32 | if not url.startswith("http"): 33 | url = urllib.parse.urljoin( 34 | f"https://github.com/astral-sh/ty/blob/{version}/README.md", url 35 | ) 36 | return f"]({url})" 37 | 38 | content = re.sub(r"]\(([^)]+)\)", replace, content) 39 | 40 | # Replace any GitHub admonitions 41 | def replace(match: re.Match) -> str: 42 | name = match.group(1) 43 | return f"> {name}:" 44 | 45 | content = re.sub(r"> \[\!(\w*)\]", replace, content) 46 | 47 | with Path("README.md").open("w", encoding="utf8") as fp: 48 | fp.write(content) 49 | 50 | 51 | if __name__ == "__main__": 52 | main() 53 | -------------------------------------------------------------------------------- /scripts/update_schemastore.py: -------------------------------------------------------------------------------- 1 | """Update ty.json in schemastore. 2 | 3 | This script will clone `astral-sh/schemastore`, update the schema and push the changes 4 | to a new branch tagged with the ty git hash. You should see a URL to create the PR 5 | to schemastore in the CLI. 6 | 7 | Usage: 8 | 9 | uv run --only-dev scripts/update_schemastore.py 10 | """ 11 | 12 | from __future__ import annotations 13 | 14 | import enum 15 | import json 16 | from pathlib import Path 17 | from subprocess import check_call, check_output 18 | from tempfile import TemporaryDirectory 19 | from typing import NamedTuple, assert_never 20 | 21 | # The remote URL for the `ty` repository. 22 | TY_REPO = "https://github.com/astral-sh/ty" 23 | 24 | # The path to the root of the `ty` repository. 25 | TY_ROOT = Path(__file__).parent.parent 26 | 27 | # The path to the JSON schema in the `ty` repository. 28 | TY_SCHEMA = TY_ROOT / "ruff" / "ty.schema.json" 29 | 30 | # The path to the JSON schema in the `schemastore` repository. 31 | TY_JSON = Path("schemas/json/ty.json") 32 | 33 | 34 | class SchemastoreRepos(NamedTuple): 35 | fork: str 36 | upstream: str 37 | 38 | 39 | class GitProtocol(enum.Enum): 40 | SSH = "ssh" 41 | HTTPS = "https" 42 | 43 | def schemastore_repos(self) -> SchemastoreRepos: 44 | match self: 45 | case GitProtocol.SSH: 46 | return SchemastoreRepos( 47 | fork="git@github.com:astral-sh/schemastore.git", 48 | upstream="git@github.com:SchemaStore/schemastore.git", 49 | ) 50 | case GitProtocol.HTTPS: 51 | return SchemastoreRepos( 52 | fork="https://github.com/astral-sh/schemastore.git", 53 | upstream="https://github.com/SchemaStore/schemastore.git", 54 | ) 55 | case _: 56 | assert_never(self) 57 | 58 | 59 | def update_schemastore( 60 | schemastore_path: Path, schemastore_repos: SchemastoreRepos 61 | ) -> None: 62 | if not (schemastore_path / ".git").is_dir(): 63 | check_call( 64 | ["git", "clone", schemastore_repos.fork, schemastore_path, "--depth=1"], 65 | ) 66 | check_call( 67 | [ 68 | "git", 69 | "remote", 70 | "add", 71 | "upstream", 72 | schemastore_repos.upstream, 73 | ], 74 | cwd=schemastore_path, 75 | ) 76 | 77 | # Create a new branch tagged with the current ty commit up to date with the latest 78 | # upstream schemastore 79 | check_call(["git", "fetch", "upstream"], cwd=schemastore_path) 80 | current_sha = check_output(["git", "rev-parse", "HEAD"], text=True).strip() 81 | branch = f"update-ty-{current_sha}" 82 | check_call( 83 | ["git", "switch", "-c", branch], 84 | cwd=schemastore_path, 85 | ) 86 | check_call( 87 | ["git", "reset", "--hard", "upstream/master"], 88 | cwd=schemastore_path, 89 | ) 90 | 91 | # Run npm install 92 | src = schemastore_path / "src" 93 | check_call(["npm", "install"], cwd=schemastore_path) 94 | 95 | # Update the schema and format appropriately 96 | schema = json.loads(TY_SCHEMA.read_text()) 97 | schema["$id"] = "https://json.schemastore.org/ty.json" 98 | (src / TY_JSON).write_text( 99 | json.dumps(dict(schema.items()), indent=2, ensure_ascii=False), 100 | ) 101 | check_call( 102 | [ 103 | "../node_modules/prettier/bin/prettier.cjs", 104 | "--plugin", 105 | "prettier-plugin-sort-json", 106 | "--write", 107 | TY_JSON, 108 | ], 109 | cwd=src, 110 | ) 111 | 112 | # Check if the schema has changed 113 | # https://stackoverflow.com/a/9393642/3549270 114 | if check_output(["git", "status", "-s"], cwd=schemastore_path).strip(): 115 | # Schema has changed, commit and push 116 | commit_url = f"{TY_REPO}/commit/{current_sha}" 117 | commit_body = f"This updates ty's JSON schema to [{current_sha}]({commit_url})" 118 | # https://stackoverflow.com/a/22909204/3549270 119 | check_call( 120 | [ 121 | "git", 122 | "commit", 123 | "-a", 124 | "-m", 125 | "Update ty's JSON schema", 126 | "-m", 127 | commit_body, 128 | ], 129 | cwd=schemastore_path, 130 | ) 131 | # This should show the link to create a PR 132 | check_call( 133 | ["git", "push", "--set-upstream", "origin", branch, "--force"], 134 | cwd=schemastore_path, 135 | ) 136 | else: 137 | print("No changes") 138 | 139 | 140 | def determine_git_protocol(argv: list[str] | None = None) -> GitProtocol: 141 | import argparse 142 | 143 | parser = argparse.ArgumentParser( 144 | description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter 145 | ) 146 | parser.add_argument( 147 | "--proto", 148 | choices=[proto.value for proto in GitProtocol], 149 | default="https", 150 | help="Protocol to use for git authentication", 151 | ) 152 | args = parser.parse_args(argv) 153 | return GitProtocol(args.proto) 154 | 155 | 156 | def main() -> None: 157 | expected_ruff_revision = check_output( 158 | ["git", "ls-tree", "main", "--format", "%(objectname)", "ruff"], cwd=TY_ROOT 159 | ).strip() 160 | actual_ruff_revision = check_output( 161 | ["git", "-C", "ruff", "rev-parse", "HEAD"], cwd=TY_ROOT 162 | ).strip() 163 | 164 | if expected_ruff_revision != actual_ruff_revision: 165 | print( 166 | f"The ruff submodule is at {expected_ruff_revision} but main expects {actual_ruff_revision}" 167 | ) 168 | match input( 169 | "How do you want to proceed (u=reset submodule, n=abort, y=continue)? " 170 | ): 171 | case "u": 172 | check_call( 173 | ["git", "-C", "ruff", "reset", "--hard", expected_ruff_revision], 174 | cwd=TY_ROOT, 175 | ) 176 | case "n": 177 | return 178 | case "y": 179 | ... 180 | case command: 181 | print(f"Invalid input '{command}', abort") 182 | return 183 | 184 | schemastore_repos = determine_git_protocol().schemastore_repos() 185 | schemastore_existing = TY_ROOT / "schemastore" 186 | if schemastore_existing.is_dir(): 187 | update_schemastore(schemastore_existing, schemastore_repos) 188 | else: 189 | with TemporaryDirectory(prefix="ty-schemastore-") as temp_dir: 190 | update_schemastore(Path(temp_dir), schemastore_repos) 191 | 192 | 193 | if __name__ == "__main__": 194 | main() 195 | --------------------------------------------------------------------------------