Compare commits
217 Commits
dhruv/avoi
...
david/stab
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6e83ce9e5d | ||
|
|
d998bea5de | ||
|
|
3a76557be2 | ||
|
|
2a217e80ca | ||
|
|
030a16cb5f | ||
|
|
0590b38214 | ||
|
|
8104b1e83b | ||
|
|
cf70c7863c | ||
|
|
faf54c0181 | ||
|
|
451c5db7a3 | ||
|
|
bd5b7f415f | ||
|
|
0230cbac2c | ||
|
|
2e94d37275 | ||
|
|
1e4377c9c6 | ||
|
|
1b4f7de840 | ||
|
|
9b52ae8991 | ||
|
|
97d7b46936 | ||
|
|
1eab59e681 | ||
|
|
e7f97a3e4b | ||
|
|
d17557f0ae | ||
|
|
8cbd433a31 | ||
|
|
65e48cb439 | ||
|
|
301d9985d8 | ||
|
|
cfbb914100 | ||
|
|
fe653de3dd | ||
|
|
a9f7521944 | ||
|
|
f8890b70c3 | ||
|
|
142c1bc760 | ||
|
|
c0f22928bd | ||
|
|
5bf5f3682a | ||
|
|
5913997c72 | ||
|
|
00f672a83b | ||
|
|
68b0386007 | ||
|
|
0ae07cdd1f | ||
|
|
0fb94c052e | ||
|
|
55df9271ba | ||
|
|
f301931159 | ||
|
|
7e9b0df18a | ||
|
|
41fa082414 | ||
|
|
c7b6108cb8 | ||
|
|
a97e72fb5e | ||
|
|
0d6fafd0f9 | ||
|
|
2eb2d5359b | ||
|
|
f549dfe39d | ||
|
|
6b64630635 | ||
|
|
d545b5bfd2 | ||
|
|
b2d9f59937 | ||
|
|
d7ef01401c | ||
|
|
c9031ce59f | ||
|
|
138ab91def | ||
|
|
550b8be552 | ||
|
|
bdccb37b4a | ||
|
|
3ccc0edfe4 | ||
|
|
6b3ff6f5b8 | ||
|
|
6f8f7506b4 | ||
|
|
797eb70904 | ||
|
|
be6ec613db | ||
|
|
fcd858e0c8 | ||
|
|
d944a1397e | ||
|
|
d3f3d92df3 | ||
|
|
38c00dfad5 | ||
|
|
d6280c5aea | ||
|
|
a34240a3f0 | ||
|
|
b86c7bbf7c | ||
|
|
d7c54ba8c4 | ||
|
|
c38d6e8045 | ||
|
|
2bfd7b1816 | ||
|
|
c1cfb43bf0 | ||
|
|
99555b775c | ||
|
|
b398b83631 | ||
|
|
bc7b30364d | ||
|
|
5792ed15da | ||
|
|
8845a13efb | ||
|
|
669855d2b5 | ||
|
|
ff7ebecf89 | ||
|
|
7e8ba2b68e | ||
|
|
0bb8cbdf07 | ||
|
|
2923c55698 | ||
|
|
316e406ca4 | ||
|
|
5ecd560c6f | ||
|
|
b765dc48e9 | ||
|
|
cd1d906ffa | ||
|
|
235b74a310 | ||
|
|
40fd52dde0 | ||
|
|
fd1eb3d801 | ||
|
|
882a1a702e | ||
|
|
b4a1ebdfe3 | ||
|
|
7a48477c67 | ||
|
|
346e82b572 | ||
|
|
5ea3a52c8a | ||
|
|
90272ad85a | ||
|
|
e9da1750a1 | ||
|
|
25e13debc0 | ||
|
|
249a852a6e | ||
|
|
861ef2504e | ||
|
|
b71ef8a26e | ||
|
|
50c780fc8b | ||
|
|
244ea27d5f | ||
|
|
2c4cbb6e29 | ||
|
|
d1bb10a66b | ||
|
|
2370297cde | ||
|
|
a137cb18d4 | ||
|
|
03a4d56624 | ||
|
|
642eac452d | ||
|
|
c1b875799b | ||
|
|
6cd8a49638 | ||
|
|
12ce445ff7 | ||
|
|
f46ed8d410 | ||
|
|
6c177e2bbe | ||
|
|
3d2485eb1b | ||
|
|
f78367979e | ||
|
|
b705664d49 | ||
|
|
f51f1f7153 | ||
|
|
9b694ada82 | ||
|
|
4d81a41107 | ||
|
|
981bd70d39 | ||
|
|
0763331f7f | ||
|
|
da8540862d | ||
|
|
6a5533c44c | ||
|
|
d608eae126 | ||
|
|
067a8ac574 | ||
|
|
5eb215e8e5 | ||
|
|
91aa853b9c | ||
|
|
57bf7dfbd9 | ||
|
|
67cd94ed64 | ||
|
|
aac862822f | ||
|
|
3755ac9fac | ||
|
|
4f890b2867 | ||
|
|
d566636ca5 | ||
|
|
51cef5a72b | ||
|
|
2cf5cba7ff | ||
|
|
ce0800fccf | ||
|
|
d03a7069ad | ||
|
|
f5096f2050 | ||
|
|
895b6161a6 | ||
|
|
74fe7982ba | ||
|
|
51386b3c7a | ||
|
|
51e2effd2d | ||
|
|
82d31a6014 | ||
|
|
78054824c0 | ||
|
|
c6f4929cdc | ||
|
|
2ec0d7e072 | ||
|
|
ad658f4d68 | ||
|
|
3dedd70a92 | ||
|
|
fab862c8cd | ||
|
|
0d9b6a0975 | ||
|
|
c5e299e796 | ||
|
|
c504001b32 | ||
|
|
04457f99b6 | ||
|
|
a33d0d4bf4 | ||
|
|
443f62e98d | ||
|
|
a2e9a7732a | ||
|
|
b2de749c32 | ||
|
|
9085f18353 | ||
|
|
8152ba7cb7 | ||
|
|
3d01d3be3e | ||
|
|
4510a236d3 | ||
|
|
76b6d53d8b | ||
|
|
f82b72882b | ||
|
|
d07eefc408 | ||
|
|
f7237e3b69 | ||
|
|
2f9992b6ef | ||
|
|
457ec4dddd | ||
|
|
aa0614509b | ||
|
|
9000eb3bfd | ||
|
|
7f50b503cf | ||
|
|
24d3fc27fb | ||
|
|
6f821ac846 | ||
|
|
d410d12bc5 | ||
|
|
89424cce5f | ||
|
|
fd76d70a31 | ||
|
|
a4c8e43c5f | ||
|
|
ada4c4cb1f | ||
|
|
bb6c7cad07 | ||
|
|
47e3aa40b3 | ||
|
|
9a6633da0b | ||
|
|
de78da5ee6 | ||
|
|
20d64b9c85 | ||
|
|
4850c187ea | ||
|
|
3f32446e16 | ||
|
|
784daae497 | ||
|
|
178c882740 | ||
|
|
101e1a5ddd | ||
|
|
965a4dd731 | ||
|
|
5e2c818417 | ||
|
|
90c12f4177 | ||
|
|
6e9fb9af38 | ||
|
|
a507c1b8b3 | ||
|
|
5a91badb8b | ||
|
|
1945bfdb84 | ||
|
|
a95c73d5d0 | ||
|
|
78b4c3ccf1 | ||
|
|
2485afe640 | ||
|
|
b8ed729f59 | ||
|
|
108c470348 | ||
|
|
87c64c9eab | ||
|
|
a10606dda2 | ||
|
|
d1c6dd9ac1 | ||
|
|
073b993ab0 | ||
|
|
6a36cd6f02 | ||
|
|
3b15af6d4f | ||
|
|
e95130ad80 | ||
|
|
68e32c103f | ||
|
|
fe4051b2e6 | ||
|
|
fa628018b2 | ||
|
|
8535af8516 | ||
|
|
b51c4f82ea | ||
|
|
e6a798b962 | ||
|
|
52b0470870 | ||
|
|
c4a08782cc | ||
|
|
91481a8be7 | ||
|
|
097af060c9 | ||
|
|
b7d0b3f9e5 | ||
|
|
084352f72c | ||
|
|
78d4356301 | ||
|
|
96697c98f3 | ||
|
|
f7cae4ffb5 |
10
.github/CODEOWNERS
vendored
10
.github/CODEOWNERS
vendored
@@ -14,11 +14,11 @@
|
||||
# flake8-pyi
|
||||
/crates/ruff_linter/src/rules/flake8_pyi/ @AlexWaygood
|
||||
|
||||
# Script for fuzzing the parser/red-knot etc.
|
||||
# Script for fuzzing the parser/ty etc.
|
||||
/python/py-fuzzer/ @AlexWaygood
|
||||
|
||||
# red-knot
|
||||
/crates/red_knot* @carljm @MichaReiser @AlexWaygood @sharkdp @dcreager
|
||||
# ty
|
||||
/crates/ty* @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
|
||||
/scripts/ty_benchmark/ @carljm @MichaReiser @AlexWaygood @sharkdp @dcreager
|
||||
/crates/ty_python_semantic @carljm @AlexWaygood @sharkdp @dcreager
|
||||
|
||||
3
.github/ISSUE_TEMPLATE/config.yml
vendored
3
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,5 +1,8 @@
|
||||
blank_issues_enabled: true
|
||||
contact_links:
|
||||
- name: Report an issue with ty
|
||||
url: https://github.com/astral-sh/ty/issues/new/choose
|
||||
about: Please report issues for our type checker ty in the ty repository.
|
||||
- name: Documentation
|
||||
url: https://docs.astral.sh/ruff
|
||||
about: Please consult the documentation before creating an issue.
|
||||
|
||||
5
.github/PULL_REQUEST_TEMPLATE.md
vendored
5
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -1,8 +1,9 @@
|
||||
<!--
|
||||
Thank you for contributing to Ruff! To help us out with reviewing, please consider the following:
|
||||
Thank you for contributing to Ruff/ty! To help us out with reviewing, please consider the following:
|
||||
|
||||
- Does this pull request include a summary of the change? (See below.)
|
||||
- Does this pull request include a descriptive title?
|
||||
- Does this pull request include a descriptive title? (Please prefix with `[ty]` for ty pull
|
||||
requests.)
|
||||
- Does this pull request include references to any relevant issues?
|
||||
-->
|
||||
|
||||
|
||||
7
.github/mypy-primer-ty.toml
vendored
Normal file
7
.github/mypy-primer-ty.toml
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
#:schema ../ty.schema.json
|
||||
# Configuration overrides for the mypy primer run
|
||||
|
||||
# Enable off-by-default rules.
|
||||
[rules]
|
||||
possibly-unresolved-reference = "warn"
|
||||
unused-ignore-comment = "warn"
|
||||
16
.github/workflows/build-binaries.yml
vendored
16
.github/workflows/build-binaries.yml
vendored
@@ -43,7 +43,7 @@ jobs:
|
||||
with:
|
||||
submodules: recursive
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55 # v5.5.0
|
||||
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
- name: "Prep README.md"
|
||||
@@ -72,7 +72,7 @@ jobs:
|
||||
with:
|
||||
submodules: recursive
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55 # v5.5.0
|
||||
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
architecture: x64
|
||||
@@ -114,7 +114,7 @@ jobs:
|
||||
with:
|
||||
submodules: recursive
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55 # v5.5.0
|
||||
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
architecture: arm64
|
||||
@@ -170,7 +170,7 @@ jobs:
|
||||
with:
|
||||
submodules: recursive
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55 # v5.5.0
|
||||
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
architecture: ${{ matrix.platform.arch }}
|
||||
@@ -223,7 +223,7 @@ jobs:
|
||||
with:
|
||||
submodules: recursive
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55 # v5.5.0
|
||||
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
architecture: x64
|
||||
@@ -298,7 +298,7 @@ jobs:
|
||||
with:
|
||||
submodules: recursive
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55 # v5.5.0
|
||||
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
- name: "Prep README.md"
|
||||
@@ -363,7 +363,7 @@ jobs:
|
||||
with:
|
||||
submodules: recursive
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55 # v5.5.0
|
||||
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
architecture: x64
|
||||
@@ -429,7 +429,7 @@ jobs:
|
||||
with:
|
||||
submodules: recursive
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55 # v5.5.0
|
||||
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
- name: "Prep README.md"
|
||||
|
||||
16
.github/workflows/build-docker.yml
vendored
16
.github/workflows/build-docker.yml
vendored
@@ -40,7 +40,7 @@ jobs:
|
||||
|
||||
- uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3
|
||||
|
||||
- uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3
|
||||
- uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
@@ -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@14487ce63c7a62a4a324b0bfb37086795e31c6c1 # v6
|
||||
uses: docker/build-push-action@14487ce63c7a62a4a324b0bfb37086795e31c6c1 # v6.16.0
|
||||
with:
|
||||
context: .
|
||||
platforms: ${{ matrix.platform }}
|
||||
@@ -113,7 +113,7 @@ jobs:
|
||||
if: ${{ inputs.plan != '' && !fromJson(inputs.plan).announcement_tag_is_implicit }}
|
||||
steps:
|
||||
- name: Download digests
|
||||
uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e # v4.2.1
|
||||
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||
with:
|
||||
path: /tmp/digests
|
||||
pattern: digests-*
|
||||
@@ -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@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3
|
||||
- uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
@@ -169,7 +169,7 @@ jobs:
|
||||
steps:
|
||||
- uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3
|
||||
|
||||
- uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3
|
||||
- uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
@@ -231,7 +231,7 @@ jobs:
|
||||
${{ env.TAG_PATTERNS }}
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@14487ce63c7a62a4a324b0bfb37086795e31c6c1 # v6
|
||||
uses: docker/build-push-action@14487ce63c7a62a4a324b0bfb37086795e31c6c1 # v6.16.0
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64
|
||||
@@ -256,7 +256,7 @@ jobs:
|
||||
if: ${{ inputs.plan != '' && !fromJson(inputs.plan).announcement_tag_is_implicit }}
|
||||
steps:
|
||||
- name: Download digests
|
||||
uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e # v4.2.1
|
||||
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||
with:
|
||||
path: /tmp/digests
|
||||
pattern: digests-*
|
||||
@@ -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@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3
|
||||
- uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
|
||||
94
.github/workflows/ci.yaml
vendored
94
.github/workflows/ci.yaml
vendored
@@ -36,8 +36,8 @@ jobs:
|
||||
code: ${{ steps.check_code.outputs.changed }}
|
||||
# Flag that is raised when any code that affects the fuzzer is changed
|
||||
fuzz: ${{ steps.check_fuzzer.outputs.changed }}
|
||||
# Flag that is set to "true" when code related to red-knot changes.
|
||||
red_knot: ${{ steps.check_red_knot.outputs.changed }}
|
||||
# Flag that is set to "true" when code related to ty changes.
|
||||
ty: ${{ steps.check_ty.outputs.changed }}
|
||||
|
||||
# Flag that is set to "true" when code related to the playground changes.
|
||||
playground: ${{ steps.check_playground.outputs.changed }}
|
||||
@@ -84,7 +84,7 @@ jobs:
|
||||
if git diff --quiet "${MERGE_BASE}...HEAD" -- ':Cargo.toml' \
|
||||
':Cargo.lock' \
|
||||
':crates/**' \
|
||||
':!crates/red_knot*/**' \
|
||||
':!crates/ty*/**' \
|
||||
':!crates/ruff_python_formatter/**' \
|
||||
':!crates/ruff_formatter/**' \
|
||||
':!crates/ruff_dev/**' \
|
||||
@@ -145,7 +145,7 @@ jobs:
|
||||
run: |
|
||||
if git diff --quiet "${MERGE_BASE}...HEAD" -- ':**' \
|
||||
':!**/*.md' \
|
||||
':crates/red_knot_python_semantic/resources/mdtest/**/*.md' \
|
||||
':crates/ty_python_semantic/resources/mdtest/**/*.md' \
|
||||
':!docs/**' \
|
||||
':!assets/**' \
|
||||
':.github/workflows/ci.yaml' \
|
||||
@@ -168,15 +168,15 @@ jobs:
|
||||
echo "changed=true" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Check if the red-knot code changed
|
||||
id: check_red_knot
|
||||
- name: Check if the ty code changed
|
||||
id: check_ty
|
||||
env:
|
||||
MERGE_BASE: ${{ steps.merge_base.outputs.sha }}
|
||||
run: |
|
||||
if git diff --quiet "${MERGE_BASE}...HEAD" -- \
|
||||
':Cargo.toml' \
|
||||
':Cargo.lock' \
|
||||
':crates/red_knot*/**' \
|
||||
':crates/ty*/**' \
|
||||
':crates/ruff_db/**' \
|
||||
':crates/ruff_annotate_snippets/**' \
|
||||
':crates/ruff_python_ast/**' \
|
||||
@@ -221,7 +221,7 @@ jobs:
|
||||
- name: "Clippy"
|
||||
run: cargo clippy --workspace --all-targets --all-features --locked -- -D warnings
|
||||
- name: "Clippy (wasm)"
|
||||
run: cargo clippy -p ruff_wasm -p red_knot_wasm --target wasm32-unknown-unknown --all-features --locked -- -D warnings
|
||||
run: cargo clippy -p ruff_wasm -p ty_wasm --target wasm32-unknown-unknown --all-features --locked -- -D warnings
|
||||
|
||||
cargo-test-linux:
|
||||
name: "cargo test (linux)"
|
||||
@@ -239,21 +239,21 @@ jobs:
|
||||
- name: "Install mold"
|
||||
uses: rui314/setup-mold@e16410e7f8d9e167b74ad5697a9089a35126eb50 # v1
|
||||
- name: "Install cargo nextest"
|
||||
uses: taiki-e/install-action@ab3728c7ba6948b9b429627f4d55a68842b27f18 # v2
|
||||
uses: taiki-e/install-action@83254c543806f3224380bf1001d6fac8feaf2d0b # v2
|
||||
with:
|
||||
tool: cargo-nextest
|
||||
- name: "Install cargo insta"
|
||||
uses: taiki-e/install-action@ab3728c7ba6948b9b429627f4d55a68842b27f18 # v2
|
||||
uses: taiki-e/install-action@83254c543806f3224380bf1001d6fac8feaf2d0b # v2
|
||||
with:
|
||||
tool: cargo-insta
|
||||
- name: Red-knot mdtests (GitHub annotations)
|
||||
if: ${{ needs.determine_changes.outputs.red_knot == 'true' }}
|
||||
- name: ty mdtests (GitHub annotations)
|
||||
if: ${{ needs.determine_changes.outputs.ty == 'true' }}
|
||||
env:
|
||||
NO_COLOR: 1
|
||||
MDTEST_GITHUB_ANNOTATIONS_FORMAT: 1
|
||||
# Ignore errors if this step fails; we want to continue to later steps in the workflow anyway.
|
||||
# This step is just to get nice GitHub annotations on the PR diff in the files-changed tab.
|
||||
run: cargo test -p red_knot_python_semantic --test mdtest || true
|
||||
run: cargo test -p ty_python_semantic --test mdtest || true
|
||||
- name: "Run tests"
|
||||
shell: bash
|
||||
env:
|
||||
@@ -268,7 +268,7 @@ jobs:
|
||||
# sync, not just public items. Eventually we should do this for all
|
||||
# crates; for now add crates here as they are warning-clean to prevent
|
||||
# regression.
|
||||
- run: cargo doc --no-deps -p red_knot_python_semantic -p red_knot -p red_knot_test -p ruff_db --document-private-items
|
||||
- run: cargo doc --no-deps -p ty_python_semantic -p ty -p ty_test -p ruff_db --document-private-items
|
||||
env:
|
||||
# Setting RUSTDOCFLAGS because `cargo doc --check` isn't yet implemented (https://github.com/rust-lang/cargo/issues/10025).
|
||||
RUSTDOCFLAGS: "-D warnings"
|
||||
@@ -278,8 +278,8 @@ jobs:
|
||||
path: target/debug/ruff
|
||||
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: red_knot
|
||||
path: target/debug/red_knot
|
||||
name: ty
|
||||
path: target/debug/ty
|
||||
|
||||
cargo-test-linux-release:
|
||||
name: "cargo test (linux, release)"
|
||||
@@ -297,11 +297,11 @@ jobs:
|
||||
- name: "Install mold"
|
||||
uses: rui314/setup-mold@e16410e7f8d9e167b74ad5697a9089a35126eb50 # v1
|
||||
- name: "Install cargo nextest"
|
||||
uses: taiki-e/install-action@ab3728c7ba6948b9b429627f4d55a68842b27f18 # v2
|
||||
uses: taiki-e/install-action@83254c543806f3224380bf1001d6fac8feaf2d0b # v2
|
||||
with:
|
||||
tool: cargo-nextest
|
||||
- name: "Install cargo insta"
|
||||
uses: taiki-e/install-action@ab3728c7ba6948b9b429627f4d55a68842b27f18 # v2
|
||||
uses: taiki-e/install-action@83254c543806f3224380bf1001d6fac8feaf2d0b # v2
|
||||
with:
|
||||
tool: cargo-insta
|
||||
- name: "Run tests"
|
||||
@@ -324,7 +324,7 @@ jobs:
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
- name: "Install cargo nextest"
|
||||
uses: taiki-e/install-action@ab3728c7ba6948b9b429627f4d55a68842b27f18 # v2
|
||||
uses: taiki-e/install-action@83254c543806f3224380bf1001d6fac8feaf2d0b # v2
|
||||
with:
|
||||
tool: cargo-nextest
|
||||
- name: "Run tests"
|
||||
@@ -362,9 +362,9 @@ jobs:
|
||||
run: |
|
||||
cd crates/ruff_wasm
|
||||
wasm-pack test --node
|
||||
- name: "Test red_knot_wasm"
|
||||
- name: "Test ty_wasm"
|
||||
run: |
|
||||
cd crates/red_knot_wasm
|
||||
cd crates/ty_wasm
|
||||
wasm-pack test --node
|
||||
|
||||
cargo-build-release:
|
||||
@@ -407,11 +407,11 @@ jobs:
|
||||
- name: "Install mold"
|
||||
uses: rui314/setup-mold@e16410e7f8d9e167b74ad5697a9089a35126eb50 # v1
|
||||
- name: "Install cargo nextest"
|
||||
uses: taiki-e/install-action@ab3728c7ba6948b9b429627f4d55a68842b27f18 # v2
|
||||
uses: taiki-e/install-action@83254c543806f3224380bf1001d6fac8feaf2d0b # v2
|
||||
with:
|
||||
tool: cargo-nextest
|
||||
- name: "Install cargo insta"
|
||||
uses: taiki-e/install-action@ab3728c7ba6948b9b429627f4d55a68842b27f18 # v2
|
||||
uses: taiki-e/install-action@83254c543806f3224380bf1001d6fac8feaf2d0b # v2
|
||||
with:
|
||||
tool: cargo-insta
|
||||
- name: "Run tests"
|
||||
@@ -437,7 +437,7 @@ jobs:
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
- name: "Install cargo-binstall"
|
||||
uses: cargo-bins/cargo-binstall@63aaa5c1932cebabc34eceda9d92a70215dcead6 # v1.12.3
|
||||
uses: cargo-bins/cargo-binstall@13f9d60d5358393bf14644dba56d9f123bc5d595 # v1.12.4
|
||||
with:
|
||||
tool: cargo-fuzz@0.11.2
|
||||
- name: "Install cargo-fuzz"
|
||||
@@ -460,7 +460,7 @@ jobs:
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5.4.2
|
||||
- uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e # v4.2.1
|
||||
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||
name: Download Ruff binary to test
|
||||
id: download-cached-binary
|
||||
with:
|
||||
@@ -525,11 +525,11 @@ jobs:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55 # v5.5.0
|
||||
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
|
||||
- uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e # v4.2.1
|
||||
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||
name: Download comparison Ruff binary
|
||||
id: ruff-target
|
||||
with:
|
||||
@@ -636,29 +636,29 @@ jobs:
|
||||
name: ecosystem-result
|
||||
path: ecosystem-result
|
||||
|
||||
fuzz-redknot:
|
||||
name: "Fuzz for new red-knot panics"
|
||||
fuzz-ty:
|
||||
name: "Fuzz for new ty panics"
|
||||
runs-on: depot-ubuntu-22.04-16
|
||||
needs:
|
||||
- cargo-test-linux
|
||||
- determine_changes
|
||||
# Only runs on pull requests, since that is the only we way we can find the base version for comparison.
|
||||
if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-test') && github.event_name == 'pull_request' && needs.determine_changes.outputs.red_knot == 'true' }}
|
||||
if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-test') && github.event_name == 'pull_request' && needs.determine_changes.outputs.ty == 'true' }}
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e # v4.2.1
|
||||
name: Download new red-knot binary
|
||||
id: redknot-new
|
||||
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||
name: Download new ty binary
|
||||
id: ty-new
|
||||
with:
|
||||
name: red_knot
|
||||
name: ty
|
||||
path: target/debug
|
||||
- uses: dawidd6/action-download-artifact@20319c5641d495c8a52e688b7dc5fada6c3a9fbc # v8
|
||||
name: Download baseline red-knot binary
|
||||
name: Download baseline ty binary
|
||||
with:
|
||||
name: red_knot
|
||||
name: ty
|
||||
branch: ${{ github.event.pull_request.base.ref }}
|
||||
workflow: "ci.yaml"
|
||||
check_artifacts: true
|
||||
@@ -666,20 +666,20 @@ jobs:
|
||||
- name: Fuzz
|
||||
env:
|
||||
FORCE_COLOR: 1
|
||||
NEW_REDKNOT: ${{ steps.redknot-new.outputs.download-path }}
|
||||
NEW_TY: ${{ steps.ty-new.outputs.download-path }}
|
||||
run: |
|
||||
# Make executable, since artifact download doesn't preserve this
|
||||
chmod +x "${PWD}/red_knot" "${NEW_REDKNOT}/red_knot"
|
||||
chmod +x "${PWD}/ty" "${NEW_TY}/ty"
|
||||
|
||||
(
|
||||
uvx \
|
||||
--python="${PYTHON_VERSION}" \
|
||||
--from=./python/py-fuzzer \
|
||||
fuzz \
|
||||
--test-executable="${NEW_REDKNOT}/red_knot" \
|
||||
--baseline-executable="${PWD}/red_knot" \
|
||||
--test-executable="${NEW_TY}/ty" \
|
||||
--baseline-executable="${PWD}/ty" \
|
||||
--only-new-bugs \
|
||||
--bin=red_knot \
|
||||
--bin=ty \
|
||||
0-500
|
||||
)
|
||||
|
||||
@@ -692,7 +692,7 @@ jobs:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: cargo-bins/cargo-binstall@63aaa5c1932cebabc34eceda9d92a70215dcead6 # v1.12.3
|
||||
- uses: cargo-bins/cargo-binstall@13f9d60d5358393bf14644dba56d9f123bc5d595 # v1.12.4
|
||||
- run: cargo binstall --no-confirm cargo-shear
|
||||
- run: cargo shear
|
||||
|
||||
@@ -705,7 +705,7 @@ jobs:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55 # v5.5.0
|
||||
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
architecture: x64
|
||||
@@ -759,7 +759,7 @@ jobs:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55 # v5.5.0
|
||||
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
||||
with:
|
||||
python-version: "3.13"
|
||||
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
|
||||
@@ -830,12 +830,12 @@ jobs:
|
||||
persist-credentials: false
|
||||
repository: "astral-sh/ruff-lsp"
|
||||
|
||||
- uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55 # v5.5.0
|
||||
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
||||
with:
|
||||
# installation fails on 3.13 and newer
|
||||
python-version: "3.12"
|
||||
|
||||
- uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e # v4.2.1
|
||||
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||
name: Download development ruff binary
|
||||
id: ruff-target
|
||||
with:
|
||||
@@ -908,7 +908,7 @@ jobs:
|
||||
run: rustup show
|
||||
|
||||
- name: "Install codspeed"
|
||||
uses: taiki-e/install-action@ab3728c7ba6948b9b429627f4d55a68842b27f18 # v2
|
||||
uses: taiki-e/install-action@83254c543806f3224380bf1001d6fac8feaf2d0b # v2
|
||||
with:
|
||||
tool: cargo-codspeed
|
||||
|
||||
|
||||
10
.github/workflows/daily_property_tests.yaml
vendored
10
.github/workflows/daily_property_tests.yaml
vendored
@@ -38,17 +38,17 @@ jobs:
|
||||
- name: "Install mold"
|
||||
uses: rui314/setup-mold@e16410e7f8d9e167b74ad5697a9089a35126eb50 # v1
|
||||
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
|
||||
- name: Build Red Knot
|
||||
- name: Build ty
|
||||
# A release build takes longer (2 min vs 1 min), but the property tests run much faster in release
|
||||
# mode (1.5 min vs 14 min), so the overall time is shorter with a release build.
|
||||
run: cargo build --locked --release --package red_knot_python_semantic --tests
|
||||
run: cargo build --locked --release --package ty_python_semantic --tests
|
||||
- name: Run property tests
|
||||
shell: bash
|
||||
run: |
|
||||
export QUICKCHECK_TESTS=100000
|
||||
for _ in {1..5}; do
|
||||
cargo test --locked --release --package red_knot_python_semantic -- --ignored list::property_tests
|
||||
cargo test --locked --release --package red_knot_python_semantic -- --ignored types::property_tests::stable
|
||||
cargo test --locked --release --package ty_python_semantic -- --ignored list::property_tests
|
||||
cargo test --locked --release --package ty_python_semantic -- --ignored types::property_tests::stable
|
||||
done
|
||||
|
||||
create-issue-on-failure:
|
||||
@@ -68,5 +68,5 @@ jobs:
|
||||
repo: "ruff",
|
||||
title: `Daily property test run failed on ${new Date().toDateString()}`,
|
||||
body: "Run listed here: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}",
|
||||
labels: ["bug", "red-knot", "testing"],
|
||||
labels: ["bug", "ty", "testing"],
|
||||
})
|
||||
|
||||
12
.github/workflows/mypy_primer.yaml
vendored
12
.github/workflows/mypy_primer.yaml
vendored
@@ -5,7 +5,7 @@ permissions: {}
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- "crates/red_knot*/**"
|
||||
- "crates/ty*/**"
|
||||
- "crates/ruff_db"
|
||||
- "crates/ruff_python_ast"
|
||||
- "crates/ruff_python_parser"
|
||||
@@ -50,7 +50,11 @@ jobs:
|
||||
run: |
|
||||
cd ruff
|
||||
|
||||
PRIMER_SELECTOR="$(paste -s -d'|' crates/red_knot_python_semantic/resources/primer/good.txt)"
|
||||
echo "Enabling mypy primer specific configuration overloads (see .github/mypy-primer-ty.toml)"
|
||||
mkdir -p ~/.config/ty
|
||||
cp .github/mypy-primer-ty.toml ~/.config/ty/ty.toml
|
||||
|
||||
PRIMER_SELECTOR="$(paste -s -d'|' crates/ty_python_semantic/resources/primer/good.txt)"
|
||||
|
||||
echo "new commit"
|
||||
git rev-list --format=%s --max-count=1 "$GITHUB_SHA"
|
||||
@@ -65,10 +69,10 @@ jobs:
|
||||
echo "Project selector: $PRIMER_SELECTOR"
|
||||
# Allow the exit code to be 0 or 1, only fail for actual mypy_primer crashes/bugs
|
||||
uvx \
|
||||
--from="git+https://github.com/hauntsaninja/mypy_primer@b83b9eade0b7ed2f4b9b129b163acac1ecb48f71" \
|
||||
--from="git+https://github.com/hauntsaninja/mypy_primer@968b2b61c05f84462d6fcc78d2f5205bbb8b98c2" \
|
||||
mypy_primer \
|
||||
--repo ruff \
|
||||
--type-checker knot \
|
||||
--type-checker ty \
|
||||
--old base_commit \
|
||||
--new "$GITHUB_SHA" \
|
||||
--project-selector "/($PRIMER_SELECTOR)\$" \
|
||||
|
||||
2
.github/workflows/publish-docs.yml
vendored
2
.github/workflows/publish-docs.yml
vendored
@@ -28,7 +28,7 @@ jobs:
|
||||
ref: ${{ inputs.ref }}
|
||||
persist-credentials: true
|
||||
|
||||
- uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55 # v5.5.0
|
||||
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
||||
with:
|
||||
python-version: 3.12
|
||||
|
||||
|
||||
2
.github/workflows/publish-pypi.yml
vendored
2
.github/workflows/publish-pypi.yml
vendored
@@ -23,7 +23,7 @@ jobs:
|
||||
steps:
|
||||
- name: "Install uv"
|
||||
uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5.4.2
|
||||
- uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e # v4.2.1
|
||||
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||
with:
|
||||
pattern: wheels-*
|
||||
path: wheels
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Publish the Red Knot playground.
|
||||
name: "[Knot Playground] Release"
|
||||
# Publish the ty playground.
|
||||
name: "[ty Playground] Release"
|
||||
|
||||
permissions: {}
|
||||
|
||||
@@ -7,12 +7,12 @@ on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- "crates/red_knot*/**"
|
||||
- "crates/ty*/**"
|
||||
- "crates/ruff_db/**"
|
||||
- "crates/ruff_python_ast/**"
|
||||
- "crates/ruff_python_parser/**"
|
||||
- "playground/**"
|
||||
- ".github/workflows/publish-knot-playground.yml"
|
||||
- ".github/workflows/publish-ty-playground.yml"
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref_name }}
|
||||
@@ -45,8 +45,8 @@ jobs:
|
||||
- name: "Run TypeScript checks"
|
||||
run: npm run check
|
||||
working-directory: playground
|
||||
- name: "Build Knot playground"
|
||||
run: npm run build --workspace knot-playground
|
||||
- name: "Build ty playground"
|
||||
run: npm run build --workspace ty-playground
|
||||
working-directory: playground
|
||||
- name: "Deploy to Cloudflare Pages"
|
||||
if: ${{ env.CF_API_TOKEN_EXISTS == 'true' }}
|
||||
@@ -55,4 +55,4 @@ jobs:
|
||||
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}
|
||||
command: pages deploy playground/ty/dist --project-name=ty-playground --branch ${{ github.head_ref || 'main' }} --commit-hash ${GITHUB_SHA}
|
||||
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@@ -69,7 +69,7 @@ jobs:
|
||||
# we specify bash to get pipefail; it guards against the `curl` command
|
||||
# failing. otherwise `sh` won't catch that `curl` returned non-0
|
||||
shell: bash
|
||||
run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/astral-sh/cargo-dist/releases/download/v0.28.4/cargo-dist-installer.sh | sh"
|
||||
run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/astral-sh/cargo-dist/releases/download/v0.28.5-prerelease.1/cargo-dist-installer.sh | sh"
|
||||
- name: Cache dist
|
||||
uses: actions/upload-artifact@6027e3dd177782cd8ab9af838c04fd81a07f1d47
|
||||
with:
|
||||
|
||||
16
.github/workflows/sync_typeshed.yaml
vendored
16
.github/workflows/sync_typeshed.yaml
vendored
@@ -39,13 +39,13 @@ jobs:
|
||||
- name: Sync typeshed
|
||||
id: sync
|
||||
run: |
|
||||
rm -rf ruff/crates/red_knot_vendored/vendor/typeshed
|
||||
mkdir ruff/crates/red_knot_vendored/vendor/typeshed
|
||||
cp typeshed/README.md ruff/crates/red_knot_vendored/vendor/typeshed
|
||||
cp typeshed/LICENSE ruff/crates/red_knot_vendored/vendor/typeshed
|
||||
cp -r typeshed/stdlib ruff/crates/red_knot_vendored/vendor/typeshed/stdlib
|
||||
rm -rf ruff/crates/red_knot_vendored/vendor/typeshed/stdlib/@tests
|
||||
git -C typeshed rev-parse HEAD > ruff/crates/red_knot_vendored/vendor/typeshed/source_commit.txt
|
||||
rm -rf ruff/crates/ty_vendored/vendor/typeshed
|
||||
mkdir ruff/crates/ty_vendored/vendor/typeshed
|
||||
cp typeshed/README.md ruff/crates/ty_vendored/vendor/typeshed
|
||||
cp typeshed/LICENSE ruff/crates/ty_vendored/vendor/typeshed
|
||||
cp -r typeshed/stdlib ruff/crates/ty_vendored/vendor/typeshed/stdlib
|
||||
rm -rf ruff/crates/ty_vendored/vendor/typeshed/stdlib/@tests
|
||||
git -C typeshed rev-parse HEAD > ruff/crates/ty_vendored/vendor/typeshed/source_commit.txt
|
||||
- name: Commit the changes
|
||||
id: commit
|
||||
if: ${{ steps.sync.outcome == 'success' }}
|
||||
@@ -79,5 +79,5 @@ jobs:
|
||||
repo: "ruff",
|
||||
title: `Automated typeshed sync failed on ${new Date().toDateString()}`,
|
||||
body: "Run listed here: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}",
|
||||
labels: ["bug", "red-knot"],
|
||||
labels: ["bug", "ty"],
|
||||
})
|
||||
|
||||
@@ -3,8 +3,9 @@ fail_fast: false
|
||||
exclude: |
|
||||
(?x)^(
|
||||
.github/workflows/release.yml|
|
||||
crates/red_knot_vendored/vendor/.*|
|
||||
crates/red_knot_project/resources/.*|
|
||||
crates/ty_vendored/vendor/.*|
|
||||
crates/ty_project/resources/.*|
|
||||
crates/ty/docs/(configuration|rules|cli).md|
|
||||
crates/ruff_benchmark/resources/.*|
|
||||
crates/ruff_linter/resources/.*|
|
||||
crates/ruff_linter/src/rules/.*/snapshots/.*|
|
||||
@@ -65,7 +66,7 @@ repos:
|
||||
- black==25.1.0
|
||||
|
||||
- repo: https://github.com/crate-ci/typos
|
||||
rev: v1.31.1
|
||||
rev: v1.32.0
|
||||
hooks:
|
||||
- id: typos
|
||||
|
||||
@@ -79,7 +80,7 @@ repos:
|
||||
pass_filenames: false # This makes it a lot faster
|
||||
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.11.7
|
||||
rev: v0.11.9
|
||||
hooks:
|
||||
- id: ruff-format
|
||||
- id: ruff
|
||||
@@ -97,7 +98,7 @@ 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.6.0
|
||||
rev: v1.7.0
|
||||
hooks:
|
||||
- id: zizmor
|
||||
|
||||
|
||||
32
CHANGELOG.md
32
CHANGELOG.md
@@ -1,5 +1,37 @@
|
||||
# Changelog
|
||||
|
||||
## 0.11.9
|
||||
|
||||
### Preview features
|
||||
|
||||
- Default to latest supported Python version for version-related syntax errors ([#17529](https://github.com/astral-sh/ruff/pull/17529))
|
||||
- Implement deferred annotations for Python 3.14 ([#17658](https://github.com/astral-sh/ruff/pull/17658))
|
||||
- \[`airflow`\] Fix `SQLTableCheckOperator` typo (`AIR302`) ([#17946](https://github.com/astral-sh/ruff/pull/17946))
|
||||
- \[`airflow`\] Remove `airflow.utils.dag_parsing_context.get_parsing_context` (`AIR301`) ([#17852](https://github.com/astral-sh/ruff/pull/17852))
|
||||
- \[`airflow`\] Skip attribute check in try catch block (`AIR301`) ([#17790](https://github.com/astral-sh/ruff/pull/17790))
|
||||
- \[`flake8-bandit`\] Mark tuples of string literals as trusted input in `S603` ([#17801](https://github.com/astral-sh/ruff/pull/17801))
|
||||
- \[`isort`\] Check full module path against project root(s) when categorizing first-party imports ([#16565](https://github.com/astral-sh/ruff/pull/16565))
|
||||
- \[`ruff`\] Add new rule `in-empty-collection` (`RUF060`) ([#16480](https://github.com/astral-sh/ruff/pull/16480))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- Fix missing `combine` call for `lint.typing-extensions` setting ([#17823](https://github.com/astral-sh/ruff/pull/17823))
|
||||
- \[`flake8-async`\] Fix module name in `ASYNC110`, `ASYNC115`, and `ASYNC116` fixes ([#17774](https://github.com/astral-sh/ruff/pull/17774))
|
||||
- \[`pyupgrade`\] Add spaces between tokens as necessary to avoid syntax errors in `UP018` autofix ([#17648](https://github.com/astral-sh/ruff/pull/17648))
|
||||
- \[`refurb`\] Fix false positive for float and complex numbers in `FURB116` ([#17661](https://github.com/astral-sh/ruff/pull/17661))
|
||||
- [parser] Flag single unparenthesized generator expr with trailing comma in arguments. ([#17893](https://github.com/astral-sh/ruff/pull/17893))
|
||||
|
||||
### Documentation
|
||||
|
||||
- Add instructions on how to upgrade to a newer Rust version ([#17928](https://github.com/astral-sh/ruff/pull/17928))
|
||||
- Update code of conduct email address ([#17875](https://github.com/astral-sh/ruff/pull/17875))
|
||||
- Add fix safety sections to `PLC2801`, `PLR1722`, and `RUF013` ([#17825](https://github.com/astral-sh/ruff/pull/17825), [#17826](https://github.com/astral-sh/ruff/pull/17826), [#17759](https://github.com/astral-sh/ruff/pull/17759))
|
||||
- Add link to `check-typed-exception` from `S110` and `S112` ([#17786](https://github.com/astral-sh/ruff/pull/17786))
|
||||
|
||||
### Other changes
|
||||
|
||||
- Allow passing a virtual environment to `ruff analyze graph` ([#17743](https://github.com/astral-sh/ruff/pull/17743))
|
||||
|
||||
## 0.11.8
|
||||
|
||||
### Preview features
|
||||
|
||||
@@ -71,8 +71,7 @@ representative at an online or offline event.
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported to the community leaders responsible for enforcement at
|
||||
<charlie.r.marsh@gmail.com>.
|
||||
reported to the community leaders responsible for enforcement at <hey@astral.sh>.
|
||||
All complaints will be reviewed and investigated promptly and fairly.
|
||||
|
||||
All community leaders are obligated to respect the privacy and security of the
|
||||
|
||||
@@ -2,6 +2,11 @@
|
||||
|
||||
Welcome! We're happy to have you here. Thank you in advance for your contribution to Ruff.
|
||||
|
||||
> [!NOTE]
|
||||
>
|
||||
> This guide is for Ruff. If you're looking to contribute to ty, please see [the ty contributing
|
||||
> guide](https://github.com/astral-sh/ruff/blob/main/crates/ty/CONTRIBUTING.md).
|
||||
|
||||
## The Basics
|
||||
|
||||
Ruff welcomes contributions in the form of pull requests.
|
||||
@@ -366,6 +371,15 @@ uvx --from ./python/ruff-ecosystem ruff-ecosystem format ruff "./target/debug/ru
|
||||
|
||||
See the [ruff-ecosystem package](https://github.com/astral-sh/ruff/tree/main/python/ruff-ecosystem) for more details.
|
||||
|
||||
## Upgrading Rust
|
||||
|
||||
1. Change the `channel` in `./rust-toolchain.toml` to the new Rust version (`<latest>`)
|
||||
1. Change the `rust-version` in the `./Cargo.toml` to `<latest> - 2` (e.g. 1.84 if the latest is 1.86)
|
||||
1. Run `cargo clippy --fix --allow-dirty --allow-staged` to fix new clippy warnings
|
||||
1. Create and merge the PR
|
||||
1. Bump the Rust version in Ruff's conda forge recipe. See [this PR](https://github.com/conda-forge/ruff-feedstock/pull/266) for an example.
|
||||
1. Enjoy the new Rust version!
|
||||
|
||||
## Benchmarking and Profiling
|
||||
|
||||
We have several ways of benchmarking and profiling Ruff:
|
||||
|
||||
588
Cargo.lock
generated
588
Cargo.lock
generated
@@ -150,9 +150,9 @@ checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
|
||||
|
||||
[[package]]
|
||||
name = "assert_fs"
|
||||
version = "1.1.2"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7efdb1fdb47602827a342857666feb372712cbc64b414172bd6b167a02927674"
|
||||
checksum = "a652f6cb1f516886fcfee5e7a5c078b9ade62cfcb889524efe5a64d682dd27a9"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"doc-comment",
|
||||
@@ -334,9 +334,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.37"
|
||||
version = "4.5.38"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eccb054f56cbd38340b380d4a8e69ef1f02f1af43db2f0cc817a4774d80ae071"
|
||||
checksum = "ed93b9805f8ba930df42c2590f05453d5ec36cbb85d018868a5b24d31f6ac000"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
@@ -344,9 +344,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.37"
|
||||
version = "4.5.38"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "efd9466fac8543255d3b1fcad4762c5e116ffe808c8a3043d4263cd4fd4862a2"
|
||||
checksum = "379026ff283facf611b0ea629334361c4211d1b12ee01024eec1591133b04120"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
@@ -409,7 +409,7 @@ version = "4.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8c41dc435a7b98e4608224bbf65282309f5403719df9113621b30f8b6f74e2f4"
|
||||
dependencies = [
|
||||
"nix",
|
||||
"nix 0.29.0",
|
||||
"terminfo",
|
||||
"thiserror 2.0.12",
|
||||
"which",
|
||||
@@ -478,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]]
|
||||
@@ -487,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]]
|
||||
@@ -681,11 +681,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ctrlc"
|
||||
version = "3.4.6"
|
||||
version = "3.4.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "697b5419f348fd5ae2478e8018cb016c00a5881c7f46c717de98ffd135a5651c"
|
||||
checksum = "46f93780a459b7d656ef7f071fe699c4d3d2cb201c4b24d085b6ddc505276e73"
|
||||
dependencies = [
|
||||
"nix",
|
||||
"nix 0.30.1",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
@@ -904,7 +904,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]]
|
||||
@@ -1057,9 +1057,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.3.2"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0"
|
||||
checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"js-sys",
|
||||
@@ -1117,9 +1117,9 @@ checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.15.2"
|
||||
version = "0.15.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289"
|
||||
checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3"
|
||||
dependencies = [
|
||||
"allocator-api2",
|
||||
"equivalent",
|
||||
@@ -1132,7 +1132,7 @@ version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1"
|
||||
dependencies = [
|
||||
"hashbrown 0.15.2",
|
||||
"hashbrown 0.15.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1361,7 +1361,7 @@ version = "0.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "17d34b7d42178945f775e84bc4c36dde7c1c6cdfea656d3354d009056f2bb3d2"
|
||||
dependencies = [
|
||||
"hashbrown 0.15.2",
|
||||
"hashbrown 0.15.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1381,7 +1381,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown 0.15.2",
|
||||
"hashbrown 0.15.3",
|
||||
"serde",
|
||||
]
|
||||
|
||||
@@ -1485,7 +1485,7 @@ checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9"
|
||||
dependencies = [
|
||||
"hermit-abi 0.5.0",
|
||||
"libc",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1539,9 +1539,9 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
|
||||
|
||||
[[package]]
|
||||
name = "jiff"
|
||||
version = "0.2.10"
|
||||
version = "0.2.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a064218214dc6a10fbae5ec5fa888d80c45d611aba169222fc272072bf7aef6"
|
||||
checksum = "f02000660d30638906021176af16b17498bd0d12813dbfe7b276d8bc7f3c0806"
|
||||
dependencies = [
|
||||
"jiff-static",
|
||||
"jiff-tzdb-platform",
|
||||
@@ -1549,14 +1549,14 @@ dependencies = [
|
||||
"portable-atomic",
|
||||
"portable-atomic-util",
|
||||
"serde",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jiff-static"
|
||||
version = "0.2.10"
|
||||
version = "0.2.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "199b7932d97e325aff3a7030e141eafe7f2c6268e1d1b24859b753a627f45254"
|
||||
checksum = "f3c30758ddd7188629c6713fc45d1188af4f44c90582311d0c8d8c9907f60c48"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -1771,6 +1771,15 @@ dependencies = [
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "markdown"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a5cab8f2cadc416a82d2e783a1946388b31654d391d1c7d92cc1f03e295b1deb"
|
||||
dependencies = [
|
||||
"unicode-id",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "matchers"
|
||||
version = "0.1.0"
|
||||
@@ -1871,6 +1880,18 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.30.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"cfg-if",
|
||||
"cfg_aliases",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "7.1.3"
|
||||
@@ -2449,7 +2470,7 @@ version = "0.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
|
||||
dependencies = [
|
||||
"getrandom 0.3.2",
|
||||
"getrandom 0.3.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2472,215 +2493,6 @@ dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "red_knot"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"argfile",
|
||||
"clap",
|
||||
"colored 3.0.0",
|
||||
"countme",
|
||||
"crossbeam",
|
||||
"ctrlc",
|
||||
"filetime",
|
||||
"insta",
|
||||
"insta-cmd",
|
||||
"jiff",
|
||||
"rayon",
|
||||
"red_knot_project",
|
||||
"red_knot_python_semantic",
|
||||
"red_knot_server",
|
||||
"regex",
|
||||
"ruff_db",
|
||||
"ruff_python_ast",
|
||||
"ruff_python_trivia",
|
||||
"salsa",
|
||||
"tempfile",
|
||||
"toml",
|
||||
"tracing",
|
||||
"tracing-flame",
|
||||
"tracing-subscriber",
|
||||
"tracing-tree",
|
||||
"wild",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "red_knot_ide"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"insta",
|
||||
"red_knot_python_semantic",
|
||||
"red_knot_vendored",
|
||||
"ruff_db",
|
||||
"ruff_python_ast",
|
||||
"ruff_python_parser",
|
||||
"ruff_text_size",
|
||||
"rustc-hash 2.1.1",
|
||||
"salsa",
|
||||
"smallvec",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "red_knot_project"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"crossbeam",
|
||||
"glob",
|
||||
"insta",
|
||||
"notify",
|
||||
"pep440_rs",
|
||||
"rayon",
|
||||
"red_knot_ide",
|
||||
"red_knot_python_semantic",
|
||||
"red_knot_vendored",
|
||||
"ruff_cache",
|
||||
"ruff_db",
|
||||
"ruff_macros",
|
||||
"ruff_python_ast",
|
||||
"ruff_python_formatter",
|
||||
"ruff_text_size",
|
||||
"rustc-hash 2.1.1",
|
||||
"salsa",
|
||||
"schemars",
|
||||
"serde",
|
||||
"thiserror 2.0.12",
|
||||
"toml",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "red_knot_python_semantic"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bitflags 2.9.0",
|
||||
"camino",
|
||||
"compact_str",
|
||||
"countme",
|
||||
"dir-test",
|
||||
"drop_bomb",
|
||||
"hashbrown 0.15.2",
|
||||
"indexmap",
|
||||
"insta",
|
||||
"itertools 0.14.0",
|
||||
"memchr",
|
||||
"ordermap",
|
||||
"quickcheck",
|
||||
"quickcheck_macros",
|
||||
"red_knot_test",
|
||||
"red_knot_vendored",
|
||||
"ruff_db",
|
||||
"ruff_index",
|
||||
"ruff_macros",
|
||||
"ruff_python_ast",
|
||||
"ruff_python_literal",
|
||||
"ruff_python_parser",
|
||||
"ruff_python_stdlib",
|
||||
"ruff_python_trivia",
|
||||
"ruff_source_file",
|
||||
"ruff_text_size",
|
||||
"rustc-hash 2.1.1",
|
||||
"salsa",
|
||||
"schemars",
|
||||
"serde",
|
||||
"smallvec",
|
||||
"static_assertions",
|
||||
"strum",
|
||||
"strum_macros",
|
||||
"tempfile",
|
||||
"test-case",
|
||||
"thiserror 2.0.12",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "red_knot_server"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"crossbeam",
|
||||
"jod-thread",
|
||||
"libc",
|
||||
"lsp-server",
|
||||
"lsp-types",
|
||||
"red_knot_ide",
|
||||
"red_knot_project",
|
||||
"red_knot_python_semantic",
|
||||
"ruff_db",
|
||||
"ruff_notebook",
|
||||
"ruff_source_file",
|
||||
"ruff_text_size",
|
||||
"rustc-hash 2.1.1",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"shellexpand",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "red_knot_test"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"camino",
|
||||
"colored 3.0.0",
|
||||
"insta",
|
||||
"memchr",
|
||||
"red_knot_python_semantic",
|
||||
"red_knot_vendored",
|
||||
"regex",
|
||||
"ruff_db",
|
||||
"ruff_index",
|
||||
"ruff_notebook",
|
||||
"ruff_python_ast",
|
||||
"ruff_python_trivia",
|
||||
"ruff_source_file",
|
||||
"ruff_text_size",
|
||||
"rustc-hash 2.1.1",
|
||||
"salsa",
|
||||
"serde",
|
||||
"smallvec",
|
||||
"tempfile",
|
||||
"thiserror 2.0.12",
|
||||
"toml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "red_knot_vendored"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"path-slash",
|
||||
"ruff_db",
|
||||
"walkdir",
|
||||
"zip",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "red_knot_wasm"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"console_error_panic_hook",
|
||||
"console_log",
|
||||
"getrandom 0.3.2",
|
||||
"js-sys",
|
||||
"log",
|
||||
"red_knot_ide",
|
||||
"red_knot_project",
|
||||
"red_knot_python_semantic",
|
||||
"ruff_db",
|
||||
"ruff_notebook",
|
||||
"ruff_python_formatter",
|
||||
"ruff_source_file",
|
||||
"ruff_text_size",
|
||||
"serde-wasm-bindgen",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-test",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.5.10"
|
||||
@@ -2758,7 +2570,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.11.8"
|
||||
version = "0.11.9"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"argfile",
|
||||
@@ -2792,6 +2604,7 @@ dependencies = [
|
||||
"ruff_linter",
|
||||
"ruff_macros",
|
||||
"ruff_notebook",
|
||||
"ruff_options_metadata",
|
||||
"ruff_python_ast",
|
||||
"ruff_python_formatter",
|
||||
"ruff_python_parser",
|
||||
@@ -2837,7 +2650,6 @@ dependencies = [
|
||||
"criterion",
|
||||
"mimalloc",
|
||||
"rayon",
|
||||
"red_knot_project",
|
||||
"ruff_db",
|
||||
"ruff_linter",
|
||||
"ruff_python_ast",
|
||||
@@ -2846,6 +2658,7 @@ dependencies = [
|
||||
"ruff_python_trivia",
|
||||
"rustc-hash 2.1.1",
|
||||
"tikv-jemallocator",
|
||||
"ty_project",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2910,15 +2723,16 @@ dependencies = [
|
||||
"indoc",
|
||||
"itertools 0.14.0",
|
||||
"libcst",
|
||||
"markdown",
|
||||
"pretty_assertions",
|
||||
"rayon",
|
||||
"red_knot_project",
|
||||
"regex",
|
||||
"ruff",
|
||||
"ruff_diagnostics",
|
||||
"ruff_formatter",
|
||||
"ruff_linter",
|
||||
"ruff_notebook",
|
||||
"ruff_options_metadata",
|
||||
"ruff_python_ast",
|
||||
"ruff_python_codegen",
|
||||
"ruff_python_formatter",
|
||||
@@ -2935,6 +2749,9 @@ dependencies = [
|
||||
"tracing",
|
||||
"tracing-indicatif",
|
||||
"tracing-subscriber",
|
||||
"ty",
|
||||
"ty_project",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2970,7 +2787,6 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
"red_knot_python_semantic",
|
||||
"ruff_cache",
|
||||
"ruff_db",
|
||||
"ruff_linter",
|
||||
@@ -2980,6 +2796,7 @@ dependencies = [
|
||||
"salsa",
|
||||
"schemars",
|
||||
"serde",
|
||||
"ty_python_semantic",
|
||||
"zip",
|
||||
]
|
||||
|
||||
@@ -2994,7 +2811,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_linter"
|
||||
version = "0.11.8"
|
||||
version = "0.11.9"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"anyhow",
|
||||
@@ -3022,6 +2839,7 @@ dependencies = [
|
||||
"regex",
|
||||
"ruff_annotate_snippets",
|
||||
"ruff_cache",
|
||||
"ruff_db",
|
||||
"ruff_diagnostics",
|
||||
"ruff_macros",
|
||||
"ruff_notebook",
|
||||
@@ -3043,6 +2861,7 @@ dependencies = [
|
||||
"smallvec",
|
||||
"strum",
|
||||
"strum_macros",
|
||||
"tempfile",
|
||||
"test-case",
|
||||
"thiserror 2.0.12",
|
||||
"toml",
|
||||
@@ -3082,6 +2901,13 @@ dependencies = [
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ruff_options_metadata"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ruff_python_ast"
|
||||
version = "0.0.0"
|
||||
@@ -3320,11 +3146,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_wasm"
|
||||
version = "0.11.8"
|
||||
version = "0.11.9"
|
||||
dependencies = [
|
||||
"console_error_panic_hook",
|
||||
"console_log",
|
||||
"getrandom 0.3.2",
|
||||
"getrandom 0.3.3",
|
||||
"js-sys",
|
||||
"log",
|
||||
"ruff_formatter",
|
||||
@@ -3368,6 +3194,7 @@ dependencies = [
|
||||
"ruff_graph",
|
||||
"ruff_linter",
|
||||
"ruff_macros",
|
||||
"ruff_options_metadata",
|
||||
"ruff_python_ast",
|
||||
"ruff_python_formatter",
|
||||
"ruff_python_semantic",
|
||||
@@ -3404,6 +3231,12 @@ version = "2.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
|
||||
|
||||
[[package]]
|
||||
name = "rustc-stable-hash"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "781442f29170c5c93b7185ad559492601acdc71d5bb0706f5868094f45cfcd08"
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.38.44"
|
||||
@@ -3414,7 +3247,7 @@ dependencies = [
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys 0.4.15",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3427,7 +3260,7 @@ dependencies = [
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys 0.9.3",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3444,14 +3277,14 @@ checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
|
||||
|
||||
[[package]]
|
||||
name = "salsa"
|
||||
version = "0.21.0"
|
||||
source = "git+https://github.com/salsa-rs/salsa.git?rev=42f15835c0005c4b37aaf5bc1a15e3e1b3df14b7#42f15835c0005c4b37aaf5bc1a15e3e1b3df14b7"
|
||||
version = "0.21.1"
|
||||
source = "git+https://github.com/salsa-rs/salsa.git?rev=7edce6e248f35c8114b4b021cdb474a3fb2813b3#7edce6e248f35c8114b4b021cdb474a3fb2813b3"
|
||||
dependencies = [
|
||||
"boxcar",
|
||||
"compact_str",
|
||||
"crossbeam-queue",
|
||||
"dashmap 6.1.0",
|
||||
"hashbrown 0.15.2",
|
||||
"hashbrown 0.15.3",
|
||||
"hashlink",
|
||||
"indexmap",
|
||||
"parking_lot",
|
||||
@@ -3467,13 +3300,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "salsa-macro-rules"
|
||||
version = "0.21.0"
|
||||
source = "git+https://github.com/salsa-rs/salsa.git?rev=42f15835c0005c4b37aaf5bc1a15e3e1b3df14b7#42f15835c0005c4b37aaf5bc1a15e3e1b3df14b7"
|
||||
version = "0.21.1"
|
||||
source = "git+https://github.com/salsa-rs/salsa.git?rev=7edce6e248f35c8114b4b021cdb474a3fb2813b3#7edce6e248f35c8114b4b021cdb474a3fb2813b3"
|
||||
|
||||
[[package]]
|
||||
name = "salsa-macros"
|
||||
version = "0.21.0"
|
||||
source = "git+https://github.com/salsa-rs/salsa.git?rev=42f15835c0005c4b37aaf5bc1a15e3e1b3df14b7#42f15835c0005c4b37aaf5bc1a15e3e1b3df14b7"
|
||||
version = "0.21.1"
|
||||
source = "git+https://github.com/salsa-rs/salsa.git?rev=7edce6e248f35c8114b4b021cdb474a3fb2813b3#7edce6e248f35c8114b4b021cdb474a3fb2813b3"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
@@ -3810,10 +3643,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7437ac7763b9b123ccf33c338a5cc1bac6f69b45a136c19bdd8a65e3916435bf"
|
||||
dependencies = [
|
||||
"fastrand",
|
||||
"getrandom 0.3.2",
|
||||
"getrandom 0.3.3",
|
||||
"once_cell",
|
||||
"rustix 1.0.2",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4008,9 +3841,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.8.20"
|
||||
version = "0.8.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148"
|
||||
checksum = "05ae329d1f08c4d17a59bed7ff5b5a769d062e64a62d34a3261b219e62cd5aae"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
@@ -4020,26 +3853,33 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.6.8"
|
||||
version = "0.6.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41"
|
||||
checksum = "3da5db5a963e24bc68be8b17b6fa82814bb22ee8660f192bb182771d498f09a3"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.22.24"
|
||||
version = "0.22.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474"
|
||||
checksum = "310068873db2c5b3e7659d2cc35d21855dbafa50d1ce336397c666e3cb08137e"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"toml_write",
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_write"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bfb942dfe1d8e29a7ee7fcbde5bd2b9a25fb89aa70caea2eba3bee836ff41076"
|
||||
|
||||
[[package]]
|
||||
name = "tracing"
|
||||
version = "0.1.41"
|
||||
@@ -4149,6 +3989,220 @@ dependencies = [
|
||||
"snapbox",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ty"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"argfile",
|
||||
"clap",
|
||||
"clap_complete_command",
|
||||
"colored 3.0.0",
|
||||
"countme",
|
||||
"crossbeam",
|
||||
"ctrlc",
|
||||
"filetime",
|
||||
"indicatif",
|
||||
"insta",
|
||||
"insta-cmd",
|
||||
"jiff",
|
||||
"rayon",
|
||||
"regex",
|
||||
"ruff_db",
|
||||
"ruff_python_ast",
|
||||
"ruff_python_trivia",
|
||||
"salsa",
|
||||
"tempfile",
|
||||
"toml",
|
||||
"tracing",
|
||||
"tracing-flame",
|
||||
"tracing-subscriber",
|
||||
"tracing-tree",
|
||||
"ty_project",
|
||||
"ty_python_semantic",
|
||||
"ty_server",
|
||||
"wild",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ty_ide"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"insta",
|
||||
"ruff_db",
|
||||
"ruff_python_ast",
|
||||
"ruff_python_parser",
|
||||
"ruff_text_size",
|
||||
"rustc-hash 2.1.1",
|
||||
"salsa",
|
||||
"smallvec",
|
||||
"tracing",
|
||||
"ty_python_semantic",
|
||||
"ty_vendored",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ty_project"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"crossbeam",
|
||||
"glob",
|
||||
"insta",
|
||||
"notify",
|
||||
"pep440_rs",
|
||||
"rayon",
|
||||
"ruff_cache",
|
||||
"ruff_db",
|
||||
"ruff_macros",
|
||||
"ruff_options_metadata",
|
||||
"ruff_python_ast",
|
||||
"ruff_python_formatter",
|
||||
"ruff_text_size",
|
||||
"rustc-hash 2.1.1",
|
||||
"salsa",
|
||||
"schemars",
|
||||
"serde",
|
||||
"thiserror 2.0.12",
|
||||
"toml",
|
||||
"tracing",
|
||||
"ty_ide",
|
||||
"ty_python_semantic",
|
||||
"ty_vendored",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ty_python_semantic"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bitflags 2.9.0",
|
||||
"camino",
|
||||
"compact_str",
|
||||
"countme",
|
||||
"dir-test",
|
||||
"drop_bomb",
|
||||
"hashbrown 0.15.3",
|
||||
"indexmap",
|
||||
"insta",
|
||||
"itertools 0.14.0",
|
||||
"memchr",
|
||||
"ordermap",
|
||||
"quickcheck",
|
||||
"quickcheck_macros",
|
||||
"ruff_db",
|
||||
"ruff_index",
|
||||
"ruff_macros",
|
||||
"ruff_python_ast",
|
||||
"ruff_python_literal",
|
||||
"ruff_python_parser",
|
||||
"ruff_python_stdlib",
|
||||
"ruff_python_trivia",
|
||||
"ruff_source_file",
|
||||
"ruff_text_size",
|
||||
"rustc-hash 2.1.1",
|
||||
"salsa",
|
||||
"schemars",
|
||||
"serde",
|
||||
"smallvec",
|
||||
"static_assertions",
|
||||
"strum",
|
||||
"strum_macros",
|
||||
"tempfile",
|
||||
"test-case",
|
||||
"thiserror 2.0.12",
|
||||
"tracing",
|
||||
"ty_test",
|
||||
"ty_vendored",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ty_server"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"crossbeam",
|
||||
"jod-thread",
|
||||
"libc",
|
||||
"lsp-server",
|
||||
"lsp-types",
|
||||
"ruff_db",
|
||||
"ruff_notebook",
|
||||
"ruff_source_file",
|
||||
"ruff_text_size",
|
||||
"rustc-hash 2.1.1",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"shellexpand",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
"ty_ide",
|
||||
"ty_project",
|
||||
"ty_python_semantic",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ty_test"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"camino",
|
||||
"colored 3.0.0",
|
||||
"insta",
|
||||
"memchr",
|
||||
"regex",
|
||||
"ruff_db",
|
||||
"ruff_index",
|
||||
"ruff_notebook",
|
||||
"ruff_python_ast",
|
||||
"ruff_python_trivia",
|
||||
"ruff_source_file",
|
||||
"ruff_text_size",
|
||||
"rustc-hash 2.1.1",
|
||||
"rustc-stable-hash",
|
||||
"salsa",
|
||||
"serde",
|
||||
"smallvec",
|
||||
"tempfile",
|
||||
"thiserror 2.0.12",
|
||||
"toml",
|
||||
"tracing",
|
||||
"ty_python_semantic",
|
||||
"ty_vendored",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ty_vendored"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"path-slash",
|
||||
"ruff_db",
|
||||
"walkdir",
|
||||
"zip",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ty_wasm"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"console_error_panic_hook",
|
||||
"console_log",
|
||||
"getrandom 0.3.3",
|
||||
"js-sys",
|
||||
"log",
|
||||
"ruff_db",
|
||||
"ruff_notebook",
|
||||
"ruff_python_formatter",
|
||||
"ruff_source_file",
|
||||
"ruff_text_size",
|
||||
"serde-wasm-bindgen",
|
||||
"ty_ide",
|
||||
"ty_project",
|
||||
"ty_python_semantic",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-test",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typed-arena"
|
||||
version = "2.0.2"
|
||||
@@ -4209,6 +4263,12 @@ dependencies = [
|
||||
"unic-common",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-id"
|
||||
version = "0.3.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "10103c57044730945224467c09f71a4db0071c123a0648cc3e818913bde6b561"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.18"
|
||||
@@ -4312,7 +4372,7 @@ version = "1.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9"
|
||||
dependencies = [
|
||||
"getrandom 0.3.2",
|
||||
"getrandom 0.3.3",
|
||||
"js-sys",
|
||||
"rand 0.9.1",
|
||||
"uuid-macro-internal",
|
||||
@@ -4585,7 +4645,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]]
|
||||
@@ -4823,9 +4883,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.7.4"
|
||||
version = "0.7.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0e97b544156e9bebe1a0ffbc03484fc1ffe3100cbce3ffb17eac35f7cdd7ab36"
|
||||
checksum = "d9fb597c990f03753e08d3c29efbfcf2019a003b4bf4ba19225c158e1549f0f3"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
91
Cargo.toml
91
Cargo.toml
@@ -23,6 +23,7 @@ ruff_index = { path = "crates/ruff_index" }
|
||||
ruff_linter = { path = "crates/ruff_linter" }
|
||||
ruff_macros = { path = "crates/ruff_macros" }
|
||||
ruff_notebook = { path = "crates/ruff_notebook" }
|
||||
ruff_options_metadata = { path = "crates/ruff_options_metadata" }
|
||||
ruff_python_ast = { path = "crates/ruff_python_ast" }
|
||||
ruff_python_codegen = { path = "crates/ruff_python_codegen" }
|
||||
ruff_python_formatter = { path = "crates/ruff_python_formatter" }
|
||||
@@ -35,14 +36,15 @@ ruff_python_trivia = { path = "crates/ruff_python_trivia" }
|
||||
ruff_server = { path = "crates/ruff_server" }
|
||||
ruff_source_file = { path = "crates/ruff_source_file" }
|
||||
ruff_text_size = { path = "crates/ruff_text_size" }
|
||||
red_knot_vendored = { path = "crates/red_knot_vendored" }
|
||||
ruff_workspace = { path = "crates/ruff_workspace" }
|
||||
|
||||
red_knot_ide = { path = "crates/red_knot_ide" }
|
||||
red_knot_project = { path = "crates/red_knot_project", default-features = false }
|
||||
red_knot_python_semantic = { path = "crates/red_knot_python_semantic" }
|
||||
red_knot_server = { path = "crates/red_knot_server" }
|
||||
red_knot_test = { path = "crates/red_knot_test" }
|
||||
ty = { path = "crates/ty" }
|
||||
ty_ide = { path = "crates/ty_ide" }
|
||||
ty_project = { path = "crates/ty_project", default-features = false }
|
||||
ty_python_semantic = { path = "crates/ty_python_semantic" }
|
||||
ty_server = { path = "crates/ty_server" }
|
||||
ty_test = { path = "crates/ty_test" }
|
||||
ty_vendored = { path = "crates/ty_vendored" }
|
||||
|
||||
aho-corasick = { version = "1.1.3" }
|
||||
anstream = { version = "0.6.18" }
|
||||
@@ -123,8 +125,9 @@ rand = { version = "0.9.0" }
|
||||
rayon = { version = "1.10.0" }
|
||||
regex = { version = "1.10.2" }
|
||||
rustc-hash = { version = "2.0.0" }
|
||||
rustc-stable-hash = { version = "0.1.2" }
|
||||
# When updating salsa, make sure to also update the revision in `fuzz/Cargo.toml`
|
||||
salsa = { git = "https://github.com/salsa-rs/salsa.git", rev = "42f15835c0005c4b37aaf5bc1a15e3e1b3df14b7" }
|
||||
salsa = { git = "https://github.com/salsa-rs/salsa.git", rev = "7edce6e248f35c8114b4b021cdb474a3fb2813b3" }
|
||||
schemars = { version = "0.8.16" }
|
||||
seahash = { version = "4.1.0" }
|
||||
serde = { version = "1.0.197", features = ["derive"] }
|
||||
@@ -182,7 +185,7 @@ wild = { version = "2" }
|
||||
zip = { version = "0.6.6", default-features = false }
|
||||
|
||||
[workspace.metadata.cargo-shear]
|
||||
ignored = ["getrandom"]
|
||||
ignored = ["getrandom", "ruff_options_metadata"]
|
||||
|
||||
|
||||
[workspace.lints.rust]
|
||||
@@ -268,75 +271,3 @@ debug = 1
|
||||
# The profile that 'cargo dist' will build with.
|
||||
[profile.dist]
|
||||
inherits = "release"
|
||||
|
||||
# Config for 'dist'
|
||||
[workspace.metadata.dist]
|
||||
# The preferred dist version to use in CI (Cargo.toml SemVer syntax)
|
||||
cargo-dist-version = "0.28.4"
|
||||
# Make distability of apps opt-in instead of opt-out
|
||||
dist = false
|
||||
# CI backends to support
|
||||
ci = "github"
|
||||
# The installers to generate for each app
|
||||
installers = ["shell", "powershell"]
|
||||
# The archive format to use for windows builds (defaults .zip)
|
||||
windows-archive = ".zip"
|
||||
# The archive format to use for non-windows builds (defaults .tar.xz)
|
||||
unix-archive = ".tar.gz"
|
||||
# Target platforms to build apps for (Rust target-triple syntax)
|
||||
targets = [
|
||||
"aarch64-apple-darwin",
|
||||
"aarch64-pc-windows-msvc",
|
||||
"aarch64-unknown-linux-gnu",
|
||||
"aarch64-unknown-linux-musl",
|
||||
"arm-unknown-linux-musleabihf",
|
||||
"armv7-unknown-linux-gnueabihf",
|
||||
"armv7-unknown-linux-musleabihf",
|
||||
"i686-pc-windows-msvc",
|
||||
"i686-unknown-linux-gnu",
|
||||
"i686-unknown-linux-musl",
|
||||
"powerpc64-unknown-linux-gnu",
|
||||
"powerpc64le-unknown-linux-gnu",
|
||||
"s390x-unknown-linux-gnu",
|
||||
"x86_64-apple-darwin",
|
||||
"x86_64-pc-windows-msvc",
|
||||
"x86_64-unknown-linux-gnu",
|
||||
"x86_64-unknown-linux-musl",
|
||||
]
|
||||
# Whether to auto-include files like READMEs, LICENSEs, and CHANGELOGs (default true)
|
||||
auto-includes = false
|
||||
# Whether dist should create a Github Release or use an existing draft
|
||||
create-release = true
|
||||
# Which actions to run on pull requests
|
||||
pr-run-mode = "plan"
|
||||
# Whether CI should trigger releases with dispatches instead of tag pushes
|
||||
dispatch-releases = true
|
||||
# Which phase dist should use to create the GitHub release
|
||||
github-release = "announce"
|
||||
# Whether CI should include auto-generated code to build local artifacts
|
||||
build-local-artifacts = false
|
||||
# Local artifacts jobs to run in CI
|
||||
local-artifacts-jobs = ["./build-binaries", "./build-docker"]
|
||||
# Publish jobs to run in CI
|
||||
publish-jobs = ["./publish-pypi", "./publish-wasm"]
|
||||
# Post-announce jobs to run in CI
|
||||
post-announce-jobs = [
|
||||
"./notify-dependents",
|
||||
"./publish-docs",
|
||||
"./publish-playground",
|
||||
]
|
||||
# Custom permissions for GitHub Jobs
|
||||
github-custom-job-permissions = { "build-docker" = { packages = "write", contents = "read" }, "publish-wasm" = { contents = "read", id-token = "write", packages = "write" } }
|
||||
# Whether to install an updater program
|
||||
install-updater = false
|
||||
# Path that installers should place binaries in
|
||||
install-path = ["$XDG_BIN_HOME/", "$XDG_DATA_HOME/../bin", "~/.local/bin"]
|
||||
|
||||
[workspace.metadata.dist.github-custom-runners]
|
||||
global = "depot-ubuntu-latest-4"
|
||||
|
||||
[workspace.metadata.dist.github-action-commits]
|
||||
"actions/checkout" = "85e6279cec87321a52edac9c87bce653a07cf6c2" # v4
|
||||
"actions/upload-artifact" = "6027e3dd177782cd8ab9af838c04fd81a07f1d47" # v4.6.2
|
||||
"actions/download-artifact" = "d3f86a106a0bac45b974a628896c90dbdf5c8093" # v4.3.0
|
||||
"actions/attest-build-provenance" = "c074443f1aee8d4aeeae555aebba3282517141b2" #v2.2.3
|
||||
|
||||
@@ -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.11.8/install.sh | sh
|
||||
powershell -c "irm https://astral.sh/ruff/0.11.8/install.ps1 | iex"
|
||||
curl -LsSf https://astral.sh/ruff/0.11.9/install.sh | sh
|
||||
powershell -c "irm https://astral.sh/ruff/0.11.9/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.11.8
|
||||
rev: v0.11.9
|
||||
hooks:
|
||||
# Run the linter.
|
||||
- id: ruff
|
||||
@@ -255,7 +255,7 @@ indent-width = 4
|
||||
target-version = "py39"
|
||||
|
||||
[lint]
|
||||
# Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default.
|
||||
# Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default.
|
||||
select = ["E4", "E7", "E9", "F"]
|
||||
ignore = []
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[files]
|
||||
# https://github.com/crate-ci/typos/issues/868
|
||||
extend-exclude = [
|
||||
"crates/red_knot_vendored/vendor/**/*",
|
||||
"crates/ty_vendored/vendor/**/*",
|
||||
"**/resources/**/*",
|
||||
"**/snapshots/**/*",
|
||||
]
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
# Red Knot
|
||||
|
||||
Red Knot is an extremely fast type checker.
|
||||
Currently, it is a work-in-progress and not ready for user testing.
|
||||
|
||||
Red Knot is designed to prioritize good type inference, even in unannotated code,
|
||||
and aims to avoid false positives.
|
||||
|
||||
While Red Knot will produce similar results to mypy and pyright on many codebases,
|
||||
100% compatibility with these tools is a non-goal.
|
||||
On some codebases, Red Knot's design decisions lead to different outcomes
|
||||
than you would get from running one of these more established tools.
|
||||
|
||||
## Contributing
|
||||
|
||||
Core type checking tests are written as Markdown code blocks.
|
||||
They can be found in [`red_knot_python_semantic/resources/mdtest`][resources-mdtest].
|
||||
See [`red_knot_test/README.md`][mdtest-readme] for more information
|
||||
on the test framework itself.
|
||||
|
||||
The list of open issues can be found [here][open-issues].
|
||||
|
||||
[mdtest-readme]: ../red_knot_test/README.md
|
||||
[open-issues]: https://github.com/astral-sh/ruff/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20label%3Ared-knot
|
||||
[resources-mdtest]: ../red_knot_python_semantic/resources/mdtest
|
||||
@@ -1,129 +0,0 @@
|
||||
# Typing-module aliases to other stdlib classes
|
||||
|
||||
The `typing` module has various aliases to other stdlib classes. These are a legacy feature, but
|
||||
still need to be supported by a type checker.
|
||||
|
||||
## Correspondence
|
||||
|
||||
All of the following symbols can be mapped one-to-one with the actual type:
|
||||
|
||||
```py
|
||||
import typing
|
||||
|
||||
def f(
|
||||
list_bare: typing.List,
|
||||
list_parametrized: typing.List[int],
|
||||
dict_bare: typing.Dict,
|
||||
dict_parametrized: typing.Dict[int, str],
|
||||
set_bare: typing.Set,
|
||||
set_parametrized: typing.Set[int],
|
||||
frozen_set_bare: typing.FrozenSet,
|
||||
frozen_set_parametrized: typing.FrozenSet[str],
|
||||
chain_map_bare: typing.ChainMap,
|
||||
chain_map_parametrized: typing.ChainMap[int],
|
||||
counter_bare: typing.Counter,
|
||||
counter_parametrized: typing.Counter[int],
|
||||
default_dict_bare: typing.DefaultDict,
|
||||
default_dict_parametrized: typing.DefaultDict[str, int],
|
||||
deque_bare: typing.Deque,
|
||||
deque_parametrized: typing.Deque[str],
|
||||
ordered_dict_bare: typing.OrderedDict,
|
||||
ordered_dict_parametrized: typing.OrderedDict[int, str],
|
||||
):
|
||||
reveal_type(list_bare) # revealed: list
|
||||
reveal_type(list_parametrized) # revealed: list
|
||||
|
||||
reveal_type(dict_bare) # revealed: dict
|
||||
reveal_type(dict_parametrized) # revealed: dict
|
||||
|
||||
reveal_type(set_bare) # revealed: set
|
||||
reveal_type(set_parametrized) # revealed: set
|
||||
|
||||
reveal_type(frozen_set_bare) # revealed: frozenset
|
||||
reveal_type(frozen_set_parametrized) # revealed: frozenset
|
||||
|
||||
reveal_type(chain_map_bare) # revealed: ChainMap
|
||||
reveal_type(chain_map_parametrized) # revealed: ChainMap
|
||||
|
||||
reveal_type(counter_bare) # revealed: Counter
|
||||
reveal_type(counter_parametrized) # revealed: Counter
|
||||
|
||||
reveal_type(default_dict_bare) # revealed: defaultdict
|
||||
reveal_type(default_dict_parametrized) # revealed: defaultdict
|
||||
|
||||
reveal_type(deque_bare) # revealed: deque
|
||||
reveal_type(deque_parametrized) # revealed: deque
|
||||
|
||||
reveal_type(ordered_dict_bare) # revealed: OrderedDict
|
||||
reveal_type(ordered_dict_parametrized) # revealed: OrderedDict
|
||||
```
|
||||
|
||||
## Inheritance
|
||||
|
||||
The aliases can be inherited from. Some of these are still partially or wholly TODOs.
|
||||
|
||||
```py
|
||||
import typing
|
||||
|
||||
####################
|
||||
### Built-ins
|
||||
####################
|
||||
|
||||
class ListSubclass(typing.List): ...
|
||||
|
||||
# TODO: generic protocols
|
||||
# revealed: tuple[Literal[ListSubclass], Literal[list], Literal[MutableSequence], Literal[Sequence], Literal[Reversible], Literal[Collection], Literal[Iterable], Literal[Container], @Todo(`Protocol[]` subscript), @Todo(`Generic[]` subscript), Literal[object]]
|
||||
reveal_type(ListSubclass.__mro__)
|
||||
|
||||
class DictSubclass(typing.Dict): ...
|
||||
|
||||
# TODO: generic protocols
|
||||
# revealed: tuple[Literal[DictSubclass], Literal[dict], Literal[MutableMapping], Literal[Mapping], Literal[Collection], Literal[Iterable], Literal[Container], @Todo(`Protocol[]` subscript), @Todo(`Generic[]` subscript), Literal[object]]
|
||||
reveal_type(DictSubclass.__mro__)
|
||||
|
||||
class SetSubclass(typing.Set): ...
|
||||
|
||||
# TODO: generic protocols
|
||||
# revealed: tuple[Literal[SetSubclass], Literal[set], Literal[MutableSet], Literal[AbstractSet], Literal[Collection], Literal[Iterable], Literal[Container], @Todo(`Protocol[]` subscript), @Todo(`Generic[]` subscript), Literal[object]]
|
||||
reveal_type(SetSubclass.__mro__)
|
||||
|
||||
class FrozenSetSubclass(typing.FrozenSet): ...
|
||||
|
||||
# TODO: should have `Generic`, should not have `Unknown`
|
||||
# revealed: tuple[Literal[FrozenSetSubclass], Literal[frozenset], Unknown, Literal[object]]
|
||||
reveal_type(FrozenSetSubclass.__mro__)
|
||||
|
||||
####################
|
||||
### `collections`
|
||||
####################
|
||||
|
||||
class ChainMapSubclass(typing.ChainMap): ...
|
||||
|
||||
# TODO: generic protocols
|
||||
# revealed: tuple[Literal[ChainMapSubclass], Literal[ChainMap], Literal[MutableMapping], Literal[Mapping], Literal[Collection], Literal[Iterable], Literal[Container], @Todo(`Protocol[]` subscript), @Todo(`Generic[]` subscript), Literal[object]]
|
||||
reveal_type(ChainMapSubclass.__mro__)
|
||||
|
||||
class CounterSubclass(typing.Counter): ...
|
||||
|
||||
# TODO: Should be (CounterSubclass, Counter, dict, MutableMapping, Mapping, Collection, Sized, Iterable, Container, Generic, object)
|
||||
# revealed: tuple[Literal[CounterSubclass], Literal[Counter], @Todo(GenericAlias instance), @Todo(`Generic[]` subscript), Literal[object]]
|
||||
reveal_type(CounterSubclass.__mro__)
|
||||
|
||||
class DefaultDictSubclass(typing.DefaultDict): ...
|
||||
|
||||
# TODO: Should be (DefaultDictSubclass, defaultdict, dict, MutableMapping, Mapping, Collection, Sized, Iterable, Container, Generic, object)
|
||||
# revealed: tuple[Literal[DefaultDictSubclass], Literal[defaultdict], @Todo(GenericAlias instance), Literal[object]]
|
||||
reveal_type(DefaultDictSubclass.__mro__)
|
||||
|
||||
class DequeSubclass(typing.Deque): ...
|
||||
|
||||
# TODO: generic protocols
|
||||
# revealed: tuple[Literal[DequeSubclass], Literal[deque], Literal[MutableSequence], Literal[Sequence], Literal[Reversible], Literal[Collection], Literal[Iterable], Literal[Container], @Todo(`Protocol[]` subscript), @Todo(`Generic[]` subscript), Literal[object]]
|
||||
reveal_type(DequeSubclass.__mro__)
|
||||
|
||||
class OrderedDictSubclass(typing.OrderedDict): ...
|
||||
|
||||
# TODO: Should be (OrderedDictSubclass, OrderedDict, dict, MutableMapping, Mapping, Collection, Sized, Iterable, Container, Generic, object)
|
||||
# revealed: tuple[Literal[OrderedDictSubclass], Literal[OrderedDict], @Todo(GenericAlias instance), Literal[object]]
|
||||
reveal_type(OrderedDictSubclass.__mro__)
|
||||
```
|
||||
@@ -1,14 +0,0 @@
|
||||
# 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]
|
||||
```
|
||||
@@ -1,66 +0,0 @@
|
||||
# `except*`
|
||||
|
||||
`except*` is only available in Python 3.11 and later:
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.11"
|
||||
```
|
||||
|
||||
## `except*` with `BaseException`
|
||||
|
||||
```py
|
||||
try:
|
||||
help()
|
||||
except* BaseException as e:
|
||||
# TODO: should be `BaseExceptionGroup[BaseException]` --Alex
|
||||
reveal_type(e) # revealed: BaseExceptionGroup
|
||||
```
|
||||
|
||||
## `except*` with specific exception
|
||||
|
||||
```py
|
||||
try:
|
||||
help()
|
||||
except* OSError as e:
|
||||
# TODO: more precise would be `ExceptionGroup[OSError]` --Alex
|
||||
# (needs homogeneous tuples + generics)
|
||||
reveal_type(e) # revealed: BaseExceptionGroup
|
||||
```
|
||||
|
||||
## `except*` with multiple exceptions
|
||||
|
||||
```py
|
||||
try:
|
||||
help()
|
||||
except* (TypeError, AttributeError) as e:
|
||||
# TODO: more precise would be `ExceptionGroup[TypeError | AttributeError]` --Alex
|
||||
# (needs homogeneous tuples + generics)
|
||||
reveal_type(e) # revealed: BaseExceptionGroup
|
||||
```
|
||||
|
||||
## `except*` with mix of `Exception`s and `BaseException`s
|
||||
|
||||
```py
|
||||
try:
|
||||
help()
|
||||
except* (KeyboardInterrupt, AttributeError) as e:
|
||||
# TODO: more precise would be `BaseExceptionGroup[KeyboardInterrupt | AttributeError]` --Alex
|
||||
reveal_type(e) # revealed: BaseExceptionGroup
|
||||
```
|
||||
|
||||
## Invalid `except*` handlers
|
||||
|
||||
```py
|
||||
try:
|
||||
help()
|
||||
except* 3 as e: # error: [invalid-exception-caught]
|
||||
# TODO: Should be `BaseExceptionGroup[Unknown]` --Alex
|
||||
reveal_type(e) # revealed: BaseExceptionGroup
|
||||
|
||||
try:
|
||||
help()
|
||||
except* (AttributeError, 42) as e: # error: [invalid-exception-caught]
|
||||
# TODO: Should be `BaseExceptionGroup[AttributeError | Unknown]` --Alex
|
||||
reveal_type(e) # revealed: BaseExceptionGroup
|
||||
```
|
||||
@@ -1,7 +0,0 @@
|
||||
# Dictionaries
|
||||
|
||||
## Empty dictionary
|
||||
|
||||
```py
|
||||
reveal_type({}) # revealed: dict
|
||||
```
|
||||
@@ -1,7 +0,0 @@
|
||||
# Lists
|
||||
|
||||
## Empty list
|
||||
|
||||
```py
|
||||
reveal_type([]) # revealed: list
|
||||
```
|
||||
@@ -1,7 +0,0 @@
|
||||
# Sets
|
||||
|
||||
## Basic set
|
||||
|
||||
```py
|
||||
reveal_type({1, 2}) # revealed: set
|
||||
```
|
||||
@@ -1,408 +0,0 @@
|
||||
# Method Resolution Order tests
|
||||
|
||||
Tests that assert that we can infer the correct type for a class's `__mro__` attribute.
|
||||
|
||||
This attribute is rarely accessed directly at runtime. However, it's extremely important for *us* to
|
||||
know the precise possible values of a class's Method Resolution Order, or we won't be able to infer
|
||||
the correct type of attributes accessed from instances.
|
||||
|
||||
For documentation on method resolution orders, see:
|
||||
|
||||
- <https://docs.python.org/3/glossary.html#term-method-resolution-order>
|
||||
- <https://docs.python.org/3/howto/mro.html#python-2-3-mro>
|
||||
|
||||
## No bases
|
||||
|
||||
```py
|
||||
class C: ...
|
||||
|
||||
reveal_type(C.__mro__) # revealed: tuple[Literal[C], Literal[object]]
|
||||
```
|
||||
|
||||
## The special case: `object` itself
|
||||
|
||||
```py
|
||||
reveal_type(object.__mro__) # revealed: tuple[Literal[object]]
|
||||
```
|
||||
|
||||
## Explicit inheritance from `object`
|
||||
|
||||
```py
|
||||
class C(object): ...
|
||||
|
||||
reveal_type(C.__mro__) # revealed: tuple[Literal[C], Literal[object]]
|
||||
```
|
||||
|
||||
## Explicit inheritance from non-`object` single base
|
||||
|
||||
```py
|
||||
class A: ...
|
||||
class B(A): ...
|
||||
|
||||
reveal_type(B.__mro__) # revealed: tuple[Literal[B], Literal[A], Literal[object]]
|
||||
```
|
||||
|
||||
## Linearization of multiple bases
|
||||
|
||||
```py
|
||||
class A: ...
|
||||
class B: ...
|
||||
class C(A, B): ...
|
||||
|
||||
reveal_type(C.__mro__) # revealed: tuple[Literal[C], Literal[A], Literal[B], Literal[object]]
|
||||
```
|
||||
|
||||
## Complex diamond inheritance (1)
|
||||
|
||||
This is "ex_2" from <https://docs.python.org/3/howto/mro.html#the-end>
|
||||
|
||||
```py
|
||||
class O: ...
|
||||
class X(O): ...
|
||||
class Y(O): ...
|
||||
class A(X, Y): ...
|
||||
class B(Y, X): ...
|
||||
|
||||
reveal_type(A.__mro__) # revealed: tuple[Literal[A], Literal[X], Literal[Y], Literal[O], Literal[object]]
|
||||
reveal_type(B.__mro__) # revealed: tuple[Literal[B], Literal[Y], Literal[X], Literal[O], Literal[object]]
|
||||
```
|
||||
|
||||
## Complex diamond inheritance (2)
|
||||
|
||||
This is "ex_5" from <https://docs.python.org/3/howto/mro.html#the-end>
|
||||
|
||||
```py
|
||||
class O: ...
|
||||
class F(O): ...
|
||||
class E(O): ...
|
||||
class D(O): ...
|
||||
class C(D, F): ...
|
||||
class B(D, E): ...
|
||||
class A(B, C): ...
|
||||
|
||||
# revealed: tuple[Literal[C], Literal[D], Literal[F], Literal[O], Literal[object]]
|
||||
reveal_type(C.__mro__)
|
||||
# revealed: tuple[Literal[B], Literal[D], Literal[E], Literal[O], Literal[object]]
|
||||
reveal_type(B.__mro__)
|
||||
# revealed: tuple[Literal[A], Literal[B], Literal[C], Literal[D], Literal[E], Literal[F], Literal[O], Literal[object]]
|
||||
reveal_type(A.__mro__)
|
||||
```
|
||||
|
||||
## Complex diamond inheritance (3)
|
||||
|
||||
This is "ex_6" from <https://docs.python.org/3/howto/mro.html#the-end>
|
||||
|
||||
```py
|
||||
class O: ...
|
||||
class F(O): ...
|
||||
class E(O): ...
|
||||
class D(O): ...
|
||||
class C(D, F): ...
|
||||
class B(E, D): ...
|
||||
class A(B, C): ...
|
||||
|
||||
# revealed: tuple[Literal[C], Literal[D], Literal[F], Literal[O], Literal[object]]
|
||||
reveal_type(C.__mro__)
|
||||
# revealed: tuple[Literal[B], Literal[E], Literal[D], Literal[O], Literal[object]]
|
||||
reveal_type(B.__mro__)
|
||||
# revealed: tuple[Literal[A], Literal[B], Literal[E], Literal[C], Literal[D], Literal[F], Literal[O], Literal[object]]
|
||||
reveal_type(A.__mro__)
|
||||
```
|
||||
|
||||
## Complex diamond inheritance (4)
|
||||
|
||||
This is "ex_9" from <https://docs.python.org/3/howto/mro.html#the-end>
|
||||
|
||||
```py
|
||||
class O: ...
|
||||
class A(O): ...
|
||||
class B(O): ...
|
||||
class C(O): ...
|
||||
class D(O): ...
|
||||
class E(O): ...
|
||||
class K1(A, B, C): ...
|
||||
class K2(D, B, E): ...
|
||||
class K3(D, A): ...
|
||||
class Z(K1, K2, K3): ...
|
||||
|
||||
# revealed: tuple[Literal[K1], Literal[A], Literal[B], Literal[C], Literal[O], Literal[object]]
|
||||
reveal_type(K1.__mro__)
|
||||
# revealed: tuple[Literal[K2], Literal[D], Literal[B], Literal[E], Literal[O], Literal[object]]
|
||||
reveal_type(K2.__mro__)
|
||||
# revealed: tuple[Literal[K3], Literal[D], Literal[A], Literal[O], Literal[object]]
|
||||
reveal_type(K3.__mro__)
|
||||
# revealed: tuple[Literal[Z], Literal[K1], Literal[K2], Literal[K3], Literal[D], Literal[A], Literal[B], Literal[C], Literal[E], Literal[O], Literal[object]]
|
||||
reveal_type(Z.__mro__)
|
||||
```
|
||||
|
||||
## Inheritance from `Unknown`
|
||||
|
||||
```py
|
||||
from does_not_exist import DoesNotExist # error: [unresolved-import]
|
||||
|
||||
class A(DoesNotExist): ...
|
||||
class B: ...
|
||||
class C: ...
|
||||
class D(A, B, C): ...
|
||||
class E(B, C): ...
|
||||
class F(E, A): ...
|
||||
|
||||
reveal_type(A.__mro__) # revealed: tuple[Literal[A], Unknown, Literal[object]]
|
||||
reveal_type(D.__mro__) # revealed: tuple[Literal[D], Literal[A], Unknown, Literal[B], Literal[C], Literal[object]]
|
||||
reveal_type(E.__mro__) # revealed: tuple[Literal[E], Literal[B], Literal[C], Literal[object]]
|
||||
reveal_type(F.__mro__) # revealed: tuple[Literal[F], Literal[E], Literal[B], Literal[C], Literal[A], Unknown, Literal[object]]
|
||||
```
|
||||
|
||||
## `__bases__` lists that cause errors at runtime
|
||||
|
||||
If the class's `__bases__` cause an exception to be raised at runtime and therefore the class
|
||||
creation to fail, we infer the class's `__mro__` as being `[<class>, Unknown, object]`:
|
||||
|
||||
```py
|
||||
# error: [inconsistent-mro] "Cannot create a consistent method resolution order (MRO) for class `Foo` with bases list `[<class 'object'>, <class 'int'>]`"
|
||||
class Foo(object, int): ...
|
||||
|
||||
reveal_type(Foo.__mro__) # revealed: tuple[Literal[Foo], Unknown, Literal[object]]
|
||||
|
||||
class Bar(Foo): ...
|
||||
|
||||
reveal_type(Bar.__mro__) # revealed: tuple[Literal[Bar], Literal[Foo], Unknown, Literal[object]]
|
||||
|
||||
# This is the `TypeError` at the bottom of "ex_2"
|
||||
# in the examples at <https://docs.python.org/3/howto/mro.html#the-end>
|
||||
class O: ...
|
||||
class X(O): ...
|
||||
class Y(O): ...
|
||||
class A(X, Y): ...
|
||||
class B(Y, X): ...
|
||||
|
||||
reveal_type(A.__mro__) # revealed: tuple[Literal[A], Literal[X], Literal[Y], Literal[O], Literal[object]]
|
||||
reveal_type(B.__mro__) # revealed: tuple[Literal[B], Literal[Y], Literal[X], Literal[O], Literal[object]]
|
||||
|
||||
# error: [inconsistent-mro] "Cannot create a consistent method resolution order (MRO) for class `Z` with bases list `[<class 'A'>, <class 'B'>]`"
|
||||
class Z(A, B): ...
|
||||
|
||||
reveal_type(Z.__mro__) # revealed: tuple[Literal[Z], Unknown, Literal[object]]
|
||||
|
||||
class AA(Z): ...
|
||||
|
||||
reveal_type(AA.__mro__) # revealed: tuple[Literal[AA], Literal[Z], Unknown, Literal[object]]
|
||||
```
|
||||
|
||||
## `__bases__` includes a `Union`
|
||||
|
||||
We don't support union types in a class's bases; a base must resolve to a single `ClassType`. If we
|
||||
find a union type in a class's bases, we infer the class's `__mro__` as being
|
||||
`[<class>, Unknown, object]`, the same as for MROs that cause errors at runtime.
|
||||
|
||||
```py
|
||||
def returns_bool() -> bool:
|
||||
return True
|
||||
|
||||
class A: ...
|
||||
class B: ...
|
||||
|
||||
if returns_bool():
|
||||
x = A
|
||||
else:
|
||||
x = B
|
||||
|
||||
reveal_type(x) # revealed: Literal[A, B]
|
||||
|
||||
# error: 11 [invalid-base] "Invalid class base with type `Literal[A, B]` (all bases must be a class, `Any`, `Unknown` or `Todo`)"
|
||||
class Foo(x): ...
|
||||
|
||||
reveal_type(Foo.__mro__) # revealed: tuple[Literal[Foo], Unknown, Literal[object]]
|
||||
```
|
||||
|
||||
## `__bases__` includes multiple `Union`s
|
||||
|
||||
```py
|
||||
def returns_bool() -> bool:
|
||||
return True
|
||||
|
||||
class A: ...
|
||||
class B: ...
|
||||
class C: ...
|
||||
class D: ...
|
||||
|
||||
if returns_bool():
|
||||
x = A
|
||||
else:
|
||||
x = B
|
||||
|
||||
if returns_bool():
|
||||
y = C
|
||||
else:
|
||||
y = D
|
||||
|
||||
reveal_type(x) # revealed: Literal[A, B]
|
||||
reveal_type(y) # revealed: Literal[C, D]
|
||||
|
||||
# error: 11 [invalid-base] "Invalid class base with type `Literal[A, B]` (all bases must be a class, `Any`, `Unknown` or `Todo`)"
|
||||
# error: 14 [invalid-base] "Invalid class base with type `Literal[C, D]` (all bases must be a class, `Any`, `Unknown` or `Todo`)"
|
||||
class Foo(x, y): ...
|
||||
|
||||
reveal_type(Foo.__mro__) # revealed: tuple[Literal[Foo], Unknown, Literal[object]]
|
||||
```
|
||||
|
||||
## `__bases__` lists that cause errors... now with `Union`s
|
||||
|
||||
```py
|
||||
def returns_bool() -> bool:
|
||||
return True
|
||||
|
||||
class O: ...
|
||||
class X(O): ...
|
||||
class Y(O): ...
|
||||
|
||||
if returns_bool():
|
||||
foo = Y
|
||||
else:
|
||||
foo = object
|
||||
|
||||
# error: 21 [invalid-base] "Invalid class base with type `Literal[Y, object]` (all bases must be a class, `Any`, `Unknown` or `Todo`)"
|
||||
class PossibleError(foo, X): ...
|
||||
|
||||
reveal_type(PossibleError.__mro__) # revealed: tuple[Literal[PossibleError], Unknown, Literal[object]]
|
||||
|
||||
class A(X, Y): ...
|
||||
|
||||
reveal_type(A.__mro__) # revealed: tuple[Literal[A], Literal[X], Literal[Y], Literal[O], Literal[object]]
|
||||
|
||||
if returns_bool():
|
||||
class B(X, Y): ...
|
||||
|
||||
else:
|
||||
class B(Y, X): ...
|
||||
|
||||
# revealed: tuple[Literal[B], Literal[X], Literal[Y], Literal[O], Literal[object]] | tuple[Literal[B], Literal[Y], Literal[X], Literal[O], Literal[object]]
|
||||
reveal_type(B.__mro__)
|
||||
|
||||
# error: 12 [invalid-base] "Invalid class base with type `Literal[B, B]` (all bases must be a class, `Any`, `Unknown` or `Todo`)"
|
||||
class Z(A, B): ...
|
||||
|
||||
reveal_type(Z.__mro__) # revealed: tuple[Literal[Z], Unknown, Literal[object]]
|
||||
```
|
||||
|
||||
## `__bases__` lists with duplicate bases
|
||||
|
||||
```py
|
||||
class Foo(str, str): ... # error: 16 [duplicate-base] "Duplicate base class `str`"
|
||||
|
||||
reveal_type(Foo.__mro__) # revealed: tuple[Literal[Foo], Unknown, Literal[object]]
|
||||
|
||||
class Spam: ...
|
||||
class Eggs: ...
|
||||
class Ham(
|
||||
Spam,
|
||||
Eggs,
|
||||
Spam, # error: [duplicate-base] "Duplicate base class `Spam`"
|
||||
Eggs, # error: [duplicate-base] "Duplicate base class `Eggs`"
|
||||
): ...
|
||||
|
||||
reveal_type(Ham.__mro__) # revealed: tuple[Literal[Ham], Unknown, Literal[object]]
|
||||
|
||||
class Mushrooms: ...
|
||||
class Omelette(Spam, Eggs, Mushrooms, Mushrooms): ... # error: [duplicate-base]
|
||||
|
||||
reveal_type(Omelette.__mro__) # revealed: tuple[Literal[Omelette], Unknown, Literal[object]]
|
||||
```
|
||||
|
||||
## `__bases__` lists with duplicate `Unknown` bases
|
||||
|
||||
```py
|
||||
# error: [unresolved-import]
|
||||
# error: [unresolved-import]
|
||||
from does_not_exist import unknown_object_1, unknown_object_2
|
||||
|
||||
reveal_type(unknown_object_1) # revealed: Unknown
|
||||
reveal_type(unknown_object_2) # revealed: Unknown
|
||||
|
||||
# We *should* emit an error here to warn the user that we have no idea
|
||||
# what the MRO of this class should really be.
|
||||
# However, we don't complain about "duplicate base classes" here,
|
||||
# even though two classes are both inferred as being `Unknown`.
|
||||
#
|
||||
# (TODO: should we revisit this? Does it violate the gradual guarantee?
|
||||
# Should we just silently infer `[Foo, Unknown, object]` as the MRO here
|
||||
# without emitting any error at all? Not sure...)
|
||||
#
|
||||
# error: [inconsistent-mro] "Cannot create a consistent method resolution order (MRO) for class `Foo` with bases list `[Unknown, Unknown]`"
|
||||
class Foo(unknown_object_1, unknown_object_2): ...
|
||||
|
||||
reveal_type(Foo.__mro__) # revealed: tuple[Literal[Foo], Unknown, Literal[object]]
|
||||
```
|
||||
|
||||
## Unrelated objects inferred as `Any`/`Unknown` do not have special `__mro__` attributes
|
||||
|
||||
```py
|
||||
from does_not_exist import unknown_object # error: [unresolved-import]
|
||||
|
||||
reveal_type(unknown_object) # revealed: Unknown
|
||||
reveal_type(unknown_object.__mro__) # revealed: Unknown
|
||||
```
|
||||
|
||||
## Classes that inherit from themselves
|
||||
|
||||
These are invalid, but we need to be able to handle them gracefully without panicking.
|
||||
|
||||
```pyi
|
||||
class Foo(Foo): ... # error: [cyclic-class-definition]
|
||||
|
||||
reveal_type(Foo) # revealed: Literal[Foo]
|
||||
reveal_type(Foo.__mro__) # revealed: tuple[Literal[Foo], Unknown, Literal[object]]
|
||||
|
||||
class Bar: ...
|
||||
class Baz: ...
|
||||
class Boz(Bar, Baz, Boz): ... # error: [cyclic-class-definition]
|
||||
|
||||
reveal_type(Boz) # revealed: Literal[Boz]
|
||||
reveal_type(Boz.__mro__) # revealed: tuple[Literal[Boz], Unknown, Literal[object]]
|
||||
```
|
||||
|
||||
## Classes with indirect cycles in their MROs
|
||||
|
||||
These are similarly unlikely, but we still shouldn't crash:
|
||||
|
||||
```pyi
|
||||
class Foo(Bar): ... # error: [cyclic-class-definition]
|
||||
class Bar(Baz): ... # error: [cyclic-class-definition]
|
||||
class Baz(Foo): ... # error: [cyclic-class-definition]
|
||||
|
||||
reveal_type(Foo.__mro__) # revealed: tuple[Literal[Foo], Unknown, Literal[object]]
|
||||
reveal_type(Bar.__mro__) # revealed: tuple[Literal[Bar], Unknown, Literal[object]]
|
||||
reveal_type(Baz.__mro__) # revealed: tuple[Literal[Baz], Unknown, Literal[object]]
|
||||
```
|
||||
|
||||
## Classes with cycles in their MROs, and multiple inheritance
|
||||
|
||||
```pyi
|
||||
class Spam: ...
|
||||
class Foo(Bar): ... # error: [cyclic-class-definition]
|
||||
class Bar(Baz): ... # error: [cyclic-class-definition]
|
||||
class Baz(Foo, Spam): ... # error: [cyclic-class-definition]
|
||||
|
||||
reveal_type(Foo.__mro__) # revealed: tuple[Literal[Foo], Unknown, Literal[object]]
|
||||
reveal_type(Bar.__mro__) # revealed: tuple[Literal[Bar], Unknown, Literal[object]]
|
||||
reveal_type(Baz.__mro__) # revealed: tuple[Literal[Baz], Unknown, Literal[object]]
|
||||
```
|
||||
|
||||
## Classes with cycles in their MRO, and a sub-graph
|
||||
|
||||
```pyi
|
||||
class FooCycle(BarCycle): ... # error: [cyclic-class-definition]
|
||||
class Foo: ...
|
||||
class BarCycle(FooCycle): ... # error: [cyclic-class-definition]
|
||||
class Bar(Foo): ...
|
||||
|
||||
# Avoid emitting the errors for these. The classes have cyclic superclasses,
|
||||
# but are not themselves cyclic...
|
||||
class Baz(Bar, BarCycle): ...
|
||||
class Spam(Baz): ...
|
||||
|
||||
reveal_type(FooCycle.__mro__) # revealed: tuple[Literal[FooCycle], Unknown, Literal[object]]
|
||||
reveal_type(BarCycle.__mro__) # revealed: tuple[Literal[BarCycle], Unknown, Literal[object]]
|
||||
reveal_type(Baz.__mro__) # revealed: tuple[Literal[Baz], Unknown, Literal[object]]
|
||||
reveal_type(Spam.__mro__) # revealed: tuple[Literal[Spam], Unknown, Literal[object]]
|
||||
```
|
||||
@@ -1,44 +0,0 @@
|
||||
# Narrowing for nested conditionals
|
||||
|
||||
## Multiple negative contributions
|
||||
|
||||
```py
|
||||
def _(x: int):
|
||||
if x != 1:
|
||||
if x != 2:
|
||||
if x != 3:
|
||||
reveal_type(x) # revealed: int & ~Literal[1] & ~Literal[2] & ~Literal[3]
|
||||
```
|
||||
|
||||
## Multiple negative contributions with simplification
|
||||
|
||||
```py
|
||||
def _(flag1: bool, flag2: bool):
|
||||
x = 1 if flag1 else 2 if flag2 else 3
|
||||
|
||||
if x != 1:
|
||||
reveal_type(x) # revealed: Literal[2, 3]
|
||||
if x != 2:
|
||||
reveal_type(x) # revealed: Literal[3]
|
||||
```
|
||||
|
||||
## elif-else blocks
|
||||
|
||||
```py
|
||||
def _(flag1: bool, flag2: bool):
|
||||
x = 1 if flag1 else 2 if flag2 else 3
|
||||
|
||||
if x != 1:
|
||||
reveal_type(x) # revealed: Literal[2, 3]
|
||||
if x == 2:
|
||||
reveal_type(x) # revealed: Literal[2]
|
||||
elif x == 3:
|
||||
reveal_type(x) # revealed: Literal[3]
|
||||
else:
|
||||
reveal_type(x) # revealed: Never
|
||||
|
||||
elif x != 2:
|
||||
reveal_type(x) # revealed: Literal[1]
|
||||
else:
|
||||
reveal_type(x) # revealed: Never
|
||||
```
|
||||
@@ -1,177 +0,0 @@
|
||||
# `global` references
|
||||
|
||||
## Implicit global in function
|
||||
|
||||
A name reference to a never-defined symbol in a function is implicitly a global lookup.
|
||||
|
||||
```py
|
||||
x = 1
|
||||
|
||||
def f():
|
||||
reveal_type(x) # revealed: Unknown | Literal[1]
|
||||
```
|
||||
|
||||
## Explicit global in function
|
||||
|
||||
```py
|
||||
x = 1
|
||||
|
||||
def f():
|
||||
global x
|
||||
reveal_type(x) # revealed: Unknown | Literal[1]
|
||||
```
|
||||
|
||||
## Unassignable type in function
|
||||
|
||||
```py
|
||||
x: int = 1
|
||||
|
||||
def f():
|
||||
y: int = 1
|
||||
# error: [invalid-assignment] "Object of type `Literal[""]` is not assignable to `int`"
|
||||
y = ""
|
||||
|
||||
global x
|
||||
# TODO: error: [invalid-assignment] "Object of type `Literal[""]` is not assignable to `int`"
|
||||
x = ""
|
||||
```
|
||||
|
||||
## Nested intervening scope
|
||||
|
||||
A `global` statement causes lookup to skip any bindings in intervening scopes:
|
||||
|
||||
```py
|
||||
x: int = 1
|
||||
|
||||
def outer():
|
||||
x: str = ""
|
||||
|
||||
def inner():
|
||||
global x
|
||||
# TODO: revealed: int
|
||||
reveal_type(x) # revealed: str
|
||||
```
|
||||
|
||||
## Narrowing
|
||||
|
||||
An assignment following a `global` statement should narrow the type in the local scope after the
|
||||
assignment.
|
||||
|
||||
```py
|
||||
x: int | None
|
||||
|
||||
def f():
|
||||
global x
|
||||
x = 1
|
||||
reveal_type(x) # revealed: Literal[1]
|
||||
```
|
||||
|
||||
## `nonlocal` and `global`
|
||||
|
||||
A binding cannot be both `nonlocal` and `global`. This should emit a semantic syntax error. CPython
|
||||
marks the `nonlocal` line, while `mypy`, `pyright`, and `ruff` (`PLE0115`) mark the `global` line.
|
||||
|
||||
```py
|
||||
x = 1
|
||||
|
||||
def f():
|
||||
x = 1
|
||||
def g() -> None:
|
||||
nonlocal x
|
||||
global x # TODO: error: [invalid-syntax] "name 'x' is nonlocal and global"
|
||||
x = None
|
||||
```
|
||||
|
||||
## Global declaration after `global` statement
|
||||
|
||||
```py
|
||||
def f():
|
||||
global x
|
||||
# TODO this should also not be an error
|
||||
y = x # error: [unresolved-reference] "Name `x` used when not defined"
|
||||
x = 1 # No error.
|
||||
|
||||
x = 2
|
||||
```
|
||||
|
||||
## Semantic syntax errors
|
||||
|
||||
Using a name prior to its `global` declaration in the same scope is a syntax error.
|
||||
|
||||
```py
|
||||
x = 1
|
||||
|
||||
def f():
|
||||
print(x) # TODO: error: [invalid-syntax] name `x` is used prior to global declaration
|
||||
global x
|
||||
print(x)
|
||||
|
||||
def f():
|
||||
global x
|
||||
print(x) # TODO: error: [invalid-syntax] name `x` is used prior to global declaration
|
||||
global x
|
||||
print(x)
|
||||
|
||||
def f():
|
||||
print(x) # TODO: error: [invalid-syntax] name `x` is used prior to global declaration
|
||||
global x, y
|
||||
print(x)
|
||||
|
||||
def f():
|
||||
global x, y
|
||||
print(x) # TODO: error: [invalid-syntax] name `x` is used prior to global declaration
|
||||
global x, y
|
||||
print(x)
|
||||
|
||||
def f():
|
||||
x = 1 # TODO: error: [invalid-syntax] name `x` is used prior to global declaration
|
||||
global x
|
||||
x = 1
|
||||
|
||||
def f():
|
||||
global x
|
||||
x = 1 # TODO: error: [invalid-syntax] name `x` is used prior to global declaration
|
||||
global x
|
||||
x = 1
|
||||
|
||||
def f():
|
||||
del x # TODO: error: [invalid-syntax] name `x` is used prior to global declaration
|
||||
global x, y
|
||||
del x
|
||||
|
||||
def f():
|
||||
global x, y
|
||||
del x # TODO: error: [invalid-syntax] name `x` is used prior to global declaration
|
||||
global x, y
|
||||
del x
|
||||
|
||||
def f():
|
||||
del x # TODO: error: [invalid-syntax] name `x` is used prior to global declaration
|
||||
global x
|
||||
del x
|
||||
|
||||
def f():
|
||||
global x
|
||||
del x # TODO: error: [invalid-syntax] name `x` is used prior to global declaration
|
||||
global x
|
||||
del x
|
||||
|
||||
def f():
|
||||
del x # TODO: error: [invalid-syntax] name `x` is used prior to global declaration
|
||||
global x, y
|
||||
del x
|
||||
|
||||
def f():
|
||||
global x, y
|
||||
del x # TODO: error: [invalid-syntax] name `x` is used prior to global declaration
|
||||
global x, y
|
||||
del x
|
||||
|
||||
def f():
|
||||
print(f"{x=}") # TODO: error: [invalid-syntax] name `x` is used prior to global declaration
|
||||
global x
|
||||
|
||||
# still an error in module scope
|
||||
x = None # TODO: error: [invalid-syntax] name `x` is used prior to global declaration
|
||||
global x
|
||||
```
|
||||
@@ -1,86 +0,0 @@
|
||||
---
|
||||
source: crates/red_knot_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: functions.md - Generic functions - Inferring a bound typevar
|
||||
mdtest path: crates/red_knot_python_semantic/resources/mdtest/generics/functions.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | from typing_extensions import reveal_type
|
||||
2 |
|
||||
3 | def f[T: int](x: T) -> T:
|
||||
4 | return x
|
||||
5 |
|
||||
6 | reveal_type(f(1)) # revealed: Literal[1]
|
||||
7 | reveal_type(f(True)) # revealed: Literal[True]
|
||||
8 | # error: [invalid-argument-type] "Argument to this function is incorrect: Argument type `Literal["string"]` does not satisfy upper bound of type variable `T`"
|
||||
9 | reveal_type(f("string")) # revealed: Unknown
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
info: revealed-type: Revealed type
|
||||
--> src/mdtest_snippet.py:6:1
|
||||
|
|
||||
4 | return x
|
||||
5 |
|
||||
6 | reveal_type(f(1)) # revealed: Literal[1]
|
||||
| ^^^^^^^^^^^^^^^^^ `Literal[1]`
|
||||
7 | reveal_type(f(True)) # revealed: Literal[True]
|
||||
8 | # error: [invalid-argument-type] "Argument to this function is incorrect: Argument type `Literal["string"]` does not satisfy upper bo...
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
info: revealed-type: Revealed type
|
||||
--> src/mdtest_snippet.py:7:1
|
||||
|
|
||||
6 | reveal_type(f(1)) # revealed: Literal[1]
|
||||
7 | reveal_type(f(True)) # revealed: Literal[True]
|
||||
| ^^^^^^^^^^^^^^^^^^^^ `Literal[True]`
|
||||
8 | # error: [invalid-argument-type] "Argument to this function is incorrect: Argument type `Literal["string"]` does not satisfy upper b...
|
||||
9 | reveal_type(f("string")) # revealed: Unknown
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error: lint:invalid-argument-type: Argument to this function is incorrect
|
||||
--> src/mdtest_snippet.py:9:15
|
||||
|
|
||||
7 | reveal_type(f(True)) # revealed: Literal[True]
|
||||
8 | # error: [invalid-argument-type] "Argument to this function is incorrect: Argument type `Literal["string"]` does not satisfy upper b...
|
||||
9 | reveal_type(f("string")) # revealed: Unknown
|
||||
| ^^^^^^^^ Argument type `Literal["string"]` does not satisfy upper bound of type variable `T`
|
||||
|
|
||||
info: Type variable defined here
|
||||
--> src/mdtest_snippet.py:3:7
|
||||
|
|
||||
1 | from typing_extensions import reveal_type
|
||||
2 |
|
||||
3 | def f[T: int](x: T) -> T:
|
||||
| ^^^^^^
|
||||
4 | return x
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
info: revealed-type: Revealed type
|
||||
--> src/mdtest_snippet.py:9:1
|
||||
|
|
||||
7 | reveal_type(f(True)) # revealed: Literal[True]
|
||||
8 | # error: [invalid-argument-type] "Argument to this function is incorrect: Argument type `Literal["string"]` does not satisfy upper b...
|
||||
9 | reveal_type(f("string")) # revealed: Unknown
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^ `Unknown`
|
||||
|
|
||||
|
||||
```
|
||||
@@ -1,28 +0,0 @@
|
||||
---
|
||||
source: crates/red_knot_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: no_matching_overload.md - No matching overload diagnostics - Calls to overloaded functions
|
||||
mdtest path: crates/red_knot_python_semantic/resources/mdtest/diagnostics/no_matching_overload.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | type("Foo", ()) # error: [no-matching-overload]
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error: lint:no-matching-overload: No overload of class `type` matches arguments
|
||||
--> src/mdtest_snippet.py:1:1
|
||||
|
|
||||
1 | type("Foo", ()) # error: [no-matching-overload]
|
||||
| ^^^^^^^^^^^^^^^
|
||||
|
|
||||
|
||||
```
|
||||
@@ -1,191 +0,0 @@
|
||||
# Suppressing errors with `knot: ignore`
|
||||
|
||||
Type check errors can be suppressed by a `knot: ignore` comment on the same line as the violation.
|
||||
|
||||
## Simple `knot: ignore`
|
||||
|
||||
```py
|
||||
a = 4 + test # knot: ignore
|
||||
```
|
||||
|
||||
## Suppressing a specific code
|
||||
|
||||
```py
|
||||
a = 4 + test # knot: ignore[unresolved-reference]
|
||||
```
|
||||
|
||||
## Unused suppression
|
||||
|
||||
```py
|
||||
test = 10
|
||||
# error: [unused-ignore-comment] "Unused `knot: ignore` directive: 'possibly-unresolved-reference'"
|
||||
a = test + 3 # knot: ignore[possibly-unresolved-reference]
|
||||
```
|
||||
|
||||
## Unused suppression if the error codes don't match
|
||||
|
||||
```py
|
||||
# error: [unresolved-reference]
|
||||
# error: [unused-ignore-comment] "Unused `knot: ignore` directive: 'possibly-unresolved-reference'"
|
||||
a = test + 3 # knot: ignore[possibly-unresolved-reference]
|
||||
```
|
||||
|
||||
## Suppressed unused comment
|
||||
|
||||
```py
|
||||
# error: [unused-ignore-comment]
|
||||
a = 10 / 2 # knot: ignore[division-by-zero]
|
||||
a = 10 / 2 # knot: ignore[division-by-zero, unused-ignore-comment]
|
||||
a = 10 / 2 # knot: ignore[unused-ignore-comment, division-by-zero]
|
||||
a = 10 / 2 # knot: ignore[unused-ignore-comment] # type: ignore
|
||||
a = 10 / 2 # type: ignore # knot: ignore[unused-ignore-comment]
|
||||
```
|
||||
|
||||
## Unused ignore comment
|
||||
|
||||
```py
|
||||
# error: [unused-ignore-comment] "Unused `knot: ignore` directive: 'unused-ignore-comment'"
|
||||
a = 10 / 0 # knot: ignore[division-by-zero, unused-ignore-comment]
|
||||
```
|
||||
|
||||
## Multiple unused comments
|
||||
|
||||
Today, Red Knot emits a diagnostic for every unused code. We might want to group the codes by
|
||||
comment at some point in the future.
|
||||
|
||||
```py
|
||||
# error: [unused-ignore-comment] "Unused `knot: ignore` directive: 'division-by-zero'"
|
||||
# error: [unused-ignore-comment] "Unused `knot: ignore` directive: 'unresolved-reference'"
|
||||
a = 10 / 2 # knot: ignore[division-by-zero, unresolved-reference]
|
||||
|
||||
# error: [unused-ignore-comment] "Unused `knot: ignore` directive: 'invalid-assignment'"
|
||||
# error: [unused-ignore-comment] "Unused `knot: ignore` directive: 'unresolved-reference'"
|
||||
a = 10 / 0 # knot: ignore[invalid-assignment, division-by-zero, unresolved-reference]
|
||||
```
|
||||
|
||||
## Multiple suppressions
|
||||
|
||||
```py
|
||||
# fmt: off
|
||||
def test(a: f"f-string type annotation", b: b"byte-string-type-annotation"): ... # knot: ignore[fstring-type-annotation, byte-string-type-annotation]
|
||||
```
|
||||
|
||||
## Can't suppress syntax errors
|
||||
|
||||
<!-- blacken-docs:off -->
|
||||
|
||||
```py
|
||||
# error: [invalid-syntax]
|
||||
# error: [unused-ignore-comment]
|
||||
def test($): # knot: ignore
|
||||
pass
|
||||
```
|
||||
|
||||
<!-- blacken-docs:on -->
|
||||
|
||||
## Can't suppress `revealed-type` diagnostics
|
||||
|
||||
```py
|
||||
a = 10
|
||||
# revealed: Literal[10]
|
||||
# error: [unknown-rule] "Unknown rule `revealed-type`"
|
||||
reveal_type(a) # knot: ignore[revealed-type]
|
||||
```
|
||||
|
||||
## Extra whitespace in type ignore comments is allowed
|
||||
|
||||
```py
|
||||
a = 10 / 0 # knot : ignore
|
||||
a = 10 / 0 # knot: ignore [ division-by-zero ]
|
||||
```
|
||||
|
||||
## Whitespace is optional
|
||||
|
||||
```py
|
||||
# fmt: off
|
||||
a = 10 / 0 #knot:ignore[division-by-zero]
|
||||
```
|
||||
|
||||
## Trailing codes comma
|
||||
|
||||
Trailing commas in the codes section are allowed:
|
||||
|
||||
```py
|
||||
a = 10 / 0 # knot: ignore[division-by-zero,]
|
||||
```
|
||||
|
||||
## Invalid characters in codes
|
||||
|
||||
```py
|
||||
# error: [division-by-zero]
|
||||
# error: [invalid-ignore-comment] "Invalid `knot: ignore` comment: expected a alphanumeric character or `-` or `_` as code"
|
||||
a = 10 / 0 # knot: ignore[*-*]
|
||||
```
|
||||
|
||||
## Trailing whitespace
|
||||
|
||||
<!-- blacken-docs:off -->
|
||||
|
||||
```py
|
||||
a = 10 / 0 # knot: ignore[division-by-zero]
|
||||
# ^^^^^^ trailing whitespace
|
||||
```
|
||||
|
||||
<!-- blacken-docs:on -->
|
||||
|
||||
## Missing comma
|
||||
|
||||
A missing comma results in an invalid suppression comment. We may want to recover from this in the
|
||||
future.
|
||||
|
||||
```py
|
||||
# error: [unresolved-reference]
|
||||
# error: [invalid-ignore-comment] "Invalid `knot: ignore` comment: expected a comma separating the rule codes"
|
||||
a = x / 0 # knot: ignore[division-by-zero unresolved-reference]
|
||||
```
|
||||
|
||||
## Missing closing bracket
|
||||
|
||||
```py
|
||||
# error: [unresolved-reference] "Name `x` used when not defined"
|
||||
# error: [invalid-ignore-comment] "Invalid `knot: ignore` comment: expected a comma separating the rule codes"
|
||||
a = x / 2 # knot: ignore[unresolved-reference
|
||||
```
|
||||
|
||||
## Empty codes
|
||||
|
||||
An empty codes array suppresses no-diagnostics and is always useless
|
||||
|
||||
```py
|
||||
# error: [division-by-zero]
|
||||
# error: [unused-ignore-comment] "Unused `knot: ignore` without a code"
|
||||
a = 4 / 0 # knot: ignore[]
|
||||
```
|
||||
|
||||
## File-level suppression comments
|
||||
|
||||
File level suppression comments are currently intentionally unsupported because we've yet to decide
|
||||
if they should use a different syntax that also supports enabling rules or changing the rule's
|
||||
severity: `knot: possibly-undefined-reference=error`
|
||||
|
||||
```py
|
||||
# error: [unused-ignore-comment]
|
||||
# knot: ignore[division-by-zero]
|
||||
|
||||
a = 4 / 0 # error: [division-by-zero]
|
||||
```
|
||||
|
||||
## Unknown rule
|
||||
|
||||
```py
|
||||
# error: [unknown-rule] "Unknown rule `is-equal-14`"
|
||||
a = 10 + 4 # knot: ignore[is-equal-14]
|
||||
```
|
||||
|
||||
## Code with `lint:` prefix
|
||||
|
||||
```py
|
||||
# error:[unknown-rule] "Unknown rule `lint:division-by-zero`. Did you mean `division-by-zero`?"
|
||||
# error: [division-by-zero]
|
||||
a = 10 / 0 # knot: ignore[lint:division-by-zero]
|
||||
```
|
||||
@@ -1,30 +0,0 @@
|
||||
Expression # cycle panic (signature_)
|
||||
Tanjun # cycle panic (signature_)
|
||||
altair # cycle panics (try_metaclass_)
|
||||
antidote # hangs / slow
|
||||
artigraph # cycle panics (value_type_)
|
||||
colour # cycle panics (try_metaclass_)
|
||||
core # cycle panics (value_type_)
|
||||
cpython # access to field whilst being initialized, too many cycle iterations
|
||||
discord.py # some kind of hang, only when multi-threaded?
|
||||
freqtrade # cycle panics (try_metaclass_)
|
||||
hydpy # cycle panics (try_metaclass_)
|
||||
ibis # cycle panics (try_metaclass_)
|
||||
manticore # stack overflow
|
||||
materialize # stack overflow
|
||||
mypy # cycle panic (signature_)
|
||||
pandas # slow
|
||||
pandas-stubs # cycle panics (try_metaclass_)
|
||||
pandera # cycle panics (try_metaclass_)
|
||||
prefect # slow
|
||||
pytest # cycle panics (signature_)
|
||||
pywin32 # bad use-def map (binding with definitely-visible unbound)
|
||||
schemathesis # cycle panics (signature_)
|
||||
scikit-learn # success, but mypy-primer hangs processing the output
|
||||
scipy # missing expression type ("expression should belong to this TypeInference region")
|
||||
spack # success, but mypy-primer hangs processing the output
|
||||
spark # cycle panics (try_metaclass_)
|
||||
steam.py # cycle panics (try_metaclass_), often hangs when multi-threaded
|
||||
streamlit # cycle panic (signature_)
|
||||
sympy # stack overflow
|
||||
xarray # cycle panics (try_metaclass_)
|
||||
@@ -1,262 +0,0 @@
|
||||
//! Instance types: both nominal and structural.
|
||||
|
||||
use ruff_python_ast::name::Name;
|
||||
|
||||
use super::{ClassType, KnownClass, SubclassOfType, Type};
|
||||
use crate::{Db, FxOrderSet};
|
||||
|
||||
impl<'db> Type<'db> {
|
||||
pub(crate) fn instance(db: &'db dyn Db, class: ClassType<'db>) -> Self {
|
||||
if class.class_literal(db).0.is_protocol(db) {
|
||||
Self::ProtocolInstance(ProtocolInstanceType(Protocol::FromClass(class)))
|
||||
} else {
|
||||
Self::NominalInstance(NominalInstanceType { class })
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) const fn into_nominal_instance(self) -> Option<NominalInstanceType<'db>> {
|
||||
match self {
|
||||
Type::NominalInstance(instance_type) => Some(instance_type),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Return `true` if `self` conforms to the interface described by `protocol`.
|
||||
///
|
||||
/// TODO: we may need to split this into two methods in the future, once we start
|
||||
/// differentiating between fully-static and non-fully-static protocols.
|
||||
pub(super) fn satisfies_protocol(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
protocol: ProtocolInstanceType<'db>,
|
||||
) -> bool {
|
||||
// TODO: this should consider the types of the protocol members
|
||||
// as well as whether each member *exists* on `self`.
|
||||
protocol
|
||||
.0
|
||||
.protocol_members(db)
|
||||
.iter()
|
||||
.all(|member| !self.member(db, member).symbol.is_unbound())
|
||||
}
|
||||
}
|
||||
|
||||
/// A type representing the set of runtime objects which are instances of a certain nominal class.
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, salsa::Update)]
|
||||
pub struct NominalInstanceType<'db> {
|
||||
// Keep this field private, so that the only way of constructing `NominalInstanceType` instances
|
||||
// is through the `Type::instance` constructor function.
|
||||
class: ClassType<'db>,
|
||||
}
|
||||
|
||||
impl<'db> NominalInstanceType<'db> {
|
||||
pub(super) fn class(self) -> ClassType<'db> {
|
||||
self.class
|
||||
}
|
||||
|
||||
pub(super) fn is_subtype_of(self, db: &'db dyn Db, other: Self) -> bool {
|
||||
// N.B. The subclass relation is fully static
|
||||
self.class.is_subclass_of(db, other.class)
|
||||
}
|
||||
|
||||
pub(super) fn is_equivalent_to(self, db: &'db dyn Db, other: Self) -> bool {
|
||||
self.class.is_equivalent_to(db, other.class)
|
||||
}
|
||||
|
||||
pub(super) fn is_assignable_to(self, db: &'db dyn Db, other: Self) -> bool {
|
||||
self.class.is_assignable_to(db, other.class)
|
||||
}
|
||||
|
||||
pub(super) fn is_disjoint_from(self, db: &'db dyn Db, other: Self) -> bool {
|
||||
if self.class.is_final(db) && !self.class.is_subclass_of(db, other.class) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if other.class.is_final(db) && !other.class.is_subclass_of(db, self.class) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check to see whether the metaclasses of `self` and `other` are disjoint.
|
||||
// Avoid this check if the metaclass of either `self` or `other` is `type`,
|
||||
// however, since we end up with infinite recursion in that case due to the fact
|
||||
// that `type` is its own metaclass (and we know that `type` cannot be disjoint
|
||||
// from any metaclass, anyway).
|
||||
let type_type = KnownClass::Type.to_instance(db);
|
||||
let self_metaclass = self.class.metaclass_instance_type(db);
|
||||
if self_metaclass == type_type {
|
||||
return false;
|
||||
}
|
||||
let other_metaclass = other.class.metaclass_instance_type(db);
|
||||
if other_metaclass == type_type {
|
||||
return false;
|
||||
}
|
||||
self_metaclass.is_disjoint_from(db, other_metaclass)
|
||||
}
|
||||
|
||||
pub(super) fn is_gradual_equivalent_to(self, db: &'db dyn Db, other: Self) -> bool {
|
||||
self.class.is_gradual_equivalent_to(db, other.class)
|
||||
}
|
||||
|
||||
pub(super) fn is_singleton(self, db: &'db dyn Db) -> bool {
|
||||
self.class.known(db).is_some_and(KnownClass::is_singleton)
|
||||
}
|
||||
|
||||
pub(super) fn is_single_valued(self, db: &'db dyn Db) -> bool {
|
||||
self.class
|
||||
.known(db)
|
||||
.is_some_and(KnownClass::is_single_valued)
|
||||
}
|
||||
|
||||
pub(super) fn to_meta_type(self, db: &'db dyn Db) -> Type<'db> {
|
||||
SubclassOfType::from(db, self.class)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'db> From<NominalInstanceType<'db>> for Type<'db> {
|
||||
fn from(value: NominalInstanceType<'db>) -> Self {
|
||||
Self::NominalInstance(value)
|
||||
}
|
||||
}
|
||||
|
||||
/// A `ProtocolInstanceType` represents the set of all possible runtime objects
|
||||
/// that conform to the interface described by a certain protocol.
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord, salsa::Update)]
|
||||
pub struct ProtocolInstanceType<'db>(
|
||||
// Keep the inner field here private,
|
||||
// so that the only way of constructing `ProtocolInstanceType` instances
|
||||
// is through the `Type::instance` constructor function.
|
||||
Protocol<'db>,
|
||||
);
|
||||
|
||||
impl<'db> ProtocolInstanceType<'db> {
|
||||
pub(super) fn inner(self) -> Protocol<'db> {
|
||||
self.0
|
||||
}
|
||||
|
||||
/// Return the meta-type of this protocol-instance type.
|
||||
pub(super) fn to_meta_type(self, db: &'db dyn Db) -> Type<'db> {
|
||||
match self.0 {
|
||||
Protocol::FromClass(class) => SubclassOfType::from(db, class),
|
||||
|
||||
// TODO: we can and should do better here.
|
||||
//
|
||||
// This is supported by mypy, and should be supported by us as well.
|
||||
// We'll need to come up with a better solution for the meta-type of
|
||||
// synthesized protocols to solve this:
|
||||
//
|
||||
// ```py
|
||||
// from typing import Callable
|
||||
//
|
||||
// def foo(x: Callable[[], int]) -> None:
|
||||
// reveal_type(type(x)) # mypy: "type[def (builtins.int) -> builtins.str]"
|
||||
// reveal_type(type(x).__call__) # mypy: "def (*args: Any, **kwds: Any) -> Any"
|
||||
// ```
|
||||
Protocol::Synthesized(_) => KnownClass::Type.to_instance(db),
|
||||
}
|
||||
}
|
||||
|
||||
/// Return a "normalized" version of this `Protocol` type.
|
||||
///
|
||||
/// See [`Type::normalized`] for more details.
|
||||
pub(super) fn normalized(self, db: &'db dyn Db) -> Type<'db> {
|
||||
let object = KnownClass::Object.to_instance(db);
|
||||
if object.satisfies_protocol(db, self) {
|
||||
return object;
|
||||
}
|
||||
match self.0 {
|
||||
Protocol::FromClass(_) => Type::ProtocolInstance(Self(Protocol::Synthesized(
|
||||
SynthesizedProtocolType::new(db, self.0.protocol_members(db)),
|
||||
))),
|
||||
Protocol::Synthesized(_) => Type::ProtocolInstance(self),
|
||||
}
|
||||
}
|
||||
|
||||
/// TODO: this should return `true` if any of the members of this protocol type contain any `Todo` types.
|
||||
#[expect(clippy::unused_self)]
|
||||
pub(super) fn contains_todo(self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
/// Return `true` if this protocol type is fully static.
|
||||
///
|
||||
/// TODO: should not be considered fully static if any members do not have fully static types
|
||||
#[expect(clippy::unused_self)]
|
||||
pub(super) fn is_fully_static(self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
/// Return `true` if this protocol type is a subtype of the protocol `other`.
|
||||
///
|
||||
/// TODO: consider the types of the members as well as their existence
|
||||
pub(super) fn is_subtype_of(self, db: &'db dyn Db, other: Self) -> bool {
|
||||
self.0
|
||||
.protocol_members(db)
|
||||
.is_superset(other.0.protocol_members(db))
|
||||
}
|
||||
|
||||
/// Return `true` if this protocol type is assignable to the protocol `other`.
|
||||
///
|
||||
/// TODO: consider the types of the members as well as their existence
|
||||
pub(super) fn is_assignable_to(self, db: &'db dyn Db, other: Self) -> bool {
|
||||
self.is_subtype_of(db, other)
|
||||
}
|
||||
|
||||
/// Return `true` if this protocol type is equivalent to the protocol `other`.
|
||||
///
|
||||
/// TODO: consider the types of the members as well as their existence
|
||||
pub(super) fn is_equivalent_to(self, db: &'db dyn Db, other: Self) -> bool {
|
||||
self.normalized(db) == other.normalized(db)
|
||||
}
|
||||
|
||||
/// Return `true` if this protocol type is gradually equivalent to the protocol `other`.
|
||||
///
|
||||
/// TODO: consider the types of the members as well as their existence
|
||||
pub(super) fn is_gradual_equivalent_to(self, db: &'db dyn Db, other: Self) -> bool {
|
||||
self.is_equivalent_to(db, other)
|
||||
}
|
||||
|
||||
/// Return `true` if this protocol type is disjoint from the protocol `other`.
|
||||
///
|
||||
/// TODO: a protocol `X` is disjoint from a protocol `Y` if `X` and `Y`
|
||||
/// have a member with the same name but disjoint types
|
||||
#[expect(clippy::unused_self)]
|
||||
pub(super) fn is_disjoint_from(self, _db: &'db dyn Db, _other: Self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// An enumeration of the two kinds of protocol types: those that originate from a class
|
||||
/// definition in source code, and those that are synthesized from a set of members.
|
||||
#[derive(
|
||||
Copy, Clone, Debug, Eq, PartialEq, Hash, salsa::Update, salsa::Supertype, PartialOrd, Ord,
|
||||
)]
|
||||
pub(super) enum Protocol<'db> {
|
||||
FromClass(ClassType<'db>),
|
||||
Synthesized(SynthesizedProtocolType<'db>),
|
||||
}
|
||||
|
||||
impl<'db> Protocol<'db> {
|
||||
/// Return the members of this protocol type
|
||||
fn protocol_members(self, db: &'db dyn Db) -> &'db FxOrderSet<Name> {
|
||||
match self {
|
||||
Self::FromClass(class) => class
|
||||
.class_literal(db)
|
||||
.0
|
||||
.into_protocol_class(db)
|
||||
.expect("Protocol class literal should be a protocol class")
|
||||
.protocol_members(db),
|
||||
Self::Synthesized(synthesized) => synthesized.members(db),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A "synthesized" protocol type that is dissociated from a class definition in source code.
|
||||
///
|
||||
/// Two synthesized protocol types with the same members will share the same Salsa ID,
|
||||
/// making them easy to compare for equivalence. A synthesized protocol type is therefore
|
||||
/// returned by [`ProtocolInstanceType::normalized`] so that two protocols with the same members
|
||||
/// will be understood as equivalent even in the context of differently ordered unions or intersections.
|
||||
#[salsa::interned(debug)]
|
||||
pub(super) struct SynthesizedProtocolType<'db> {
|
||||
#[return_ref]
|
||||
pub(super) members: FxOrderSet<Name>,
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
The `knot_extensions.pyi` file in this directory will be symlinked into
|
||||
the `vendor/typeshed/stdlib` directory every time we sync our `typeshed`
|
||||
stubs (see `.github/workflows/sync_typeshed.yaml`).
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff"
|
||||
version = "0.11.8"
|
||||
version = "0.11.9"
|
||||
publish = true
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
@@ -20,6 +20,7 @@ ruff_graph = { workspace = true, features = ["serde", "clap"] }
|
||||
ruff_linter = { workspace = true, features = ["clap"] }
|
||||
ruff_macros = { workspace = true }
|
||||
ruff_notebook = { workspace = true }
|
||||
ruff_options_metadata = { workspace = true, features = ["serde"] }
|
||||
ruff_python_ast = { workspace = true }
|
||||
ruff_python_formatter = { workspace = true }
|
||||
ruff_python_parser = { workspace = true }
|
||||
@@ -83,7 +84,7 @@ dist = true
|
||||
[target.'cfg(target_os = "windows")'.dependencies]
|
||||
mimalloc = { workspace = true }
|
||||
|
||||
[target.'cfg(all(not(target_os = "windows"), not(target_os = "openbsd"), not(target_os = "aix"), any(target_arch = "x86_64", target_arch = "aarch64", target_arch = "powerpc64")))'.dependencies]
|
||||
[target.'cfg(all(not(target_os = "windows"), not(target_os = "openbsd"), not(target_os = "aix"), not(target_os = "android"), any(target_arch = "x86_64", target_arch = "aarch64", target_arch = "powerpc64")))'.dependencies]
|
||||
tikv-jemallocator = { workspace = true }
|
||||
|
||||
[lints]
|
||||
|
||||
@@ -13,7 +13,6 @@ fn main() {
|
||||
|
||||
commit_info(&workspace_root);
|
||||
|
||||
#[allow(clippy::disallowed_methods)]
|
||||
let target = std::env::var("TARGET").unwrap();
|
||||
println!("cargo::rustc-env=RUST_HOST_TARGET={target}");
|
||||
}
|
||||
|
||||
@@ -22,12 +22,12 @@ use ruff_linter::settings::types::{
|
||||
PythonVersion, UnsafeFixes,
|
||||
};
|
||||
use ruff_linter::{RuleParser, RuleSelector, RuleSelectorParser};
|
||||
use ruff_options_metadata::{OptionEntry, OptionsMetadata};
|
||||
use ruff_python_ast as ast;
|
||||
use ruff_source_file::{LineIndex, OneIndexed, PositionEncoding};
|
||||
use ruff_text_size::TextRange;
|
||||
use ruff_workspace::configuration::{Configuration, RuleSelection};
|
||||
use ruff_workspace::options::{Options, PycodestyleOptions};
|
||||
use ruff_workspace::options_base::{OptionEntry, OptionsMetadata};
|
||||
use ruff_workspace::resolver::ConfigurationTransformer;
|
||||
use rustc_hash::FxHashMap;
|
||||
use toml;
|
||||
@@ -93,7 +93,7 @@ pub struct Args {
|
||||
pub(crate) global_options: GlobalConfigArgs,
|
||||
}
|
||||
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
#[expect(clippy::large_enum_variant)]
|
||||
#[derive(Debug, clap::Subcommand)]
|
||||
pub enum Command {
|
||||
/// Run Ruff on the given files or directories.
|
||||
@@ -184,7 +184,7 @@ pub struct AnalyzeGraphCommand {
|
||||
|
||||
// The `Parser` derive is for ruff_dev, for ruff `Args` would be sufficient
|
||||
#[derive(Clone, Debug, clap::Parser)]
|
||||
#[allow(clippy::struct_excessive_bools)]
|
||||
#[expect(clippy::struct_excessive_bools)]
|
||||
pub struct CheckCommand {
|
||||
/// List of files or directories to check.
|
||||
#[clap(help = "List of files or directories to check [default: .]")]
|
||||
@@ -446,7 +446,7 @@ pub struct CheckCommand {
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, clap::Parser)]
|
||||
#[allow(clippy::struct_excessive_bools)]
|
||||
#[expect(clippy::struct_excessive_bools)]
|
||||
pub struct FormatCommand {
|
||||
/// List of files or directories to format.
|
||||
#[clap(help = "List of files or directories to format [default: .]")]
|
||||
@@ -560,7 +560,7 @@ pub enum HelpFormat {
|
||||
Json,
|
||||
}
|
||||
|
||||
#[allow(clippy::module_name_repetitions)]
|
||||
#[expect(clippy::module_name_repetitions)]
|
||||
#[derive(Debug, Default, Clone, clap::Args)]
|
||||
pub struct LogLevelArgs {
|
||||
/// Enable verbose logging.
|
||||
@@ -1031,7 +1031,7 @@ Possible choices:
|
||||
|
||||
/// CLI settings that are distinct from configuration (commands, lists of files,
|
||||
/// etc.).
|
||||
#[allow(clippy::struct_excessive_bools)]
|
||||
#[expect(clippy::struct_excessive_bools)]
|
||||
pub struct CheckArguments {
|
||||
pub add_noqa: bool,
|
||||
pub diff: bool,
|
||||
@@ -1050,7 +1050,7 @@ pub struct CheckArguments {
|
||||
|
||||
/// CLI settings that are distinct from configuration (commands, lists of files,
|
||||
/// etc.).
|
||||
#[allow(clippy::struct_excessive_bools)]
|
||||
#[expect(clippy::struct_excessive_bools)]
|
||||
pub struct FormatArguments {
|
||||
pub check: bool,
|
||||
pub no_cache: bool,
|
||||
@@ -1271,7 +1271,6 @@ pub struct AnalyzeGraphArgs {
|
||||
/// Configuration overrides provided via dedicated CLI flags:
|
||||
/// `--line-length`, `--respect-gitignore`, etc.
|
||||
#[derive(Clone, Default)]
|
||||
#[allow(clippy::struct_excessive_bools)]
|
||||
struct ExplicitConfigOverrides {
|
||||
dummy_variable_rgx: Option<Regex>,
|
||||
exclude: Option<Vec<FilePattern>>,
|
||||
|
||||
@@ -86,7 +86,7 @@ pub(crate) struct Cache {
|
||||
changes: Mutex<Vec<Change>>,
|
||||
/// The "current" timestamp used as cache for the updates of
|
||||
/// [`FileCache::last_seen`]
|
||||
#[allow(clippy::struct_field_names)]
|
||||
#[expect(clippy::struct_field_names)]
|
||||
last_seen_cache: u64,
|
||||
}
|
||||
|
||||
@@ -146,7 +146,7 @@ impl Cache {
|
||||
Cache::new(path, package)
|
||||
}
|
||||
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
#[expect(clippy::cast_possible_truncation)]
|
||||
fn new(path: PathBuf, package: PackageCache) -> Self {
|
||||
Cache {
|
||||
path,
|
||||
@@ -204,7 +204,7 @@ impl Cache {
|
||||
}
|
||||
|
||||
/// Applies the pending changes without storing the cache to disk.
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
#[expect(clippy::cast_possible_truncation)]
|
||||
pub(crate) fn save(&mut self) -> bool {
|
||||
/// Maximum duration for which we keep a file in cache that hasn't been seen.
|
||||
const MAX_LAST_SEEN: Duration = Duration::from_secs(30 * 24 * 60 * 60); // 30 days.
|
||||
@@ -439,7 +439,7 @@ impl LintCacheData {
|
||||
.map(|msg| {
|
||||
// Make sure that all message use the same source file.
|
||||
assert_eq!(
|
||||
&msg.file,
|
||||
msg.file,
|
||||
messages.first().unwrap().source_file(),
|
||||
"message uses a different source file"
|
||||
);
|
||||
@@ -616,7 +616,7 @@ mod tests {
|
||||
let settings = Settings {
|
||||
cache_dir,
|
||||
linter: LinterSettings {
|
||||
unresolved_target_version: PythonVersion::latest(),
|
||||
unresolved_target_version: PythonVersion::latest().into(),
|
||||
..Default::default()
|
||||
},
|
||||
..Settings::default()
|
||||
@@ -834,7 +834,6 @@ mod tests {
|
||||
// Regression test for issue #3086.
|
||||
|
||||
#[cfg(unix)]
|
||||
#[allow(clippy::items_after_statements)]
|
||||
fn flip_execute_permission_bit(path: &Path) -> io::Result<()> {
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
let file = fs::OpenOptions::new().write(true).open(path)?;
|
||||
@@ -843,7 +842,6 @@ mod tests {
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
#[allow(clippy::items_after_statements)]
|
||||
fn flip_read_only_permission(path: &Path) -> io::Result<()> {
|
||||
let file = fs::OpenOptions::new().write(true).open(path)?;
|
||||
let mut perms = file.metadata()?.permissions();
|
||||
|
||||
@@ -30,7 +30,6 @@ use crate::cache::{Cache, PackageCacheMap, PackageCaches};
|
||||
use crate::diagnostics::Diagnostics;
|
||||
|
||||
/// Run the linter over a collection of files.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) fn check(
|
||||
files: &[PathBuf],
|
||||
pyproject_config: &PyprojectConfig,
|
||||
@@ -181,7 +180,6 @@ pub(crate) fn check(
|
||||
|
||||
/// Wraps [`lint_path`](crate::diagnostics::lint_path) in a [`catch_unwind`](std::panic::catch_unwind) and emits
|
||||
/// a diagnostic if the linting the file panics.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn lint_path(
|
||||
path: &Path,
|
||||
package: Option<PackageRoot<'_>>,
|
||||
|
||||
@@ -2,10 +2,8 @@ use clap::builder::{PossibleValue, TypedValueParser, ValueParserFactory};
|
||||
use itertools::Itertools;
|
||||
use std::str::FromStr;
|
||||
|
||||
use ruff_workspace::{
|
||||
options::Options,
|
||||
options_base::{OptionField, OptionSet, OptionsMetadata, Visit},
|
||||
};
|
||||
use ruff_options_metadata::{OptionField, OptionSet, OptionsMetadata, Visit};
|
||||
use ruff_workspace::options::Options;
|
||||
|
||||
#[derive(Default)]
|
||||
struct CollectOptionsVisitor {
|
||||
|
||||
@@ -2,10 +2,10 @@ use anyhow::{anyhow, Result};
|
||||
|
||||
use crate::args::HelpFormat;
|
||||
|
||||
use ruff_options_metadata::OptionsMetadata;
|
||||
use ruff_workspace::options::Options;
|
||||
use ruff_workspace::options_base::OptionsMetadata;
|
||||
|
||||
#[allow(clippy::print_stdout)]
|
||||
#[expect(clippy::print_stdout)]
|
||||
pub(crate) fn config(key: Option<&str>, format: HelpFormat) -> Result<()> {
|
||||
match key {
|
||||
None => {
|
||||
|
||||
@@ -362,7 +362,7 @@ pub(crate) fn format_source(
|
||||
})
|
||||
} else {
|
||||
// Using `Printed::into_code` requires adding `ruff_formatter` as a direct dependency, and I suspect that Rust can optimize the closure away regardless.
|
||||
#[allow(clippy::redundant_closure_for_method_calls)]
|
||||
#[expect(clippy::redundant_closure_for_method_calls)]
|
||||
format_module_source(unformatted, options).map(|formatted| formatted.into_code())
|
||||
};
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ struct Explanation<'a> {
|
||||
summary: &'a str,
|
||||
message_formats: &'a [&'a str],
|
||||
fix: String,
|
||||
#[allow(clippy::struct_field_names)]
|
||||
#[expect(clippy::struct_field_names)]
|
||||
explanation: Option<&'a str>,
|
||||
preview: bool,
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ use rustc_hash::FxHashMap;
|
||||
use ruff_diagnostics::Diagnostic;
|
||||
use ruff_linter::codes::Rule;
|
||||
use ruff_linter::linter::{lint_fix, lint_only, FixTable, FixerResult, LinterResult, ParseSource};
|
||||
use ruff_linter::message::{Message, SyntaxErrorMessage};
|
||||
use ruff_linter::message::Message;
|
||||
use ruff_linter::package::PackageRoot;
|
||||
use ruff_linter::pyproject_toml::lint_pyproject_toml;
|
||||
use ruff_linter::settings::types::UnsafeFixes;
|
||||
@@ -102,11 +102,7 @@ impl Diagnostics {
|
||||
let name = path.map_or_else(|| "-".into(), Path::to_string_lossy);
|
||||
let dummy = SourceFileBuilder::new(name, "").finish();
|
||||
Self::new(
|
||||
vec![Message::SyntaxError(SyntaxErrorMessage {
|
||||
message: err.to_string(),
|
||||
range: TextRange::default(),
|
||||
file: dummy,
|
||||
})],
|
||||
vec![Message::syntax_error(err, TextRange::default(), dummy)],
|
||||
FxHashMap::default(),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -134,7 +134,7 @@ pub fn run(
|
||||
{
|
||||
let default_panic_hook = std::panic::take_hook();
|
||||
std::panic::set_hook(Box::new(move |info| {
|
||||
#[allow(clippy::print_stderr)]
|
||||
#[expect(clippy::print_stderr)]
|
||||
{
|
||||
eprintln!(
|
||||
r#"
|
||||
@@ -326,7 +326,7 @@ pub fn check(args: CheckCommand, global_options: GlobalConfigArgs) -> Result<Exi
|
||||
commands::add_noqa::add_noqa(&files, &pyproject_config, &config_arguments)?;
|
||||
if modifications > 0 && config_arguments.log_level >= LogLevel::Default {
|
||||
let s = if modifications == 1 { "" } else { "s" };
|
||||
#[allow(clippy::print_stderr)]
|
||||
#[expect(clippy::print_stderr)]
|
||||
{
|
||||
eprintln!("Added {modifications} noqa directive{s}.");
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
|
||||
not(target_os = "windows"),
|
||||
not(target_os = "openbsd"),
|
||||
not(target_os = "aix"),
|
||||
not(target_os = "android"),
|
||||
any(
|
||||
target_arch = "x86_64",
|
||||
target_arch = "aarch64",
|
||||
|
||||
@@ -241,7 +241,6 @@ impl Printer {
|
||||
}
|
||||
|
||||
if !self.flags.intersects(Flags::SHOW_VIOLATIONS) {
|
||||
#[allow(deprecated)]
|
||||
if matches!(
|
||||
self.format,
|
||||
OutputFormat::Full | OutputFormat::Concise | OutputFormat::Grouped
|
||||
|
||||
@@ -552,7 +552,7 @@ fn venv() -> Result<()> {
|
||||
});
|
||||
|
||||
// test the error message for a non-existent venv. it's important that the `ruff analyze graph`
|
||||
// flag matches the red-knot flag used to generate the error message (`--python`)
|
||||
// flag matches the ty flag used to generate the error message (`--python`)
|
||||
insta::with_settings!({
|
||||
filters => INSTA_FILTERS.to_vec(),
|
||||
}, {
|
||||
|
||||
@@ -1900,6 +1900,40 @@ def first_square():
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Regression test for <https://github.com/astral-sh/ruff/issues/2253>
|
||||
#[test]
|
||||
fn add_noqa_parent() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
let test_path = tempdir.path().join("noqa.py");
|
||||
fs::write(
|
||||
&test_path,
|
||||
r#"
|
||||
from foo import ( # noqa: F401
|
||||
bar
|
||||
)
|
||||
"#,
|
||||
)?;
|
||||
|
||||
insta::with_settings!({
|
||||
filters => vec![(tempdir_filter(&tempdir).as_str(), "[TMP]/")]
|
||||
}, {
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(STDIN_BASE_OPTIONS)
|
||||
.arg("--add-noqa")
|
||||
.arg("--select=F401")
|
||||
.arg("noqa.py")
|
||||
.current_dir(&tempdir), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
");
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Infer `3.11` from `requires-python` in `pyproject.toml`.
|
||||
#[test]
|
||||
fn requires_python() -> Result<()> {
|
||||
@@ -3896,7 +3930,7 @@ from typing import Union;foo: Union[int, str] = 1
|
||||
linter.per_file_ignores = {}
|
||||
linter.safety_table.forced_safe = []
|
||||
linter.safety_table.forced_unsafe = []
|
||||
linter.unresolved_target_version = 3.9
|
||||
linter.unresolved_target_version = none
|
||||
linter.per_file_target_version = {}
|
||||
linter.preview = disabled
|
||||
linter.explicit_preview_rules = false
|
||||
@@ -4181,7 +4215,7 @@ from typing import Union;foo: Union[int, str] = 1
|
||||
linter.per_file_ignores = {}
|
||||
linter.safety_table.forced_safe = []
|
||||
linter.safety_table.forced_unsafe = []
|
||||
linter.unresolved_target_version = 3.9
|
||||
linter.unresolved_target_version = none
|
||||
linter.per_file_target_version = {}
|
||||
linter.preview = disabled
|
||||
linter.explicit_preview_rules = false
|
||||
@@ -5620,3 +5654,34 @@ fn semantic_syntax_errors() -> Result<()> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Regression test for <https://github.com/astral-sh/ruff/issues/17821>.
|
||||
///
|
||||
/// `lint.typing-extensions = false` with Python 3.9 should disable the PYI019 lint because it would
|
||||
/// try to import `Self` from `typing_extensions`
|
||||
#[test]
|
||||
fn combine_typing_extensions_config() {
|
||||
let contents = "
|
||||
from typing import TypeVar
|
||||
T = TypeVar('T')
|
||||
class Foo:
|
||||
def f(self: T) -> T: ...
|
||||
";
|
||||
assert_cmd_snapshot!(
|
||||
Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(STDIN_BASE_OPTIONS)
|
||||
.args(["--config", "lint.typing-extensions = false"])
|
||||
.arg("--select=PYI019")
|
||||
.arg("--target-version=py39")
|
||||
.arg("-")
|
||||
.pass_stdin(contents),
|
||||
@r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
All checks passed!
|
||||
|
||||
----- stderr -----
|
||||
"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ info:
|
||||
args:
|
||||
- rule
|
||||
- F401
|
||||
snapshot_kind: text
|
||||
---
|
||||
success: true
|
||||
exit_code: 0
|
||||
@@ -84,6 +83,11 @@ else:
|
||||
print("numpy is not installed")
|
||||
```
|
||||
|
||||
## Preview
|
||||
When [preview](https://docs.astral.sh/ruff/preview/) is enabled,
|
||||
the criterion for determining whether an import is first-party
|
||||
is stricter, which could affect the suggested fix. See [this FAQ section](https://docs.astral.sh/ruff/faq/#how-does-ruff-determine-which-of-my-imports-are-first-party-third-party-etc) for more details.
|
||||
|
||||
## Options
|
||||
- `lint.ignore-init-module-imports`
|
||||
- `lint.pyflakes.allowed-unused-imports`
|
||||
|
||||
@@ -33,7 +33,7 @@ name = "formatter"
|
||||
harness = false
|
||||
|
||||
[[bench]]
|
||||
name = "red_knot"
|
||||
name = "ty"
|
||||
harness = false
|
||||
|
||||
[dependencies]
|
||||
@@ -49,7 +49,7 @@ ruff_python_ast = { workspace = true }
|
||||
ruff_python_formatter = { workspace = true }
|
||||
ruff_python_parser = { workspace = true }
|
||||
ruff_python_trivia = { workspace = true }
|
||||
red_knot_project = { workspace = true }
|
||||
ty_project = { workspace = true }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
@@ -45,9 +45,9 @@ static GLOBAL: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc;
|
||||
target_arch = "powerpc64"
|
||||
)
|
||||
))]
|
||||
#[allow(non_upper_case_globals)]
|
||||
#[expect(non_upper_case_globals)]
|
||||
#[export_name = "_rjem_malloc_conf"]
|
||||
#[allow(unsafe_code)]
|
||||
#[expect(unsafe_code)]
|
||||
pub static _rjem_malloc_conf: &[u8] = b"dirty_decay_ms:-1,muzzy_decay_ms:-1\0";
|
||||
|
||||
fn create_test_cases() -> Vec<TestCase> {
|
||||
|
||||
@@ -7,16 +7,16 @@ use criterion::{criterion_group, criterion_main, BatchSize, Criterion};
|
||||
use rayon::ThreadPoolBuilder;
|
||||
use rustc_hash::FxHashSet;
|
||||
|
||||
use red_knot_project::metadata::options::{EnvironmentOptions, Options};
|
||||
use red_knot_project::metadata::value::RangedValue;
|
||||
use red_knot_project::watch::{ChangeEvent, ChangedKind};
|
||||
use red_knot_project::{Db, ProjectDatabase, ProjectMetadata};
|
||||
use ruff_benchmark::TestFile;
|
||||
use ruff_db::diagnostic::{Diagnostic, DiagnosticId, Severity};
|
||||
use ruff_db::files::{system_path_to_file, File};
|
||||
use ruff_db::source::source_text;
|
||||
use ruff_db::system::{MemoryFileSystem, SystemPath, SystemPathBuf, TestSystem};
|
||||
use ruff_python_ast::PythonVersion;
|
||||
use ty_project::metadata::options::{EnvironmentOptions, Options};
|
||||
use ty_project::metadata::value::RangedValue;
|
||||
use ty_project::watch::{ChangeEvent, ChangedKind};
|
||||
use ty_project::{Db, ProjectDatabase, ProjectMetadata};
|
||||
|
||||
struct Case {
|
||||
db: ProjectDatabase,
|
||||
@@ -59,13 +59,7 @@ type KeyDiagnosticFields = (
|
||||
Severity,
|
||||
);
|
||||
|
||||
static EXPECTED_TOMLLIB_DIAGNOSTICS: &[KeyDiagnosticFields] = &[(
|
||||
DiagnosticId::lint("unused-ignore-comment"),
|
||||
Some("/src/tomllib/_parser.py"),
|
||||
Some(22299..22333),
|
||||
"Unused blanket `type: ignore` directive",
|
||||
Severity::Warning,
|
||||
)];
|
||||
static EXPECTED_TOMLLIB_DIAGNOSTICS: &[KeyDiagnosticFields] = &[];
|
||||
|
||||
fn tomllib_path(file: &TestFile) -> SystemPathBuf {
|
||||
SystemPathBuf::from("src").join(file.name())
|
||||
@@ -122,7 +116,7 @@ static RAYON_INITIALIZED: std::sync::Once = std::sync::Once::new();
|
||||
fn setup_rayon() {
|
||||
// Initialize the rayon thread pool outside the benchmark because it has a significant cost.
|
||||
// We limit the thread pool to only one (the current thread) because we're focused on
|
||||
// where red knot spends time and less about how well the code runs concurrently.
|
||||
// where ty spends time and less about how well the code runs concurrently.
|
||||
// We might want to add a benchmark focusing on concurrency to detect congestion in the future.
|
||||
RAYON_INITIALIZED.call_once(|| {
|
||||
ThreadPoolBuilder::new()
|
||||
@@ -172,7 +166,7 @@ fn benchmark_incremental(criterion: &mut Criterion) {
|
||||
|
||||
setup_rayon();
|
||||
|
||||
criterion.bench_function("red_knot_check_file[incremental]", |b| {
|
||||
criterion.bench_function("ty_check_file[incremental]", |b| {
|
||||
b.iter_batched_ref(setup, incremental, BatchSize::SmallInput);
|
||||
});
|
||||
}
|
||||
@@ -180,7 +174,7 @@ fn benchmark_incremental(criterion: &mut Criterion) {
|
||||
fn benchmark_cold(criterion: &mut Criterion) {
|
||||
setup_rayon();
|
||||
|
||||
criterion.bench_function("red_knot_check_file[cold]", |b| {
|
||||
criterion.bench_function("ty_check_file[cold]", |b| {
|
||||
b.iter_batched_ref(
|
||||
setup_tomllib_case,
|
||||
|case| {
|
||||
@@ -203,7 +197,7 @@ fn assert_diagnostics(db: &dyn Db, diagnostics: &[Diagnostic], expected: &[KeyDi
|
||||
diagnostic.id(),
|
||||
diagnostic
|
||||
.primary_span()
|
||||
.map(|span| span.file())
|
||||
.map(|span| span.expect_ty_file())
|
||||
.map(|file| file.path(db).as_str()),
|
||||
diagnostic
|
||||
.primary_span()
|
||||
@@ -257,7 +251,7 @@ fn setup_micro_case(code: &str) -> Case {
|
||||
fn benchmark_many_string_assignments(criterion: &mut Criterion) {
|
||||
setup_rayon();
|
||||
|
||||
criterion.bench_function("red_knot_micro[many_string_assignments]", |b| {
|
||||
criterion.bench_function("ty_micro[many_string_assignments]", |b| {
|
||||
b.iter_batched_ref(
|
||||
|| {
|
||||
// This is a micro benchmark, but it is effectively identical to a code sample
|
||||
@@ -307,6 +301,62 @@ fn benchmark_many_string_assignments(criterion: &mut Criterion) {
|
||||
});
|
||||
}
|
||||
|
||||
fn benchmark_many_tuple_assignments(criterion: &mut Criterion) {
|
||||
setup_rayon();
|
||||
|
||||
criterion.bench_function("ty_micro[many_tuple_assignments]", |b| {
|
||||
b.iter_batched_ref(
|
||||
|| {
|
||||
// This is a micro benchmark, but it is effectively identical to a code sample
|
||||
// observed in https://github.com/astral-sh/ty/issues/362
|
||||
setup_micro_case(
|
||||
r#"
|
||||
def flag() -> bool:
|
||||
return True
|
||||
|
||||
t = ()
|
||||
if flag():
|
||||
t += (1,)
|
||||
if flag():
|
||||
t += (2,)
|
||||
if flag():
|
||||
t += (3,)
|
||||
if flag():
|
||||
t += (4,)
|
||||
if flag():
|
||||
t += (5,)
|
||||
if flag():
|
||||
t += (6,)
|
||||
if flag():
|
||||
t += (7,)
|
||||
if flag():
|
||||
t += (8,)
|
||||
if flag():
|
||||
t += (9,)
|
||||
if flag():
|
||||
t += (10,)
|
||||
if flag():
|
||||
t += (11,)
|
||||
|
||||
# Perform some kind of operation on the union type
|
||||
print(1 in t)
|
||||
"#,
|
||||
)
|
||||
},
|
||||
|case| {
|
||||
let Case { db, .. } = case;
|
||||
let result = db.check().unwrap();
|
||||
assert_eq!(result.len(), 0);
|
||||
},
|
||||
BatchSize::SmallInput,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
criterion_group!(check_file, benchmark_cold, benchmark_incremental);
|
||||
criterion_group!(micro, benchmark_many_string_assignments);
|
||||
criterion_group!(
|
||||
micro,
|
||||
benchmark_many_string_assignments,
|
||||
benchmark_many_tuple_assignments
|
||||
);
|
||||
criterion_main!(check_file, micro);
|
||||
@@ -213,7 +213,7 @@ macro_rules! impl_cache_key_tuple {
|
||||
|
||||
( $($name:ident)+) => (
|
||||
impl<$($name: CacheKey),+> CacheKey for ($($name,)+) where last_type!($($name,)+): ?Sized {
|
||||
#[allow(non_snake_case)]
|
||||
#[expect(non_snake_case)]
|
||||
#[inline]
|
||||
fn cache_key(&self, state: &mut CacheKeyHasher) {
|
||||
let ($(ref $name,)+) = *self;
|
||||
|
||||
@@ -47,7 +47,7 @@ fn struct_ignored_fields() {
|
||||
struct NamedFieldsStruct {
|
||||
a: String,
|
||||
#[cache_key(ignore)]
|
||||
#[allow(unused)]
|
||||
#[expect(unused)]
|
||||
b: String,
|
||||
}
|
||||
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
use std::{fmt::Formatter, sync::Arc};
|
||||
|
||||
use thiserror::Error;
|
||||
use render::{FileResolver, Input};
|
||||
use ruff_source_file::{SourceCode, SourceFile};
|
||||
|
||||
use ruff_annotate_snippets::Level as AnnotateLevel;
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
|
||||
pub use self::render::DisplayDiagnostic;
|
||||
use crate::files::File;
|
||||
use crate::Db;
|
||||
use crate::{files::File, Db};
|
||||
|
||||
use self::render::FileResolver;
|
||||
mod render;
|
||||
mod stylesheet;
|
||||
|
||||
@@ -115,10 +114,9 @@ impl Diagnostic {
|
||||
/// callers should prefer using this with `write!` instead of `writeln!`.
|
||||
pub fn display<'a>(
|
||||
&'a self,
|
||||
db: &'a dyn Db,
|
||||
resolver: &'a dyn FileResolver,
|
||||
config: &'a DisplayDiagnosticConfig,
|
||||
) -> DisplayDiagnostic<'a> {
|
||||
let resolver = FileResolver::new(db);
|
||||
DisplayDiagnostic::new(resolver, config, self)
|
||||
}
|
||||
|
||||
@@ -134,20 +132,20 @@ impl Diagnostic {
|
||||
/// NOTE: At present, this routine will return the first primary
|
||||
/// annotation's message as the primary message when the main diagnostic
|
||||
/// message is empty. This is meant to facilitate an incremental migration
|
||||
/// in Red Knot over to the new diagnostic data model. (The old data model
|
||||
/// in ty over to the new diagnostic data model. (The old data model
|
||||
/// didn't distinguish between messages on the entire diagnostic and
|
||||
/// messages attached to a particular span.)
|
||||
pub fn primary_message(&self) -> &str {
|
||||
if !self.inner.message.as_str().is_empty() {
|
||||
return self.inner.message.as_str();
|
||||
}
|
||||
// FIXME: As a special case, while we're migrating Red Knot
|
||||
// FIXME: As a special case, while we're migrating ty
|
||||
// to the new diagnostic data model, we'll look for a primary
|
||||
// message from the primary annotation. This is because most
|
||||
// Red Knot diagnostics are created with an empty diagnostic
|
||||
// ty diagnostics are created with an empty diagnostic
|
||||
// message and instead attach the message to the annotation.
|
||||
// Fixing this will require touching basically every diagnostic
|
||||
// in Red Knot, so we do it this way for now to match the old
|
||||
// in ty, so we do it this way for now to match the old
|
||||
// semantics. ---AG
|
||||
self.primary_annotation()
|
||||
.and_then(|ann| ann.get_message())
|
||||
@@ -165,7 +163,7 @@ impl Diagnostic {
|
||||
///
|
||||
/// The reason why we don't just always return both the main diagnostic
|
||||
/// message and the primary annotation message is because this was written
|
||||
/// in the midst of an incremental migration of Red Knot over to the new
|
||||
/// in the midst of an incremental migration of ty over to the new
|
||||
/// diagnostic data model. At time of writing, diagnostics were still
|
||||
/// constructed in the old model where the main diagnostic message and the
|
||||
/// primary annotation message were not distinguished from each other. So
|
||||
@@ -227,6 +225,30 @@ impl Diagnostic {
|
||||
pub fn primary_span(&self) -> Option<Span> {
|
||||
self.primary_annotation().map(|ann| ann.span.clone())
|
||||
}
|
||||
|
||||
/// Returns the tags from the primary annotation of this diagnostic if it exists.
|
||||
pub fn primary_tags(&self) -> Option<&[DiagnosticTag]> {
|
||||
self.primary_annotation().map(|ann| ann.tags.as_slice())
|
||||
}
|
||||
|
||||
/// Returns the "primary" span of this diagnostic, panicking if it does not exist.
|
||||
///
|
||||
/// This should typically only be used when working with diagnostics in Ruff, where diagnostics
|
||||
/// are currently required to have a primary span.
|
||||
///
|
||||
/// See [`Diagnostic::primary_span`] for more details.
|
||||
pub fn expect_primary_span(&self) -> Span {
|
||||
self.primary_span().expect("Expected a primary span")
|
||||
}
|
||||
|
||||
/// Returns a key that can be used to sort two diagnostics into the canonical order
|
||||
/// in which they should appear when rendered.
|
||||
pub fn rendering_sort_key<'a>(&'a self, db: &'a dyn Db) -> impl Ord + 'a {
|
||||
RenderingSortKey {
|
||||
db,
|
||||
diagnostic: self,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
@@ -238,6 +260,60 @@ struct DiagnosticInner {
|
||||
subs: Vec<SubDiagnostic>,
|
||||
}
|
||||
|
||||
struct RenderingSortKey<'a> {
|
||||
db: &'a dyn Db,
|
||||
diagnostic: &'a Diagnostic,
|
||||
}
|
||||
|
||||
impl Ord for RenderingSortKey<'_> {
|
||||
// We sort diagnostics in a way that keeps them in source order
|
||||
// and grouped by file. After that, we fall back to severity
|
||||
// (with fatal messages sorting before info messages) and then
|
||||
// finally the diagnostic ID.
|
||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||
if let (Some(span1), Some(span2)) = (
|
||||
self.diagnostic.primary_span(),
|
||||
other.diagnostic.primary_span(),
|
||||
) {
|
||||
let order = span1.file().path(&self.db).cmp(span2.file().path(&self.db));
|
||||
if order.is_ne() {
|
||||
return order;
|
||||
}
|
||||
|
||||
if let (Some(range1), Some(range2)) = (span1.range(), span2.range()) {
|
||||
let order = range1.start().cmp(&range2.start());
|
||||
if order.is_ne() {
|
||||
return order;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Reverse so that, e.g., Fatal sorts before Info.
|
||||
let order = self
|
||||
.diagnostic
|
||||
.severity()
|
||||
.cmp(&other.diagnostic.severity())
|
||||
.reverse();
|
||||
if order.is_ne() {
|
||||
return order;
|
||||
}
|
||||
self.diagnostic.id().cmp(&other.diagnostic.id())
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for RenderingSortKey<'_> {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for RenderingSortKey<'_> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.cmp(other).is_eq()
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for RenderingSortKey<'_> {}
|
||||
|
||||
/// A collection of information subservient to a diagnostic.
|
||||
///
|
||||
/// A sub-diagnostic is always rendered after the parent diagnostic it is
|
||||
@@ -338,6 +414,8 @@ pub struct Annotation {
|
||||
/// Whether this annotation is "primary" or not. When it isn't primary, an
|
||||
/// annotation is said to be "secondary."
|
||||
is_primary: bool,
|
||||
/// The diagnostic tags associated with this annotation.
|
||||
tags: Vec<DiagnosticTag>,
|
||||
}
|
||||
|
||||
impl Annotation {
|
||||
@@ -355,6 +433,7 @@ impl Annotation {
|
||||
span,
|
||||
message: None,
|
||||
is_primary: true,
|
||||
tags: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -370,6 +449,7 @@ impl Annotation {
|
||||
span,
|
||||
message: None,
|
||||
is_primary: false,
|
||||
tags: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -412,6 +492,36 @@ impl Annotation {
|
||||
pub fn get_span(&self) -> &Span {
|
||||
&self.span
|
||||
}
|
||||
|
||||
/// Returns the tags associated with this annotation.
|
||||
pub fn get_tags(&self) -> &[DiagnosticTag] {
|
||||
&self.tags
|
||||
}
|
||||
|
||||
/// Attaches this tag to this annotation.
|
||||
///
|
||||
/// It will not replace any existing tags.
|
||||
pub fn tag(mut self, tag: DiagnosticTag) -> Annotation {
|
||||
self.tags.push(tag);
|
||||
self
|
||||
}
|
||||
|
||||
/// Attaches an additional tag to this annotation.
|
||||
pub fn push_tag(&mut self, tag: DiagnosticTag) {
|
||||
self.tags.push(tag);
|
||||
}
|
||||
}
|
||||
|
||||
/// Tags that can be associated with an annotation.
|
||||
///
|
||||
/// These tags are used to provide additional information about the annotation.
|
||||
/// and are passed through to the language server protocol.
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub enum DiagnosticTag {
|
||||
/// Unused or unnecessary code. Used for unused parameters, unreachable code, etc.
|
||||
Unnecessary,
|
||||
/// Deprecated or obsolete code.
|
||||
Deprecated,
|
||||
}
|
||||
|
||||
/// A string identifier for a lint rule.
|
||||
@@ -502,62 +612,84 @@ impl DiagnosticId {
|
||||
code.split_once(':').map(|(_, rest)| rest)
|
||||
}
|
||||
|
||||
/// Returns `true` if this `DiagnosticId` matches the given name.
|
||||
/// Returns a concise description of this diagnostic ID.
|
||||
///
|
||||
/// ## Examples
|
||||
/// ```
|
||||
/// use ruff_db::diagnostic::DiagnosticId;
|
||||
///
|
||||
/// assert!(DiagnosticId::Io.matches("io"));
|
||||
/// assert!(DiagnosticId::lint("test").matches("lint:test"));
|
||||
/// assert!(!DiagnosticId::lint("test").matches("test"));
|
||||
/// ```
|
||||
pub fn matches(&self, expected_name: &str) -> bool {
|
||||
match self.as_str() {
|
||||
Ok(id) => id == expected_name,
|
||||
Err(DiagnosticAsStrError::Category { category, name }) => expected_name
|
||||
.strip_prefix(category)
|
||||
.and_then(|prefix| prefix.strip_prefix(":"))
|
||||
.is_some_and(|rest| rest == name),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_str(&self) -> Result<&str, DiagnosticAsStrError> {
|
||||
Ok(match self {
|
||||
/// Note that this doesn't include the lint's category. It
|
||||
/// only includes the lint's name.
|
||||
pub fn as_str(&self) -> &str {
|
||||
match self {
|
||||
DiagnosticId::Panic => "panic",
|
||||
DiagnosticId::Io => "io",
|
||||
DiagnosticId::InvalidSyntax => "invalid-syntax",
|
||||
DiagnosticId::Lint(name) => {
|
||||
return Err(DiagnosticAsStrError::Category {
|
||||
category: "lint",
|
||||
name: name.as_str(),
|
||||
})
|
||||
}
|
||||
DiagnosticId::Lint(name) => name.as_str(),
|
||||
DiagnosticId::RevealedType => "revealed-type",
|
||||
DiagnosticId::UnknownRule => "unknown-rule",
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Error)]
|
||||
pub enum DiagnosticAsStrError {
|
||||
/// The id can't be converted to a string because it belongs to a sub-category.
|
||||
#[error("id from a sub-category: {category}:{name}")]
|
||||
Category {
|
||||
/// The id's category.
|
||||
category: &'static str,
|
||||
/// The diagnostic id in this category.
|
||||
name: &'static str,
|
||||
},
|
||||
pub fn is_invalid_syntax(&self) -> bool {
|
||||
matches!(self, Self::InvalidSyntax)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for DiagnosticId {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self.as_str() {
|
||||
Ok(name) => f.write_str(name),
|
||||
Err(DiagnosticAsStrError::Category { category, name }) => {
|
||||
write!(f, "{category}:{name}")
|
||||
}
|
||||
write!(f, "{}", self.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
/// A unified file representation for both ruff and ty.
|
||||
///
|
||||
/// Such a representation is needed for rendering [`Diagnostic`]s that can optionally contain
|
||||
/// [`Annotation`]s with [`Span`]s that need to refer to the text of a file. However, ty and ruff
|
||||
/// use very different file types: a `Copy`-able salsa-interned [`File`], and a heavier-weight
|
||||
/// [`SourceFile`], respectively.
|
||||
///
|
||||
/// This enum presents a unified interface to these two types for the sake of creating [`Span`]s and
|
||||
/// emitting diagnostics from both ty and ruff.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum UnifiedFile {
|
||||
Ty(File),
|
||||
Ruff(SourceFile),
|
||||
}
|
||||
|
||||
impl UnifiedFile {
|
||||
pub fn path<'a>(&'a self, resolver: &'a dyn FileResolver) -> &'a str {
|
||||
match self {
|
||||
UnifiedFile::Ty(file) => resolver.path(*file),
|
||||
UnifiedFile::Ruff(file) => file.name(),
|
||||
}
|
||||
}
|
||||
|
||||
fn diagnostic_source(&self, resolver: &dyn FileResolver) -> DiagnosticSource {
|
||||
match self {
|
||||
UnifiedFile::Ty(file) => DiagnosticSource::Ty(resolver.input(*file)),
|
||||
UnifiedFile::Ruff(file) => DiagnosticSource::Ruff(file.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A unified wrapper for types that can be converted to a [`SourceCode`].
|
||||
///
|
||||
/// As with [`UnifiedFile`], ruff and ty use slightly different representations for source code.
|
||||
/// [`DiagnosticSource`] wraps both of these and provides the single
|
||||
/// [`DiagnosticSource::as_source_code`] method to produce a [`SourceCode`] with the appropriate
|
||||
/// lifetimes.
|
||||
///
|
||||
/// See [`UnifiedFile::diagnostic_source`] for a way to obtain a [`DiagnosticSource`] from a file
|
||||
/// and [`FileResolver`].
|
||||
#[derive(Clone, Debug)]
|
||||
enum DiagnosticSource {
|
||||
Ty(Input),
|
||||
Ruff(SourceFile),
|
||||
}
|
||||
|
||||
impl DiagnosticSource {
|
||||
/// Returns this input as a `SourceCode` for convenient querying.
|
||||
fn as_source_code(&self) -> SourceCode {
|
||||
match self {
|
||||
DiagnosticSource::Ty(input) => SourceCode::new(input.text.as_str(), &input.line_index),
|
||||
DiagnosticSource::Ruff(source) => SourceCode::new(source.source_text(), source.index()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -569,14 +701,14 @@ impl std::fmt::Display for DiagnosticId {
|
||||
/// the entire file. For example, when the file should be executable but isn't.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Span {
|
||||
file: File,
|
||||
file: UnifiedFile,
|
||||
range: Option<TextRange>,
|
||||
}
|
||||
|
||||
impl Span {
|
||||
/// Returns the `File` attached to this `Span`.
|
||||
pub fn file(&self) -> File {
|
||||
self.file
|
||||
/// Returns the `UnifiedFile` attached to this `Span`.
|
||||
pub fn file(&self) -> &UnifiedFile {
|
||||
&self.file
|
||||
}
|
||||
|
||||
/// Returns the range, if available, attached to this `Span`.
|
||||
@@ -597,10 +729,38 @@ impl Span {
|
||||
pub fn with_optional_range(self, range: Option<TextRange>) -> Span {
|
||||
Span { range, ..self }
|
||||
}
|
||||
|
||||
/// Returns the [`File`] attached to this [`Span`].
|
||||
///
|
||||
/// Panics if the file is a [`UnifiedFile::Ruff`] instead of a [`UnifiedFile::Ty`].
|
||||
pub fn expect_ty_file(&self) -> File {
|
||||
match self.file {
|
||||
UnifiedFile::Ty(file) => file,
|
||||
UnifiedFile::Ruff(_) => panic!("Expected a ty `File`, found a ruff `SourceFile`"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the [`SourceFile`] attached to this [`Span`].
|
||||
///
|
||||
/// Panics if the file is a [`UnifiedFile::Ty`] instead of a [`UnifiedFile::Ruff`].
|
||||
pub fn expect_ruff_file(&self) -> &SourceFile {
|
||||
match &self.file {
|
||||
UnifiedFile::Ty(_) => panic!("Expected a ruff `SourceFile`, found a ty `File`"),
|
||||
UnifiedFile::Ruff(file) => file,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<File> for Span {
|
||||
fn from(file: File) -> Span {
|
||||
let file = UnifiedFile::Ty(file);
|
||||
Span { file, range: None }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SourceFile> for Span {
|
||||
fn from(file: SourceFile) -> Self {
|
||||
let file = UnifiedFile::Ruff(file);
|
||||
Span { file, range: None }
|
||||
}
|
||||
}
|
||||
@@ -635,6 +795,10 @@ impl Severity {
|
||||
Severity::Fatal => AnnotateLevel::Error,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn is_fatal(self) -> bool {
|
||||
matches!(self, Severity::Fatal)
|
||||
}
|
||||
}
|
||||
|
||||
/// Configuration for rendering diagnostics.
|
||||
|
||||
@@ -16,7 +16,8 @@ use crate::{
|
||||
};
|
||||
|
||||
use super::{
|
||||
Annotation, Diagnostic, DiagnosticFormat, DisplayDiagnosticConfig, Severity, SubDiagnostic,
|
||||
Annotation, Diagnostic, DiagnosticFormat, DiagnosticSource, DisplayDiagnosticConfig, Severity,
|
||||
SubDiagnostic,
|
||||
};
|
||||
|
||||
/// A type that implements `std::fmt::Display` for diagnostic rendering.
|
||||
@@ -30,17 +31,16 @@ use super::{
|
||||
/// values. When using Salsa, this most commonly corresponds to the lifetime
|
||||
/// of a Salsa `Db`.
|
||||
/// * The lifetime of the diagnostic being rendered.
|
||||
#[derive(Debug)]
|
||||
pub struct DisplayDiagnostic<'a> {
|
||||
config: &'a DisplayDiagnosticConfig,
|
||||
resolver: FileResolver<'a>,
|
||||
resolver: &'a dyn FileResolver,
|
||||
annotate_renderer: AnnotateRenderer,
|
||||
diag: &'a Diagnostic,
|
||||
}
|
||||
|
||||
impl<'a> DisplayDiagnostic<'a> {
|
||||
pub(crate) fn new(
|
||||
resolver: FileResolver<'a>,
|
||||
resolver: &'a dyn FileResolver,
|
||||
config: &'a DisplayDiagnosticConfig,
|
||||
diag: &'a Diagnostic,
|
||||
) -> DisplayDiagnostic<'a> {
|
||||
@@ -86,11 +86,13 @@ impl std::fmt::Display for DisplayDiagnostic<'_> {
|
||||
write!(
|
||||
f,
|
||||
" {path}",
|
||||
path = fmt_styled(self.resolver.path(span.file()), stylesheet.emphasis)
|
||||
path = fmt_styled(span.file().path(self.resolver), stylesheet.emphasis)
|
||||
)?;
|
||||
if let Some(range) = span.range() {
|
||||
let input = self.resolver.input(span.file());
|
||||
let start = input.as_source_code().line_column(range.start());
|
||||
let diagnostic_source = span.file().diagnostic_source(self.resolver);
|
||||
let start = diagnostic_source
|
||||
.as_source_code()
|
||||
.line_column(range.start());
|
||||
|
||||
write!(
|
||||
f,
|
||||
@@ -115,7 +117,7 @@ impl std::fmt::Display for DisplayDiagnostic<'_> {
|
||||
.emphasis(stylesheet.emphasis)
|
||||
.none(stylesheet.none);
|
||||
|
||||
let resolved = Resolved::new(&self.resolver, self.diag);
|
||||
let resolved = Resolved::new(self.resolver, self.diag);
|
||||
let renderable = resolved.to_renderable(self.config.context);
|
||||
for diag in renderable.diagnostics.iter() {
|
||||
writeln!(f, "{}", renderer.render(diag.to_annotate()))?;
|
||||
@@ -138,26 +140,23 @@ impl std::fmt::Display for DisplayDiagnostic<'_> {
|
||||
/// both.)
|
||||
#[derive(Debug)]
|
||||
struct Resolved<'a> {
|
||||
id: String,
|
||||
diagnostics: Vec<ResolvedDiagnostic<'a>>,
|
||||
}
|
||||
|
||||
impl<'a> Resolved<'a> {
|
||||
/// Creates a new resolved set of diagnostics.
|
||||
fn new(resolver: &FileResolver<'a>, diag: &'a Diagnostic) -> Resolved<'a> {
|
||||
fn new(resolver: &'a dyn FileResolver, diag: &'a Diagnostic) -> Resolved<'a> {
|
||||
let mut diagnostics = vec![];
|
||||
diagnostics.push(ResolvedDiagnostic::from_diagnostic(resolver, diag));
|
||||
for sub in &diag.inner.subs {
|
||||
diagnostics.push(ResolvedDiagnostic::from_sub_diagnostic(resolver, sub));
|
||||
}
|
||||
let id = diag.inner.id.to_string();
|
||||
Resolved { id, diagnostics }
|
||||
Resolved { diagnostics }
|
||||
}
|
||||
|
||||
/// Creates a value that is amenable to rendering directly.
|
||||
fn to_renderable(&self, context: usize) -> Renderable<'_> {
|
||||
Renderable {
|
||||
id: &self.id,
|
||||
diagnostics: self
|
||||
.diagnostics
|
||||
.iter()
|
||||
@@ -175,6 +174,7 @@ impl<'a> Resolved<'a> {
|
||||
#[derive(Debug)]
|
||||
struct ResolvedDiagnostic<'a> {
|
||||
severity: Severity,
|
||||
id: Option<String>,
|
||||
message: String,
|
||||
annotations: Vec<ResolvedAnnotation<'a>>,
|
||||
}
|
||||
@@ -182,7 +182,7 @@ struct ResolvedDiagnostic<'a> {
|
||||
impl<'a> ResolvedDiagnostic<'a> {
|
||||
/// Resolve a single diagnostic.
|
||||
fn from_diagnostic(
|
||||
resolver: &FileResolver<'a>,
|
||||
resolver: &'a dyn FileResolver,
|
||||
diag: &'a Diagnostic,
|
||||
) -> ResolvedDiagnostic<'a> {
|
||||
let annotations: Vec<_> = diag
|
||||
@@ -190,25 +190,16 @@ impl<'a> ResolvedDiagnostic<'a> {
|
||||
.annotations
|
||||
.iter()
|
||||
.filter_map(|ann| {
|
||||
let path = resolver.path(ann.span.file);
|
||||
let input = resolver.input(ann.span.file);
|
||||
ResolvedAnnotation::new(path, &input, ann)
|
||||
let path = ann.span.file.path(resolver);
|
||||
let diagnostic_source = ann.span.file.diagnostic_source(resolver);
|
||||
ResolvedAnnotation::new(path, &diagnostic_source, ann)
|
||||
})
|
||||
.collect();
|
||||
let message = if diag.inner.message.as_str().is_empty() {
|
||||
diag.inner.id.to_string()
|
||||
} else {
|
||||
// TODO: See the comment on `Renderable::id` for
|
||||
// a plausible better idea than smushing the ID
|
||||
// into the diagnostic message.
|
||||
format!(
|
||||
"{id}: {message}",
|
||||
id = diag.inner.id,
|
||||
message = diag.inner.message.as_str(),
|
||||
)
|
||||
};
|
||||
let id = Some(diag.inner.id.to_string());
|
||||
let message = diag.inner.message.as_str().to_string();
|
||||
ResolvedDiagnostic {
|
||||
severity: diag.inner.severity,
|
||||
id,
|
||||
message,
|
||||
annotations,
|
||||
}
|
||||
@@ -216,7 +207,7 @@ impl<'a> ResolvedDiagnostic<'a> {
|
||||
|
||||
/// Resolve a single sub-diagnostic.
|
||||
fn from_sub_diagnostic(
|
||||
resolver: &FileResolver<'a>,
|
||||
resolver: &'a dyn FileResolver,
|
||||
diag: &'a SubDiagnostic,
|
||||
) -> ResolvedDiagnostic<'a> {
|
||||
let annotations: Vec<_> = diag
|
||||
@@ -224,13 +215,14 @@ impl<'a> ResolvedDiagnostic<'a> {
|
||||
.annotations
|
||||
.iter()
|
||||
.filter_map(|ann| {
|
||||
let path = resolver.path(ann.span.file);
|
||||
let input = resolver.input(ann.span.file);
|
||||
ResolvedAnnotation::new(path, &input, ann)
|
||||
let path = ann.span.file.path(resolver);
|
||||
let diagnostic_source = ann.span.file.diagnostic_source(resolver);
|
||||
ResolvedAnnotation::new(path, &diagnostic_source, ann)
|
||||
})
|
||||
.collect();
|
||||
ResolvedDiagnostic {
|
||||
severity: diag.inner.severity,
|
||||
id: None,
|
||||
message: diag.inner.message.as_str().to_string(),
|
||||
annotations,
|
||||
}
|
||||
@@ -259,10 +251,18 @@ impl<'a> ResolvedDiagnostic<'a> {
|
||||
continue;
|
||||
};
|
||||
|
||||
let prev_context_ends =
|
||||
context_after(&prev.input.as_source_code(), context, prev.line_end).get();
|
||||
let this_context_begins =
|
||||
context_before(&ann.input.as_source_code(), context, ann.line_start).get();
|
||||
let prev_context_ends = context_after(
|
||||
&prev.diagnostic_source.as_source_code(),
|
||||
context,
|
||||
prev.line_end,
|
||||
)
|
||||
.get();
|
||||
let this_context_begins = context_before(
|
||||
&ann.diagnostic_source.as_source_code(),
|
||||
context,
|
||||
ann.line_start,
|
||||
)
|
||||
.get();
|
||||
// The boundary case here is when `prev_context_ends`
|
||||
// is exactly one less than `this_context_begins`. In
|
||||
// that case, the context windows are adajcent and we
|
||||
@@ -289,6 +289,7 @@ impl<'a> ResolvedDiagnostic<'a> {
|
||||
.sort_by(|snips1, snips2| snips1.has_primary.cmp(&snips2.has_primary).reverse());
|
||||
RenderableDiagnostic {
|
||||
severity: self.severity,
|
||||
id: self.id.as_deref(),
|
||||
message: &self.message,
|
||||
snippets_by_input,
|
||||
}
|
||||
@@ -304,7 +305,7 @@ impl<'a> ResolvedDiagnostic<'a> {
|
||||
#[derive(Debug)]
|
||||
struct ResolvedAnnotation<'a> {
|
||||
path: &'a str,
|
||||
input: Input,
|
||||
diagnostic_source: DiagnosticSource,
|
||||
range: TextRange,
|
||||
line_start: OneIndexed,
|
||||
line_end: OneIndexed,
|
||||
@@ -318,8 +319,12 @@ impl<'a> ResolvedAnnotation<'a> {
|
||||
/// `path` is the path of the file that this annotation points to.
|
||||
///
|
||||
/// `input` is the contents of the file that this annotation points to.
|
||||
fn new(path: &'a str, input: &Input, ann: &'a Annotation) -> Option<ResolvedAnnotation<'a>> {
|
||||
let source = input.as_source_code();
|
||||
fn new(
|
||||
path: &'a str,
|
||||
diagnostic_source: &DiagnosticSource,
|
||||
ann: &'a Annotation,
|
||||
) -> Option<ResolvedAnnotation<'a>> {
|
||||
let source = diagnostic_source.as_source_code();
|
||||
let (range, line_start, line_end) = match (ann.span.range(), ann.message.is_some()) {
|
||||
// An annotation with no range AND no message is probably(?)
|
||||
// meaningless, but we should try to render it anyway.
|
||||
@@ -345,7 +350,7 @@ impl<'a> ResolvedAnnotation<'a> {
|
||||
};
|
||||
Some(ResolvedAnnotation {
|
||||
path,
|
||||
input: input.clone(),
|
||||
diagnostic_source: diagnostic_source.clone(),
|
||||
range,
|
||||
line_start,
|
||||
line_end,
|
||||
@@ -364,20 +369,6 @@ impl<'a> ResolvedAnnotation<'a> {
|
||||
/// renderable value. This is usually the lifetime of `Resolved`.
|
||||
#[derive(Debug)]
|
||||
struct Renderable<'r> {
|
||||
// TODO: This is currently unused in the rendering logic below. I'm not
|
||||
// 100% sure yet where I want to put it, but I like what `rustc` does:
|
||||
//
|
||||
// error[E0599]: no method named `sub_builder` <..snip..>
|
||||
//
|
||||
// I believe in order to do this, we'll need to patch it in to
|
||||
// `ruff_annotate_snippets` though. We leave it here for now with that plan
|
||||
// in mind.
|
||||
//
|
||||
// (At time of writing, 2025-03-13, we currently render the diagnostic
|
||||
// ID into the main message of the parent diagnostic. We don't use this
|
||||
// specific field to do that though.)
|
||||
#[allow(dead_code)]
|
||||
id: &'r str,
|
||||
diagnostics: Vec<RenderableDiagnostic<'r>>,
|
||||
}
|
||||
|
||||
@@ -386,6 +377,12 @@ struct Renderable<'r> {
|
||||
struct RenderableDiagnostic<'r> {
|
||||
/// The severity of the diagnostic.
|
||||
severity: Severity,
|
||||
/// The ID of the diagnostic. The ID can usually be used on the CLI or in a
|
||||
/// config file to change the severity of a lint.
|
||||
///
|
||||
/// An ID is always present for top-level diagnostics and always absent for
|
||||
/// sub-diagnostics.
|
||||
id: Option<&'r str>,
|
||||
/// The message emitted with the diagnostic, before any snippets are
|
||||
/// rendered.
|
||||
message: &'r str,
|
||||
@@ -406,7 +403,11 @@ impl RenderableDiagnostic<'_> {
|
||||
.iter()
|
||||
.map(|snippet| snippet.to_annotate(path))
|
||||
});
|
||||
level.title(self.message).snippets(snippets)
|
||||
let mut message = level.title(self.message);
|
||||
if let Some(id) = self.id {
|
||||
message = message.id(id);
|
||||
}
|
||||
message.snippets(snippets)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -510,8 +511,8 @@ impl<'r> RenderableSnippet<'r> {
|
||||
!anns.is_empty(),
|
||||
"creating a renderable snippet requires a non-zero number of annotations",
|
||||
);
|
||||
let input = &anns[0].input;
|
||||
let source = input.as_source_code();
|
||||
let diagnostic_source = &anns[0].diagnostic_source;
|
||||
let source = diagnostic_source.as_source_code();
|
||||
let has_primary = anns.iter().any(|ann| ann.is_primary);
|
||||
|
||||
let line_start = context_before(
|
||||
@@ -527,7 +528,7 @@ impl<'r> RenderableSnippet<'r> {
|
||||
|
||||
let snippet_start = source.line_start(line_start);
|
||||
let snippet_end = source.line_end(line_end);
|
||||
let snippet = input
|
||||
let snippet = diagnostic_source
|
||||
.as_source_code()
|
||||
.slice(TextRange::new(snippet_start, snippet_end));
|
||||
|
||||
@@ -613,7 +614,7 @@ impl<'r> RenderableAnnotation<'r> {
|
||||
}
|
||||
}
|
||||
|
||||
/// A type that facilitates the retrieval of source code from a `Span`.
|
||||
/// A trait that facilitates the retrieval of source code from a `Span`.
|
||||
///
|
||||
/// At present, this is tightly coupled with a Salsa database. In the future,
|
||||
/// it is intended for this resolver to become an abstraction providing a
|
||||
@@ -624,40 +625,28 @@ impl<'r> RenderableAnnotation<'r> {
|
||||
/// For example, at time of writing (2025-03-07), the plan is (roughly) for
|
||||
/// Ruff to grow its own interner of file paths so that a `Span` can store an
|
||||
/// interned ID instead of a (roughly) `Arc<Path>`. This interner is planned
|
||||
/// to be entirely separate from the Salsa interner used by Red Knot, and so,
|
||||
/// to be entirely separate from the Salsa interner used by ty, and so,
|
||||
/// callers will need to pass in a different "resolver" for turning `Span`s
|
||||
/// into actual file paths/contents. The infrastructure for this isn't fully in
|
||||
/// place, but this type serves to demarcate the intended abstraction boundary.
|
||||
pub(crate) struct FileResolver<'a> {
|
||||
db: &'a dyn Db,
|
||||
}
|
||||
|
||||
impl<'a> FileResolver<'a> {
|
||||
/// Creates a new resolver from a Salsa database.
|
||||
pub(crate) fn new(db: &'a dyn Db) -> FileResolver<'a> {
|
||||
FileResolver { db }
|
||||
}
|
||||
|
||||
pub trait FileResolver {
|
||||
/// Returns the path associated with the file given.
|
||||
fn path(&self, file: File) -> &'a str {
|
||||
relativize_path(
|
||||
self.db.system().current_directory(),
|
||||
file.path(self.db).as_str(),
|
||||
)
|
||||
}
|
||||
fn path(&self, file: File) -> &str;
|
||||
|
||||
/// Returns the input contents associated with the file given.
|
||||
fn input(&self, file: File) -> Input {
|
||||
Input {
|
||||
text: source_text(self.db, file),
|
||||
line_index: line_index(self.db, file),
|
||||
}
|
||||
}
|
||||
fn input(&self, file: File) -> Input;
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for FileResolver<'_> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
write!(f, "<salsa based file resolver>")
|
||||
impl FileResolver for &dyn Db {
|
||||
fn path(&self, file: File) -> &str {
|
||||
relativize_path(self.system().current_directory(), file.path(*self).as_str())
|
||||
}
|
||||
|
||||
fn input(&self, file: File) -> Input {
|
||||
Input {
|
||||
text: source_text(*self, file),
|
||||
line_index: line_index(*self, file),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -667,16 +656,9 @@ impl std::fmt::Debug for FileResolver<'_> {
|
||||
/// This contains the actual content of that input as well as a
|
||||
/// line index for efficiently querying its contents.
|
||||
#[derive(Clone, Debug)]
|
||||
struct Input {
|
||||
text: SourceText,
|
||||
line_index: LineIndex,
|
||||
}
|
||||
|
||||
impl Input {
|
||||
/// Returns this input as a `SourceCode` for convenient querying.
|
||||
fn as_source_code(&self) -> SourceCode<'_, '_> {
|
||||
SourceCode::new(self.text.as_str(), &self.line_index)
|
||||
}
|
||||
pub struct Input {
|
||||
pub(crate) text: SourceText,
|
||||
pub(crate) line_index: LineIndex,
|
||||
}
|
||||
|
||||
/// Returns the line number accounting for the given `len`
|
||||
@@ -730,6 +712,7 @@ mod tests {
|
||||
use crate::files::system_path_to_file;
|
||||
use crate::system::{DbWithWritableSystem, SystemPath};
|
||||
use crate::tests::TestDb;
|
||||
use crate::Upcast;
|
||||
|
||||
use super::*;
|
||||
|
||||
@@ -803,7 +786,7 @@ watermelon
|
||||
insta::assert_snapshot!(
|
||||
env.render(&diag),
|
||||
@r"
|
||||
error: lint:test-diagnostic: main diagnostic message
|
||||
error[test-diagnostic]: main diagnostic message
|
||||
--> animals:5:1
|
||||
|
|
||||
3 | canary
|
||||
@@ -827,7 +810,7 @@ watermelon
|
||||
insta::assert_snapshot!(
|
||||
env.render(&diag),
|
||||
@r"
|
||||
warning: lint:test-diagnostic: main diagnostic message
|
||||
warning[test-diagnostic]: main diagnostic message
|
||||
--> animals:5:1
|
||||
|
|
||||
3 | canary
|
||||
@@ -847,7 +830,7 @@ watermelon
|
||||
insta::assert_snapshot!(
|
||||
env.render(&diag),
|
||||
@r"
|
||||
info: lint:test-diagnostic: main diagnostic message
|
||||
info[test-diagnostic]: main diagnostic message
|
||||
--> animals:5:1
|
||||
|
|
||||
3 | canary
|
||||
@@ -874,7 +857,7 @@ watermelon
|
||||
insta::assert_snapshot!(
|
||||
env.render(&diag),
|
||||
@r"
|
||||
error: lint:test-diagnostic: main diagnostic message
|
||||
error[test-diagnostic]: main diagnostic message
|
||||
--> animals:1:1
|
||||
|
|
||||
1 | aardvark
|
||||
@@ -893,7 +876,7 @@ watermelon
|
||||
insta::assert_snapshot!(
|
||||
env.render(&diag),
|
||||
@r"
|
||||
error: lint:test-diagnostic: main diagnostic message
|
||||
error[test-diagnostic]: main diagnostic message
|
||||
--> animals:1:1
|
||||
|
|
||||
1 | aardvark
|
||||
@@ -914,7 +897,7 @@ watermelon
|
||||
insta::assert_snapshot!(
|
||||
env.render(&diag),
|
||||
@r"
|
||||
error: lint:test-diagnostic: main diagnostic message
|
||||
error[test-diagnostic]: main diagnostic message
|
||||
--> non-ascii:5:1
|
||||
|
|
||||
3 | ΔΔΔΔΔΔΔΔΔΔΔΔ
|
||||
@@ -933,7 +916,7 @@ watermelon
|
||||
insta::assert_snapshot!(
|
||||
env.render(&diag),
|
||||
@r"
|
||||
error: lint:test-diagnostic: main diagnostic message
|
||||
error[test-diagnostic]: main diagnostic message
|
||||
--> non-ascii:2:2
|
||||
|
|
||||
1 | ☃☃☃☃☃☃☃☃☃☃☃☃
|
||||
@@ -957,7 +940,7 @@ watermelon
|
||||
insta::assert_snapshot!(
|
||||
env.render(&diag),
|
||||
@r"
|
||||
error: lint:test-diagnostic: main diagnostic message
|
||||
error[test-diagnostic]: main diagnostic message
|
||||
--> animals:5:1
|
||||
|
|
||||
4 | dog
|
||||
@@ -974,7 +957,7 @@ watermelon
|
||||
insta::assert_snapshot!(
|
||||
env.render(&diag),
|
||||
@r"
|
||||
error: lint:test-diagnostic: main diagnostic message
|
||||
error[test-diagnostic]: main diagnostic message
|
||||
--> animals:5:1
|
||||
|
|
||||
5 | elephant
|
||||
@@ -989,7 +972,7 @@ watermelon
|
||||
insta::assert_snapshot!(
|
||||
env.render(&diag),
|
||||
@r"
|
||||
error: lint:test-diagnostic: main diagnostic message
|
||||
error[test-diagnostic]: main diagnostic message
|
||||
--> animals:1:1
|
||||
|
|
||||
1 | aardvark
|
||||
@@ -1006,7 +989,7 @@ watermelon
|
||||
insta::assert_snapshot!(
|
||||
env.render(&diag),
|
||||
@r"
|
||||
error: lint:test-diagnostic: main diagnostic message
|
||||
error[test-diagnostic]: main diagnostic message
|
||||
--> animals:11:1
|
||||
|
|
||||
9 | inchworm
|
||||
@@ -1023,7 +1006,7 @@ watermelon
|
||||
insta::assert_snapshot!(
|
||||
env.render(&diag),
|
||||
@r"
|
||||
error: lint:test-diagnostic: main diagnostic message
|
||||
error[test-diagnostic]: main diagnostic message
|
||||
--> animals:5:1
|
||||
|
|
||||
1 | aardvark
|
||||
@@ -1056,7 +1039,7 @@ watermelon
|
||||
insta::assert_snapshot!(
|
||||
env.render(&diag),
|
||||
@r"
|
||||
error: lint:test-diagnostic: main diagnostic message
|
||||
error[test-diagnostic]: main diagnostic message
|
||||
--> animals:1:1
|
||||
|
|
||||
1 | aardvark
|
||||
@@ -1100,7 +1083,7 @@ watermelon
|
||||
insta::assert_snapshot!(
|
||||
env.render(&diag),
|
||||
@r"
|
||||
error: lint:test-diagnostic: main diagnostic message
|
||||
error[test-diagnostic]: main diagnostic message
|
||||
--> animals:1:1
|
||||
|
|
||||
1 | aardvark
|
||||
@@ -1125,7 +1108,7 @@ watermelon
|
||||
insta::assert_snapshot!(
|
||||
env.render(&diag),
|
||||
@r"
|
||||
error: lint:test-diagnostic: main diagnostic message
|
||||
error[test-diagnostic]: main diagnostic message
|
||||
--> animals:1:1
|
||||
|
|
||||
1 | aardvark
|
||||
@@ -1153,7 +1136,7 @@ watermelon
|
||||
insta::assert_snapshot!(
|
||||
env.render(&diag),
|
||||
@r"
|
||||
error: lint:test-diagnostic: main diagnostic message
|
||||
error[test-diagnostic]: main diagnostic message
|
||||
--> animals:1:1
|
||||
|
|
||||
1 | aardvark
|
||||
@@ -1181,7 +1164,7 @@ watermelon
|
||||
insta::assert_snapshot!(
|
||||
env.render(&diag),
|
||||
@r"
|
||||
error: lint:test-diagnostic: main diagnostic message
|
||||
error[test-diagnostic]: main diagnostic message
|
||||
--> animals:1:1
|
||||
|
|
||||
1 | aardvark
|
||||
@@ -1206,7 +1189,7 @@ watermelon
|
||||
insta::assert_snapshot!(
|
||||
env.render(&diag),
|
||||
@r"
|
||||
error: lint:test-diagnostic: main diagnostic message
|
||||
error[test-diagnostic]: main diagnostic message
|
||||
--> animals:1:1
|
||||
|
|
||||
1 | aardvark
|
||||
@@ -1237,7 +1220,7 @@ watermelon
|
||||
insta::assert_snapshot!(
|
||||
env.render(&diag),
|
||||
@r"
|
||||
error: lint:test-diagnostic: main diagnostic message
|
||||
error[test-diagnostic]: main diagnostic message
|
||||
--> animals:1:1
|
||||
|
|
||||
1 | aardvark
|
||||
@@ -1275,7 +1258,7 @@ watermelon
|
||||
insta::assert_snapshot!(
|
||||
env.render(&diag),
|
||||
@r"
|
||||
error: lint:test-diagnostic: main diagnostic message
|
||||
error[test-diagnostic]: main diagnostic message
|
||||
--> spacey-animals:8:1
|
||||
|
|
||||
7 | dog
|
||||
@@ -1292,7 +1275,7 @@ watermelon
|
||||
insta::assert_snapshot!(
|
||||
env.render(&diag),
|
||||
@r"
|
||||
error: lint:test-diagnostic: main diagnostic message
|
||||
error[test-diagnostic]: main diagnostic message
|
||||
--> spacey-animals:12:1
|
||||
|
|
||||
11 | gorilla
|
||||
@@ -1310,7 +1293,7 @@ watermelon
|
||||
insta::assert_snapshot!(
|
||||
env.render(&diag),
|
||||
@r"
|
||||
error: lint:test-diagnostic: main diagnostic message
|
||||
error[test-diagnostic]: main diagnostic message
|
||||
--> spacey-animals:13:1
|
||||
|
|
||||
11 | gorilla
|
||||
@@ -1350,7 +1333,7 @@ watermelon
|
||||
insta::assert_snapshot!(
|
||||
env.render(&diag),
|
||||
@r"
|
||||
error: lint:test-diagnostic: main diagnostic message
|
||||
error[test-diagnostic]: main diagnostic message
|
||||
--> spacey-animals:3:1
|
||||
|
|
||||
3 | beetle
|
||||
@@ -1379,7 +1362,7 @@ watermelon
|
||||
insta::assert_snapshot!(
|
||||
env.render(&diag),
|
||||
@r"
|
||||
error: lint:test-diagnostic: main diagnostic message
|
||||
error[test-diagnostic]: main diagnostic message
|
||||
--> animals:3:1
|
||||
|
|
||||
1 | aardvark
|
||||
@@ -1416,7 +1399,7 @@ watermelon
|
||||
insta::assert_snapshot!(
|
||||
env.render(&diag),
|
||||
@r"
|
||||
error: lint:test-diagnostic: main diagnostic message
|
||||
error[test-diagnostic]: main diagnostic message
|
||||
--> animals:3:1
|
||||
|
|
||||
1 | aardvark
|
||||
@@ -1453,7 +1436,7 @@ watermelon
|
||||
insta::assert_snapshot!(
|
||||
env.render(&diag),
|
||||
@r"
|
||||
error: lint:test-diagnostic: main diagnostic message
|
||||
error[test-diagnostic]: main diagnostic message
|
||||
--> animals:3:1
|
||||
|
|
||||
1 | aardvark
|
||||
@@ -1481,7 +1464,7 @@ watermelon
|
||||
insta::assert_snapshot!(
|
||||
env.render(&diag),
|
||||
@r"
|
||||
error: lint:test-diagnostic: main diagnostic message
|
||||
error[test-diagnostic]: main diagnostic message
|
||||
--> animals:3:1
|
||||
|
|
||||
1 | aardvark
|
||||
@@ -1517,7 +1500,7 @@ watermelon
|
||||
insta::assert_snapshot!(
|
||||
env.render(&diag),
|
||||
@r"
|
||||
error: lint:test-diagnostic: main diagnostic message
|
||||
error[test-diagnostic]: main diagnostic message
|
||||
--> animals:3:1
|
||||
|
|
||||
1 | aardvark
|
||||
@@ -1556,7 +1539,7 @@ watermelon
|
||||
insta::assert_snapshot!(
|
||||
env.render(&diag),
|
||||
@r"
|
||||
error: lint:test-diagnostic: main diagnostic message
|
||||
error[test-diagnostic]: main diagnostic message
|
||||
--> animals:3:1
|
||||
|
|
||||
1 | aardvark
|
||||
@@ -1604,7 +1587,7 @@ watermelon
|
||||
insta::assert_snapshot!(
|
||||
env.render(&diag),
|
||||
@r"
|
||||
error: lint:test-diagnostic: main diagnostic message
|
||||
error[test-diagnostic]: main diagnostic message
|
||||
--> animals:3:1
|
||||
|
|
||||
1 | aardvark
|
||||
@@ -1640,7 +1623,7 @@ watermelon
|
||||
insta::assert_snapshot!(
|
||||
env.render(&diag),
|
||||
@r"
|
||||
error: lint:test-diagnostic: main diagnostic message
|
||||
error[test-diagnostic]: main diagnostic message
|
||||
--> animals:5:1
|
||||
|
|
||||
3 | canary
|
||||
@@ -1663,7 +1646,7 @@ watermelon
|
||||
insta::assert_snapshot!(
|
||||
env.render(&diag),
|
||||
@r"
|
||||
error: lint:test-diagnostic: main diagnostic message
|
||||
error[test-diagnostic]: main diagnostic message
|
||||
--> animals:5:1
|
||||
|
|
||||
3 | canary
|
||||
@@ -1683,7 +1666,7 @@ watermelon
|
||||
insta::assert_snapshot!(
|
||||
env.render(&diag),
|
||||
@r"
|
||||
error: lint:test-diagnostic: main diagnostic message
|
||||
error[test-diagnostic]: main diagnostic message
|
||||
--> animals:5:1
|
||||
|
|
||||
3 | canary
|
||||
@@ -1703,7 +1686,7 @@ watermelon
|
||||
insta::assert_snapshot!(
|
||||
env.render(&diag),
|
||||
@r"
|
||||
error: lint:test-diagnostic: main diagnostic message
|
||||
error[test-diagnostic]: main diagnostic message
|
||||
--> animals:5:4
|
||||
|
|
||||
3 | canary
|
||||
@@ -1725,7 +1708,7 @@ watermelon
|
||||
insta::assert_snapshot!(
|
||||
env.render(&diag),
|
||||
@r"
|
||||
error: lint:test-diagnostic: main diagnostic message
|
||||
error[test-diagnostic]: main diagnostic message
|
||||
--> animals:5:4
|
||||
|
|
||||
3 | canary
|
||||
@@ -1757,7 +1740,7 @@ watermelon
|
||||
insta::assert_snapshot!(
|
||||
env.render(&diag),
|
||||
@r"
|
||||
error: lint:test-diagnostic: main diagnostic message
|
||||
error[test-diagnostic]: main diagnostic message
|
||||
--> animals:4:1
|
||||
|
|
||||
2 | beetle
|
||||
@@ -1786,7 +1769,7 @@ watermelon
|
||||
insta::assert_snapshot!(
|
||||
env.render(&diag),
|
||||
@r"
|
||||
error: lint:test-diagnostic: main diagnostic message
|
||||
error[test-diagnostic]: main diagnostic message
|
||||
--> animals:4:1
|
||||
|
|
||||
2 | beetle
|
||||
@@ -1817,7 +1800,7 @@ watermelon
|
||||
insta::assert_snapshot!(
|
||||
env.render(&diag),
|
||||
@r"
|
||||
error: lint:test-diagnostic: main diagnostic message
|
||||
error[test-diagnostic]: main diagnostic message
|
||||
--> animals:5:1
|
||||
|
|
||||
3 | canary
|
||||
@@ -1852,7 +1835,7 @@ watermelon
|
||||
insta::assert_snapshot!(
|
||||
env.render(&diag),
|
||||
@r"
|
||||
error: lint:test-diagnostic: main diagnostic message
|
||||
error[test-diagnostic]: main diagnostic message
|
||||
--> animals:5:1
|
||||
|
|
||||
3 | canary
|
||||
@@ -1880,7 +1863,7 @@ watermelon
|
||||
insta::assert_snapshot!(
|
||||
env.render(&diag),
|
||||
@r"
|
||||
error: lint:test-diagnostic: main diagnostic message
|
||||
error[test-diagnostic]: main diagnostic message
|
||||
--> animals:5:1
|
||||
|
|
||||
3 | canary
|
||||
@@ -1912,7 +1895,7 @@ watermelon
|
||||
insta::assert_snapshot!(
|
||||
env.render(&diag),
|
||||
@r"
|
||||
error: lint:test-diagnostic: main diagnostic message
|
||||
error[test-diagnostic]: main diagnostic message
|
||||
--> animals:5:3
|
||||
|
|
||||
3 | canary
|
||||
@@ -1934,7 +1917,7 @@ watermelon
|
||||
insta::assert_snapshot!(
|
||||
env.render(&diag),
|
||||
@r"
|
||||
error: lint:test-diagnostic: main diagnostic message
|
||||
error[test-diagnostic]: main diagnostic message
|
||||
--> animals:5:3
|
||||
|
|
||||
3 | canary
|
||||
@@ -1967,7 +1950,7 @@ watermelon
|
||||
insta::assert_snapshot!(
|
||||
env.render(&diag),
|
||||
@r"
|
||||
error: lint:test-diagnostic: main diagnostic message
|
||||
error[test-diagnostic]: main diagnostic message
|
||||
--> animals:8:1
|
||||
|
|
||||
6 | finch
|
||||
@@ -2007,7 +1990,7 @@ watermelon
|
||||
insta::assert_snapshot!(
|
||||
env.render(&diag),
|
||||
@r"
|
||||
error: lint:test-diagnostic: main diagnostic message
|
||||
error[test-diagnostic]: main diagnostic message
|
||||
--> animals:5:1
|
||||
|
|
||||
5 | elephant
|
||||
@@ -2051,7 +2034,7 @@ watermelon
|
||||
insta::assert_snapshot!(
|
||||
env.render(&diag),
|
||||
@r"
|
||||
error: lint:test-diagnostic: main diagnostic message
|
||||
error[test-diagnostic]: main diagnostic message
|
||||
--> fruits:1:1
|
||||
|
|
||||
1 | apple
|
||||
@@ -2086,7 +2069,7 @@ watermelon
|
||||
insta::assert_snapshot!(
|
||||
env.render(&diag),
|
||||
@r"
|
||||
error: lint:test-diagnostic: main diagnostic message
|
||||
error[test-diagnostic]: main diagnostic message
|
||||
--> animals:11:1
|
||||
|
|
||||
11 | kangaroo
|
||||
@@ -2174,8 +2157,9 @@ watermelon
|
||||
fn span(&self, path: &str, line_offset_start: &str, line_offset_end: &str) -> Span {
|
||||
let span = self.path(path);
|
||||
|
||||
let text = source_text(&self.db, span.file());
|
||||
let line_index = line_index(&self.db, span.file());
|
||||
let file = span.expect_ty_file();
|
||||
let text = source_text(&self.db, file);
|
||||
let line_index = line_index(&self.db, file);
|
||||
let source = SourceCode::new(text.as_str(), &line_index);
|
||||
|
||||
let (line_start, offset_start) = parse_line_offset(line_offset_start);
|
||||
@@ -2237,7 +2221,7 @@ watermelon
|
||||
///
|
||||
/// (This will set the "printed" flag on `Diagnostic`.)
|
||||
fn render(&self, diag: &Diagnostic) -> String {
|
||||
diag.display(&self.db, &self.config).to_string()
|
||||
diag.display(&self.db.upcast(), &self.config).to_string()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -277,7 +277,7 @@ impl std::panic::RefUnwindSafe for Files {}
|
||||
#[salsa::input]
|
||||
pub struct File {
|
||||
/// The path of the file (immutable).
|
||||
#[return_ref]
|
||||
#[returns(ref)]
|
||||
pub path: FilePath,
|
||||
|
||||
/// The unix permissions of the file. Only supported on unix systems. Always `None` on Windows
|
||||
|
||||
@@ -19,8 +19,8 @@ use crate::Db;
|
||||
#[salsa::input(debug)]
|
||||
pub struct FileRoot {
|
||||
/// The path of a root is guaranteed to never change.
|
||||
#[return_ref]
|
||||
path_buf: SystemPathBuf,
|
||||
#[returns(deref)]
|
||||
pub path: SystemPathBuf,
|
||||
|
||||
/// The kind of the root at the time of its creation.
|
||||
kind_at_time_of_creation: FileRootKind,
|
||||
@@ -32,10 +32,6 @@ pub struct FileRoot {
|
||||
}
|
||||
|
||||
impl FileRoot {
|
||||
pub fn path(self, db: &dyn Db) -> &SystemPath {
|
||||
self.path_buf(db)
|
||||
}
|
||||
|
||||
pub fn durability(self, db: &dyn Db) -> salsa::Durability {
|
||||
self.kind_at_time_of_creation(db).durability()
|
||||
}
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
use std::hash::BuildHasherDefault;
|
||||
|
||||
use ruff_python_ast::PythonVersion;
|
||||
use rustc_hash::FxHasher;
|
||||
|
||||
use crate::files::Files;
|
||||
use crate::system::System;
|
||||
use crate::vendored::VendoredFileSystem;
|
||||
use ruff_python_ast::PythonVersion;
|
||||
use rustc_hash::FxHasher;
|
||||
use std::hash::BuildHasherDefault;
|
||||
use std::num::NonZeroUsize;
|
||||
|
||||
pub mod diagnostic;
|
||||
pub mod display;
|
||||
@@ -37,15 +36,40 @@ pub trait Upcast<T: ?Sized> {
|
||||
fn upcast_mut(&mut self) -> &mut T;
|
||||
}
|
||||
|
||||
/// Returns the maximum number of tasks that ty is allowed
|
||||
/// to process in parallel.
|
||||
///
|
||||
/// Returns [`std::thread::available_parallelism`], unless the environment
|
||||
/// variable `TY_MAX_PARALLELISM` or `RAYON_NUM_THREADS` is set. `TY_MAX_PARALLELISM` takes
|
||||
/// precedence over `RAYON_NUM_THREADS`.
|
||||
///
|
||||
/// Falls back to `1` if `available_parallelism` is not available.
|
||||
///
|
||||
/// Setting `TY_MAX_PARALLELISM` to `2` only restricts the number of threads that ty spawns
|
||||
/// to process work in parallel. For example, to index a directory or checking the files of a project.
|
||||
/// ty can still spawn more threads for other tasks, e.g. to wait for a Ctrl+C signal or
|
||||
/// watching the files for changes.
|
||||
pub fn max_parallelism() -> NonZeroUsize {
|
||||
std::env::var("TY_MAX_PARALLELISM")
|
||||
.or_else(|_| std::env::var("RAYON_NUM_THREADS"))
|
||||
.ok()
|
||||
.and_then(|s| s.parse().ok())
|
||||
.unwrap_or_else(|| {
|
||||
std::thread::available_parallelism().unwrap_or_else(|_| NonZeroUsize::new(1).unwrap())
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::sync::Arc;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use crate::files::Files;
|
||||
use crate::system::TestSystem;
|
||||
use crate::system::{DbWithTestSystem, System};
|
||||
use crate::vendored::VendoredFileSystem;
|
||||
use crate::Db;
|
||||
use crate::{Db, Upcast};
|
||||
|
||||
type Events = Arc<Mutex<Vec<salsa::Event>>>;
|
||||
|
||||
/// Database that can be used for testing.
|
||||
///
|
||||
@@ -57,36 +81,37 @@ mod tests {
|
||||
files: Files,
|
||||
system: TestSystem,
|
||||
vendored: VendoredFileSystem,
|
||||
events: Arc<std::sync::Mutex<Vec<salsa::Event>>>,
|
||||
events: Events,
|
||||
}
|
||||
|
||||
impl TestDb {
|
||||
pub(crate) fn new() -> Self {
|
||||
let events = Events::default();
|
||||
Self {
|
||||
storage: salsa::Storage::default(),
|
||||
storage: salsa::Storage::new(Some(Box::new({
|
||||
let events = events.clone();
|
||||
move |event| {
|
||||
tracing::trace!("event: {:?}", event);
|
||||
let mut events = events.lock().unwrap();
|
||||
events.push(event);
|
||||
}
|
||||
}))),
|
||||
system: TestSystem::default(),
|
||||
vendored: VendoredFileSystem::default(),
|
||||
events: std::sync::Arc::default(),
|
||||
events,
|
||||
files: Files::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Empties the internal store of salsa events that have been emitted,
|
||||
/// and returns them as a `Vec` (equivalent to [`std::mem::take`]).
|
||||
///
|
||||
/// ## Panics
|
||||
/// If there are pending database snapshots.
|
||||
pub(crate) fn take_salsa_events(&mut self) -> Vec<salsa::Event> {
|
||||
let inner = Arc::get_mut(&mut self.events)
|
||||
.expect("expected no pending salsa database snapshots.");
|
||||
let mut events = self.events.lock().unwrap();
|
||||
|
||||
std::mem::take(inner.get_mut().unwrap())
|
||||
std::mem::take(&mut *events)
|
||||
}
|
||||
|
||||
/// Clears the emitted salsa events.
|
||||
///
|
||||
/// ## Panics
|
||||
/// If there are pending database snapshots.
|
||||
pub(crate) fn clear_salsa_events(&mut self) {
|
||||
self.take_salsa_events();
|
||||
}
|
||||
@@ -111,7 +136,16 @@ mod tests {
|
||||
}
|
||||
|
||||
fn python_version(&self) -> ruff_python_ast::PythonVersion {
|
||||
ruff_python_ast::PythonVersion::latest()
|
||||
ruff_python_ast::PythonVersion::latest_ty()
|
||||
}
|
||||
}
|
||||
|
||||
impl Upcast<dyn Db> for TestDb {
|
||||
fn upcast(&self) -> &(dyn Db + 'static) {
|
||||
self
|
||||
}
|
||||
fn upcast_mut(&mut self) -> &mut (dyn Db + 'static) {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,12 +160,5 @@ mod tests {
|
||||
}
|
||||
|
||||
#[salsa::db]
|
||||
impl salsa::Database for TestDb {
|
||||
fn salsa_event(&self, event: &dyn Fn() -> salsa::Event) {
|
||||
let event = event();
|
||||
tracing::trace!("event: {:?}", event);
|
||||
let mut events = self.events.lock().unwrap();
|
||||
events.push(event);
|
||||
}
|
||||
}
|
||||
impl salsa::Database for TestDb {}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ use crate::Db;
|
||||
/// reflected in the changed AST offsets.
|
||||
/// The other reason is that Ruff's AST doesn't implement `Eq` which Sala requires
|
||||
/// for determining if a query result is unchanged.
|
||||
#[salsa::tracked(return_ref, no_eq)]
|
||||
#[salsa::tracked(returns(ref), no_eq)]
|
||||
pub fn parsed_module(db: &dyn Db, file: File) -> ParsedModule {
|
||||
let _span = tracing::trace_span!("parsed_module", ?file).entered();
|
||||
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
use filetime::FileTime;
|
||||
use ruff_notebook::{Notebook, NotebookError};
|
||||
use rustc_hash::FxHashSet;
|
||||
use std::panic::RefUnwindSafe;
|
||||
use std::sync::Arc;
|
||||
use std::{any::Any, path::PathBuf};
|
||||
|
||||
use crate::system::{
|
||||
CaseSensitivity, DirectoryEntry, FileType, GlobError, GlobErrorKind, Metadata, Result, System,
|
||||
SystemPath, SystemPathBuf, SystemVirtualPath, WritableSystem,
|
||||
};
|
||||
|
||||
use super::walk_directory::{
|
||||
self, DirectoryWalker, WalkDirectoryBuilder, WalkDirectoryConfiguration,
|
||||
WalkDirectoryVisitorBuilder, WalkState,
|
||||
};
|
||||
use crate::max_parallelism;
|
||||
use crate::system::{
|
||||
CaseSensitivity, DirectoryEntry, FileType, GlobError, GlobErrorKind, Metadata, Result, System,
|
||||
SystemPath, SystemPathBuf, SystemVirtualPath, WritableSystem,
|
||||
};
|
||||
use filetime::FileTime;
|
||||
use ruff_notebook::{Notebook, NotebookError};
|
||||
use rustc_hash::FxHashSet;
|
||||
use std::num::NonZeroUsize;
|
||||
use std::panic::RefUnwindSafe;
|
||||
use std::sync::Arc;
|
||||
use std::{any::Any, path::PathBuf};
|
||||
|
||||
/// A system implementation that uses the OS file system.
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -50,7 +50,6 @@ impl OsSystem {
|
||||
|
||||
Self {
|
||||
// Spreading `..Default` because it isn't possible to feature gate the initializer of a single field.
|
||||
#[allow(clippy::needless_update)]
|
||||
inner: Arc::new(OsSystemInner {
|
||||
cwd: cwd.to_path_buf(),
|
||||
case_sensitivity,
|
||||
@@ -427,11 +426,7 @@ impl DirectoryWalker for OsDirectoryWalker {
|
||||
builder.add(additional_path.as_std_path());
|
||||
}
|
||||
|
||||
builder.threads(
|
||||
std::thread::available_parallelism()
|
||||
.map_or(1, std::num::NonZeroUsize::get)
|
||||
.min(12),
|
||||
);
|
||||
builder.threads(max_parallelism().min(NonZeroUsize::new(12).unwrap()).get());
|
||||
|
||||
builder.build_parallel().run(|| {
|
||||
let mut visitor = visitor_builder.build();
|
||||
|
||||
@@ -35,7 +35,7 @@ impl WalkDirectoryBuilder {
|
||||
/// Each additional path is traversed recursively.
|
||||
/// This should be preferred over building multiple
|
||||
/// walkers since it enables reusing resources.
|
||||
#[allow(clippy::should_implement_trait)]
|
||||
#[expect(clippy::should_implement_trait)]
|
||||
pub fn add(mut self, path: impl AsRef<SystemPath>) -> Self {
|
||||
self.paths.push(path.as_ref().to_path_buf());
|
||||
self
|
||||
|
||||
@@ -107,7 +107,7 @@ fn query_name<Q>(_query: &Q) -> &'static str {
|
||||
.unwrap_or(full_qualified_query_name)
|
||||
}
|
||||
|
||||
/// Sets up logging for the current thread. It captures all `red_knot` and `ruff` events.
|
||||
/// Sets up logging for the current thread. It captures all `ty` and `ruff` events.
|
||||
///
|
||||
/// Useful for capturing the tracing output in a failing test.
|
||||
///
|
||||
@@ -128,7 +128,7 @@ pub fn setup_logging() -> LoggingGuard {
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use ruff_db::testing::setup_logging_with_filter;
|
||||
/// let _logging = setup_logging_with_filter("red_knot_module_resolver::resolver");
|
||||
/// let _logging = setup_logging_with_filter("ty_module_resolver::resolver");
|
||||
/// ```
|
||||
///
|
||||
/// # Filter
|
||||
@@ -148,11 +148,7 @@ impl LoggingBuilder {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
filter: EnvFilter::default()
|
||||
.add_directive(
|
||||
"red_knot=trace"
|
||||
.parse()
|
||||
.expect("Hardcoded directive to be valid"),
|
||||
)
|
||||
.add_directive("ty=trace".parse().expect("Hardcoded directive to be valid"))
|
||||
.add_directive(
|
||||
"ruff=trace"
|
||||
.parse()
|
||||
|
||||
@@ -172,7 +172,7 @@ impl Default for VendoredFileSystem {
|
||||
/// that users of the `VendoredFileSystem` could realistically need.
|
||||
/// For debugging purposes, however, we want to have all information
|
||||
/// available.
|
||||
#[allow(unused)]
|
||||
#[expect(unused)]
|
||||
#[derive(Debug)]
|
||||
struct ZipFileDebugInfo {
|
||||
crc32_hash: u32,
|
||||
|
||||
@@ -11,12 +11,14 @@ repository = { workspace = true }
|
||||
license = { workspace = true }
|
||||
|
||||
[dependencies]
|
||||
red_knot_project = { workspace = true, features = ["schemars"] }
|
||||
ty = { workspace = true }
|
||||
ty_project = { workspace = true, features = ["schemars"] }
|
||||
ruff = { workspace = true }
|
||||
ruff_diagnostics = { workspace = true }
|
||||
ruff_formatter = { workspace = true }
|
||||
ruff_linter = { workspace = true, features = ["schemars"] }
|
||||
ruff_notebook = { workspace = true }
|
||||
ruff_options_metadata = { workspace = true }
|
||||
ruff_python_ast = { workspace = true }
|
||||
ruff_python_codegen = { workspace = true }
|
||||
ruff_python_formatter = { workspace = true }
|
||||
@@ -31,6 +33,7 @@ imara-diff = { workspace = true }
|
||||
indicatif = { workspace = true }
|
||||
itertools = { workspace = true }
|
||||
libcst = { workspace = true }
|
||||
markdown = { version = "1.0.0" }
|
||||
pretty_assertions = { workspace = true }
|
||||
rayon = { workspace = true }
|
||||
regex = { workspace = true }
|
||||
@@ -44,6 +47,7 @@ toml = { workspace = true, features = ["parse"] }
|
||||
tracing = { workspace = true }
|
||||
tracing-indicatif = { workspace = true }
|
||||
tracing-subscriber = { workspace = true, features = ["env-filter"] }
|
||||
url = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
indoc = { workspace = true }
|
||||
|
||||
@@ -63,7 +63,6 @@ fn find_pyproject_config(
|
||||
}
|
||||
|
||||
/// Find files that ruff would check so we can format them. Adapted from `ruff`.
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn ruff_check_paths<'a>(
|
||||
pyproject_config: &'a PyprojectConfig,
|
||||
cli: &FormatArguments,
|
||||
@@ -135,12 +134,12 @@ impl Statistics {
|
||||
}
|
||||
|
||||
/// We currently prefer the similarity index, but i'd like to keep this around
|
||||
#[allow(clippy::cast_precision_loss, unused)]
|
||||
#[expect(clippy::cast_precision_loss, unused)]
|
||||
pub(crate) fn jaccard_index(&self) -> f32 {
|
||||
self.intersection as f32 / (self.black_input + self.ruff_output + self.intersection) as f32
|
||||
}
|
||||
|
||||
#[allow(clippy::cast_precision_loss)]
|
||||
#[expect(clippy::cast_precision_loss)]
|
||||
pub(crate) fn similarity_index(&self) -> f32 {
|
||||
self.intersection as f32 / (self.black_input + self.intersection) as f32
|
||||
}
|
||||
@@ -177,7 +176,7 @@ pub(crate) enum Format {
|
||||
Full,
|
||||
}
|
||||
|
||||
#[allow(clippy::struct_excessive_bools)]
|
||||
#[expect(clippy::struct_excessive_bools)]
|
||||
#[derive(clap::Args)]
|
||||
pub(crate) struct Args {
|
||||
/// Like `ruff check`'s files. See `--multi-project` if you want to format an ecosystem
|
||||
@@ -222,7 +221,7 @@ pub(crate) struct Args {
|
||||
#[arg(long)]
|
||||
pub(crate) files_with_errors: Option<u32>,
|
||||
#[clap(flatten)]
|
||||
#[allow(clippy::struct_field_names)]
|
||||
#[expect(clippy::struct_field_names)]
|
||||
pub(crate) log_level_args: LogLevelArgs,
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,10 @@
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
use crate::{generate_cli_help, generate_docs, generate_json_schema, generate_knot_schema};
|
||||
use crate::{
|
||||
generate_cli_help, generate_docs, generate_json_schema, generate_ty_cli_reference,
|
||||
generate_ty_options, generate_ty_rules, generate_ty_schema,
|
||||
};
|
||||
|
||||
pub(crate) const REGENERATE_ALL_COMMAND: &str = "cargo dev generate-all";
|
||||
|
||||
@@ -33,10 +36,13 @@ impl Mode {
|
||||
|
||||
pub(crate) fn main(args: &Args) -> Result<()> {
|
||||
generate_json_schema::main(&generate_json_schema::Args { mode: args.mode })?;
|
||||
generate_knot_schema::main(&generate_knot_schema::Args { mode: args.mode })?;
|
||||
generate_ty_schema::main(&generate_ty_schema::Args { mode: args.mode })?;
|
||||
generate_cli_help::main(&generate_cli_help::Args { mode: args.mode })?;
|
||||
generate_docs::main(&generate_docs::Args {
|
||||
dry_run: args.mode.is_dry_run(),
|
||||
})?;
|
||||
generate_ty_options::main(&generate_ty_options::Args { mode: args.mode })?;
|
||||
generate_ty_rules::main(&generate_ty_rules::Args { mode: args.mode })?;
|
||||
generate_ty_cli_reference::main(&generate_ty_cli_reference::Args { mode: args.mode })?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
//! Generate CLI help.
|
||||
#![allow(clippy::print_stdout)]
|
||||
|
||||
use std::path::PathBuf;
|
||||
use std::{fs, str};
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
//! Generate Markdown documentation for applicable rules.
|
||||
#![allow(clippy::print_stdout, clippy::print_stderr)]
|
||||
|
||||
use std::collections::HashSet;
|
||||
use std::fmt::Write as _;
|
||||
@@ -13,8 +12,8 @@ use strum::IntoEnumIterator;
|
||||
|
||||
use ruff_diagnostics::FixAvailability;
|
||||
use ruff_linter::registry::{Linter, Rule, RuleNamespace};
|
||||
use ruff_options_metadata::{OptionEntry, OptionsMetadata};
|
||||
use ruff_workspace::options::Options;
|
||||
use ruff_workspace::options_base::{OptionEntry, OptionsMetadata};
|
||||
|
||||
use crate::ROOT_DIR;
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
#![allow(clippy::print_stdout, clippy::print_stderr)]
|
||||
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
use itertools::Itertools;
|
||||
use std::fmt::Write;
|
||||
|
||||
use ruff_options_metadata::{OptionField, OptionSet, OptionsMetadata, Visit};
|
||||
use ruff_python_trivia::textwrap;
|
||||
use ruff_workspace::options::Options;
|
||||
use ruff_workspace::options_base::{OptionField, OptionSet, OptionsMetadata, Visit};
|
||||
|
||||
pub(crate) fn generate() -> String {
|
||||
let mut output = String::new();
|
||||
|
||||
@@ -11,8 +11,8 @@ use strum::IntoEnumIterator;
|
||||
use ruff_diagnostics::FixAvailability;
|
||||
use ruff_linter::registry::{Linter, Rule, RuleNamespace};
|
||||
use ruff_linter::upstream_categories::UpstreamCategoryAndPrefix;
|
||||
use ruff_options_metadata::OptionsMetadata;
|
||||
use ruff_workspace::options::Options;
|
||||
use ruff_workspace::options_base::OptionsMetadata;
|
||||
|
||||
const FIX_SYMBOL: &str = "🛠️";
|
||||
const PREVIEW_SYMBOL: &str = "🧪";
|
||||
@@ -34,7 +34,6 @@ fn generate_table(table_out: &mut String, rules: impl IntoIterator<Item = Rule>,
|
||||
RuleGroup::Deprecated => {
|
||||
format!("<span title='Rule has been deprecated'>{WARNING_SYMBOL}</span>")
|
||||
}
|
||||
#[allow(deprecated)]
|
||||
RuleGroup::Preview => {
|
||||
format!("<span title='Rule is in preview'>{PREVIEW_SYMBOL}</span>")
|
||||
}
|
||||
@@ -78,7 +77,7 @@ fn generate_table(table_out: &mut String, rules: impl IntoIterator<Item = Rule>,
|
||||
se = "</span>";
|
||||
}
|
||||
|
||||
#[allow(clippy::or_fun_call)]
|
||||
#[expect(clippy::or_fun_call)]
|
||||
let _ = write!(
|
||||
table_out,
|
||||
"| {ss}{0}{1}{se} {{ #{0}{1} }} | {ss}{2}{se} | {ss}{3}{se} | {ss}{4}{se} |",
|
||||
|
||||
334
crates/ruff_dev/src/generate_ty_cli_reference.rs
Normal file
334
crates/ruff_dev/src/generate_ty_cli_reference.rs
Normal file
@@ -0,0 +1,334 @@
|
||||
//! Generate a Markdown-compatible reference for the ty command-line interface.
|
||||
use std::cmp::max;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use clap::{Command, CommandFactory};
|
||||
use itertools::Itertools;
|
||||
use pretty_assertions::StrComparison;
|
||||
|
||||
use crate::generate_all::{Mode, REGENERATE_ALL_COMMAND};
|
||||
use crate::ROOT_DIR;
|
||||
|
||||
use ty::Cli;
|
||||
|
||||
const SHOW_HIDDEN_COMMANDS: &[&str] = &["generate-shell-completion"];
|
||||
|
||||
#[derive(clap::Args)]
|
||||
pub(crate) struct Args {
|
||||
#[arg(long, default_value_t, value_enum)]
|
||||
pub(crate) mode: Mode,
|
||||
}
|
||||
|
||||
pub(crate) fn main(args: &Args) -> Result<()> {
|
||||
let reference_string = generate();
|
||||
let filename = "crates/ty/docs/cli.md";
|
||||
let reference_path = PathBuf::from(ROOT_DIR).join(filename);
|
||||
|
||||
match args.mode {
|
||||
Mode::DryRun => {
|
||||
println!("{reference_string}");
|
||||
}
|
||||
Mode::Check => {
|
||||
match std::fs::read_to_string(reference_path) {
|
||||
Ok(current) => {
|
||||
if current == reference_string {
|
||||
println!("Up-to-date: {filename}");
|
||||
} else {
|
||||
let comparison = StrComparison::new(¤t, &reference_string);
|
||||
bail!("{filename} changed, please run `{REGENERATE_ALL_COMMAND}`:\n{comparison}");
|
||||
}
|
||||
}
|
||||
Err(err) if err.kind() == std::io::ErrorKind::NotFound => {
|
||||
bail!("{filename} not found, please run `{REGENERATE_ALL_COMMAND}`");
|
||||
}
|
||||
Err(err) => {
|
||||
bail!("{filename} changed, please run `{REGENERATE_ALL_COMMAND}`:\n{err}");
|
||||
}
|
||||
}
|
||||
}
|
||||
Mode::Write => match std::fs::read_to_string(&reference_path) {
|
||||
Ok(current) => {
|
||||
if current == reference_string {
|
||||
println!("Up-to-date: {filename}");
|
||||
} else {
|
||||
println!("Updating: {filename}");
|
||||
std::fs::write(reference_path, reference_string.as_bytes())?;
|
||||
}
|
||||
}
|
||||
Err(err) if err.kind() == std::io::ErrorKind::NotFound => {
|
||||
println!("Updating: {filename}");
|
||||
std::fs::write(reference_path, reference_string.as_bytes())?;
|
||||
}
|
||||
Err(err) => {
|
||||
bail!("{filename} changed, please run `cargo dev generate-cli-reference`:\n{err}");
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn generate() -> String {
|
||||
let mut output = String::new();
|
||||
|
||||
let mut ty = Cli::command();
|
||||
|
||||
// It is very important to build the command before beginning inspection or subcommands
|
||||
// will be missing all of the propagated options.
|
||||
ty.build();
|
||||
|
||||
let mut parents = Vec::new();
|
||||
|
||||
output.push_str("# CLI Reference\n\n");
|
||||
generate_command(&mut output, &ty, &mut parents);
|
||||
|
||||
output
|
||||
}
|
||||
|
||||
#[allow(clippy::format_push_string)]
|
||||
fn generate_command<'a>(output: &mut String, command: &'a Command, parents: &mut Vec<&'a Command>) {
|
||||
if command.is_hide_set() && !SHOW_HIDDEN_COMMANDS.contains(&command.get_name()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Generate the command header.
|
||||
let name = if parents.is_empty() {
|
||||
command.get_name().to_string()
|
||||
} else {
|
||||
format!(
|
||||
"{} {}",
|
||||
parents.iter().map(|cmd| cmd.get_name()).join(" "),
|
||||
command.get_name()
|
||||
)
|
||||
};
|
||||
|
||||
// Display the top-level `ty` command at the same level as its children
|
||||
let level = max(2, parents.len() + 1);
|
||||
output.push_str(&format!("{} {name}\n\n", "#".repeat(level)));
|
||||
|
||||
// Display the command description.
|
||||
if let Some(about) = command.get_long_about().or_else(|| command.get_about()) {
|
||||
output.push_str(&about.to_string());
|
||||
output.push_str("\n\n");
|
||||
}
|
||||
|
||||
// Display the usage
|
||||
{
|
||||
// This appears to be the simplest way to get rendered usage from Clap,
|
||||
// it is complicated to render it manually. It's annoying that it
|
||||
// requires a mutable reference but it doesn't really matter.
|
||||
let mut command = command.clone();
|
||||
output.push_str("<h3 class=\"cli-reference\">Usage</h3>\n\n");
|
||||
output.push_str(&format!(
|
||||
"```\n{}\n```",
|
||||
command
|
||||
.render_usage()
|
||||
.to_string()
|
||||
.trim_start_matches("Usage: "),
|
||||
));
|
||||
output.push_str("\n\n");
|
||||
}
|
||||
|
||||
if command.get_name() == "help" {
|
||||
return;
|
||||
}
|
||||
|
||||
// Display a list of child commands
|
||||
let mut subcommands = command.get_subcommands().peekable();
|
||||
let has_subcommands = subcommands.peek().is_some();
|
||||
if has_subcommands {
|
||||
output.push_str("<h3 class=\"cli-reference\">Commands</h3>\n\n");
|
||||
output.push_str("<dl class=\"cli-reference\">");
|
||||
|
||||
for subcommand in subcommands {
|
||||
if subcommand.is_hide_set() {
|
||||
continue;
|
||||
}
|
||||
let subcommand_name = format!("{name} {}", subcommand.get_name());
|
||||
output.push_str(&format!(
|
||||
"<dt><a href=\"#{}\"><code>{subcommand_name}</code></a></dt>",
|
||||
subcommand_name.replace(' ', "-")
|
||||
));
|
||||
if let Some(about) = subcommand.get_about() {
|
||||
output.push_str(&format!(
|
||||
"<dd>{}</dd>\n",
|
||||
markdown::to_html(&about.to_string())
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
output.push_str("</dl>\n\n");
|
||||
}
|
||||
|
||||
// Do not display options for commands with children
|
||||
if !has_subcommands {
|
||||
let name_key = name.replace(' ', "-");
|
||||
|
||||
// Display positional arguments
|
||||
let mut arguments = command
|
||||
.get_positionals()
|
||||
.filter(|arg| !arg.is_hide_set())
|
||||
.peekable();
|
||||
|
||||
if arguments.peek().is_some() {
|
||||
output.push_str("<h3 class=\"cli-reference\">Arguments</h3>\n\n");
|
||||
output.push_str("<dl class=\"cli-reference\">");
|
||||
|
||||
for arg in arguments {
|
||||
let id = format!("{name_key}--{}", arg.get_id());
|
||||
output.push_str(&format!("<dt id=\"{id}\">"));
|
||||
output.push_str(&format!(
|
||||
"<a href=\"#{id}\"><code>{}</code></a>",
|
||||
arg.get_id().to_string().to_uppercase(),
|
||||
));
|
||||
output.push_str("</dt>");
|
||||
if let Some(help) = arg.get_long_help().or_else(|| arg.get_help()) {
|
||||
output.push_str("<dd>");
|
||||
output.push_str(&format!("{}\n", markdown::to_html(&help.to_string())));
|
||||
output.push_str("</dd>");
|
||||
}
|
||||
}
|
||||
|
||||
output.push_str("</dl>\n\n");
|
||||
}
|
||||
|
||||
// Display options and flags
|
||||
let mut options = command
|
||||
.get_arguments()
|
||||
.filter(|arg| !arg.is_positional())
|
||||
.filter(|arg| !arg.is_hide_set())
|
||||
.sorted_by_key(|arg| arg.get_id())
|
||||
.peekable();
|
||||
|
||||
if options.peek().is_some() {
|
||||
output.push_str("<h3 class=\"cli-reference\">Options</h3>\n\n");
|
||||
output.push_str("<dl class=\"cli-reference\">");
|
||||
for opt in options {
|
||||
let Some(long) = opt.get_long() else { continue };
|
||||
let id = format!("{name_key}--{long}");
|
||||
|
||||
output.push_str(&format!("<dt id=\"{id}\">"));
|
||||
output.push_str(&format!("<a href=\"#{id}\"><code>--{long}</code></a>"));
|
||||
for long_alias in opt.get_all_aliases().into_iter().flatten() {
|
||||
output.push_str(&format!(", <code>--{long_alias}</code>"));
|
||||
}
|
||||
if let Some(short) = opt.get_short() {
|
||||
output.push_str(&format!(", <code>-{short}</code>"));
|
||||
}
|
||||
for short_alias in opt.get_all_short_aliases().into_iter().flatten() {
|
||||
output.push_str(&format!(", <code>-{short_alias}</code>"));
|
||||
}
|
||||
|
||||
// Re-implements private `Arg::is_takes_value_set` used in `Command::get_opts`
|
||||
if opt
|
||||
.get_num_args()
|
||||
.unwrap_or_else(|| 1.into())
|
||||
.takes_values()
|
||||
{
|
||||
if let Some(values) = opt.get_value_names() {
|
||||
for value in values {
|
||||
output.push_str(&format!(
|
||||
" <i>{}</i>",
|
||||
value.to_lowercase().replace('_', "-")
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
output.push_str("</dt>");
|
||||
if let Some(help) = opt.get_long_help().or_else(|| opt.get_help()) {
|
||||
output.push_str("<dd>");
|
||||
output.push_str(&format!("{}\n", markdown::to_html(&help.to_string())));
|
||||
emit_env_option(opt, output);
|
||||
emit_default_option(opt, output);
|
||||
emit_possible_options(opt, output);
|
||||
output.push_str("</dd>");
|
||||
}
|
||||
}
|
||||
|
||||
output.push_str("</dl>");
|
||||
}
|
||||
|
||||
output.push_str("\n\n");
|
||||
}
|
||||
|
||||
parents.push(command);
|
||||
|
||||
// Recurse to all of the subcommands.
|
||||
for subcommand in command.get_subcommands() {
|
||||
generate_command(output, subcommand, parents);
|
||||
}
|
||||
|
||||
parents.pop();
|
||||
}
|
||||
|
||||
fn emit_env_option(opt: &clap::Arg, output: &mut String) {
|
||||
if opt.is_hide_env_set() {
|
||||
return;
|
||||
}
|
||||
if let Some(env) = opt.get_env() {
|
||||
output.push_str(&markdown::to_html(&format!(
|
||||
"May also be set with the `{}` environment variable.",
|
||||
env.to_string_lossy()
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
fn emit_default_option(opt: &clap::Arg, output: &mut String) {
|
||||
if opt.is_hide_default_value_set() || !opt.get_num_args().expect("built").takes_values() {
|
||||
return;
|
||||
}
|
||||
|
||||
let values = opt.get_default_values();
|
||||
if !values.is_empty() {
|
||||
let value = format!(
|
||||
"\n[default: {}]",
|
||||
opt.get_default_values()
|
||||
.iter()
|
||||
.map(|s| s.to_string_lossy())
|
||||
.join(",")
|
||||
);
|
||||
output.push_str(&markdown::to_html(&value));
|
||||
}
|
||||
}
|
||||
|
||||
fn emit_possible_options(opt: &clap::Arg, output: &mut String) {
|
||||
if opt.is_hide_possible_values_set() {
|
||||
return;
|
||||
}
|
||||
|
||||
let values = opt.get_possible_values();
|
||||
if !values.is_empty() {
|
||||
let value = format!(
|
||||
"\nPossible values:\n{}",
|
||||
values
|
||||
.into_iter()
|
||||
.filter(|value| !value.is_hide_set())
|
||||
.map(|value| {
|
||||
let name = value.get_name();
|
||||
value.get_help().map_or_else(
|
||||
|| format!(" - `{name}`"),
|
||||
|help| format!(" - `{name}`: {help}"),
|
||||
)
|
||||
})
|
||||
.collect_vec()
|
||||
.join("\n"),
|
||||
);
|
||||
output.push_str(&markdown::to_html(&value));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
use crate::generate_all::Mode;
|
||||
|
||||
use super::{main, Args};
|
||||
|
||||
#[test]
|
||||
fn ty_cli_reference_is_up_to_date() -> Result<()> {
|
||||
main(&Args { mode: Mode::Check })
|
||||
}
|
||||
}
|
||||
257
crates/ruff_dev/src/generate_ty_options.rs
Normal file
257
crates/ruff_dev/src/generate_ty_options.rs
Normal file
@@ -0,0 +1,257 @@
|
||||
//! Generate a Markdown-compatible listing of configuration options for `pyproject.toml`.
|
||||
|
||||
use anyhow::bail;
|
||||
use itertools::Itertools;
|
||||
use pretty_assertions::StrComparison;
|
||||
use std::{fmt::Write, path::PathBuf};
|
||||
|
||||
use ruff_options_metadata::{OptionField, OptionSet, OptionsMetadata, Visit};
|
||||
use ty_project::metadata::Options;
|
||||
|
||||
use crate::{
|
||||
generate_all::{Mode, REGENERATE_ALL_COMMAND},
|
||||
ROOT_DIR,
|
||||
};
|
||||
|
||||
#[derive(clap::Args)]
|
||||
pub(crate) struct Args {
|
||||
/// Write the generated table to stdout (rather than to `crates/ty/docs/configuration.md`).
|
||||
#[arg(long, default_value_t, value_enum)]
|
||||
pub(crate) mode: Mode,
|
||||
}
|
||||
|
||||
pub(crate) fn main(args: &Args) -> anyhow::Result<()> {
|
||||
let mut output = String::new();
|
||||
let file_name = "crates/ty/docs/configuration.md";
|
||||
let markdown_path = PathBuf::from(ROOT_DIR).join(file_name);
|
||||
|
||||
generate_set(
|
||||
&mut output,
|
||||
Set::Toplevel(Options::metadata()),
|
||||
&mut Vec::new(),
|
||||
);
|
||||
|
||||
match args.mode {
|
||||
Mode::DryRun => {
|
||||
println!("{output}");
|
||||
}
|
||||
Mode::Check => {
|
||||
let current = std::fs::read_to_string(&markdown_path)?;
|
||||
if output == current {
|
||||
println!("Up-to-date: {file_name}",);
|
||||
} else {
|
||||
let comparison = StrComparison::new(¤t, &output);
|
||||
bail!("{file_name} changed, please run `{REGENERATE_ALL_COMMAND}`:\n{comparison}",);
|
||||
}
|
||||
}
|
||||
Mode::Write => {
|
||||
let current = std::fs::read_to_string(&markdown_path)?;
|
||||
if current == output {
|
||||
println!("Up-to-date: {file_name}",);
|
||||
} else {
|
||||
println!("Updating: {file_name}",);
|
||||
std::fs::write(markdown_path, output.as_bytes())?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn generate_set(output: &mut String, set: Set, parents: &mut Vec<Set>) {
|
||||
match &set {
|
||||
Set::Toplevel(_) => {
|
||||
output.push_str("# Configuration\n");
|
||||
}
|
||||
Set::Named { name, .. } => {
|
||||
let title = parents
|
||||
.iter()
|
||||
.filter_map(|set| set.name())
|
||||
.chain(std::iter::once(name.as_str()))
|
||||
.join(".");
|
||||
writeln!(output, "## `{title}`\n",).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(documentation) = set.metadata().documentation() {
|
||||
output.push_str(documentation);
|
||||
output.push('\n');
|
||||
output.push('\n');
|
||||
}
|
||||
|
||||
let mut visitor = CollectOptionsVisitor::default();
|
||||
set.metadata().record(&mut visitor);
|
||||
|
||||
let (mut fields, mut sets) = (visitor.fields, visitor.groups);
|
||||
|
||||
fields.sort_unstable_by(|(name, _), (name2, _)| name.cmp(name2));
|
||||
sets.sort_unstable_by(|(name, _), (name2, _)| name.cmp(name2));
|
||||
|
||||
parents.push(set);
|
||||
|
||||
// Generate the fields.
|
||||
for (name, field) in &fields {
|
||||
emit_field(output, name, field, parents.as_slice());
|
||||
output.push_str("---\n\n");
|
||||
}
|
||||
|
||||
// Generate all the sub-sets.
|
||||
for (set_name, sub_set) in &sets {
|
||||
generate_set(
|
||||
output,
|
||||
Set::Named {
|
||||
name: set_name.to_string(),
|
||||
set: *sub_set,
|
||||
},
|
||||
parents,
|
||||
);
|
||||
}
|
||||
|
||||
parents.pop();
|
||||
}
|
||||
|
||||
enum Set {
|
||||
Toplevel(OptionSet),
|
||||
Named { name: String, set: OptionSet },
|
||||
}
|
||||
|
||||
impl Set {
|
||||
fn name(&self) -> Option<&str> {
|
||||
match self {
|
||||
Set::Toplevel(_) => None,
|
||||
Set::Named { name, .. } => Some(name),
|
||||
}
|
||||
}
|
||||
|
||||
fn metadata(&self) -> &OptionSet {
|
||||
match self {
|
||||
Set::Toplevel(set) => set,
|
||||
Set::Named { set, .. } => set,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn emit_field(output: &mut String, name: &str, field: &OptionField, parents: &[Set]) {
|
||||
let header_level = if parents.is_empty() { "###" } else { "####" };
|
||||
|
||||
let _ = writeln!(output, "{header_level} `{name}`");
|
||||
|
||||
output.push('\n');
|
||||
|
||||
if let Some(deprecated) = &field.deprecated {
|
||||
output.push_str("> [!WARN] \"Deprecated\"\n");
|
||||
output.push_str("> This option has been deprecated");
|
||||
|
||||
if let Some(since) = deprecated.since {
|
||||
write!(output, " in {since}").unwrap();
|
||||
}
|
||||
|
||||
output.push('.');
|
||||
|
||||
if let Some(message) = deprecated.message {
|
||||
writeln!(output, " {message}").unwrap();
|
||||
}
|
||||
|
||||
output.push('\n');
|
||||
}
|
||||
|
||||
output.push_str(field.doc);
|
||||
output.push_str("\n\n");
|
||||
let _ = writeln!(output, "**Default value**: `{}`", field.default);
|
||||
output.push('\n');
|
||||
let _ = writeln!(output, "**Type**: `{}`", field.value_type);
|
||||
output.push('\n');
|
||||
output.push_str("**Example usage** (`pyproject.toml`):\n\n");
|
||||
output.push_str(&format_example(
|
||||
&format_header(
|
||||
field.scope,
|
||||
field.example,
|
||||
parents,
|
||||
ConfigurationFile::PyprojectToml,
|
||||
),
|
||||
field.example,
|
||||
));
|
||||
output.push('\n');
|
||||
}
|
||||
|
||||
fn format_example(header: &str, content: &str) -> String {
|
||||
if header.is_empty() {
|
||||
format!("```toml\n{content}\n```\n",)
|
||||
} else {
|
||||
format!("```toml\n{header}\n{content}\n```\n",)
|
||||
}
|
||||
}
|
||||
|
||||
/// Format the TOML header for the example usage for a given option.
|
||||
///
|
||||
/// For example: `[tool.ruff.format]` or `[tool.ruff.lint.isort]`.
|
||||
fn format_header(
|
||||
scope: Option<&str>,
|
||||
example: &str,
|
||||
parents: &[Set],
|
||||
configuration: ConfigurationFile,
|
||||
) -> String {
|
||||
let tool_parent = match configuration {
|
||||
ConfigurationFile::PyprojectToml => Some("tool.ty"),
|
||||
ConfigurationFile::TyToml => None,
|
||||
};
|
||||
|
||||
let header = tool_parent
|
||||
.into_iter()
|
||||
.chain(parents.iter().filter_map(|parent| parent.name()))
|
||||
.chain(scope)
|
||||
.join(".");
|
||||
|
||||
// Ex) `[[tool.ty.xx]]`
|
||||
if example.starts_with(&format!("[[{header}")) {
|
||||
return String::new();
|
||||
}
|
||||
// Ex) `[tool.ty.rules]`
|
||||
if example.starts_with(&format!("[{header}")) {
|
||||
return String::new();
|
||||
}
|
||||
|
||||
if header.is_empty() {
|
||||
String::new()
|
||||
} else {
|
||||
format!("[{header}]")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct CollectOptionsVisitor {
|
||||
groups: Vec<(String, OptionSet)>,
|
||||
fields: Vec<(String, OptionField)>,
|
||||
}
|
||||
|
||||
impl Visit for CollectOptionsVisitor {
|
||||
fn record_set(&mut self, name: &str, group: OptionSet) {
|
||||
self.groups.push((name.to_owned(), group));
|
||||
}
|
||||
|
||||
fn record_field(&mut self, name: &str, field: OptionField) {
|
||||
self.fields.push((name.to_owned(), field));
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
enum ConfigurationFile {
|
||||
PyprojectToml,
|
||||
#[expect(dead_code)]
|
||||
TyToml,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use anyhow::Result;
|
||||
|
||||
use crate::generate_all::Mode;
|
||||
|
||||
use super::{main, Args};
|
||||
|
||||
#[test]
|
||||
fn ty_configuration_markdown_up_to_date() -> Result<()> {
|
||||
main(&Args { mode: Mode::Check })?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
143
crates/ruff_dev/src/generate_ty_rules.rs
Normal file
143
crates/ruff_dev/src/generate_ty_rules.rs
Normal file
@@ -0,0 +1,143 @@
|
||||
//! Generates the rules table for ty
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::fmt::Write as _;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use itertools::Itertools as _;
|
||||
use pretty_assertions::StrComparison;
|
||||
|
||||
use crate::generate_all::{Mode, REGENERATE_ALL_COMMAND};
|
||||
use crate::ROOT_DIR;
|
||||
|
||||
#[derive(clap::Args)]
|
||||
pub(crate) struct Args {
|
||||
/// Write the generated table to stdout (rather than to `ty.schema.json`).
|
||||
#[arg(long, default_value_t, value_enum)]
|
||||
pub(crate) mode: Mode,
|
||||
}
|
||||
|
||||
pub(crate) fn main(args: &Args) -> Result<()> {
|
||||
let markdown = generate_markdown();
|
||||
let filename = "crates/ty/docs/rules.md";
|
||||
let schema_path = PathBuf::from(ROOT_DIR).join(filename);
|
||||
|
||||
match args.mode {
|
||||
Mode::DryRun => {
|
||||
println!("{markdown}");
|
||||
}
|
||||
Mode::Check => {
|
||||
let current = fs::read_to_string(schema_path)?;
|
||||
if current == markdown {
|
||||
println!("Up-to-date: {filename}");
|
||||
} else {
|
||||
let comparison = StrComparison::new(¤t, &markdown);
|
||||
bail!("{filename} changed, please run `{REGENERATE_ALL_COMMAND}`:\n{comparison}");
|
||||
}
|
||||
}
|
||||
Mode::Write => {
|
||||
let current = fs::read_to_string(&schema_path)?;
|
||||
if current == markdown {
|
||||
println!("Up-to-date: {filename}");
|
||||
} else {
|
||||
println!("Updating: {filename}");
|
||||
fs::write(schema_path, markdown.as_bytes())?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn generate_markdown() -> String {
|
||||
let registry = &*ty_project::DEFAULT_LINT_REGISTRY;
|
||||
|
||||
let mut output = String::new();
|
||||
|
||||
let _ = writeln!(&mut output, "# Rules\n");
|
||||
|
||||
let mut lints: Vec<_> = registry.lints().iter().collect();
|
||||
lints.sort_by(|a, b| {
|
||||
a.default_level()
|
||||
.cmp(&b.default_level())
|
||||
.reverse()
|
||||
.then_with(|| a.name().cmp(&b.name()))
|
||||
});
|
||||
|
||||
for lint in lints {
|
||||
let _ = writeln!(&mut output, "## `{rule_name}`\n", rule_name = lint.name());
|
||||
|
||||
// Increase the header-level by one
|
||||
let documentation = lint
|
||||
.documentation_lines()
|
||||
.map(|line| {
|
||||
if line.starts_with('#') {
|
||||
Cow::Owned(format!("#{line}"))
|
||||
} else {
|
||||
Cow::Borrowed(line)
|
||||
}
|
||||
})
|
||||
.join("\n");
|
||||
|
||||
let _ = writeln!(
|
||||
&mut output,
|
||||
r#"**Default level**: {level}
|
||||
|
||||
<details>
|
||||
<summary>{summary}</summary>
|
||||
|
||||
{documentation}
|
||||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20{encoded_name})
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/{file}#L{line})
|
||||
</details>
|
||||
"#,
|
||||
level = lint.default_level(),
|
||||
// GitHub doesn't support markdown in `summary` headers
|
||||
summary = replace_inline_code(lint.summary()),
|
||||
encoded_name = url::form_urlencoded::byte_serialize(lint.name().as_str().as_bytes())
|
||||
.collect::<String>(),
|
||||
file = url::form_urlencoded::byte_serialize(lint.file().replace('\\', "/").as_bytes())
|
||||
.collect::<String>(),
|
||||
line = lint.line(),
|
||||
);
|
||||
}
|
||||
|
||||
output
|
||||
}
|
||||
|
||||
/// Replaces inline code blocks (`code`) with `<code>code</code>`
|
||||
fn replace_inline_code(input: &str) -> String {
|
||||
let mut output = String::new();
|
||||
let mut parts = input.split('`');
|
||||
|
||||
while let Some(before) = parts.next() {
|
||||
if let Some(between) = parts.next() {
|
||||
output.push_str(before);
|
||||
output.push_str("<code>");
|
||||
output.push_str(between);
|
||||
output.push_str("</code>");
|
||||
} else {
|
||||
output.push_str(before);
|
||||
}
|
||||
}
|
||||
|
||||
output
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use anyhow::Result;
|
||||
|
||||
use crate::generate_all::Mode;
|
||||
|
||||
use super::{main, Args};
|
||||
|
||||
#[test]
|
||||
fn ty_rules_up_to_date() -> Result<()> {
|
||||
main(&Args { mode: Mode::Check })
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,3 @@
|
||||
#![allow(clippy::print_stdout, clippy::print_stderr)]
|
||||
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
|
||||
@@ -9,11 +7,11 @@ use schemars::schema_for;
|
||||
|
||||
use crate::generate_all::{Mode, REGENERATE_ALL_COMMAND};
|
||||
use crate::ROOT_DIR;
|
||||
use red_knot_project::metadata::options::Options;
|
||||
use ty_project::metadata::options::Options;
|
||||
|
||||
#[derive(clap::Args)]
|
||||
pub(crate) struct Args {
|
||||
/// Write the generated table to stdout (rather than to `knot.schema.json`).
|
||||
/// Write the generated table to stdout (rather than to `ty.schema.json`).
|
||||
#[arg(long, default_value_t, value_enum)]
|
||||
pub(crate) mode: Mode,
|
||||
}
|
||||
@@ -21,7 +19,7 @@ pub(crate) struct Args {
|
||||
pub(crate) fn main(args: &Args) -> Result<()> {
|
||||
let schema = schema_for!(Options);
|
||||
let schema_string = serde_json::to_string_pretty(&schema).unwrap();
|
||||
let filename = "knot.schema.json";
|
||||
let filename = "ty.schema.json";
|
||||
let schema_path = PathBuf::from(ROOT_DIR).join(filename);
|
||||
|
||||
match args.mode {
|
||||
@@ -62,7 +60,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_generate_json_schema() -> Result<()> {
|
||||
let mode = if env::var("KNOT_UPDATE_SCHEMA").as_deref() == Ok("1") {
|
||||
let mode = if env::var("TY_UPDATE_SCHEMA").as_deref() == Ok("1") {
|
||||
Mode::Write
|
||||
} else {
|
||||
Mode::Check
|
||||
@@ -2,6 +2,8 @@
|
||||
//!
|
||||
//! Within the ruff repository you can run it with `cargo dev`.
|
||||
|
||||
#![allow(clippy::print_stdout, clippy::print_stderr)]
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::{Parser, Subcommand};
|
||||
use ruff::{args::GlobalConfigArgs, check};
|
||||
@@ -13,9 +15,12 @@ mod generate_all;
|
||||
mod generate_cli_help;
|
||||
mod generate_docs;
|
||||
mod generate_json_schema;
|
||||
mod generate_knot_schema;
|
||||
mod generate_options;
|
||||
mod generate_rules_table;
|
||||
mod generate_ty_cli_reference;
|
||||
mod generate_ty_options;
|
||||
mod generate_ty_rules;
|
||||
mod generate_ty_schema;
|
||||
mod print_ast;
|
||||
mod print_cst;
|
||||
mod print_tokens;
|
||||
@@ -34,18 +39,20 @@ struct Args {
|
||||
}
|
||||
|
||||
#[derive(Subcommand)]
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
#[expect(clippy::large_enum_variant)]
|
||||
enum Command {
|
||||
/// Run all code and documentation generation steps.
|
||||
GenerateAll(generate_all::Args),
|
||||
/// Generate JSON schema for the TOML configuration file.
|
||||
GenerateJSONSchema(generate_json_schema::Args),
|
||||
/// Generate JSON schema for the Red Knot TOML configuration file.
|
||||
GenerateKnotSchema(generate_knot_schema::Args),
|
||||
/// Generate JSON schema for the ty TOML configuration file.
|
||||
GenerateTySchema(generate_ty_schema::Args),
|
||||
/// Generate a Markdown-compatible table of supported lint rules.
|
||||
GenerateRulesTable,
|
||||
GenerateTyRules(generate_ty_rules::Args),
|
||||
/// Generate a Markdown-compatible listing of configuration options.
|
||||
GenerateOptions,
|
||||
GenerateTyOptions(generate_ty_options::Args),
|
||||
/// Generate CLI help.
|
||||
GenerateCliHelp(generate_cli_help::Args),
|
||||
/// Generate Markdown docs.
|
||||
@@ -82,13 +89,15 @@ fn main() -> Result<ExitCode> {
|
||||
command,
|
||||
global_options,
|
||||
} = Args::parse();
|
||||
#[allow(clippy::print_stdout)]
|
||||
#[expect(clippy::print_stdout)]
|
||||
match command {
|
||||
Command::GenerateAll(args) => generate_all::main(&args)?,
|
||||
Command::GenerateJSONSchema(args) => generate_json_schema::main(&args)?,
|
||||
Command::GenerateKnotSchema(args) => generate_knot_schema::main(&args)?,
|
||||
Command::GenerateTySchema(args) => generate_ty_schema::main(&args)?,
|
||||
Command::GenerateRulesTable => println!("{}", generate_rules_table::generate()),
|
||||
Command::GenerateTyRules(args) => generate_ty_rules::main(&args)?,
|
||||
Command::GenerateOptions => println!("{}", generate_options::generate()),
|
||||
Command::GenerateTyOptions(args) => generate_ty_options::main(&args)?,
|
||||
Command::GenerateCliHelp(args) => generate_cli_help::main(&args)?,
|
||||
Command::GenerateDocs(args) => generate_docs::main(&args)?,
|
||||
Command::PrintAST(args) => print_ast::main(&args)?,
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
//! Print the AST for a given Python file.
|
||||
#![allow(clippy::print_stdout, clippy::print_stderr)]
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
//! Print the `LibCST` CST for a given Python file.
|
||||
#![allow(clippy::print_stdout, clippy::print_stderr)]
|
||||
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
//! Print the token stream for a given Python file.
|
||||
#![allow(clippy::print_stdout, clippy::print_stderr)]
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
//! Run round-trip source code generation on a given Python or Jupyter notebook file.
|
||||
#![allow(clippy::print_stdout, clippy::print_stderr)]
|
||||
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
|
||||
@@ -519,7 +519,7 @@ impl TextWidth {
|
||||
let char_width = match c {
|
||||
'\t' => indent_width.value(),
|
||||
'\n' => return TextWidth::Multiline,
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
#[expect(clippy::cast_possible_truncation)]
|
||||
c => c.width().unwrap_or(0) as u32,
|
||||
};
|
||||
width += char_width;
|
||||
|
||||
@@ -280,7 +280,7 @@ impl Format<IrFormatContext<'_>> for &[FormatElement] {
|
||||
| FormatElement::SourceCodeSlice { .. }) => {
|
||||
fn write_escaped(element: &FormatElement, f: &mut Formatter<IrFormatContext>) {
|
||||
let (text, text_width) = match element {
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
#[expect(clippy::cast_possible_truncation)]
|
||||
FormatElement::Token { text } => {
|
||||
(*text, TextWidth::Width(Width::new(text.len() as u32)))
|
||||
}
|
||||
|
||||
@@ -379,7 +379,7 @@ impl PartialEq for LabelId {
|
||||
}
|
||||
|
||||
impl LabelId {
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
#[expect(clippy::needless_pass_by_value)]
|
||||
pub fn of<T: LabelDefinition>(label: T) -> Self {
|
||||
Self {
|
||||
value: label.value(),
|
||||
|
||||
@@ -92,7 +92,7 @@ impl std::fmt::Display for IndentStyle {
|
||||
}
|
||||
}
|
||||
|
||||
/// The visual width of a indentation.
|
||||
/// The visual width of an indentation.
|
||||
///
|
||||
/// Determines the visual width of a tab character (`\t`) and the number of
|
||||
/// spaces per indent when using [`IndentStyle::Space`].
|
||||
@@ -207,7 +207,7 @@ pub trait FormatOptions {
|
||||
/// What's the max width of a line. Defaults to 80.
|
||||
fn line_width(&self) -> LineWidth;
|
||||
|
||||
/// Derives the print options from the these format options
|
||||
/// Derives the print options from these format options
|
||||
fn as_print_options(&self) -> PrinterOptions;
|
||||
}
|
||||
|
||||
@@ -925,7 +925,7 @@ pub struct FormatState<Context> {
|
||||
group_id_builder: UniqueGroupIdBuilder,
|
||||
}
|
||||
|
||||
#[allow(clippy::missing_fields_in_debug)]
|
||||
#[expect(clippy::missing_fields_in_debug)]
|
||||
impl<Context> std::fmt::Debug for FormatState<Context>
|
||||
where
|
||||
Context: std::fmt::Debug,
|
||||
|
||||
@@ -331,7 +331,7 @@ impl<'a> Printer<'a> {
|
||||
FormatElement::Tag(StartVerbatim(kind)) => {
|
||||
if let VerbatimKind::Verbatim { length } = kind {
|
||||
// SAFETY: Ruff only supports formatting files <= 4GB
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
#[expect(clippy::cast_possible_truncation)]
|
||||
self.state.verbatim_markers.push(TextRange::at(
|
||||
TextSize::from(self.state.buffer.len() as u32),
|
||||
*length,
|
||||
@@ -464,7 +464,7 @@ impl<'a> Printer<'a> {
|
||||
self.push_marker();
|
||||
|
||||
match text {
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
#[expect(clippy::cast_possible_truncation)]
|
||||
Text::Token(token) => {
|
||||
self.state.buffer.push_str(token);
|
||||
self.state.line_width += token.len() as u32;
|
||||
@@ -831,7 +831,7 @@ impl<'a> Printer<'a> {
|
||||
} else {
|
||||
self.state.buffer.push(char);
|
||||
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
#[expect(clippy::cast_possible_truncation)]
|
||||
let char_width = if char == '\t' {
|
||||
self.options.indent_width.value()
|
||||
} else {
|
||||
@@ -1480,7 +1480,7 @@ impl<'a, 'print> FitsMeasurer<'a, 'print> {
|
||||
u32::from(indent.level()) * self.options().indent_width() + u32::from(indent.align());
|
||||
|
||||
match text {
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
#[expect(clippy::cast_possible_truncation)]
|
||||
Text::Token(token) => {
|
||||
self.state.line_width += token.len() as u32;
|
||||
}
|
||||
@@ -1511,7 +1511,7 @@ impl<'a, 'print> FitsMeasurer<'a, 'print> {
|
||||
}
|
||||
}
|
||||
// SAFETY: A u32 is sufficient to format files <= 4GB
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
#[expect(clippy::cast_possible_truncation)]
|
||||
c => c.width().unwrap_or(0) as u32,
|
||||
};
|
||||
self.state.line_width += char_width;
|
||||
|
||||
@@ -10,13 +10,13 @@ authors.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
red_knot_python_semantic = { workspace = true }
|
||||
ruff_cache = { workspace = true }
|
||||
ruff_db = { workspace = true, features = ["os", "serde"] }
|
||||
ruff_linter = { workspace = true }
|
||||
ruff_macros = { workspace = true }
|
||||
ruff_python_ast = { workspace = true }
|
||||
ruff_python_parser = { workspace = true }
|
||||
ty_python_semantic = { workspace = true }
|
||||
|
||||
anyhow = { workspace = true }
|
||||
clap = { workspace = true, optional = true }
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use red_knot_python_semantic::ModuleName;
|
||||
use ruff_python_ast::visitor::source_order::{
|
||||
walk_expr, walk_module, walk_stmt, SourceOrderVisitor,
|
||||
};
|
||||
use ruff_python_ast::{self as ast, Expr, Mod, Stmt};
|
||||
use ty_python_semantic::ModuleName;
|
||||
|
||||
/// Collect all imports for a given Python file.
|
||||
#[derive(Default, Debug)]
|
||||
|
||||
@@ -2,16 +2,16 @@ use anyhow::Result;
|
||||
use std::sync::Arc;
|
||||
use zip::CompressionMethod;
|
||||
|
||||
use red_knot_python_semantic::lint::{LintRegistry, RuleSelection};
|
||||
use red_knot_python_semantic::{
|
||||
default_lint_registry, Db, Program, ProgramSettings, PythonPath, PythonPlatform,
|
||||
SearchPathSettings,
|
||||
};
|
||||
use ruff_db::files::{File, Files};
|
||||
use ruff_db::system::{OsSystem, System, SystemPathBuf};
|
||||
use ruff_db::vendored::{VendoredFileSystem, VendoredFileSystemBuilder};
|
||||
use ruff_db::{Db as SourceDb, Upcast};
|
||||
use ruff_python_ast::PythonVersion;
|
||||
use ty_python_semantic::lint::{LintRegistry, RuleSelection};
|
||||
use ty_python_semantic::{
|
||||
default_lint_registry, Db, Program, ProgramSettings, PythonPath, PythonPlatform,
|
||||
SearchPathSettings,
|
||||
};
|
||||
|
||||
static EMPTY_VENDORED: std::sync::LazyLock<VendoredFileSystem> = std::sync::LazyLock::new(|| {
|
||||
let mut builder = VendoredFileSystemBuilder::new(CompressionMethod::Stored);
|
||||
@@ -88,8 +88,8 @@ impl Db for ModuleDb {
|
||||
!file.path(self).is_vendored_path()
|
||||
}
|
||||
|
||||
fn rule_selection(&self) -> Arc<RuleSelection> {
|
||||
self.rule_selection.clone()
|
||||
fn rule_selection(&self) -> &RuleSelection {
|
||||
&self.rule_selection
|
||||
}
|
||||
|
||||
fn lint_registry(&self) -> &LintRegistry {
|
||||
@@ -98,6 +98,4 @@ impl Db for ModuleDb {
|
||||
}
|
||||
|
||||
#[salsa::db]
|
||||
impl salsa::Database for ModuleDb {
|
||||
fn salsa_event(&self, _event: &dyn Fn() -> salsa::Event) {}
|
||||
}
|
||||
impl salsa::Database for ModuleDb {}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use red_knot_python_semantic::resolve_module;
|
||||
use ruff_db::files::FilePath;
|
||||
use ty_python_semantic::resolve_module;
|
||||
|
||||
use crate::collector::CollectedImport;
|
||||
use crate::ModuleDb;
|
||||
|
||||
@@ -22,7 +22,7 @@ impl<I: Idx, T> IndexSlice<I, T> {
|
||||
pub const fn from_raw(raw: &[T]) -> &Self {
|
||||
let ptr: *const [T] = raw;
|
||||
|
||||
#[allow(unsafe_code)]
|
||||
#[expect(unsafe_code)]
|
||||
// SAFETY: `IndexSlice` is `repr(transparent)` over a normal slice
|
||||
unsafe {
|
||||
&*(ptr as *const Self)
|
||||
@@ -33,7 +33,7 @@ impl<I: Idx, T> IndexSlice<I, T> {
|
||||
pub fn from_raw_mut(raw: &mut [T]) -> &mut Self {
|
||||
let ptr: *mut [T] = raw;
|
||||
|
||||
#[allow(unsafe_code)]
|
||||
#[expect(unsafe_code)]
|
||||
// SAFETY: `IndexSlice` is `repr(transparent)` over a normal slice
|
||||
unsafe {
|
||||
&mut *(ptr as *mut Self)
|
||||
@@ -209,5 +209,5 @@ impl<I: Idx, T> Default for &mut IndexSlice<I, T> {
|
||||
|
||||
// Whether `IndexSlice` is `Send` depends only on the data,
|
||||
// not the phantom data.
|
||||
#[allow(unsafe_code)]
|
||||
#[expect(unsafe_code)]
|
||||
unsafe impl<I: Idx, T> Send for IndexSlice<I, T> where T: Send {}
|
||||
|
||||
@@ -179,16 +179,16 @@ impl<I: Idx, T, const N: usize> From<[T; N]> for IndexVec<I, T> {
|
||||
|
||||
// Whether `IndexVec` is `Send` depends only on the data,
|
||||
// not the phantom data.
|
||||
#[allow(unsafe_code)]
|
||||
#[expect(unsafe_code)]
|
||||
unsafe impl<I: Idx, T> Send for IndexVec<I, T> where T: Send {}
|
||||
|
||||
#[allow(unsafe_code)]
|
||||
#[expect(unsafe_code)]
|
||||
#[cfg(feature = "salsa")]
|
||||
unsafe impl<I, T> salsa::Update for IndexVec<I, T>
|
||||
where
|
||||
T: salsa::Update,
|
||||
{
|
||||
#[allow(unsafe_code)]
|
||||
#[expect(unsafe_code)]
|
||||
unsafe fn maybe_update(old_pointer: *mut Self, new_value: Self) -> bool {
|
||||
let old_vec: &mut IndexVec<I, T> = unsafe { &mut *old_pointer };
|
||||
salsa::Update::maybe_update(&mut old_vec.raw, new_value.raw)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_linter"
|
||||
version = "0.11.8"
|
||||
version = "0.11.9"
|
||||
publish = false
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
@@ -15,6 +15,7 @@ license = { workspace = true }
|
||||
[dependencies]
|
||||
ruff_annotate_snippets = { workspace = true }
|
||||
ruff_cache = { workspace = true }
|
||||
ruff_db = { workspace = true }
|
||||
ruff_diagnostics = { workspace = true, features = ["serde"] }
|
||||
ruff_notebook = { workspace = true }
|
||||
ruff_macros = { workspace = true }
|
||||
@@ -76,6 +77,7 @@ insta = { workspace = true, features = ["filters", "json", "redactions"] }
|
||||
test-case = { workspace = true }
|
||||
# Disable colored output in tests
|
||||
colored = { workspace = true, features = ["no-color"] }
|
||||
tempfile = { workspace = true }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
||||
@@ -24,9 +24,6 @@ from airflow.contrib.aws_athena_hook import AWSAthenaHook
|
||||
from airflow.datasets import DatasetAliasEvent
|
||||
from airflow.hooks.base_hook import BaseHook
|
||||
from airflow.operators.subdag import SubDagOperator
|
||||
from airflow.providers.mysql.datasets import mysql
|
||||
from airflow.providers.postgres.datasets import postgres
|
||||
from airflow.providers.trino.datasets import trino
|
||||
from airflow.secrets.local_filesystem import LocalFilesystemBackend
|
||||
from airflow.sensors.base_sensor_operator import BaseSensorOperator
|
||||
from airflow.triggers.external_task import TaskStateTrigger
|
||||
@@ -78,14 +75,6 @@ BaseHook()
|
||||
# airflow.operators.subdag.*
|
||||
SubDagOperator()
|
||||
|
||||
# airflow.providers.mysql
|
||||
mysql.sanitize_uri
|
||||
|
||||
# airflow.providers.postgres
|
||||
postgres.sanitize_uri
|
||||
|
||||
# airflow.providers.trino
|
||||
trino.sanitize_uri
|
||||
|
||||
# airflow.secrets
|
||||
# get_connection
|
||||
@@ -155,3 +144,18 @@ should_hide_value_for_key
|
||||
from airflow.operators.python import get_current_context
|
||||
|
||||
get_current_context()
|
||||
|
||||
# airflow.providers.mysql
|
||||
from airflow.providers.mysql.datasets.mysql import sanitize_uri
|
||||
|
||||
sanitize_uri
|
||||
|
||||
# airflow.providers.postgres
|
||||
from airflow.providers.postgres.datasets.postgres import sanitize_uri
|
||||
|
||||
sanitize_uri
|
||||
|
||||
# airflow.providers.trino
|
||||
from airflow.providers.trino.datasets.trino import sanitize_uri
|
||||
|
||||
sanitize_uri
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
from __future__ import annotations
|
||||
|
||||
try:
|
||||
from airflow.sdk import Asset
|
||||
from airflow.assets.manager import AssetManager
|
||||
except ModuleNotFoundError:
|
||||
from airflow.datasets import Dataset as Asset
|
||||
from airflow.datasets.manager import DatasetManager as AssetManager
|
||||
|
||||
Asset
|
||||
AssetManager()
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user