Compare commits
171 Commits
david/mypy
...
0.11.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4773878ee7 | ||
|
|
2a4d835132 | ||
|
|
04a8756379 | ||
|
|
193c38199e | ||
|
|
63e78b41cd | ||
|
|
296d67a496 | ||
|
|
42cbce538b | ||
|
|
67602512b6 | ||
|
|
23382f5f8c | ||
|
|
c1971fdde2 | ||
|
|
cdafd8e32b | ||
|
|
12725943cd | ||
|
|
9d72685f8d | ||
|
|
47c4ccff5d | ||
|
|
74f64d3f96 | ||
|
|
999fd4f885 | ||
|
|
433a342656 | ||
|
|
4ed93b4311 | ||
|
|
98fdc0ebae | ||
|
|
861931795c | ||
|
|
a3f3d734a1 | ||
|
|
3a5f1d46c0 | ||
|
|
f3f3e55d97 | ||
|
|
22de00de16 | ||
|
|
f2a9960fb3 | ||
|
|
fd341bb1b2 | ||
|
|
15a6aeb998 | ||
|
|
81759be14b | ||
|
|
a69f6240cc | ||
|
|
c3d429ddd8 | ||
|
|
ab3ec4de6a | ||
|
|
a9f5dddbaa | ||
|
|
cc3ddaf070 | ||
|
|
c027979851 | ||
|
|
dcf31c9348 | ||
|
|
4ab529803f | ||
|
|
23b7df9b29 | ||
|
|
3e2cf5d7c4 | ||
|
|
c9cd0acaeb | ||
|
|
ded9c69888 | ||
|
|
433879d852 | ||
|
|
b7d232cf89 | ||
|
|
4d3a5afea5 | ||
|
|
90a8d92b2f | ||
|
|
c8bd5eeb56 | ||
|
|
bd9eab059f | ||
|
|
6883c1dde7 | ||
|
|
9291074ba6 | ||
|
|
602a27c4e3 | ||
|
|
ff548b1272 | ||
|
|
7512a71bbb | ||
|
|
7ca5f132ca | ||
|
|
743f85f1a4 | ||
|
|
b2e0ae6416 | ||
|
|
23ccb52fa6 | ||
|
|
3ccc8dbbf9 | ||
|
|
75a562d313 | ||
|
|
8d3643f409 | ||
|
|
50b66dc025 | ||
|
|
24707777af | ||
|
|
93ca4a96e0 | ||
|
|
38bfda94ce | ||
|
|
4da6936ec4 | ||
|
|
238ec39c56 | ||
|
|
b04103fa1d | ||
|
|
c100d519e9 | ||
|
|
dbdb46dcd2 | ||
|
|
2a6d43740c | ||
|
|
6f5a68608e | ||
|
|
c61d9c6bb7 | ||
|
|
43d371a1c9 | ||
|
|
5f80129112 | ||
|
|
db4ae242dc | ||
|
|
d3b5ef9f0b | ||
|
|
a7a20a3684 | ||
|
|
6fc953119f | ||
|
|
405fc7f5cf | ||
|
|
d5e045df47 | ||
|
|
adac1e6a61 | ||
|
|
3768f9cb52 | ||
|
|
01f3ef4e4f | ||
|
|
2de8455e43 | ||
|
|
1fab292ec1 | ||
|
|
c755eec91e | ||
|
|
ebcad6e641 | ||
|
|
fe275725e0 | ||
|
|
a467e7c8d3 | ||
|
|
a128ca761f | ||
|
|
3a32e56445 | ||
|
|
b9d7c36a23 | ||
|
|
ef9a825827 | ||
|
|
2bcd2b4147 | ||
|
|
eb6871d209 | ||
|
|
6311412373 | ||
|
|
4f2851982d | ||
|
|
2cd25ef641 | ||
|
|
a22d206db2 | ||
|
|
270318c2e0 | ||
|
|
d03b12e711 | ||
|
|
14c5ed5d7d | ||
|
|
595565015b | ||
|
|
2382fe1f25 | ||
|
|
27e9d1fe3e | ||
|
|
acf35c55f8 | ||
|
|
b9b256209b | ||
|
|
abaa18993b | ||
|
|
360ba095ff | ||
|
|
d8159e816f | ||
|
|
04ad562afd | ||
|
|
91674718c4 | ||
|
|
348815d6d6 | ||
|
|
1326d55c29 | ||
|
|
c19cd58670 | ||
|
|
8155197549 | ||
|
|
92193a3254 | ||
|
|
3d2f2a2f8d | ||
|
|
0aded52c40 | ||
|
|
7af5b98606 | ||
|
|
b9ed3e3876 | ||
|
|
e740286bbd | ||
|
|
776a401703 | ||
|
|
5c3d555950 | ||
|
|
ba37c7cdba | ||
|
|
c605ce6fe2 | ||
|
|
16b0902e28 | ||
|
|
5bcc0c460b | ||
|
|
62e9c9e506 | ||
|
|
a6d4e5aae4 | ||
|
|
692b651776 | ||
|
|
2f2d72dc95 | ||
|
|
d15641faea | ||
|
|
9b1b3c1859 | ||
|
|
e4b46913d2 | ||
|
|
f48bc3aea1 | ||
|
|
64b248ccfc | ||
|
|
c236be4320 | ||
|
|
8bd140c99d | ||
|
|
a04347b7a3 | ||
|
|
958e1177ce | ||
|
|
fce5d892c1 | ||
|
|
66cae0a3ec | ||
|
|
bbcddf7e79 | ||
|
|
c387a51cad | ||
|
|
5285e3fcbc | ||
|
|
ed4152dec6 | ||
|
|
24ec94562c | ||
|
|
c0b1413ecd | ||
|
|
798fa47c2e | ||
|
|
a4b7c4ef70 | ||
|
|
df6c850a53 | ||
|
|
a90cf9d59c | ||
|
|
58d5fe982e | ||
|
|
08fa9b4a90 | ||
|
|
dd2313ab0f | ||
|
|
057e497d30 | ||
|
|
083df0cf84 | ||
|
|
a176c1ac80 | ||
|
|
a6572a57c4 | ||
|
|
b250304ad3 | ||
|
|
11b5cbcd2f | ||
|
|
3228545598 | ||
|
|
860b95a318 | ||
|
|
6de2b2873b | ||
|
|
6b84253679 | ||
|
|
0340e23395 | ||
|
|
78b5f0b165 | ||
|
|
e17cd350b6 | ||
|
|
c16237ddc0 | ||
|
|
989075dc16 | ||
|
|
e8e24310fb | ||
|
|
3b497716f1 |
7
.github/CODEOWNERS
vendored
7
.github/CODEOWNERS
vendored
@@ -18,6 +18,7 @@
|
||||
/python/py-fuzzer/ @AlexWaygood
|
||||
|
||||
# red-knot
|
||||
/crates/red_knot* @carljm @MichaReiser @AlexWaygood @sharkdp
|
||||
/crates/ruff_db/ @carljm @MichaReiser @AlexWaygood @sharkdp
|
||||
/scripts/knot_benchmark/ @carljm @MichaReiser @AlexWaygood @sharkdp
|
||||
/crates/red_knot* @carljm @MichaReiser @AlexWaygood @sharkdp @dcreager
|
||||
/crates/ruff_db/ @carljm @MichaReiser @AlexWaygood @sharkdp @dcreager
|
||||
/scripts/knot_benchmark/ @carljm @MichaReiser @AlexWaygood @sharkdp @dcreager
|
||||
/crates/red_knot_python_semantic @carljm @AlexWaygood @sharkdp @dcreager
|
||||
|
||||
11
.github/renovate.json5
vendored
11
.github/renovate.json5
vendored
@@ -40,6 +40,17 @@
|
||||
enabled: true,
|
||||
},
|
||||
packageRules: [
|
||||
// Pin GitHub Actions to immutable SHAs.
|
||||
{
|
||||
matchDepTypes: ["action"],
|
||||
pinDigests: true,
|
||||
},
|
||||
// Annotate GitHub Actions SHAs with a SemVer version.
|
||||
{
|
||||
extends: ["helpers:pinGitHubActionDigests"],
|
||||
extractVersion: "^(?<version>v?\\d+\\.\\d+\\.\\d+)$",
|
||||
versioning: "regex:^v?(?<major>\\d+)(\\.(?<minor>\\d+)\\.(?<patch>\\d+))?$",
|
||||
},
|
||||
{
|
||||
// Group upload/download artifact updates, the versions are dependent
|
||||
groupName: "Artifact GitHub Actions dependencies",
|
||||
|
||||
84
.github/workflows/build-binaries.yml
vendored
84
.github/workflows/build-binaries.yml
vendored
@@ -39,17 +39,17 @@ jobs:
|
||||
if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-build') }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
with:
|
||||
submodules: recursive
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-python@v5
|
||||
- uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
- name: "Prep README.md"
|
||||
run: python scripts/transform_readme.py --target pypi
|
||||
- name: "Build sdist"
|
||||
uses: PyO3/maturin-action@v1
|
||||
uses: PyO3/maturin-action@36db84001d74475ad1b8e6613557ae4ee2dc3598 # v1
|
||||
with:
|
||||
command: sdist
|
||||
args: --out dist
|
||||
@@ -59,7 +59,7 @@ jobs:
|
||||
"${MODULE_NAME}" --help
|
||||
python -m "${MODULE_NAME}" --help
|
||||
- name: "Upload sdist"
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4
|
||||
with:
|
||||
name: wheels-sdist
|
||||
path: dist
|
||||
@@ -68,23 +68,23 @@ jobs:
|
||||
if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-build') }}
|
||||
runs-on: macos-14
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
with:
|
||||
submodules: recursive
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-python@v5
|
||||
- uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
architecture: x64
|
||||
- name: "Prep README.md"
|
||||
run: python scripts/transform_readme.py --target pypi
|
||||
- name: "Build wheels - x86_64"
|
||||
uses: PyO3/maturin-action@v1
|
||||
uses: PyO3/maturin-action@36db84001d74475ad1b8e6613557ae4ee2dc3598 # v1
|
||||
with:
|
||||
target: x86_64
|
||||
args: --release --locked --out dist
|
||||
- name: "Upload wheels"
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4
|
||||
with:
|
||||
name: wheels-macos-x86_64
|
||||
path: dist
|
||||
@@ -99,7 +99,7 @@ jobs:
|
||||
tar czvf $ARCHIVE_FILE $ARCHIVE_NAME
|
||||
shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
|
||||
- name: "Upload binary"
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4
|
||||
with:
|
||||
name: artifacts-macos-x86_64
|
||||
path: |
|
||||
@@ -110,18 +110,18 @@ jobs:
|
||||
if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-build') }}
|
||||
runs-on: macos-14
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
with:
|
||||
submodules: recursive
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-python@v5
|
||||
- uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
architecture: arm64
|
||||
- name: "Prep README.md"
|
||||
run: python scripts/transform_readme.py --target pypi
|
||||
- name: "Build wheels - aarch64"
|
||||
uses: PyO3/maturin-action@v1
|
||||
uses: PyO3/maturin-action@36db84001d74475ad1b8e6613557ae4ee2dc3598 # v1
|
||||
with:
|
||||
target: aarch64
|
||||
args: --release --locked --out dist
|
||||
@@ -131,7 +131,7 @@ jobs:
|
||||
ruff --help
|
||||
python -m ruff --help
|
||||
- name: "Upload wheels"
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4
|
||||
with:
|
||||
name: wheels-aarch64-apple-darwin
|
||||
path: dist
|
||||
@@ -146,7 +146,7 @@ jobs:
|
||||
tar czvf $ARCHIVE_FILE $ARCHIVE_NAME
|
||||
shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
|
||||
- name: "Upload binary"
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4
|
||||
with:
|
||||
name: artifacts-aarch64-apple-darwin
|
||||
path: |
|
||||
@@ -166,18 +166,18 @@ jobs:
|
||||
- target: aarch64-pc-windows-msvc
|
||||
arch: x64
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
with:
|
||||
submodules: recursive
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-python@v5
|
||||
- uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
architecture: ${{ matrix.platform.arch }}
|
||||
- name: "Prep README.md"
|
||||
run: python scripts/transform_readme.py --target pypi
|
||||
- name: "Build wheels"
|
||||
uses: PyO3/maturin-action@v1
|
||||
uses: PyO3/maturin-action@36db84001d74475ad1b8e6613557ae4ee2dc3598 # v1
|
||||
with:
|
||||
target: ${{ matrix.platform.target }}
|
||||
args: --release --locked --out dist
|
||||
@@ -192,7 +192,7 @@ jobs:
|
||||
"${MODULE_NAME}" --help
|
||||
python -m "${MODULE_NAME}" --help
|
||||
- name: "Upload wheels"
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4
|
||||
with:
|
||||
name: wheels-${{ matrix.platform.target }}
|
||||
path: dist
|
||||
@@ -203,7 +203,7 @@ jobs:
|
||||
7z a $ARCHIVE_FILE ./target/${{ matrix.platform.target }}/release/ruff.exe
|
||||
sha256sum $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
|
||||
- name: "Upload binary"
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4
|
||||
with:
|
||||
name: artifacts-${{ matrix.platform.target }}
|
||||
path: |
|
||||
@@ -219,18 +219,18 @@ jobs:
|
||||
- x86_64-unknown-linux-gnu
|
||||
- i686-unknown-linux-gnu
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
with:
|
||||
submodules: recursive
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-python@v5
|
||||
- uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
architecture: x64
|
||||
- name: "Prep README.md"
|
||||
run: python scripts/transform_readme.py --target pypi
|
||||
- name: "Build wheels"
|
||||
uses: PyO3/maturin-action@v1
|
||||
uses: PyO3/maturin-action@36db84001d74475ad1b8e6613557ae4ee2dc3598 # v1
|
||||
with:
|
||||
target: ${{ matrix.target }}
|
||||
manylinux: auto
|
||||
@@ -242,7 +242,7 @@ jobs:
|
||||
"${MODULE_NAME}" --help
|
||||
python -m "${MODULE_NAME}" --help
|
||||
- name: "Upload wheels"
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4
|
||||
with:
|
||||
name: wheels-${{ matrix.target }}
|
||||
path: dist
|
||||
@@ -260,7 +260,7 @@ jobs:
|
||||
tar czvf $ARCHIVE_FILE $ARCHIVE_NAME
|
||||
shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
|
||||
- name: "Upload binary"
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4
|
||||
with:
|
||||
name: artifacts-${{ matrix.target }}
|
||||
path: |
|
||||
@@ -294,24 +294,24 @@ jobs:
|
||||
arch: arm
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
with:
|
||||
submodules: recursive
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-python@v5
|
||||
- uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
- name: "Prep README.md"
|
||||
run: python scripts/transform_readme.py --target pypi
|
||||
- name: "Build wheels"
|
||||
uses: PyO3/maturin-action@v1
|
||||
uses: PyO3/maturin-action@36db84001d74475ad1b8e6613557ae4ee2dc3598 # v1
|
||||
with:
|
||||
target: ${{ matrix.platform.target }}
|
||||
manylinux: auto
|
||||
docker-options: ${{ matrix.platform.maturin_docker_options }}
|
||||
args: --release --locked --out dist
|
||||
- uses: uraimo/run-on-arch-action@v2
|
||||
if: matrix.platform.arch != 'ppc64'
|
||||
- uses: uraimo/run-on-arch-action@ac33288c3728ca72563c97b8b88dda5a65a84448 # v2
|
||||
if: ${{ matrix.platform.arch != 'ppc64' && matrix.platform.arch != 'ppc64le'}}
|
||||
name: Test wheel
|
||||
with:
|
||||
arch: ${{ matrix.platform.arch == 'arm' && 'armv6' || matrix.platform.arch }}
|
||||
@@ -325,7 +325,7 @@ jobs:
|
||||
pip3 install ${{ env.PACKAGE_NAME }} --no-index --find-links dist/ --force-reinstall
|
||||
ruff --help
|
||||
- name: "Upload wheels"
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4
|
||||
with:
|
||||
name: wheels-${{ matrix.platform.target }}
|
||||
path: dist
|
||||
@@ -343,7 +343,7 @@ jobs:
|
||||
tar czvf $ARCHIVE_FILE $ARCHIVE_NAME
|
||||
shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
|
||||
- name: "Upload binary"
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4
|
||||
with:
|
||||
name: artifacts-${{ matrix.platform.target }}
|
||||
path: |
|
||||
@@ -359,18 +359,18 @@ jobs:
|
||||
- x86_64-unknown-linux-musl
|
||||
- i686-unknown-linux-musl
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
with:
|
||||
submodules: recursive
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-python@v5
|
||||
- uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
architecture: x64
|
||||
- name: "Prep README.md"
|
||||
run: python scripts/transform_readme.py --target pypi
|
||||
- name: "Build wheels"
|
||||
uses: PyO3/maturin-action@v1
|
||||
uses: PyO3/maturin-action@36db84001d74475ad1b8e6613557ae4ee2dc3598 # v1
|
||||
with:
|
||||
target: ${{ matrix.target }}
|
||||
manylinux: musllinux_1_2
|
||||
@@ -387,7 +387,7 @@ jobs:
|
||||
.venv/bin/pip3 install ${{ env.PACKAGE_NAME }} --no-index --find-links dist/ --force-reinstall
|
||||
.venv/bin/${{ env.MODULE_NAME }} --help
|
||||
- name: "Upload wheels"
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4
|
||||
with:
|
||||
name: wheels-${{ matrix.target }}
|
||||
path: dist
|
||||
@@ -405,7 +405,7 @@ jobs:
|
||||
tar czvf $ARCHIVE_FILE $ARCHIVE_NAME
|
||||
shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
|
||||
- name: "Upload binary"
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4
|
||||
with:
|
||||
name: artifacts-${{ matrix.target }}
|
||||
path: |
|
||||
@@ -425,23 +425,23 @@ jobs:
|
||||
arch: armv7
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
with:
|
||||
submodules: recursive
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-python@v5
|
||||
- uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
- name: "Prep README.md"
|
||||
run: python scripts/transform_readme.py --target pypi
|
||||
- name: "Build wheels"
|
||||
uses: PyO3/maturin-action@v1
|
||||
uses: PyO3/maturin-action@36db84001d74475ad1b8e6613557ae4ee2dc3598 # v1
|
||||
with:
|
||||
target: ${{ matrix.platform.target }}
|
||||
manylinux: musllinux_1_2
|
||||
args: --release --locked --out dist
|
||||
docker-options: ${{ matrix.platform.maturin_docker_options }}
|
||||
- uses: uraimo/run-on-arch-action@v2
|
||||
- uses: uraimo/run-on-arch-action@ac33288c3728ca72563c97b8b88dda5a65a84448 # v2
|
||||
name: Test wheel
|
||||
with:
|
||||
arch: ${{ matrix.platform.arch }}
|
||||
@@ -454,7 +454,7 @@ jobs:
|
||||
.venv/bin/pip3 install ${{ env.PACKAGE_NAME }} --no-index --find-links dist/ --force-reinstall
|
||||
.venv/bin/${{ env.MODULE_NAME }} --help
|
||||
- name: "Upload wheels"
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4
|
||||
with:
|
||||
name: wheels-${{ matrix.platform.target }}
|
||||
path: dist
|
||||
@@ -472,7 +472,7 @@ jobs:
|
||||
tar czvf $ARCHIVE_FILE $ARCHIVE_NAME
|
||||
shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
|
||||
- name: "Upload binary"
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4
|
||||
with:
|
||||
name: artifacts-${{ matrix.platform.target }}
|
||||
path: |
|
||||
|
||||
38
.github/workflows/build-docker.yml
vendored
38
.github/workflows/build-docker.yml
vendored
@@ -33,14 +33,14 @@ jobs:
|
||||
- linux/amd64
|
||||
- linux/arm64
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
with:
|
||||
submodules: recursive
|
||||
persist-credentials: false
|
||||
|
||||
- uses: docker/setup-buildx-action@v3
|
||||
- uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3
|
||||
|
||||
- uses: docker/login-action@v3
|
||||
- uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
@@ -63,7 +63,7 @@ jobs:
|
||||
|
||||
- name: Extract metadata (tags, labels) for Docker
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5
|
||||
with:
|
||||
images: ${{ env.RUFF_BASE_IMG }}
|
||||
# Defining this makes sure the org.opencontainers.image.version OCI label becomes the actual release version and not the branch name
|
||||
@@ -79,7 +79,7 @@ jobs:
|
||||
# Adapted from https://docs.docker.com/build/ci/github-actions/multi-platform/
|
||||
- name: Build and push by digest
|
||||
id: build
|
||||
uses: docker/build-push-action@v6
|
||||
uses: docker/build-push-action@471d1dc4e07e5cdedd4c2171150001c434f0b7a4 # v6
|
||||
with:
|
||||
context: .
|
||||
platforms: ${{ matrix.platform }}
|
||||
@@ -96,7 +96,7 @@ jobs:
|
||||
touch "/tmp/digests/${digest#sha256:}"
|
||||
|
||||
- name: Upload digests
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4
|
||||
with:
|
||||
name: digests-${{ env.PLATFORM_TUPLE }}
|
||||
path: /tmp/digests/*
|
||||
@@ -113,17 +113,17 @@ jobs:
|
||||
if: ${{ inputs.plan != '' && !fromJson(inputs.plan).announcement_tag_is_implicit }}
|
||||
steps:
|
||||
- name: Download digests
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@cc203385981b70ca67e1cc392babf9cc229d5806 # v4
|
||||
with:
|
||||
path: /tmp/digests
|
||||
pattern: digests-*
|
||||
merge-multiple: true
|
||||
|
||||
- uses: docker/setup-buildx-action@v3
|
||||
- uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3
|
||||
|
||||
- name: Extract metadata (tags, labels) for Docker
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5
|
||||
with:
|
||||
images: ${{ env.RUFF_BASE_IMG }}
|
||||
# Order is on purpose such that the label org.opencontainers.image.version has the first pattern with the full version
|
||||
@@ -131,7 +131,7 @@ jobs:
|
||||
type=pep440,pattern={{ version }},value=${{ fromJson(inputs.plan).announcement_tag }}
|
||||
type=pep440,pattern={{ major }}.{{ minor }},value=${{ fromJson(inputs.plan).announcement_tag }}
|
||||
|
||||
- uses: docker/login-action@v3
|
||||
- uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
@@ -163,13 +163,13 @@ jobs:
|
||||
# Mapping of base image followed by a comma followed by one or more base tags (comma separated)
|
||||
# Note, org.opencontainers.image.version label will use the first base tag (use the most specific tag first)
|
||||
image-mapping:
|
||||
- alpine:3.20,alpine3.20,alpine
|
||||
- alpine:3.21,alpine3.21,alpine
|
||||
- debian:bookworm-slim,bookworm-slim,debian-slim
|
||||
- buildpack-deps:bookworm,bookworm,debian
|
||||
steps:
|
||||
- uses: docker/setup-buildx-action@v3
|
||||
- uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3
|
||||
|
||||
- uses: docker/login-action@v3
|
||||
- uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
@@ -219,7 +219,7 @@ jobs:
|
||||
|
||||
- name: Extract metadata (tags, labels) for Docker
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5
|
||||
# ghcr.io prefers index level annotations
|
||||
env:
|
||||
DOCKER_METADATA_ANNOTATIONS_LEVELS: index
|
||||
@@ -231,7 +231,7 @@ jobs:
|
||||
${{ env.TAG_PATTERNS }}
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v6
|
||||
uses: docker/build-push-action@471d1dc4e07e5cdedd4c2171150001c434f0b7a4 # v6
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64
|
||||
@@ -256,17 +256,17 @@ jobs:
|
||||
if: ${{ inputs.plan != '' && !fromJson(inputs.plan).announcement_tag_is_implicit }}
|
||||
steps:
|
||||
- name: Download digests
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@cc203385981b70ca67e1cc392babf9cc229d5806 # v4
|
||||
with:
|
||||
path: /tmp/digests
|
||||
pattern: digests-*
|
||||
merge-multiple: true
|
||||
|
||||
- uses: docker/setup-buildx-action@v3
|
||||
- uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3
|
||||
|
||||
- name: Extract metadata (tags, labels) for Docker
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5
|
||||
env:
|
||||
DOCKER_METADATA_ANNOTATIONS_LEVELS: index
|
||||
with:
|
||||
@@ -276,7 +276,7 @@ jobs:
|
||||
type=pep440,pattern={{ version }},value=${{ fromJson(inputs.plan).announcement_tag }}
|
||||
type=pep440,pattern={{ major }}.{{ minor }},value=${{ fromJson(inputs.plan).announcement_tag }}
|
||||
|
||||
- uses: docker/login-action@v3
|
||||
- uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
|
||||
340
.github/workflows/ci.yaml
vendored
340
.github/workflows/ci.yaml
vendored
@@ -26,83 +26,152 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
# Flag that is raised when any code that affects parser is changed
|
||||
parser: ${{ steps.changed.outputs.parser_any_changed }}
|
||||
parser: ${{ steps.check_parser.outputs.changed }}
|
||||
# Flag that is raised when any code that affects linter is changed
|
||||
linter: ${{ steps.changed.outputs.linter_any_changed }}
|
||||
linter: ${{ steps.check_linter.outputs.changed }}
|
||||
# Flag that is raised when any code that affects formatter is changed
|
||||
formatter: ${{ steps.changed.outputs.formatter_any_changed }}
|
||||
formatter: ${{ steps.check_formatter.outputs.changed }}
|
||||
# Flag that is raised when any code is changed
|
||||
# This is superset of the linter and formatter
|
||||
code: ${{ steps.changed.outputs.code_any_changed }}
|
||||
code: ${{ steps.check_code.outputs.changed }}
|
||||
# Flag that is raised when any code that affects the fuzzer is changed
|
||||
fuzz: ${{ steps.changed.outputs.fuzz_any_changed }}
|
||||
fuzz: ${{ steps.check_fuzzer.outputs.changed }}
|
||||
|
||||
# Flag that is set to "true" when code related to the playground changes.
|
||||
playground: ${{ steps.check_playground.outputs.changed }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
||||
- uses: tj-actions/changed-files@v45
|
||||
id: changed
|
||||
with:
|
||||
files_yaml: |
|
||||
parser:
|
||||
- Cargo.toml
|
||||
- Cargo.lock
|
||||
- crates/ruff_python_trivia/**
|
||||
- crates/ruff_source_file/**
|
||||
- crates/ruff_text_size/**
|
||||
- crates/ruff_python_ast/**
|
||||
- crates/ruff_python_parser/**
|
||||
- python/py-fuzzer/**
|
||||
- .github/workflows/ci.yaml
|
||||
- name: Determine merge base
|
||||
id: merge_base
|
||||
env:
|
||||
BASE_REF: ${{ github.event.pull_request.base.ref || 'main' }}
|
||||
run: |
|
||||
sha=$(git merge-base HEAD "origin/${BASE_REF}")
|
||||
echo "sha=${sha}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
linter:
|
||||
- Cargo.toml
|
||||
- Cargo.lock
|
||||
- crates/**
|
||||
- "!crates/red_knot*/**"
|
||||
- "!crates/ruff_python_formatter/**"
|
||||
- "!crates/ruff_formatter/**"
|
||||
- "!crates/ruff_dev/**"
|
||||
- scripts/*
|
||||
- python/**
|
||||
- .github/workflows/ci.yaml
|
||||
- name: Check if the parser code changed
|
||||
id: check_parser
|
||||
env:
|
||||
MERGE_BASE: ${{ steps.merge_base.outputs.sha }}
|
||||
run: |
|
||||
if git diff --quiet "${MERGE_BASE}...HEAD" -- \
|
||||
':Cargo.toml' \
|
||||
':Cargo.lock' \
|
||||
':crates/ruff_python_trivia/**' \
|
||||
':crates/ruff_source_file/**' \
|
||||
':crates/ruff_text_size/**' \
|
||||
':crates/ruff_python_ast/**' \
|
||||
':crates/ruff_python_parser/**' \
|
||||
':python/py-fuzzer/**' \
|
||||
':.github/workflows/ci.yaml' \
|
||||
; then
|
||||
echo "changed=false" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "changed=true" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
formatter:
|
||||
- Cargo.toml
|
||||
- Cargo.lock
|
||||
- crates/ruff_python_formatter/**
|
||||
- crates/ruff_formatter/**
|
||||
- crates/ruff_python_trivia/**
|
||||
- crates/ruff_python_ast/**
|
||||
- crates/ruff_source_file/**
|
||||
- crates/ruff_python_index/**
|
||||
- crates/ruff_text_size/**
|
||||
- crates/ruff_python_parser/**
|
||||
- crates/ruff_dev/**
|
||||
- scripts/*
|
||||
- python/**
|
||||
- .github/workflows/ci.yaml
|
||||
- name: Check if the linter code changed
|
||||
id: check_linter
|
||||
env:
|
||||
MERGE_BASE: ${{ steps.merge_base.outputs.sha }}
|
||||
run: |
|
||||
if git diff --quiet "${MERGE_BASE}...HEAD" -- ':Cargo.toml' \
|
||||
':Cargo.lock' \
|
||||
':crates/**' \
|
||||
':!crates/red_knot*/**' \
|
||||
':!crates/ruff_python_formatter/**' \
|
||||
':!crates/ruff_formatter/**' \
|
||||
':!crates/ruff_dev/**' \
|
||||
':!crates/ruff_db/**' \
|
||||
':scripts/*' \
|
||||
':python/**' \
|
||||
':.github/workflows/ci.yaml' \
|
||||
; then
|
||||
echo "changed=false" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "changed=true" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
fuzz:
|
||||
- fuzz/Cargo.toml
|
||||
- fuzz/Cargo.lock
|
||||
- fuzz/fuzz_targets/**
|
||||
- name: Check if the formatter code changed
|
||||
id: check_formatter
|
||||
env:
|
||||
MERGE_BASE: ${{ steps.merge_base.outputs.sha }}
|
||||
run: |
|
||||
if git diff --quiet "${MERGE_BASE}...HEAD" -- ':Cargo.toml' \
|
||||
':Cargo.lock' \
|
||||
':crates/ruff_python_formatter/**' \
|
||||
':crates/ruff_formatter/**' \
|
||||
':crates/ruff_python_trivia/**' \
|
||||
':crates/ruff_python_ast/**' \
|
||||
':crates/ruff_source_file/**' \
|
||||
':crates/ruff_python_index/**' \
|
||||
':crates/ruff_python_index/**' \
|
||||
':crates/ruff_text_size/**' \
|
||||
':crates/ruff_python_parser/**' \
|
||||
':scripts/*' \
|
||||
':python/**' \
|
||||
':.github/workflows/ci.yaml' \
|
||||
; then
|
||||
echo "changed=false" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "changed=true" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
code:
|
||||
- "**/*"
|
||||
- "!**/*.md"
|
||||
- "crates/red_knot_python_semantic/resources/mdtest/**/*.md"
|
||||
- "!docs/**"
|
||||
- "!assets/**"
|
||||
- name: Check if the fuzzer code changed
|
||||
id: check_fuzzer
|
||||
env:
|
||||
MERGE_BASE: ${{ steps.merge_base.outputs.sha }}
|
||||
run: |
|
||||
if git diff --quiet "${MERGE_BASE}...HEAD" -- ':Cargo.toml' \
|
||||
':Cargo.lock' \
|
||||
':fuzz/fuzz_targets/**' \
|
||||
':.github/workflows/ci.yaml' \
|
||||
; then
|
||||
echo "changed=false" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "changed=true" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Check if there was any code related change
|
||||
id: check_code
|
||||
env:
|
||||
MERGE_BASE: ${{ steps.merge_base.outputs.sha }}
|
||||
run: |
|
||||
if git diff --quiet "${MERGE_BASE}...HEAD" -- ':**/*' \
|
||||
':!**/*.md' \
|
||||
':crates/red_knot_python_semantic/resources/mdtest/**/*.md' \
|
||||
':!docs/**' \
|
||||
':!assets/**' \
|
||||
':.github/workflows/ci.yaml' \
|
||||
; then
|
||||
echo "changed=false" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "changed=true" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Check if there was any playground related change
|
||||
id: check_playground
|
||||
env:
|
||||
MERGE_BASE: ${{ steps.merge_base.outputs.sha }}
|
||||
run: |
|
||||
if git diff --quiet "${MERGE_BASE}...HEAD" -- \
|
||||
':playground/**' \
|
||||
; then
|
||||
echo "changed=false" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "changed=true" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
cargo-fmt:
|
||||
name: "cargo fmt"
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: "Install Rust toolchain"
|
||||
@@ -116,10 +185,10 @@ jobs:
|
||||
if: ${{ needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main' }}
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- uses: Swatinem/rust-cache@f0deed1e0edfc6a9be95417288c0e1099b1eeec3 # v2
|
||||
- name: "Install Rust toolchain"
|
||||
run: |
|
||||
rustup component add clippy
|
||||
@@ -136,20 +205,20 @@ jobs:
|
||||
if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-test') && (needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main') }}
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- uses: Swatinem/rust-cache@f0deed1e0edfc6a9be95417288c0e1099b1eeec3 # v2
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
- name: "Install mold"
|
||||
uses: rui314/setup-mold@v1
|
||||
- name: "Install cargo nextest"
|
||||
uses: taiki-e/install-action@v2
|
||||
uses: taiki-e/install-action@2c41309d51ede152b6f2ee6bf3b71e6dc9a8b7df # v2
|
||||
with:
|
||||
tool: cargo-nextest
|
||||
- name: "Install cargo insta"
|
||||
uses: taiki-e/install-action@v2
|
||||
uses: taiki-e/install-action@2c41309d51ede152b6f2ee6bf3b71e6dc9a8b7df # v2
|
||||
with:
|
||||
tool: cargo-insta
|
||||
- name: "Run tests"
|
||||
@@ -170,7 +239,7 @@ jobs:
|
||||
env:
|
||||
# Setting RUSTDOCFLAGS because `cargo doc --check` isn't yet implemented (https://github.com/rust-lang/cargo/issues/10025).
|
||||
RUSTDOCFLAGS: "-D warnings"
|
||||
- uses: actions/upload-artifact@v4
|
||||
- uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4
|
||||
with:
|
||||
name: ruff
|
||||
path: target/debug/ruff
|
||||
@@ -182,20 +251,20 @@ jobs:
|
||||
if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-test') && (needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main') }}
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- uses: Swatinem/rust-cache@f0deed1e0edfc6a9be95417288c0e1099b1eeec3 # v2
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
- name: "Install mold"
|
||||
uses: rui314/setup-mold@v1
|
||||
- name: "Install cargo nextest"
|
||||
uses: taiki-e/install-action@v2
|
||||
uses: taiki-e/install-action@2c41309d51ede152b6f2ee6bf3b71e6dc9a8b7df # v2
|
||||
with:
|
||||
tool: cargo-nextest
|
||||
- name: "Install cargo insta"
|
||||
uses: taiki-e/install-action@v2
|
||||
uses: taiki-e/install-action@2c41309d51ede152b6f2ee6bf3b71e6dc9a8b7df # v2
|
||||
with:
|
||||
tool: cargo-insta
|
||||
- name: "Run tests"
|
||||
@@ -211,14 +280,14 @@ jobs:
|
||||
if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-test') && (needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main') }}
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- uses: Swatinem/rust-cache@f0deed1e0edfc6a9be95417288c0e1099b1eeec3 # v2
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
- name: "Install cargo nextest"
|
||||
uses: taiki-e/install-action@v2
|
||||
uses: taiki-e/install-action@2c41309d51ede152b6f2ee6bf3b71e6dc9a8b7df # v2
|
||||
with:
|
||||
tool: cargo-nextest
|
||||
- name: "Run tests"
|
||||
@@ -238,18 +307,18 @@ jobs:
|
||||
if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-test') && (needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main') }}
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- uses: Swatinem/rust-cache@f0deed1e0edfc6a9be95417288c0e1099b1eeec3 # v2
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup target add wasm32-unknown-unknown
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: "npm"
|
||||
cache-dependency-path: playground/package-lock.json
|
||||
- uses: jetli/wasm-pack-action@v0.4.0
|
||||
- uses: jetli/wasm-pack-action@0d096b08b4e5a7de8c28de67e11e945404e9eefa # v0.4.0
|
||||
with:
|
||||
version: v0.13.1
|
||||
- name: "Test ruff_wasm"
|
||||
@@ -267,10 +336,10 @@ jobs:
|
||||
if: ${{ github.ref == 'refs/heads/main' }}
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- uses: Swatinem/rust-cache@f0deed1e0edfc6a9be95417288c0e1099b1eeec3 # v2
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
- name: "Install mold"
|
||||
@@ -285,15 +354,15 @@ jobs:
|
||||
if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-test') && (needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main') }}
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: SebRollen/toml-action@v1.2.0
|
||||
- uses: SebRollen/toml-action@b1b3628f55fc3a28208d4203ada8b737e9687876 # v1.2.0
|
||||
id: msrv
|
||||
with:
|
||||
file: "Cargo.toml"
|
||||
field: "workspace.package.rust-version"
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- uses: Swatinem/rust-cache@f0deed1e0edfc6a9be95417288c0e1099b1eeec3 # v2
|
||||
- name: "Install Rust toolchain"
|
||||
env:
|
||||
MSRV: ${{ steps.msrv.outputs.value }}
|
||||
@@ -301,11 +370,11 @@ jobs:
|
||||
- name: "Install mold"
|
||||
uses: rui314/setup-mold@v1
|
||||
- name: "Install cargo nextest"
|
||||
uses: taiki-e/install-action@v2
|
||||
uses: taiki-e/install-action@2c41309d51ede152b6f2ee6bf3b71e6dc9a8b7df # v2
|
||||
with:
|
||||
tool: cargo-nextest
|
||||
- name: "Install cargo insta"
|
||||
uses: taiki-e/install-action@v2
|
||||
uses: taiki-e/install-action@2c41309d51ede152b6f2ee6bf3b71e6dc9a8b7df # v2
|
||||
with:
|
||||
tool: cargo-insta
|
||||
- name: "Run tests"
|
||||
@@ -322,10 +391,10 @@ jobs:
|
||||
if: ${{ github.ref == 'refs/heads/main' || needs.determine_changes.outputs.fuzz == 'true' || needs.determine_changes.outputs.code == 'true' }}
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- uses: Swatinem/rust-cache@f0deed1e0edfc6a9be95417288c0e1099b1eeec3 # v2
|
||||
with:
|
||||
workspaces: "fuzz -> target"
|
||||
- name: "Install Rust toolchain"
|
||||
@@ -350,11 +419,11 @@ jobs:
|
||||
env:
|
||||
FORCE_COLOR: 1
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: astral-sh/setup-uv@v5
|
||||
- uses: actions/download-artifact@v4
|
||||
- uses: astral-sh/setup-uv@f94ec6bedd8674c4426838e6b50417d36b6ab231 # v5
|
||||
- uses: actions/download-artifact@cc203385981b70ca67e1cc392babf9cc229d5806 # v4
|
||||
name: Download Ruff binary to test
|
||||
id: download-cached-binary
|
||||
with:
|
||||
@@ -384,10 +453,10 @@ jobs:
|
||||
if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-test') && (needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main') }}
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- uses: Swatinem/rust-cache@f0deed1e0edfc6a9be95417288c0e1099b1eeec3 # v2
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup component add rustfmt
|
||||
# Run all code generation scripts, and verify that the current output is
|
||||
@@ -416,21 +485,21 @@ jobs:
|
||||
if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-test') && github.event_name == 'pull_request' && needs.determine_changes.outputs.code == 'true' }}
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-python@v5
|
||||
- uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
|
||||
- uses: actions/download-artifact@v4
|
||||
- uses: actions/download-artifact@cc203385981b70ca67e1cc392babf9cc229d5806 # v4
|
||||
name: Download comparison Ruff binary
|
||||
id: ruff-target
|
||||
with:
|
||||
name: ruff
|
||||
path: target/debug
|
||||
|
||||
- uses: dawidd6/action-download-artifact@v8
|
||||
- uses: dawidd6/action-download-artifact@20319c5641d495c8a52e688b7dc5fada6c3a9fbc # v8
|
||||
name: Download baseline Ruff binary
|
||||
with:
|
||||
name: ruff
|
||||
@@ -518,13 +587,13 @@ jobs:
|
||||
run: |
|
||||
echo ${{ github.event.number }} > pr-number
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
- uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4
|
||||
name: Upload PR Number
|
||||
with:
|
||||
name: pr-number
|
||||
path: pr-number
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
- uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4
|
||||
name: Upload Results
|
||||
with:
|
||||
name: ecosystem-result
|
||||
@@ -536,7 +605,7 @@ jobs:
|
||||
needs: determine_changes
|
||||
if: ${{ needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: cargo-bins/cargo-binstall@main
|
||||
@@ -549,18 +618,18 @@ jobs:
|
||||
timeout-minutes: 20
|
||||
if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-test') }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-python@v5
|
||||
- uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
architecture: x64
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- uses: Swatinem/rust-cache@f0deed1e0edfc6a9be95417288c0e1099b1eeec3 # v2
|
||||
- name: "Prep README.md"
|
||||
run: python scripts/transform_readme.py --target pypi
|
||||
- name: "Build wheels"
|
||||
uses: PyO3/maturin-action@v1
|
||||
uses: PyO3/maturin-action@36db84001d74475ad1b8e6613557ae4ee2dc3598 # v1
|
||||
with:
|
||||
args: --out dist
|
||||
- name: "Test wheel"
|
||||
@@ -576,19 +645,19 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-python@v5
|
||||
- uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- uses: Swatinem/rust-cache@f0deed1e0edfc6a9be95417288c0e1099b1eeec3 # v2
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
- name: "Install pre-commit"
|
||||
run: pip install pre-commit
|
||||
- name: "Cache pre-commit"
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@d4323d4df104b026a6aa633fdb11d772146be0bf # v4
|
||||
with:
|
||||
path: ~/.cache/pre-commit
|
||||
key: pre-commit-${{ hashFiles('.pre-commit-config.yaml') }}
|
||||
@@ -610,22 +679,22 @@ jobs:
|
||||
env:
|
||||
MKDOCS_INSIDERS_SSH_KEY_EXISTS: ${{ secrets.MKDOCS_INSIDERS_SSH_KEY != '' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-python@v5
|
||||
- uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5
|
||||
with:
|
||||
python-version: "3.13"
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- uses: Swatinem/rust-cache@f0deed1e0edfc6a9be95417288c0e1099b1eeec3 # v2
|
||||
- name: "Add SSH key"
|
||||
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }}
|
||||
uses: webfactory/ssh-agent@v0.9.0
|
||||
uses: webfactory/ssh-agent@dc588b651fe13675774614f8e6a936a468676387 # v0.9.0
|
||||
with:
|
||||
ssh-private-key: ${{ secrets.MKDOCS_INSIDERS_SSH_KEY }}
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@v5
|
||||
uses: astral-sh/setup-uv@f94ec6bedd8674c4426838e6b50417d36b6ab231 # v5
|
||||
- name: "Install Insiders dependencies"
|
||||
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }}
|
||||
run: uv pip install -r docs/requirements-insiders.txt --system
|
||||
@@ -652,10 +721,10 @@ jobs:
|
||||
if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-test') && (needs.determine_changes.outputs.formatter == 'true' || github.ref == 'refs/heads/main') }}
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- uses: Swatinem/rust-cache@f0deed1e0edfc6a9be95417288c0e1099b1eeec3 # v2
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
- name: "Run checks"
|
||||
@@ -674,21 +743,21 @@ jobs:
|
||||
- determine_changes
|
||||
if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-test') && (needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main') }}
|
||||
steps:
|
||||
- uses: extractions/setup-just@v2
|
||||
- uses: extractions/setup-just@dd310ad5a97d8e7b41793f8ef055398d51ad4de6 # v2
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
name: "Download ruff-lsp source"
|
||||
with:
|
||||
persist-credentials: false
|
||||
repository: "astral-sh/ruff-lsp"
|
||||
|
||||
- uses: actions/setup-python@v5
|
||||
- uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
|
||||
- uses: actions/download-artifact@v4
|
||||
- uses: actions/download-artifact@cc203385981b70ca67e1cc392babf9cc229d5806 # v4
|
||||
name: Download development ruff binary
|
||||
id: ruff-target
|
||||
with:
|
||||
@@ -711,6 +780,39 @@ jobs:
|
||||
|
||||
just test
|
||||
|
||||
check-playground:
|
||||
name: "check playground"
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
needs:
|
||||
- determine_changes
|
||||
if: ${{ (needs.determine_changes.outputs.playground == 'true') }}
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup target add wasm32-unknown-unknown
|
||||
- uses: Swatinem/rust-cache@f0deed1e0edfc6a9be95417288c0e1099b1eeec3 # v2
|
||||
- uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: "npm"
|
||||
cache-dependency-path: playground/package-lock.json
|
||||
- uses: jetli/wasm-bindgen-action@20b33e20595891ab1a0ed73145d8a21fc96e7c29 # v0.2.0
|
||||
- name: "Install Node dependencies"
|
||||
run: npm ci
|
||||
working-directory: playground
|
||||
- name: "Build playgrounds"
|
||||
run: npm run dev:wasm
|
||||
working-directory: playground
|
||||
- name: "Run TypeScript checks"
|
||||
run: npm run check
|
||||
working-directory: playground
|
||||
- name: "Check formatting"
|
||||
run: npm run fmt:check
|
||||
working-directory: playground
|
||||
|
||||
benchmarks:
|
||||
runs-on: ubuntu-24.04
|
||||
needs: determine_changes
|
||||
@@ -718,17 +820,17 @@ jobs:
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- name: "Checkout Branch"
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- uses: Swatinem/rust-cache@f0deed1e0edfc6a9be95417288c0e1099b1eeec3 # v2
|
||||
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
|
||||
- name: "Install codspeed"
|
||||
uses: taiki-e/install-action@v2
|
||||
uses: taiki-e/install-action@2c41309d51ede152b6f2ee6bf3b71e6dc9a8b7df # v2
|
||||
with:
|
||||
tool: cargo-codspeed
|
||||
|
||||
@@ -736,7 +838,7 @@ jobs:
|
||||
run: cargo codspeed build --features codspeed -p ruff_benchmark
|
||||
|
||||
- name: "Run benchmarks"
|
||||
uses: CodSpeedHQ/action@v3
|
||||
uses: CodSpeedHQ/action@0010eb0ca6e89b80c88e8edaaa07cfe5f3e6664d # v3
|
||||
with:
|
||||
run: cargo codspeed run
|
||||
token: ${{ secrets.CODSPEED_TOKEN }}
|
||||
|
||||
8
.github/workflows/daily_fuzz.yaml
vendored
8
.github/workflows/daily_fuzz.yaml
vendored
@@ -31,15 +31,15 @@ jobs:
|
||||
# Don't run the cron job on forks:
|
||||
if: ${{ github.repository == 'astral-sh/ruff' || github.event_name != 'schedule' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: astral-sh/setup-uv@v5
|
||||
- uses: astral-sh/setup-uv@f94ec6bedd8674c4426838e6b50417d36b6ab231 # v5
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
- name: "Install mold"
|
||||
uses: rui314/setup-mold@v1
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- uses: Swatinem/rust-cache@f0deed1e0edfc6a9be95417288c0e1099b1eeec3 # v2
|
||||
- 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
|
||||
@@ -65,7 +65,7 @@ jobs:
|
||||
permissions:
|
||||
issues: write
|
||||
steps:
|
||||
- uses: actions/github-script@v7
|
||||
- uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
|
||||
6
.github/workflows/daily_property_tests.yaml
vendored
6
.github/workflows/daily_property_tests.yaml
vendored
@@ -30,14 +30,14 @@ jobs:
|
||||
# Don't run the cron job on forks:
|
||||
if: ${{ github.repository == 'astral-sh/ruff' || github.event_name != 'schedule' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
- name: "Install mold"
|
||||
uses: rui314/setup-mold@v1
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- uses: Swatinem/rust-cache@f0deed1e0edfc6a9be95417288c0e1099b1eeec3 # v2
|
||||
- name: Build Red Knot
|
||||
# A release build takes longer (2 min vs 1 min), but the property tests run much faster in release
|
||||
# mode (1.5 min vs 14 min), so the overall time is shorter with a release build.
|
||||
@@ -59,7 +59,7 @@ jobs:
|
||||
permissions:
|
||||
issues: write
|
||||
steps:
|
||||
- uses: actions/github-script@v7
|
||||
- uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
|
||||
35
.github/workflows/mypy_primer.yaml
vendored
35
.github/workflows/mypy_primer.yaml
vendored
@@ -28,21 +28,25 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
with:
|
||||
path: ruff
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install the latest version of uv
|
||||
uses: astral-sh/setup-uv@v5
|
||||
uses: astral-sh/setup-uv@f94ec6bedd8674c4426838e6b50417d36b6ab231 # v5
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- uses: Swatinem/rust-cache@f0deed1e0edfc6a9be95417288c0e1099b1eeec3 # v2
|
||||
with:
|
||||
workspaces: "ruff"
|
||||
- name: Install Rust toolchain
|
||||
run: rustup show
|
||||
|
||||
- name: Install mypy_primer
|
||||
run: |
|
||||
uv tool install "git+https://github.com/astral-sh/mypy_primer.git@add-red-knot-support"
|
||||
|
||||
- name: Run mypy_primer
|
||||
shell: bash
|
||||
run: |
|
||||
@@ -58,18 +62,15 @@ jobs:
|
||||
|
||||
cd ..
|
||||
|
||||
echo "Running mypy_primer"
|
||||
|
||||
(
|
||||
uvx --verbose --from "git+https://github.com/astral-sh/mypy_primer.git@add-red-knot-support" mypy_primer \
|
||||
--repo ruff \
|
||||
--type-checker knot \
|
||||
--old base_commit \
|
||||
--new "$GITHUB_SHA" \
|
||||
--project-selector '/(mypy_primer|black|pyp|git-revise|zipp|arrow)$' \
|
||||
--output concise \
|
||||
--debug > mypy_primer.diff
|
||||
) || [ $? -eq 1 ]
|
||||
# Allow the exit code to be 0 or 1, only fail for actual mypy_primer crashes/bugs
|
||||
uvx mypy_primer \
|
||||
--repo ruff \
|
||||
--type-checker knot \
|
||||
--old base_commit \
|
||||
--new "$GITHUB_SHA" \
|
||||
--project-selector '/(mypy_primer|black|pyp|git-revise|zipp|arrow|isort|itsdangerous|rich|packaging|pybind11|pyinstrument)$' \
|
||||
--output concise \
|
||||
--debug > mypy_primer.diff || [ $? -eq 1 ]
|
||||
|
||||
# Output diff with ANSI color codes
|
||||
cat mypy_primer.diff
|
||||
@@ -80,13 +81,13 @@ jobs:
|
||||
echo ${{ github.event.number }} > pr-number
|
||||
|
||||
- name: Upload diff
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4
|
||||
with:
|
||||
name: mypy_primer_diff
|
||||
path: mypy_primer.diff
|
||||
|
||||
- name: Upload pr-number
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4
|
||||
with:
|
||||
name: pr-number
|
||||
path: pr-number
|
||||
|
||||
8
.github/workflows/mypy_primer_comment.yaml
vendored
8
.github/workflows/mypy_primer_comment.yaml
vendored
@@ -16,7 +16,7 @@ jobs:
|
||||
permissions:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: dawidd6/action-download-artifact@v8
|
||||
- uses: dawidd6/action-download-artifact@20319c5641d495c8a52e688b7dc5fada6c3a9fbc # v8
|
||||
name: Download PR number
|
||||
with:
|
||||
name: pr-number
|
||||
@@ -32,7 +32,7 @@ jobs:
|
||||
echo "pr-number=$(<pr-number)" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- uses: dawidd6/action-download-artifact@v8
|
||||
- uses: dawidd6/action-download-artifact@20319c5641d495c8a52e688b7dc5fada6c3a9fbc # v8
|
||||
name: "Download mypy_primer results"
|
||||
id: download-mypy_primer_diff
|
||||
if: steps.pr-number.outputs.pr-number
|
||||
@@ -79,7 +79,7 @@ jobs:
|
||||
echo 'EOF' >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Find existing comment
|
||||
uses: peter-evans/find-comment@v3
|
||||
uses: peter-evans/find-comment@3eae4d37986fb5a8592848f6a574fdf654e61f9e # v3
|
||||
if: steps.generate-comment.outcome == 'success'
|
||||
id: find-comment
|
||||
with:
|
||||
@@ -89,7 +89,7 @@ jobs:
|
||||
|
||||
- name: Create or update comment
|
||||
if: steps.find-comment.outcome == 'success'
|
||||
uses: peter-evans/create-or-update-comment@v4
|
||||
uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4
|
||||
with:
|
||||
comment-id: ${{ steps.find-comment.outputs.comment-id }}
|
||||
issue-number: ${{ steps.pr-number.outputs.pr-number }}
|
||||
|
||||
2
.github/workflows/notify-dependents.yml
vendored
2
.github/workflows/notify-dependents.yml
vendored
@@ -17,7 +17,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: "Update pre-commit mirror"
|
||||
uses: actions/github-script@v7
|
||||
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7
|
||||
with:
|
||||
github-token: ${{ secrets.RUFF_PRE_COMMIT_PAT }}
|
||||
script: |
|
||||
|
||||
8
.github/workflows/pr-comment.yaml
vendored
8
.github/workflows/pr-comment.yaml
vendored
@@ -16,7 +16,7 @@ jobs:
|
||||
permissions:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: dawidd6/action-download-artifact@v8
|
||||
- uses: dawidd6/action-download-artifact@20319c5641d495c8a52e688b7dc5fada6c3a9fbc # v8
|
||||
name: Download pull request number
|
||||
with:
|
||||
name: pr-number
|
||||
@@ -32,7 +32,7 @@ jobs:
|
||||
echo "pr-number=$(<pr-number)" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- uses: dawidd6/action-download-artifact@v8
|
||||
- uses: dawidd6/action-download-artifact@20319c5641d495c8a52e688b7dc5fada6c3a9fbc # v8
|
||||
name: "Download ecosystem results"
|
||||
id: download-ecosystem-result
|
||||
if: steps.pr-number.outputs.pr-number
|
||||
@@ -70,7 +70,7 @@ jobs:
|
||||
echo 'EOF' >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Find existing comment
|
||||
uses: peter-evans/find-comment@v3
|
||||
uses: peter-evans/find-comment@3eae4d37986fb5a8592848f6a574fdf654e61f9e # v3
|
||||
if: steps.generate-comment.outcome == 'success'
|
||||
id: find-comment
|
||||
with:
|
||||
@@ -80,7 +80,7 @@ jobs:
|
||||
|
||||
- name: Create or update comment
|
||||
if: steps.find-comment.outcome == 'success'
|
||||
uses: peter-evans/create-or-update-comment@v4
|
||||
uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4
|
||||
with:
|
||||
comment-id: ${{ steps.find-comment.outputs.comment-id }}
|
||||
issue-number: ${{ steps.pr-number.outputs.pr-number }}
|
||||
|
||||
8
.github/workflows/publish-docs.yml
vendored
8
.github/workflows/publish-docs.yml
vendored
@@ -23,12 +23,12 @@ jobs:
|
||||
env:
|
||||
MKDOCS_INSIDERS_SSH_KEY_EXISTS: ${{ secrets.MKDOCS_INSIDERS_SSH_KEY != '' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
with:
|
||||
ref: ${{ inputs.ref }}
|
||||
persist-credentials: true
|
||||
|
||||
- uses: actions/setup-python@v5
|
||||
- uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5
|
||||
with:
|
||||
python-version: 3.12
|
||||
|
||||
@@ -61,14 +61,14 @@ jobs:
|
||||
|
||||
- name: "Add SSH key"
|
||||
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }}
|
||||
uses: webfactory/ssh-agent@v0.9.0
|
||||
uses: webfactory/ssh-agent@dc588b651fe13675774614f8e6a936a468676387 # v0.9.0
|
||||
with:
|
||||
ssh-private-key: ${{ secrets.MKDOCS_INSIDERS_SSH_KEY }}
|
||||
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- uses: Swatinem/rust-cache@f0deed1e0edfc6a9be95417288c0e1099b1eeec3 # v2
|
||||
|
||||
- name: "Install Insiders dependencies"
|
||||
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }}
|
||||
|
||||
58
.github/workflows/publish-knot-playground.yml
vendored
Normal file
58
.github/workflows/publish-knot-playground.yml
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
# Publish the Red Knot playground.
|
||||
name: "[Knot Playground] Release"
|
||||
|
||||
permissions: {}
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- "crates/red_knot*/**"
|
||||
- "crates/ruff_db"
|
||||
- "crates/ruff_python_ast"
|
||||
- "crates/ruff_python_parser"
|
||||
- "playground"
|
||||
- ".github/workflows/publish-knot-playground.yml"
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref_name }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
CARGO_INCREMENTAL: 0
|
||||
CARGO_NET_RETRY: 10
|
||||
CARGO_TERM_COLOR: always
|
||||
RUSTUP_MAX_RETRIES: 10
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
CF_API_TOKEN_EXISTS: ${{ secrets.CF_API_TOKEN != '' }}
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup target add wasm32-unknown-unknown
|
||||
- uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4
|
||||
with:
|
||||
node-version: 22
|
||||
- uses: jetli/wasm-bindgen-action@20b33e20595891ab1a0ed73145d8a21fc96e7c29 # v0.2.0
|
||||
- name: "Install Node dependencies"
|
||||
run: npm ci
|
||||
working-directory: playground
|
||||
- name: "Run TypeScript checks"
|
||||
run: npm run check
|
||||
working-directory: playground
|
||||
- name: "Build Knot playground"
|
||||
run: npm run build --workspace knot-playground
|
||||
working-directory: playground
|
||||
- name: "Deploy to Cloudflare Pages"
|
||||
if: ${{ env.CF_API_TOKEN_EXISTS == 'true' }}
|
||||
uses: cloudflare/wrangler-action@da0e0dfe58b7a431659754fdf3f186c529afbe65 # v3.14.1
|
||||
with:
|
||||
apiToken: ${{ secrets.CF_API_TOKEN }}
|
||||
accountId: ${{ secrets.CF_ACCOUNT_ID }}
|
||||
# `github.head_ref` is only set during pull requests and for manual runs or tags we use `main` to deploy to production
|
||||
command: pages deploy playground/knot/dist --project-name=knot-playground --branch ${{ github.head_ref || 'main' }} --commit-hash ${GITHUB_SHA}
|
||||
21
.github/workflows/publish-playground.yml
vendored
21
.github/workflows/publish-playground.yml
vendored
@@ -24,36 +24,31 @@ jobs:
|
||||
env:
|
||||
CF_API_TOKEN_EXISTS: ${{ secrets.CF_API_TOKEN != '' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup target add wasm32-unknown-unknown
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4
|
||||
with:
|
||||
node-version: 20
|
||||
node-version: 22
|
||||
cache: "npm"
|
||||
cache-dependency-path: playground/package-lock.json
|
||||
- uses: jetli/wasm-pack-action@v0.4.0
|
||||
with:
|
||||
version: v0.13.1
|
||||
- uses: jetli/wasm-bindgen-action@v0.2.0
|
||||
- name: "Run wasm-pack"
|
||||
run: wasm-pack build --target web --out-dir ../../playground/src/pkg crates/ruff_wasm
|
||||
- uses: jetli/wasm-bindgen-action@20b33e20595891ab1a0ed73145d8a21fc96e7c29 # v0.2.0
|
||||
- name: "Install Node dependencies"
|
||||
run: npm ci
|
||||
working-directory: playground
|
||||
- name: "Run TypeScript checks"
|
||||
run: npm run check
|
||||
working-directory: playground
|
||||
- name: "Build JavaScript bundle"
|
||||
run: npm run build
|
||||
- name: "Build Ruff playground"
|
||||
run: npm run build --workspace ruff-playground
|
||||
working-directory: playground
|
||||
- name: "Deploy to Cloudflare Pages"
|
||||
if: ${{ env.CF_API_TOKEN_EXISTS == 'true' }}
|
||||
uses: cloudflare/wrangler-action@v3.14.0
|
||||
uses: cloudflare/wrangler-action@da0e0dfe58b7a431659754fdf3f186c529afbe65 # v3.14.1
|
||||
with:
|
||||
apiToken: ${{ secrets.CF_API_TOKEN }}
|
||||
accountId: ${{ secrets.CF_ACCOUNT_ID }}
|
||||
# `github.head_ref` is only set during pull requests and for manual runs or tags we use `main` to deploy to production
|
||||
command: pages deploy playground/dist --project-name=ruff-playground --branch ${{ github.head_ref || 'main' }} --commit-hash ${GITHUB_SHA}
|
||||
command: pages deploy playground/ruff/dist --project-name=ruff-playground --branch ${{ github.head_ref || 'main' }} --commit-hash ${GITHUB_SHA}
|
||||
|
||||
4
.github/workflows/publish-pypi.yml
vendored
4
.github/workflows/publish-pypi.yml
vendored
@@ -22,8 +22,8 @@ jobs:
|
||||
id-token: write
|
||||
steps:
|
||||
- name: "Install uv"
|
||||
uses: astral-sh/setup-uv@v5
|
||||
- uses: actions/download-artifact@v4
|
||||
uses: astral-sh/setup-uv@f94ec6bedd8674c4426838e6b50417d36b6ab231 # v5
|
||||
- uses: actions/download-artifact@cc203385981b70ca67e1cc392babf9cc229d5806 # v4
|
||||
with:
|
||||
pattern: wheels-*
|
||||
path: wheels
|
||||
|
||||
8
.github/workflows/publish-wasm.yml
vendored
8
.github/workflows/publish-wasm.yml
vendored
@@ -29,15 +29,15 @@ jobs:
|
||||
target: [web, bundler, nodejs]
|
||||
fail-fast: false
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup target add wasm32-unknown-unknown
|
||||
- uses: jetli/wasm-pack-action@v0.4.0
|
||||
- uses: jetli/wasm-pack-action@0d096b08b4e5a7de8c28de67e11e945404e9eefa # v0.4.0
|
||||
with:
|
||||
version: v0.13.1
|
||||
- uses: jetli/wasm-bindgen-action@v0.2.0
|
||||
- uses: jetli/wasm-bindgen-action@20b33e20595891ab1a0ed73145d8a21fc96e7c29 # v0.2.0
|
||||
- name: "Run wasm-pack build"
|
||||
run: wasm-pack build --target ${{ matrix.target }} crates/ruff_wasm
|
||||
- name: "Rename generated package"
|
||||
@@ -45,7 +45,7 @@ jobs:
|
||||
jq '.name="@astral-sh/ruff-wasm-${{ matrix.target }}"' crates/ruff_wasm/pkg/package.json > /tmp/package.json
|
||||
mv /tmp/package.json crates/ruff_wasm/pkg
|
||||
- run: cp LICENSE crates/ruff_wasm/pkg # wasm-pack does not put the LICENSE file in the pkg
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4
|
||||
with:
|
||||
node-version: 20
|
||||
registry-url: "https://registry.npmjs.org"
|
||||
|
||||
34
.github/workflows/release.yml
vendored
34
.github/workflows/release.yml
vendored
@@ -50,7 +50,7 @@ on:
|
||||
jobs:
|
||||
# Run 'dist plan' (or host) to determine what tasks we need to do
|
||||
plan:
|
||||
runs-on: "ubuntu-20.04"
|
||||
runs-on: "depot-ubuntu-latest-4"
|
||||
outputs:
|
||||
val: ${{ steps.plan.outputs.manifest }}
|
||||
tag: ${{ (inputs.tag != 'dry-run' && inputs.tag) || '' }}
|
||||
@@ -59,7 +59,7 @@ jobs:
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Install dist
|
||||
@@ -68,7 +68,7 @@ jobs:
|
||||
shell: bash
|
||||
run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v0.25.2-prerelease.3/cargo-dist-installer.sh | sh"
|
||||
- name: Cache dist
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4
|
||||
with:
|
||||
name: cargo-dist-cache
|
||||
path: ~/.cargo/bin/dist
|
||||
@@ -84,7 +84,7 @@ jobs:
|
||||
cat plan-dist-manifest.json
|
||||
echo "manifest=$(jq -c "." plan-dist-manifest.json)" >> "$GITHUB_OUTPUT"
|
||||
- name: "Upload dist-manifest.json"
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4
|
||||
with:
|
||||
name: artifacts-plan-dist-manifest
|
||||
path: plan-dist-manifest.json
|
||||
@@ -116,23 +116,23 @@ jobs:
|
||||
- plan
|
||||
- custom-build-binaries
|
||||
- custom-build-docker
|
||||
runs-on: "ubuntu-20.04"
|
||||
runs-on: "depot-ubuntu-latest-4"
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
BUILD_MANIFEST_NAME: target/distrib/global-dist-manifest.json
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Install cached dist
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@cc203385981b70ca67e1cc392babf9cc229d5806 # v4
|
||||
with:
|
||||
name: cargo-dist-cache
|
||||
path: ~/.cargo/bin/
|
||||
- run: chmod +x ~/.cargo/bin/dist
|
||||
# Get all the local artifacts for the global tasks to use (for e.g. checksums)
|
||||
- name: Fetch local artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@cc203385981b70ca67e1cc392babf9cc229d5806 # v4
|
||||
with:
|
||||
pattern: artifacts-*
|
||||
path: target/distrib/
|
||||
@@ -150,7 +150,7 @@ jobs:
|
||||
|
||||
cp dist-manifest.json "$BUILD_MANIFEST_NAME"
|
||||
- name: "Upload artifacts"
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4
|
||||
with:
|
||||
name: artifacts-build-global
|
||||
path: |
|
||||
@@ -167,22 +167,22 @@ jobs:
|
||||
if: ${{ always() && needs.plan.outputs.publishing == 'true' && (needs.build-global-artifacts.result == 'skipped' || needs.build-global-artifacts.result == 'success') && (needs.custom-build-binaries.result == 'skipped' || needs.custom-build-binaries.result == 'success') && (needs.custom-build-docker.result == 'skipped' || needs.custom-build-docker.result == 'success') }}
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
runs-on: "ubuntu-20.04"
|
||||
runs-on: "depot-ubuntu-latest-4"
|
||||
outputs:
|
||||
val: ${{ steps.host.outputs.manifest }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Install cached dist
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@cc203385981b70ca67e1cc392babf9cc229d5806 # v4
|
||||
with:
|
||||
name: cargo-dist-cache
|
||||
path: ~/.cargo/bin/
|
||||
- run: chmod +x ~/.cargo/bin/dist
|
||||
# Fetch artifacts from scratch-storage
|
||||
- name: Fetch artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@cc203385981b70ca67e1cc392babf9cc229d5806 # v4
|
||||
with:
|
||||
pattern: artifacts-*
|
||||
path: target/distrib/
|
||||
@@ -196,7 +196,7 @@ jobs:
|
||||
cat dist-manifest.json
|
||||
echo "manifest=$(jq -c "." dist-manifest.json)" >> "$GITHUB_OUTPUT"
|
||||
- name: "Upload dist-manifest.json"
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4
|
||||
with:
|
||||
# Overwrite the previous copy
|
||||
name: artifacts-dist-manifest
|
||||
@@ -242,16 +242,16 @@ jobs:
|
||||
# still allowing individual publish jobs to skip themselves (for prereleases).
|
||||
# "host" however must run to completion, no skipping allowed!
|
||||
if: ${{ always() && needs.host.result == 'success' && (needs.custom-publish-pypi.result == 'skipped' || needs.custom-publish-pypi.result == 'success') && (needs.custom-publish-wasm.result == 'skipped' || needs.custom-publish-wasm.result == 'success') }}
|
||||
runs-on: "ubuntu-20.04"
|
||||
runs-on: "depot-ubuntu-latest-4"
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
with:
|
||||
submodules: recursive
|
||||
# Create a GitHub Release while uploading all files to it
|
||||
- name: "Download GitHub Artifacts"
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@cc203385981b70ca67e1cc392babf9cc229d5806 # v4
|
||||
with:
|
||||
pattern: artifacts-*
|
||||
path: artifacts
|
||||
|
||||
6
.github/workflows/sync_typeshed.yaml
vendored
6
.github/workflows/sync_typeshed.yaml
vendored
@@ -21,12 +21,12 @@ jobs:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
name: Checkout Ruff
|
||||
with:
|
||||
path: ruff
|
||||
persist-credentials: true
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
name: Checkout typeshed
|
||||
with:
|
||||
repository: python/typeshed
|
||||
@@ -70,7 +70,7 @@ jobs:
|
||||
permissions:
|
||||
issues: write
|
||||
steps:
|
||||
- uses: actions/github-script@v7
|
||||
- uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
|
||||
@@ -19,7 +19,7 @@ exclude: |
|
||||
|
||||
repos:
|
||||
- repo: https://github.com/abravalheri/validate-pyproject
|
||||
rev: v0.23
|
||||
rev: v0.24
|
||||
hooks:
|
||||
- id: validate-pyproject
|
||||
|
||||
@@ -60,7 +60,7 @@ repos:
|
||||
- black==25.1.0
|
||||
|
||||
- repo: https://github.com/crate-ci/typos
|
||||
rev: v1.30.0
|
||||
rev: v1.30.2
|
||||
hooks:
|
||||
- id: typos
|
||||
|
||||
@@ -74,7 +74,7 @@ repos:
|
||||
pass_filenames: false # This makes it a lot faster
|
||||
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.9.9
|
||||
rev: v0.11.0
|
||||
hooks:
|
||||
- id: ruff-format
|
||||
- id: ruff
|
||||
@@ -84,7 +84,7 @@ repos:
|
||||
|
||||
# Prettier
|
||||
- repo: https://github.com/rbubley/mirrors-prettier
|
||||
rev: v3.5.2
|
||||
rev: v3.5.3
|
||||
hooks:
|
||||
- id: prettier
|
||||
types: [yaml]
|
||||
@@ -92,12 +92,12 @@ repos:
|
||||
# zizmor detects security vulnerabilities in GitHub Actions workflows.
|
||||
# Additional configuration for the tool is found in `.github/zizmor.yml`
|
||||
- repo: https://github.com/woodruffw/zizmor-pre-commit
|
||||
rev: v1.4.1
|
||||
rev: v1.5.1
|
||||
hooks:
|
||||
- id: zizmor
|
||||
|
||||
- repo: https://github.com/python-jsonschema/check-jsonschema
|
||||
rev: 0.31.2
|
||||
rev: 0.31.3
|
||||
hooks:
|
||||
- id: check-github-workflows
|
||||
|
||||
|
||||
@@ -1,5 +1,55 @@
|
||||
# Breaking Changes
|
||||
|
||||
## 0.11.0
|
||||
|
||||
This is a follow-up to release 0.10.0. Because of a mistake in the release process, the `requires-python` inference changes were not included in that release. Ruff 0.11.0 now includes this change as well as the stabilization of the preview behavior for `PGH004`.
|
||||
|
||||
- **Changes to how the Python version is inferred when a `target-version` is not specified** ([#16319](https://github.com/astral-sh/ruff/pull/16319))
|
||||
|
||||
In previous versions of Ruff, you could specify your Python version with:
|
||||
|
||||
- The `target-version` option in a `ruff.toml` file or the `[tool.ruff]` section of a pyproject.toml file.
|
||||
- The `project.requires-python` field in a `pyproject.toml` file with a `[tool.ruff]` section.
|
||||
|
||||
These options worked well in most cases, and are still recommended for fine control of the Python version. However, because of the way Ruff discovers config files, `pyproject.toml` files without a `[tool.ruff]` section would be ignored, including the `requires-python` setting. Ruff would then use the default Python version (3.9 as of this writing) instead, which is surprising when you've attempted to request another version.
|
||||
|
||||
In v0.10, config discovery has been updated to address this issue:
|
||||
|
||||
- If Ruff finds a `ruff.toml` file without a `target-version`, it will check
|
||||
for a `pyproject.toml` file in the same directory and respect its
|
||||
`requires-python` version, even if it does not contain a `[tool.ruff]`
|
||||
section.
|
||||
- If Ruff finds a user-level configuration, the `requires-python` field of the closest `pyproject.toml` in a parent directory will take precedence.
|
||||
- If there is no config file (`ruff.toml`or `pyproject.toml` with a
|
||||
`[tool.ruff]` section) in the directory of the file being checked, Ruff will
|
||||
search for the closest `pyproject.toml` in the parent directories and use its
|
||||
`requires-python` setting.
|
||||
|
||||
## 0.10.0
|
||||
|
||||
- **Changes to how the Python version is inferred when a `target-version` is not specified** ([#16319](https://github.com/astral-sh/ruff/pull/16319))
|
||||
|
||||
Because of a mistake in the release process, the `requires-python` inference changes are not included in this release and instead shipped as part of 0.11.0.
|
||||
You can find a description of this change in the 0.11.0 section.
|
||||
|
||||
- **Updated `TYPE_CHECKING` behavior** ([#16669](https://github.com/astral-sh/ruff/pull/16669))
|
||||
|
||||
Previously, Ruff only recognized typechecking blocks that tested the `typing.TYPE_CHECKING` symbol. Now, Ruff recognizes any local variable named `TYPE_CHECKING`. This release also removes support for the legacy `if 0:` and `if False:` typechecking checks. Use a local `TYPE_CHECKING` variable instead.
|
||||
|
||||
- **More robust noqa parsing** ([#16483](https://github.com/astral-sh/ruff/pull/16483))
|
||||
|
||||
The syntax for both file-level and in-line suppression comments has been unified and made more robust to certain errors. In most cases, this will result in more suppression comments being read by Ruff, but there are a few instances where previously read comments will now log an error to the user instead. Please refer to the documentation on [_Error suppression_](https://docs.astral.sh/ruff/linter/#error-suppression) for the full specification.
|
||||
|
||||
- **Avoid unnecessary parentheses around with statements with a single context manager and a trailing comment** ([#14005](https://github.com/astral-sh/ruff/pull/14005))
|
||||
|
||||
This change fixes a bug in the formatter where it introduced unnecessary parentheses around with statements with a single context manager and a trailing comment. This change may result in a change in formatting for some users.
|
||||
|
||||
- **Bump alpine default tag to 3.21 for derived Docker images** ([#16456](https://github.com/astral-sh/ruff/pull/16456))
|
||||
|
||||
Alpine 3.21 was released in Dec 2024 and is used in the official Alpine-based Python images. Now the ruff:alpine image will use 3.21 instead of 3.20 and ruff:alpine3.20 will no longer be updated.
|
||||
|
||||
- **\[`unsafe-markup-use`\]: `RUF035` has been recoded to `S704`** ([#15957](https://github.com/astral-sh/ruff/pull/15957))
|
||||
|
||||
## 0.9.0
|
||||
|
||||
Ruff now formats your code according to the 2025 style guide. As a result, your code might now get formatted differently. See the [changelog](./CHANGELOG.md#090) for a detailed list of changes.
|
||||
|
||||
176
CHANGELOG.md
176
CHANGELOG.md
@@ -1,5 +1,181 @@
|
||||
# Changelog
|
||||
|
||||
## 0.11.2
|
||||
|
||||
### Preview features
|
||||
|
||||
- [syntax-errors] Fix false-positive syntax errors emitted for annotations on variadic parameters before Python 3.11 ([#16878](https://github.com/astral-sh/ruff/pull/16878))
|
||||
|
||||
## 0.11.1
|
||||
|
||||
### Preview features
|
||||
|
||||
- \[`airflow`\] Add `chain`, `chain_linear` and `cross_downstream` for `AIR302` ([#16647](https://github.com/astral-sh/ruff/pull/16647))
|
||||
- [syntax-errors] Improve error message and range for pre-PEP-614 decorator syntax errors ([#16581](https://github.com/astral-sh/ruff/pull/16581))
|
||||
- [syntax-errors] PEP 701 f-strings before Python 3.12 ([#16543](https://github.com/astral-sh/ruff/pull/16543))
|
||||
- [syntax-errors] Parenthesized context managers before Python 3.9 ([#16523](https://github.com/astral-sh/ruff/pull/16523))
|
||||
- [syntax-errors] Star annotations before Python 3.11 ([#16545](https://github.com/astral-sh/ruff/pull/16545))
|
||||
- [syntax-errors] Star expression in index before Python 3.11 ([#16544](https://github.com/astral-sh/ruff/pull/16544))
|
||||
- [syntax-errors] Unparenthesized assignment expressions in sets and indexes ([#16404](https://github.com/astral-sh/ruff/pull/16404))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- Server: Allow `FixAll` action in presence of version-specific syntax errors ([#16848](https://github.com/astral-sh/ruff/pull/16848))
|
||||
- \[`flake8-bandit`\] Allow raw strings in `suspicious-mark-safe-usage` (`S308`) #16702 ([#16770](https://github.com/astral-sh/ruff/pull/16770))
|
||||
- \[`refurb`\] Avoid panicking `unwrap` in `verbose-decimal-constructor` (`FURB157`) ([#16777](https://github.com/astral-sh/ruff/pull/16777))
|
||||
- \[`refurb`\] Fix starred expressions fix (`FURB161`) ([#16550](https://github.com/astral-sh/ruff/pull/16550))
|
||||
- Fix `--statistics` reporting for unsafe fixes ([#16756](https://github.com/astral-sh/ruff/pull/16756))
|
||||
|
||||
### Rule changes
|
||||
|
||||
- \[`flake8-executables`\] Allow `uv run` in shebang line for `shebang-missing-python` (`EXE003`) ([#16849](https://github.com/astral-sh/ruff/pull/16849),[#16855](https://github.com/astral-sh/ruff/pull/16855))
|
||||
|
||||
### CLI
|
||||
|
||||
- Add `--exit-non-zero-on-format` ([#16009](https://github.com/astral-sh/ruff/pull/16009))
|
||||
|
||||
### Documentation
|
||||
|
||||
- Update Ruff tutorial to avoid non-existent fix in `__init__.py` ([#16818](https://github.com/astral-sh/ruff/pull/16818))
|
||||
- \[`flake8-gettext`\] Swap `format-` and `printf-in-get-text-func-call` examples (`INT002`, `INT003`) ([#16769](https://github.com/astral-sh/ruff/pull/16769))
|
||||
|
||||
## 0.11.0
|
||||
|
||||
This is a follow-up to release 0.10.0. Because of a mistake in the release process, the `requires-python` inference changes were not included in that release. Ruff 0.11.0 now includes this change as well as the stabilization of the preview behavior for `PGH004`.
|
||||
|
||||
### Breaking changes
|
||||
|
||||
- **Changes to how the Python version is inferred when a `target-version` is not specified** ([#16319](https://github.com/astral-sh/ruff/pull/16319))
|
||||
|
||||
In previous versions of Ruff, you could specify your Python version with:
|
||||
|
||||
- The `target-version` option in a `ruff.toml` file or the `[tool.ruff]` section of a pyproject.toml file.
|
||||
- The `project.requires-python` field in a `pyproject.toml` file with a `[tool.ruff]` section.
|
||||
|
||||
These options worked well in most cases, and are still recommended for fine control of the Python version. However, because of the way Ruff discovers config files, `pyproject.toml` files without a `[tool.ruff]` section would be ignored, including the `requires-python` setting. Ruff would then use the default Python version (3.9 as of this writing) instead, which is surprising when you've attempted to request another version.
|
||||
|
||||
In v0.10, config discovery has been updated to address this issue:
|
||||
|
||||
- If Ruff finds a `ruff.toml` file without a `target-version`, it will check
|
||||
for a `pyproject.toml` file in the same directory and respect its
|
||||
`requires-python` version, even if it does not contain a `[tool.ruff]`
|
||||
section.
|
||||
- If Ruff finds a user-level configuration, the `requires-python` field of the closest `pyproject.toml` in a parent directory will take precedence.
|
||||
- If there is no config file (`ruff.toml`or `pyproject.toml` with a
|
||||
`[tool.ruff]` section) in the directory of the file being checked, Ruff will
|
||||
search for the closest `pyproject.toml` in the parent directories and use its
|
||||
`requires-python` setting.
|
||||
|
||||
### Stabilization
|
||||
|
||||
The following behaviors have been stabilized:
|
||||
|
||||
- [`blanket-noqa`](https://docs.astral.sh/ruff/rules/blanket-noqa/) (`PGH004`): Also detect blanked file-level noqa comments (and not just line level comments).
|
||||
|
||||
### Preview features
|
||||
|
||||
- [syntax-errors] Tuple unpacking in `for` statement iterator clause before Python 3.9 ([#16558](https://github.com/astral-sh/ruff/pull/16558))
|
||||
|
||||
## 0.10.0
|
||||
|
||||
Check out the [blog post](https://astral.sh/blog/ruff-v0.10.0) for a migration guide and overview of the changes!
|
||||
|
||||
### Breaking changes
|
||||
|
||||
See also, the "Remapped rules" section which may result in disabled rules.
|
||||
|
||||
- **Changes to how the Python version is inferred when a `target-version` is not specified** ([#16319](https://github.com/astral-sh/ruff/pull/16319))
|
||||
|
||||
Because of a mistake in the release process, the `requires-python` inference changes are not included in this release and instead shipped as part of 0.11.0.
|
||||
You can find a description of this change in the 0.11.0 section.
|
||||
|
||||
- **Updated `TYPE_CHECKING` behavior** ([#16669](https://github.com/astral-sh/ruff/pull/16669))
|
||||
|
||||
Previously, Ruff only recognized typechecking blocks that tested the `typing.TYPE_CHECKING` symbol. Now, Ruff recognizes any local variable named `TYPE_CHECKING`. This release also removes support for the legacy `if 0:` and `if False:` typechecking checks. Use a local `TYPE_CHECKING` variable instead.
|
||||
|
||||
- **More robust noqa parsing** ([#16483](https://github.com/astral-sh/ruff/pull/16483))
|
||||
|
||||
The syntax for both file-level and in-line suppression comments has been unified and made more robust to certain errors. In most cases, this will result in more suppression comments being read by Ruff, but there are a few instances where previously read comments will now log an error to the user instead. Please refer to the documentation on [*Error suppression*](https://docs.astral.sh/ruff/linter/#error-suppression) for the full specification.
|
||||
|
||||
- **Avoid unnecessary parentheses around with statements with a single context manager and a trailing comment** ([#14005](https://github.com/astral-sh/ruff/pull/14005))
|
||||
|
||||
This change fixes a bug in the formatter where it introduced unnecessary parentheses around with statements with a single context manager and a trailing comment. This change may result in a change in formatting for some users.
|
||||
|
||||
- **Bump alpine default tag to 3.21 for derived Docker images** ([#16456](https://github.com/astral-sh/ruff/pull/16456))
|
||||
|
||||
Alpine 3.21 was released in Dec 2024 and is used in the official Alpine-based Python images. Now the ruff:alpine image will use 3.21 instead of 3.20 and ruff:alpine3.20 will no longer be updated.
|
||||
|
||||
### Deprecated Rules
|
||||
|
||||
The following rules have been deprecated:
|
||||
|
||||
- [`non-pep604-isinstance`](https://docs.astral.sh/ruff/rules/non-pep604-isinstance/) (`UP038`)
|
||||
- [`suspicious-xmle-tree-usage`](https://docs.astral.sh/ruff/rules/suspicious-xmle-tree-usage/) (`S320`)
|
||||
|
||||
### Remapped rules
|
||||
|
||||
The following rules have been remapped to new rule codes:
|
||||
|
||||
- \[`unsafe-markup-use`\]: `RUF035` to `S704`
|
||||
|
||||
### Stabilization
|
||||
|
||||
The following rules have been stabilized and are no longer in preview:
|
||||
|
||||
- [`batched-without-explicit-strict`](https://docs.astral.sh/ruff/rules/batched-without-explicit-strict) (`B911`)
|
||||
- [`unnecessary-dict-comprehension-for-iterable`](https://docs.astral.sh/ruff/rules/unnecessary-dict-comprehension-for-iterable) (`C420`)
|
||||
- [`datetime-min-max`](https://docs.astral.sh/ruff/rules/datetime-min-max) (`DTZ901`)
|
||||
- [`fast-api-unused-path-parameter`](https://docs.astral.sh/ruff/rules/fast-api-unused-path-parameter) (`FAST003`)
|
||||
- [`root-logger-call`](https://docs.astral.sh/ruff/rules/root-logger-call) (`LOG015`)
|
||||
- [`len-test`](https://docs.astral.sh/ruff/rules/len-test) (`PLC1802`)
|
||||
- [`shallow-copy-environ`](https://docs.astral.sh/ruff/rules/shallow-copy-environ) (`PLW1507`)
|
||||
- [`os-listdir`](https://docs.astral.sh/ruff/rules/os-listdir) (`PTH208`)
|
||||
- [`invalid-pathlib-with-suffix`](https://docs.astral.sh/ruff/rules/invalid-pathlib-with-suffix) (`PTH210`)
|
||||
- [`invalid-assert-message-literal-argument`](https://docs.astral.sh/ruff/rules/invalid-assert-message-literal-argument) (`RUF040`)
|
||||
- [`unnecessary-nested-literal`](https://docs.astral.sh/ruff/rules/unnecessary-nested-literal) (`RUF041`)
|
||||
- [`unnecessary-cast-to-int`](https://docs.astral.sh/ruff/rules/unnecessary-cast-to-int) (`RUF046`)
|
||||
- [`map-int-version-parsing`](https://docs.astral.sh/ruff/rules/map-int-version-parsing) (`RUF048`)
|
||||
- [`if-key-in-dict-del`](https://docs.astral.sh/ruff/rules/if-key-in-dict-del) (`RUF051`)
|
||||
- [`unsafe-markup-use`](https://docs.astral.sh/ruff/rules/unsafe-markup-use) (`S704`). This rule has also been renamed from `RUF035`.
|
||||
- [`split-static-string`](https://docs.astral.sh/ruff/rules/split-static-string) (`SIM905`)
|
||||
- [`runtime-cast-value`](https://docs.astral.sh/ruff/rules/runtime-cast-value) (`TC006`)
|
||||
- [`unquoted-type-alias`](https://docs.astral.sh/ruff/rules/unquoted-type-alias) (`TC007`)
|
||||
- [`non-pep646-unpack`](https://docs.astral.sh/ruff/rules/non-pep646-unpack) (`UP044`)
|
||||
|
||||
The following behaviors have been stabilized:
|
||||
|
||||
- [`bad-staticmethod-argument`](https://docs.astral.sh/ruff/rules/bad-staticmethod-argument/) (`PLW0211`) [`invalid-first-argument-name-for-class-method`](https://docs.astral.sh/ruff/rules/invalid-first-argument-name-for-class-method/) (`N804`): `__new__` methods are now no longer flagged by `invalid-first-argument-name-for-class-method` (`N804`) but instead by `bad-staticmethod-argument` (`PLW0211`)
|
||||
- [`bad-str-strip-call`](https://docs.astral.sh/ruff/rules/bad-str-strip-call/) (`PLE1310`): The rule now applies to objects which are known to have type `str` or `bytes`.
|
||||
- [`custom-type-var-for-self`](https://docs.astral.sh/ruff/rules/custom-type-var-for-self/) (`PYI019`): More accurate detection of custom `TypeVars` replaceable by `Self`. The range of the diagnostic is now the full function header rather than just the return annotation.
|
||||
- [`invalid-argument-name`](https://docs.astral.sh/ruff/rules/invalid-argument-name/) (`N803`): Ignore argument names of functions decorated with `typing.override`
|
||||
- [`invalid-envvar-default`](https://docs.astral.sh/ruff/rules/invalid-envvar-default/) (`PLW1508`): Detect default value arguments to `os.environ.get` with invalid type.
|
||||
- [`pytest-raises-with-multiple-statements`](https://docs.astral.sh/ruff/rules/pytest-raises-with-multiple-statements/) (`PT012`) [`pytest-warns-with-multiple-statements`](https://docs.astral.sh/ruff/rules/pytest-warns-with-multiple-statements/) (`PT031`): Allow `for` statements with an empty body in `pytest.raises` and `pytest.warns` `with` statements.
|
||||
- [`redundant-open-modes`](https://docs.astral.sh/ruff/rules/redundant-open-modes/) (`UP015`): The diagnostic range is now the range of the redundant mode argument where it previously was the range of the entire open call. You may have to replace your `noqa` comments when suppressing `UP015`.
|
||||
- [`stdlib-module-shadowing`](https://docs.astral.sh/ruff/rules/stdlib-module-shadowing/) (`A005`): Changes the default value of `lint.flake8-builtins.strict-checking` from `true` to `false`.
|
||||
- [`type-none-comparison`](https://docs.astral.sh/ruff/rules/type-none-comparison/) (`FURB169`): Now also recognizes `type(expr) is type(None)` comparisons where `expr` isn't a name expression.
|
||||
|
||||
The following fixes or improvements to fixes have been stabilized:
|
||||
|
||||
- [`repeated-equality-comparison`](https://docs.astral.sh/ruff/rules/repeated-equality-comparison/) (`PLR1714`) ([#16685](https://github.com/astral-sh/ruff/pull/16685))
|
||||
- [`needless-bool`](https://docs.astral.sh/ruff/rules/needless-bool/) (`SIM103`) ([#16684](https://github.com/astral-sh/ruff/pull/16684))
|
||||
- [`unused-private-type-var`](https://docs.astral.sh/ruff/rules/unused-private-type-var/) (`PYI018`) ([#16682](https://github.com/astral-sh/ruff/pull/16682))
|
||||
|
||||
### Server
|
||||
|
||||
- Remove logging output for `ruff.printDebugInformation` ([#16617](https://github.com/astral-sh/ruff/pull/16617))
|
||||
|
||||
### Configuration
|
||||
|
||||
- \[`flake8-builtins`\] Deprecate the `builtins-` prefixed options in favor of the unprefixed options (e.g. `builtins-allowed-modules` is now deprecated in favor of `allowed-modules`) ([#16092](https://github.com/astral-sh/ruff/pull/16092))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- [flake8-bandit] Fix mixed-case hash algorithm names (S324) ([#16552](https://github.com/astral-sh/ruff/pull/16552))
|
||||
|
||||
### CLI
|
||||
|
||||
- [ruff] Fix `last_tag`/`commits_since_last_tag` for `version` command ([#16686](https://github.com/astral-sh/ruff/pull/16686))
|
||||
|
||||
## 0.9.10
|
||||
|
||||
### Preview features
|
||||
|
||||
449
Cargo.lock
generated
449
Cargo.lock
generated
@@ -46,9 +46,13 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
|
||||
|
||||
[[package]]
|
||||
name = "annotate-snippets"
|
||||
version = "0.6.1"
|
||||
version = "0.11.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c7021ce4924a3f25f802b2cccd1af585e39ea1a363a1aa2e72afe54b67a3a7a7"
|
||||
checksum = "710e8eae58854cdc1790fcb56cca04d712a17be849eeb81da2a724bf4bae2bc4"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"unicode-width 0.2.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
@@ -268,9 +272,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.11"
|
||||
version = "1.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e4730490333d58093109dc02c23174c3f4d490998c3fed3cc8e82d57afedb9cf"
|
||||
checksum = "be714c154be609ec7f5dad223a33bf1482fff90472de28f7362806e6d4832b8c"
|
||||
dependencies = [
|
||||
"jobserver",
|
||||
"libc",
|
||||
@@ -289,15 +293,6 @@ version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
||||
|
||||
[[package]]
|
||||
name = "chic"
|
||||
version = "1.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a5b5db619f3556839cb2223ae86ff3f9a09da2c5013be42bc9af08c9589bf70c"
|
||||
dependencies = [
|
||||
"annotate-snippets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.40"
|
||||
@@ -339,9 +334,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.31"
|
||||
version = "4.5.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "027bb0d98429ae334a8698531da7077bdf906419543a35a55c2cb1b66437d767"
|
||||
checksum = "6088f3ae8c3608d19260cd7445411865a485688711b78b5be70d78cd96136f83"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
@@ -349,9 +344,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.31"
|
||||
version = "4.5.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5589e0cba072e0f3d23791efac0fd8627b49c829c196a492e88168e6a669d863"
|
||||
checksum = "22a7ef7f676155edfb82daa97f99441f3ebf4a58d5e32f295a56259f1b6facc8"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
@@ -362,9 +357,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_complete"
|
||||
version = "4.5.44"
|
||||
version = "4.5.46"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "375f9d8255adeeedd51053574fd8d4ba875ea5fa558e86617b07f09f1680c8b6"
|
||||
checksum = "f5c5508ea23c5366f77e53f5a0070e5a84e51687ec3ef9e0464c86dc8d13ce98"
|
||||
dependencies = [
|
||||
"clap",
|
||||
]
|
||||
@@ -392,9 +387,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.5.28"
|
||||
version = "4.5.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bf4ced95c6f4a675af3da73304b9ac4ed991640c36374e4b46795c49e17cf1ed"
|
||||
checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
@@ -423,9 +418,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "codspeed"
|
||||
version = "2.8.1"
|
||||
version = "2.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "de4b67ff8985f3993f06167d71cf4aec178b0a1580f91a987170c59d60021103"
|
||||
checksum = "60e744216bfa9add3b1f2505587cbbb837923232ed10963609f4a6e3cbd99c3e"
|
||||
dependencies = [
|
||||
"colored 2.2.0",
|
||||
"libc",
|
||||
@@ -436,13 +431,38 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "codspeed-criterion-compat"
|
||||
version = "2.8.1"
|
||||
version = "2.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68403d768ed1def18a87e2306676781314448393ecf0d3057c4527cabf524a3d"
|
||||
checksum = "d5926ca63222a35b9a2299adcaafecf596efe20a9a2048e4a81cb2fc3463b4a8"
|
||||
dependencies = [
|
||||
"codspeed",
|
||||
"codspeed-criterion-compat-walltime",
|
||||
"colored 2.2.0",
|
||||
"criterion",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "codspeed-criterion-compat-walltime"
|
||||
version = "2.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dbae4da05076cbc673e242400ac8f4353bdb686e48020edc6e36a5c36ae0878e"
|
||||
dependencies = [
|
||||
"anes",
|
||||
"cast",
|
||||
"ciborium",
|
||||
"clap",
|
||||
"codspeed",
|
||||
"criterion-plot",
|
||||
"is-terminal",
|
||||
"itertools 0.10.5",
|
||||
"num-traits",
|
||||
"once_cell",
|
||||
"oorandom",
|
||||
"regex",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"tinytemplate",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -458,7 +478,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -467,7 +487,7 @@ version = "3.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fde0e0ec90c9dfb3b4b1a0891a7dcd0e2bffde2f7efed5fe7c9bb00e5bfb915e"
|
||||
dependencies = [
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -475,6 +495,20 @@ name = "compact_str"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b79c4069c6cad78e2e0cdfcbd26275770669fb39fd308a752dc110e83b9af32"
|
||||
dependencies = [
|
||||
"castaway",
|
||||
"cfg-if",
|
||||
"itoa",
|
||||
"rustversion",
|
||||
"ryu",
|
||||
"static_assertions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "compact_str"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3fdb1325a1cece981e8a296ab8f0f9b63ae357bd0784a9faaf548cc7b480707a"
|
||||
dependencies = [
|
||||
"castaway",
|
||||
"cfg-if",
|
||||
@@ -487,9 +521,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "console"
|
||||
version = "0.15.10"
|
||||
version = "0.15.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ea3c6ecd8059b57859df5c69830340ed3c41d30e3da0c1cbed90a96ac853041b"
|
||||
checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8"
|
||||
dependencies = [
|
||||
"encode_unicode",
|
||||
"libc",
|
||||
@@ -826,15 +860,15 @@ checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813"
|
||||
|
||||
[[package]]
|
||||
name = "dyn-clone"
|
||||
version = "1.0.18"
|
||||
version = "1.0.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "feeef44e73baff3a26d371801df019877a9866a8c493d315ab00177843314f35"
|
||||
checksum = "1c7a8fb8a9fbf66c1f703fe16184d10ca0ee9d23be5b4436400408ba54a95005"
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.13.0"
|
||||
version = "1.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
|
||||
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
|
||||
|
||||
[[package]]
|
||||
name = "encode_unicode"
|
||||
@@ -860,22 +894,22 @@ checksum = "c7f84e12ccf0a7ddc17a6c41c93326024c42920d7ee630d04950e6926645c0fe"
|
||||
|
||||
[[package]]
|
||||
name = "env_logger"
|
||||
version = "0.11.6"
|
||||
version = "0.11.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dcaee3d8e3cfc3fd92428d477bc97fc29ec8716d180c0d74c643bb26166660e0"
|
||||
checksum = "c3716d7a920fb4fac5d84e9d4bce8ceb321e9414b4409da61b07b75c1e3d0697"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
"env_filter",
|
||||
"humantime",
|
||||
"jiff",
|
||||
"log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.1"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
|
||||
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
@@ -884,7 +918,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -907,13 +941,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "etcetera"
|
||||
version = "0.8.0"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943"
|
||||
checksum = "26c7b13d0780cb82722fd59f6f57f925e143427e4a75313a6c77243bf5326ae6"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"home",
|
||||
"windows-sys 0.48.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -945,9 +979,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.0.35"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c"
|
||||
checksum = "11faaf5a5236997af9848be0bef4db95824b1d534ebc64d0f0c6cf3e67bd38dc"
|
||||
dependencies = [
|
||||
"crc32fast",
|
||||
"miniz_oxide",
|
||||
@@ -961,9 +995,9 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||
|
||||
[[package]]
|
||||
name = "foldhash"
|
||||
version = "0.1.4"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f"
|
||||
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
|
||||
|
||||
[[package]]
|
||||
name = "form_urlencoded"
|
||||
@@ -1081,9 +1115,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "half"
|
||||
version = "2.4.1"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888"
|
||||
checksum = "7db2ff139bba50379da6aa0766b52fdcb62cb5b263009b09ed58ba604e14bbd1"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"crunchy",
|
||||
@@ -1129,17 +1163,17 @@ checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.4.0"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc"
|
||||
checksum = "fbd780fe5cc30f81464441920d82ac8740e2e46b29a6fad543ddd075229ce37e"
|
||||
|
||||
[[package]]
|
||||
name = "home"
|
||||
version = "0.5.9"
|
||||
version = "0.5.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5"
|
||||
checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf"
|
||||
dependencies = [
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1151,12 +1185,6 @@ dependencies = [
|
||||
"utf8-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "humantime"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone"
|
||||
version = "0.1.61"
|
||||
@@ -1362,9 +1390,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.7.1"
|
||||
version = "2.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652"
|
||||
checksum = "3954d50fe15b02142bf25d3b8bdadb634ec3948f103d04ffe3031bc8fe9d7058"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown 0.15.2",
|
||||
@@ -1465,13 +1493,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "is-terminal"
|
||||
version = "0.4.15"
|
||||
version = "0.4.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e19b23d53f35ce9f56aebc7d1bb4e6ac1e9c0db7ac85c8d1760c04379edced37"
|
||||
checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9"
|
||||
dependencies = [
|
||||
"hermit-abi 0.4.0",
|
||||
"hermit-abi 0.5.0",
|
||||
"libc",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1519,9 +1547,33 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.14"
|
||||
version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674"
|
||||
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
|
||||
|
||||
[[package]]
|
||||
name = "jiff"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d699bc6dfc879fb1bf9bdff0d4c56f0884fc6f0d0eb0fba397a6d00cd9a6b85e"
|
||||
dependencies = [
|
||||
"jiff-static",
|
||||
"log",
|
||||
"portable-atomic",
|
||||
"portable-atomic-util",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jiff-static"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8d16e75759ee0aa64c57a56acbf43916987b20c77373cb7e808979e02b93c9f9"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jobserver"
|
||||
@@ -1576,17 +1628,17 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.170"
|
||||
version = "0.2.171"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "875b3680cb2f8f71bdcf9a30f38d48282f5d3c95cbf9b3fa57269bb5d5c06828"
|
||||
checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6"
|
||||
|
||||
[[package]]
|
||||
name = "libcst"
|
||||
version = "1.6.0"
|
||||
version = "1.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "649801a698a649791541a3125d396d5db065ed7cea53faca3652b0179394922a"
|
||||
checksum = "ad9e315e3f679e61b9095ffd5e509de78b8a4ea3bba9d772f6fb243209f808d4"
|
||||
dependencies = [
|
||||
"chic",
|
||||
"annotate-snippets",
|
||||
"libcst_derive",
|
||||
"memchr",
|
||||
"paste",
|
||||
@@ -1597,9 +1649,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "libcst_derive"
|
||||
version = "1.6.0"
|
||||
version = "1.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3bf66548c351bcaed792ef3e2b430cc840fbde504e09da6b29ed114ca60dcd4b"
|
||||
checksum = "bfa96ed35d0dccc67cf7ba49350cb86de3dcb1d072a7ab28f99117f19d874953"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn 2.0.100",
|
||||
@@ -1651,10 +1703,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab"
|
||||
|
||||
[[package]]
|
||||
name = "litemap"
|
||||
version = "0.7.4"
|
||||
name = "linux-raw-sys"
|
||||
version = "0.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104"
|
||||
checksum = "fe7db12097d22ec582439daf8618b8fdd1a7bef6270e9af3b1ebcd30893cf413"
|
||||
|
||||
[[package]]
|
||||
name = "litemap"
|
||||
version = "0.7.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856"
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
@@ -1764,9 +1822,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.8.3"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8402cab7aefae129c6977bb0ff1b8fd9a04eb5b51efc50a70bea51cda0c7924"
|
||||
checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5"
|
||||
dependencies = [
|
||||
"adler2",
|
||||
]
|
||||
@@ -1897,15 +1955,15 @@ checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3"
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.20.2"
|
||||
version = "1.21.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
|
||||
checksum = "d75b0bedcc4fe52caa0e03d9f1151a323e4aa5e2d78ba3580400cd3c9e2bc4bc"
|
||||
|
||||
[[package]]
|
||||
name = "oorandom"
|
||||
version = "11.1.4"
|
||||
version = "11.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9"
|
||||
checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e"
|
||||
|
||||
[[package]]
|
||||
name = "option-ext"
|
||||
@@ -1915,9 +1973,9 @@ checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
|
||||
|
||||
[[package]]
|
||||
name = "ordermap"
|
||||
version = "0.5.5"
|
||||
version = "0.5.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c55fdf45a2b1e929e3656d404395767e05c98b6ebd8157eb31e370077d545160"
|
||||
checksum = "6e98f974336ceffd5b1b1f4fcbb89a23c8dcd334adc4b8612f11b7fa99944535"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
]
|
||||
@@ -1934,9 +1992,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "os_str_bytes"
|
||||
version = "7.0.0"
|
||||
version = "7.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7ac44c994af577c799b1b4bd80dc214701e349873ad894d6cdf96f4f7526e0b9"
|
||||
checksum = "c86e2db86dd008b4c88c77a9bb83d9286bf77204e255bb3fda3b2eebcae66b62"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
@@ -2008,9 +2066,9 @@ checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3"
|
||||
|
||||
[[package]]
|
||||
name = "peg"
|
||||
version = "0.8.4"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "295283b02df346d1ef66052a757869b2876ac29a6bb0ac3f5f7cd44aebe40e8f"
|
||||
checksum = "9928cfca101b36ec5163e70049ee5368a8a1c3c6efc9ca9c5f9cc2f816152477"
|
||||
dependencies = [
|
||||
"peg-macros",
|
||||
"peg-runtime",
|
||||
@@ -2018,9 +2076,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "peg-macros"
|
||||
version = "0.8.4"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bdad6a1d9cf116a059582ce415d5f5566aabcd4008646779dab7fdc2a9a9d426"
|
||||
checksum = "6298ab04c202fa5b5d52ba03269fb7b74550b150323038878fe6c372d8280f71"
|
||||
dependencies = [
|
||||
"peg-runtime",
|
||||
"proc-macro2",
|
||||
@@ -2029,9 +2087,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "peg-runtime"
|
||||
version = "0.8.3"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3aeb8f54c078314c2065ee649a7241f46b9d8e418e1a9581ba0546657d7aa3a"
|
||||
checksum = "132dca9b868d927b35b5dd728167b2dee150eb1ad686008fc71ccb298b776fca"
|
||||
|
||||
[[package]]
|
||||
name = "pep440_rs"
|
||||
@@ -2159,18 +2217,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pin-project"
|
||||
version = "1.1.9"
|
||||
version = "1.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dfe2e71e1471fe07709406bf725f710b02927c9c54b2b5b2ec0e8087d97c327d"
|
||||
checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a"
|
||||
dependencies = [
|
||||
"pin-project-internal",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-internal"
|
||||
version = "1.1.9"
|
||||
version = "1.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f6e859e6e5bd50440ab63c47e3ebabc90f26251f7c73c3d3e837b74a1cc3fa67"
|
||||
checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -2185,23 +2243,32 @@ checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
|
||||
|
||||
[[package]]
|
||||
name = "pkg-config"
|
||||
version = "0.3.31"
|
||||
version = "0.3.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2"
|
||||
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
|
||||
|
||||
[[package]]
|
||||
name = "portable-atomic"
|
||||
version = "1.10.0"
|
||||
version = "1.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "280dc24453071f1b63954171985a0b0d30058d287960968b9b2aca264c8d4ee6"
|
||||
checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e"
|
||||
|
||||
[[package]]
|
||||
name = "portable-atomic-util"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507"
|
||||
dependencies = [
|
||||
"portable-atomic",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.20"
|
||||
version = "0.2.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04"
|
||||
checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
|
||||
dependencies = [
|
||||
"zerocopy 0.7.35",
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2310,9 +2377,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.39"
|
||||
version = "1.0.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c1f1914ce909e1658d9907913b4b91947430c7d9be598b15a1912935b8c04801"
|
||||
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
@@ -2335,8 +2402,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94"
|
||||
dependencies = [
|
||||
"rand_chacha 0.9.0",
|
||||
"rand_core 0.9.0",
|
||||
"zerocopy 0.8.14",
|
||||
"rand_core 0.9.3",
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2356,7 +2423,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core 0.9.0",
|
||||
"rand_core 0.9.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2370,12 +2437,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.9.0"
|
||||
version = "0.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b08f3c9802962f7e1b25113931d94f43ed9725bebc59db9d0c3e9a23b67e15ff"
|
||||
checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
|
||||
dependencies = [
|
||||
"getrandom 0.3.1",
|
||||
"zerocopy 0.8.14",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2465,7 +2531,7 @@ dependencies = [
|
||||
"anyhow",
|
||||
"bitflags 2.9.0",
|
||||
"camino",
|
||||
"compact_str",
|
||||
"compact_str 0.9.0",
|
||||
"countme",
|
||||
"dir-test",
|
||||
"drop_bomb",
|
||||
@@ -2578,15 +2644,17 @@ dependencies = [
|
||||
"ruff_db",
|
||||
"ruff_notebook",
|
||||
"ruff_python_ast",
|
||||
"ruff_source_file",
|
||||
"ruff_text_size",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-test",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.5.8"
|
||||
version = "0.5.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834"
|
||||
checksum = "0b8c0c260b63a8219631167be35e6a988e9554dbd323f8bd08439c8ed1302bd1"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
]
|
||||
@@ -2659,7 +2727,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.9.10"
|
||||
version = "0.11.2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"argfile",
|
||||
@@ -2894,7 +2962,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_linter"
|
||||
version = "0.9.10"
|
||||
version = "0.11.2"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"anyhow",
|
||||
@@ -2989,7 +3057,7 @@ version = "0.0.0"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"bitflags 2.9.0",
|
||||
"compact_str",
|
||||
"compact_str 0.9.0",
|
||||
"is-macro",
|
||||
"itertools 0.14.0",
|
||||
"memchr",
|
||||
@@ -3085,7 +3153,7 @@ dependencies = [
|
||||
"anyhow",
|
||||
"bitflags 2.9.0",
|
||||
"bstr",
|
||||
"compact_str",
|
||||
"compact_str 0.9.0",
|
||||
"insta",
|
||||
"memchr",
|
||||
"ruff_annotate_snippets",
|
||||
@@ -3192,6 +3260,7 @@ dependencies = [
|
||||
"thiserror 2.0.12",
|
||||
"toml",
|
||||
"tracing",
|
||||
"tracing-log",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
@@ -3216,7 +3285,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_wasm"
|
||||
version = "0.9.10"
|
||||
version = "0.11.2"
|
||||
dependencies = [
|
||||
"console_error_panic_hook",
|
||||
"console_log",
|
||||
@@ -3309,35 +3378,49 @@ dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys 0.52.0",
|
||||
"linux-raw-sys 0.4.15",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f7178faa4b75a30e269c71e61c353ce2748cf3d76f0c44c393f4e60abf49b825"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys 0.9.3",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.19"
|
||||
version = "1.0.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4"
|
||||
checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2"
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.19"
|
||||
version = "1.0.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd"
|
||||
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
|
||||
|
||||
[[package]]
|
||||
name = "salsa"
|
||||
version = "0.18.0"
|
||||
source = "git+https://github.com/salsa-rs/salsa.git?rev=99be5d9917c3dd88e19735a82ef6bf39ba84bd7e#99be5d9917c3dd88e19735a82ef6bf39ba84bd7e"
|
||||
version = "0.19.0"
|
||||
source = "git+https://github.com/salsa-rs/salsa.git?rev=d758691ba17ee1a60c5356ea90888d529e1782ad#d758691ba17ee1a60c5356ea90888d529e1782ad"
|
||||
dependencies = [
|
||||
"boxcar",
|
||||
"compact_str",
|
||||
"compact_str 0.8.1",
|
||||
"crossbeam-queue",
|
||||
"dashmap 6.1.0",
|
||||
"hashbrown 0.15.2",
|
||||
"hashlink",
|
||||
"indexmap",
|
||||
"parking_lot",
|
||||
"portable-atomic",
|
||||
"rayon",
|
||||
"rustc-hash 2.1.1",
|
||||
"salsa-macro-rules",
|
||||
@@ -3348,13 +3431,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "salsa-macro-rules"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/salsa-rs/salsa.git?rev=99be5d9917c3dd88e19735a82ef6bf39ba84bd7e#99be5d9917c3dd88e19735a82ef6bf39ba84bd7e"
|
||||
version = "0.19.0"
|
||||
source = "git+https://github.com/salsa-rs/salsa.git?rev=d758691ba17ee1a60c5356ea90888d529e1782ad#d758691ba17ee1a60c5356ea90888d529e1782ad"
|
||||
|
||||
[[package]]
|
||||
name = "salsa-macros"
|
||||
version = "0.18.0"
|
||||
source = "git+https://github.com/salsa-rs/salsa.git?rev=99be5d9917c3dd88e19735a82ef6bf39ba84bd7e#99be5d9917c3dd88e19735a82ef6bf39ba84bd7e"
|
||||
version = "0.19.0"
|
||||
source = "git+https://github.com/salsa-rs/salsa.git?rev=d758691ba17ee1a60c5356ea90888d529e1782ad#d758691ba17ee1a60c5356ea90888d529e1782ad"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
@@ -3470,9 +3553,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_repr"
|
||||
version = "0.1.19"
|
||||
version = "0.1.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9"
|
||||
checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -3686,16 +3769,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.17.1"
|
||||
version = "3.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22e5a0acb1f3f55f65cc4a866c361b2fb2a0ff6366785ae6fbb5f85df07ba230"
|
||||
checksum = "488960f40a3fd53d72c2a29a58722561dee8afdd175bd88e3db4677d7b2ba600"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"fastrand",
|
||||
"getrandom 0.3.1",
|
||||
"once_cell",
|
||||
"rustix",
|
||||
"windows-sys 0.52.0",
|
||||
"rustix 1.0.2",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3709,11 +3791,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "terminal_size"
|
||||
version = "0.4.1"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5352447f921fda68cf61b4101566c0bdb5104eff6804d0678e5227580ab6a4e9"
|
||||
checksum = "45c6481c4829e4cc63825e62c49186a34538b7b2750b73b266581ffb612fb5ed"
|
||||
dependencies = [
|
||||
"rustix",
|
||||
"rustix 1.0.2",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
@@ -3869,9 +3951,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tinyvec"
|
||||
version = "1.8.1"
|
||||
version = "1.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "022db8904dfa342efe721985167e9fcd16c29b226db4397ed752a761cfce81e8"
|
||||
checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71"
|
||||
dependencies = [
|
||||
"tinyvec_macros",
|
||||
]
|
||||
@@ -3905,9 +3987,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.22.23"
|
||||
version = "0.22.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02a8b472d1a3d7c18e2d61a489aee3453fd9031c33e4f55bd533f4a7adca1bee"
|
||||
checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde",
|
||||
@@ -4033,9 +4115,9 @@ checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a"
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.17.0"
|
||||
version = "1.18.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
|
||||
checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f"
|
||||
|
||||
[[package]]
|
||||
name = "ucd-trie"
|
||||
@@ -4184,9 +4266,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.13.1"
|
||||
version = "1.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ced87ca4be083373936a67f8de945faa23b6b42384bd5b64434850802c6dccd0"
|
||||
checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9"
|
||||
dependencies = [
|
||||
"getrandom 0.3.1",
|
||||
"js-sys",
|
||||
@@ -4197,9 +4279,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "uuid-macro-internal"
|
||||
version = "1.13.1"
|
||||
version = "1.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d28dd23acb5f2fa7bd2155ab70b960e770596b3bb6395119b40476c3655dfba4"
|
||||
checksum = "72dcd78c4f979627a754f5522cea6e6a25e55139056535fe6e69c506cd64a862"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -4271,9 +4353,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wait-timeout"
|
||||
version = "0.2.0"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6"
|
||||
checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
@@ -4420,13 +4502,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "which"
|
||||
version = "7.0.1"
|
||||
version = "7.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fb4a9e33648339dc1642b0e36e21b3385e6148e289226f657c809dee59df5028"
|
||||
checksum = "2774c861e1f072b3aadc02f8ba886c26ad6321567ecc294c935434cad06f1283"
|
||||
dependencies = [
|
||||
"either",
|
||||
"env_home",
|
||||
"rustix",
|
||||
"rustix 0.38.44",
|
||||
"winsafe",
|
||||
]
|
||||
|
||||
@@ -4461,7 +4543,7 @@ version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
|
||||
dependencies = [
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4699,9 +4781,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.7.0"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7e49d2d35d3fad69b39b94139037ecfb4f359f08958b9c11e7315ce770462419"
|
||||
checksum = "0e97b544156e9bebe1a0ffbc03484fc1ffe3100cbce3ffb17eac35f7cdd7ab36"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
@@ -4765,39 +4847,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.7.35"
|
||||
version = "0.8.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
|
||||
checksum = "fd97444d05a4328b90e75e503a34bad781f14e28a823ad3557f0750df1ebcbc6"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"zerocopy-derive 0.7.35",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.8.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a367f292d93d4eab890745e75a778da40909cab4d6ff8173693812f79c4a2468"
|
||||
dependencies = [
|
||||
"zerocopy-derive 0.8.14",
|
||||
"zerocopy-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.7.35"
|
||||
version = "0.8.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.8.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3931cb58c62c13adec22e38686b559c86a30565e16ad6e8510a337cedc611e1"
|
||||
checksum = "6352c01d0edd5db859a63e2605f4ea3183ddbd15e2c4a9e7d32184df75e4f154"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -4806,18 +4867,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zerofrom"
|
||||
version = "0.1.5"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e"
|
||||
checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5"
|
||||
dependencies = [
|
||||
"zerofrom-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerofrom-derive"
|
||||
version = "0.1.5"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808"
|
||||
checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -4881,9 +4942,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zstd-sys"
|
||||
version = "2.0.13+zstd.1.5.6"
|
||||
version = "2.0.14+zstd.1.5.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "38ff0f21cfee8f97d94cef41359e0c89aa6113028ab0291aa8ca0038995a95aa"
|
||||
checksum = "8fb060d4926e4ac3a3ad15d864e99ceb5f343c6b34f5bd6d81ae6ed417311be5"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"pkg-config",
|
||||
|
||||
13
Cargo.toml
13
Cargo.toml
@@ -63,7 +63,7 @@ colored = { version = "3.0.0" }
|
||||
console_error_panic_hook = { version = "0.1.7" }
|
||||
console_log = { version = "1.0.0" }
|
||||
countme = { version = "3.0.1" }
|
||||
compact_str = "0.8.0"
|
||||
compact_str = "0.9.0"
|
||||
criterion = { version = "0.5.1", default-features = false }
|
||||
crossbeam = { version = "0.8.4" }
|
||||
dashmap = { version = "6.0.1" }
|
||||
@@ -71,7 +71,7 @@ dir-test = { version = "0.4.0" }
|
||||
dunce = { version = "1.0.5" }
|
||||
drop_bomb = { version = "0.1.5" }
|
||||
env_logger = { version = "0.11.0" }
|
||||
etcetera = { version = "0.8.0" }
|
||||
etcetera = { version = "0.10.0" }
|
||||
fern = { version = "0.7.0" }
|
||||
filetime = { version = "0.2.23" }
|
||||
getrandom = { version = "0.3.1" }
|
||||
@@ -123,7 +123,7 @@ rayon = { version = "1.10.0" }
|
||||
regex = { version = "1.10.2" }
|
||||
rustc-hash = { version = "2.0.0" }
|
||||
# When updating salsa, make sure to also update the revision in `fuzz/Cargo.toml`
|
||||
salsa = { git = "https://github.com/salsa-rs/salsa.git", rev = "99be5d9917c3dd88e19735a82ef6bf39ba84bd7e" }
|
||||
salsa = { git = "https://github.com/salsa-rs/salsa.git", rev = "d758691ba17ee1a60c5356ea90888d529e1782ad" }
|
||||
schemars = { version = "0.8.16" }
|
||||
seahash = { version = "4.1.0" }
|
||||
serde = { version = "1.0.197", features = ["derive"] }
|
||||
@@ -154,6 +154,7 @@ toml = { version = "0.8.11" }
|
||||
tracing = { version = "0.1.40" }
|
||||
tracing-flame = { version = "0.2.0" }
|
||||
tracing-indicatif = { version = "0.3.6" }
|
||||
tracing-log = { version = "0.2.0" }
|
||||
tracing-subscriber = { version = "0.3.18", default-features = false, features = [
|
||||
"env-filter",
|
||||
"fmt",
|
||||
@@ -326,3 +327,9 @@ github-custom-job-permissions = { "build-docker" = { packages = "write", content
|
||||
install-updater = false
|
||||
# Path that installers should place binaries in
|
||||
install-path = ["$XDG_BIN_HOME/", "$XDG_DATA_HOME/../bin", "~/.local/bin"]
|
||||
# Temporarily allow changes to the `release` workflow, in which we pin actions
|
||||
# to a SHA instead of a tag (https://github.com/astral-sh/uv/issues/12253)
|
||||
allow-dirty = ["ci"]
|
||||
|
||||
[workspace.metadata.dist.github-custom-runners]
|
||||
global = "depot-ubuntu-latest-4"
|
||||
|
||||
@@ -149,8 +149,8 @@ curl -LsSf https://astral.sh/ruff/install.sh | sh
|
||||
powershell -c "irm https://astral.sh/ruff/install.ps1 | iex"
|
||||
|
||||
# For a specific version.
|
||||
curl -LsSf https://astral.sh/ruff/0.9.10/install.sh | sh
|
||||
powershell -c "irm https://astral.sh/ruff/0.9.10/install.ps1 | iex"
|
||||
curl -LsSf https://astral.sh/ruff/0.11.2/install.sh | sh
|
||||
powershell -c "irm https://astral.sh/ruff/0.11.2/install.ps1 | iex"
|
||||
```
|
||||
|
||||
You can also install Ruff via [Homebrew](https://formulae.brew.sh/formula/ruff), [Conda](https://anaconda.org/conda-forge/ruff),
|
||||
@@ -183,7 +183,7 @@ Ruff can also be used as a [pre-commit](https://pre-commit.com/) hook via [`ruff
|
||||
```yaml
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: v0.9.10
|
||||
rev: v0.11.2
|
||||
hooks:
|
||||
# Run the linter.
|
||||
- id: ruff
|
||||
|
||||
61
crates/red_knot/docs/mypy_primer.md
Normal file
61
crates/red_knot/docs/mypy_primer.md
Normal file
@@ -0,0 +1,61 @@
|
||||
# Running `mypy_primer`
|
||||
|
||||
## Basics
|
||||
|
||||
For now, we use our own [fork of mypy primer]. It can be run using `uvx --from "…" mypy_primer`. For example, to see the help message, run:
|
||||
|
||||
```sh
|
||||
uvx --from "git+https://github.com/astral-sh/mypy_primer.git@add-red-knot-support" mypy_primer -h
|
||||
```
|
||||
|
||||
Alternatively, you can install the forked version of `mypy_primer` using:
|
||||
|
||||
```sh
|
||||
uv tool install "git+https://github.com/astral-sh/mypy_primer.git@add-red-knot-support"
|
||||
```
|
||||
|
||||
and then run it using `uvx mypy_primer` or just `mypy_primer`, if your `PATH` is set up accordingly (see: [Tool executables]).
|
||||
|
||||
## Showing the diagnostics diff between two Git revisions
|
||||
|
||||
To show the diagnostics diff between two Git revisions (e.g. your feature branch and `main`), run:
|
||||
|
||||
```sh
|
||||
mypy_primer \
|
||||
--type-checker knot \
|
||||
--old origin/main \
|
||||
--new my/feature \
|
||||
--debug \
|
||||
--output concise \
|
||||
--project-selector '/black$'
|
||||
```
|
||||
|
||||
This will show the diagnostics diff for the `black` project between the `main` branch and your `my/feature` branch. To run the
|
||||
diff for all projects, you currently need to copy the project-selector regex from the CI pipeline in `.github/workflows/mypy_primer.yaml`.
|
||||
|
||||
You can also take a look at the [full list of ecosystem projects]. Note that some of them might still need a `knot_paths` configuration
|
||||
option to work correctly.
|
||||
|
||||
## Avoiding recompilation
|
||||
|
||||
If you want to run `mypy_primer` repeatedly, e.g. for different projects, but for the same combination of `--old` and `--new`, you
|
||||
can use set the `MYPY_PRIMER_NO_REBUILD` environment variable to avoid recompilation of Red Knot:
|
||||
|
||||
```sh
|
||||
MYPY_PRIMER_NO_REBUILD=1 mypy_primer …
|
||||
```
|
||||
|
||||
## Running from a local copy of the repository
|
||||
|
||||
If you are working on a local branch, you can use `mypy_primer`'s `--repo` option to specify the path to your local copy of the `ruff` repository.
|
||||
This allows `mypy_primer` to check out local branches:
|
||||
|
||||
```sh
|
||||
mypy_primer --repo /path/to/ruff --old origin/main --new my/local-branch …
|
||||
```
|
||||
|
||||
Note that you might need to clean up `/tmp/mypy_primer` in order for this to work correctly.
|
||||
|
||||
[fork of mypy primer]: https://github.com/astral-sh/mypy_primer/tree/add-red-knot-support
|
||||
[full list of ecosystem projects]: https://github.com/astral-sh/mypy_primer/blob/add-red-knot-support/mypy_primer/projects.py
|
||||
[tool executables]: https://docs.astral.sh/uv/concepts/tools/#tool-executables
|
||||
@@ -50,6 +50,8 @@ pub(crate) struct CheckCommand {
|
||||
|
||||
/// Path to the Python installation from which Red Knot resolves type information and third-party dependencies.
|
||||
///
|
||||
/// If not specified, Red Knot will look at the `VIRTUAL_ENV` environment variable.
|
||||
///
|
||||
/// Red Knot will search in the path's `site-packages` directories for type information and
|
||||
/// third-party imports.
|
||||
///
|
||||
@@ -75,6 +77,14 @@ pub(crate) struct CheckCommand {
|
||||
#[clap(flatten)]
|
||||
pub(crate) rules: RulesArg,
|
||||
|
||||
/// The format to use for printing diagnostic messages.
|
||||
#[arg(long)]
|
||||
pub(crate) output_format: Option<OutputFormat>,
|
||||
|
||||
/// Control when colored output is used.
|
||||
#[arg(long, value_name = "WHEN")]
|
||||
pub(crate) color: Option<TerminalColor>,
|
||||
|
||||
/// Use exit code 1 if there are any warning-level diagnostics.
|
||||
#[arg(long, conflicts_with = "exit_zero", default_missing_value = "true", num_args=0..1)]
|
||||
pub(crate) error_on_warning: Option<bool>,
|
||||
@@ -117,6 +127,9 @@ impl CheckCommand {
|
||||
..EnvironmentOptions::default()
|
||||
}),
|
||||
terminal: Some(TerminalOptions {
|
||||
output_format: self
|
||||
.output_format
|
||||
.map(|output_format| RangedValue::cli(output_format.into())),
|
||||
error_on_warning: self.error_on_warning,
|
||||
}),
|
||||
rules,
|
||||
@@ -211,3 +224,46 @@ impl clap::Args for RulesArg {
|
||||
Self::augment_args(cmd)
|
||||
}
|
||||
}
|
||||
|
||||
/// The diagnostic output format.
|
||||
#[derive(Copy, Clone, Hash, Debug, PartialEq, Eq, PartialOrd, Ord, Default, clap::ValueEnum)]
|
||||
pub enum OutputFormat {
|
||||
/// Print diagnostics verbosely, with context and helpful hints.
|
||||
///
|
||||
/// Diagnostic messages may include additional context and
|
||||
/// annotations on the input to help understand the message.
|
||||
#[default]
|
||||
#[value(name = "full")]
|
||||
Full,
|
||||
/// Print diagnostics concisely, one per line.
|
||||
///
|
||||
/// This will guarantee that each diagnostic is printed on
|
||||
/// a single line. Only the most important or primary aspects
|
||||
/// of the diagnostic are included. Contextual information is
|
||||
/// dropped.
|
||||
#[value(name = "concise")]
|
||||
Concise,
|
||||
}
|
||||
|
||||
impl From<OutputFormat> for ruff_db::diagnostic::DiagnosticFormat {
|
||||
fn from(format: OutputFormat) -> ruff_db::diagnostic::DiagnosticFormat {
|
||||
match format {
|
||||
OutputFormat::Full => Self::Full,
|
||||
OutputFormat::Concise => Self::Concise,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Control when colored output is used.
|
||||
#[derive(Copy, Clone, Hash, Debug, PartialEq, Eq, PartialOrd, Ord, Default, clap::ValueEnum)]
|
||||
pub(crate) enum TerminalColor {
|
||||
/// Display colors if the output goes to an interactive terminal.
|
||||
#[default]
|
||||
Auto,
|
||||
|
||||
/// Always display colors.
|
||||
Always,
|
||||
|
||||
/// Never display colors.
|
||||
Never,
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ use std::process::{ExitCode, Termination};
|
||||
use anyhow::Result;
|
||||
use std::sync::Mutex;
|
||||
|
||||
use crate::args::{Args, CheckCommand, Command};
|
||||
use crate::args::{Args, CheckCommand, Command, TerminalColor};
|
||||
use crate::logging::setup_tracing;
|
||||
use anyhow::{anyhow, Context};
|
||||
use clap::Parser;
|
||||
@@ -76,10 +76,14 @@ pub(crate) fn version() -> Result<()> {
|
||||
}
|
||||
|
||||
fn run_check(args: CheckCommand) -> anyhow::Result<ExitStatus> {
|
||||
set_colored_override(args.color);
|
||||
|
||||
let verbosity = args.verbosity.level();
|
||||
countme::enable(verbosity.is_trace());
|
||||
let _guard = setup_tracing(verbosity)?;
|
||||
|
||||
tracing::debug!("Version: {}", version::version());
|
||||
|
||||
// The base path to which all CLI arguments are relative to.
|
||||
let cwd = {
|
||||
let cwd = std::env::current_dir().context("Failed to get the current working directory")?;
|
||||
@@ -255,15 +259,16 @@ impl MainLoop {
|
||||
result,
|
||||
revision: check_revision,
|
||||
} => {
|
||||
let terminal_settings = db.project().settings(db).terminal();
|
||||
let display_config = DisplayDiagnosticConfig::default()
|
||||
.format(terminal_settings.output_format)
|
||||
.color(colored::control::SHOULD_COLORIZE.should_colorize());
|
||||
|
||||
let min_error_severity =
|
||||
if db.project().settings(db).terminal().error_on_warning {
|
||||
Severity::Warning
|
||||
} else {
|
||||
Severity::Error
|
||||
};
|
||||
let min_error_severity = if terminal_settings.error_on_warning {
|
||||
Severity::Warning
|
||||
} else {
|
||||
Severity::Error
|
||||
};
|
||||
|
||||
if check_revision == revision {
|
||||
if db.project().files(db).is_empty() {
|
||||
@@ -360,3 +365,21 @@ enum MainLoopMessage {
|
||||
ApplyChanges(Vec<watch::ChangeEvent>),
|
||||
Exit,
|
||||
}
|
||||
|
||||
fn set_colored_override(color: Option<TerminalColor>) {
|
||||
let Some(color) = color else {
|
||||
return;
|
||||
};
|
||||
|
||||
match color {
|
||||
TerminalColor::Auto => {
|
||||
colored::control::unset_override();
|
||||
}
|
||||
TerminalColor::Always => {
|
||||
colored::control::set_override(true);
|
||||
}
|
||||
TerminalColor::Never => {
|
||||
colored::control::set_override(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,7 +86,7 @@ fn cli_arguments_are_relative_to_the_current_directory() -> anyhow::Result<()> {
|
||||
"libs/utils.py",
|
||||
r#"
|
||||
def add(a: int, b: int) -> int:
|
||||
a + b
|
||||
return a + b
|
||||
"#,
|
||||
),
|
||||
(
|
||||
@@ -158,7 +158,7 @@ fn paths_in_configuration_files_are_relative_to_the_project_root() -> anyhow::Re
|
||||
"libs/utils.py",
|
||||
r#"
|
||||
def add(a: int, b: int) -> int:
|
||||
a + b
|
||||
return a + b
|
||||
"#,
|
||||
),
|
||||
(
|
||||
@@ -219,7 +219,7 @@ fn configuration_rule_severity() -> anyhow::Result<()> {
|
||||
5 | x = a
|
||||
6 |
|
||||
7 | print(x) # possibly-unresolved-reference
|
||||
| - Name `x` used when possibly not defined
|
||||
| ^ Name `x` used when possibly not defined
|
||||
|
|
||||
|
||||
Found 2 diagnostics
|
||||
@@ -244,7 +244,7 @@ fn configuration_rule_severity() -> anyhow::Result<()> {
|
||||
--> <temp_dir>/test.py:2:5
|
||||
|
|
||||
2 | y = 4 / 0
|
||||
| ----- Cannot divide object of type `Literal[4]` by zero
|
||||
| ^^^^^ Cannot divide object of type `Literal[4]` by zero
|
||||
3 |
|
||||
4 | for a in range(0, y):
|
||||
|
|
||||
@@ -306,7 +306,7 @@ fn cli_rule_severity() -> anyhow::Result<()> {
|
||||
7 | x = a
|
||||
8 |
|
||||
9 | print(x) # possibly-unresolved-reference
|
||||
| - Name `x` used when possibly not defined
|
||||
| ^ Name `x` used when possibly not defined
|
||||
|
|
||||
|
||||
Found 3 diagnostics
|
||||
@@ -331,7 +331,7 @@ fn cli_rule_severity() -> anyhow::Result<()> {
|
||||
--> <temp_dir>/test.py:2:8
|
||||
|
|
||||
2 | import does_not_exit
|
||||
| ------------- Cannot resolve import `does_not_exit`
|
||||
| ^^^^^^^^^^^^^ Cannot resolve import `does_not_exit`
|
||||
3 |
|
||||
4 | y = 4 / 0
|
||||
|
|
||||
@@ -342,7 +342,7 @@ fn cli_rule_severity() -> anyhow::Result<()> {
|
||||
2 | import does_not_exit
|
||||
3 |
|
||||
4 | y = 4 / 0
|
||||
| ----- Cannot divide object of type `Literal[4]` by zero
|
||||
| ^^^^^ Cannot divide object of type `Literal[4]` by zero
|
||||
5 |
|
||||
6 | for a in range(0, y):
|
||||
|
|
||||
@@ -393,7 +393,7 @@ fn cli_rule_severity_precedence() -> anyhow::Result<()> {
|
||||
5 | x = a
|
||||
6 |
|
||||
7 | print(x) # possibly-unresolved-reference
|
||||
| - Name `x` used when possibly not defined
|
||||
| ^ Name `x` used when possibly not defined
|
||||
|
|
||||
|
||||
Found 2 diagnostics
|
||||
@@ -419,7 +419,7 @@ fn cli_rule_severity_precedence() -> anyhow::Result<()> {
|
||||
--> <temp_dir>/test.py:2:5
|
||||
|
|
||||
2 | y = 4 / 0
|
||||
| ----- Cannot divide object of type `Literal[4]` by zero
|
||||
| ^^^^^ Cannot divide object of type `Literal[4]` by zero
|
||||
3 |
|
||||
4 | for a in range(0, y):
|
||||
|
|
||||
@@ -456,7 +456,7 @@ fn configuration_unknown_rules() -> anyhow::Result<()> {
|
||||
|
|
||||
2 | [tool.knot.rules]
|
||||
3 | division-by-zer = "warn" # incorrect rule name
|
||||
| --------------- Unknown lint rule `division-by-zer`
|
||||
| ^^^^^^^^^^^^^^^ Unknown lint rule `division-by-zer`
|
||||
|
|
||||
|
||||
Found 1 diagnostic
|
||||
@@ -498,7 +498,7 @@ fn exit_code_only_warnings() -> anyhow::Result<()> {
|
||||
--> <temp_dir>/test.py:1:7
|
||||
|
|
||||
1 | print(x) # [unresolved-reference]
|
||||
| - Name `x` used when not defined
|
||||
| ^ Name `x` used when not defined
|
||||
|
|
||||
|
||||
Found 1 diagnostic
|
||||
@@ -528,7 +528,7 @@ fn exit_code_only_info() -> anyhow::Result<()> {
|
||||
|
|
||||
2 | from typing_extensions import reveal_type
|
||||
3 | reveal_type(1)
|
||||
| -------------- info: Revealed type is `Literal[1]`
|
||||
| ^^^^^^^^^^^^^^ Revealed type is `Literal[1]`
|
||||
|
|
||||
|
||||
Found 1 diagnostic
|
||||
@@ -558,7 +558,7 @@ fn exit_code_only_info_and_error_on_warning_is_true() -> anyhow::Result<()> {
|
||||
|
|
||||
2 | from typing_extensions import reveal_type
|
||||
3 | reveal_type(1)
|
||||
| -------------- info: Revealed type is `Literal[1]`
|
||||
| ^^^^^^^^^^^^^^ Revealed type is `Literal[1]`
|
||||
|
|
||||
|
||||
Found 1 diagnostic
|
||||
@@ -581,7 +581,7 @@ fn exit_code_no_errors_but_error_on_warning_is_true() -> anyhow::Result<()> {
|
||||
--> <temp_dir>/test.py:1:7
|
||||
|
|
||||
1 | print(x) # [unresolved-reference]
|
||||
| - Name `x` used when not defined
|
||||
| ^ Name `x` used when not defined
|
||||
|
|
||||
|
||||
Found 1 diagnostic
|
||||
@@ -613,7 +613,7 @@ fn exit_code_no_errors_but_error_on_warning_is_enabled_in_configuration() -> any
|
||||
--> <temp_dir>/test.py:1:7
|
||||
|
|
||||
1 | print(x) # [unresolved-reference]
|
||||
| - Name `x` used when not defined
|
||||
| ^ Name `x` used when not defined
|
||||
|
|
||||
|
||||
Found 1 diagnostic
|
||||
@@ -642,7 +642,7 @@ fn exit_code_both_warnings_and_errors() -> anyhow::Result<()> {
|
||||
--> <temp_dir>/test.py:2:7
|
||||
|
|
||||
2 | print(x) # [unresolved-reference]
|
||||
| - Name `x` used when not defined
|
||||
| ^ Name `x` used when not defined
|
||||
3 | print(4[1]) # [non-subscriptable]
|
||||
|
|
||||
|
||||
@@ -680,7 +680,7 @@ fn exit_code_both_warnings_and_errors_and_error_on_warning_is_true() -> anyhow::
|
||||
--> <temp_dir>/test.py:2:7
|
||||
|
|
||||
2 | print(x) # [unresolved-reference]
|
||||
| - Name `x` used when not defined
|
||||
| ^ Name `x` used when not defined
|
||||
3 | print(4[1]) # [non-subscriptable]
|
||||
|
|
||||
|
||||
@@ -718,7 +718,7 @@ fn exit_code_exit_zero_is_true() -> anyhow::Result<()> {
|
||||
--> <temp_dir>/test.py:2:7
|
||||
|
|
||||
2 | print(x) # [unresolved-reference]
|
||||
| - Name `x` used when not defined
|
||||
| ^ Name `x` used when not defined
|
||||
3 | print(4[1]) # [non-subscriptable]
|
||||
|
|
||||
|
||||
@@ -778,7 +778,7 @@ fn user_configuration() -> anyhow::Result<()> {
|
||||
--> <temp_dir>/project/main.py:2:5
|
||||
|
|
||||
2 | y = 4 / 0
|
||||
| ----- Cannot divide object of type `Literal[4]` by zero
|
||||
| ^^^^^ Cannot divide object of type `Literal[4]` by zero
|
||||
3 |
|
||||
4 | for a in range(0, y):
|
||||
|
|
||||
@@ -789,7 +789,7 @@ fn user_configuration() -> anyhow::Result<()> {
|
||||
5 | x = a
|
||||
6 |
|
||||
7 | print(x)
|
||||
| - Name `x` used when possibly not defined
|
||||
| ^ Name `x` used when possibly not defined
|
||||
|
|
||||
|
||||
Found 2 diagnostics
|
||||
@@ -820,7 +820,7 @@ fn user_configuration() -> anyhow::Result<()> {
|
||||
--> <temp_dir>/project/main.py:2:5
|
||||
|
|
||||
2 | y = 4 / 0
|
||||
| ----- Cannot divide object of type `Literal[4]` by zero
|
||||
| ^^^^^ Cannot divide object of type `Literal[4]` by zero
|
||||
3 |
|
||||
4 | for a in range(0, y):
|
||||
|
|
||||
@@ -967,6 +967,30 @@ fn check_non_existing_path() -> anyhow::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn concise_diagnostics() -> anyhow::Result<()> {
|
||||
let case = TestCase::with_file(
|
||||
"test.py",
|
||||
r#"
|
||||
print(x) # [unresolved-reference]
|
||||
print(4[1]) # [non-subscriptable]
|
||||
"#,
|
||||
)?;
|
||||
|
||||
assert_cmd_snapshot!(case.command().arg("--output-format=concise"), @r"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
warning[lint:unresolved-reference] <temp_dir>/test.py:2:7: Name `x` used when not defined
|
||||
error[lint:non-subscriptable] <temp_dir>/test.py:3:7: Cannot subscript object of type `Literal[4]` with no `__getitem__` method
|
||||
Found 2 diagnostics
|
||||
|
||||
----- stderr -----
|
||||
");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
struct TestCase {
|
||||
_temp_dir: TempDir,
|
||||
_settings_scope: SettingsBindDropGuard,
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
#![allow(clippy::disallowed_names)]
|
||||
|
||||
use std::collections::HashSet;
|
||||
use std::io::Write;
|
||||
use std::time::{Duration, Instant};
|
||||
@@ -16,7 +14,7 @@ use ruff_db::source::source_text;
|
||||
use ruff_db::system::{
|
||||
OsSystem, System, SystemPath, SystemPathBuf, UserConfigDirectoryOverrideGuard,
|
||||
};
|
||||
use ruff_db::Upcast;
|
||||
use ruff_db::{Db as _, Upcast};
|
||||
use ruff_python_ast::PythonVersion;
|
||||
|
||||
struct TestCase {
|
||||
@@ -1790,3 +1788,82 @@ fn changes_to_user_configuration() -> anyhow::Result<()> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Tests that renaming a file from `lib.py` to `Lib.py` is correctly reflected.
|
||||
///
|
||||
/// This test currently fails on case-insensitive systems because `Files` is case-sensitive
|
||||
/// but the `System::metadata` call isn't. This means that
|
||||
/// Red Knot considers both `Lib.py` and `lib.py` to exist when only `lib.py` does
|
||||
///
|
||||
/// The incoming change events then are no-ops because they don't change either file's
|
||||
/// status nor does it update their last modified time (renaming a file doesn't bump it's
|
||||
/// last modified timestamp).
|
||||
///
|
||||
/// Fixing this requires to either make `Files` case-insensitive and store the
|
||||
/// real-case path (if it differs) on `File` or make `Files` use a
|
||||
/// case-sensitive `System::metadata` call. This does open the question if all
|
||||
/// `System` calls should be case sensitive. This would be the most consistent
|
||||
/// but might be hard to pull off.
|
||||
///
|
||||
/// What the right solution is also depends on if Red Knot itself should be case
|
||||
/// sensitive or not. E.g. should `include="src"` be case sensitive on all systems
|
||||
/// or only on case-sensitive systems?
|
||||
///
|
||||
/// Lastly, whatever solution we pick must also work well with VS Code which,
|
||||
/// unfortunately ,doesn't propagate casing-only renames.
|
||||
/// <https://github.com/rust-lang/rust-analyzer/issues/9581>
|
||||
#[ignore]
|
||||
#[test]
|
||||
fn rename_files_casing_only() -> anyhow::Result<()> {
|
||||
let mut case = setup([("lib.py", "class Foo: ...")])?;
|
||||
|
||||
assert!(
|
||||
resolve_module(case.db(), &ModuleName::new("lib").unwrap()).is_some(),
|
||||
"Expected `lib` module to exist."
|
||||
);
|
||||
assert_eq!(
|
||||
resolve_module(case.db(), &ModuleName::new("Lib").unwrap()),
|
||||
None,
|
||||
"Expected `Lib` module not to exist"
|
||||
);
|
||||
|
||||
// Now rename `lib.py` to `Lib.py`
|
||||
if case.db().system().case_sensitivity().is_case_sensitive() {
|
||||
std::fs::rename(
|
||||
case.project_path("lib.py").as_std_path(),
|
||||
case.project_path("Lib.py").as_std_path(),
|
||||
)
|
||||
.context("Failed to rename `lib.py` to `Lib.py`")?;
|
||||
} else {
|
||||
// On case-insensitive file systems, renaming a file to a different casing is a no-op.
|
||||
// Rename to a different name first
|
||||
std::fs::rename(
|
||||
case.project_path("lib.py").as_std_path(),
|
||||
case.project_path("temp.py").as_std_path(),
|
||||
)
|
||||
.context("Failed to rename `lib.py` to `temp.py`")?;
|
||||
|
||||
std::fs::rename(
|
||||
case.project_path("temp.py").as_std_path(),
|
||||
case.project_path("Lib.py").as_std_path(),
|
||||
)
|
||||
.context("Failed to rename `temp.py` to `Lib.py`")?;
|
||||
}
|
||||
|
||||
let changes = case.stop_watch(event_for_file("Lib.py"));
|
||||
case.apply_changes(changes);
|
||||
|
||||
// Resolving `lib` should now fail but `Lib` should now succeed
|
||||
assert_eq!(
|
||||
resolve_module(case.db(), &ModuleName::new("lib").unwrap()),
|
||||
None,
|
||||
"Expected `lib` module to no longer exist."
|
||||
);
|
||||
|
||||
assert!(
|
||||
resolve_module(case.db(), &ModuleName::new("Lib").unwrap()).is_some(),
|
||||
"Expected `Lib` module to exist"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -59,9 +59,8 @@ impl ProjectDatabase {
|
||||
self.with_db(|db| db.project().check(db))
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip(self))]
|
||||
pub fn check_file(&self, file: File) -> Result<Vec<Box<dyn OldDiagnosticTrait>>, Cancelled> {
|
||||
let _span = tracing::debug_span!("check_file", file=%file.path(self)).entered();
|
||||
|
||||
self.with_db(|db| self.project().check_file(db, file))
|
||||
}
|
||||
|
||||
|
||||
@@ -195,7 +195,8 @@ impl Project {
|
||||
let project_span = project_span.clone();
|
||||
|
||||
scope.spawn(move |_| {
|
||||
let check_file_span = tracing::debug_span!(parent: &project_span, "check_file", file=%file.path(&db));
|
||||
let check_file_span =
|
||||
tracing::debug_span!(parent: &project_span, "check_file", ?file);
|
||||
let _entered = check_file_span.entered();
|
||||
|
||||
let file_diagnostics = check_file_impl(&db, file);
|
||||
@@ -325,7 +326,7 @@ impl Project {
|
||||
self.files(db).contains(&file)
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip(db))]
|
||||
#[tracing::instrument(level = "debug", skip(self, db))]
|
||||
pub fn remove_file(self, db: &mut dyn Db, file: File) {
|
||||
tracing::debug!(
|
||||
"Removing file `{}` from project `{}`",
|
||||
|
||||
@@ -2,7 +2,7 @@ use crate::metadata::value::{RangedValue, RelativePathBuf, ValueSource, ValueSou
|
||||
use crate::Db;
|
||||
use red_knot_python_semantic::lint::{GetLintError, Level, LintSource, RuleSelection};
|
||||
use red_knot_python_semantic::{ProgramSettings, PythonPath, PythonPlatform, SearchPathSettings};
|
||||
use ruff_db::diagnostic::{DiagnosticId, OldDiagnosticTrait, Severity, Span};
|
||||
use ruff_db::diagnostic::{DiagnosticFormat, DiagnosticId, OldDiagnosticTrait, Severity, Span};
|
||||
use ruff_db::files::system_path_to_file;
|
||||
use ruff_db::system::{System, SystemPath};
|
||||
use ruff_macros::Combine;
|
||||
@@ -106,9 +106,14 @@ impl Options {
|
||||
custom_typeshed: typeshed.map(|path| path.absolute(project_root, system)),
|
||||
python_path: python
|
||||
.map(|python_path| {
|
||||
PythonPath::SysPrefix(python_path.absolute(project_root, system))
|
||||
PythonPath::from_cli_flag(python_path.absolute(project_root, system))
|
||||
})
|
||||
.unwrap_or(PythonPath::KnownSitePackages(vec![])),
|
||||
.or_else(|| {
|
||||
std::env::var("VIRTUAL_ENV")
|
||||
.ok()
|
||||
.map(PythonPath::from_virtual_env_var)
|
||||
})
|
||||
.unwrap_or_else(|| PythonPath::KnownSitePackages(vec![])),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,6 +125,11 @@ impl Options {
|
||||
|
||||
if let Some(terminal) = self.terminal.as_ref() {
|
||||
settings.set_terminal(TerminalSettings {
|
||||
output_format: terminal
|
||||
.output_format
|
||||
.as_deref()
|
||||
.copied()
|
||||
.unwrap_or_default(),
|
||||
error_on_warning: terminal.error_on_warning.unwrap_or_default(),
|
||||
});
|
||||
}
|
||||
@@ -277,6 +287,11 @@ impl FromIterator<(RangedValue<String>, RangedValue<Level>)> for Rules {
|
||||
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct TerminalOptions {
|
||||
/// The format to use for printing diagnostic messages.
|
||||
///
|
||||
/// Defaults to `full`.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub output_format: Option<RangedValue<DiagnosticFormat>>,
|
||||
/// Use exit code 1 if there are any warning-level diagnostics.
|
||||
///
|
||||
/// Defaults to `false`.
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use red_knot_python_semantic::lint::RuleSelection;
|
||||
use ruff_db::diagnostic::DiagnosticFormat;
|
||||
|
||||
/// The resolved [`super::Options`] for the project.
|
||||
///
|
||||
@@ -49,5 +50,6 @@ impl Settings {
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default)]
|
||||
pub struct TerminalSettings {
|
||||
pub output_format: DiagnosticFormat,
|
||||
pub error_on_warning: bool,
|
||||
}
|
||||
|
||||
@@ -279,23 +279,4 @@ impl SourceOrderVisitor<'_> for PullTypesVisitor<'_> {
|
||||
|
||||
/// Whether or not the .py/.pyi version of this file is expected to fail
|
||||
#[rustfmt::skip]
|
||||
const KNOWN_FAILURES: &[(&str, bool, bool)] = &[
|
||||
// related to circular references in nested functions
|
||||
("crates/ruff_linter/resources/test/fixtures/flake8_return/RET503.py", false, true),
|
||||
// related to circular references in class definitions
|
||||
("crates/ruff_linter/resources/test/fixtures/pyflakes/F821_26.py", true, true),
|
||||
("crates/ruff_linter/resources/test/fixtures/pyflakes/F821_27.py", true, true),
|
||||
("crates/ruff_linter/resources/test/fixtures/pyflakes/F811_19.py", true, false),
|
||||
("crates/ruff_linter/resources/test/fixtures/pyupgrade/UP039.py", true, false),
|
||||
// related to circular references in type aliases (salsa cycle panic):
|
||||
("crates/ruff_python_parser/resources/inline/err/type_alias_invalid_value_expr.py", true, true),
|
||||
("crates/ruff_linter/resources/test/fixtures/flake8_type_checking/TC008.py", true, true),
|
||||
// related to circular references in f-string annotations (invalid syntax)
|
||||
("crates/ruff_linter/resources/test/fixtures/pyflakes/F821_15.py", true, true),
|
||||
("crates/ruff_linter/resources/test/fixtures/pyflakes/F821_14.py", false, true),
|
||||
// related to circular references in stub type annotations (salsa cycle panic):
|
||||
("crates/ruff_linter/resources/test/fixtures/pycodestyle/E501_4.py", false, true),
|
||||
("crates/ruff_linter/resources/test/fixtures/pyflakes/F401_0.py", false, true),
|
||||
("crates/ruff_linter/resources/test/fixtures/pyflakes/F401_12.py", false, true),
|
||||
("crates/ruff_linter/resources/test/fixtures/pyflakes/F401_14.py", false, true),
|
||||
];
|
||||
const KNOWN_FAILURES: &[(&str, bool, bool)] = &[];
|
||||
|
||||
@@ -29,7 +29,7 @@ It is invalid to parameterize `Annotated` with less than two arguments.
|
||||
```py
|
||||
from typing_extensions import Annotated
|
||||
|
||||
# error: [invalid-type-form] "`Annotated` requires at least two arguments when used in an annotation or type expression"
|
||||
# error: [invalid-type-form] "`typing.Annotated` requires at least two arguments when used in a type expression"
|
||||
def _(x: Annotated):
|
||||
reveal_type(x) # revealed: Unknown
|
||||
|
||||
@@ -39,11 +39,11 @@ def _(flag: bool):
|
||||
else:
|
||||
X = bool
|
||||
|
||||
# error: [invalid-type-form] "`Annotated` requires at least two arguments when used in an annotation or type expression"
|
||||
# error: [invalid-type-form] "`typing.Annotated` requires at least two arguments when used in a type expression"
|
||||
def f(y: X):
|
||||
reveal_type(y) # revealed: Unknown | bool
|
||||
|
||||
# error: [invalid-type-form] "`Annotated` requires at least two arguments when used in an annotation or type expression"
|
||||
# error: [invalid-type-form] "`typing.Annotated` requires at least two arguments when used in a type expression"
|
||||
def _(x: Annotated | bool):
|
||||
reveal_type(x) # revealed: Unknown | bool
|
||||
|
||||
@@ -73,12 +73,10 @@ Inheriting from `Annotated[T, ...]` is equivalent to inheriting from `T` itself.
|
||||
```py
|
||||
from typing_extensions import Annotated
|
||||
|
||||
# TODO: False positive
|
||||
# error: [invalid-base]
|
||||
class C(Annotated[int, "foo"]): ...
|
||||
|
||||
# TODO: Should be `tuple[Literal[C], Literal[int], Literal[object]]`
|
||||
reveal_type(C.__mro__) # revealed: tuple[Literal[C], Unknown, Literal[object]]
|
||||
reveal_type(C.__mro__) # revealed: tuple[Literal[C], @Todo(Inference of subscript on special form), Literal[object]]
|
||||
```
|
||||
|
||||
### Not parameterized
|
||||
|
||||
@@ -47,8 +47,10 @@ def _(c: Callable[42, str]):
|
||||
Or, when one of the parameter type is invalid in the list:
|
||||
|
||||
```py
|
||||
# error: [invalid-type-form] "Int literals are not allowed in this context in a type expression"
|
||||
# error: [invalid-type-form] "Boolean literals are not allowed in this context in a type expression"
|
||||
def _(c: Callable[[int, 42, str, False], None]):
|
||||
# revealed: (int, @Todo(number literal in type expression), str, @Todo(boolean literal in type expression), /) -> None
|
||||
# revealed: (int, Unknown, str, Unknown, /) -> None
|
||||
reveal_type(c)
|
||||
```
|
||||
|
||||
@@ -61,7 +63,7 @@ from typing import Callable
|
||||
|
||||
# error: [invalid-type-form] "Special form `typing.Callable` expected exactly two arguments (parameter types and return type)"
|
||||
def _(c: Callable[[int, str]]):
|
||||
reveal_type(c) # revealed: (int, str, /) -> Unknown
|
||||
reveal_type(c) # revealed: (...) -> Unknown
|
||||
```
|
||||
|
||||
Or, an ellipsis:
|
||||
@@ -72,6 +74,18 @@ def _(c: Callable[...]):
|
||||
reveal_type(c) # revealed: (...) -> Unknown
|
||||
```
|
||||
|
||||
Or something else that's invalid in a type expression generally:
|
||||
|
||||
```py
|
||||
# fmt: off
|
||||
|
||||
def _(c: Callable[ # error: [invalid-type-form] "Special form `typing.Callable` expected exactly two arguments (parameter types and return type)"
|
||||
{1, 2} # error: [invalid-type-form] "The first argument to `Callable` must be either a list of types, ParamSpec, Concatenate, or `...`"
|
||||
]
|
||||
):
|
||||
reveal_type(c) # revealed: (...) -> Unknown
|
||||
```
|
||||
|
||||
### More than two arguments
|
||||
|
||||
We can't reliably infer the callable type if there are more then 2 arguments because we don't know
|
||||
@@ -85,6 +99,48 @@ def _(c: Callable[[int], str, str]):
|
||||
reveal_type(c) # revealed: (...) -> Unknown
|
||||
```
|
||||
|
||||
### List as the second argument
|
||||
|
||||
```py
|
||||
from typing import Callable
|
||||
|
||||
# fmt: off
|
||||
|
||||
def _(c: Callable[
|
||||
int, # error: [invalid-type-form] "The first argument to `Callable` must be either a list of types, ParamSpec, Concatenate, or `...`"
|
||||
[str] # error: [invalid-type-form] "List literals are not allowed in this context in a type expression"
|
||||
]
|
||||
):
|
||||
reveal_type(c) # revealed: (...) -> Unknown
|
||||
```
|
||||
|
||||
### List as both arguments
|
||||
|
||||
```py
|
||||
from typing import Callable
|
||||
|
||||
# error: [invalid-type-form] "List literals are not allowed in this context in a type expression"
|
||||
def _(c: Callable[[int], [str]]):
|
||||
reveal_type(c) # revealed: (int, /) -> Unknown
|
||||
```
|
||||
|
||||
### Three list arguments
|
||||
|
||||
```py
|
||||
from typing import Callable
|
||||
|
||||
# fmt: off
|
||||
|
||||
|
||||
def _(c: Callable[ # error: [invalid-type-form] "Special form `typing.Callable` expected exactly two arguments (parameter types and return type)"
|
||||
[int],
|
||||
[str], # error: [invalid-type-form] "List literals are not allowed in this context in a type expression"
|
||||
[bytes] # error: [invalid-type-form] "List literals are not allowed in this context in a type expression"
|
||||
]
|
||||
):
|
||||
reveal_type(c) # revealed: (...) -> Unknown
|
||||
```
|
||||
|
||||
## Simple
|
||||
|
||||
A simple `Callable` with multiple parameters and a return type:
|
||||
@@ -192,4 +248,20 @@ def _(c: Callable[[int, Unpack[Ts]], int]):
|
||||
reveal_type(c) # revealed: (*args: @Todo(todo signature *args), **kwargs: @Todo(todo signature **kwargs)) -> int
|
||||
```
|
||||
|
||||
## Member lookup
|
||||
|
||||
```py
|
||||
from typing import Callable
|
||||
|
||||
def _(c: Callable[[int], int]):
|
||||
reveal_type(c.__init__) # revealed: Literal[__init__]
|
||||
reveal_type(c.__class__) # revealed: type
|
||||
|
||||
# TODO: The member lookup for `Callable` uses `object` which does not have a `__call__`
|
||||
# attribute. We could special case `__call__` in this context. Refer to
|
||||
# https://github.com/astral-sh/ruff/pull/16493#discussion_r1985098508 for more details.
|
||||
# error: [unresolved-attribute] "Type `(int, /) -> int` has no attribute `__call__`"
|
||||
reveal_type(c.__call__) # revealed: Unknown
|
||||
```
|
||||
|
||||
[gradual form]: https://typing.readthedocs.io/en/latest/spec/glossary.html#term-gradual-form
|
||||
|
||||
@@ -38,7 +38,8 @@ If `__future__.annotations` is imported, annotations *are* deferred.
|
||||
```py
|
||||
from __future__ import annotations
|
||||
|
||||
def get_foo() -> Foo: ...
|
||||
def get_foo() -> Foo:
|
||||
return Foo()
|
||||
|
||||
class Foo: ...
|
||||
|
||||
|
||||
@@ -9,6 +9,8 @@ import typing
|
||||
from knot_extensions import AlwaysTruthy, AlwaysFalsy
|
||||
from typing_extensions import Literal, Never
|
||||
|
||||
class A: ...
|
||||
|
||||
def _(
|
||||
a: type[int],
|
||||
b: AlwaysTruthy,
|
||||
@@ -18,28 +20,95 @@ def _(
|
||||
f: Literal[b"foo"],
|
||||
g: tuple[int, str],
|
||||
h: Never,
|
||||
i: int,
|
||||
j: A,
|
||||
):
|
||||
def foo(): ...
|
||||
def invalid(
|
||||
i: a, # error: [invalid-type-form] "Variable of type `type[int]` is not allowed in a type expression"
|
||||
j: b, # error: [invalid-type-form]
|
||||
k: c, # error: [invalid-type-form]
|
||||
l: d, # error: [invalid-type-form]
|
||||
m: e, # error: [invalid-type-form]
|
||||
n: f, # error: [invalid-type-form]
|
||||
o: g, # error: [invalid-type-form]
|
||||
p: h, # error: [invalid-type-form]
|
||||
q: typing, # error: [invalid-type-form]
|
||||
r: foo, # error: [invalid-type-form]
|
||||
a_: a, # error: [invalid-type-form] "Variable of type `type[int]` is not allowed in a type expression"
|
||||
b_: b, # error: [invalid-type-form]
|
||||
c_: c, # error: [invalid-type-form]
|
||||
d_: d, # error: [invalid-type-form]
|
||||
e_: e, # error: [invalid-type-form]
|
||||
f_: f, # error: [invalid-type-form]
|
||||
g_: g, # error: [invalid-type-form]
|
||||
h_: h, # error: [invalid-type-form]
|
||||
i_: typing, # error: [invalid-type-form]
|
||||
j_: foo, # error: [invalid-type-form]
|
||||
k_: i, # error: [invalid-type-form] "Variable of type `int` is not allowed in a type expression"
|
||||
l_: j, # error: [invalid-type-form] "Variable of type `A` is not allowed in a type expression"
|
||||
):
|
||||
reveal_type(i) # revealed: Unknown
|
||||
reveal_type(j) # revealed: Unknown
|
||||
reveal_type(k) # revealed: Unknown
|
||||
reveal_type(l) # revealed: Unknown
|
||||
reveal_type(m) # revealed: Unknown
|
||||
reveal_type(n) # revealed: Unknown
|
||||
reveal_type(o) # revealed: Unknown
|
||||
reveal_type(p) # revealed: Unknown
|
||||
reveal_type(q) # revealed: Unknown
|
||||
reveal_type(r) # revealed: Unknown
|
||||
reveal_type(a_) # revealed: Unknown
|
||||
reveal_type(b_) # revealed: Unknown
|
||||
reveal_type(c_) # revealed: Unknown
|
||||
reveal_type(d_) # revealed: Unknown
|
||||
reveal_type(e_) # revealed: Unknown
|
||||
reveal_type(f_) # revealed: Unknown
|
||||
reveal_type(g_) # revealed: Unknown
|
||||
reveal_type(h_) # revealed: Unknown
|
||||
reveal_type(i_) # revealed: Unknown
|
||||
reveal_type(j_) # revealed: Unknown
|
||||
```
|
||||
|
||||
## Invalid AST nodes
|
||||
|
||||
```py
|
||||
def bar() -> None:
|
||||
return None
|
||||
|
||||
def _(
|
||||
a: 1, # error: [invalid-type-form] "Int literals are not allowed in this context in a type expression"
|
||||
b: 2.3, # error: [invalid-type-form] "Float literals are not allowed in type expressions"
|
||||
c: 4j, # error: [invalid-type-form] "Complex literals are not allowed in type expressions"
|
||||
d: True, # error: [invalid-type-form] "Boolean literals are not allowed in this context in a type expression"
|
||||
e: int | b"foo", # error: [invalid-type-form] "Bytes literals are not allowed in this context in a type expression"
|
||||
f: 1 and 2, # error: [invalid-type-form] "Boolean operations are not allowed in type expressions"
|
||||
g: 1 or 2, # error: [invalid-type-form] "Boolean operations are not allowed in type expressions"
|
||||
h: (foo := 1), # error: [invalid-type-form] "Named expressions are not allowed in type expressions"
|
||||
i: not 1, # error: [invalid-type-form] "Unary operations are not allowed in type expressions"
|
||||
j: lambda: 1, # error: [invalid-type-form] "`lambda` expressions are not allowed in type expressions"
|
||||
k: 1 if True else 2, # error: [invalid-type-form] "`if` expressions are not allowed in type expressions"
|
||||
l: await 1, # error: [invalid-type-form] "`await` expressions are not allowed in type expressions"
|
||||
m: (yield 1), # error: [invalid-type-form] "`yield` expressions are not allowed in type expressions"
|
||||
n: (yield from [1]), # error: [invalid-type-form] "`yield from` expressions are not allowed in type expressions"
|
||||
o: 1 < 2, # error: [invalid-type-form] "Comparison expressions are not allowed in type expressions"
|
||||
p: bar(), # error: [invalid-type-form] "Function calls are not allowed in type expressions"
|
||||
q: int | f"foo", # error: [invalid-type-form] "F-strings are not allowed in type expressions"
|
||||
r: [1, 2, 3][1:2], # error: [invalid-type-form] "Slices are not allowed in type expressions"
|
||||
):
|
||||
reveal_type(a) # revealed: Unknown
|
||||
reveal_type(b) # revealed: Unknown
|
||||
reveal_type(c) # revealed: Unknown
|
||||
reveal_type(d) # revealed: Unknown
|
||||
reveal_type(e) # revealed: int | Unknown
|
||||
reveal_type(f) # revealed: Unknown
|
||||
reveal_type(g) # revealed: Unknown
|
||||
reveal_type(h) # revealed: Unknown
|
||||
reveal_type(i) # revealed: Unknown
|
||||
reveal_type(j) # revealed: Unknown
|
||||
reveal_type(k) # revealed: Unknown
|
||||
reveal_type(p) # revealed: Unknown
|
||||
reveal_type(q) # revealed: int | Unknown
|
||||
reveal_type(r) # revealed: @Todo(generics)
|
||||
```
|
||||
|
||||
## Invalid Collection based AST nodes
|
||||
|
||||
```py
|
||||
def _(
|
||||
a: {1: 2}, # error: [invalid-type-form] "Dict literals are not allowed in type expressions"
|
||||
b: {1, 2}, # error: [invalid-type-form] "Set literals are not allowed in type expressions"
|
||||
c: {k: v for k, v in [(1, 2)]}, # error: [invalid-type-form] "Dict comprehensions are not allowed in type expressions"
|
||||
d: [k for k in [1, 2]], # error: [invalid-type-form] "List comprehensions are not allowed in type expressions"
|
||||
e: {k for k in [1, 2]}, # error: [invalid-type-form] "Set comprehensions are not allowed in type expressions"
|
||||
f: (k for k in [1, 2]), # error: [invalid-type-form] "Generator expressions are not allowed in type expressions"
|
||||
g: [int, str], # error: [invalid-type-form] "List literals are not allowed in this context in a type expression"
|
||||
):
|
||||
reveal_type(a) # revealed: Unknown
|
||||
reveal_type(b) # revealed: Unknown
|
||||
reveal_type(c) # revealed: Unknown
|
||||
reveal_type(d) # revealed: Unknown
|
||||
reveal_type(e) # revealed: Unknown
|
||||
reveal_type(f) # revealed: Unknown
|
||||
reveal_type(g) # revealed: Unknown
|
||||
```
|
||||
|
||||
@@ -127,6 +127,13 @@ Literal: _SpecialForm
|
||||
```py
|
||||
from other import Literal
|
||||
|
||||
# TODO: can we add a subdiagnostic here saying something like:
|
||||
#
|
||||
# `other.Literal` and `typing.Literal` have similar names, but are different symbols and don't have the same semantics
|
||||
#
|
||||
# ?
|
||||
#
|
||||
# error: [invalid-type-form] "Int literals are not allowed in this context in a type expression"
|
||||
a1: Literal[26]
|
||||
|
||||
def f():
|
||||
@@ -149,7 +156,7 @@ def f():
|
||||
```py
|
||||
from typing import Literal
|
||||
|
||||
# error: [invalid-type-form] "`Literal` requires at least one argument when used in a type expression"
|
||||
# error: [invalid-type-form] "`typing.Literal` requires at least one argument when used in a type expression"
|
||||
def _(x: Literal):
|
||||
reveal_type(x) # revealed: Unknown
|
||||
```
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
# NewType
|
||||
|
||||
Currently, red-knot doesn't support `typing.NewType` in type annotations.
|
||||
|
||||
## Valid forms
|
||||
|
||||
```py
|
||||
from typing_extensions import NewType
|
||||
from types import GenericAlias
|
||||
|
||||
A = NewType("A", int)
|
||||
B = GenericAlias(A, ())
|
||||
|
||||
def _(
|
||||
a: A,
|
||||
b: B,
|
||||
):
|
||||
reveal_type(a) # revealed: @Todo(Support for `typing.NewType` instances in type expressions)
|
||||
reveal_type(b) # revealed: @Todo(Support for `typing.GenericAlias` instances in type expressions)
|
||||
```
|
||||
@@ -45,3 +45,13 @@ def f():
|
||||
# revealed: int | None
|
||||
reveal_type(a)
|
||||
```
|
||||
|
||||
## Invalid
|
||||
|
||||
```py
|
||||
from typing import Optional
|
||||
|
||||
# error: [invalid-type-form] "`typing.Optional` requires exactly one argument when used in a type expression"
|
||||
def f(x: Optional) -> None:
|
||||
reveal_type(x) # revealed: Unknown
|
||||
```
|
||||
|
||||
@@ -59,3 +59,13 @@ def f():
|
||||
# revealed: int | str
|
||||
reveal_type(a)
|
||||
```
|
||||
|
||||
## Invalid
|
||||
|
||||
```py
|
||||
from typing import Union
|
||||
|
||||
# error: [invalid-type-form] "`typing.Union` requires at least one argument when used in a type expression"
|
||||
def f(x: Union) -> None:
|
||||
reveal_type(x) # revealed: Unknown
|
||||
```
|
||||
|
||||
@@ -18,7 +18,7 @@ def f(*args: Unpack[Ts]) -> tuple[Unpack[Ts]]:
|
||||
# TODO: should understand the annotation
|
||||
reveal_type(args) # revealed: tuple
|
||||
|
||||
reveal_type(Alias) # revealed: @Todo(Invalid or unsupported `KnownInstanceType` in `Type::to_type_expression`)
|
||||
reveal_type(Alias) # revealed: @Todo(Support for `typing.TypeAlias`)
|
||||
|
||||
def g() -> TypeGuard[int]: ...
|
||||
def h() -> TypeIs[int]: ...
|
||||
@@ -35,7 +35,30 @@ def i(callback: Callable[Concatenate[int, P], R_co], *args: P.args, **kwargs: P.
|
||||
|
||||
class Foo:
|
||||
def method(self, x: Self):
|
||||
reveal_type(x) # revealed: @Todo(Invalid or unsupported `KnownInstanceType` in `Type::to_type_expression`)
|
||||
reveal_type(x) # revealed: @Todo(Support for `typing.Self`)
|
||||
```
|
||||
|
||||
## Type expressions
|
||||
|
||||
One thing that is supported is error messages for using special forms in type expressions.
|
||||
|
||||
```py
|
||||
from typing_extensions import Unpack, TypeGuard, TypeIs, Concatenate, ParamSpec
|
||||
|
||||
def _(
|
||||
a: Unpack, # error: [invalid-type-form] "`typing.Unpack` requires exactly one argument when used in a type expression"
|
||||
b: TypeGuard, # error: [invalid-type-form] "`typing.TypeGuard` requires exactly one argument when used in a type expression"
|
||||
c: TypeIs, # error: [invalid-type-form] "`typing.TypeIs` requires exactly one argument when used in a type expression"
|
||||
d: Concatenate, # error: [invalid-type-form] "`typing.Concatenate` requires at least two arguments when used in a type expression"
|
||||
e: ParamSpec,
|
||||
) -> None:
|
||||
reveal_type(a) # revealed: Unknown
|
||||
reveal_type(b) # revealed: Unknown
|
||||
reveal_type(c) # revealed: Unknown
|
||||
reveal_type(d) # revealed: Unknown
|
||||
|
||||
def foo(a_: e) -> None:
|
||||
reveal_type(a_) # revealed: @Todo(Support for `typing.ParamSpec` instances in type expressions)
|
||||
```
|
||||
|
||||
## Inheritance
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Unsupported type qualifiers
|
||||
|
||||
## Not yet supported
|
||||
## Not yet fully supported
|
||||
|
||||
Several type qualifiers are unsupported by red-knot currently. However, we also don't emit
|
||||
false-positive errors if you use one in an annotation:
|
||||
@@ -19,6 +19,33 @@ class Bar(TypedDict):
|
||||
z: ReadOnly[bytes]
|
||||
```
|
||||
|
||||
## Type expressions
|
||||
|
||||
One thing that is supported is error messages for using type qualifiers in type expressions.
|
||||
|
||||
```py
|
||||
from typing_extensions import Final, ClassVar, Required, NotRequired, ReadOnly
|
||||
|
||||
def _(
|
||||
a: (
|
||||
Final # error: [invalid-type-form] "Type qualifier `typing.Final` is not allowed in type expressions (only in annotation expressions)"
|
||||
| int
|
||||
),
|
||||
b: (
|
||||
ClassVar # error: [invalid-type-form] "Type qualifier `typing.ClassVar` is not allowed in type expressions (only in annotation expressions)"
|
||||
| int
|
||||
),
|
||||
c: Required, # error: [invalid-type-form] "Type qualifier `typing.Required` is not allowed in type expressions (only in annotation expressions, and only with exactly one argument)"
|
||||
d: NotRequired, # error: [invalid-type-form] "Type qualifier `typing.NotRequired` is not allowed in type expressions (only in annotation expressions, and only with exactly one argument)"
|
||||
e: ReadOnly, # error: [invalid-type-form] "Type qualifier `typing.ReadOnly` is not allowed in type expressions (only in annotation expressions, and only with exactly one argument)"
|
||||
) -> None:
|
||||
reveal_type(a) # revealed: Unknown | int
|
||||
reveal_type(b) # revealed: Unknown | int
|
||||
reveal_type(c) # revealed: Unknown
|
||||
reveal_type(d) # revealed: Unknown
|
||||
reveal_type(e) # revealed: Unknown
|
||||
```
|
||||
|
||||
## Inheritance
|
||||
|
||||
You can't inherit from a type qualifier.
|
||||
|
||||
@@ -10,6 +10,10 @@ reveal_type(x) # revealed: Literal[2]
|
||||
x = 1.0
|
||||
x /= 2
|
||||
reveal_type(x) # revealed: int | float
|
||||
|
||||
x = (1, 2)
|
||||
x += (3, 4)
|
||||
reveal_type(x) # revealed: tuple[Literal[1], Literal[2], Literal[3], Literal[4]]
|
||||
```
|
||||
|
||||
## Dunder methods
|
||||
@@ -161,3 +165,18 @@ def f(flag: bool, flag2: bool):
|
||||
|
||||
reveal_type(f) # revealed: int | str | float
|
||||
```
|
||||
|
||||
## Implicit dunder calls on class objects
|
||||
|
||||
```py
|
||||
class Meta(type):
|
||||
def __iadd__(cls, other: int) -> str:
|
||||
return ""
|
||||
|
||||
class C(metaclass=Meta): ...
|
||||
|
||||
cls = C
|
||||
cls += 1
|
||||
|
||||
reveal_type(cls) # revealed: str
|
||||
```
|
||||
|
||||
@@ -348,8 +348,11 @@ reveal_type(C().y) # revealed: Unknown | str
|
||||
|
||||
```py
|
||||
class ContextManager:
|
||||
def __enter__(self) -> int | None: ...
|
||||
def __exit__(self, exc_type, exc_value, traceback) -> None: ...
|
||||
def __enter__(self) -> int | None:
|
||||
return 1
|
||||
|
||||
def __exit__(self, exc_type, exc_value, traceback) -> None:
|
||||
pass
|
||||
|
||||
class C:
|
||||
def __init__(self) -> None:
|
||||
@@ -365,8 +368,11 @@ reveal_type(c_instance.x) # revealed: Unknown | int | None
|
||||
|
||||
```py
|
||||
class ContextManager:
|
||||
def __enter__(self) -> tuple[int | None, int]: ...
|
||||
def __exit__(self, exc_type, exc_value, traceback) -> None: ...
|
||||
def __enter__(self) -> tuple[int | None, int]:
|
||||
return 1, 2
|
||||
|
||||
def __exit__(self, exc_type, exc_value, traceback) -> None:
|
||||
pass
|
||||
|
||||
class C:
|
||||
def __init__(self) -> None:
|
||||
@@ -802,7 +808,7 @@ def _(flag1: bool, flag2: bool):
|
||||
reveal_type(C5.attr1) # revealed: Unknown | Literal["metaclass value", "class value"]
|
||||
```
|
||||
|
||||
## Union of attributes
|
||||
## Unions of attributes
|
||||
|
||||
If the (meta)class is a union type or if the attribute on the (meta) class has a union type, we
|
||||
infer those union types accordingly:
|
||||
@@ -812,77 +818,74 @@ def _(flag: bool):
|
||||
if flag:
|
||||
class C1:
|
||||
x = 1
|
||||
y: int = 1
|
||||
|
||||
else:
|
||||
class C1:
|
||||
x = 2
|
||||
y: int | str = "b"
|
||||
|
||||
reveal_type(C1.x) # revealed: Unknown | Literal[1, 2]
|
||||
reveal_type(C1.y) # revealed: int | str
|
||||
|
||||
C1.y = 100
|
||||
# error: [invalid-assignment] "Object of type `Literal["problematic"]` is not assignable to attribute `y` on type `Literal[C1, C1]`"
|
||||
C1.y = "problematic"
|
||||
|
||||
class C2:
|
||||
if flag:
|
||||
x = 3
|
||||
y: int = 3
|
||||
else:
|
||||
x = 4
|
||||
y: int | str = "d"
|
||||
|
||||
reveal_type(C2.x) # revealed: Unknown | Literal[3, 4]
|
||||
reveal_type(C2.y) # revealed: int | str
|
||||
|
||||
C2.y = 100
|
||||
# error: [invalid-assignment] "Object of type `None` is not assignable to attribute `y` of type `int | str`"
|
||||
C2.y = None
|
||||
# TODO: should be an error, needs more sophisticated union handling in `validate_attribute_assignment`
|
||||
C2.y = "problematic"
|
||||
|
||||
if flag:
|
||||
class Meta3(type):
|
||||
x = 5
|
||||
y: int = 5
|
||||
|
||||
else:
|
||||
class Meta3(type):
|
||||
x = 6
|
||||
y: int | str = "f"
|
||||
|
||||
class C3(metaclass=Meta3): ...
|
||||
reveal_type(C3.x) # revealed: Unknown | Literal[5, 6]
|
||||
reveal_type(C3.y) # revealed: int | str
|
||||
|
||||
C3.y = 100
|
||||
# error: [invalid-assignment] "Object of type `None` is not assignable to attribute `y` of type `int | str`"
|
||||
C3.y = None
|
||||
# TODO: should be an error, needs more sophisticated union handling in `validate_attribute_assignment`
|
||||
C3.y = "problematic"
|
||||
|
||||
class Meta4(type):
|
||||
if flag:
|
||||
x = 7
|
||||
y: int = 7
|
||||
else:
|
||||
x = 8
|
||||
y: int | str = "h"
|
||||
|
||||
class C4(metaclass=Meta4): ...
|
||||
reveal_type(C4.x) # revealed: Unknown | Literal[7, 8]
|
||||
```
|
||||
reveal_type(C4.y) # revealed: int | str
|
||||
|
||||
## Inherited class attributes
|
||||
|
||||
### Basic
|
||||
|
||||
```py
|
||||
class A:
|
||||
X = "foo"
|
||||
|
||||
class B(A): ...
|
||||
class C(B): ...
|
||||
|
||||
reveal_type(C.X) # revealed: Unknown | Literal["foo"]
|
||||
```
|
||||
|
||||
### Multiple inheritance
|
||||
|
||||
```py
|
||||
class O: ...
|
||||
|
||||
class F(O):
|
||||
X = 56
|
||||
|
||||
class E(O):
|
||||
X = 42
|
||||
|
||||
class D(O): ...
|
||||
class C(D, F): ...
|
||||
class B(E, D): ...
|
||||
class A(B, C): ...
|
||||
|
||||
# revealed: tuple[Literal[A], Literal[B], Literal[E], Literal[C], Literal[D], Literal[F], Literal[O], Literal[object]]
|
||||
reveal_type(A.__mro__)
|
||||
|
||||
# `E` is earlier in the MRO than `F`, so we should use the type of `E.X`
|
||||
reveal_type(A.X) # revealed: Unknown | Literal[42]
|
||||
C4.y = 100
|
||||
# error: [invalid-assignment] "Object of type `None` is not assignable to attribute `y` of type `int | str`"
|
||||
C4.y = None
|
||||
# TODO: should be an error, needs more sophisticated union handling in `validate_attribute_assignment`
|
||||
C4.y = "problematic"
|
||||
```
|
||||
|
||||
## Unions with possibly unbound paths
|
||||
@@ -906,8 +909,14 @@ def _(flag1: bool, flag2: bool):
|
||||
# error: [possibly-unbound-attribute] "Attribute `x` on type `Literal[C1, C2, C3]` is possibly unbound"
|
||||
reveal_type(C.x) # revealed: Unknown | Literal[1, 3]
|
||||
|
||||
# error: [invalid-assignment] "Object of type `Literal[100]` is not assignable to attribute `x` on type `Literal[C1, C2, C3]`"
|
||||
C.x = 100
|
||||
|
||||
# error: [possibly-unbound-attribute] "Attribute `x` on type `C1 | C2 | C3` is possibly unbound"
|
||||
reveal_type(C().x) # revealed: Unknown | Literal[1, 3]
|
||||
|
||||
# error: [invalid-assignment] "Object of type `Literal[100]` is not assignable to attribute `x` on type `C1 | C2 | C3`"
|
||||
C().x = 100
|
||||
```
|
||||
|
||||
### Possibly-unbound within a class
|
||||
@@ -932,10 +941,16 @@ def _(flag: bool, flag1: bool, flag2: bool):
|
||||
# error: [possibly-unbound-attribute] "Attribute `x` on type `Literal[C1, C2, C3]` is possibly unbound"
|
||||
reveal_type(C.x) # revealed: Unknown | Literal[1, 2, 3]
|
||||
|
||||
# error: [possibly-unbound-attribute]
|
||||
C.x = 100
|
||||
|
||||
# Note: we might want to consider ignoring possibly-unbound diagnostics for instance attributes eventually,
|
||||
# see the "Possibly unbound/undeclared instance attribute" section below.
|
||||
# error: [possibly-unbound-attribute] "Attribute `x` on type `C1 | C2 | C3` is possibly unbound"
|
||||
reveal_type(C().x) # revealed: Unknown | Literal[1, 2, 3]
|
||||
|
||||
# error: [possibly-unbound-attribute]
|
||||
C().x = 100
|
||||
```
|
||||
|
||||
### Possibly-unbound within gradual types
|
||||
@@ -953,6 +968,9 @@ def _(flag: bool):
|
||||
x: int
|
||||
|
||||
reveal_type(Derived().x) # revealed: int | Any
|
||||
|
||||
Derived().x = 1
|
||||
Derived().x = "a"
|
||||
```
|
||||
|
||||
### Attribute possibly unbound on a subclass but not on a superclass
|
||||
@@ -967,8 +985,10 @@ def _(flag: bool):
|
||||
x = 2
|
||||
|
||||
reveal_type(Bar.x) # revealed: Unknown | Literal[2, 1]
|
||||
Bar.x = 3
|
||||
|
||||
reveal_type(Bar().x) # revealed: Unknown | Literal[2, 1]
|
||||
Bar().x = 3
|
||||
```
|
||||
|
||||
### Attribute possibly unbound on a subclass and on a superclass
|
||||
@@ -986,8 +1006,14 @@ def _(flag: bool):
|
||||
# error: [possibly-unbound-attribute]
|
||||
reveal_type(Bar.x) # revealed: Unknown | Literal[2, 1]
|
||||
|
||||
# error: [possibly-unbound-attribute]
|
||||
Bar.x = 3
|
||||
|
||||
# error: [possibly-unbound-attribute]
|
||||
reveal_type(Bar().x) # revealed: Unknown | Literal[2, 1]
|
||||
|
||||
# error: [possibly-unbound-attribute]
|
||||
Bar().x = 3
|
||||
```
|
||||
|
||||
### Possibly unbound/undeclared instance attribute
|
||||
@@ -1006,6 +1032,9 @@ def _(flag: bool):
|
||||
|
||||
# error: [possibly-unbound-attribute]
|
||||
reveal_type(Foo().x) # revealed: int | Unknown
|
||||
|
||||
# error: [possibly-unbound-attribute]
|
||||
Foo().x = 1
|
||||
```
|
||||
|
||||
#### Possibly unbound
|
||||
@@ -1020,9 +1049,229 @@ def _(flag: bool):
|
||||
# Emitting a diagnostic in a case like this is not something we support, and it's unclear
|
||||
# if we ever will (or want to)
|
||||
reveal_type(Foo().x) # revealed: Unknown | Literal[1]
|
||||
|
||||
# Same here
|
||||
Foo().x = 2
|
||||
```
|
||||
|
||||
### Attribute access on `Any`
|
||||
### Unions with all paths unbound
|
||||
|
||||
If the symbol is unbound in all elements of the union, we detect that:
|
||||
|
||||
```py
|
||||
def _(flag: bool):
|
||||
class C1: ...
|
||||
class C2: ...
|
||||
C = C1 if flag else C2
|
||||
|
||||
# error: [unresolved-attribute] "Type `Literal[C1, C2]` has no attribute `x`"
|
||||
reveal_type(C.x) # revealed: Unknown
|
||||
|
||||
# TODO: This should ideally be a `unresolved-attribute` error. We need better union
|
||||
# handling in `validate_attribute_assignment` for this.
|
||||
# error: [invalid-assignment] "Object of type `Literal[1]` is not assignable to attribute `x` on type `Literal[C1, C2]`"
|
||||
C.x = 1
|
||||
```
|
||||
|
||||
## Inherited class attributes
|
||||
|
||||
### Basic
|
||||
|
||||
```py
|
||||
class A:
|
||||
X = "foo"
|
||||
|
||||
class B(A): ...
|
||||
class C(B): ...
|
||||
|
||||
reveal_type(C.X) # revealed: Unknown | Literal["foo"]
|
||||
|
||||
C.X = "bar"
|
||||
```
|
||||
|
||||
### Multiple inheritance
|
||||
|
||||
```py
|
||||
class O: ...
|
||||
|
||||
class F(O):
|
||||
X = 56
|
||||
|
||||
class E(O):
|
||||
X = 42
|
||||
|
||||
class D(O): ...
|
||||
class C(D, F): ...
|
||||
class B(E, D): ...
|
||||
class A(B, C): ...
|
||||
|
||||
# revealed: tuple[Literal[A], Literal[B], Literal[E], Literal[C], Literal[D], Literal[F], Literal[O], Literal[object]]
|
||||
reveal_type(A.__mro__)
|
||||
|
||||
# `E` is earlier in the MRO than `F`, so we should use the type of `E.X`
|
||||
reveal_type(A.X) # revealed: Unknown | Literal[42]
|
||||
|
||||
A.X = 100
|
||||
```
|
||||
|
||||
## Intersections of attributes
|
||||
|
||||
### Attribute only available on one element
|
||||
|
||||
```py
|
||||
from knot_extensions import Intersection
|
||||
|
||||
class A:
|
||||
x: int = 1
|
||||
|
||||
class B: ...
|
||||
|
||||
def _(a_and_b: Intersection[A, B]):
|
||||
reveal_type(a_and_b.x) # revealed: int
|
||||
|
||||
a_and_b.x = 2
|
||||
|
||||
# Same for class objects
|
||||
def _(a_and_b: Intersection[type[A], type[B]]):
|
||||
reveal_type(a_and_b.x) # revealed: int
|
||||
|
||||
a_and_b.x = 2
|
||||
```
|
||||
|
||||
### Attribute available on both elements
|
||||
|
||||
```py
|
||||
from knot_extensions import Intersection
|
||||
|
||||
class P: ...
|
||||
class Q: ...
|
||||
class R(P, Q): ...
|
||||
|
||||
class A:
|
||||
x: P = P()
|
||||
|
||||
class B:
|
||||
x: Q = Q()
|
||||
|
||||
def _(a_and_b: Intersection[A, B]):
|
||||
reveal_type(a_and_b.x) # revealed: P & Q
|
||||
a_and_b.x = R()
|
||||
|
||||
# Same for class objects
|
||||
def _(a_and_b: Intersection[type[A], type[B]]):
|
||||
reveal_type(a_and_b.x) # revealed: P & Q
|
||||
a_and_b.x = R()
|
||||
```
|
||||
|
||||
### Possible unboundness
|
||||
|
||||
```py
|
||||
from knot_extensions import Intersection
|
||||
|
||||
class P: ...
|
||||
class Q: ...
|
||||
class R(P, Q): ...
|
||||
|
||||
def _(flag: bool):
|
||||
class A1:
|
||||
if flag:
|
||||
x: P = P()
|
||||
|
||||
class B1: ...
|
||||
|
||||
def inner1(a_and_b: Intersection[A1, B1]):
|
||||
# error: [possibly-unbound-attribute]
|
||||
reveal_type(a_and_b.x) # revealed: P
|
||||
|
||||
# error: [possibly-unbound-attribute]
|
||||
a_and_b.x = R()
|
||||
# Same for class objects
|
||||
def inner1_class(a_and_b: Intersection[type[A1], type[B1]]):
|
||||
# error: [possibly-unbound-attribute]
|
||||
reveal_type(a_and_b.x) # revealed: P
|
||||
|
||||
# error: [possibly-unbound-attribute]
|
||||
a_and_b.x = R()
|
||||
|
||||
class A2:
|
||||
if flag:
|
||||
x: P = P()
|
||||
|
||||
class B1:
|
||||
x: Q = Q()
|
||||
|
||||
def inner2(a_and_b: Intersection[A2, B1]):
|
||||
reveal_type(a_and_b.x) # revealed: P & Q
|
||||
|
||||
# TODO: this should not be an error, we need better intersection
|
||||
# handling in `validate_attribute_assignment` for this
|
||||
# error: [possibly-unbound-attribute]
|
||||
a_and_b.x = R()
|
||||
# Same for class objects
|
||||
def inner2_class(a_and_b: Intersection[type[A2], type[B1]]):
|
||||
reveal_type(a_and_b.x) # revealed: P & Q
|
||||
|
||||
class A3:
|
||||
if flag:
|
||||
x: P = P()
|
||||
|
||||
class B3:
|
||||
if flag:
|
||||
x: Q = Q()
|
||||
|
||||
def inner3(a_and_b: Intersection[A3, B3]):
|
||||
# error: [possibly-unbound-attribute]
|
||||
reveal_type(a_and_b.x) # revealed: P & Q
|
||||
|
||||
# error: [possibly-unbound-attribute]
|
||||
a_and_b.x = R()
|
||||
# Same for class objects
|
||||
def inner3_class(a_and_b: Intersection[type[A3], type[B3]]):
|
||||
# error: [possibly-unbound-attribute]
|
||||
reveal_type(a_and_b.x) # revealed: P & Q
|
||||
|
||||
# error: [possibly-unbound-attribute]
|
||||
a_and_b.x = R()
|
||||
|
||||
class A4: ...
|
||||
class B4: ...
|
||||
|
||||
def inner4(a_and_b: Intersection[A4, B4]):
|
||||
# error: [unresolved-attribute]
|
||||
reveal_type(a_and_b.x) # revealed: Unknown
|
||||
|
||||
# error: [invalid-assignment]
|
||||
a_and_b.x = R()
|
||||
# Same for class objects
|
||||
def inner4_class(a_and_b: Intersection[type[A4], type[B4]]):
|
||||
# error: [unresolved-attribute]
|
||||
reveal_type(a_and_b.x) # revealed: Unknown
|
||||
|
||||
# error: [invalid-assignment]
|
||||
a_and_b.x = R()
|
||||
```
|
||||
|
||||
### Intersection of implicit instance attributes
|
||||
|
||||
```py
|
||||
from knot_extensions import Intersection
|
||||
|
||||
class P: ...
|
||||
class Q: ...
|
||||
|
||||
class A:
|
||||
def __init__(self):
|
||||
self.x: P = P()
|
||||
|
||||
class B:
|
||||
def __init__(self):
|
||||
self.x: Q = Q()
|
||||
|
||||
def _(a_and_b: Intersection[A, B]):
|
||||
reveal_type(a_and_b.x) # revealed: P & Q
|
||||
```
|
||||
|
||||
## Attribute access on `Any`
|
||||
|
||||
The union of the set of types that `Any` could materialise to is equivalent to `object`. It follows
|
||||
from this that attribute access on `Any` resolves to `Any` if the attribute does not exist on
|
||||
@@ -1053,18 +1302,96 @@ reveal_type(C.__mro__) # revealed: tuple[Literal[C], Literal[B], Any, Literal[A
|
||||
reveal_type(C.x) # revealed: Literal[1] & Any
|
||||
```
|
||||
|
||||
### Unions with all paths unbound
|
||||
## Classes with custom `__getattr__` methods
|
||||
|
||||
If the symbol is unbound in all elements of the union, we detect that:
|
||||
### Basic
|
||||
|
||||
If a type provides a custom `__getattr__` method, we use the return type of that method as the type
|
||||
for unknown attributes. Consider the following `CustomGetAttr` class:
|
||||
|
||||
```py
|
||||
def _(flag: bool):
|
||||
class C1: ...
|
||||
class C2: ...
|
||||
C = C1 if flag else C2
|
||||
from typing import Literal
|
||||
|
||||
# error: [unresolved-attribute] "Type `Literal[C1, C2]` has no attribute `x`"
|
||||
reveal_type(C.x) # revealed: Unknown
|
||||
def flag() -> bool:
|
||||
return True
|
||||
|
||||
class GetAttrReturnType: ...
|
||||
|
||||
class CustomGetAttr:
|
||||
class_attr: int = 1
|
||||
|
||||
if flag():
|
||||
possibly_unbound: bytes = b"a"
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.instance_attr: str = "a"
|
||||
|
||||
def __getattr__(self, name: str) -> GetAttrReturnType:
|
||||
return GetAttrReturnType()
|
||||
```
|
||||
|
||||
We can access arbitrary attributes on instances of this class, and the type of the attribute will be
|
||||
`GetAttrReturnType`:
|
||||
|
||||
```py
|
||||
c = CustomGetAttr()
|
||||
|
||||
reveal_type(c.whatever) # revealed: GetAttrReturnType
|
||||
```
|
||||
|
||||
If an attribute is defined on the class, it takes precedence over the `__getattr__` method:
|
||||
|
||||
```py
|
||||
reveal_type(c.class_attr) # revealed: int
|
||||
```
|
||||
|
||||
If the class attribute is possibly unbound, we union the type of the attribute with the fallback
|
||||
type of the `__getattr__` method:
|
||||
|
||||
```py
|
||||
reveal_type(c.possibly_unbound) # revealed: bytes | GetAttrReturnType
|
||||
```
|
||||
|
||||
Instance attributes also take precedence over the `__getattr__` method:
|
||||
|
||||
```py
|
||||
# Note: we could attempt to union with the fallback type of `__getattr__` here, as we currently do not
|
||||
# attempt to determine if instance attributes are always bound or not. Neither mypy nor pyright do this,
|
||||
# so it's not a priority.
|
||||
reveal_type(c.instance_attr) # revealed: str
|
||||
```
|
||||
|
||||
### Type of the `name` parameter
|
||||
|
||||
If the `name` parameter of the `__getattr__` method is annotated with a (union of) literal type(s),
|
||||
we only consider the attribute access to be valid if the accessed attribute is one of them:
|
||||
|
||||
```py
|
||||
from typing import Literal
|
||||
|
||||
class Date:
|
||||
def __getattr__(self, name: Literal["day", "month", "year"]) -> int:
|
||||
return 0
|
||||
|
||||
date = Date()
|
||||
|
||||
reveal_type(date.day) # revealed: int
|
||||
reveal_type(date.month) # revealed: int
|
||||
reveal_type(date.year) # revealed: int
|
||||
|
||||
# error: [unresolved-attribute] "Type `Date` has no attribute `century`"
|
||||
reveal_type(date.century) # revealed: Unknown
|
||||
```
|
||||
|
||||
### `argparse.Namespace`
|
||||
|
||||
A standard library example of a class with a custom `__getattr__` method is `argparse.Namespace`:
|
||||
|
||||
```py
|
||||
import argparse
|
||||
|
||||
def _(ns: argparse.Namespace):
|
||||
reveal_type(ns.whatever) # revealed: Any
|
||||
```
|
||||
|
||||
## Objects of all types have a `__class__` method
|
||||
@@ -1119,6 +1446,8 @@ reveal_type(Foo.__class__) # revealed: Literal[type]
|
||||
|
||||
## Module attributes
|
||||
|
||||
### Basic
|
||||
|
||||
`mod.py`:
|
||||
|
||||
```py
|
||||
@@ -1153,7 +1482,7 @@ for mod.global_symbol in IntIterable():
|
||||
pass
|
||||
```
|
||||
|
||||
## Nested attributes
|
||||
### Nested module attributes
|
||||
|
||||
`outer/__init__.py`:
|
||||
|
||||
|
||||
@@ -363,7 +363,7 @@ reveal_type(X() + Y()) # revealed: int
|
||||
|
||||
```py
|
||||
class NotBoolable:
|
||||
__bool__ = 3
|
||||
__bool__: int = 3
|
||||
|
||||
a = NotBoolable()
|
||||
|
||||
@@ -406,10 +406,12 @@ A left-hand dunder method doesn't apply for the right-hand operand, or vice vers
|
||||
|
||||
```py
|
||||
class A:
|
||||
def __add__(self, other) -> int: ...
|
||||
def __add__(self, other) -> int:
|
||||
return 1
|
||||
|
||||
class B:
|
||||
def __radd__(self, other) -> int: ...
|
||||
def __radd__(self, other) -> int:
|
||||
return 1
|
||||
|
||||
class C: ...
|
||||
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
# Binary operations on tuples
|
||||
|
||||
## Concatenation for heterogeneous tuples
|
||||
|
||||
```py
|
||||
reveal_type((1, 2) + (3, 4)) # revealed: tuple[Literal[1], Literal[2], Literal[3], Literal[4]]
|
||||
reveal_type(() + (1, 2)) # revealed: tuple[Literal[1], Literal[2]]
|
||||
reveal_type((1, 2) + ()) # revealed: tuple[Literal[1], Literal[2]]
|
||||
reveal_type(() + ()) # revealed: tuple[()]
|
||||
|
||||
def _(x: tuple[int, str], y: tuple[None, tuple[int]]):
|
||||
reveal_type(x + y) # revealed: tuple[int, str, None, tuple[int]]
|
||||
reveal_type(y + x) # revealed: tuple[None, tuple[int], int, str]
|
||||
```
|
||||
|
||||
## Concatenation for homogeneous tuples
|
||||
|
||||
```py
|
||||
def _(x: tuple[int, ...], y: tuple[str, ...]):
|
||||
reveal_type(x + y) # revealed: @Todo(full tuple[...] support)
|
||||
reveal_type(x + (1, 2)) # revealed: @Todo(full tuple[...] support)
|
||||
```
|
||||
@@ -0,0 +1,51 @@
|
||||
# Binary operations on union types
|
||||
|
||||
Binary operations on union types are only available if they are supported for all possible
|
||||
combinations of types:
|
||||
|
||||
```py
|
||||
def f1(i: int, u: int | None):
|
||||
# error: [unsupported-operator] "Operator `+` is unsupported between objects of type `int` and `int | None`"
|
||||
reveal_type(i + u) # revealed: Unknown
|
||||
# error: [unsupported-operator] "Operator `+` is unsupported between objects of type `int | None` and `int`"
|
||||
reveal_type(u + i) # revealed: Unknown
|
||||
```
|
||||
|
||||
`int` can be added to `int`, and `str` can be added to `str`, but expressions of type `int | str`
|
||||
cannot be added, because that would require addition of `int` and `str` or vice versa:
|
||||
|
||||
```py
|
||||
def f2(i: int, s: str, int_or_str: int | str):
|
||||
i + i
|
||||
s + s
|
||||
# error: [unsupported-operator] "Operator `+` is unsupported between objects of type `int | str` and `int | str`"
|
||||
reveal_type(int_or_str + int_or_str) # revealed: Unknown
|
||||
```
|
||||
|
||||
However, if an operation is supported for all possible combinations, the result will be a union of
|
||||
the possible outcomes:
|
||||
|
||||
```py
|
||||
from typing import Literal
|
||||
|
||||
def f3(two_or_three: Literal[2, 3], a_or_b: Literal["a", "b"]):
|
||||
reveal_type(two_or_three + two_or_three) # revealed: Literal[4, 5, 6]
|
||||
reveal_type(two_or_three**two_or_three) # revealed: Literal[4, 8, 9, 27]
|
||||
|
||||
reveal_type(a_or_b + a_or_b) # revealed: Literal["aa", "ab", "ba", "bb"]
|
||||
|
||||
reveal_type(two_or_three * a_or_b) # revealed: Literal["aa", "bb", "aaa", "bbb"]
|
||||
```
|
||||
|
||||
We treat a type annotation of `float` as a union of `int` and `float`, so union handling is relevant
|
||||
here:
|
||||
|
||||
```py
|
||||
def f4(x: float, y: float):
|
||||
reveal_type(x + y) # revealed: int | float
|
||||
reveal_type(x - y) # revealed: int | float
|
||||
reveal_type(x * y) # revealed: int | float
|
||||
reveal_type(x / y) # revealed: int | float
|
||||
reveal_type(x // y) # revealed: int | float
|
||||
reveal_type(x % y) # revealed: int | float
|
||||
```
|
||||
@@ -69,7 +69,8 @@ without raising an error.
|
||||
from typing import Any
|
||||
|
||||
def any() -> Any: ...
|
||||
def flag() -> bool: ...
|
||||
def flag() -> bool:
|
||||
return True
|
||||
|
||||
a: int
|
||||
b: str
|
||||
@@ -126,7 +127,8 @@ inferred types:
|
||||
from typing import Any
|
||||
|
||||
def any() -> Any: ...
|
||||
def flag() -> bool: ...
|
||||
def flag() -> bool:
|
||||
return True
|
||||
|
||||
a = 1
|
||||
b = 2
|
||||
@@ -164,7 +166,8 @@ error for both `a` and `b`:
|
||||
```py
|
||||
from typing import Any
|
||||
|
||||
def flag() -> bool: ...
|
||||
def flag() -> bool:
|
||||
return True
|
||||
|
||||
if flag():
|
||||
a: Any = 1
|
||||
@@ -194,7 +197,8 @@ seems inconsistent when compared to the case just above.
|
||||
`mod.py`:
|
||||
|
||||
```py
|
||||
def flag() -> bool: ...
|
||||
def flag() -> bool:
|
||||
return True
|
||||
|
||||
if flag():
|
||||
a: int
|
||||
@@ -248,7 +252,8 @@ inconsistent when compared to the "possibly-undeclared-and-possibly-unbound" cas
|
||||
`mod.py`:
|
||||
|
||||
```py
|
||||
def flag() -> bool: ...
|
||||
def flag() -> bool:
|
||||
return True
|
||||
|
||||
if flag:
|
||||
a = 1
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
class NotBool:
|
||||
__bool__ = None
|
||||
|
||||
# TODO: We should emit an `invalid-argument` error here for `2` because `bool` only takes one argument.
|
||||
# error: [too-many-positional-arguments] "Too many positional arguments to class `bool`: expected 1, got 2"
|
||||
bool(1, 2)
|
||||
|
||||
# TODO: We should emit an `unsupported-bool-conversion` error here because the argument doesn't implement `__bool__` correctly.
|
||||
@@ -29,9 +29,12 @@ But a three-argument call to type creates a dynamic instance of the `type` class
|
||||
reveal_type(type("Foo", (), {})) # revealed: type
|
||||
```
|
||||
|
||||
Other numbers of arguments are invalid (TODO -- these should emit a diagnostic)
|
||||
Other numbers of arguments are invalid
|
||||
|
||||
```py
|
||||
# error: [no-matching-overload] "No overload of class `type` matches arguments"
|
||||
type("Foo", ())
|
||||
|
||||
# error: [no-matching-overload] "No overload of class `type` matches arguments"
|
||||
type("Foo", (), {}, weird_other_arg=42)
|
||||
```
|
||||
|
||||
@@ -25,7 +25,8 @@ reveal_type(b) # revealed: Unknown
|
||||
def _(flag: bool):
|
||||
class PossiblyNotCallable:
|
||||
if flag:
|
||||
def __call__(self) -> int: ...
|
||||
def __call__(self) -> int:
|
||||
return 1
|
||||
|
||||
a = PossiblyNotCallable()
|
||||
result = a() # error: "Object of type `PossiblyNotCallable` is not callable (possibly unbound `__call__` method)"
|
||||
@@ -38,7 +39,8 @@ def _(flag: bool):
|
||||
def _(flag: bool):
|
||||
if flag:
|
||||
class PossiblyUnbound:
|
||||
def __call__(self) -> int: ...
|
||||
def __call__(self) -> int:
|
||||
return 1
|
||||
|
||||
# error: [possibly-unresolved-reference]
|
||||
a = PossiblyUnbound()
|
||||
@@ -64,11 +66,12 @@ def _(flag: bool):
|
||||
if flag:
|
||||
__call__ = 1
|
||||
else:
|
||||
def __call__(self) -> int: ...
|
||||
def __call__(self) -> int:
|
||||
return 1
|
||||
|
||||
a = NonCallable()
|
||||
# error: [call-non-callable] "Object of type `Literal[1]` is not callable"
|
||||
reveal_type(a()) # revealed: int | Unknown
|
||||
reveal_type(a()) # revealed: Unknown | int
|
||||
```
|
||||
|
||||
## Call binding errors
|
||||
|
||||
@@ -185,7 +185,7 @@ def _(flag: bool):
|
||||
return str(key)
|
||||
else:
|
||||
def __getitem__(self, key: int) -> bytes:
|
||||
return key
|
||||
return bytes()
|
||||
|
||||
c = C()
|
||||
reveal_type(c[0]) # revealed: str | bytes
|
||||
@@ -198,7 +198,7 @@ def _(flag: bool):
|
||||
else:
|
||||
class D:
|
||||
def __getitem__(self, key: int) -> bytes:
|
||||
return key
|
||||
return bytes()
|
||||
|
||||
d = D()
|
||||
reveal_type(d[0]) # revealed: str | bytes
|
||||
|
||||
@@ -37,6 +37,8 @@ def foo() -> int:
|
||||
return 42
|
||||
|
||||
def decorator(func) -> Callable[[], int]:
|
||||
# TODO: no error
|
||||
# error: [invalid-return-type]
|
||||
return foo
|
||||
|
||||
@decorator
|
||||
|
||||
@@ -235,23 +235,23 @@ method_wrapper(C(), None)
|
||||
method_wrapper(None, C)
|
||||
|
||||
# Passing `None` without an `owner` argument is an
|
||||
# error: [missing-argument] "No argument provided for required parameter `owner`"
|
||||
# error: [no-matching-overload] "No overload of method wrapper `__get__` of function `f` matches arguments"
|
||||
method_wrapper(None)
|
||||
|
||||
# Passing something that is not assignable to `type` as the `owner` argument is an
|
||||
# error: [invalid-argument-type] "Object of type `Literal[1]` cannot be assigned to parameter 2 (`owner`) of method wrapper `__get__` of function `f`; expected type `type`"
|
||||
# error: [no-matching-overload] "No overload of method wrapper `__get__` of function `f` matches arguments"
|
||||
method_wrapper(None, 1)
|
||||
|
||||
# Passing `None` as the `owner` argument when `instance` is `None` is an
|
||||
# error: [invalid-argument-type] "Object of type `None` cannot be assigned to parameter 2 (`owner`) of method wrapper `__get__` of function `f`; expected type `type`"
|
||||
# error: [no-matching-overload] "No overload of method wrapper `__get__` of function `f` matches arguments"
|
||||
method_wrapper(None, None)
|
||||
|
||||
# Calling `__get__` without any arguments is an
|
||||
# error: [missing-argument] "No argument provided for required parameter `instance`"
|
||||
# error: [no-matching-overload] "No overload of method wrapper `__get__` of function `f` matches arguments"
|
||||
method_wrapper()
|
||||
|
||||
# Calling `__get__` with too many positional arguments is an
|
||||
# error: [too-many-positional-arguments] "Too many positional arguments to method wrapper `__get__` of function `f`: expected 2, got 3"
|
||||
# error: [no-matching-overload] "No overload of method wrapper `__get__` of function `f` matches arguments"
|
||||
method_wrapper(C(), C, "one too many")
|
||||
```
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ def _(flag: bool):
|
||||
def f() -> int:
|
||||
return 1
|
||||
x = f() # error: [call-non-callable] "Object of type `Literal[1]` is not callable"
|
||||
reveal_type(x) # revealed: int | Unknown
|
||||
reveal_type(x) # revealed: Unknown | int
|
||||
```
|
||||
|
||||
## Multiple non-callable elements in a union
|
||||
@@ -58,7 +58,7 @@ def _(flag: bool, flag2: bool):
|
||||
return 1
|
||||
# TODO we should mention all non-callable elements of the union
|
||||
# error: [call-non-callable] "Object of type `Literal[1]` is not callable"
|
||||
# revealed: int | Unknown
|
||||
# revealed: Unknown | int
|
||||
reveal_type(f())
|
||||
```
|
||||
|
||||
@@ -82,8 +82,12 @@ def _(flag: bool):
|
||||
Calling a union where the arguments don't match the signature of all variants.
|
||||
|
||||
```py
|
||||
def f1(a: int) -> int: ...
|
||||
def f2(a: str) -> str: ...
|
||||
def f1(a: int) -> int:
|
||||
return a
|
||||
|
||||
def f2(a: str) -> str:
|
||||
return a
|
||||
|
||||
def _(flag: bool):
|
||||
if flag:
|
||||
f = f1
|
||||
@@ -144,3 +148,16 @@ def _(flag: bool):
|
||||
x = f(3)
|
||||
reveal_type(x) # revealed: Unknown
|
||||
```
|
||||
|
||||
## Union including a special-cased function
|
||||
|
||||
```py
|
||||
def _(flag: bool):
|
||||
if flag:
|
||||
f = str
|
||||
else:
|
||||
f = repr
|
||||
reveal_type(str("string")) # revealed: Literal["string"]
|
||||
reveal_type(repr("string")) # revealed: Literal["'string'"]
|
||||
reveal_type(f("string")) # revealed: Literal["string", "'string'"]
|
||||
```
|
||||
|
||||
@@ -191,7 +191,7 @@ It may also be more appropriate to use `unsupported-operator` as the error code.
|
||||
|
||||
```py
|
||||
class NotBoolable:
|
||||
__bool__ = 3
|
||||
__bool__: int = 3
|
||||
|
||||
class WithContains:
|
||||
def __contains__(self, item) -> NotBoolable:
|
||||
|
||||
@@ -154,7 +154,7 @@ reveal_type(B() >= A()) # revealed: LeReturnType
|
||||
|
||||
class C:
|
||||
def __gt__(self, other: C) -> EqReturnType:
|
||||
return 42
|
||||
return EqReturnType()
|
||||
|
||||
def __ge__(self, other: C) -> NeReturnType:
|
||||
return NeReturnType()
|
||||
@@ -355,7 +355,7 @@ element) of a chained comparison.
|
||||
|
||||
```py
|
||||
class NotBoolable:
|
||||
__bool__ = 3
|
||||
__bool__: int = 3
|
||||
|
||||
class Comparable:
|
||||
def __lt__(self, item) -> NotBoolable:
|
||||
|
||||
@@ -110,7 +110,8 @@ given operator:
|
||||
|
||||
```py
|
||||
class Container:
|
||||
def __contains__(self, x) -> bool: ...
|
||||
def __contains__(self, x) -> bool:
|
||||
return False
|
||||
|
||||
class NonContainer: ...
|
||||
|
||||
@@ -130,7 +131,8 @@ unsupported for the given operator:
|
||||
|
||||
```py
|
||||
class Container:
|
||||
def __contains__(self, x) -> bool: ...
|
||||
def __contains__(self, x) -> bool:
|
||||
return False
|
||||
|
||||
class NonContainer: ...
|
||||
|
||||
|
||||
@@ -22,14 +22,19 @@ Walking through examples:
|
||||
from __future__ import annotations
|
||||
|
||||
class A:
|
||||
def __lt__(self, other) -> A: ...
|
||||
def __gt__(self, other) -> bool: ...
|
||||
def __lt__(self, other) -> A:
|
||||
return self
|
||||
|
||||
def __gt__(self, other) -> bool:
|
||||
return False
|
||||
|
||||
class B:
|
||||
def __lt__(self, other) -> B: ...
|
||||
def __lt__(self, other) -> B:
|
||||
return self
|
||||
|
||||
class C:
|
||||
def __lt__(self, other) -> C: ...
|
||||
def __lt__(self, other) -> C:
|
||||
return self
|
||||
|
||||
x = A() < B() < C()
|
||||
reveal_type(x) # revealed: A & ~AlwaysTruthy | B
|
||||
|
||||
@@ -197,7 +197,7 @@ class LtReturnTypeOnB: ...
|
||||
|
||||
class B:
|
||||
def __lt__(self, o: B) -> LtReturnTypeOnB:
|
||||
return set()
|
||||
return LtReturnTypeOnB()
|
||||
|
||||
reveal_type((A(), B()) < (A(), B())) # revealed: LtReturnType | LtReturnTypeOnB | Literal[False]
|
||||
```
|
||||
@@ -355,7 +355,7 @@ def compute_chained_comparison():
|
||||
|
||||
```py
|
||||
class NotBoolable:
|
||||
__bool__ = 5
|
||||
__bool__: int = 5
|
||||
|
||||
class Comparable:
|
||||
def __lt__(self, other) -> NotBoolable:
|
||||
@@ -387,7 +387,7 @@ class A:
|
||||
return NotBoolable()
|
||||
|
||||
class NotBoolable:
|
||||
__bool__ = None
|
||||
__bool__: None = None
|
||||
|
||||
# error: [unsupported-bool-conversion]
|
||||
(A(),) == (A(),)
|
||||
|
||||
@@ -104,7 +104,8 @@ class Iterator:
|
||||
return 42
|
||||
|
||||
class Iterable:
|
||||
def __iter__(self) -> Iterator: ...
|
||||
def __iter__(self) -> Iterator:
|
||||
return Iterator()
|
||||
|
||||
# This is fine:
|
||||
x = [*Iterable()]
|
||||
|
||||
@@ -40,7 +40,7 @@ def _(flag: bool):
|
||||
|
||||
```py
|
||||
class NotBoolable:
|
||||
__bool__ = 3
|
||||
__bool__: int = 3
|
||||
|
||||
# error: [unsupported-bool-conversion] "Boolean conversion is unsupported for type `NotBoolable`; its `__bool__` method isn't callable"
|
||||
3 if NotBoolable() else 4
|
||||
|
||||
@@ -152,7 +152,7 @@ def _(flag: bool):
|
||||
|
||||
```py
|
||||
class NotBoolable:
|
||||
__bool__ = 3
|
||||
__bool__: int = 3
|
||||
|
||||
# error: [unsupported-bool-conversion] "Boolean conversion is unsupported for type `NotBoolable`; its `__bool__` method isn't callable"
|
||||
if NotBoolable():
|
||||
|
||||
@@ -48,7 +48,7 @@ def _(target: int):
|
||||
|
||||
```py
|
||||
class NotBoolable:
|
||||
__bool__ = 3
|
||||
__bool__: int = 3
|
||||
|
||||
def _(target: int, flag: NotBoolable):
|
||||
y = 1
|
||||
|
||||
@@ -6,7 +6,9 @@ A descriptor is an attribute value that has one of the methods in the descriptor
|
||||
methods are `__get__()`, `__set__()`, and `__delete__()`. If any of those methods are defined for an
|
||||
attribute, it is said to be a descriptor.
|
||||
|
||||
## Basic example
|
||||
## Basic properties
|
||||
|
||||
### Example
|
||||
|
||||
An introductory example, modeled after a [simple example] in the primer on descriptors, involving a
|
||||
descriptor that returns a constant value:
|
||||
@@ -30,18 +32,26 @@ reveal_type(c.ten) # revealed: Literal[10]
|
||||
|
||||
reveal_type(C.ten) # revealed: Literal[10]
|
||||
|
||||
# These are fine:
|
||||
# This is fine:
|
||||
c.ten = 10
|
||||
C.ten = 10
|
||||
|
||||
# error: [invalid-assignment] "Object of type `Literal[11]` is not assignable to attribute `ten` of type `Literal[10]`"
|
||||
# error: [invalid-assignment] "Invalid assignment to data descriptor attribute `ten` on type `C` with custom `__set__` method"
|
||||
c.ten = 11
|
||||
```
|
||||
|
||||
# error: [invalid-assignment] "Object of type `Literal[11]` is not assignable to attribute `ten` of type `Literal[10]`"
|
||||
When assigning to the `ten` attribute from the class object, we get an error. The descriptor
|
||||
protocol is *not* triggered in this case. Since the attribute is declared as `Ten` in the class
|
||||
body, we do not allow these assignments, preventing users from accidentally overwriting the data
|
||||
descriptor, which is what would happen at runtime:
|
||||
|
||||
```py
|
||||
# error: [invalid-assignment] "Object of type `Literal[10]` is not assignable to attribute `ten` of type `Ten`"
|
||||
C.ten = 10
|
||||
# error: [invalid-assignment] "Object of type `Literal[11]` is not assignable to attribute `ten` of type `Ten`"
|
||||
C.ten = 11
|
||||
```
|
||||
|
||||
## Different types for `__get__` and `__set__`
|
||||
### Different types for `__get__` and `__set__`
|
||||
|
||||
The return type of `__get__` and the value type of `__set__` can be different:
|
||||
|
||||
@@ -64,19 +74,17 @@ c = C()
|
||||
reveal_type(c.flexible_int) # revealed: int | None
|
||||
|
||||
c.flexible_int = 42 # okay
|
||||
# TODO: This should not be an error
|
||||
# error: [invalid-assignment]
|
||||
c.flexible_int = "42" # also okay!
|
||||
|
||||
reveal_type(c.flexible_int) # revealed: int | None
|
||||
|
||||
# TODO: This should be an error
|
||||
# error: [invalid-assignment] "Invalid assignment to data descriptor attribute `flexible_int` on type `C` with custom `__set__` method"
|
||||
c.flexible_int = None # not okay
|
||||
|
||||
reveal_type(c.flexible_int) # revealed: int | None
|
||||
```
|
||||
|
||||
## Data and non-data descriptors
|
||||
### Data and non-data descriptors
|
||||
|
||||
Descriptors that define `__set__` or `__delete__` are called *data descriptors*. An example of a
|
||||
data descriptor is a `property` with a setter and/or a deleter. Descriptors that only define
|
||||
@@ -131,6 +139,84 @@ reveal_type(C.non_data_descriptor) # revealed: Unknown | Literal["non-data"]
|
||||
C.data_descriptor = "something else" # This is okay
|
||||
```
|
||||
|
||||
### Partial fall back
|
||||
|
||||
Our implementation of the descriptor protocol takes into account that symbols can be possibly
|
||||
unbound. In those cases, we fall back to lower precedence steps of the descriptor protocol and union
|
||||
all possible results accordingly. We start by defining a data and a non-data descriptor:
|
||||
|
||||
```py
|
||||
from typing import Literal
|
||||
|
||||
class DataDescriptor:
|
||||
def __get__(self, instance: object, owner: type | None = None) -> Literal["data"]:
|
||||
return "data"
|
||||
|
||||
def __set__(self, instance: object, value: int) -> None:
|
||||
pass
|
||||
|
||||
class NonDataDescriptor:
|
||||
def __get__(self, instance: object, owner: type | None = None) -> Literal["non-data"]:
|
||||
return "non-data"
|
||||
```
|
||||
|
||||
Then, we demonstrate that we fall back to an instance attribute if a data descriptor is possibly
|
||||
unbound:
|
||||
|
||||
```py
|
||||
def f1(flag: bool):
|
||||
class C1:
|
||||
if flag:
|
||||
attr = DataDescriptor()
|
||||
|
||||
def f(self):
|
||||
self.attr = "normal"
|
||||
|
||||
reveal_type(C1().attr) # revealed: Unknown | Literal["data", "normal"]
|
||||
|
||||
# Assigning to the attribute also causes no `possibly-unbound` diagnostic:
|
||||
C1().attr = 1
|
||||
```
|
||||
|
||||
We never treat implicit instance attributes as definitely bound, so we fall back to the non-data
|
||||
descriptor here:
|
||||
|
||||
```py
|
||||
class C2:
|
||||
def f(self):
|
||||
self.attr = "normal"
|
||||
attr = NonDataDescriptor()
|
||||
|
||||
reveal_type(C2().attr) # revealed: Unknown | Literal["non-data", "normal"]
|
||||
|
||||
# Assignments always go to the instance attribute in this case
|
||||
C2().attr = 1
|
||||
```
|
||||
|
||||
### Descriptors only work when used as class variables
|
||||
|
||||
Descriptors only work when used as class variables. When put in instances, they have no effect.
|
||||
|
||||
```py
|
||||
from typing import Literal
|
||||
|
||||
class Ten:
|
||||
def __get__(self, instance: object, owner: type | None = None) -> Literal[10]:
|
||||
return 10
|
||||
|
||||
class C:
|
||||
def __init__(self):
|
||||
self.ten: Ten = Ten()
|
||||
|
||||
reveal_type(C().ten) # revealed: Ten
|
||||
|
||||
C().ten = Ten()
|
||||
|
||||
# The instance attribute is declared as `Ten`, so this is an
|
||||
# error: [invalid-assignment] "Object of type `Literal[10]` is not assignable to attribute `ten` of type `Ten`"
|
||||
C().ten = 10
|
||||
```
|
||||
|
||||
## Descriptor protocol for class objects
|
||||
|
||||
When attributes are accessed on a class object, the following [precedence chain] is used:
|
||||
@@ -150,7 +236,7 @@ class DataDescriptor:
|
||||
def __get__(self, instance: object, owner: type | None = None) -> Literal["data"]:
|
||||
return "data"
|
||||
|
||||
def __set__(self, instance: object, value: str) -> None:
|
||||
def __set__(self, instance: object, value: int) -> None:
|
||||
pass
|
||||
|
||||
class NonDataDescriptor:
|
||||
@@ -177,7 +263,28 @@ reveal_type(C1.class_data_descriptor) # revealed: Literal["data"]
|
||||
reveal_type(C1.class_non_data_descriptor) # revealed: Literal["non-data"]
|
||||
```
|
||||
|
||||
Next, we demonstrate that a *metaclass data descriptor* takes precedence over all class-level
|
||||
Assignments to class object attribute only trigger the descriptor protocol if the data descriptor is
|
||||
on the metaclass:
|
||||
|
||||
```py
|
||||
C1.meta_data_descriptor = 1
|
||||
|
||||
# error: [invalid-assignment] "Invalid assignment to data descriptor attribute `meta_data_descriptor` on type `Literal[C1]` with custom `__set__` method"
|
||||
C1.meta_data_descriptor = "invalid"
|
||||
```
|
||||
|
||||
When writing to a class-level data descriptor from the class object itself, the descriptor protocol
|
||||
is *not* triggered (this is in contrast to what happens when you read class-level descriptor
|
||||
attributes!). So the following assignment does not call `__set__`. At runtime, the assignment would
|
||||
overwrite the data descriptor, but the attribute is declared as `DataDescriptor` in the class body,
|
||||
so we do not allow this:
|
||||
|
||||
```py
|
||||
# error: [invalid-assignment] "Object of type `Literal[1]` is not assignable to attribute `class_data_descriptor` of type `DataDescriptor`"
|
||||
C1.class_data_descriptor = 1
|
||||
```
|
||||
|
||||
We now demonstrate that a *metaclass data descriptor* takes precedence over all class-level
|
||||
attributes:
|
||||
|
||||
```py
|
||||
@@ -198,6 +305,14 @@ class C2(metaclass=Meta2):
|
||||
|
||||
reveal_type(C2.meta_data_descriptor1) # revealed: Literal["data"]
|
||||
reveal_type(C2.meta_data_descriptor2) # revealed: Literal["data"]
|
||||
|
||||
C2.meta_data_descriptor1 = 1
|
||||
C2.meta_data_descriptor2 = 1
|
||||
|
||||
# error: [invalid-assignment]
|
||||
C2.meta_data_descriptor1 = "invalid"
|
||||
# error: [invalid-assignment]
|
||||
C2.meta_data_descriptor2 = "invalid"
|
||||
```
|
||||
|
||||
On the other hand, normal metaclass attributes and metaclass non-data descriptors are shadowed by
|
||||
@@ -252,6 +367,16 @@ def _(flag: bool):
|
||||
reveal_type(C5.meta_data_descriptor1) # revealed: Literal["data", "value on class"]
|
||||
# error: [possibly-unbound-attribute]
|
||||
reveal_type(C5.meta_data_descriptor2) # revealed: Literal["data"]
|
||||
|
||||
# TODO: We currently emit two diagnostics here, corresponding to the two states of `flag`. The diagnostics are not
|
||||
# wrong, but they could be subsumed under a higher-level diagnostic.
|
||||
|
||||
# error: [invalid-assignment] "Invalid assignment to data descriptor attribute `meta_data_descriptor1` on type `Literal[C5]` with custom `__set__` method"
|
||||
# error: [invalid-assignment] "Object of type `None` is not assignable to attribute `meta_data_descriptor1` of type `Literal["value on class"]`"
|
||||
C5.meta_data_descriptor1 = None
|
||||
|
||||
# error: [possibly-unbound-attribute]
|
||||
C5.meta_data_descriptor2 = 1
|
||||
```
|
||||
|
||||
When a class-level attribute is possibly unbound, we union its (descriptor protocol) type with the
|
||||
@@ -304,58 +429,71 @@ def _(flag: bool):
|
||||
reveal_type(C7.union_of_metaclass_data_descriptor_and_attribute) # revealed: Literal["data", 2]
|
||||
reveal_type(C7.union_of_class_attributes) # revealed: Literal[1, 2]
|
||||
reveal_type(C7.union_of_class_data_descriptor_and_attribute) # revealed: Literal["data", 2]
|
||||
|
||||
C7.union_of_metaclass_attributes = 2 if flag else 1
|
||||
C7.union_of_metaclass_data_descriptor_and_attribute = 2 if flag else 100
|
||||
C7.union_of_class_attributes = 2 if flag else 1
|
||||
C7.union_of_class_data_descriptor_and_attribute = 2 if flag else DataDescriptor()
|
||||
```
|
||||
|
||||
## Partial fall back
|
||||
## Descriptors distinguishing between class and instance access
|
||||
|
||||
Our implementation of the descriptor protocol takes into account that symbols can be possibly
|
||||
unbound. In those cases, we fall back to lower precedence steps of the descriptor protocol and union
|
||||
all possible results accordingly. We start by defining a data and a non-data descriptor:
|
||||
Overloads can be used to distinguish between when a descriptor is accessed on a class object and
|
||||
when it is accessed on an instance. A real-world example of this is the `__get__` method on
|
||||
`types.FunctionType`.
|
||||
|
||||
```py
|
||||
from typing import Literal
|
||||
from typing_extensions import Literal, LiteralString, overload
|
||||
|
||||
class DataDescriptor:
|
||||
def __get__(self, instance: object, owner: type | None = None) -> Literal["data"]:
|
||||
return "data"
|
||||
class Descriptor:
|
||||
@overload
|
||||
def __get__(self, instance: None, owner: type, /) -> Literal["called on class object"]: ...
|
||||
@overload
|
||||
def __get__(self, instance: object, owner: type | None = None, /) -> Literal["called on instance"]: ...
|
||||
def __get__(self, instance, owner=None, /) -> LiteralString:
|
||||
if instance:
|
||||
return "called on instance"
|
||||
else:
|
||||
return "called on class object"
|
||||
|
||||
def __set__(self, instance: object, value: int) -> None:
|
||||
pass
|
||||
class C:
|
||||
d: Descriptor = Descriptor()
|
||||
|
||||
class NonDataDescriptor:
|
||||
def __get__(self, instance: object, owner: type | None = None) -> Literal["non-data"]:
|
||||
return "non-data"
|
||||
# TODO: should be `Literal["called on class object"]
|
||||
reveal_type(C.d) # revealed: LiteralString
|
||||
|
||||
# TODO: should be `Literal["called on instance"]
|
||||
reveal_type(C().d) # revealed: LiteralString
|
||||
```
|
||||
|
||||
Then, we demonstrate that we fall back to an instance attribute if a data descriptor is possibly
|
||||
unbound:
|
||||
## Descriptor protocol for dunder methods
|
||||
|
||||
Dunder methods are always looked up on the meta-type. There is no instance fallback. This means that
|
||||
an implicit dunder call on an instance-like object will not only look up the dunder method on the
|
||||
class object, without considering instance attributes. And an implicit dunder call on a class object
|
||||
will look up the dunder method on the metaclass, without considering class attributes.
|
||||
|
||||
```py
|
||||
def f1(flag: bool):
|
||||
class C1:
|
||||
if flag:
|
||||
attr = DataDescriptor()
|
||||
class SomeCallable:
|
||||
def __call__(self, x: int) -> str:
|
||||
return "a"
|
||||
|
||||
def f(self):
|
||||
self.attr = "normal"
|
||||
class Descriptor:
|
||||
def __get__(self, instance: object, owner: type | None = None) -> SomeCallable:
|
||||
return SomeCallable()
|
||||
|
||||
reveal_type(C1().attr) # revealed: Unknown | Literal["data", "normal"]
|
||||
class B:
|
||||
__call__: Descriptor = Descriptor()
|
||||
|
||||
b_instance = B()
|
||||
reveal_type(b_instance(1)) # revealed: str
|
||||
|
||||
b_instance("bla") # error: [invalid-argument-type]
|
||||
```
|
||||
|
||||
We never treat implicit instance attributes as definitely bound, so we fall back to the non-data
|
||||
descriptor here:
|
||||
## Special descriptors
|
||||
|
||||
```py
|
||||
def f2(flag: bool):
|
||||
class C2:
|
||||
def f(self):
|
||||
self.attr = "normal"
|
||||
attr = NonDataDescriptor()
|
||||
|
||||
reveal_type(C2().attr) # revealed: Unknown | Literal["non-data", "normal"]
|
||||
```
|
||||
|
||||
## Built-in `property` descriptor
|
||||
### Built-in `property` descriptor
|
||||
|
||||
The built-in `property` decorator creates a descriptor. The names for attribute reads/writes are
|
||||
determined by the return type of the `name` method and the parameter type of the setter,
|
||||
@@ -392,11 +530,11 @@ c.name = "new"
|
||||
c.name = None
|
||||
|
||||
# TODO: this should be an error, but with a proper error message
|
||||
# error: [invalid-assignment] "Object of type `Literal[42]` is not assignable to attribute `name` of type `<bound method `name` of `C`>`"
|
||||
# error: [invalid-assignment] "Implicit shadowing of function `name`; annotate to make it explicit if this is intentional"
|
||||
c.name = 42
|
||||
```
|
||||
|
||||
## Built-in `classmethod` descriptor
|
||||
### Built-in `classmethod` descriptor
|
||||
|
||||
Similarly to `property`, `classmethod` decorator creates an implicit descriptor that binds the first
|
||||
argument to the class instead of the instance.
|
||||
@@ -423,244 +561,7 @@ reveal_type(C.get_name()) # revealed: str
|
||||
reveal_type(C("42").get_name()) # revealed: str
|
||||
```
|
||||
|
||||
## Descriptors only work when used as class variables
|
||||
|
||||
From the descriptor guide:
|
||||
|
||||
> Descriptors only work when used as class variables. When put in instances, they have no effect.
|
||||
|
||||
```py
|
||||
from typing import Literal
|
||||
|
||||
class Ten:
|
||||
def __get__(self, instance: object, owner: type | None = None) -> Literal[10]:
|
||||
return 10
|
||||
|
||||
class C:
|
||||
def __init__(self):
|
||||
self.ten: Ten = Ten()
|
||||
|
||||
reveal_type(C().ten) # revealed: Ten
|
||||
```
|
||||
|
||||
## Descriptors distinguishing between class and instance access
|
||||
|
||||
Overloads can be used to distinguish between when a descriptor is accessed on a class object and
|
||||
when it is accessed on an instance. A real-world example of this is the `__get__` method on
|
||||
`types.FunctionType`.
|
||||
|
||||
```py
|
||||
from typing_extensions import Literal, LiteralString, overload
|
||||
|
||||
class Descriptor:
|
||||
@overload
|
||||
def __get__(self, instance: None, owner: type, /) -> Literal["called on class object"]: ...
|
||||
@overload
|
||||
def __get__(self, instance: object, owner: type | None = None, /) -> Literal["called on instance"]: ...
|
||||
def __get__(self, instance, owner=None, /) -> LiteralString:
|
||||
if instance:
|
||||
return "called on instance"
|
||||
else:
|
||||
return "called on class object"
|
||||
|
||||
class C:
|
||||
d: Descriptor = Descriptor()
|
||||
|
||||
# TODO: should be `Literal["called on class object"]
|
||||
reveal_type(C.d) # revealed: LiteralString
|
||||
|
||||
# TODO: should be `Literal["called on instance"]
|
||||
reveal_type(C().d) # revealed: LiteralString
|
||||
```
|
||||
|
||||
## Undeclared descriptor arguments
|
||||
|
||||
If a descriptor attribute is not declared, we union with `Unknown`, just like for regular
|
||||
attributes, since that attribute could be overwritten externally. Even a data descriptor with a
|
||||
`__set__` method can be overwritten when accessed through a class object.
|
||||
|
||||
```py
|
||||
class Descriptor:
|
||||
def __get__(self, instance: object, owner: type | None = None) -> int:
|
||||
return 1
|
||||
|
||||
def __set__(self, instance: object, value: int) -> None:
|
||||
pass
|
||||
|
||||
class C:
|
||||
descriptor = Descriptor()
|
||||
|
||||
C.descriptor = "something else"
|
||||
|
||||
# This could also be `Literal["something else"]` if we support narrowing of attribute types based on assignments
|
||||
reveal_type(C.descriptor) # revealed: Unknown | int
|
||||
```
|
||||
|
||||
## `__get__` is called with correct arguments
|
||||
|
||||
```py
|
||||
from __future__ import annotations
|
||||
|
||||
class TailoredForClassObjectAccess:
|
||||
def __get__(self, instance: None, owner: type[C]) -> int:
|
||||
return 1
|
||||
|
||||
class TailoredForInstanceAccess:
|
||||
def __get__(self, instance: C, owner: type[C] | None = None) -> str:
|
||||
return "a"
|
||||
|
||||
class TailoredForMetaclassAccess:
|
||||
def __get__(self, instance: type[C], owner: type[Meta]) -> bytes:
|
||||
return b"a"
|
||||
|
||||
class Meta(type):
|
||||
metaclass_access: TailoredForMetaclassAccess = TailoredForMetaclassAccess()
|
||||
|
||||
class C(metaclass=Meta):
|
||||
class_object_access: TailoredForClassObjectAccess = TailoredForClassObjectAccess()
|
||||
instance_access: TailoredForInstanceAccess = TailoredForInstanceAccess()
|
||||
|
||||
reveal_type(C.class_object_access) # revealed: int
|
||||
reveal_type(C().instance_access) # revealed: str
|
||||
reveal_type(C.metaclass_access) # revealed: bytes
|
||||
|
||||
# TODO: These should emit a diagnostic
|
||||
reveal_type(C().class_object_access) # revealed: TailoredForClassObjectAccess
|
||||
reveal_type(C.instance_access) # revealed: TailoredForInstanceAccess
|
||||
```
|
||||
|
||||
## Descriptors with incorrect `__get__` signature
|
||||
|
||||
```py
|
||||
class Descriptor:
|
||||
# `__get__` method with missing parameters:
|
||||
def __get__(self) -> int:
|
||||
return 1
|
||||
|
||||
class C:
|
||||
descriptor: Descriptor = Descriptor()
|
||||
|
||||
# TODO: This should be an error
|
||||
reveal_type(C.descriptor) # revealed: Descriptor
|
||||
|
||||
# TODO: This should be an error
|
||||
reveal_type(C().descriptor) # revealed: Descriptor
|
||||
```
|
||||
|
||||
## Possibly unbound descriptor attributes
|
||||
|
||||
```py
|
||||
class DataDescriptor:
|
||||
def __get__(self, instance: object, owner: type | None = None) -> int:
|
||||
return 1
|
||||
|
||||
def __set__(self, instance: int, value) -> None:
|
||||
pass
|
||||
|
||||
class NonDataDescriptor:
|
||||
def __get__(self, instance: object, owner: type | None = None) -> int:
|
||||
return 1
|
||||
|
||||
def _(flag: bool):
|
||||
class PossiblyUnbound:
|
||||
if flag:
|
||||
non_data: NonDataDescriptor = NonDataDescriptor()
|
||||
data: DataDescriptor = DataDescriptor()
|
||||
|
||||
# error: [possibly-unbound-attribute] "Attribute `non_data` on type `Literal[PossiblyUnbound]` is possibly unbound"
|
||||
reveal_type(PossiblyUnbound.non_data) # revealed: int
|
||||
|
||||
# error: [possibly-unbound-attribute] "Attribute `non_data` on type `PossiblyUnbound` is possibly unbound"
|
||||
reveal_type(PossiblyUnbound().non_data) # revealed: int
|
||||
|
||||
# error: [possibly-unbound-attribute] "Attribute `data` on type `Literal[PossiblyUnbound]` is possibly unbound"
|
||||
reveal_type(PossiblyUnbound.data) # revealed: int
|
||||
|
||||
# error: [possibly-unbound-attribute] "Attribute `data` on type `PossiblyUnbound` is possibly unbound"
|
||||
reveal_type(PossiblyUnbound().data) # revealed: int
|
||||
```
|
||||
|
||||
## Possibly-unbound `__get__` method
|
||||
|
||||
```py
|
||||
def _(flag: bool):
|
||||
class MaybeDescriptor:
|
||||
if flag:
|
||||
def __get__(self, instance: object, owner: type | None = None) -> int:
|
||||
return 1
|
||||
|
||||
class C:
|
||||
descriptor: MaybeDescriptor = MaybeDescriptor()
|
||||
|
||||
reveal_type(C.descriptor) # revealed: int | MaybeDescriptor
|
||||
|
||||
reveal_type(C().descriptor) # revealed: int | MaybeDescriptor
|
||||
```
|
||||
|
||||
## Descriptors with non-function `__get__` callables that are descriptors themselves
|
||||
|
||||
The descriptor protocol is recursive, i.e. looking up `__get__` can involve triggering the
|
||||
descriptor protocol on the callable's `__call__` method:
|
||||
|
||||
```py
|
||||
from __future__ import annotations
|
||||
|
||||
class ReturnedCallable2:
|
||||
def __call__(self, descriptor: Descriptor1, instance: None, owner: type[C]) -> int:
|
||||
return 1
|
||||
|
||||
class ReturnedCallable1:
|
||||
def __call__(self, descriptor: Descriptor2, instance: Callable1, owner: type[Callable1]) -> ReturnedCallable2:
|
||||
return ReturnedCallable2()
|
||||
|
||||
class Callable3:
|
||||
def __call__(self, descriptor: Descriptor3, instance: Callable2, owner: type[Callable2]) -> ReturnedCallable1:
|
||||
return ReturnedCallable1()
|
||||
|
||||
class Descriptor3:
|
||||
__get__: Callable3 = Callable3()
|
||||
|
||||
class Callable2:
|
||||
__call__: Descriptor3 = Descriptor3()
|
||||
|
||||
class Descriptor2:
|
||||
__get__: Callable2 = Callable2()
|
||||
|
||||
class Callable1:
|
||||
__call__: Descriptor2 = Descriptor2()
|
||||
|
||||
class Descriptor1:
|
||||
__get__: Callable1 = Callable1()
|
||||
|
||||
class C:
|
||||
d: Descriptor1 = Descriptor1()
|
||||
|
||||
reveal_type(C.d) # revealed: int
|
||||
```
|
||||
|
||||
## Dunder methods
|
||||
|
||||
Dunder methods are looked up on the meta-type, but we still need to invoke the descriptor protocol:
|
||||
|
||||
```py
|
||||
class SomeCallable:
|
||||
def __call__(self, x: int) -> str:
|
||||
return "a"
|
||||
|
||||
class Descriptor:
|
||||
def __get__(self, instance: object, owner: type | None = None) -> SomeCallable:
|
||||
return SomeCallable()
|
||||
|
||||
class B:
|
||||
__call__: Descriptor = Descriptor()
|
||||
|
||||
b_instance = B()
|
||||
reveal_type(b_instance(1)) # revealed: str
|
||||
|
||||
b_instance("bla") # error: [invalid-argument-type]
|
||||
```
|
||||
|
||||
## Functions as descriptors
|
||||
### Functions as descriptors
|
||||
|
||||
Functions are descriptors because they implement a `__get__` method. This is crucial in making sure
|
||||
that method calls work as expected. See [this test suite](./call/methods.md) for more information.
|
||||
@@ -710,33 +611,202 @@ Finally, we test some error cases for the call to the wrapper descriptor:
|
||||
|
||||
```py
|
||||
# Calling the wrapper descriptor without any arguments is an
|
||||
# error: [missing-argument] "No arguments provided for required parameters `self`, `instance`"
|
||||
# error: [no-matching-overload] "No overload of wrapper descriptor `FunctionType.__get__` matches arguments"
|
||||
wrapper_descriptor()
|
||||
|
||||
# Calling it without the `instance` argument is an also an
|
||||
# error: [missing-argument] "No argument provided for required parameter `instance`"
|
||||
# error: [no-matching-overload] "No overload of wrapper descriptor `FunctionType.__get__` matches arguments"
|
||||
wrapper_descriptor(f)
|
||||
|
||||
# Calling it without the `owner` argument if `instance` is not `None` is an
|
||||
# error: [missing-argument] "No argument provided for required parameter `owner`"
|
||||
# error: [no-matching-overload] "No overload of wrapper descriptor `FunctionType.__get__` matches arguments"
|
||||
wrapper_descriptor(f, None)
|
||||
|
||||
# But calling it with an instance is fine (in this case, the `owner` argument is optional):
|
||||
wrapper_descriptor(f, C())
|
||||
|
||||
# Calling it with something that is not a `FunctionType` as the first argument is an
|
||||
# error: [invalid-argument-type] "Object of type `Literal[1]` cannot be assigned to parameter 1 (`self`) of wrapper descriptor `FunctionType.__get__`; expected type `FunctionType`"
|
||||
# error: [no-matching-overload] "No overload of wrapper descriptor `FunctionType.__get__` matches arguments"
|
||||
wrapper_descriptor(1, None, type(f))
|
||||
|
||||
# Calling it with something that is not a `type` as the `owner` argument is an
|
||||
# error: [invalid-argument-type] "Object of type `Literal[f]` cannot be assigned to parameter 3 (`owner`) of wrapper descriptor `FunctionType.__get__`; expected type `type`"
|
||||
# error: [no-matching-overload] "No overload of wrapper descriptor `FunctionType.__get__` matches arguments"
|
||||
wrapper_descriptor(f, None, f)
|
||||
|
||||
# Calling it with too many positional arguments is an
|
||||
# error: [too-many-positional-arguments] "Too many positional arguments to wrapper descriptor `FunctionType.__get__`: expected 3, got 4"
|
||||
# error: [no-matching-overload] "No overload of wrapper descriptor `FunctionType.__get__` matches arguments"
|
||||
wrapper_descriptor(f, None, type(f), "one too many")
|
||||
```
|
||||
|
||||
## Error handling and edge cases
|
||||
|
||||
### `__get__` is called with correct arguments
|
||||
|
||||
This test makes sure that we call `__get__` with the right argument types for various scenarios:
|
||||
|
||||
```py
|
||||
from __future__ import annotations
|
||||
|
||||
class TailoredForClassObjectAccess:
|
||||
def __get__(self, instance: None, owner: type[C]) -> int:
|
||||
return 1
|
||||
|
||||
class TailoredForInstanceAccess:
|
||||
def __get__(self, instance: C, owner: type[C] | None = None) -> str:
|
||||
return "a"
|
||||
|
||||
class TailoredForMetaclassAccess:
|
||||
def __get__(self, instance: type[C], owner: type[Meta]) -> bytes:
|
||||
return b"a"
|
||||
|
||||
class Meta(type):
|
||||
metaclass_access: TailoredForMetaclassAccess = TailoredForMetaclassAccess()
|
||||
|
||||
class C(metaclass=Meta):
|
||||
class_object_access: TailoredForClassObjectAccess = TailoredForClassObjectAccess()
|
||||
instance_access: TailoredForInstanceAccess = TailoredForInstanceAccess()
|
||||
|
||||
reveal_type(C.class_object_access) # revealed: int
|
||||
reveal_type(C().instance_access) # revealed: str
|
||||
reveal_type(C.metaclass_access) # revealed: bytes
|
||||
|
||||
# TODO: These should emit a diagnostic
|
||||
reveal_type(C().class_object_access) # revealed: TailoredForClassObjectAccess
|
||||
reveal_type(C.instance_access) # revealed: TailoredForInstanceAccess
|
||||
```
|
||||
|
||||
### Descriptors with incorrect `__get__` signature
|
||||
|
||||
```py
|
||||
class Descriptor:
|
||||
# `__get__` method with missing parameters:
|
||||
def __get__(self) -> int:
|
||||
return 1
|
||||
|
||||
class C:
|
||||
descriptor: Descriptor = Descriptor()
|
||||
|
||||
# TODO: This should be an error
|
||||
reveal_type(C.descriptor) # revealed: Descriptor
|
||||
|
||||
# TODO: This should be an error
|
||||
reveal_type(C().descriptor) # revealed: Descriptor
|
||||
```
|
||||
|
||||
### Undeclared descriptor arguments
|
||||
|
||||
If a descriptor attribute is not declared, we union with `Unknown`, just like for regular
|
||||
attributes, since that attribute could be overwritten externally. Even a data descriptor with a
|
||||
`__set__` method can be overwritten when accessed through a class object.
|
||||
|
||||
```py
|
||||
class Descriptor:
|
||||
def __get__(self, instance: object, owner: type | None = None) -> int:
|
||||
return 1
|
||||
|
||||
def __set__(self, instance: object, value: int) -> None:
|
||||
pass
|
||||
|
||||
class C:
|
||||
descriptor = Descriptor()
|
||||
|
||||
C.descriptor = "something else"
|
||||
|
||||
# This could also be `Literal["something else"]` if we support narrowing of attribute types based on assignments
|
||||
reveal_type(C.descriptor) # revealed: Unknown | int
|
||||
```
|
||||
|
||||
### Possibly unbound descriptor attributes
|
||||
|
||||
```py
|
||||
class DataDescriptor:
|
||||
def __get__(self, instance: object, owner: type | None = None) -> int:
|
||||
return 1
|
||||
|
||||
def __set__(self, instance: int, value) -> None:
|
||||
pass
|
||||
|
||||
class NonDataDescriptor:
|
||||
def __get__(self, instance: object, owner: type | None = None) -> int:
|
||||
return 1
|
||||
|
||||
def _(flag: bool):
|
||||
class PossiblyUnbound:
|
||||
if flag:
|
||||
non_data: NonDataDescriptor = NonDataDescriptor()
|
||||
data: DataDescriptor = DataDescriptor()
|
||||
|
||||
# error: [possibly-unbound-attribute] "Attribute `non_data` on type `Literal[PossiblyUnbound]` is possibly unbound"
|
||||
reveal_type(PossiblyUnbound.non_data) # revealed: int
|
||||
|
||||
# error: [possibly-unbound-attribute] "Attribute `non_data` on type `PossiblyUnbound` is possibly unbound"
|
||||
reveal_type(PossiblyUnbound().non_data) # revealed: int
|
||||
|
||||
# error: [possibly-unbound-attribute] "Attribute `data` on type `Literal[PossiblyUnbound]` is possibly unbound"
|
||||
reveal_type(PossiblyUnbound.data) # revealed: int
|
||||
|
||||
# error: [possibly-unbound-attribute] "Attribute `data` on type `PossiblyUnbound` is possibly unbound"
|
||||
reveal_type(PossiblyUnbound().data) # revealed: int
|
||||
```
|
||||
|
||||
### Possibly-unbound `__get__` method
|
||||
|
||||
```py
|
||||
def _(flag: bool):
|
||||
class MaybeDescriptor:
|
||||
if flag:
|
||||
def __get__(self, instance: object, owner: type | None = None) -> int:
|
||||
return 1
|
||||
|
||||
class C:
|
||||
descriptor: MaybeDescriptor = MaybeDescriptor()
|
||||
|
||||
reveal_type(C.descriptor) # revealed: int | MaybeDescriptor
|
||||
|
||||
reveal_type(C().descriptor) # revealed: int | MaybeDescriptor
|
||||
```
|
||||
|
||||
### Descriptors with non-function `__get__` callables that are descriptors themselves
|
||||
|
||||
The descriptor protocol is recursive, i.e. looking up `__get__` can involve triggering the
|
||||
descriptor protocol on the callable's `__call__` method:
|
||||
|
||||
```py
|
||||
from __future__ import annotations
|
||||
|
||||
class ReturnedCallable2:
|
||||
def __call__(self, descriptor: Descriptor1, instance: None, owner: type[C]) -> int:
|
||||
return 1
|
||||
|
||||
class ReturnedCallable1:
|
||||
def __call__(self, descriptor: Descriptor2, instance: Callable1, owner: type[Callable1]) -> ReturnedCallable2:
|
||||
return ReturnedCallable2()
|
||||
|
||||
class Callable3:
|
||||
def __call__(self, descriptor: Descriptor3, instance: Callable2, owner: type[Callable2]) -> ReturnedCallable1:
|
||||
return ReturnedCallable1()
|
||||
|
||||
class Descriptor3:
|
||||
__get__: Callable3 = Callable3()
|
||||
|
||||
class Callable2:
|
||||
__call__: Descriptor3 = Descriptor3()
|
||||
|
||||
class Descriptor2:
|
||||
__get__: Callable2 = Callable2()
|
||||
|
||||
class Callable1:
|
||||
__call__: Descriptor2 = Descriptor2()
|
||||
|
||||
class Descriptor1:
|
||||
__get__: Callable1 = Callable1()
|
||||
|
||||
class C:
|
||||
d: Descriptor1 = Descriptor1()
|
||||
|
||||
reveal_type(C.d) # revealed: int
|
||||
```
|
||||
|
||||
[descriptors]: https://docs.python.org/3/howto/descriptor.html
|
||||
[precedence chain]: https://github.com/python/cpython/blob/3.13/Objects/typeobject.c#L5393-L5481
|
||||
[simple example]: https://docs.python.org/3/howto/descriptor.html#simple-example-a-descriptor-that-returns-a-constant
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
# Attribute assignment
|
||||
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
This test suite demonstrates various kinds of diagnostics that can be emitted in a
|
||||
`obj.attr = value` assignment.
|
||||
|
||||
## Instance attributes with class-level defaults
|
||||
|
||||
These can be set on instances and on class objects.
|
||||
|
||||
```py
|
||||
class C:
|
||||
attr: int = 0
|
||||
|
||||
instance = C()
|
||||
instance.attr = 1 # fine
|
||||
instance.attr = "wrong" # error: [invalid-assignment]
|
||||
|
||||
C.attr = 1 # fine
|
||||
C.attr = "wrong" # error: [invalid-assignment]
|
||||
```
|
||||
|
||||
## Pure instance attributes
|
||||
|
||||
These can only be set on instances. When trying to set them on class objects, we generate a useful
|
||||
diagnostic that mentions that the attribute is only available on instances.
|
||||
|
||||
```py
|
||||
class C:
|
||||
def __init__(self):
|
||||
self.attr: int = 0
|
||||
|
||||
instance = C()
|
||||
instance.attr = 1 # fine
|
||||
instance.attr = "wrong" # error: [invalid-assignment]
|
||||
|
||||
C.attr = 1 # error: [invalid-attribute-access]
|
||||
```
|
||||
|
||||
## `ClassVar`s
|
||||
|
||||
These can only be set on class objects. When trying to set them on instances, we generate a useful
|
||||
diagnostic that mentions that the attribute is only available on class objects.
|
||||
|
||||
```py
|
||||
from typing import ClassVar
|
||||
|
||||
class C:
|
||||
attr: ClassVar[int] = 0
|
||||
|
||||
C.attr = 1 # fine
|
||||
C.attr = "wrong" # error: [invalid-assignment]
|
||||
|
||||
instance = C()
|
||||
instance.attr = 1 # error: [invalid-attribute-access]
|
||||
```
|
||||
|
||||
## Unknown attributes
|
||||
|
||||
When trying to set an attribute that is not defined, we also emit errors:
|
||||
|
||||
```py
|
||||
class C: ...
|
||||
|
||||
C.non_existent = 1 # error: [unresolved-attribute]
|
||||
|
||||
instance = C()
|
||||
instance.non_existent = 1 # error: [unresolved-attribute]
|
||||
```
|
||||
|
||||
## Possibly-unbound attributes
|
||||
|
||||
When trying to set an attribute that is not defined in all branches, we emit errors:
|
||||
|
||||
```py
|
||||
def _(flag: bool) -> None:
|
||||
class C:
|
||||
if flag:
|
||||
attr: int = 0
|
||||
|
||||
C.attr = 1 # error: [possibly-unbound-attribute]
|
||||
|
||||
instance = C()
|
||||
instance.attr = 1 # error: [possibly-unbound-attribute]
|
||||
```
|
||||
|
||||
## Data descriptors
|
||||
|
||||
When assigning to a data descriptor attribute, we implicitly call the descriptor's `__set__` method.
|
||||
This can lead to various kinds of diagnostics.
|
||||
|
||||
### Invalid argument type
|
||||
|
||||
```py
|
||||
class Descriptor:
|
||||
def __set__(self, instance: object, value: int) -> None:
|
||||
pass
|
||||
|
||||
class C:
|
||||
attr: Descriptor = Descriptor()
|
||||
|
||||
instance = C()
|
||||
instance.attr = 1 # fine
|
||||
|
||||
# TODO: ideally, we would mention why this is an invalid assignment (wrong argument type for `value` parameter)
|
||||
instance.attr = "wrong" # error: [invalid-assignment]
|
||||
```
|
||||
|
||||
### Invalid `__set__` method signature
|
||||
|
||||
```py
|
||||
class WrongDescriptor:
|
||||
def __set__(self, instance: object, value: int, extra: int) -> None:
|
||||
pass
|
||||
|
||||
class C:
|
||||
attr: WrongDescriptor = WrongDescriptor()
|
||||
|
||||
instance = C()
|
||||
|
||||
# TODO: ideally, we would mention why this is an invalid assignment (wrong number of arguments for `__set__`)
|
||||
instance.attr = 1 # error: [invalid-assignment]
|
||||
```
|
||||
|
||||
## Setting attributes on union types
|
||||
|
||||
```py
|
||||
def _(flag: bool) -> None:
|
||||
if flag:
|
||||
class C1:
|
||||
attr: int = 0
|
||||
|
||||
else:
|
||||
class C1:
|
||||
attr: str = ""
|
||||
|
||||
# TODO: The error message here could be improved to explain why the assignment fails.
|
||||
C1.attr = 1 # error: [invalid-assignment]
|
||||
|
||||
class C2:
|
||||
if flag:
|
||||
attr: int = 0
|
||||
else:
|
||||
attr: str = ""
|
||||
|
||||
# TODO: This should be an error
|
||||
C2.attr = 1
|
||||
```
|
||||
@@ -0,0 +1,14 @@
|
||||
# No matching overload diagnostics
|
||||
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
## Calls to overloaded functions
|
||||
|
||||
TODO: Note that we do not yet support the `@overload` decorator to define overloaded functions in
|
||||
real Python code. We are instead testing a special-cased function where we create an overloaded
|
||||
signature internally. Update this to an `@overload` function in the Python snippet itself once we
|
||||
can.
|
||||
|
||||
```py
|
||||
type("Foo", ()) # error: [no-matching-overload]
|
||||
```
|
||||
@@ -16,8 +16,10 @@ reveal_type(cast(int | str, 1)) # revealed: int | str
|
||||
# error: [invalid-type-form]
|
||||
reveal_type(cast(Literal, True)) # revealed: Unknown
|
||||
|
||||
# error: [invalid-type-form]
|
||||
reveal_type(cast(1, True)) # revealed: Unknown
|
||||
|
||||
# TODO: These should be errors
|
||||
cast(1)
|
||||
cast(str)
|
||||
cast(str, b"ar", "foo")
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
```py
|
||||
class NotBoolable:
|
||||
__bool__ = 3
|
||||
__bool__: int = 3
|
||||
|
||||
# error: [unsupported-bool-conversion] "Boolean conversion is unsupported for type `NotBoolable`; its `__bool__` method isn't callable"
|
||||
assert NotBoolable()
|
||||
|
||||
@@ -121,7 +121,7 @@ if NotBoolable():
|
||||
|
||||
```py
|
||||
class NotBoolable:
|
||||
__bool__ = None
|
||||
__bool__: None = None
|
||||
|
||||
# error: [unsupported-bool-conversion] "Boolean conversion is unsupported for type `NotBoolable`; its `__bool__` method isn't callable"
|
||||
if NotBoolable():
|
||||
@@ -133,9 +133,9 @@ if NotBoolable():
|
||||
```py
|
||||
def test(cond: bool):
|
||||
class NotBoolable:
|
||||
__bool__ = None if cond else 3
|
||||
__bool__: int | None = None if cond else 3
|
||||
|
||||
# error: [unsupported-bool-conversion] "Boolean conversion is unsupported for type `NotBoolable`; it incorrectly implements `__bool__`"
|
||||
# error: [unsupported-bool-conversion] "Boolean conversion is unsupported for type `NotBoolable`; its `__bool__` method isn't callable"
|
||||
if NotBoolable():
|
||||
...
|
||||
```
|
||||
@@ -145,7 +145,7 @@ def test(cond: bool):
|
||||
```py
|
||||
def test(cond: bool):
|
||||
class NotBoolable:
|
||||
__bool__ = None
|
||||
__bool__: None = None
|
||||
|
||||
a = 10 if cond else NotBoolable()
|
||||
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
`lambda` expressions can be defined without any parameters.
|
||||
|
||||
```py
|
||||
reveal_type(lambda: 1) # revealed: () -> @Todo(lambda return type)
|
||||
reveal_type(lambda: 1) # revealed: () -> Unknown
|
||||
|
||||
# error: [unresolved-reference]
|
||||
reveal_type(lambda: a) # revealed: () -> @Todo(lambda return type)
|
||||
reveal_type(lambda: a) # revealed: () -> Unknown
|
||||
```
|
||||
|
||||
## With parameters
|
||||
@@ -17,45 +17,45 @@ Unlike parameters in function definition, the parameters in a `lambda` expressio
|
||||
annotated.
|
||||
|
||||
```py
|
||||
reveal_type(lambda a: a) # revealed: (a) -> @Todo(lambda return type)
|
||||
reveal_type(lambda a, b: a + b) # revealed: (a, b) -> @Todo(lambda return type)
|
||||
reveal_type(lambda a: a) # revealed: (a) -> Unknown
|
||||
reveal_type(lambda a, b: a + b) # revealed: (a, b) -> Unknown
|
||||
```
|
||||
|
||||
But, it can have default values:
|
||||
|
||||
```py
|
||||
reveal_type(lambda a=1: a) # revealed: (a=Literal[1]) -> @Todo(lambda return type)
|
||||
reveal_type(lambda a, b=2: a) # revealed: (a, b=Literal[2]) -> @Todo(lambda return type)
|
||||
reveal_type(lambda a=1: a) # revealed: (a=Literal[1]) -> Unknown
|
||||
reveal_type(lambda a, b=2: a) # revealed: (a, b=Literal[2]) -> Unknown
|
||||
```
|
||||
|
||||
And, positional-only parameters:
|
||||
|
||||
```py
|
||||
reveal_type(lambda a, b, /, c: c) # revealed: (a, b, /, c) -> @Todo(lambda return type)
|
||||
reveal_type(lambda a, b, /, c: c) # revealed: (a, b, /, c) -> Unknown
|
||||
```
|
||||
|
||||
And, keyword-only parameters:
|
||||
|
||||
```py
|
||||
reveal_type(lambda a, *, b=2, c: b) # revealed: (a, *, b=Literal[2], c) -> @Todo(lambda return type)
|
||||
reveal_type(lambda a, *, b=2, c: b) # revealed: (a, *, b=Literal[2], c) -> Unknown
|
||||
```
|
||||
|
||||
And, variadic parameter:
|
||||
|
||||
```py
|
||||
reveal_type(lambda *args: args) # revealed: (*args) -> @Todo(lambda return type)
|
||||
reveal_type(lambda *args: args) # revealed: (*args) -> Unknown
|
||||
```
|
||||
|
||||
And, keyword-varidic parameter:
|
||||
|
||||
```py
|
||||
reveal_type(lambda **kwargs: kwargs) # revealed: (**kwargs) -> @Todo(lambda return type)
|
||||
reveal_type(lambda **kwargs: kwargs) # revealed: (**kwargs) -> Unknown
|
||||
```
|
||||
|
||||
Mixing all of them together:
|
||||
|
||||
```py
|
||||
# revealed: (a, b, /, c=Literal[True], *args, *, d=Literal["default"], e=Literal[5], **kwargs) -> @Todo(lambda return type)
|
||||
# revealed: (a, b, /, c=Literal[True], *args, *, d=Literal["default"], e=Literal[5], **kwargs) -> Unknown
|
||||
reveal_type(lambda a, b, /, c=True, *args, d="default", e=5, **kwargs: None)
|
||||
```
|
||||
|
||||
@@ -96,5 +96,5 @@ Here, a `lambda` expression is used as the default value for a parameter in anot
|
||||
expression.
|
||||
|
||||
```py
|
||||
reveal_type(lambda a=lambda x, y: 0: 2) # revealed: (a=(x, y) -> @Todo(lambda return type)) -> @Todo(lambda return type)
|
||||
reveal_type(lambda a=lambda x, y: 0: 2) # revealed: (a=(x, y) -> Unknown) -> Unknown
|
||||
```
|
||||
|
||||
@@ -99,22 +99,28 @@ The returned value of `__len__` is implicitly and recursively converted to `int`
|
||||
from typing import Literal
|
||||
|
||||
class Zero:
|
||||
def __len__(self) -> Literal[0]: ...
|
||||
def __len__(self) -> Literal[0]:
|
||||
return 0
|
||||
|
||||
class ZeroOrOne:
|
||||
def __len__(self) -> Literal[0, 1]: ...
|
||||
def __len__(self) -> Literal[0, 1]:
|
||||
return 0
|
||||
|
||||
class ZeroOrTrue:
|
||||
def __len__(self) -> Literal[0, True]: ...
|
||||
def __len__(self) -> Literal[0, True]:
|
||||
return 0
|
||||
|
||||
class OneOrFalse:
|
||||
def __len__(self) -> Literal[1] | Literal[False]: ...
|
||||
def __len__(self) -> Literal[1] | Literal[False]:
|
||||
return 1
|
||||
|
||||
class OneOrFoo:
|
||||
def __len__(self) -> Literal[1, "foo"]: ...
|
||||
def __len__(self) -> Literal[1, "foo"]:
|
||||
return 1
|
||||
|
||||
class ZeroOrStr:
|
||||
def __len__(self) -> Literal[0] | str: ...
|
||||
def __len__(self) -> Literal[0] | str:
|
||||
return 0
|
||||
|
||||
reveal_type(len(Zero())) # revealed: Literal[0]
|
||||
reveal_type(len(ZeroOrOne())) # revealed: Literal[0, 1]
|
||||
@@ -134,10 +140,12 @@ reveal_type(len(ZeroOrStr())) # revealed: int
|
||||
from typing import Literal
|
||||
|
||||
class LiteralTrue:
|
||||
def __len__(self) -> Literal[True]: ...
|
||||
def __len__(self) -> Literal[True]:
|
||||
return True
|
||||
|
||||
class LiteralFalse:
|
||||
def __len__(self) -> Literal[False]: ...
|
||||
def __len__(self) -> Literal[False]:
|
||||
return False
|
||||
|
||||
reveal_type(len(LiteralTrue())) # revealed: Literal[1]
|
||||
reveal_type(len(LiteralFalse())) # revealed: Literal[0]
|
||||
@@ -157,19 +165,24 @@ class SomeEnum(Enum):
|
||||
INT_2 = 3_2
|
||||
|
||||
class Auto:
|
||||
def __len__(self) -> Literal[SomeEnum.AUTO]: ...
|
||||
def __len__(self) -> Literal[SomeEnum.AUTO]:
|
||||
return SomeEnum.AUTO
|
||||
|
||||
class Int:
|
||||
def __len__(self) -> Literal[SomeEnum.INT]: ...
|
||||
def __len__(self) -> Literal[SomeEnum.INT]:
|
||||
return SomeEnum.INT
|
||||
|
||||
class Str:
|
||||
def __len__(self) -> Literal[SomeEnum.STR]: ...
|
||||
def __len__(self) -> Literal[SomeEnum.STR]:
|
||||
return SomeEnum.STR
|
||||
|
||||
class Tuple:
|
||||
def __len__(self) -> Literal[SomeEnum.TUPLE]: ...
|
||||
def __len__(self) -> Literal[SomeEnum.TUPLE]:
|
||||
return SomeEnum.TUPLE
|
||||
|
||||
class IntUnion:
|
||||
def __len__(self) -> Literal[SomeEnum.INT, SomeEnum.INT_2]: ...
|
||||
def __len__(self) -> Literal[SomeEnum.INT, SomeEnum.INT_2]:
|
||||
return SomeEnum.INT
|
||||
|
||||
reveal_type(len(Auto())) # revealed: int
|
||||
reveal_type(len(Int())) # revealed: int
|
||||
@@ -184,7 +197,8 @@ reveal_type(len(IntUnion())) # revealed: int
|
||||
from typing import Literal
|
||||
|
||||
class Negative:
|
||||
def __len__(self) -> Literal[-1]: ...
|
||||
def __len__(self) -> Literal[-1]:
|
||||
return -1
|
||||
|
||||
# TODO: Emit a diagnostic
|
||||
reveal_type(len(Negative())) # revealed: int
|
||||
@@ -196,10 +210,12 @@ reveal_type(len(Negative())) # revealed: int
|
||||
from typing import Literal
|
||||
|
||||
class SecondOptionalArgument:
|
||||
def __len__(self, v: int = 0) -> Literal[0]: ...
|
||||
def __len__(self, v: int = 0) -> Literal[0]:
|
||||
return 0
|
||||
|
||||
class SecondRequiredArgument:
|
||||
def __len__(self, v: int) -> Literal[1]: ...
|
||||
def __len__(self, v: int) -> Literal[1]:
|
||||
return 1
|
||||
|
||||
# TODO: Emit a diagnostic
|
||||
reveal_type(len(SecondOptionalArgument())) # revealed: Literal[0]
|
||||
|
||||
@@ -0,0 +1,245 @@
|
||||
# Function return type
|
||||
|
||||
When a function's return type is annotated, all return statements are checked to ensure that the
|
||||
type of the returned value is assignable to the annotated return type.
|
||||
|
||||
## Basic examples
|
||||
|
||||
A return value assignable to the annotated return type is valid.
|
||||
|
||||
```py
|
||||
def f() -> int:
|
||||
return 1
|
||||
```
|
||||
|
||||
The type of the value obtained by calling a function is the annotated return type, not the inferred
|
||||
return type.
|
||||
|
||||
```py
|
||||
reveal_type(f()) # revealed: int
|
||||
```
|
||||
|
||||
A `raise` is equivalent to a return of `Never`, which is assignable to any annotated return type.
|
||||
|
||||
```py
|
||||
def f() -> str:
|
||||
raise ValueError()
|
||||
|
||||
reveal_type(f()) # revealed: str
|
||||
```
|
||||
|
||||
## Stub functions
|
||||
|
||||
"Stub" function definitions (that is, function definitions with an empty body) are permissible in
|
||||
stub files, or in a few other locations: Protocol method definitions, abstract methods, and
|
||||
overloads. In this case the function body is considered to be omitted (thus no return type checking
|
||||
is performed on it), not assumed to implicitly return `None`.
|
||||
|
||||
A stub function's "empty" body may contain only an optional docstring, followed (optionally) by an
|
||||
ellipsis (`...`) or `pass`.
|
||||
|
||||
### In stub file
|
||||
|
||||
```pyi
|
||||
def f() -> int: ...
|
||||
|
||||
def f() -> int:
|
||||
pass
|
||||
|
||||
def f() -> int:
|
||||
"""Some docstring"""
|
||||
|
||||
def f() -> int:
|
||||
"""Some docstring"""
|
||||
...
|
||||
```
|
||||
|
||||
### In Protocol
|
||||
|
||||
```py
|
||||
from typing import Protocol
|
||||
|
||||
class Bar(Protocol):
|
||||
# TODO: no error
|
||||
# error: [invalid-return-type]
|
||||
def f(self) -> int: ...
|
||||
```
|
||||
|
||||
### In abstract method
|
||||
|
||||
```py
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
class Foo(ABC):
|
||||
@abstractmethod
|
||||
# TODO: no error
|
||||
# error: [invalid-return-type]
|
||||
def f(self) -> int: ...
|
||||
@abstractmethod
|
||||
# error: [invalid-return-type]
|
||||
def g[T](self, x: T) -> T: ...
|
||||
```
|
||||
|
||||
### In overload
|
||||
|
||||
```py
|
||||
from typing import overload
|
||||
|
||||
@overload
|
||||
def f(x: int) -> int: ...
|
||||
@overload
|
||||
def f(x: str) -> str: ...
|
||||
def f(x: int | str):
|
||||
return x
|
||||
```
|
||||
|
||||
## Conditional return type
|
||||
|
||||
```py
|
||||
def f(cond: bool) -> int:
|
||||
if cond:
|
||||
return 1
|
||||
else:
|
||||
return 2
|
||||
|
||||
def f(cond: bool) -> int | None:
|
||||
if cond:
|
||||
return 1
|
||||
else:
|
||||
return
|
||||
|
||||
def f(cond: bool) -> int:
|
||||
if cond:
|
||||
return 1
|
||||
else:
|
||||
raise ValueError()
|
||||
|
||||
def f(cond: bool) -> str | int:
|
||||
if cond:
|
||||
return "a"
|
||||
else:
|
||||
return 1
|
||||
```
|
||||
|
||||
## Implicit return type
|
||||
|
||||
```py
|
||||
def f(cond: bool) -> int | None:
|
||||
if cond:
|
||||
return 1
|
||||
|
||||
# no implicit return
|
||||
def f() -> int:
|
||||
if True:
|
||||
return 1
|
||||
|
||||
# no implicit return
|
||||
def f(cond: bool) -> int:
|
||||
cond = True
|
||||
if cond:
|
||||
return 1
|
||||
|
||||
def f(cond: bool) -> int:
|
||||
if cond:
|
||||
cond = True
|
||||
else:
|
||||
return 1
|
||||
if cond:
|
||||
return 2
|
||||
```
|
||||
|
||||
## Invalid return type
|
||||
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
```py
|
||||
# error: [invalid-return-type]
|
||||
def f() -> int:
|
||||
1
|
||||
|
||||
def f() -> str:
|
||||
# error: [invalid-return-type]
|
||||
return 1
|
||||
|
||||
def f() -> int:
|
||||
# error: [invalid-return-type]
|
||||
return
|
||||
|
||||
from typing import TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
# TODO: `invalid-return-type` error should be emitted
|
||||
def m(x: T) -> T: ...
|
||||
```
|
||||
|
||||
## Invalid return type in stub file
|
||||
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
```pyi
|
||||
def f() -> int:
|
||||
# error: [invalid-return-type]
|
||||
return ...
|
||||
|
||||
# error: [invalid-return-type]
|
||||
def foo() -> int:
|
||||
print("...")
|
||||
...
|
||||
|
||||
# error: [invalid-return-type]
|
||||
def foo() -> int:
|
||||
f"""{foo} is a function that ..."""
|
||||
...
|
||||
```
|
||||
|
||||
## Invalid conditional return type
|
||||
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
```py
|
||||
def f(cond: bool) -> str:
|
||||
if cond:
|
||||
return "a"
|
||||
else:
|
||||
# error: [invalid-return-type]
|
||||
return 1
|
||||
|
||||
def f(cond: bool) -> str:
|
||||
if cond:
|
||||
# error: [invalid-return-type]
|
||||
return 1
|
||||
else:
|
||||
# error: [invalid-return-type]
|
||||
return 2
|
||||
```
|
||||
|
||||
## Invalid implicit return type
|
||||
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
```py
|
||||
def f() -> None:
|
||||
if False:
|
||||
# error: [invalid-return-type]
|
||||
return 1
|
||||
|
||||
# error: [invalid-return-type]
|
||||
def f(cond: bool) -> int:
|
||||
if cond:
|
||||
return 1
|
||||
|
||||
# error: [invalid-return-type]
|
||||
def f(cond: bool) -> int:
|
||||
if cond:
|
||||
raise ValueError()
|
||||
|
||||
# error: [invalid-return-type]
|
||||
def f(cond: bool) -> int:
|
||||
if cond:
|
||||
cond = False
|
||||
else:
|
||||
return 1
|
||||
if cond:
|
||||
return 2
|
||||
```
|
||||
@@ -162,6 +162,8 @@ class Sub(Base[Sub]): ...
|
||||
reveal_type(Sub) # revealed: Literal[Sub]
|
||||
```
|
||||
|
||||
A similar case can work in a non-stub file, if forward references are stringified:
|
||||
|
||||
`string_annotation.py`:
|
||||
|
||||
```py
|
||||
@@ -174,14 +176,25 @@ class Sub(Base["Sub"]): ...
|
||||
reveal_type(Sub) # revealed: Literal[Sub]
|
||||
```
|
||||
|
||||
In a non-stub file, without stringified forward references, this raises a `NameError`:
|
||||
|
||||
`bare_annotation.py`:
|
||||
|
||||
```py
|
||||
class Base[T]: ...
|
||||
|
||||
# TODO: error: [unresolved-reference]
|
||||
# error: [non-subscriptable]
|
||||
class Sub(Base[Sub]): ...
|
||||
```
|
||||
|
||||
## Another cyclic case
|
||||
|
||||
```pyi
|
||||
# TODO no error (generics)
|
||||
# error: [invalid-base]
|
||||
class Derived[T](list[Derived[T]]): ...
|
||||
```
|
||||
|
||||
[crtp]: https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern
|
||||
[f-bound]: https://en.wikipedia.org/wiki/Bounded_quantification#F-bounded_quantification
|
||||
|
||||
@@ -35,7 +35,7 @@ Each typevar must also appear _somewhere_ in the parameter list:
|
||||
```py
|
||||
def absurd[T]() -> T:
|
||||
# There's no way to construct a T!
|
||||
...
|
||||
raise ValueError("absurd")
|
||||
```
|
||||
|
||||
## Inferring generic function parameter types
|
||||
@@ -48,7 +48,8 @@ whether we want to infer a more specific `Literal` type where possible, or use h
|
||||
the inferred type to e.g. `int`.
|
||||
|
||||
```py
|
||||
def f[T](x: T) -> T: ...
|
||||
def f[T](x: T) -> T:
|
||||
return x
|
||||
|
||||
# TODO: no error
|
||||
# TODO: revealed: int or Literal[1]
|
||||
@@ -77,7 +78,8 @@ The matching up of call arguments and discovery of constraints on typevars can b
|
||||
process for arbitrarily-nested generic types in parameters.
|
||||
|
||||
```py
|
||||
def f[T](x: list[T]) -> T: ...
|
||||
def f[T](x: list[T]) -> T:
|
||||
return x[0]
|
||||
|
||||
# TODO: revealed: float
|
||||
reveal_type(f([1.0, 2.0])) # revealed: T
|
||||
@@ -119,7 +121,7 @@ def different_types[T, S](cond: bool, t: T, s: S) -> T:
|
||||
if cond:
|
||||
return t
|
||||
else:
|
||||
# TODO: error: S is not assignable to T
|
||||
# error: [invalid-return-type] "Object of type `S` is not assignable to return type `T`"
|
||||
return s
|
||||
|
||||
def same_types[T](cond: bool, t1: T, t2: T) -> T:
|
||||
|
||||
@@ -37,8 +37,11 @@ from typing import TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
def f1(x: T) -> T: ...
|
||||
def f2(x: T) -> T: ...
|
||||
def f1(x: T) -> T:
|
||||
return x
|
||||
|
||||
def f2(x: T) -> T:
|
||||
return x
|
||||
|
||||
f1(1)
|
||||
f2("a")
|
||||
@@ -53,7 +56,8 @@ This also applies to a single generic function being used multiple times, instan
|
||||
to a different type each time.
|
||||
|
||||
```py
|
||||
def f[T](x: T) -> T: ...
|
||||
def f[T](x: T) -> T:
|
||||
return x
|
||||
|
||||
# TODO: no error
|
||||
# TODO: revealed: int or Literal[1]
|
||||
@@ -72,8 +76,11 @@ reveal_type(f("a")) # revealed: T
|
||||
|
||||
```py
|
||||
class C[T]:
|
||||
def m1(self, x: T) -> T: ...
|
||||
def m2(self, x: T) -> T: ...
|
||||
def m1(self, x: T) -> T:
|
||||
return x
|
||||
|
||||
def m2(self, x: T) -> T:
|
||||
return x
|
||||
|
||||
c: C[int] = C()
|
||||
# TODO: no error
|
||||
@@ -101,18 +108,20 @@ S = TypeVar("S")
|
||||
# TODO: no error
|
||||
# error: [invalid-base]
|
||||
class Legacy(Generic[T]):
|
||||
def m(self, x: T, y: S) -> S: ...
|
||||
def m(self, x: T, y: S) -> S:
|
||||
return y
|
||||
|
||||
legacy: Legacy[int] = Legacy()
|
||||
# TODO: revealed: str
|
||||
reveal_type(legacy.m(1, "string")) # revealed: @Todo(Invalid or unsupported `Instance` in `Type::to_type_expression`)
|
||||
reveal_type(legacy.m(1, "string")) # revealed: @Todo(Support for `typing.TypeVar` instances in type expressions)
|
||||
```
|
||||
|
||||
With PEP 695 syntax, it is clearer that the method uses a separate typevar:
|
||||
|
||||
```py
|
||||
class C[T]:
|
||||
def m[S](self, x: T, y: S) -> S: ...
|
||||
def m[S](self, x: T, y: S) -> S:
|
||||
return y
|
||||
|
||||
c: C[int] = C()
|
||||
# TODO: no errors
|
||||
@@ -147,7 +156,8 @@ class C(Generic[T]):
|
||||
x: list[S] = []
|
||||
|
||||
# This is not an error, as shown in the previous test
|
||||
def m(self, x: S) -> S: ...
|
||||
def m(self, x: S) -> S:
|
||||
return x
|
||||
```
|
||||
|
||||
This is true with PEP 695 syntax, as well, though we must use the legacy syntax to define the
|
||||
@@ -169,8 +179,11 @@ class C[T]:
|
||||
# TODO: error
|
||||
x: list[S] = []
|
||||
|
||||
def m1(self, x: S) -> S: ...
|
||||
def m2[S](self, x: S) -> S: ...
|
||||
def m1(self, x: S) -> S:
|
||||
return x
|
||||
|
||||
def m2[S](self, x: S) -> S:
|
||||
return x
|
||||
```
|
||||
|
||||
## Nested formal typevars must be distinct
|
||||
|
||||
@@ -140,3 +140,27 @@ import b.foo # error: [unresolved-import] "Cannot resolve import `b.foo`"
|
||||
|
||||
```py
|
||||
```
|
||||
|
||||
## Long paths
|
||||
|
||||
It's unlikely that a single module component is as long as in this example, but Windows treats paths
|
||||
that are longer than 200 and something specially. This test ensures that Red Knot can handle those
|
||||
paths gracefully.
|
||||
|
||||
```toml
|
||||
system = "os"
|
||||
```
|
||||
|
||||
`AveryLongPathAveryLongPathAveryLongPathAveryLongPathAveryLongPathAveryLongPathAveryLongPathAveryLongPathAveryLongPathAveryLongPathAveryLongPathAveryLongPathAveryLongPathAveryLongPathAveryLongPathAveryLongPathAveryLongPath/__init__.py`:
|
||||
|
||||
```py
|
||||
class Foo: ...
|
||||
```
|
||||
|
||||
```py
|
||||
from AveryLongPathAveryLongPathAveryLongPathAveryLongPathAveryLongPathAveryLongPathAveryLongPathAveryLongPathAveryLongPathAveryLongPathAveryLongPathAveryLongPathAveryLongPathAveryLongPathAveryLongPathAveryLongPathAveryLongPath import (
|
||||
Foo,
|
||||
)
|
||||
|
||||
reveal_type(Foo()) # revealed: Foo
|
||||
```
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
# Case Sensitive Imports
|
||||
|
||||
```toml
|
||||
# TODO: This test should use the real file system instead of the memory file system.
|
||||
# but we can't change the file system yet because the tests would then start failing for
|
||||
# case-insensitive file systems.
|
||||
#system = "os"
|
||||
system = "os"
|
||||
```
|
||||
|
||||
Python's import system is case-sensitive even on case-insensitive file system. This means, importing
|
||||
|
||||
@@ -663,6 +663,7 @@ to the fact that `bool` is a `@final` class at runtime that cannot be subclassed
|
||||
|
||||
```py
|
||||
from knot_extensions import Intersection, Not, AlwaysTruthy, AlwaysFalsy
|
||||
from typing_extensions import Literal
|
||||
|
||||
class P: ...
|
||||
|
||||
@@ -686,6 +687,19 @@ def f(
|
||||
reveal_type(f) # revealed: Never
|
||||
reveal_type(g) # revealed: Never
|
||||
reveal_type(h) # revealed: Never
|
||||
|
||||
def never(
|
||||
a: Intersection[Intersection[AlwaysFalsy, Not[Literal[False]]], bool],
|
||||
b: Intersection[Intersection[AlwaysTruthy, Not[Literal[True]]], bool],
|
||||
c: Intersection[Intersection[Literal[True], Not[AlwaysTruthy]], bool],
|
||||
d: Intersection[Intersection[Literal[False], Not[AlwaysFalsy]], bool],
|
||||
):
|
||||
# TODO: This should be `Never`
|
||||
reveal_type(a) # revealed: Literal[True]
|
||||
# TODO: This should be `Never`
|
||||
reveal_type(b) # revealed: Literal[False]
|
||||
reveal_type(c) # revealed: Never
|
||||
reveal_type(d) # revealed: Never
|
||||
```
|
||||
|
||||
## Simplification of `LiteralString`, `AlwaysTruthy` and `AlwaysFalsy`
|
||||
@@ -846,5 +860,19 @@ def mixed(
|
||||
reveal_type(i4) # revealed: Any & Unknown
|
||||
```
|
||||
|
||||
## Invalid
|
||||
|
||||
```py
|
||||
from knot_extensions import Intersection, Not
|
||||
|
||||
# error: [invalid-type-form] "`knot_extensions.Intersection` requires at least one argument when used in a type expression"
|
||||
def f(x: Intersection) -> None:
|
||||
reveal_type(x) # revealed: Unknown
|
||||
|
||||
# error: [invalid-type-form] "`knot_extensions.Not` requires exactly one argument when used in a type expression"
|
||||
def f(x: Not) -> None:
|
||||
reveal_type(x) # revealed: Unknown
|
||||
```
|
||||
|
||||
[complement laws]: https://en.wikipedia.org/wiki/Complement_(set_theory)
|
||||
[de morgan's laws]: https://en.wikipedia.org/wiki/De_Morgan%27s_laws
|
||||
|
||||
@@ -431,7 +431,7 @@ def _(flag: bool):
|
||||
# invalid signature because it only accepts a `str`,
|
||||
# but the old-style iteration protocol will pass it an `int`
|
||||
def __getitem__(self, key: str) -> bytes:
|
||||
return 42
|
||||
return bytes()
|
||||
|
||||
# error: [not-iterable]
|
||||
for x in Iterable():
|
||||
@@ -484,7 +484,7 @@ def _(flag1: bool, flag2: bool):
|
||||
return Iterator()
|
||||
if flag2:
|
||||
def __getitem__(self, key: int) -> bytes:
|
||||
return 42
|
||||
return bytes()
|
||||
|
||||
# error: [not-iterable]
|
||||
for x in Iterable():
|
||||
@@ -682,7 +682,7 @@ def _(flag: bool):
|
||||
return "foo"
|
||||
else:
|
||||
def __getitem__(self, item: str) -> int:
|
||||
return "foo"
|
||||
return 42
|
||||
|
||||
# error: [not-iterable]
|
||||
for x in Iterable1():
|
||||
@@ -723,7 +723,7 @@ def _(flag: bool, flag2: bool):
|
||||
return "foo"
|
||||
else:
|
||||
def __getitem__(self, item: str) -> int:
|
||||
return "foo"
|
||||
return 42
|
||||
if flag2:
|
||||
def __iter__(self) -> Iterator:
|
||||
return Iterator()
|
||||
|
||||
@@ -10,7 +10,8 @@ class Iterator:
|
||||
return 42
|
||||
|
||||
class Iterable:
|
||||
def __iter__(self) -> Iterator: ...
|
||||
def __iter__(self) -> Iterator:
|
||||
return Iterator()
|
||||
|
||||
def generator_function():
|
||||
yield from Iterable()
|
||||
|
||||
@@ -121,7 +121,7 @@ def _(flag: bool, flag2: bool):
|
||||
|
||||
```py
|
||||
class NotBoolable:
|
||||
__bool__ = 3
|
||||
__bool__: int = 3
|
||||
|
||||
# error: [unsupported-bool-conversion] "Boolean conversion is unsupported for type `NotBoolable`; its `__bool__` method isn't callable"
|
||||
while NotBoolable():
|
||||
|
||||
@@ -166,7 +166,8 @@ When a class has an explicit `metaclass` that is not a class, but is a callable
|
||||
`type.__new__` arguments, we should return the meta-type of its return type.
|
||||
|
||||
```py
|
||||
def f(*args, **kwargs) -> int: ...
|
||||
def f(*args, **kwargs) -> int:
|
||||
return 1
|
||||
|
||||
class A(metaclass=f): ...
|
||||
|
||||
|
||||
@@ -22,11 +22,13 @@ def _(flag: bool):
|
||||
|
||||
# invalid invocation, too many positional args
|
||||
reveal_type(x) # revealed: Literal[1] | None
|
||||
if bool(x is not None, 5): # TODO diagnostic
|
||||
# error: [too-many-positional-arguments] "Too many positional arguments to class `bool`: expected 1, got 2"
|
||||
if bool(x is not None, 5):
|
||||
reveal_type(x) # revealed: Literal[1] | None
|
||||
|
||||
# invalid invocation, too many kwargs
|
||||
reveal_type(x) # revealed: Literal[1] | None
|
||||
if bool(x is not None, y=5): # TODO diagnostic
|
||||
# error: [unknown-argument] "Argument `y` does not match any known parameter of class `bool`"
|
||||
if bool(x is not None, y=5):
|
||||
reveal_type(x) # revealed: Literal[1] | None
|
||||
```
|
||||
|
||||
@@ -170,7 +170,8 @@ if issubclass(t, int):
|
||||
def issubclass(c, ci):
|
||||
return True
|
||||
|
||||
def flag() -> bool: ...
|
||||
def flag() -> bool:
|
||||
return True
|
||||
|
||||
t = int if flag() else str
|
||||
if issubclass(t, int):
|
||||
@@ -182,7 +183,8 @@ if issubclass(t, int):
|
||||
```py
|
||||
issubclass_alias = issubclass
|
||||
|
||||
def flag() -> bool: ...
|
||||
def flag() -> bool:
|
||||
return True
|
||||
|
||||
t = int if flag() else str
|
||||
if issubclass_alias(t, int):
|
||||
@@ -194,7 +196,8 @@ if issubclass_alias(t, int):
|
||||
```py
|
||||
from builtins import issubclass as imported_issubclass
|
||||
|
||||
def flag() -> bool: ...
|
||||
def flag() -> bool:
|
||||
return True
|
||||
|
||||
t = int if flag() else str
|
||||
if imported_issubclass(t, int):
|
||||
@@ -206,7 +209,8 @@ if imported_issubclass(t, int):
|
||||
```py
|
||||
from typing import Any
|
||||
|
||||
def flag() -> bool: ...
|
||||
def flag() -> bool:
|
||||
return True
|
||||
|
||||
t = int if flag() else str
|
||||
|
||||
@@ -229,7 +233,8 @@ if issubclass(t, Any):
|
||||
### Do not narrow if there are keyword arguments
|
||||
|
||||
```py
|
||||
def flag() -> bool: ...
|
||||
def flag() -> bool:
|
||||
return True
|
||||
|
||||
t = int if flag() else str
|
||||
|
||||
|
||||
@@ -20,7 +20,8 @@ def _(flag: bool):
|
||||
## Class patterns
|
||||
|
||||
```py
|
||||
def get_object() -> object: ...
|
||||
def get_object() -> object:
|
||||
return object()
|
||||
|
||||
class A: ...
|
||||
class B: ...
|
||||
@@ -42,10 +43,12 @@ reveal_type(x) # revealed: object
|
||||
## Class pattern with guard
|
||||
|
||||
```py
|
||||
def get_object() -> object: ...
|
||||
def get_object() -> object:
|
||||
return object()
|
||||
|
||||
class A:
|
||||
def y() -> int: ...
|
||||
def y() -> int:
|
||||
return 1
|
||||
|
||||
class B: ...
|
||||
|
||||
|
||||
@@ -233,16 +233,20 @@ reveal_type(y) # revealed: A
|
||||
from typing import Literal
|
||||
|
||||
class MetaAmbiguous(type):
|
||||
def __bool__(self) -> bool: ...
|
||||
def __bool__(self) -> bool:
|
||||
return True
|
||||
|
||||
class MetaFalsy(type):
|
||||
def __bool__(self) -> Literal[False]: ...
|
||||
def __bool__(self) -> Literal[False]:
|
||||
return False
|
||||
|
||||
class MetaTruthy(type):
|
||||
def __bool__(self) -> Literal[True]: ...
|
||||
def __bool__(self) -> Literal[True]:
|
||||
return True
|
||||
|
||||
class MetaDeferred(type):
|
||||
def __bool__(self) -> MetaAmbiguous: ...
|
||||
def __bool__(self) -> MetaAmbiguous:
|
||||
return MetaAmbiguous()
|
||||
|
||||
class AmbiguousClass(metaclass=MetaAmbiguous): ...
|
||||
class FalsyClass(metaclass=MetaFalsy): ...
|
||||
@@ -300,20 +304,24 @@ def _(x: bool | str):
|
||||
reveal_type(x and A()) # revealed: Literal[False] | str & ~AlwaysTruthy | A
|
||||
|
||||
class Falsy:
|
||||
def __bool__(self) -> Literal[False]: ...
|
||||
def __bool__(self) -> Literal[False]:
|
||||
return False
|
||||
|
||||
class Truthy:
|
||||
def __bool__(self) -> Literal[True]: ...
|
||||
def __bool__(self) -> Literal[True]:
|
||||
return True
|
||||
|
||||
def _(x: Falsy | Truthy):
|
||||
reveal_type(x or A()) # revealed: Truthy | A
|
||||
reveal_type(x and A()) # revealed: Falsy | A
|
||||
|
||||
class MetaFalsy(type):
|
||||
def __bool__(self) -> Literal[False]: ...
|
||||
def __bool__(self) -> Literal[False]:
|
||||
return False
|
||||
|
||||
class MetaTruthy(type):
|
||||
def __bool__(self) -> Literal[True]: ...
|
||||
def __bool__(self) -> Literal[True]:
|
||||
return True
|
||||
|
||||
class FalsyClass(metaclass=MetaFalsy): ...
|
||||
class TruthyClass(metaclass=MetaTruthy): ...
|
||||
|
||||
@@ -88,7 +88,7 @@ def _(x: str | int):
|
||||
|
||||
```py
|
||||
def _(x: str | int):
|
||||
# TODO: we could issue a diagnostic here
|
||||
# error: [no-matching-overload] "No overload of class `type` matches arguments"
|
||||
if type(object=x) is str:
|
||||
reveal_type(x) # revealed: str | int
|
||||
```
|
||||
|
||||
@@ -9,7 +9,8 @@ is retained after the loop.
|
||||
## Basic `while` loop
|
||||
|
||||
```py
|
||||
def next_item() -> int | None: ...
|
||||
def next_item() -> int | None:
|
||||
return 1
|
||||
|
||||
x = next_item()
|
||||
|
||||
@@ -23,7 +24,8 @@ reveal_type(x) # revealed: None
|
||||
## `while` loop with `else`
|
||||
|
||||
```py
|
||||
def next_item() -> int | None: ...
|
||||
def next_item() -> int | None:
|
||||
return 1
|
||||
|
||||
x = next_item()
|
||||
|
||||
@@ -41,7 +43,8 @@ reveal_type(x) # revealed: None
|
||||
```py
|
||||
from typing import Literal
|
||||
|
||||
def next_item() -> Literal[1, 2, 3]: ...
|
||||
def next_item() -> Literal[1, 2, 3]:
|
||||
raise NotImplementedError
|
||||
|
||||
x = next_item()
|
||||
|
||||
|
||||
@@ -13,3 +13,13 @@ def _(some_int: int, some_literal_int: Literal[1], some_indexable: SupportsIndex
|
||||
b: SupportsIndex = some_literal_int
|
||||
c: SupportsIndex = some_indexable
|
||||
```
|
||||
|
||||
## Invalid
|
||||
|
||||
```py
|
||||
from typing import Protocol
|
||||
|
||||
# error: [invalid-type-form] "`typing.Protocol` is not allowed in type expressions"
|
||||
def f(x: Protocol) -> None:
|
||||
reveal_type(x) # revealed: Unknown
|
||||
```
|
||||
|
||||
@@ -113,6 +113,24 @@ class D(B, A): ... # fine
|
||||
class E(B, C, A): ... # fine
|
||||
```
|
||||
|
||||
## Post-hoc modifications
|
||||
|
||||
```py
|
||||
class A:
|
||||
__slots__ = ()
|
||||
__slots__ += ("a", "b")
|
||||
|
||||
reveal_type(A.__slots__) # revealed: tuple[Literal["a"], Literal["b"]]
|
||||
|
||||
class B:
|
||||
__slots__ = ("c", "d")
|
||||
|
||||
class C(
|
||||
A, # error: [incompatible-slots]
|
||||
B, # error: [incompatible-slots]
|
||||
): ...
|
||||
```
|
||||
|
||||
## False negatives
|
||||
|
||||
### Possibly unbound
|
||||
@@ -160,22 +178,6 @@ class B:
|
||||
class C(A, B): ...
|
||||
```
|
||||
|
||||
### Post-hoc modifications
|
||||
|
||||
```py
|
||||
class A:
|
||||
__slots__ = ()
|
||||
__slots__ += ("a", "b")
|
||||
|
||||
reveal_type(A.__slots__) # revealed: @Todo(return type of decorated function)
|
||||
|
||||
class B:
|
||||
__slots__ = ("c", "d")
|
||||
|
||||
# False negative: [incompatible-slots]
|
||||
class C(A, B): ...
|
||||
```
|
||||
|
||||
### Built-ins with implicit layouts
|
||||
|
||||
```py
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
---
|
||||
source: crates/red_knot_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: attribute_assignment.md - Attribute assignment - Data descriptors - Invalid `__set__` method signature
|
||||
mdtest path: crates/red_knot_python_semantic/resources/mdtest/diagnostics/attribute_assignment.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | class WrongDescriptor:
|
||||
2 | def __set__(self, instance: object, value: int, extra: int) -> None:
|
||||
3 | pass
|
||||
4 |
|
||||
5 | class C:
|
||||
6 | attr: WrongDescriptor = WrongDescriptor()
|
||||
7 |
|
||||
8 | instance = C()
|
||||
9 |
|
||||
10 | # TODO: ideally, we would mention why this is an invalid assignment (wrong number of arguments for `__set__`)
|
||||
11 | instance.attr = 1 # error: [invalid-assignment]
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error: lint:invalid-assignment
|
||||
--> /src/mdtest_snippet.py:11:1
|
||||
|
|
||||
10 | # TODO: ideally, we would mention why this is an invalid assignment (wrong number of arguments for `__set__`)
|
||||
11 | instance.attr = 1 # error: [invalid-assignment]
|
||||
| ^^^^^^^^^^^^^ Invalid assignment to data descriptor attribute `attr` on type `C` with custom `__set__` method
|
||||
|
|
||||
|
||||
```
|
||||
@@ -0,0 +1,40 @@
|
||||
---
|
||||
source: crates/red_knot_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: attribute_assignment.md - Attribute assignment - Data descriptors - Invalid argument type
|
||||
mdtest path: crates/red_knot_python_semantic/resources/mdtest/diagnostics/attribute_assignment.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | class Descriptor:
|
||||
2 | def __set__(self, instance: object, value: int) -> None:
|
||||
3 | pass
|
||||
4 |
|
||||
5 | class C:
|
||||
6 | attr: Descriptor = Descriptor()
|
||||
7 |
|
||||
8 | instance = C()
|
||||
9 | instance.attr = 1 # fine
|
||||
10 |
|
||||
11 | # TODO: ideally, we would mention why this is an invalid assignment (wrong argument type for `value` parameter)
|
||||
12 | instance.attr = "wrong" # error: [invalid-assignment]
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error: lint:invalid-assignment
|
||||
--> /src/mdtest_snippet.py:12:1
|
||||
|
|
||||
11 | # TODO: ideally, we would mention why this is an invalid assignment (wrong argument type for `value` parameter)
|
||||
12 | instance.attr = "wrong" # error: [invalid-assignment]
|
||||
| ^^^^^^^^^^^^^ Invalid assignment to data descriptor attribute `attr` on type `C` with custom `__set__` method
|
||||
|
|
||||
|
||||
```
|
||||
@@ -0,0 +1,51 @@
|
||||
---
|
||||
source: crates/red_knot_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: attribute_assignment.md - Attribute assignment - Instance attributes with class-level defaults
|
||||
mdtest path: crates/red_knot_python_semantic/resources/mdtest/diagnostics/attribute_assignment.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | class C:
|
||||
2 | attr: int = 0
|
||||
3 |
|
||||
4 | instance = C()
|
||||
5 | instance.attr = 1 # fine
|
||||
6 | instance.attr = "wrong" # error: [invalid-assignment]
|
||||
7 |
|
||||
8 | C.attr = 1 # fine
|
||||
9 | C.attr = "wrong" # error: [invalid-assignment]
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error: lint:invalid-assignment
|
||||
--> /src/mdtest_snippet.py:6:1
|
||||
|
|
||||
4 | instance = C()
|
||||
5 | instance.attr = 1 # fine
|
||||
6 | instance.attr = "wrong" # error: [invalid-assignment]
|
||||
| ^^^^^^^^^^^^^ Object of type `Literal["wrong"]` is not assignable to attribute `attr` of type `int`
|
||||
7 |
|
||||
8 | C.attr = 1 # fine
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error: lint:invalid-assignment
|
||||
--> /src/mdtest_snippet.py:9:1
|
||||
|
|
||||
8 | C.attr = 1 # fine
|
||||
9 | C.attr = "wrong" # error: [invalid-assignment]
|
||||
| ^^^^^^ Object of type `Literal["wrong"]` is not assignable to attribute `attr` of type `int`
|
||||
|
|
||||
|
||||
```
|
||||
@@ -0,0 +1,51 @@
|
||||
---
|
||||
source: crates/red_knot_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: attribute_assignment.md - Attribute assignment - Possibly-unbound attributes
|
||||
mdtest path: crates/red_knot_python_semantic/resources/mdtest/diagnostics/attribute_assignment.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | def _(flag: bool) -> None:
|
||||
2 | class C:
|
||||
3 | if flag:
|
||||
4 | attr: int = 0
|
||||
5 |
|
||||
6 | C.attr = 1 # error: [possibly-unbound-attribute]
|
||||
7 |
|
||||
8 | instance = C()
|
||||
9 | instance.attr = 1 # error: [possibly-unbound-attribute]
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
warning: lint:possibly-unbound-attribute
|
||||
--> /src/mdtest_snippet.py:6:5
|
||||
|
|
||||
4 | attr: int = 0
|
||||
5 |
|
||||
6 | C.attr = 1 # error: [possibly-unbound-attribute]
|
||||
| ^^^^^^ Attribute `attr` on type `Literal[C]` is possibly unbound
|
||||
7 |
|
||||
8 | instance = C()
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
warning: lint:possibly-unbound-attribute
|
||||
--> /src/mdtest_snippet.py:9:5
|
||||
|
|
||||
8 | instance = C()
|
||||
9 | instance.attr = 1 # error: [possibly-unbound-attribute]
|
||||
| ^^^^^^^^^^^^^ Attribute `attr` on type `C` is possibly unbound
|
||||
|
|
||||
|
||||
```
|
||||
@@ -0,0 +1,52 @@
|
||||
---
|
||||
source: crates/red_knot_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: attribute_assignment.md - Attribute assignment - Pure instance attributes
|
||||
mdtest path: crates/red_knot_python_semantic/resources/mdtest/diagnostics/attribute_assignment.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | class C:
|
||||
2 | def __init__(self):
|
||||
3 | self.attr: int = 0
|
||||
4 |
|
||||
5 | instance = C()
|
||||
6 | instance.attr = 1 # fine
|
||||
7 | instance.attr = "wrong" # error: [invalid-assignment]
|
||||
8 |
|
||||
9 | C.attr = 1 # error: [invalid-attribute-access]
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error: lint:invalid-assignment
|
||||
--> /src/mdtest_snippet.py:7:1
|
||||
|
|
||||
5 | instance = C()
|
||||
6 | instance.attr = 1 # fine
|
||||
7 | instance.attr = "wrong" # error: [invalid-assignment]
|
||||
| ^^^^^^^^^^^^^ Object of type `Literal["wrong"]` is not assignable to attribute `attr` of type `int`
|
||||
8 |
|
||||
9 | C.attr = 1 # error: [invalid-attribute-access]
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error: lint:invalid-attribute-access
|
||||
--> /src/mdtest_snippet.py:9:1
|
||||
|
|
||||
7 | instance.attr = "wrong" # error: [invalid-assignment]
|
||||
8 |
|
||||
9 | C.attr = 1 # error: [invalid-attribute-access]
|
||||
| ^^^^^^ Cannot assign to instance attribute `attr` from the class object `Literal[C]`
|
||||
|
|
||||
|
||||
```
|
||||
@@ -0,0 +1,50 @@
|
||||
---
|
||||
source: crates/red_knot_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: attribute_assignment.md - Attribute assignment - Setting attributes on union types
|
||||
mdtest path: crates/red_knot_python_semantic/resources/mdtest/diagnostics/attribute_assignment.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | def _(flag: bool) -> None:
|
||||
2 | if flag:
|
||||
3 | class C1:
|
||||
4 | attr: int = 0
|
||||
5 |
|
||||
6 | else:
|
||||
7 | class C1:
|
||||
8 | attr: str = ""
|
||||
9 |
|
||||
10 | # TODO: The error message here could be improved to explain why the assignment fails.
|
||||
11 | C1.attr = 1 # error: [invalid-assignment]
|
||||
12 |
|
||||
13 | class C2:
|
||||
14 | if flag:
|
||||
15 | attr: int = 0
|
||||
16 | else:
|
||||
17 | attr: str = ""
|
||||
18 |
|
||||
19 | # TODO: This should be an error
|
||||
20 | C2.attr = 1
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error: lint:invalid-assignment
|
||||
--> /src/mdtest_snippet.py:11:5
|
||||
|
|
||||
10 | # TODO: The error message here could be improved to explain why the assignment fails.
|
||||
11 | C1.attr = 1 # error: [invalid-assignment]
|
||||
| ^^^^^^^ Object of type `Literal[1]` is not assignable to attribute `attr` on type `Literal[C1, C1]`
|
||||
12 |
|
||||
13 | class C2:
|
||||
|
|
||||
|
||||
```
|
||||
@@ -0,0 +1,48 @@
|
||||
---
|
||||
source: crates/red_knot_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: attribute_assignment.md - Attribute assignment - Unknown attributes
|
||||
mdtest path: crates/red_knot_python_semantic/resources/mdtest/diagnostics/attribute_assignment.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | class C: ...
|
||||
2 |
|
||||
3 | C.non_existent = 1 # error: [unresolved-attribute]
|
||||
4 |
|
||||
5 | instance = C()
|
||||
6 | instance.non_existent = 1 # error: [unresolved-attribute]
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error: lint:unresolved-attribute
|
||||
--> /src/mdtest_snippet.py:3:1
|
||||
|
|
||||
1 | class C: ...
|
||||
2 |
|
||||
3 | C.non_existent = 1 # error: [unresolved-attribute]
|
||||
| ^^^^^^^^^^^^^^ Unresolved attribute `non_existent` on type `Literal[C]`.
|
||||
4 |
|
||||
5 | instance = C()
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error: lint:unresolved-attribute
|
||||
--> /src/mdtest_snippet.py:6:1
|
||||
|
|
||||
5 | instance = C()
|
||||
6 | instance.non_existent = 1 # error: [unresolved-attribute]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^ Unresolved attribute `non_existent` on type `C`.
|
||||
|
|
||||
|
||||
```
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user