diff --git a/.github/workflows/build-binaries.yml b/.github/workflows/build-binaries.yml index c26802228f..abc6b8a448 100644 --- a/.github/workflows/build-binaries.yml +++ b/.github/workflows/build-binaries.yml @@ -43,7 +43,7 @@ jobs: with: submodules: recursive persist-credentials: false - - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 + - uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0 with: python-version: ${{ env.PYTHON_VERSION }} - name: "Prep README.md" @@ -72,7 +72,7 @@ jobs: with: submodules: recursive persist-credentials: false - - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 + - uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0 with: python-version: ${{ env.PYTHON_VERSION }} architecture: x64 @@ -114,7 +114,7 @@ jobs: with: submodules: recursive persist-credentials: false - - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 + - uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0 with: python-version: ${{ env.PYTHON_VERSION }} architecture: arm64 @@ -170,7 +170,7 @@ jobs: with: submodules: recursive persist-credentials: false - - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 + - uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0 with: python-version: ${{ env.PYTHON_VERSION }} architecture: ${{ matrix.platform.arch }} @@ -223,7 +223,7 @@ jobs: with: submodules: recursive persist-credentials: false - - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 + - uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0 with: python-version: ${{ env.PYTHON_VERSION }} architecture: x64 @@ -300,7 +300,7 @@ jobs: with: submodules: recursive persist-credentials: false - - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 + - uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0 with: python-version: ${{ env.PYTHON_VERSION }} - name: "Prep README.md" @@ -365,7 +365,7 @@ jobs: with: submodules: recursive persist-credentials: false - - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 + - uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0 with: python-version: ${{ env.PYTHON_VERSION }} architecture: x64 @@ -431,7 +431,7 @@ jobs: with: submodules: recursive persist-credentials: false - - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 + - uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0 with: python-version: ${{ env.PYTHON_VERSION }} - name: "Prep README.md" diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 4418e35b82..2685f96f94 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -230,7 +230,7 @@ jobs: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: persist-credentials: false - - uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1 + - uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2 with: save-if: ${{ github.ref == 'refs/heads/main' }} - name: "Install Rust toolchain" @@ -252,7 +252,7 @@ jobs: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: persist-credentials: false - - uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1 + - uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2 with: shared-key: ruff-linux-debug save-if: ${{ github.ref == 'refs/heads/main' }} @@ -261,11 +261,11 @@ jobs: - name: "Install mold" uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1 - name: "Install cargo nextest" - uses: taiki-e/install-action@f79fe7514db78f0a7bdba3cb6dd9c1baa7d046d9 # v2.62.56 + uses: taiki-e/install-action@3575e532701a5fc614b0c842e4119af4cc5fd16d # v2.62.60 with: tool: cargo-nextest - name: "Install cargo insta" - uses: taiki-e/install-action@f79fe7514db78f0a7bdba3cb6dd9c1baa7d046d9 # v2.62.56 + uses: taiki-e/install-action@3575e532701a5fc614b0c842e4119af4cc5fd16d # v2.62.60 with: tool: cargo-insta - name: "Install uv" @@ -315,7 +315,7 @@ jobs: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: persist-credentials: false - - uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1 + - uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2 with: save-if: ${{ github.ref == 'refs/heads/main' }} - name: "Install Rust toolchain" @@ -323,7 +323,7 @@ jobs: - name: "Install mold" uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1 - name: "Install cargo nextest" - uses: taiki-e/install-action@f79fe7514db78f0a7bdba3cb6dd9c1baa7d046d9 # v2.62.56 + uses: taiki-e/install-action@3575e532701a5fc614b0c842e4119af4cc5fd16d # v2.62.60 with: tool: cargo-nextest - name: "Install uv" @@ -350,13 +350,13 @@ jobs: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: persist-credentials: false - - uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1 + - uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2 with: save-if: ${{ github.ref == 'refs/heads/main' }} - name: "Install Rust toolchain" run: rustup show - name: "Install cargo nextest" - uses: taiki-e/install-action@f79fe7514db78f0a7bdba3cb6dd9c1baa7d046d9 # v2.62.56 + uses: taiki-e/install-action@3575e532701a5fc614b0c842e4119af4cc5fd16d # v2.62.60 with: tool: cargo-nextest - name: "Install uv" @@ -378,7 +378,7 @@ jobs: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: persist-credentials: false - - uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1 + - uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2 with: save-if: ${{ github.ref == 'refs/heads/main' }} - name: "Install Rust toolchain" @@ -415,7 +415,7 @@ jobs: with: file: "Cargo.toml" field: "workspace.package.rust-version" - - uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1 + - uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2 with: save-if: ${{ github.ref == 'refs/heads/main' }} - name: "Install Rust toolchain" @@ -439,7 +439,7 @@ jobs: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: persist-credentials: false - - uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1 + - uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2 with: workspaces: "fuzz -> target" save-if: ${{ github.ref == 'refs/heads/main' }} @@ -448,7 +448,7 @@ jobs: - name: "Install mold" uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1 - name: "Install cargo-binstall" - uses: cargo-bins/cargo-binstall@ae04fb5e853ae6cd3ad7de4a1d554a8b646d12aa # v1.15.11 + uses: cargo-bins/cargo-binstall@3fc81674af4165a753833a94cae9f91d8849049f # v1.16.2 - name: "Install cargo-fuzz" # Download the latest version from quick install and not the github releases because github releases only has MUSL targets. run: cargo binstall cargo-fuzz --force --disable-strategies crate-meta-data --no-confirm @@ -467,7 +467,7 @@ jobs: with: persist-credentials: false - uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4 - - uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1 + - uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2 with: shared-key: ruff-linux-debug save-if: false @@ -498,7 +498,7 @@ jobs: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: persist-credentials: false - - uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1 + - uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2 with: save-if: ${{ github.ref == 'refs/heads/main' }} - uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4 @@ -547,7 +547,7 @@ jobs: - name: "Install mold" uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1 - - uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1 + - uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2 with: shared-key: ruff-linux-debug save-if: false @@ -643,7 +643,7 @@ jobs: fetch-depth: 0 persist-credentials: false - uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4 - - uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1 + - uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2 with: save-if: ${{ github.ref == 'refs/heads/main' }} - name: "Install Rust toolchain" @@ -688,7 +688,7 @@ jobs: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: persist-credentials: false - - uses: cargo-bins/cargo-binstall@ae04fb5e853ae6cd3ad7de4a1d554a8b646d12aa # v1.15.11 + - uses: cargo-bins/cargo-binstall@3fc81674af4165a753833a94cae9f91d8849049f # v1.16.2 - run: cargo binstall --no-confirm cargo-shear - run: cargo shear @@ -702,7 +702,7 @@ jobs: with: persist-credentials: false - uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4 - - uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1 + - uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2 with: save-if: ${{ github.ref == 'refs/heads/main' }} - name: "Install Rust toolchain" @@ -723,11 +723,11 @@ jobs: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: persist-credentials: false - - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 + - uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0 with: python-version: ${{ env.PYTHON_VERSION }} architecture: x64 - - uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1 + - uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2 with: save-if: ${{ github.ref == 'refs/heads/main' }} - name: "Prep README.md" @@ -753,7 +753,7 @@ jobs: with: persist-credentials: false - uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4 - - uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1 + - uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2 with: save-if: ${{ github.ref == 'refs/heads/main' }} - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 @@ -785,7 +785,7 @@ jobs: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: persist-credentials: false - - uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1 + - uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2 with: save-if: ${{ github.ref == 'refs/heads/main' }} - name: "Add SSH key" @@ -829,7 +829,7 @@ jobs: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: persist-credentials: false - - uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1 + - uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2 with: save-if: ${{ github.ref == 'refs/heads/main' }} - name: "Install Rust toolchain" @@ -857,7 +857,7 @@ jobs: with: persist-credentials: false - - uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1 + - uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2 with: shared-key: ruff-linux-debug save-if: false @@ -875,7 +875,7 @@ jobs: repository: "astral-sh/ruff-lsp" path: ruff-lsp - - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 + - uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0 with: # installation fails on 3.13 and newer python-version: "3.12" @@ -908,7 +908,7 @@ jobs: persist-credentials: false - name: "Install Rust toolchain" run: rustup target add wasm32-unknown-unknown - - uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1 + - uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2 with: save-if: ${{ github.ref == 'refs/heads/main' }} - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 @@ -948,7 +948,7 @@ jobs: with: persist-credentials: false - - uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1 + - uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2 with: save-if: ${{ github.ref == 'refs/heads/main' }} - uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4 @@ -957,7 +957,7 @@ jobs: run: rustup show - name: "Install codspeed" - uses: taiki-e/install-action@f79fe7514db78f0a7bdba3cb6dd9c1baa7d046d9 # v2.62.56 + uses: taiki-e/install-action@3575e532701a5fc614b0c842e4119af4cc5fd16d # v2.62.60 with: tool: cargo-codspeed @@ -965,7 +965,7 @@ jobs: run: cargo codspeed build --features "codspeed,instrumented" --profile profiling --no-default-features -p ruff_benchmark --bench formatter --bench lexer --bench linter --bench parser - name: "Run benchmarks" - uses: CodSpeedHQ/action@6a8e2b874c338bf81cc5e8be715ada75908d3871 # v4.3.4 + uses: CodSpeedHQ/action@346a2d8a8d9d38909abd0bc3d23f773110f076ad # v4.4.1 with: mode: instrumentation run: cargo codspeed run @@ -988,7 +988,7 @@ jobs: with: persist-credentials: false - - uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1 + - uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2 with: save-if: ${{ github.ref == 'refs/heads/main' }} - uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4 @@ -997,7 +997,7 @@ jobs: run: rustup show - name: "Install codspeed" - uses: taiki-e/install-action@f79fe7514db78f0a7bdba3cb6dd9c1baa7d046d9 # v2.62.56 + uses: taiki-e/install-action@3575e532701a5fc614b0c842e4119af4cc5fd16d # v2.62.60 with: tool: cargo-codspeed @@ -1005,7 +1005,7 @@ jobs: run: cargo codspeed build --features "codspeed,instrumented" --profile profiling --no-default-features -p ruff_benchmark --bench ty - name: "Run benchmarks" - uses: CodSpeedHQ/action@6a8e2b874c338bf81cc5e8be715ada75908d3871 # v4.3.4 + uses: CodSpeedHQ/action@346a2d8a8d9d38909abd0bc3d23f773110f076ad # v4.4.1 with: mode: instrumentation run: cargo codspeed run @@ -1028,7 +1028,7 @@ jobs: with: persist-credentials: false - - uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1 + - uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2 with: save-if: ${{ github.ref == 'refs/heads/main' }} - uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4 @@ -1037,7 +1037,7 @@ jobs: run: rustup show - name: "Install codspeed" - uses: taiki-e/install-action@f79fe7514db78f0a7bdba3cb6dd9c1baa7d046d9 # v2.62.56 + uses: taiki-e/install-action@3575e532701a5fc614b0c842e4119af4cc5fd16d # v2.62.60 with: tool: cargo-codspeed @@ -1045,7 +1045,7 @@ jobs: run: cargo codspeed build --features "codspeed,walltime" --profile profiling --no-default-features -p ruff_benchmark - name: "Run benchmarks" - uses: CodSpeedHQ/action@6a8e2b874c338bf81cc5e8be715ada75908d3871 # v4.3.4 + uses: CodSpeedHQ/action@346a2d8a8d9d38909abd0bc3d23f773110f076ad # v4.4.1 env: # enabling walltime flamegraphs adds ~6 minutes to the CI time, and they don't # appear to provide much useful insight for our walltime benchmarks right now diff --git a/.github/workflows/daily_fuzz.yaml b/.github/workflows/daily_fuzz.yaml index 7b013df83f..630b79ce49 100644 --- a/.github/workflows/daily_fuzz.yaml +++ b/.github/workflows/daily_fuzz.yaml @@ -39,7 +39,7 @@ jobs: run: rustup show - name: "Install mold" uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1 - - uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1 + - uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2 - name: Build ruff # A debug build means the script runs slower once it gets started, # but this is outweighed by the fact that a release build takes *much* longer to compile in CI diff --git a/.github/workflows/mypy_primer.yaml b/.github/workflows/mypy_primer.yaml index d4a480d028..b2f7f5e275 100644 --- a/.github/workflows/mypy_primer.yaml +++ b/.github/workflows/mypy_primer.yaml @@ -45,7 +45,7 @@ jobs: - name: Install the latest version of uv uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4 - - uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1 + - uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2 with: workspaces: "ruff" @@ -83,7 +83,7 @@ jobs: - name: Install the latest version of uv uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4 - - uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1 + - uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2 with: workspaces: "ruff" diff --git a/.github/workflows/publish-docs.yml b/.github/workflows/publish-docs.yml index 8adbe4536c..000b866c48 100644 --- a/.github/workflows/publish-docs.yml +++ b/.github/workflows/publish-docs.yml @@ -28,7 +28,7 @@ jobs: ref: ${{ inputs.ref }} persist-credentials: true - - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 + - uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0 with: python-version: 3.12 @@ -68,7 +68,7 @@ jobs: - name: "Install Rust toolchain" run: rustup show - - uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1 + - uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2 - name: "Install Insiders dependencies" if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }} diff --git a/.github/workflows/sync_typeshed.yaml b/.github/workflows/sync_typeshed.yaml index ef3d5e6598..276e190c49 100644 --- a/.github/workflows/sync_typeshed.yaml +++ b/.github/workflows/sync_typeshed.yaml @@ -198,7 +198,7 @@ jobs: run: | rm "${VENDORED_TYPESHED}/pyproject.toml" git commit -am "Remove pyproject.toml file" - - uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1 + - uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2 - name: "Install Rust toolchain" if: ${{ success() }} run: rustup show @@ -207,12 +207,12 @@ jobs: uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1 - name: "Install cargo nextest" if: ${{ success() }} - uses: taiki-e/install-action@f79fe7514db78f0a7bdba3cb6dd9c1baa7d046d9 # v2.62.56 + uses: taiki-e/install-action@3575e532701a5fc614b0c842e4119af4cc5fd16d # v2.62.60 with: tool: cargo-nextest - name: "Install cargo insta" if: ${{ success() }} - uses: taiki-e/install-action@f79fe7514db78f0a7bdba3cb6dd9c1baa7d046d9 # v2.62.56 + uses: taiki-e/install-action@3575e532701a5fc614b0c842e4119af4cc5fd16d # v2.62.60 with: tool: cargo-insta - name: Update snapshots diff --git a/.github/workflows/ty-ecosystem-analyzer.yaml b/.github/workflows/ty-ecosystem-analyzer.yaml index 417ab77bad..6586d97845 100644 --- a/.github/workflows/ty-ecosystem-analyzer.yaml +++ b/.github/workflows/ty-ecosystem-analyzer.yaml @@ -37,7 +37,7 @@ jobs: with: enable-cache: true # zizmor: ignore[cache-poisoning] acceptable risk for CloudFlare pages artifact - - uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1 + - uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2 with: workspaces: "ruff" lookup-only: false # zizmor: ignore[cache-poisoning] acceptable risk for CloudFlare pages artifact diff --git a/.github/workflows/ty-ecosystem-report.yaml b/.github/workflows/ty-ecosystem-report.yaml index aa07cccd04..cdfb3b1446 100644 --- a/.github/workflows/ty-ecosystem-report.yaml +++ b/.github/workflows/ty-ecosystem-report.yaml @@ -33,7 +33,7 @@ jobs: with: enable-cache: true # zizmor: ignore[cache-poisoning] acceptable risk for CloudFlare pages artifact - - uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1 + - uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2 with: workspaces: "ruff" lookup-only: false # zizmor: ignore[cache-poisoning] acceptable risk for CloudFlare pages artifact diff --git a/.github/workflows/typing_conformance.yaml b/.github/workflows/typing_conformance.yaml index aefe0b6c40..82fac8a456 100644 --- a/.github/workflows/typing_conformance.yaml +++ b/.github/workflows/typing_conformance.yaml @@ -45,7 +45,7 @@ jobs: path: typing persist-credentials: false - - uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1 + - uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2 with: workspaces: "ruff" diff --git a/Cargo.lock b/Cargo.lock index dd9f1f40f6..fa2b9e4252 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1108,7 +1108,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -1763,7 +1763,7 @@ dependencies = [ "portable-atomic", "portable-atomic-util", "serde_core", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -3570,7 +3570,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -3588,7 +3588,7 @@ checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" [[package]] name = "salsa" version = "0.24.0" -source = "git+https://github.com/salsa-rs/salsa.git?rev=17bc55d699565e5a1cb1bd42363b905af2f9f3e7#17bc55d699565e5a1cb1bd42363b905af2f9f3e7" +source = "git+https://github.com/salsa-rs/salsa.git?rev=59aa1075e837f5deb0d6ffb24b68fedc0f4bc5e0#59aa1075e837f5deb0d6ffb24b68fedc0f4bc5e0" dependencies = [ "boxcar", "compact_str", @@ -3612,12 +3612,12 @@ dependencies = [ [[package]] name = "salsa-macro-rules" version = "0.24.0" -source = "git+https://github.com/salsa-rs/salsa.git?rev=17bc55d699565e5a1cb1bd42363b905af2f9f3e7#17bc55d699565e5a1cb1bd42363b905af2f9f3e7" +source = "git+https://github.com/salsa-rs/salsa.git?rev=59aa1075e837f5deb0d6ffb24b68fedc0f4bc5e0#59aa1075e837f5deb0d6ffb24b68fedc0f4bc5e0" [[package]] name = "salsa-macros" version = "0.24.0" -source = "git+https://github.com/salsa-rs/salsa.git?rev=17bc55d699565e5a1cb1bd42363b905af2f9f3e7#17bc55d699565e5a1cb1bd42363b905af2f9f3e7" +source = "git+https://github.com/salsa-rs/salsa.git?rev=59aa1075e837f5deb0d6ffb24b68fedc0f4bc5e0#59aa1075e837f5deb0d6ffb24b68fedc0f4bc5e0" dependencies = [ "proc-macro2", "quote", @@ -3971,7 +3971,7 @@ dependencies = [ "getrandom 0.3.4", "once_cell", "rustix", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -4216,9 +4216,9 @@ checksum = "df8b2b54733674ad286d16267dcfc7a71ed5c776e4ac7aa3c3e2561f7c637bf2" [[package]] name = "tracing" -version = "0.1.41" +version = "0.1.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +checksum = "2d15d90a0b5c19378952d479dc858407149d7bb45a14de0142f6c534b16fc647" dependencies = [ "log", "pin-project-lite", @@ -4228,9 +4228,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.30" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", @@ -4239,9 +4239,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.34" +version = "0.1.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +checksum = "7a04e24fab5c89c6a36eb8558c9656f30d81de51dfa4d3b45f26b21d61fa0a6c" dependencies = [ "once_cell", "valuable", @@ -4283,9 +4283,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.20" +version = "0.3.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5" +checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e" dependencies = [ "chrono", "matchers", @@ -5024,7 +5024,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 0b9578a245..e948f46085 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -146,7 +146,7 @@ regex-automata = { version = "0.4.9" } rustc-hash = { version = "2.0.0" } rustc-stable-hash = { version = "0.1.2" } # When updating salsa, make sure to also update the revision in `fuzz/Cargo.toml` -salsa = { git = "https://github.com/salsa-rs/salsa.git", rev = "17bc55d699565e5a1cb1bd42363b905af2f9f3e7", default-features = false, features = [ +salsa = { git = "https://github.com/salsa-rs/salsa.git", rev = "59aa1075e837f5deb0d6ffb24b68fedc0f4bc5e0", default-features = false, features = [ "compact_str", "macros", "salsa_unstable", diff --git a/crates/ruff_db/src/diagnostic/mod.rs b/crates/ruff_db/src/diagnostic/mod.rs index a8f2d09dd8..33348ddf2e 100644 --- a/crates/ruff_db/src/diagnostic/mod.rs +++ b/crates/ruff_db/src/diagnostic/mod.rs @@ -354,6 +354,13 @@ impl Diagnostic { Arc::make_mut(&mut self.inner).fix = Some(fix); } + /// If `fix` is `Some`, set the fix for this diagnostic. + pub fn set_optional_fix(&mut self, fix: Option) { + if let Some(fix) = fix { + self.set_fix(fix); + } + } + /// Remove the fix for this diagnostic. pub fn remove_fix(&mut self) { Arc::make_mut(&mut self.inner).fix = None; diff --git a/crates/ty/docs/rules.md b/crates/ty/docs/rules.md index 6923aeef6f..78f757a6bb 100644 --- a/crates/ty/docs/rules.md +++ b/crates/ty/docs/rules.md @@ -39,7 +39,7 @@ def test(): -> "int": Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -63,7 +63,7 @@ Calling a non-callable object will raise a `TypeError` at runtime. Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -95,7 +95,7 @@ f(int) # error Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -126,7 +126,7 @@ a = 1 Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -158,7 +158,7 @@ class C(A, B): ... Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -190,7 +190,7 @@ class B(A): ... Default level: error · Preview (since 1.0.0) · Related issues · -View source +View source @@ -218,7 +218,7 @@ type B = A Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -245,7 +245,7 @@ class B(A, A): ... Default level: error · Added in 0.0.1-alpha.12 · Related issues · -View source +View source @@ -357,7 +357,7 @@ def test(): -> "Literal[5]": Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -387,7 +387,7 @@ class C(A, B): ... Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -413,7 +413,7 @@ t[3] # IndexError: tuple index out of range Default level: error · Added in 0.0.1-alpha.12 · Related issues · -View source +View source @@ -502,7 +502,7 @@ an atypical memory layout. Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -529,7 +529,7 @@ func("foo") # error: [invalid-argument-type] Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -557,7 +557,7 @@ a: int = '' Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -591,7 +591,7 @@ C.instance_var = 3 # error: Cannot assign to instance variable Default level: error · Added in 0.0.1-alpha.19 · Related issues · -View source +View source @@ -627,7 +627,7 @@ asyncio.run(main()) Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -651,7 +651,7 @@ class A(42): ... # error: [invalid-base] Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -678,7 +678,7 @@ with 1: Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -707,7 +707,7 @@ a: str Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -751,7 +751,7 @@ except ZeroDivisionError: Default level: error · Added in 0.0.1-alpha.28 · Related issues · -View source +View source @@ -793,7 +793,7 @@ class D(A): Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -826,7 +826,7 @@ class C[U](Generic[T]): ... Default level: error · Added in 0.0.1-alpha.17 · Related issues · -View source +View source @@ -865,7 +865,7 @@ carol = Person(name="Carol", age=25) # typo! Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -900,7 +900,7 @@ def f(t: TypeVar("U")): ... Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -934,7 +934,7 @@ class B(metaclass=f): ... Default level: error · Added in 0.0.1-alpha.20 · Related issues · -View source +View source @@ -1041,7 +1041,7 @@ Correct use of `@override` is enforced by ty's `invalid-explicit-override` rule. Default level: error · Added in 0.0.1-alpha.19 · Related issues · -View source +View source @@ -1073,7 +1073,7 @@ TypeError: can only inherit from a NamedTuple type and Generic Default level: error · Preview (since 1.0.0) · Related issues · -View source +View source @@ -1103,7 +1103,7 @@ Baz = NewType("Baz", int | str) # error: invalid base for `typing.NewType` Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -1153,7 +1153,7 @@ def foo(x: int) -> int: ... Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -1179,7 +1179,7 @@ def f(a: int = ''): ... Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -1210,7 +1210,7 @@ P2 = ParamSpec("S2") # error: ParamSpec name must match the variable it's assig Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -1244,7 +1244,7 @@ TypeError: Protocols can only inherit from other protocols, got Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -1293,7 +1293,7 @@ def g(): Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -1318,7 +1318,7 @@ def func() -> int: Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -1376,7 +1376,7 @@ TODO #14889 Default level: error · Added in 0.0.1-alpha.6 · Related issues · -View source +View source @@ -1403,7 +1403,7 @@ NewAlias = TypeAliasType(get_name(), int) # error: TypeAliasType name mus Default level: error · Added in 0.0.1-alpha.29 · Related issues · -View source +View source @@ -1450,7 +1450,7 @@ Bar[int] # error: too few arguments Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -1480,7 +1480,7 @@ TYPE_CHECKING = '' Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -1510,7 +1510,7 @@ b: Annotated[int] # `Annotated` expects at least two arguments Default level: error · Added in 0.0.1-alpha.11 · Related issues · -View source +View source @@ -1544,7 +1544,7 @@ f(10) # Error Default level: error · Added in 0.0.1-alpha.11 · Related issues · -View source +View source @@ -1578,7 +1578,7 @@ class C: Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -1613,7 +1613,7 @@ T = TypeVar('T', bound=str) # valid bound TypeVar Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -1638,7 +1638,7 @@ func() # TypeError: func() missing 1 required positional argument: 'x' Default level: error · Added in 0.0.1-alpha.20 · Related issues · -View source +View source @@ -1671,7 +1671,7 @@ alice["age"] # KeyError Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -1700,7 +1700,7 @@ func("string") # error: [no-matching-overload] Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -1724,7 +1724,7 @@ Subscripting an object that does not support it will raise a `TypeError` at runt Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -1750,7 +1750,7 @@ for i in 34: # TypeError: 'int' object is not iterable Default level: error · Added in 0.0.1-alpha.29 · Related issues · -View source +View source @@ -1783,7 +1783,7 @@ class B(A): Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -1810,7 +1810,7 @@ f(1, x=2) # Error raised here Default level: error · Added in 0.0.1-alpha.22 · Related issues · -View source +View source @@ -1868,7 +1868,7 @@ def test(): -> "int": Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -1898,7 +1898,7 @@ static_assert(int(2.0 * 3.0) == 6) # error: does not have a statically known tr Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -1921,13 +1921,47 @@ class A: ... class B(A): ... # Error raised here ``` +## `super-call-in-named-tuple-method` + + +Default level: error · +Preview (since 0.0.1-alpha.30) · +Related issues · +View source + + + +**What it does** + +Checks for calls to `super()` inside methods of `NamedTuple` classes. + +**Why is this bad?** + +Using `super()` in a method of a `NamedTuple` class will raise an exception at runtime. + +**Examples** + +```python +from typing import NamedTuple + +class F(NamedTuple): + x: int + + def method(self): + super() # error: super() is not supported in methods of NamedTuple classes +``` + +**References** + +- [Python documentation: super()](https://docs.python.org/3/library/functions.html#super) + ## `too-many-positional-arguments` Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -1954,7 +1988,7 @@ f("foo") # Error raised here Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -1982,7 +2016,7 @@ def _(x: int): Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -2028,7 +2062,7 @@ class A: Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -2055,7 +2089,7 @@ f(x=1, y=2) # Error raised here Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -2083,7 +2117,7 @@ A().foo # AttributeError: 'A' object has no attribute 'foo' Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -2108,7 +2142,7 @@ import foo # ModuleNotFoundError: No module named 'foo' Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -2133,7 +2167,7 @@ print(x) # NameError: name 'x' is not defined Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -2170,7 +2204,7 @@ b1 < b2 < b1 # exception raised here Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -2198,7 +2232,7 @@ A() + A() # TypeError: unsupported operand type(s) for +: 'A' and 'A' Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -2223,7 +2257,7 @@ l[1:10:0] # ValueError: slice step cannot be zero Default level: warn · Added in 0.0.1-alpha.20 · Related issues · -View source +View source @@ -2264,7 +2298,7 @@ class SubProto(BaseProto, Protocol): Default level: warn · Added in 0.0.1-alpha.16 · Related issues · -View source +View source @@ -2352,7 +2386,7 @@ a = 20 / 0 # type: ignore Default level: warn · Added in 0.0.1-alpha.22 · Related issues · -View source +View source @@ -2380,7 +2414,7 @@ A.c # AttributeError: type object 'A' has no attribute 'c' Default level: warn · Added in 0.0.1-alpha.22 · Related issues · -View source +View source @@ -2412,7 +2446,7 @@ A()[0] # TypeError: 'A' object is not subscriptable Default level: warn · Added in 0.0.1-alpha.22 · Related issues · -View source +View source @@ -2444,7 +2478,7 @@ from module import a # ImportError: cannot import name 'a' from 'module' Default level: warn · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -2471,7 +2505,7 @@ cast(int, f()) # Redundant Default level: warn · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -2495,7 +2529,7 @@ reveal_type(1) # NameError: name 'reveal_type' is not defined Default level: warn · Added in 0.0.1-alpha.15 · Related issues · -View source +View source @@ -2553,7 +2587,7 @@ def g(): Default level: warn · Added in 0.0.1-alpha.7 · Related issues · -View source +View source @@ -2592,7 +2626,7 @@ class D(C): ... # error: [unsupported-base] Default level: warn · Added in 0.0.1-alpha.22 · Related issues · -View source +View source @@ -2655,7 +2689,7 @@ def foo(x: int | str) -> int | str: Default level: ignore · Preview (since 0.0.1-alpha.1) · Related issues · -View source +View source @@ -2679,7 +2713,7 @@ Dividing by zero raises a `ZeroDivisionError` at runtime. Default level: ignore · Added in 0.0.1-alpha.1 · Related issues · -View source +View source diff --git a/crates/ty_python_semantic/resources/mdtest/final.md b/crates/ty_python_semantic/resources/mdtest/final.md index 1ccb647e4d..91c6f9273f 100644 --- a/crates/ty_python_semantic/resources/mdtest/final.md +++ b/crates/ty_python_semantic/resources/mdtest/final.md @@ -51,6 +51,10 @@ class Parent: @final def my_property2(self) -> int: ... + @property + @final + def my_property3(self) -> int: ... + @final @classmethod def class_method1(cls) -> int: ... @@ -86,6 +90,13 @@ class Child(Parent): @property def my_property2(self) -> int: ... # error: [override-of-final-method] + @my_property2.setter + def my_property2(self, x: int) -> None: ... + + @property + def my_property3(self) -> int: ... # error: [override-of-final-method] + @my_property3.deleter + def my_proeprty3(self) -> None: ... @classmethod def class_method1(cls) -> int: ... # error: [override-of-final-method] @@ -230,7 +241,7 @@ class ChildOfBad(Bad): def bar(self, x: str) -> str: ... @overload def bar(self, x: int) -> int: ... # error: [override-of-final-method] - + @overload def baz(self, x: str) -> str: ... @overload @@ -461,14 +472,17 @@ class B(A): def method1(self) -> None: ... # error: [override-of-final-method] def method2(self) -> None: ... # error: [override-of-final-method] def method3(self) -> None: ... # error: [override-of-final-method] - def method4(self) -> None: ... # error: [override-of-final-method] + + # check that autofixes don't introduce invalid syntax + # if there are multiple statements on one line + # + # TODO: we should emit a Liskov violation here too + # error: [override-of-final-method] + method4 = 42; unrelated = 56 # fmt: skip # Possible overrides of possibly `@final` methods... class C(A): if coinflip(): - # TODO: the autofix here introduces invalid syntax because there are now no - # statements inside the `if:` branch - # (but it might still be a useful autofix in an IDE context?) def method1(self) -> None: ... # error: [override-of-final-method] else: pass diff --git a/crates/ty_python_semantic/resources/mdtest/named_tuple.md b/crates/ty_python_semantic/resources/mdtest/named_tuple.md index c49cf1708c..439cfff06b 100644 --- a/crates/ty_python_semantic/resources/mdtest/named_tuple.md +++ b/crates/ty_python_semantic/resources/mdtest/named_tuple.md @@ -408,3 +408,77 @@ class Vec2(NamedTuple): Vec2(0.0, 0.0) ``` + +## `super()` is not supported in NamedTuple methods + +Using `super()` in a method of a `NamedTuple` class will raise an exception at runtime. In Python +3.14+, a `TypeError` is raised; in earlier versions, a confusing `RuntimeError` about +`__classcell__` is raised. + +```py +from typing import NamedTuple + +class F(NamedTuple): + x: int + + def method(self): + # error: [super-call-in-named-tuple-method] "Cannot use `super()` in a method of NamedTuple class `F`" + super() + + def method_with_args(self): + # error: [super-call-in-named-tuple-method] "Cannot use `super()` in a method of NamedTuple class `F`" + super(F, self) + + def method_with_different_pivot(self): + # Even passing a different pivot class fails. + # error: [super-call-in-named-tuple-method] "Cannot use `super()` in a method of NamedTuple class `F`" + super(tuple, self) + + @classmethod + def class_method(cls): + # error: [super-call-in-named-tuple-method] "Cannot use `super()` in a method of NamedTuple class `F`" + super() + + @staticmethod + def static_method(): + # error: [super-call-in-named-tuple-method] "Cannot use `super()` in a method of NamedTuple class `F`" + super() + + @property + def prop(self): + # error: [super-call-in-named-tuple-method] "Cannot use `super()` in a method of NamedTuple class `F`" + return super() +``` + +However, classes that **inherit from** a `NamedTuple` class (but don't directly inherit from +`NamedTuple`) can use `super()` normally: + +```py +from typing import NamedTuple + +class Base(NamedTuple): + x: int + +class Child(Base): + def method(self): + super() +``` + +And regular classes that don't inherit from `NamedTuple` at all can use `super()` as normal: + +```py +class Regular: + def method(self): + super() # fine +``` + +Using `super()` on a `NamedTuple` class also works fine if it occurs outside the class: + +```py +from typing import NamedTuple + +class F(NamedTuple): + x: int + +super(F, F(42)) # fine +``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/final.md_-_Tests_for_the_`@typi…_-_A_possibly-undefined…_(fc7b496fd1986deb).snap b/crates/ty_python_semantic/resources/mdtest/snapshots/final.md_-_Tests_for_the_`@typi…_-_A_possibly-undefined…_(fc7b496fd1986deb).snap index 41b39fef34..a137c44c51 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/final.md_-_Tests_for_the_`@typi…_-_A_possibly-undefined…_(fc7b496fd1986deb).snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/final.md_-_Tests_for_the_`@typi…_-_A_possibly-undefined…_(fc7b496fd1986deb).snap @@ -49,26 +49,29 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/final.md 35 | def method1(self) -> None: ... # error: [override-of-final-method] 36 | def method2(self) -> None: ... # error: [override-of-final-method] 37 | def method3(self) -> None: ... # error: [override-of-final-method] -38 | def method4(self) -> None: ... # error: [override-of-final-method] -39 | -40 | # Possible overrides of possibly `@final` methods... -41 | class C(A): -42 | if coinflip(): -43 | # TODO: the autofix here introduces invalid syntax because there are now no -44 | # statements inside the `if:` branch -45 | # (but it might still be a useful autofix in an IDE context?) -46 | def method1(self) -> None: ... # error: [override-of-final-method] -47 | else: -48 | pass -49 | -50 | if coinflip(): -51 | def method2(self) -> None: ... # TODO: should emit [override-of-final-method] -52 | else: -53 | def method2(self) -> None: ... # TODO: should emit [override-of-final-method] -54 | -55 | if coinflip(): -56 | def method3(self) -> None: ... # error: [override-of-final-method] -57 | def method4(self) -> None: ... # error: [override-of-final-method] +38 | +39 | # check that autofixes don't introduce invalid syntax +40 | # if there are multiple statements on one line +41 | # +42 | # TODO: we should emit a Liskov violation here too +43 | # error: [override-of-final-method] +44 | method4 = 42; unrelated = 56 # fmt: skip +45 | +46 | # Possible overrides of possibly `@final` methods... +47 | class C(A): +48 | if coinflip(): +49 | def method1(self) -> None: ... # error: [override-of-final-method] +50 | else: +51 | pass +52 | +53 | if coinflip(): +54 | def method2(self) -> None: ... # TODO: should emit [override-of-final-method] +55 | else: +56 | def method2(self) -> None: ... # TODO: should emit [override-of-final-method] +57 | +58 | if coinflip(): +59 | def method3(self) -> None: ... # error: [override-of-final-method] +60 | def method4(self) -> None: ... # error: [override-of-final-method] ``` # Diagnostics @@ -104,7 +107,7 @@ info: rule `override-of-final-method` is enabled by default 35 + # error: [override-of-final-method] 36 | def method2(self) -> None: ... # error: [override-of-final-method] 37 | def method3(self) -> None: ... # error: [override-of-final-method] -38 | def method4(self) -> None: ... # error: [override-of-final-method] +38 | note: This is an unsafe fix and may change runtime behavior ``` @@ -118,7 +121,6 @@ error[override-of-final-method]: Cannot override `A.method2` 36 | def method2(self) -> None: ... # error: [override-of-final-method] | ^^^^^^^ Overrides a definition from superclass `A` 37 | def method3(self) -> None: ... # error: [override-of-final-method] -38 | def method4(self) -> None: ... # error: [override-of-final-method] | info: `A.method2` is decorated with `@final`, forbidding overrides --> src/mdtest_snippet.py:16:9 @@ -140,8 +142,8 @@ info: rule `override-of-final-method` is enabled by default - def method2(self) -> None: ... # error: [override-of-final-method] 36 + # error: [override-of-final-method] 37 | def method3(self) -> None: ... # error: [override-of-final-method] -38 | def method4(self) -> None: ... # error: [override-of-final-method] -39 | +38 | +39 | # check that autofixes don't introduce invalid syntax note: This is an unsafe fix and may change runtime behavior ``` @@ -154,7 +156,8 @@ error[override-of-final-method]: Cannot override `A.method3` 36 | def method2(self) -> None: ... # error: [override-of-final-method] 37 | def method3(self) -> None: ... # error: [override-of-final-method] | ^^^^^^^ Overrides a definition from superclass `A` -38 | def method4(self) -> None: ... # error: [override-of-final-method] +38 | +39 | # check that autofixes don't introduce invalid syntax | info: `A.method3` is decorated with `@final`, forbidding overrides --> src/mdtest_snippet.py:20:9 @@ -174,23 +177,23 @@ info: rule `override-of-final-method` is enabled by default 36 | def method2(self) -> None: ... # error: [override-of-final-method] - def method3(self) -> None: ... # error: [override-of-final-method] 37 + # error: [override-of-final-method] -38 | def method4(self) -> None: ... # error: [override-of-final-method] -39 | -40 | # Possible overrides of possibly `@final` methods... +38 | +39 | # check that autofixes don't introduce invalid syntax +40 | # if there are multiple statements on one line note: This is an unsafe fix and may change runtime behavior ``` ``` error[override-of-final-method]: Cannot override `A.method4` - --> src/mdtest_snippet.py:38:9 + --> src/mdtest_snippet.py:44:5 | -36 | def method2(self) -> None: ... # error: [override-of-final-method] -37 | def method3(self) -> None: ... # error: [override-of-final-method] -38 | def method4(self) -> None: ... # error: [override-of-final-method] - | ^^^^^^^ Overrides a definition from superclass `A` -39 | -40 | # Possible overrides of possibly `@final` methods... +42 | # TODO: we should emit a Liskov violation here too +43 | # error: [override-of-final-method] +44 | method4 = 42; unrelated = 56 # fmt: skip + | ^^^^^^^ Overrides a definition from superclass `A` +45 | +46 | # Possible overrides of possibly `@final` methods... | info: `A.method4` is decorated with `@final`, forbidding overrides --> src/mdtest_snippet.py:29:9 @@ -206,28 +209,19 @@ info: `A.method4` is decorated with `@final`, forbidding overrides | help: Remove the override of `method4` info: rule `override-of-final-method` is enabled by default -35 | def method1(self) -> None: ... # error: [override-of-final-method] -36 | def method2(self) -> None: ... # error: [override-of-final-method] -37 | def method3(self) -> None: ... # error: [override-of-final-method] - - def method4(self) -> None: ... # error: [override-of-final-method] -38 + # error: [override-of-final-method] -39 | -40 | # Possible overrides of possibly `@final` methods... -41 | class C(A): -note: This is an unsafe fix and may change runtime behavior ``` ``` error[override-of-final-method]: Cannot override `A.method1` - --> src/mdtest_snippet.py:46:13 + --> src/mdtest_snippet.py:49:13 | -44 | # statements inside the `if:` branch -45 | # (but it might still be a useful autofix in an IDE context?) -46 | def method1(self) -> None: ... # error: [override-of-final-method] +47 | class C(A): +48 | if coinflip(): +49 | def method1(self) -> None: ... # error: [override-of-final-method] | ^^^^^^^ Overrides a definition from superclass `A` -47 | else: -48 | pass +50 | else: +51 | pass | info: `A.method1` is decorated with `@final`, forbidding overrides --> src/mdtest_snippet.py:8:9 @@ -243,26 +237,17 @@ info: `A.method1` is decorated with `@final`, forbidding overrides | help: Remove the override of `method1` info: rule `override-of-final-method` is enabled by default -43 | # TODO: the autofix here introduces invalid syntax because there are now no -44 | # statements inside the `if:` branch -45 | # (but it might still be a useful autofix in an IDE context?) - - def method1(self) -> None: ... # error: [override-of-final-method] -46 + # error: [override-of-final-method] -47 | else: -48 | pass -49 | -note: This is an unsafe fix and may change runtime behavior ``` ``` error[override-of-final-method]: Cannot override `A.method3` - --> src/mdtest_snippet.py:56:13 + --> src/mdtest_snippet.py:59:13 | -55 | if coinflip(): -56 | def method3(self) -> None: ... # error: [override-of-final-method] +58 | if coinflip(): +59 | def method3(self) -> None: ... # error: [override-of-final-method] | ^^^^^^^ Overrides a definition from superclass `A` -57 | def method4(self) -> None: ... # error: [override-of-final-method] +60 | def method4(self) -> None: ... # error: [override-of-final-method] | info: `A.method3` is decorated with `@final`, forbidding overrides --> src/mdtest_snippet.py:20:9 @@ -277,23 +262,16 @@ info: `A.method3` is decorated with `@final`, forbidding overrides | help: Remove the override of `method3` info: rule `override-of-final-method` is enabled by default -53 | def method2(self) -> None: ... # TODO: should emit [override-of-final-method] -54 | -55 | if coinflip(): - - def method3(self) -> None: ... # error: [override-of-final-method] -56 + # error: [override-of-final-method] -57 | def method4(self) -> None: ... # error: [override-of-final-method] -note: This is an unsafe fix and may change runtime behavior ``` ``` error[override-of-final-method]: Cannot override `A.method4` - --> src/mdtest_snippet.py:57:13 + --> src/mdtest_snippet.py:60:13 | -55 | if coinflip(): -56 | def method3(self) -> None: ... # error: [override-of-final-method] -57 | def method4(self) -> None: ... # error: [override-of-final-method] +58 | if coinflip(): +59 | def method3(self) -> None: ... # error: [override-of-final-method] +60 | def method4(self) -> None: ... # error: [override-of-final-method] | ^^^^^^^ Overrides a definition from superclass `A` | info: `A.method4` is decorated with `@final`, forbidding overrides @@ -310,11 +288,5 @@ info: `A.method4` is decorated with `@final`, forbidding overrides | help: Remove the override of `method4` info: rule `override-of-final-method` is enabled by default -54 | -55 | if coinflip(): -56 | def method3(self) -> None: ... # error: [override-of-final-method] - - def method4(self) -> None: ... # error: [override-of-final-method] -57 + # error: [override-of-final-method] -note: This is an unsafe fix and may change runtime behavior ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/final.md_-_Tests_for_the_`@typi…_-_Cannot_override_a_me…_(338615109711a91b).snap b/crates/ty_python_semantic/resources/mdtest/snapshots/final.md_-_Tests_for_the_`@typi…_-_Cannot_override_a_me…_(338615109711a91b).snap index ad51738ec5..fbb89644c6 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/final.md_-_Tests_for_the_`@typi…_-_Cannot_override_a_me…_(338615109711a91b).snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/final.md_-_Tests_for_the_`@typi…_-_Cannot_override_a_me…_(338615109711a91b).snap @@ -28,93 +28,93 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/final.md 14 | @final 15 | def my_property2(self) -> int: ... 16 | - 17 | @final - 18 | @classmethod - 19 | def class_method1(cls) -> int: ... + 17 | @property + 18 | @final + 19 | def my_property3(self) -> int: ... 20 | - 21 | @classmethod - 22 | @final - 23 | def class_method2(cls) -> int: ... + 21 | @final + 22 | @classmethod + 23 | def class_method1(cls) -> int: ... 24 | - 25 | @final - 26 | @staticmethod - 27 | def static_method1() -> int: ... + 25 | @classmethod + 26 | @final + 27 | def class_method2(cls) -> int: ... 28 | - 29 | @staticmethod - 30 | @final - 31 | def static_method2() -> int: ... + 29 | @final + 30 | @staticmethod + 31 | def static_method1() -> int: ... 32 | - 33 | @lossy_decorator + 33 | @staticmethod 34 | @final - 35 | def decorated_1(self): ... + 35 | def static_method2() -> int: ... 36 | - 37 | @final - 38 | @lossy_decorator - 39 | def decorated_2(self): ... + 37 | @lossy_decorator + 38 | @final + 39 | def decorated_1(self): ... 40 | - 41 | class Child(Parent): - 42 | # explicitly test the concise diagnostic message, - 43 | # which is different to the verbose diagnostic summary message: - 44 | # - 45 | # error: [override-of-final-method] "Cannot override final member `foo` from superclass `Parent`" - 46 | def foo(self): ... - 47 | @property - 48 | def my_property1(self) -> int: ... # error: [override-of-final-method] - 49 | - 50 | @property - 51 | def my_property2(self) -> int: ... # error: [override-of-final-method] - 52 | - 53 | @classmethod - 54 | def class_method1(cls) -> int: ... # error: [override-of-final-method] - 55 | - 56 | @staticmethod - 57 | def static_method1() -> int: ... # error: [override-of-final-method] + 41 | @final + 42 | @lossy_decorator + 43 | def decorated_2(self): ... + 44 | + 45 | class Child(Parent): + 46 | # explicitly test the concise diagnostic message, + 47 | # which is different to the verbose diagnostic summary message: + 48 | # + 49 | # error: [override-of-final-method] "Cannot override final member `foo` from superclass `Parent`" + 50 | def foo(self): ... + 51 | @property + 52 | def my_property1(self) -> int: ... # error: [override-of-final-method] + 53 | + 54 | @property + 55 | def my_property2(self) -> int: ... # error: [override-of-final-method] + 56 | @my_property2.setter + 57 | def my_property2(self, x: int) -> None: ... 58 | - 59 | @classmethod - 60 | def class_method2(cls) -> int: ... # error: [override-of-final-method] - 61 | - 62 | @staticmethod - 63 | def static_method2() -> int: ... # error: [override-of-final-method] - 64 | - 65 | def decorated_1(self): ... # TODO: should emit [override-of-final-method] + 59 | @property + 60 | def my_property3(self) -> int: ... # error: [override-of-final-method] + 61 | @my_property3.deleter + 62 | def my_proeprty3(self) -> None: ... + 63 | + 64 | @classmethod + 65 | def class_method1(cls) -> int: ... # error: [override-of-final-method] 66 | - 67 | @lossy_decorator - 68 | def decorated_2(self): ... # TODO: should emit [override-of-final-method] + 67 | @staticmethod + 68 | def static_method1() -> int: ... # error: [override-of-final-method] 69 | - 70 | class OtherChild(Parent): ... - 71 | - 72 | class Grandchild(OtherChild): + 70 | @classmethod + 71 | def class_method2(cls) -> int: ... # error: [override-of-final-method] + 72 | 73 | @staticmethod - 74 | # TODO: we should emit a Liskov violation here too - 75 | # error: [override-of-final-method] - 76 | def foo(): ... - 77 | @property - 78 | # TODO: we should emit a Liskov violation here too - 79 | # error: [override-of-final-method] - 80 | def my_property1(self) -> str: ... - 81 | # TODO: we should emit a Liskov violation here too - 82 | # error: [override-of-final-method] - 83 | class_method1 = None - 84 | - 85 | # Diagnostic edge case: `final` is very far away from the method definition in the source code: - 86 | - 87 | T = TypeVar("T") - 88 | - 89 | def identity(x: T) -> T: ... - 90 | - 91 | class Foo: - 92 | @final - 93 | @identity - 94 | @identity - 95 | @identity - 96 | @identity - 97 | @identity - 98 | @identity - 99 | @identity -100 | @identity -101 | @identity -102 | @identity -103 | @identity + 74 | def static_method2() -> int: ... # error: [override-of-final-method] + 75 | + 76 | def decorated_1(self): ... # TODO: should emit [override-of-final-method] + 77 | + 78 | @lossy_decorator + 79 | def decorated_2(self): ... # TODO: should emit [override-of-final-method] + 80 | + 81 | class OtherChild(Parent): ... + 82 | + 83 | class Grandchild(OtherChild): + 84 | @staticmethod + 85 | # TODO: we should emit a Liskov violation here too + 86 | # error: [override-of-final-method] + 87 | def foo(): ... + 88 | @property + 89 | # TODO: we should emit a Liskov violation here too + 90 | # error: [override-of-final-method] + 91 | def my_property1(self) -> str: ... + 92 | # TODO: we should emit a Liskov violation here too + 93 | # error: [override-of-final-method] + 94 | class_method1 = None + 95 | + 96 | # Diagnostic edge case: `final` is very far away from the method definition in the source code: + 97 | + 98 | T = TypeVar("T") + 99 | +100 | def identity(x: T) -> T: ... +101 | +102 | class Foo: +103 | @final 104 | @identity 105 | @identity 106 | @identity @@ -122,24 +122,35 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/final.md 108 | @identity 109 | @identity 110 | @identity -111 | def bar(self): ... -112 | -113 | class Baz(Foo): -114 | def bar(self): ... # error: [override-of-final-method] +111 | @identity +112 | @identity +113 | @identity +114 | @identity +115 | @identity +116 | @identity +117 | @identity +118 | @identity +119 | @identity +120 | @identity +121 | @identity +122 | def bar(self): ... +123 | +124 | class Baz(Foo): +125 | def bar(self): ... # error: [override-of-final-method] ``` # Diagnostics ``` error[override-of-final-method]: Cannot override `Parent.foo` - --> src/mdtest_snippet.pyi:46:9 + --> src/mdtest_snippet.pyi:50:9 | -44 | # -45 | # error: [override-of-final-method] "Cannot override final member `foo` from superclass `Parent`" -46 | def foo(self): ... +48 | # +49 | # error: [override-of-final-method] "Cannot override final member `foo` from superclass `Parent`" +50 | def foo(self): ... | ^^^ Overrides a definition from superclass `Parent` -47 | @property -48 | def my_property1(self) -> int: ... # error: [override-of-final-method] +51 | @property +52 | def my_property1(self) -> int: ... # error: [override-of-final-method] | info: `Parent.foo` is decorated with `@final`, forbidding overrides --> src/mdtest_snippet.pyi:6:5 @@ -154,28 +165,28 @@ info: `Parent.foo` is decorated with `@final`, forbidding overrides | help: Remove the override of `foo` info: rule `override-of-final-method` is enabled by default -43 | # which is different to the verbose diagnostic summary message: -44 | # -45 | # error: [override-of-final-method] "Cannot override final member `foo` from superclass `Parent`" +47 | # which is different to the verbose diagnostic summary message: +48 | # +49 | # error: [override-of-final-method] "Cannot override final member `foo` from superclass `Parent`" - def foo(self): ... -46 + -47 | @property -48 | def my_property1(self) -> int: ... # error: [override-of-final-method] -49 | +50 + +51 | @property +52 | def my_property1(self) -> int: ... # error: [override-of-final-method] +53 | note: This is an unsafe fix and may change runtime behavior ``` ``` error[override-of-final-method]: Cannot override `Parent.my_property1` - --> src/mdtest_snippet.pyi:48:9 + --> src/mdtest_snippet.pyi:52:9 | -46 | def foo(self): ... -47 | @property -48 | def my_property1(self) -> int: ... # error: [override-of-final-method] +50 | def foo(self): ... +51 | @property +52 | def my_property1(self) -> int: ... # error: [override-of-final-method] | ^^^^^^^^^^^^ Overrides a definition from superclass `Parent` -49 | -50 | @property +53 | +54 | @property | info: `Parent.my_property1` is decorated with `@final`, forbidding overrides --> src/mdtest_snippet.pyi:9:5 @@ -192,28 +203,18 @@ info: `Parent.my_property1` is decorated with `@final`, forbidding overrides | help: Remove the override of `my_property1` info: rule `override-of-final-method` is enabled by default -44 | # -45 | # error: [override-of-final-method] "Cannot override final member `foo` from superclass `Parent`" -46 | def foo(self): ... - - @property - - def my_property1(self) -> int: ... # error: [override-of-final-method] -47 + # error: [override-of-final-method] -48 | -49 | @property -50 | def my_property2(self) -> int: ... # error: [override-of-final-method] -note: This is an unsafe fix and may change runtime behavior ``` ``` error[override-of-final-method]: Cannot override `Parent.my_property2` - --> src/mdtest_snippet.pyi:51:9 + --> src/mdtest_snippet.pyi:55:9 | -50 | @property -51 | def my_property2(self) -> int: ... # error: [override-of-final-method] +54 | @property +55 | def my_property2(self) -> int: ... # error: [override-of-final-method] | ^^^^^^^^^^^^ Overrides a definition from superclass `Parent` -52 | -53 | @classmethod +56 | @my_property2.setter +57 | def my_property2(self, x: int) -> None: ... | info: `Parent.my_property2` is decorated with `@final`, forbidding overrides --> src/mdtest_snippet.pyi:14:5 @@ -224,181 +225,197 @@ info: `Parent.my_property2` is decorated with `@final`, forbidding overrides 15 | def my_property2(self) -> int: ... | ------------ `Parent.my_property2` defined here 16 | -17 | @final +17 | @property | -help: Remove the override of `my_property2` +help: Remove the getter and setter for `my_property2` +info: rule `override-of-final-method` is enabled by default + +``` + +``` +error[override-of-final-method]: Cannot override `Parent.my_property3` + --> src/mdtest_snippet.pyi:60:9 + | +59 | @property +60 | def my_property3(self) -> int: ... # error: [override-of-final-method] + | ^^^^^^^^^^^^ Overrides a definition from superclass `Parent` +61 | @my_property3.deleter +62 | def my_proeprty3(self) -> None: ... + | +info: `Parent.my_property3` is decorated with `@final`, forbidding overrides + --> src/mdtest_snippet.pyi:18:5 + | +17 | @property +18 | @final + | ------ +19 | def my_property3(self) -> int: ... + | ------------ `Parent.my_property3` defined here +20 | +21 | @final + | +help: Remove the override of `my_property3` info: rule `override-of-final-method` is enabled by default -47 | @property -48 | def my_property1(self) -> int: ... # error: [override-of-final-method] -49 | - - @property - - def my_property2(self) -> int: ... # error: [override-of-final-method] -50 + # error: [override-of-final-method] -51 | -52 | @classmethod -53 | def class_method1(cls) -> int: ... # error: [override-of-final-method] -note: This is an unsafe fix and may change runtime behavior ``` ``` error[override-of-final-method]: Cannot override `Parent.class_method1` - --> src/mdtest_snippet.pyi:54:9 + --> src/mdtest_snippet.pyi:65:9 | -53 | @classmethod -54 | def class_method1(cls) -> int: ... # error: [override-of-final-method] +64 | @classmethod +65 | def class_method1(cls) -> int: ... # error: [override-of-final-method] | ^^^^^^^^^^^^^ Overrides a definition from superclass `Parent` -55 | -56 | @staticmethod +66 | +67 | @staticmethod | info: `Parent.class_method1` is decorated with `@final`, forbidding overrides - --> src/mdtest_snippet.pyi:17:5 + --> src/mdtest_snippet.pyi:21:5 | -15 | def my_property2(self) -> int: ... -16 | -17 | @final - | ------ -18 | @classmethod -19 | def class_method1(cls) -> int: ... - | ------------- `Parent.class_method1` defined here +19 | def my_property3(self) -> int: ... 20 | -21 | @classmethod +21 | @final + | ------ +22 | @classmethod +23 | def class_method1(cls) -> int: ... + | ------------- `Parent.class_method1` defined here +24 | +25 | @classmethod | help: Remove the override of `class_method1` info: rule `override-of-final-method` is enabled by default -50 | @property -51 | def my_property2(self) -> int: ... # error: [override-of-final-method] -52 | +61 | @my_property3.deleter +62 | def my_proeprty3(self) -> None: ... +63 | - @classmethod - def class_method1(cls) -> int: ... # error: [override-of-final-method] -53 + # error: [override-of-final-method] -54 | -55 | @staticmethod -56 | def static_method1() -> int: ... # error: [override-of-final-method] +64 + # error: [override-of-final-method] +65 | +66 | @staticmethod +67 | def static_method1() -> int: ... # error: [override-of-final-method] note: This is an unsafe fix and may change runtime behavior ``` ``` error[override-of-final-method]: Cannot override `Parent.static_method1` - --> src/mdtest_snippet.pyi:57:9 + --> src/mdtest_snippet.pyi:68:9 | -56 | @staticmethod -57 | def static_method1() -> int: ... # error: [override-of-final-method] +67 | @staticmethod +68 | def static_method1() -> int: ... # error: [override-of-final-method] | ^^^^^^^^^^^^^^ Overrides a definition from superclass `Parent` -58 | -59 | @classmethod +69 | +70 | @classmethod | info: `Parent.static_method1` is decorated with `@final`, forbidding overrides - --> src/mdtest_snippet.pyi:25:5 + --> src/mdtest_snippet.pyi:29:5 | -23 | def class_method2(cls) -> int: ... -24 | -25 | @final - | ------ -26 | @staticmethod -27 | def static_method1() -> int: ... - | -------------- `Parent.static_method1` defined here +27 | def class_method2(cls) -> int: ... 28 | -29 | @staticmethod +29 | @final + | ------ +30 | @staticmethod +31 | def static_method1() -> int: ... + | -------------- `Parent.static_method1` defined here +32 | +33 | @staticmethod | help: Remove the override of `static_method1` info: rule `override-of-final-method` is enabled by default -53 | @classmethod -54 | def class_method1(cls) -> int: ... # error: [override-of-final-method] -55 | +64 | @classmethod +65 | def class_method1(cls) -> int: ... # error: [override-of-final-method] +66 | - @staticmethod - def static_method1() -> int: ... # error: [override-of-final-method] -56 + # error: [override-of-final-method] -57 | -58 | @classmethod -59 | def class_method2(cls) -> int: ... # error: [override-of-final-method] +67 + # error: [override-of-final-method] +68 | +69 | @classmethod +70 | def class_method2(cls) -> int: ... # error: [override-of-final-method] note: This is an unsafe fix and may change runtime behavior ``` ``` error[override-of-final-method]: Cannot override `Parent.class_method2` - --> src/mdtest_snippet.pyi:60:9 + --> src/mdtest_snippet.pyi:71:9 | -59 | @classmethod -60 | def class_method2(cls) -> int: ... # error: [override-of-final-method] +70 | @classmethod +71 | def class_method2(cls) -> int: ... # error: [override-of-final-method] | ^^^^^^^^^^^^^ Overrides a definition from superclass `Parent` -61 | -62 | @staticmethod +72 | +73 | @staticmethod | info: `Parent.class_method2` is decorated with `@final`, forbidding overrides - --> src/mdtest_snippet.pyi:22:5 + --> src/mdtest_snippet.pyi:26:5 | -21 | @classmethod -22 | @final +25 | @classmethod +26 | @final | ------ -23 | def class_method2(cls) -> int: ... +27 | def class_method2(cls) -> int: ... | ------------- `Parent.class_method2` defined here -24 | -25 | @final +28 | +29 | @final | help: Remove the override of `class_method2` info: rule `override-of-final-method` is enabled by default -56 | @staticmethod -57 | def static_method1() -> int: ... # error: [override-of-final-method] -58 | +67 | @staticmethod +68 | def static_method1() -> int: ... # error: [override-of-final-method] +69 | - @classmethod - def class_method2(cls) -> int: ... # error: [override-of-final-method] -59 + # error: [override-of-final-method] -60 | -61 | @staticmethod -62 | def static_method2() -> int: ... # error: [override-of-final-method] +70 + # error: [override-of-final-method] +71 | +72 | @staticmethod +73 | def static_method2() -> int: ... # error: [override-of-final-method] note: This is an unsafe fix and may change runtime behavior ``` ``` error[override-of-final-method]: Cannot override `Parent.static_method2` - --> src/mdtest_snippet.pyi:63:9 + --> src/mdtest_snippet.pyi:74:9 | -62 | @staticmethod -63 | def static_method2() -> int: ... # error: [override-of-final-method] +73 | @staticmethod +74 | def static_method2() -> int: ... # error: [override-of-final-method] | ^^^^^^^^^^^^^^ Overrides a definition from superclass `Parent` -64 | -65 | def decorated_1(self): ... # TODO: should emit [override-of-final-method] +75 | +76 | def decorated_1(self): ... # TODO: should emit [override-of-final-method] | info: `Parent.static_method2` is decorated with `@final`, forbidding overrides - --> src/mdtest_snippet.pyi:30:5 + --> src/mdtest_snippet.pyi:34:5 | -29 | @staticmethod -30 | @final +33 | @staticmethod +34 | @final | ------ -31 | def static_method2() -> int: ... +35 | def static_method2() -> int: ... | -------------- `Parent.static_method2` defined here -32 | -33 | @lossy_decorator +36 | +37 | @lossy_decorator | help: Remove the override of `static_method2` info: rule `override-of-final-method` is enabled by default -59 | @classmethod -60 | def class_method2(cls) -> int: ... # error: [override-of-final-method] -61 | +70 | @classmethod +71 | def class_method2(cls) -> int: ... # error: [override-of-final-method] +72 | - @staticmethod - def static_method2() -> int: ... # error: [override-of-final-method] -62 + # error: [override-of-final-method] -63 | -64 | def decorated_1(self): ... # TODO: should emit [override-of-final-method] -65 | +73 + # error: [override-of-final-method] +74 | +75 | def decorated_1(self): ... # TODO: should emit [override-of-final-method] +76 | note: This is an unsafe fix and may change runtime behavior ``` ``` error[override-of-final-method]: Cannot override `Parent.foo` - --> src/mdtest_snippet.pyi:76:9 + --> src/mdtest_snippet.pyi:87:9 | -74 | # TODO: we should emit a Liskov violation here too -75 | # error: [override-of-final-method] -76 | def foo(): ... +85 | # TODO: we should emit a Liskov violation here too +86 | # error: [override-of-final-method] +87 | def foo(): ... | ^^^ Overrides a definition from superclass `Parent` -77 | @property -78 | # TODO: we should emit a Liskov violation here too +88 | @property +89 | # TODO: we should emit a Liskov violation here too | info: `Parent.foo` is decorated with `@final`, forbidding overrides --> src/mdtest_snippet.pyi:6:5 @@ -413,31 +430,31 @@ info: `Parent.foo` is decorated with `@final`, forbidding overrides | help: Remove the override of `foo` info: rule `override-of-final-method` is enabled by default -70 | class OtherChild(Parent): ... -71 | -72 | class Grandchild(OtherChild): +81 | class OtherChild(Parent): ... +82 | +83 | class Grandchild(OtherChild): - @staticmethod - # TODO: we should emit a Liskov violation here too - # error: [override-of-final-method] - def foo(): ... -73 + -74 | @property -75 | # TODO: we should emit a Liskov violation here too -76 | # error: [override-of-final-method] +84 + +85 | @property +86 | # TODO: we should emit a Liskov violation here too +87 | # error: [override-of-final-method] note: This is an unsafe fix and may change runtime behavior ``` ``` error[override-of-final-method]: Cannot override `Parent.my_property1` - --> src/mdtest_snippet.pyi:80:9 + --> src/mdtest_snippet.pyi:91:9 | -78 | # TODO: we should emit a Liskov violation here too -79 | # error: [override-of-final-method] -80 | def my_property1(self) -> str: ... +89 | # TODO: we should emit a Liskov violation here too +90 | # error: [override-of-final-method] +91 | def my_property1(self) -> str: ... | ^^^^^^^^^^^^ Overrides a definition from superclass `Parent` -81 | # TODO: we should emit a Liskov violation here too -82 | # error: [override-of-final-method] +92 | # TODO: we should emit a Liskov violation here too +93 | # error: [override-of-final-method] | info: `Parent.my_property1` is decorated with `@final`, forbidding overrides --> src/mdtest_snippet.pyi:9:5 @@ -454,92 +471,71 @@ info: `Parent.my_property1` is decorated with `@final`, forbidding overrides | help: Remove the override of `my_property1` info: rule `override-of-final-method` is enabled by default -74 | # TODO: we should emit a Liskov violation here too -75 | # error: [override-of-final-method] -76 | def foo(): ... - - @property - - # TODO: we should emit a Liskov violation here too - - # error: [override-of-final-method] - - def my_property1(self) -> str: ... -77 + -78 | # TODO: we should emit a Liskov violation here too -79 | # error: [override-of-final-method] -80 | class_method1 = None -note: This is an unsafe fix and may change runtime behavior ``` ``` error[override-of-final-method]: Cannot override `Parent.class_method1` - --> src/mdtest_snippet.pyi:83:5 + --> src/mdtest_snippet.pyi:94:5 | -81 | # TODO: we should emit a Liskov violation here too -82 | # error: [override-of-final-method] -83 | class_method1 = None +92 | # TODO: we should emit a Liskov violation here too +93 | # error: [override-of-final-method] +94 | class_method1 = None | ^^^^^^^^^^^^^ Overrides a definition from superclass `Parent` -84 | -85 | # Diagnostic edge case: `final` is very far away from the method definition in the source code: +95 | +96 | # Diagnostic edge case: `final` is very far away from the method definition in the source code: | info: `Parent.class_method1` is decorated with `@final`, forbidding overrides - --> src/mdtest_snippet.pyi:17:5 + --> src/mdtest_snippet.pyi:21:5 | -15 | def my_property2(self) -> int: ... -16 | -17 | @final - | ------ -18 | @classmethod -19 | def class_method1(cls) -> int: ... - | ------------- `Parent.class_method1` defined here +19 | def my_property3(self) -> int: ... 20 | -21 | @classmethod +21 | @final + | ------ +22 | @classmethod +23 | def class_method1(cls) -> int: ... + | ------------- `Parent.class_method1` defined here +24 | +25 | @classmethod | help: Remove the override of `class_method1` info: rule `override-of-final-method` is enabled by default -80 | def my_property1(self) -> str: ... -81 | # TODO: we should emit a Liskov violation here too -82 | # error: [override-of-final-method] - - class_method1 = None -83 + -84 | -85 | # Diagnostic edge case: `final` is very far away from the method definition in the source code: -86 | -note: This is an unsafe fix and may change runtime behavior ``` ``` error[override-of-final-method]: Cannot override `Foo.bar` - --> src/mdtest_snippet.pyi:114:9 + --> src/mdtest_snippet.pyi:125:9 | -113 | class Baz(Foo): -114 | def bar(self): ... # error: [override-of-final-method] +124 | class Baz(Foo): +125 | def bar(self): ... # error: [override-of-final-method] | ^^^ Overrides a definition from superclass `Foo` | info: `Foo.bar` is decorated with `@final`, forbidding overrides - --> src/mdtest_snippet.pyi:92:5 + --> src/mdtest_snippet.pyi:103:5 | - 91 | class Foo: - 92 | @final +102 | class Foo: +103 | @final | ------ - 93 | @identity - 94 | @identity +104 | @identity +105 | @identity | - ::: src/mdtest_snippet.pyi:111:9 + ::: src/mdtest_snippet.pyi:122:9 | -109 | @identity -110 | @identity -111 | def bar(self): ... +120 | @identity +121 | @identity +122 | def bar(self): ... | --- `Foo.bar` defined here -112 | -113 | class Baz(Foo): +123 | +124 | class Baz(Foo): | help: Remove the override of `bar` info: rule `override-of-final-method` is enabled by default -111 | def bar(self): ... -112 | -113 | class Baz(Foo): +122 | def bar(self): ... +123 | +124 | class Baz(Foo): - def bar(self): ... # error: [override-of-final-method] -114 + # error: [override-of-final-method] +125 + pass # error: [override-of-final-method] note: This is an unsafe fix and may change runtime behavior ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/final.md_-_Tests_for_the_`@typi…_-_Diagnostic_edge_case…_(2389d52c5ecfa2bd).snap b/crates/ty_python_semantic/resources/mdtest/snapshots/final.md_-_Tests_for_the_`@typi…_-_Diagnostic_edge_case…_(2389d52c5ecfa2bd).snap index 5e7f11ca02..9282af1cea 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/final.md_-_Tests_for_the_`@typi…_-_Diagnostic_edge_case…_(2389d52c5ecfa2bd).snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/final.md_-_Tests_for_the_`@typi…_-_Diagnostic_edge_case…_(2389d52c5ecfa2bd).snap @@ -53,7 +53,7 @@ info: rule `override-of-final-method` is enabled by default 2 | 3 | class Foo(module1.Foo): - def f(self): ... # error: [override-of-final-method] -4 + # error: [override-of-final-method] +4 + pass # error: [override-of-final-method] note: This is an unsafe fix and may change runtime behavior ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/final.md_-_Tests_for_the_`@typi…_-_Only_the_first_`@fin…_(9863b583f4c651c5).snap b/crates/ty_python_semantic/resources/mdtest/snapshots/final.md_-_Tests_for_the_`@typi…_-_Only_the_first_`@fin…_(9863b583f4c651c5).snap index 342b7ec8b6..9f1c33a07d 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/final.md_-_Tests_for_the_`@typi…_-_Only_the_first_`@fin…_(9863b583f4c651c5).snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/final.md_-_Tests_for_the_`@typi…_-_Only_the_first_`@fin…_(9863b583f4c651c5).snap @@ -59,7 +59,7 @@ info: rule `override-of-final-method` is enabled by default 7 | class B(A): - @final - def f(self): ... # error: [override-of-final-method] -8 + # error: [override-of-final-method] +8 + pass # error: [override-of-final-method] 9 | 10 | class C(B): 11 | @final @@ -95,7 +95,7 @@ info: rule `override-of-final-method` is enabled by default - @final - # we only emit one error here, not two - def f(self): ... # error: [override-of-final-method] -12 + # error: [override-of-final-method] +12 + pass # error: [override-of-final-method] note: This is an unsafe fix and may change runtime behavior ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/final.md_-_Tests_for_the_`@typi…_-_Overloaded_methods_d…_(861757f48340ed92).snap b/crates/ty_python_semantic/resources/mdtest/snapshots/final.md_-_Tests_for_the_`@typi…_-_Overloaded_methods_d…_(861757f48340ed92).snap index 38ec75b003..f49f4ac3fc 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/final.md_-_Tests_for_the_`@typi…_-_Overloaded_methods_d…_(861757f48340ed92).snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/final.md_-_Tests_for_the_`@typi…_-_Overloaded_methods_d…_(861757f48340ed92).snap @@ -58,7 +58,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/final.md 44 | def bar(self, x: str) -> str: ... 45 | @overload 46 | def bar(self, x: int) -> int: ... # error: [override-of-final-method] -47 | +47 | 48 | @overload 49 | def baz(self, x: str) -> str: ... 50 | @overload @@ -265,7 +265,7 @@ error[override-of-final-method]: Cannot override `Bad.bar` 45 | @overload 46 | def bar(self, x: int) -> int: ... # error: [override-of-final-method] | ^^^ Overrides a definition from superclass `Bad` -47 | +47 | 48 | @overload | info: `Bad.bar` is decorated with `@final`, forbidding overrides @@ -287,12 +287,11 @@ info: rule `override-of-final-method` is enabled by default - def bar(self, x: str) -> str: ... - @overload - def bar(self, x: int) -> int: ... # error: [override-of-final-method] -43 | +43 + 44 + # error: [override-of-final-method] -45 + +45 | 46 | @overload 47 | def baz(self, x: str) -> str: ... -48 | @overload note: This is an unsafe fix and may change runtime behavior ``` @@ -319,7 +318,7 @@ help: Remove all overloads for `baz` info: rule `override-of-final-method` is enabled by default 45 | @overload 46 | def bar(self, x: int) -> int: ... # error: [override-of-final-method] -47 | +47 | - @overload - def baz(self, x: str) -> str: ... - @overload @@ -360,12 +359,12 @@ info: rule `override-of-final-method` is enabled by default - def f(self, x: str) -> str: ... - @overload - def f(self, x: int) -> int: ... -13 + -14 + +13 + pass +14 + pass 15 | # error: [override-of-final-method] - def f(self, x: int | str) -> int | str: - return x -16 + +16 + pass 17 | 18 | class Bad: 19 | @overload @@ -459,15 +458,6 @@ info: `Bad.f` is decorated with `@final`, forbidding overrides | help: Remove the override of `f` info: rule `override-of-final-method` is enabled by default -57 | -58 | class ChildOfBad(Bad): -59 | # TODO: these should all cause us to emit Liskov violations as well - - f = None # error: [override-of-final-method] -60 + # error: [override-of-final-method] -61 | g = None # error: [override-of-final-method] -62 | h = None # error: [override-of-final-method] -63 | i = None # error: [override-of-final-method] -note: This is an unsafe fix and may change runtime behavior ``` @@ -493,14 +483,6 @@ info: `Bad.g` is decorated with `@final`, forbidding overrides | help: Remove the override of `g` info: rule `override-of-final-method` is enabled by default -58 | class ChildOfBad(Bad): -59 | # TODO: these should all cause us to emit Liskov violations as well -60 | f = None # error: [override-of-final-method] - - g = None # error: [override-of-final-method] -61 + # error: [override-of-final-method] -62 | h = None # error: [override-of-final-method] -63 | i = None # error: [override-of-final-method] -note: This is an unsafe fix and may change runtime behavior ``` @@ -525,13 +507,6 @@ info: `Bad.h` is decorated with `@final`, forbidding overrides | help: Remove the override of `h` info: rule `override-of-final-method` is enabled by default -59 | # TODO: these should all cause us to emit Liskov violations as well -60 | f = None # error: [override-of-final-method] -61 | g = None # error: [override-of-final-method] - - h = None # error: [override-of-final-method] -62 + # error: [override-of-final-method] -63 | i = None # error: [override-of-final-method] -note: This is an unsafe fix and may change runtime behavior ``` @@ -555,11 +530,5 @@ info: `Bad.i` is decorated with `@final`, forbidding overrides | help: Remove the override of `i` info: rule `override-of-final-method` is enabled by default -60 | f = None # error: [override-of-final-method] -61 | g = None # error: [override-of-final-method] -62 | h = None # error: [override-of-final-method] - - i = None # error: [override-of-final-method] -63 + # error: [override-of-final-method] -note: This is an unsafe fix and may change runtime behavior ``` diff --git a/crates/ty_python_semantic/src/types/class.rs b/crates/ty_python_semantic/src/types/class.rs index a88cfd4fe4..1dd3f7995f 100644 --- a/crates/ty_python_semantic/src/types/class.rs +++ b/crates/ty_python_semantic/src/types/class.rs @@ -19,7 +19,7 @@ use crate::semantic_index::{ use crate::types::bound_super::BoundSuperError; use crate::types::constraints::{ConstraintSet, IteratorConstraintsExtension}; use crate::types::context::InferContext; -use crate::types::diagnostic::INVALID_TYPE_ALIAS_TYPE; +use crate::types::diagnostic::{INVALID_TYPE_ALIAS_TYPE, SUPER_CALL_IN_NAMED_TUPLE_METHOD}; use crate::types::enums::enum_metadata; use crate::types::function::{DataclassTransformerParams, KnownFunction}; use crate::types::generics::{ @@ -5584,6 +5584,20 @@ impl KnownClass { return; }; + // Check if the enclosing class is a `NamedTuple`, which forbids the use of `super()`. + if CodeGeneratorKind::NamedTuple.matches(db, enclosing_class, None) { + if let Some(builder) = context + .report_lint(&SUPER_CALL_IN_NAMED_TUPLE_METHOD, call_expression) + { + builder.into_diagnostic(format_args!( + "Cannot use `super()` in a method of NamedTuple class `{}`", + enclosing_class.name(db) + )); + } + overload.set_return_type(Type::unknown()); + return; + } + // The type of the first parameter if the given scope is function-like (i.e. function or lambda). // `None` if the scope is not function-like, or has no parameters. let first_param = match scope.node(db) { @@ -5623,6 +5637,22 @@ impl KnownClass { overload.set_return_type(bound_super); } [Some(pivot_class_type), Some(owner_type)] => { + // Check if the enclosing class is a `NamedTuple`, which forbids the use of `super()`. + if let Some(enclosing_class) = nearest_enclosing_class(db, index, scope) { + if CodeGeneratorKind::NamedTuple.matches(db, enclosing_class, None) { + if let Some(builder) = context + .report_lint(&SUPER_CALL_IN_NAMED_TUPLE_METHOD, call_expression) + { + builder.into_diagnostic(format_args!( + "Cannot use `super()` in a method of NamedTuple class `{}`", + enclosing_class.name(db) + )); + } + overload.set_return_type(Type::unknown()); + return; + } + } + let bound_super = BoundSuperType::build(db, *pivot_class_type, *owner_type) .unwrap_or_else(|err| { err.report_diagnostic(context, call_expression.into()); diff --git a/crates/ty_python_semantic/src/types/diagnostic.rs b/crates/ty_python_semantic/src/types/diagnostic.rs index 782a25b98e..8e92c495cf 100644 --- a/crates/ty_python_semantic/src/types/diagnostic.rs +++ b/crates/ty_python_semantic/src/types/diagnostic.rs @@ -121,6 +121,7 @@ pub(crate) fn register_lints(registry: &mut LintRegistryBuilder) { registry.register_lint(&MISSING_TYPED_DICT_KEY); registry.register_lint(&INVALID_METHOD_OVERRIDE); registry.register_lint(&INVALID_EXPLICIT_OVERRIDE); + registry.register_lint(&SUPER_CALL_IN_NAMED_TUPLE_METHOD); // String annotations registry.register_lint(&BYTE_STRING_TYPE_ANNOTATION); @@ -1760,6 +1761,33 @@ declare_lint! { } } +declare_lint! { + /// ## What it does + /// Checks for calls to `super()` inside methods of `NamedTuple` classes. + /// + /// ## Why is this bad? + /// Using `super()` in a method of a `NamedTuple` class will raise an exception at runtime. + /// + /// ## Examples + /// ```python + /// from typing import NamedTuple + /// + /// class F(NamedTuple): + /// x: int + /// + /// def method(self): + /// super() # error: super() is not supported in methods of NamedTuple classes + /// ``` + /// + /// ## References + /// - [Python documentation: super()](https://docs.python.org/3/library/functions.html#super) + pub(crate) static SUPER_CALL_IN_NAMED_TUPLE_METHOD = { + summary: "detects `super()` calls in methods of `NamedTuple` classes", + status: LintStatus::preview("0.0.1-alpha.30"), + default_level: Level::Error, + } +} + declare_lint! { /// ## What it does /// Checks for calls to `reveal_type` without importing it. @@ -3804,6 +3832,7 @@ pub(super) fn report_overridden_final_method<'db>( context: &InferContext<'db, '_>, member: &str, subclass_definition: Definition<'db>, + // N.B. the type of the *definition*, not the type on an instance of the subclass subclass_type: Type<'db>, superclass: ClassType<'db>, subclass: ClassType<'db>, @@ -3811,6 +3840,23 @@ pub(super) fn report_overridden_final_method<'db>( ) { let db = context.db(); + // Some hijinks so that we emit a diagnostic on the property getter rather than the property setter + let property_getter_definition = if subclass_definition.kind(db).is_function_def() + && let Type::PropertyInstance(property) = subclass_type + && let Some(Type::FunctionLiteral(getter)) = property.getter(db) + { + let getter_definition = getter.definition(db); + if getter_definition.scope(db) == subclass_definition.scope(db) { + Some(getter_definition) + } else { + None + } + } else { + None + }; + + let subclass_definition = property_getter_definition.unwrap_or(subclass_definition); + let Some(builder) = context.report_lint( &OVERRIDE_OF_FINAL_METHOD, subclass_definition.focus_range(db, context.module()), @@ -3871,37 +3917,69 @@ pub(super) fn report_overridden_final_method<'db>( diagnostic.sub(sub); - let underlying_function = match subclass_type { - Type::FunctionLiteral(function) => Some(function), - Type::BoundMethod(method) => Some(method.function(db)), - _ => None, - }; + // It's tempting to autofix properties as well, + // but you'd want to delete the `@my_property.deleter` as well as the getter and the deleter, + // and we don't model property deleters at all right now. + if let Type::FunctionLiteral(function) = subclass_type { + let class_node = subclass + .class_literal(db) + .0 + .body_scope(db) + .node(db) + .expect_class() + .node(context.module()); + + let (overloads, implementation) = function.overloads_and_implementation(db); + let overload_count = overloads.len() + usize::from(implementation.is_some()); + let is_only = overload_count >= class_node.body.len(); - if let Some(function) = underlying_function { let overload_deletion = |overload: &OverloadLiteral<'db>| { - Edit::range_deletion(overload.node(db, context.file(), context.module()).range()) + let range = overload.node(db, context.file(), context.module()).range(); + if is_only { + Edit::range_replacement("pass".to_string(), range) + } else { + Edit::range_deletion(range) + } }; + let should_fix = overloads + .iter() + .copied() + .chain(implementation) + .all(|overload| { + class_node + .body + .iter() + .filter_map(ast::Stmt::as_function_def_stmt) + .contains(overload.node(db, context.file(), context.module())) + }); + match function.overloads_and_implementation(db) { ([first_overload, rest @ ..], None) => { diagnostic.help(format_args!("Remove all overloads for `{member}`")); - diagnostic.set_fix(Fix::unsafe_edits( - overload_deletion(first_overload), - rest.iter().map(overload_deletion), - )); + diagnostic.set_optional_fix(should_fix.then(|| { + Fix::unsafe_edits( + overload_deletion(first_overload), + rest.iter().map(overload_deletion), + ) + })); } ([first_overload, rest @ ..], Some(implementation)) => { diagnostic.help(format_args!( "Remove all overloads and the implementation for `{member}`" )); - diagnostic.set_fix(Fix::unsafe_edits( - overload_deletion(first_overload), - rest.iter().chain([&implementation]).map(overload_deletion), - )); + diagnostic.set_optional_fix(should_fix.then(|| { + Fix::unsafe_edits( + overload_deletion(first_overload), + rest.iter().chain([&implementation]).map(overload_deletion), + ) + })); } ([], Some(implementation)) => { diagnostic.help(format_args!("Remove the override of `{member}`")); - diagnostic.set_fix(Fix::unsafe_edit(overload_deletion(&implementation))); + diagnostic.set_optional_fix( + should_fix.then(|| Fix::unsafe_edit(overload_deletion(&implementation))), + ); } ([], None) => { // Should be impossible to get here: how would we even infer a function as a function @@ -3911,11 +3989,12 @@ pub(super) fn report_overridden_final_method<'db>( ); } } + } else if let Type::PropertyInstance(property) = subclass_type + && property.setter(db).is_some() + { + diagnostic.help(format_args!("Remove the getter and setter for `{member}`")); } else { diagnostic.help(format_args!("Remove the override of `{member}`")); - diagnostic.set_fix(Fix::unsafe_edit(Edit::range_deletion( - subclass_definition.full_range(db, context.module()).range(), - ))); } } diff --git a/crates/ty_python_semantic/src/types/overrides.rs b/crates/ty_python_semantic/src/types/overrides.rs index 0cfc8c477b..517878202a 100644 --- a/crates/ty_python_semantic/src/types/overrides.rs +++ b/crates/ty_python_semantic/src/types/overrides.rs @@ -327,7 +327,7 @@ fn check_class_declaration<'db>( context, &member.name, *definition, - type_on_subclass_instance, + member.ty, superclass, class, &superclass_method, diff --git a/crates/ty_server/tests/e2e/snapshots/e2e__commands__debug_command.snap b/crates/ty_server/tests/e2e/snapshots/e2e__commands__debug_command.snap index 5505fbd5f2..0daa6c768a 100644 --- a/crates/ty_server/tests/e2e/snapshots/e2e__commands__debug_command.snap +++ b/crates/ty_server/tests/e2e/snapshots/e2e__commands__debug_command.snap @@ -93,6 +93,7 @@ Settings: Settings { "redundant-cast": Warning (Default), "static-assert-error": Error (Default), "subclass-of-final-class": Error (Default), + "super-call-in-named-tuple-method": Error (Default), "too-many-positional-arguments": Error (Default), "type-assertion-failure": Error (Default), "unavailable-implicit-super-arguments": Error (Default), diff --git a/docs/integrations.md b/docs/integrations.md index fc364ae8ca..65553c6bdf 100644 --- a/docs/integrations.md +++ b/docs/integrations.md @@ -88,7 +88,7 @@ You can add the following configuration to `.gitlab-ci.yml` to run a `ruff forma Ruff Check: extends: .base_ruff script: - - ruff check --output-format=gitlab > code-quality-report.json + - ruff check --output-format=gitlab --output-file=code-quality-report.json artifacts: reports: codequality: $CI_PROJECT_DIR/code-quality-report.json diff --git a/docs/requirements-insiders.txt b/docs/requirements-insiders.txt index 6d3586a58a..9726b8ab5a 100644 --- a/docs/requirements-insiders.txt +++ b/docs/requirements-insiders.txt @@ -1,5 +1,5 @@ PyYAML==6.0.3 -ruff==0.14.6 +ruff==0.14.7 mkdocs==1.6.1 mkdocs-material @ git+ssh://git@github.com/astral-sh/mkdocs-material-insiders.git@39da7a5e761410349e9a1b8abf593b0cdd5453ff mkdocs-redirects==1.2.2 diff --git a/docs/requirements.txt b/docs/requirements.txt index 73c6e31906..a9b415267f 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,5 +1,5 @@ PyYAML==6.0.3 -ruff==0.14.6 +ruff==0.14.7 mkdocs==1.6.1 mkdocs-material==9.5.38 mkdocs-redirects==1.2.2 diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index fe9a85f065..adbdcd7545 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -30,7 +30,7 @@ ty_python_semantic = { path = "../crates/ty_python_semantic" } ty_vendored = { path = "../crates/ty_vendored" } libfuzzer-sys = { git = "https://github.com/rust-fuzz/libfuzzer", default-features = false } -salsa = { git = "https://github.com/salsa-rs/salsa.git", rev = "17bc55d699565e5a1cb1bd42363b905af2f9f3e7", default-features = false, features = [ +salsa = { git = "https://github.com/salsa-rs/salsa.git", rev = "59aa1075e837f5deb0d6ffb24b68fedc0f4bc5e0", default-features = false, features = [ "compact_str", "macros", "salsa_unstable", diff --git a/ty.schema.json b/ty.schema.json index 38d5fd1326..e2325d6ac4 100644 --- a/ty.schema.json +++ b/ty.schema.json @@ -973,6 +973,16 @@ } ] }, + "super-call-in-named-tuple-method": { + "title": "detects `super()` calls in methods of `NamedTuple` classes", + "description": "## What it does\nChecks for calls to `super()` inside methods of `NamedTuple` classes.\n\n## Why is this bad?\nUsing `super()` in a method of a `NamedTuple` class will raise an exception at runtime.\n\n## Examples\n```python\nfrom typing import NamedTuple\n\nclass F(NamedTuple):\n x: int\n\n def method(self):\n super() # error: super() is not supported in methods of NamedTuple classes\n```\n\n## References\n- [Python documentation: super()](https://docs.python.org/3/library/functions.html#super)", + "default": "error", + "oneOf": [ + { + "$ref": "#/definitions/Level" + } + ] + }, "too-many-positional-arguments": { "title": "detects calls passing too many positional arguments", "description": "## What it does\nChecks for calls that pass more positional arguments than the callable can accept.\n\n## Why is this bad?\nPassing too many positional arguments will raise `TypeError` at runtime.\n\n## Example\n\n```python\ndef f(): ...\n\nf(\"foo\") # Error raised here\n```",