Compare commits
1 Commits
dhruv/vers
...
charlie/fo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
400732a655 |
130
.github/workflows/publish-docs.yml
vendored
130
.github/workflows/publish-docs.yml
vendored
@@ -36,30 +36,28 @@ jobs:
|
||||
version="${{ (inputs.plan != '' && fromJson(inputs.plan).announcement_tag) || inputs.ref }}"
|
||||
# if version is missing, exit with error
|
||||
if [[ -z "$version" ]]; then
|
||||
echo "Can't build docs without a version."
|
||||
exit 1
|
||||
echo "Can't build docs without a version."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Use version as display name for now
|
||||
display_name="$version"
|
||||
|
||||
# Extract the major and minor part of the version for the docs
|
||||
docs_version="$(echo -n "$version" | cut -d "." -f 1-2)"
|
||||
|
||||
echo "version=$version" >> "$GITHUB_ENV"
|
||||
echo "docs_version=$docs_version" >> "$GITHUB_ENV"
|
||||
echo "display_name=$display_name" >> "$GITHUB_ENV"
|
||||
echo "version=$version" >> $GITHUB_ENV
|
||||
echo "display_name=$display_name" >> $GITHUB_ENV
|
||||
|
||||
- name: "Set branch name"
|
||||
run: |
|
||||
version="${{ env.version }}"
|
||||
display_name="${{ env.display_name }}"
|
||||
timestamp="$(date +%s)"
|
||||
|
||||
# Create `branch_display_name` from `display_name` by replacing all
|
||||
# create branch_display_name from display_name by replacing all
|
||||
# characters disallowed in git branch names with hyphens
|
||||
branch_display_name="$(echo -n "$display_name" | tr -c '[:alnum:]._' '-' | tr -s '-')"
|
||||
branch_display_name="$(echo "$display_name" | tr -c '[:alnum:]._' '-' | tr -s '-')"
|
||||
|
||||
echo "branch_name=update-docs-$branch_display_name-$timestamp" >> "$GITHUB_ENV"
|
||||
echo "timestamp=$timestamp" >> "$GITHUB_ENV"
|
||||
echo "branch_name=update-docs-$branch_display_name-$timestamp" >> $GITHUB_ENV
|
||||
echo "timestamp=$timestamp" >> $GITHUB_ENV
|
||||
|
||||
- name: "Add SSH key"
|
||||
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }}
|
||||
@@ -72,7 +70,7 @@ jobs:
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
|
||||
- name: "Install insiders dependencies"
|
||||
- name: "Install Insiders dependencies"
|
||||
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }}
|
||||
run: pip install -r docs/requirements-insiders.txt
|
||||
|
||||
@@ -80,86 +78,74 @@ jobs:
|
||||
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS != 'true' }}
|
||||
run: pip install -r docs/requirements.txt
|
||||
|
||||
- name: "Fetch docs repo"
|
||||
run: |
|
||||
remote_name="astral-docs"
|
||||
|
||||
git remote add "$remote_name" https://${{ secrets.ASTRAL_DOCS_PAT }}@github.com/astral-sh/docs.git
|
||||
git fetch astral-docs "main:$branch_name"
|
||||
|
||||
echo "remote_name=$remote_name" >> "$GITHUB_ENV"
|
||||
|
||||
- name: "Configure git"
|
||||
run: |
|
||||
git config user.name "astral-docs-bot"
|
||||
git config user.email "176161322+astral-docs-bot@users.noreply.github.com"
|
||||
|
||||
- name: "Transform README and generate docs"
|
||||
- name: "Copy README File"
|
||||
run: |
|
||||
python scripts/transform_readme.py --target mkdocs
|
||||
python scripts/generate_mkdocs.py
|
||||
|
||||
- name: "Build Insiders docs"
|
||||
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }}
|
||||
run: |
|
||||
mike deploy \
|
||||
--remote "$remote_name" \
|
||||
--branch "$branch_name" \
|
||||
--message "Update ruff documentation for $version" \
|
||||
--config-file mkdocs.insiders.yml \
|
||||
--update-aliases \
|
||||
--push \
|
||||
"$docs_version" latest
|
||||
run: mkdocs build --strict -f mkdocs.insiders.yml
|
||||
|
||||
- name: "Build docs"
|
||||
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS != 'true' }}
|
||||
run: mkdocs build --strict -f mkdocs.public.yml
|
||||
|
||||
- name: "Clone docs repo"
|
||||
run: |
|
||||
mike deploy \
|
||||
--remote "$remote_name" \
|
||||
--branch "$branch_name" \
|
||||
--message "Update ruff documentation for $version" \
|
||||
--config-file mkdocs.public.yml \
|
||||
--update-aliases \
|
||||
--push \
|
||||
"$docs_version" latest
|
||||
version="${{ env.version }}"
|
||||
git clone https://${{ secrets.ASTRAL_DOCS_PAT }}@github.com/astral-sh/docs.git astral-docs
|
||||
|
||||
- name: "Copy docs"
|
||||
run: rm -rf astral-docs/site/ruff && mkdir -p astral-docs/site && cp -r site/ruff astral-docs/site/
|
||||
|
||||
- name: "Commit docs"
|
||||
working-directory: astral-docs
|
||||
run: |
|
||||
branch_name="${{ env.branch_name }}"
|
||||
|
||||
git config user.name "astral-docs-bot"
|
||||
git config user.email "176161322+astral-docs-bot@users.noreply.github.com"
|
||||
|
||||
git checkout -b $branch_name
|
||||
git add site/ruff
|
||||
git commit -m "Update ruff documentation for $version"
|
||||
|
||||
- name: "Create Pull Request"
|
||||
working-directory: astral-docs
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.ASTRAL_DOCS_PAT }}
|
||||
run: |
|
||||
# Set the docs repository
|
||||
astral_docs_repo="astral-sh/docs"
|
||||
version="${{ env.version }}"
|
||||
display_name="${{ env.display_name }}"
|
||||
branch_name="${{ env.branch_name }}"
|
||||
|
||||
# Set the PR title
|
||||
# set the PR title
|
||||
pull_request_title="Update ruff documentation for $display_name"
|
||||
|
||||
# Delete any existing pull requests that are open for this version
|
||||
# by checking against `pull_request_title` because the new PR will
|
||||
# by checking against pull_request_title because the new PR will
|
||||
# supersede the old one.
|
||||
gh pr list \
|
||||
--state open \
|
||||
--json title,number \
|
||||
--jq '.[] | select(.title == "$pull_request_title") | .number' \
|
||||
--repo "$astral_docs_repo" | \
|
||||
xargs -I {} gh pr close {}
|
||||
gh pr list --state open --json title --jq '.[] | select(.title == "$pull_request_title") | .number' | \
|
||||
xargs -I {} gh pr close {}
|
||||
|
||||
# Create the PR, the branch has already been pushed by `mike`
|
||||
gh pr create --base main --head "$branch_name" \
|
||||
# push the branch to GitHub
|
||||
git push origin $branch_name
|
||||
|
||||
# create the PR
|
||||
gh pr create --base main --head $branch_name \
|
||||
--title "$pull_request_title" \
|
||||
--body "Automated documentation update for $display_name" \
|
||||
--label "documentation" \
|
||||
--repo "$astral_docs_repo"
|
||||
--label "documentation"
|
||||
|
||||
# TODO(dhruvmanila): Uncomment once a patch and minor release are done, thus
|
||||
# confirming that it works as intended
|
||||
#
|
||||
# - name: "Merge Pull Request"
|
||||
# if: ${{ inputs.plan != '' && !fromJson(inputs.plan).announcement_tag_is_implicit }}
|
||||
# env:
|
||||
# GITHUB_TOKEN: ${{ secrets.ASTRAL_DOCS_PAT }}
|
||||
# run: |
|
||||
# branch_name="${{ env.branch_name }}"
|
||||
# # auto-merge the PR if the build was triggered by a release. Manual builds should be reviewed by a human.
|
||||
# # give the PR a few seconds to be created before trying to auto-merge it
|
||||
# sleep 10
|
||||
# gh pr merge --squash $branch_name --repo "astral-sh/docs"
|
||||
- name: "Merge Pull Request"
|
||||
if: ${{ inputs.plan != '' && !fromJson(inputs.plan).announcement_tag_is_implicit }}
|
||||
working-directory: astral-docs
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.ASTRAL_DOCS_PAT }}
|
||||
run: |
|
||||
branch_name="${{ env.branch_name }}"
|
||||
# auto-merge the PR if the build was triggered by a release. Manual builds should be reviewed by a human.
|
||||
# give the PR a few seconds to be created before trying to auto-merge it
|
||||
sleep 10
|
||||
gh pr merge --squash $branch_name
|
||||
|
||||
@@ -61,7 +61,7 @@ The following rules have been stabilized and are no longer in preview:
|
||||
- [`invalid-bytes-return-type`](https://docs.astral.sh/ruff/rules/invalid-bytes-return-type/) (`PLE0308`)
|
||||
- [`invalid-hash-return-type`](https://docs.astral.sh/ruff/rules/invalid-hash-return-type/) (`PLE0309`)
|
||||
- [`invalid-index-return-type`](https://docs.astral.sh/ruff/rules/invalid-index-return-type/) (`PLE0305`)
|
||||
- [`invalid-length-return-type`](https://docs.astral.sh/ruff/rules/invalid-length-return-type/) (`PLEE303`)
|
||||
- [`invalid-length-return-type`](https://docs.astral.sh/ruff/rules/invalid-length-return-type/) (`E303`)
|
||||
- [`self-or-cls-assignment`](https://docs.astral.sh/ruff/rules/self-or-cls-assignment/) (`PLW0642`)
|
||||
- [`byte-string-usage`](https://docs.astral.sh/ruff/rules/byte-string-usage/) (`PYI057`)
|
||||
- [`duplicate-literal-member`](https://docs.astral.sh/ruff/rules/duplicate-literal-member/) (`PYI062`)
|
||||
|
||||
146
Cargo.lock
generated
146
Cargo.lock
generated
@@ -228,9 +228,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "camino"
|
||||
version = "1.1.9"
|
||||
version = "1.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b96ec4966b5813e2c0507c1f86115c8c5abaadc3980879c3424042a02fd1ad3"
|
||||
checksum = "e0ec6b951b160caa93cc0c7b209e5a3bff7aae9062213451ac99493cd844c239"
|
||||
|
||||
[[package]]
|
||||
name = "cast"
|
||||
@@ -270,12 +270,6 @@ version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e"
|
||||
|
||||
[[package]]
|
||||
name = "cfg_aliases"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
||||
|
||||
[[package]]
|
||||
name = "chic"
|
||||
version = "1.2.2"
|
||||
@@ -326,9 +320,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.16"
|
||||
version = "4.5.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed6719fffa43d0d87e5fd8caeab59be1554fb028cd30edc88fc4369b17971019"
|
||||
checksum = "11d8838454fda655dafd3accb2b6e2bea645b9e4078abe84a22ceb947235c5cc"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
@@ -401,7 +395,7 @@ version = "3.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2f8c93eb5f77c9050c7750e14f13ef1033a40a0aac70c6371535b6763a01438c"
|
||||
dependencies = [
|
||||
"nix 0.28.0",
|
||||
"nix",
|
||||
"terminfo",
|
||||
"thiserror",
|
||||
"which",
|
||||
@@ -618,12 +612,12 @@ checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
|
||||
|
||||
[[package]]
|
||||
name = "ctrlc"
|
||||
version = "3.4.5"
|
||||
version = "3.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90eeab0aa92f3f9b4e87f258c72b139c207d251f9cbc1080a0086b86a8870dd3"
|
||||
checksum = "672465ae37dc1bc6380a6547a8883d5dd397b0f1faaad4f265726cc7042a5345"
|
||||
dependencies = [
|
||||
"nix 0.29.0",
|
||||
"windows-sys 0.59.0",
|
||||
"nix",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -852,6 +846,12 @@ version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||
|
||||
[[package]]
|
||||
name = "foldhash"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4deb59dd6330afa472c000b86c0c9ada26274836eb59563506c3e34e4bb9a819"
|
||||
|
||||
[[package]]
|
||||
name = "form_urlencoded"
|
||||
version = "1.2.1"
|
||||
@@ -1053,9 +1053,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.4.0"
|
||||
version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93ead53efc7ea8ed3cfb0c79fc8023fbb782a5432b52830b6518941cebe6505c"
|
||||
checksum = "de3fc2e30ba82dd1b3911c8de1ffc143c74a914a14e99514d7637e3099df5ea0"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown",
|
||||
@@ -1221,9 +1221,9 @@ checksum = "8b23360e99b8717f20aaa4598f5a6541efbe30630039fbc7706cf954a87947ae"
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.70"
|
||||
version = "0.3.69"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a"
|
||||
checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d"
|
||||
dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
@@ -1256,9 +1256,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.157"
|
||||
version = "0.2.155"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "374af5f94e54fa97cf75e945cce8a6b201e88a1a07e688b47dfd2a59c66dbd86"
|
||||
checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c"
|
||||
|
||||
[[package]]
|
||||
name = "libcst"
|
||||
@@ -1394,16 +1394,6 @@ dependencies = [
|
||||
"libmimalloc-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "minicov"
|
||||
version = "0.3.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c71e683cd655513b99affab7d317deb690528255a0d5f717f1024093c12b169"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "minimal-lexical"
|
||||
version = "0.2.1"
|
||||
@@ -1454,19 +1444,7 @@ checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"cfg-if",
|
||||
"cfg_aliases 0.1.1",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.29.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"cfg-if",
|
||||
"cfg_aliases 0.2.1",
|
||||
"cfg_aliases",
|
||||
"libc",
|
||||
]
|
||||
|
||||
@@ -1553,9 +1531,9 @@ checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
|
||||
|
||||
[[package]]
|
||||
name = "ordermap"
|
||||
version = "0.5.2"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "61d7d835be600a7ac71b24e39c92fe6fad9e818b3c71bfc379e3ba65e327d77f"
|
||||
checksum = "8c81974681ab4f0cc9fe49cad56f821d1cc67a08cd2caa9b5d58b0adaa5dd36d"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
]
|
||||
@@ -1932,8 +1910,6 @@ dependencies = [
|
||||
"ruff_text_size",
|
||||
"rustc-hash 2.0.0",
|
||||
"salsa",
|
||||
"smallvec",
|
||||
"static_assertions",
|
||||
"tempfile",
|
||||
"tracing",
|
||||
"walkdir",
|
||||
@@ -1946,6 +1922,7 @@ version = "0.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"crossbeam",
|
||||
"foldhash",
|
||||
"jod-thread",
|
||||
"libc",
|
||||
"lsp-server",
|
||||
@@ -1958,7 +1935,6 @@ dependencies = [
|
||||
"ruff_python_ast",
|
||||
"ruff_source_file",
|
||||
"ruff_text_size",
|
||||
"rustc-hash 2.0.0",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"shellexpand",
|
||||
@@ -1988,13 +1964,13 @@ version = "0.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"crossbeam",
|
||||
"foldhash",
|
||||
"notify",
|
||||
"red_knot_python_semantic",
|
||||
"ruff_cache",
|
||||
"ruff_db",
|
||||
"ruff_python_ast",
|
||||
"ruff_text_size",
|
||||
"rustc-hash 2.0.0",
|
||||
"salsa",
|
||||
"thiserror",
|
||||
"tracing",
|
||||
@@ -2103,6 +2079,7 @@ dependencies = [
|
||||
"clearscreen",
|
||||
"colored",
|
||||
"filetime",
|
||||
"foldhash",
|
||||
"ignore",
|
||||
"insta",
|
||||
"insta-cmd",
|
||||
@@ -2125,7 +2102,6 @@ dependencies = [
|
||||
"ruff_source_file",
|
||||
"ruff_text_size",
|
||||
"ruff_workspace",
|
||||
"rustc-hash 2.0.0",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"shellexpand",
|
||||
@@ -2184,6 +2160,7 @@ dependencies = [
|
||||
"countme",
|
||||
"dashmap 6.0.1",
|
||||
"filetime",
|
||||
"foldhash",
|
||||
"ignore",
|
||||
"insta",
|
||||
"matchit",
|
||||
@@ -2195,7 +2172,6 @@ dependencies = [
|
||||
"ruff_python_trivia",
|
||||
"ruff_source_file",
|
||||
"ruff_text_size",
|
||||
"rustc-hash 2.0.0",
|
||||
"salsa",
|
||||
"tempfile",
|
||||
"thiserror",
|
||||
@@ -2261,10 +2237,10 @@ name = "ruff_formatter"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"drop_bomb",
|
||||
"foldhash",
|
||||
"ruff_cache",
|
||||
"ruff_macros",
|
||||
"ruff_text_size",
|
||||
"rustc-hash 2.0.0",
|
||||
"schemars",
|
||||
"serde",
|
||||
"static_assertions",
|
||||
@@ -2292,6 +2268,7 @@ dependencies = [
|
||||
"clap",
|
||||
"colored",
|
||||
"fern",
|
||||
"foldhash",
|
||||
"glob",
|
||||
"globset",
|
||||
"imperative",
|
||||
@@ -2324,7 +2301,6 @@ dependencies = [
|
||||
"ruff_python_trivia",
|
||||
"ruff_source_file",
|
||||
"ruff_text_size",
|
||||
"rustc-hash 2.0.0",
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -2379,6 +2355,7 @@ dependencies = [
|
||||
"aho-corasick",
|
||||
"bitflags 2.6.0",
|
||||
"compact_str",
|
||||
"foldhash",
|
||||
"is-macro",
|
||||
"itertools 0.13.0",
|
||||
"once_cell",
|
||||
@@ -2387,7 +2364,6 @@ dependencies = [
|
||||
"ruff_python_trivia",
|
||||
"ruff_source_file",
|
||||
"ruff_text_size",
|
||||
"rustc-hash 2.0.0",
|
||||
"schemars",
|
||||
"serde",
|
||||
]
|
||||
@@ -2422,6 +2398,7 @@ dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
"countme",
|
||||
"foldhash",
|
||||
"insta",
|
||||
"itertools 0.13.0",
|
||||
"memchr",
|
||||
@@ -2435,7 +2412,6 @@ dependencies = [
|
||||
"ruff_python_trivia",
|
||||
"ruff_source_file",
|
||||
"ruff_text_size",
|
||||
"rustc-hash 2.0.0",
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -2476,13 +2452,13 @@ dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"bstr",
|
||||
"compact_str",
|
||||
"foldhash",
|
||||
"insta",
|
||||
"memchr",
|
||||
"ruff_python_ast",
|
||||
"ruff_python_trivia",
|
||||
"ruff_source_file",
|
||||
"ruff_text_size",
|
||||
"rustc-hash 2.0.0",
|
||||
"static_assertions",
|
||||
"unicode-ident",
|
||||
"unicode-normalization",
|
||||
@@ -2505,6 +2481,7 @@ name = "ruff_python_semantic"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"foldhash",
|
||||
"is-macro",
|
||||
"ruff_cache",
|
||||
"ruff_index",
|
||||
@@ -2514,7 +2491,6 @@ dependencies = [
|
||||
"ruff_python_stdlib",
|
||||
"ruff_source_file",
|
||||
"ruff_text_size",
|
||||
"rustc-hash 2.0.0",
|
||||
"schemars",
|
||||
"serde",
|
||||
]
|
||||
@@ -2553,6 +2529,7 @@ version = "0.2.2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"crossbeam",
|
||||
"foldhash",
|
||||
"ignore",
|
||||
"insta",
|
||||
"jod-thread",
|
||||
@@ -2572,7 +2549,6 @@ dependencies = [
|
||||
"ruff_source_file",
|
||||
"ruff_text_size",
|
||||
"ruff_workspace",
|
||||
"rustc-hash 2.0.0",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"shellexpand",
|
||||
@@ -2632,6 +2608,7 @@ dependencies = [
|
||||
"anyhow",
|
||||
"colored",
|
||||
"etcetera",
|
||||
"foldhash",
|
||||
"glob",
|
||||
"globset",
|
||||
"ignore",
|
||||
@@ -2651,7 +2628,6 @@ dependencies = [
|
||||
"ruff_python_formatter",
|
||||
"ruff_python_semantic",
|
||||
"ruff_source_file",
|
||||
"rustc-hash 2.0.0",
|
||||
"schemars",
|
||||
"serde",
|
||||
"shellexpand",
|
||||
@@ -2829,9 +2805,9 @@ checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.208"
|
||||
version = "1.0.206"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cff085d2cb684faa248efb494c39b68e522822ac0de72ccf08109abde717cfb2"
|
||||
checksum = "5b3e4cd94123dd520a128bcd11e34d9e9e423e7e3e50425cb1b4b1e3549d0284"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
@@ -2849,9 +2825,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.208"
|
||||
version = "1.0.206"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24008e81ff7613ed8e5ba0cfaf24e2c2f1e5b8a0495711e44fcd4882fca62bcf"
|
||||
checksum = "fabfb6138d2383ea8208cf98ccf69cdfb1aff4088460681d84189aa259762f97"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -2871,9 +2847,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.125"
|
||||
version = "1.0.124"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "83c8e735a073ccf5be70aa8066aa984eaf2fa000db6c8d0100ae605b366d31ed"
|
||||
checksum = "66ad62847a56b3dba58cc891acd13884b9c61138d330c0d7b6181713d4fce38d"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
@@ -3032,9 +3008,9 @@ checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.75"
|
||||
version = "2.0.74"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f6af063034fc1935ede7be0122941bafa9bacb949334d090b77ca98b5817c7d9"
|
||||
checksum = "1fceb41e3d546d0bd83421d3409b1460cc7444cd389341a4c880fe7a042cb3d7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -3556,20 +3532,19 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.93"
|
||||
version = "0.2.92"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5"
|
||||
checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
"wasm-bindgen-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-backend"
|
||||
version = "0.2.93"
|
||||
version = "0.2.92"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b"
|
||||
checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"log",
|
||||
@@ -3582,9 +3557,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-futures"
|
||||
version = "0.4.43"
|
||||
version = "0.4.42"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "61e9300f63a621e96ed275155c108eb6f843b6a26d053f122ab69724559dc8ed"
|
||||
checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"js-sys",
|
||||
@@ -3594,9 +3569,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.93"
|
||||
version = "0.2.92"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf"
|
||||
checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
@@ -3604,9 +3579,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.93"
|
||||
version = "0.2.92"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836"
|
||||
checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -3617,19 +3592,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.93"
|
||||
version = "0.2.92"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484"
|
||||
checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-test"
|
||||
version = "0.3.43"
|
||||
version = "0.3.42"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68497a05fb21143a08a7d24fc81763384a3072ee43c44e86aad1744d6adef9d9"
|
||||
checksum = "d9bf62a58e0780af3e852044583deee40983e5886da43a271dd772379987667b"
|
||||
dependencies = [
|
||||
"console_error_panic_hook",
|
||||
"js-sys",
|
||||
"minicov",
|
||||
"scoped-tls",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
@@ -3638,9 +3612,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-test-macro"
|
||||
version = "0.3.43"
|
||||
version = "0.3.42"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4b8220be1fa9e4c889b30fd207d4906657e7e90b12e0e6b0c8b8d8709f5de021"
|
||||
checksum = "b7f89739351a2e03cb94beb799d47fb2cac01759b40ec441f7de39b00cbf7ef0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
||||
@@ -105,9 +105,10 @@ pyproject-toml = { version = "0.9.0" }
|
||||
quick-junit = { version = "0.4.0" }
|
||||
quote = { version = "1.0.23" }
|
||||
rand = { version = "0.8.5" }
|
||||
rustc-hash = { version = "2.0.0" }
|
||||
rayon = { version = "1.10.0" }
|
||||
regex = { version = "1.10.2" }
|
||||
rustc-hash = { version = "2.0.0" }
|
||||
foldhash = { version = "0.1.0" }
|
||||
salsa = { git = "https://github.com/MichaReiser/salsa.git", tag = "red-knot-0.0.1" }
|
||||
schemars = { version = "0.8.16" }
|
||||
seahash = { version = "4.1.0" }
|
||||
|
||||
@@ -29,8 +29,6 @@ salsa = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
rustc-hash = { workspace = true }
|
||||
hashbrown = { workspace = true }
|
||||
smallvec = { workspace = true }
|
||||
static_assertions = { workspace = true }
|
||||
|
||||
[build-dependencies]
|
||||
path-slash = { workspace = true }
|
||||
|
||||
@@ -16,9 +16,10 @@ use crate::semantic_index::expression::Expression;
|
||||
use crate::semantic_index::symbol::{
|
||||
FileScopeId, NodeWithScopeKey, NodeWithScopeRef, Scope, ScopeId, ScopedSymbolId, SymbolTable,
|
||||
};
|
||||
use crate::semantic_index::use_def::UseDefMap;
|
||||
use crate::Db;
|
||||
|
||||
pub(crate) use self::use_def::UseDefMap;
|
||||
|
||||
pub mod ast_ids;
|
||||
mod builder;
|
||||
pub mod definition;
|
||||
@@ -26,8 +27,6 @@ pub mod expression;
|
||||
pub mod symbol;
|
||||
mod use_def;
|
||||
|
||||
pub(crate) use self::use_def::{DefinitionWithConstraints, DefinitionWithConstraintsIterator};
|
||||
|
||||
type SymbolMap = hashbrown::HashMap<ScopedSymbolId, (), ()>;
|
||||
|
||||
/// Returns the semantic index for `file`.
|
||||
@@ -311,29 +310,12 @@ mod tests {
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
|
||||
use crate::db::tests::TestDb;
|
||||
use crate::semantic_index::ast_ids::{HasScopedUseId, ScopedUseId};
|
||||
use crate::semantic_index::definition::{Definition, DefinitionKind};
|
||||
use crate::semantic_index::symbol::{
|
||||
FileScopeId, Scope, ScopeKind, ScopedSymbolId, SymbolTable,
|
||||
};
|
||||
use crate::semantic_index::use_def::UseDefMap;
|
||||
use crate::semantic_index::ast_ids::HasScopedUseId;
|
||||
use crate::semantic_index::definition::DefinitionKind;
|
||||
use crate::semantic_index::symbol::{FileScopeId, Scope, ScopeKind, SymbolTable};
|
||||
use crate::semantic_index::{global_scope, semantic_index, symbol_table, use_def_map};
|
||||
use crate::Db;
|
||||
|
||||
impl UseDefMap<'_> {
|
||||
fn first_public_definition(&self, symbol: ScopedSymbolId) -> Option<Definition<'_>> {
|
||||
self.public_definitions(symbol)
|
||||
.next()
|
||||
.map(|constrained_definition| constrained_definition.definition)
|
||||
}
|
||||
|
||||
fn first_use_definition(&self, use_id: ScopedUseId) -> Option<Definition<'_>> {
|
||||
self.use_definitions(use_id)
|
||||
.next()
|
||||
.map(|constrained_definition| constrained_definition.definition)
|
||||
}
|
||||
}
|
||||
|
||||
struct TestCase {
|
||||
db: TestDb,
|
||||
file: File,
|
||||
@@ -392,7 +374,9 @@ mod tests {
|
||||
let foo = global_table.symbol_id_by_name("foo").unwrap();
|
||||
|
||||
let use_def = use_def_map(&db, scope);
|
||||
let definition = use_def.first_public_definition(foo).unwrap();
|
||||
let [definition] = use_def.public_definitions(foo) else {
|
||||
panic!("expected one definition");
|
||||
};
|
||||
assert!(matches!(definition.node(&db), DefinitionKind::Import(_)));
|
||||
}
|
||||
|
||||
@@ -427,13 +411,13 @@ mod tests {
|
||||
);
|
||||
|
||||
let use_def = use_def_map(&db, scope);
|
||||
let definition = use_def
|
||||
.first_public_definition(
|
||||
global_table
|
||||
.symbol_id_by_name("foo")
|
||||
.expect("symbol to exist"),
|
||||
)
|
||||
.unwrap();
|
||||
let [definition] = use_def.public_definitions(
|
||||
global_table
|
||||
.symbol_id_by_name("foo")
|
||||
.expect("symbol to exist"),
|
||||
) else {
|
||||
panic!("expected one definition");
|
||||
};
|
||||
assert!(matches!(
|
||||
definition.node(&db),
|
||||
DefinitionKind::ImportFrom(_)
|
||||
@@ -454,9 +438,11 @@ mod tests {
|
||||
"a symbol used but not defined in a scope should have only the used flag"
|
||||
);
|
||||
let use_def = use_def_map(&db, scope);
|
||||
let definition = use_def
|
||||
.first_public_definition(global_table.symbol_id_by_name("x").expect("symbol exists"))
|
||||
.unwrap();
|
||||
let [definition] =
|
||||
use_def.public_definitions(global_table.symbol_id_by_name("x").expect("symbol exists"))
|
||||
else {
|
||||
panic!("expected one definition");
|
||||
};
|
||||
assert!(matches!(
|
||||
definition.node(&db),
|
||||
DefinitionKind::Assignment(_)
|
||||
@@ -491,9 +477,11 @@ y = 2
|
||||
assert_eq!(names(&class_table), vec!["x"]);
|
||||
|
||||
let use_def = index.use_def_map(class_scope_id);
|
||||
let definition = use_def
|
||||
.first_public_definition(class_table.symbol_id_by_name("x").expect("symbol exists"))
|
||||
.unwrap();
|
||||
let [definition] =
|
||||
use_def.public_definitions(class_table.symbol_id_by_name("x").expect("symbol exists"))
|
||||
else {
|
||||
panic!("expected one definition");
|
||||
};
|
||||
assert!(matches!(
|
||||
definition.node(&db),
|
||||
DefinitionKind::Assignment(_)
|
||||
@@ -527,13 +515,13 @@ y = 2
|
||||
assert_eq!(names(&function_table), vec!["x"]);
|
||||
|
||||
let use_def = index.use_def_map(function_scope_id);
|
||||
let definition = use_def
|
||||
.first_public_definition(
|
||||
function_table
|
||||
.symbol_id_by_name("x")
|
||||
.expect("symbol exists"),
|
||||
)
|
||||
.unwrap();
|
||||
let [definition] = use_def.public_definitions(
|
||||
function_table
|
||||
.symbol_id_by_name("x")
|
||||
.expect("symbol exists"),
|
||||
) else {
|
||||
panic!("expected one definition");
|
||||
};
|
||||
assert!(matches!(
|
||||
definition.node(&db),
|
||||
DefinitionKind::Assignment(_)
|
||||
@@ -569,26 +557,26 @@ def f(a: str, /, b: str, c: int = 1, *args, d: int = 2, **kwargs):
|
||||
|
||||
let use_def = index.use_def_map(function_scope_id);
|
||||
for name in ["a", "b", "c", "d"] {
|
||||
let definition = use_def
|
||||
.first_public_definition(
|
||||
function_table
|
||||
.symbol_id_by_name(name)
|
||||
.expect("symbol exists"),
|
||||
)
|
||||
.unwrap();
|
||||
let [definition] = use_def.public_definitions(
|
||||
function_table
|
||||
.symbol_id_by_name(name)
|
||||
.expect("symbol exists"),
|
||||
) else {
|
||||
panic!("Expected parameter definition for {name}");
|
||||
};
|
||||
assert!(matches!(
|
||||
definition.node(&db),
|
||||
DefinitionKind::ParameterWithDefault(_)
|
||||
));
|
||||
}
|
||||
for name in ["args", "kwargs"] {
|
||||
let definition = use_def
|
||||
.first_public_definition(
|
||||
function_table
|
||||
.symbol_id_by_name(name)
|
||||
.expect("symbol exists"),
|
||||
)
|
||||
.unwrap();
|
||||
let [definition] = use_def.public_definitions(
|
||||
function_table
|
||||
.symbol_id_by_name(name)
|
||||
.expect("symbol exists"),
|
||||
) else {
|
||||
panic!("Expected parameter definition for {name}");
|
||||
};
|
||||
assert!(matches!(definition.node(&db), DefinitionKind::Parameter(_)));
|
||||
}
|
||||
}
|
||||
@@ -617,22 +605,22 @@ def f(a: str, /, b: str, c: int = 1, *args, d: int = 2, **kwargs):
|
||||
|
||||
let use_def = index.use_def_map(lambda_scope_id);
|
||||
for name in ["a", "b", "c", "d"] {
|
||||
let definition = use_def
|
||||
.first_public_definition(
|
||||
lambda_table.symbol_id_by_name(name).expect("symbol exists"),
|
||||
)
|
||||
.unwrap();
|
||||
let [definition] = use_def
|
||||
.public_definitions(lambda_table.symbol_id_by_name(name).expect("symbol exists"))
|
||||
else {
|
||||
panic!("Expected parameter definition for {name}");
|
||||
};
|
||||
assert!(matches!(
|
||||
definition.node(&db),
|
||||
DefinitionKind::ParameterWithDefault(_)
|
||||
));
|
||||
}
|
||||
for name in ["args", "kwargs"] {
|
||||
let definition = use_def
|
||||
.first_public_definition(
|
||||
lambda_table.symbol_id_by_name(name).expect("symbol exists"),
|
||||
)
|
||||
.unwrap();
|
||||
let [definition] = use_def
|
||||
.public_definitions(lambda_table.symbol_id_by_name(name).expect("symbol exists"))
|
||||
else {
|
||||
panic!("Expected parameter definition for {name}");
|
||||
};
|
||||
assert!(matches!(definition.node(&db), DefinitionKind::Parameter(_)));
|
||||
}
|
||||
}
|
||||
@@ -703,7 +691,9 @@ def f(a: str, /, b: str, c: int = 1, *args, d: int = 2, **kwargs):
|
||||
let element_use_id =
|
||||
element.scoped_use_id(&db, comprehension_scope_id.to_scope_id(&db, file));
|
||||
|
||||
let definition = use_def.first_use_definition(element_use_id).unwrap();
|
||||
let [definition] = use_def.use_definitions(element_use_id) else {
|
||||
panic!("expected one definition")
|
||||
};
|
||||
let DefinitionKind::Comprehension(comprehension) = definition.node(&db) else {
|
||||
panic!("expected generator definition")
|
||||
};
|
||||
@@ -800,13 +790,13 @@ def func():
|
||||
assert_eq!(names(&func2_table), vec!["y"]);
|
||||
|
||||
let use_def = index.use_def_map(FileScopeId::global());
|
||||
let definition = use_def
|
||||
.first_public_definition(
|
||||
global_table
|
||||
.symbol_id_by_name("func")
|
||||
.expect("symbol exists"),
|
||||
)
|
||||
.unwrap();
|
||||
let [definition] = use_def.public_definitions(
|
||||
global_table
|
||||
.symbol_id_by_name("func")
|
||||
.expect("symbol exists"),
|
||||
) else {
|
||||
panic!("expected one definition");
|
||||
};
|
||||
assert!(matches!(definition.node(&db), DefinitionKind::Function(_)));
|
||||
}
|
||||
|
||||
@@ -907,7 +897,9 @@ class C[T]:
|
||||
};
|
||||
let x_use_id = x_use_expr_name.scoped_use_id(&db, scope);
|
||||
let use_def = use_def_map(&db, scope);
|
||||
let definition = use_def.first_use_definition(x_use_id).unwrap();
|
||||
let [definition] = use_def.use_definitions(x_use_id) else {
|
||||
panic!("expected one definition");
|
||||
};
|
||||
let DefinitionKind::Assignment(assignment) = definition.node(&db) else {
|
||||
panic!("should be an assignment definition")
|
||||
};
|
||||
|
||||
@@ -8,7 +8,6 @@ use ruff_index::IndexVec;
|
||||
use ruff_python_ast as ast;
|
||||
use ruff_python_ast::name::Name;
|
||||
use ruff_python_ast::visitor::{walk_expr, walk_stmt, Visitor};
|
||||
use ruff_python_ast::AnyParameterRef;
|
||||
|
||||
use crate::ast_node_ref::AstNodeRef;
|
||||
use crate::semantic_index::ast_ids::node_key::ExpressionNodeKey;
|
||||
@@ -156,7 +155,7 @@ impl<'db> SemanticIndexBuilder<'db> {
|
||||
self.current_use_def_map_mut().restore(state);
|
||||
}
|
||||
|
||||
fn flow_merge(&mut self, state: FlowSnapshot) {
|
||||
fn flow_merge(&mut self, state: &FlowSnapshot) {
|
||||
self.current_use_def_map_mut().merge(state);
|
||||
}
|
||||
|
||||
@@ -196,16 +195,9 @@ impl<'db> SemanticIndexBuilder<'db> {
|
||||
definition
|
||||
}
|
||||
|
||||
fn add_constraint(&mut self, constraint_node: &ast::Expr) -> Expression<'db> {
|
||||
let expression = self.add_standalone_expression(constraint_node);
|
||||
self.current_use_def_map_mut().record_constraint(expression);
|
||||
|
||||
expression
|
||||
}
|
||||
|
||||
/// Record an expression that needs to be a Salsa ingredient, because we need to infer its type
|
||||
/// standalone (type narrowing tests, RHS of an assignment.)
|
||||
fn add_standalone_expression(&mut self, expression_node: &ast::Expr) -> Expression<'db> {
|
||||
fn add_standalone_expression(&mut self, expression_node: &ast::Expr) {
|
||||
let expression = Expression::new(
|
||||
self.db,
|
||||
self.file,
|
||||
@@ -218,7 +210,6 @@ impl<'db> SemanticIndexBuilder<'db> {
|
||||
);
|
||||
self.expressions_by_node
|
||||
.insert(expression_node.into(), expression);
|
||||
expression
|
||||
}
|
||||
|
||||
fn with_type_params(
|
||||
@@ -310,23 +301,6 @@ impl<'db> SemanticIndexBuilder<'db> {
|
||||
}
|
||||
}
|
||||
|
||||
fn declare_parameter(&mut self, parameter: AnyParameterRef) {
|
||||
let symbol =
|
||||
self.add_or_update_symbol(parameter.name().id().clone(), SymbolFlags::IS_DEFINED);
|
||||
|
||||
let definition = self.add_definition(symbol, parameter);
|
||||
|
||||
if let AnyParameterRef::NonVariadic(with_default) = parameter {
|
||||
// Insert a mapping from the parameter to the same definition.
|
||||
// This ensures that calling `HasTy::ty` on the inner parameter returns
|
||||
// a valid type (and doesn't panic)
|
||||
self.definitions_by_node.insert(
|
||||
DefinitionNodeRef::from(AnyParameterRef::Variadic(&with_default.parameter)).key(),
|
||||
definition,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn build(mut self) -> SemanticIndex<'db> {
|
||||
let module = self.module;
|
||||
self.visit_body(module.suite());
|
||||
@@ -417,7 +391,11 @@ where
|
||||
|
||||
// Add symbols and definitions for the parameters to the function scope.
|
||||
for parameter in &*function_def.parameters {
|
||||
builder.declare_parameter(parameter);
|
||||
let symbol = builder.add_or_update_symbol(
|
||||
parameter.name().id().clone(),
|
||||
SymbolFlags::IS_DEFINED,
|
||||
);
|
||||
builder.add_definition(symbol, parameter);
|
||||
}
|
||||
|
||||
builder.visit_body(&function_def.body);
|
||||
@@ -498,7 +476,6 @@ where
|
||||
ast::Stmt::If(node) => {
|
||||
self.visit_expr(&node.test);
|
||||
let pre_if = self.flow_snapshot();
|
||||
self.add_constraint(&node.test);
|
||||
self.visit_body(&node.body);
|
||||
let mut post_clauses: Vec<FlowSnapshot> = vec![];
|
||||
for clause in &node.elif_else_clauses {
|
||||
@@ -511,7 +488,7 @@ where
|
||||
self.visit_elif_else_clause(clause);
|
||||
}
|
||||
for post_clause_state in post_clauses {
|
||||
self.flow_merge(post_clause_state);
|
||||
self.flow_merge(&post_clause_state);
|
||||
}
|
||||
let has_else = node
|
||||
.elif_else_clauses
|
||||
@@ -520,7 +497,7 @@ where
|
||||
if !has_else {
|
||||
// if there's no else clause, then it's possible we took none of the branches,
|
||||
// and the pre_if state can reach here
|
||||
self.flow_merge(pre_if);
|
||||
self.flow_merge(&pre_if);
|
||||
}
|
||||
}
|
||||
ast::Stmt::While(node) => {
|
||||
@@ -538,13 +515,13 @@ where
|
||||
|
||||
// We may execute the `else` clause without ever executing the body, so merge in
|
||||
// the pre-loop state before visiting `else`.
|
||||
self.flow_merge(pre_loop);
|
||||
self.flow_merge(&pre_loop);
|
||||
self.visit_body(&node.orelse);
|
||||
|
||||
// Breaking out of a while loop bypasses the `else` clause, so merge in the break
|
||||
// states after visiting `else`.
|
||||
for break_state in break_states {
|
||||
self.flow_merge(break_state);
|
||||
self.flow_merge(&break_state);
|
||||
}
|
||||
}
|
||||
ast::Stmt::Break(_) => {
|
||||
@@ -632,7 +609,11 @@ where
|
||||
// Add symbols and definitions for the parameters to the lambda scope.
|
||||
if let Some(parameters) = &lambda.parameters {
|
||||
for parameter in &**parameters {
|
||||
self.declare_parameter(parameter);
|
||||
let symbol = self.add_or_update_symbol(
|
||||
parameter.name().id().clone(),
|
||||
SymbolFlags::IS_DEFINED,
|
||||
);
|
||||
self.add_definition(symbol, parameter);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -650,7 +631,7 @@ where
|
||||
let post_body = self.flow_snapshot();
|
||||
self.flow_restore(pre_if);
|
||||
self.visit_expr(orelse);
|
||||
self.flow_merge(post_body);
|
||||
self.flow_merge(&post_body);
|
||||
}
|
||||
ast::Expr::ListComp(
|
||||
list_comprehension @ ast::ExprListComp {
|
||||
|
||||
@@ -248,10 +248,6 @@ impl AssignmentDefinitionKind {
|
||||
pub(crate) fn assignment(&self) -> &ast::StmtAssign {
|
||||
self.assignment.node()
|
||||
}
|
||||
|
||||
pub(crate) fn target(&self) -> &ast::ExprName {
|
||||
self.target.node()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
|
||||
|
||||
@@ -21,7 +21,7 @@ pub(crate) struct Expression<'db> {
|
||||
/// The expression node.
|
||||
#[no_eq]
|
||||
#[return_ref]
|
||||
pub(crate) node_ref: AstNodeRef<ast::Expr>,
|
||||
pub(crate) node: AstNodeRef<ast::Expr>,
|
||||
|
||||
#[no_eq]
|
||||
count: countme::Count<Expression<'static>>,
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
//! Build a map from each use of a symbol to the definitions visible from that use, and the
|
||||
//! type-narrowing constraints that apply to each definition.
|
||||
//! Build a map from each use of a symbol to the definitions visible from that use.
|
||||
//!
|
||||
//! Let's take this code sample:
|
||||
//!
|
||||
@@ -7,7 +6,7 @@
|
||||
//! x = 1
|
||||
//! x = 2
|
||||
//! y = x
|
||||
//! if y is not None:
|
||||
//! if flag:
|
||||
//! x = 3
|
||||
//! else:
|
||||
//! x = 4
|
||||
@@ -35,8 +34,8 @@
|
||||
//! [`AstIds`](crate::semantic_index::ast_ids::AstIds) we number all uses (that means a `Name` node
|
||||
//! with `Load` context) so we have a `ScopedUseId` to efficiently represent each use.
|
||||
//!
|
||||
//! Another case we need to handle is when a symbol is referenced from a different scope (the most
|
||||
//! obvious example of this is an import). We call this "public" use of a symbol. So the other
|
||||
//! The other case we need to handle is when a symbol is referenced from a different scope (the
|
||||
//! most obvious example of this is an import). We call this "public" use of a symbol. So the other
|
||||
//! question we need to be able to answer is, what are the publicly-visible definitions of each
|
||||
//! symbol?
|
||||
//!
|
||||
@@ -54,55 +53,42 @@
|
||||
//! start.)
|
||||
//!
|
||||
//! So this means that the publicly-visible definitions of a symbol are the definitions still
|
||||
//! visible at the end of the scope; effectively we have an implicit "use" of every symbol at the
|
||||
//! end of the scope.
|
||||
//! visible at the end of the scope.
|
||||
//!
|
||||
//! We also need to know, for a given definition of a symbol, what type-narrowing constraints apply
|
||||
//! to it. For instance, in this code sample:
|
||||
//!
|
||||
//! ```python
|
||||
//! x = 1 if flag else None
|
||||
//! if x is not None:
|
||||
//! y = x
|
||||
//! ```
|
||||
//!
|
||||
//! At the use of `x` in `y = x`, the visible definition of `x` is `1 if flag else None`, which
|
||||
//! would infer as the type `Literal[1] | None`. But the constraint `x is not None` dominates this
|
||||
//! use, which means we can rule out the possibility that `x` is `None` here, which should give us
|
||||
//! the type `Literal[1]` for this use.
|
||||
//!
|
||||
//! The data structure we build to answer these questions is the `UseDefMap`. It has a
|
||||
//! The data structure we build to answer these two questions is the `UseDefMap`. It has a
|
||||
//! `definitions_by_use` vector indexed by [`ScopedUseId`] and a `public_definitions` vector
|
||||
//! indexed by [`ScopedSymbolId`]. The values in each of these vectors are (in principle) a list of
|
||||
//! visible definitions at that use, or at the end of the scope for that symbol, with a list of the
|
||||
//! dominating constraints for each of those definitions.
|
||||
//! visible definitions at that use, or at the end of the scope for that symbol.
|
||||
//!
|
||||
//! In order to avoid vectors-of-vectors-of-vectors and all the allocations that would entail, we
|
||||
//! don't actually store these "list of visible definitions" as a vector of [`Definition`].
|
||||
//! Instead, the values in `definitions_by_use` and `public_definitions` are a [`SymbolState`]
|
||||
//! struct which uses bit-sets to track definitions and constraints in terms of
|
||||
//! [`ScopedDefinitionId`] and [`ScopedConstraintId`], which are indices into the `all_definitions`
|
||||
//! and `all_constraints` indexvecs in the [`UseDefMap`].
|
||||
//! In order to avoid vectors-of-vectors and all the allocations that would entail, we don't
|
||||
//! actually store these "list of visible definitions" as a vector of [`Definition`] IDs. Instead,
|
||||
//! the values in `definitions_by_use` and `public_definitions` are a [`Definitions`] struct that
|
||||
//! keeps a [`Range`] into a third vector of [`Definition`] IDs, `all_definitions`. The trick with
|
||||
//! this representation is that it requires that the definitions visible at any given use of a
|
||||
//! symbol are stored sequentially in `all_definitions`.
|
||||
//!
|
||||
//! There is another special kind of possible "definition" for a symbol: there might be a path from
|
||||
//! the scope entry to a given use in which the symbol is never bound.
|
||||
//! There is another special kind of possible "definition" for a symbol: it might be unbound in the
|
||||
//! scope. (This isn't equivalent to "zero visible definitions", since we may go through an `if`
|
||||
//! that has a definition for the symbol, leaving us with one visible definition, but still also
|
||||
//! the "unbound" possibility, since we might not have taken the `if` branch.)
|
||||
//!
|
||||
//! The simplest way to model "unbound" would be as an actual [`Definition`] itself: the initial
|
||||
//! visible [`Definition`] for each symbol in a scope. But actually modeling it this way would
|
||||
//! unnecessarily increase the number of [`Definition`] that Salsa must track. Since "unbound" is a
|
||||
//! dramatically increase the number of [`Definition`] that Salsa must track. Since "unbound" is a
|
||||
//! special definition in that all symbols share it, and it doesn't have any additional per-symbol
|
||||
//! state, and constraints are irrelevant to it, we can represent it more efficiently: we use the
|
||||
//! `may_be_unbound` boolean on the [`SymbolState`] struct. If this flag is `true`, it means the
|
||||
//! symbol/use really has one additional visible "definition", which is the unbound state. If this
|
||||
//! flag is `false`, it means we've eliminated the possibility of unbound: every path we've
|
||||
//! followed includes a definition for this symbol.
|
||||
//! state, we can represent it more efficiently: we use the `may_be_unbound` boolean on the
|
||||
//! [`Definitions`] struct. If this flag is `true`, it means the symbol/use really has one
|
||||
//! additional visible "definition", which is the unbound state. If this flag is `false`, it means
|
||||
//! we've eliminated the possibility of unbound: every path we've followed includes a definition
|
||||
//! for this symbol.
|
||||
//!
|
||||
//! To build a [`UseDefMap`], the [`UseDefMapBuilder`] is notified of each new use, definition, and
|
||||
//! constraint as they are encountered by the
|
||||
//! To build a [`UseDefMap`], the [`UseDefMapBuilder`] is notified of each new use and definition
|
||||
//! as they are encountered by the
|
||||
//! [`SemanticIndexBuilder`](crate::semantic_index::builder::SemanticIndexBuilder) AST visit. For
|
||||
//! each symbol, the builder tracks the `SymbolState` for that symbol. When we hit a use of a
|
||||
//! symbol, it records the current state for that symbol for that use. When we reach the end of the
|
||||
//! scope, it records the state for each symbol as the public definitions of that symbol.
|
||||
//! each symbol, the builder tracks the currently-visible definitions for that symbol. When we hit
|
||||
//! a use of a symbol, it records the currently-visible definitions for that symbol as the visible
|
||||
//! definitions for that use. When we reach the end of the scope, it records the currently-visible
|
||||
//! definitions for each symbol as the public definitions of that symbol.
|
||||
//!
|
||||
//! Let's walk through the above example. Initially we record for `x` that it has no visible
|
||||
//! definitions, and may be unbound. When we see `x = 1`, we record that as the sole visible
|
||||
@@ -112,11 +98,10 @@
|
||||
//!
|
||||
//! Then we hit the `if` branch. We visit the `test` node (`flag` in this case), since that will
|
||||
//! happen regardless. Then we take a pre-branch snapshot of the currently visible definitions for
|
||||
//! all symbols, which we'll need later. Then we record `flag` as a possible constraint on the
|
||||
//! currently visible definition (`x = 2`), and go ahead and visit the `if` body. When we see `x =
|
||||
//! 3`, it replaces `x = 2` (constrained by `flag`) as the sole visible definition of `x`. At the
|
||||
//! end of the `if` body, we take another snapshot of the currently-visible definitions; we'll call
|
||||
//! this the post-if-body snapshot.
|
||||
//! all symbols, which we'll need later. Then we go ahead and visit the `if` body. When we see `x =
|
||||
//! 3`, it replaces `x = 2` as the sole visible definition of `x`. At the end of the `if` body, we
|
||||
//! take another snapshot of the currently-visible definitions; we'll call this the post-if-body
|
||||
//! snapshot.
|
||||
//!
|
||||
//! Now we need to visit the `else` clause. The conditions when entering the `else` clause should
|
||||
//! be the pre-if conditions; if we are entering the `else` clause, we know that the `if` test
|
||||
@@ -140,142 +125,98 @@
|
||||
//! (In the future we may have some other questions we want to answer as well, such as "is this
|
||||
//! definition used?", which will require tracking a bit more info in our map, e.g. a "used" bit
|
||||
//! for each [`Definition`] which is flipped to true when we record that definition for a use.)
|
||||
use self::symbol_state::{
|
||||
ConstraintIdIterator, DefinitionIdWithConstraintsIterator, ScopedConstraintId,
|
||||
ScopedDefinitionId, SymbolState,
|
||||
};
|
||||
use crate::semantic_index::ast_ids::ScopedUseId;
|
||||
use crate::semantic_index::definition::Definition;
|
||||
use crate::semantic_index::expression::Expression;
|
||||
use crate::semantic_index::symbol::ScopedSymbolId;
|
||||
use ruff_index::IndexVec;
|
||||
use std::ops::Range;
|
||||
|
||||
mod bitset;
|
||||
mod symbol_state;
|
||||
|
||||
/// Applicable definitions and constraints for every use of a name.
|
||||
/// All definitions that can reach a given use of a name.
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub(crate) struct UseDefMap<'db> {
|
||||
/// Array of [`Definition`] in this scope.
|
||||
all_definitions: IndexVec<ScopedDefinitionId, Definition<'db>>,
|
||||
// TODO store constraints with definitions for type narrowing
|
||||
/// Definition IDs array for `definitions_by_use` and `public_definitions` to slice into.
|
||||
all_definitions: Vec<Definition<'db>>,
|
||||
|
||||
/// Array of constraints (as [`Expression`]) in this scope.
|
||||
all_constraints: IndexVec<ScopedConstraintId, Expression<'db>>,
|
||||
/// Definitions that can reach a [`ScopedUseId`].
|
||||
definitions_by_use: IndexVec<ScopedUseId, Definitions>,
|
||||
|
||||
/// [`SymbolState`] visible at a [`ScopedUseId`].
|
||||
definitions_by_use: IndexVec<ScopedUseId, SymbolState>,
|
||||
|
||||
/// [`SymbolState`] visible at end of scope for each symbol.
|
||||
public_definitions: IndexVec<ScopedSymbolId, SymbolState>,
|
||||
/// Definitions of each symbol visible at end of scope.
|
||||
public_definitions: IndexVec<ScopedSymbolId, Definitions>,
|
||||
}
|
||||
|
||||
impl<'db> UseDefMap<'db> {
|
||||
pub(crate) fn use_definitions(
|
||||
&self,
|
||||
use_id: ScopedUseId,
|
||||
) -> DefinitionWithConstraintsIterator<'_, 'db> {
|
||||
DefinitionWithConstraintsIterator {
|
||||
all_definitions: &self.all_definitions,
|
||||
all_constraints: &self.all_constraints,
|
||||
inner: self.definitions_by_use[use_id].visible_definitions(),
|
||||
}
|
||||
pub(crate) fn use_definitions(&self, use_id: ScopedUseId) -> &[Definition<'db>] {
|
||||
&self.all_definitions[self.definitions_by_use[use_id].definitions_range.clone()]
|
||||
}
|
||||
|
||||
pub(crate) fn use_may_be_unbound(&self, use_id: ScopedUseId) -> bool {
|
||||
self.definitions_by_use[use_id].may_be_unbound()
|
||||
self.definitions_by_use[use_id].may_be_unbound
|
||||
}
|
||||
|
||||
pub(crate) fn public_definitions(
|
||||
&self,
|
||||
symbol: ScopedSymbolId,
|
||||
) -> DefinitionWithConstraintsIterator<'_, 'db> {
|
||||
DefinitionWithConstraintsIterator {
|
||||
all_definitions: &self.all_definitions,
|
||||
all_constraints: &self.all_constraints,
|
||||
inner: self.public_definitions[symbol].visible_definitions(),
|
||||
}
|
||||
pub(crate) fn public_definitions(&self, symbol: ScopedSymbolId) -> &[Definition<'db>] {
|
||||
&self.all_definitions[self.public_definitions[symbol].definitions_range.clone()]
|
||||
}
|
||||
|
||||
pub(crate) fn public_may_be_unbound(&self, symbol: ScopedSymbolId) -> bool {
|
||||
self.public_definitions[symbol].may_be_unbound()
|
||||
self.public_definitions[symbol].may_be_unbound
|
||||
}
|
||||
}
|
||||
|
||||
/// Definitions visible for a symbol at a particular use (or end-of-scope).
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
struct Definitions {
|
||||
/// [`Range`] in `all_definitions` of the visible definition IDs.
|
||||
definitions_range: Range<usize>,
|
||||
/// Is the symbol possibly unbound at this point?
|
||||
may_be_unbound: bool,
|
||||
}
|
||||
|
||||
impl Definitions {
|
||||
/// The default state of a symbol is "no definitions, may be unbound", aka definitely-unbound.
|
||||
fn unbound() -> Self {
|
||||
Self {
|
||||
definitions_range: Range::default(),
|
||||
may_be_unbound: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Definitions {
|
||||
fn default() -> Self {
|
||||
Definitions::unbound()
|
||||
}
|
||||
}
|
||||
|
||||
/// A snapshot of the visible definitions for each symbol at a particular point in control flow.
|
||||
#[derive(Clone, Debug)]
|
||||
pub(super) struct FlowSnapshot {
|
||||
definitions_by_symbol: IndexVec<ScopedSymbolId, Definitions>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct DefinitionWithConstraintsIterator<'map, 'db> {
|
||||
all_definitions: &'map IndexVec<ScopedDefinitionId, Definition<'db>>,
|
||||
all_constraints: &'map IndexVec<ScopedConstraintId, Expression<'db>>,
|
||||
inner: DefinitionIdWithConstraintsIterator<'map>,
|
||||
}
|
||||
|
||||
impl<'map, 'db> Iterator for DefinitionWithConstraintsIterator<'map, 'db> {
|
||||
type Item = DefinitionWithConstraints<'map, 'db>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.inner
|
||||
.next()
|
||||
.map(|def_id_with_constraints| DefinitionWithConstraints {
|
||||
definition: self.all_definitions[def_id_with_constraints.definition],
|
||||
constraints: ConstraintsIterator {
|
||||
all_constraints: self.all_constraints,
|
||||
constraint_ids: def_id_with_constraints.constraint_ids,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl std::iter::FusedIterator for DefinitionWithConstraintsIterator<'_, '_> {}
|
||||
|
||||
pub(crate) struct DefinitionWithConstraints<'map, 'db> {
|
||||
pub(crate) definition: Definition<'db>,
|
||||
pub(crate) constraints: ConstraintsIterator<'map, 'db>,
|
||||
}
|
||||
|
||||
pub(crate) struct ConstraintsIterator<'map, 'db> {
|
||||
all_constraints: &'map IndexVec<ScopedConstraintId, Expression<'db>>,
|
||||
constraint_ids: ConstraintIdIterator<'map>,
|
||||
}
|
||||
|
||||
impl<'map, 'db> Iterator for ConstraintsIterator<'map, 'db> {
|
||||
type Item = Expression<'db>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.constraint_ids
|
||||
.next()
|
||||
.map(|constraint_id| self.all_constraints[constraint_id])
|
||||
}
|
||||
}
|
||||
|
||||
impl std::iter::FusedIterator for ConstraintsIterator<'_, '_> {}
|
||||
|
||||
/// A snapshot of the definitions and constraints state at a particular point in control flow.
|
||||
#[derive(Clone, Debug)]
|
||||
pub(super) struct FlowSnapshot {
|
||||
definitions_by_symbol: IndexVec<ScopedSymbolId, SymbolState>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub(super) struct UseDefMapBuilder<'db> {
|
||||
/// Append-only array of [`Definition`]; None is unbound.
|
||||
all_definitions: IndexVec<ScopedDefinitionId, Definition<'db>>,
|
||||
|
||||
/// Append-only array of constraints (as [`Expression`]).
|
||||
all_constraints: IndexVec<ScopedConstraintId, Expression<'db>>,
|
||||
/// Definition IDs array for `definitions_by_use` and `definitions_by_symbol` to slice into.
|
||||
all_definitions: Vec<Definition<'db>>,
|
||||
|
||||
/// Visible definitions at each so-far-recorded use.
|
||||
definitions_by_use: IndexVec<ScopedUseId, SymbolState>,
|
||||
definitions_by_use: IndexVec<ScopedUseId, Definitions>,
|
||||
|
||||
/// Currently visible definitions for each symbol.
|
||||
definitions_by_symbol: IndexVec<ScopedSymbolId, SymbolState>,
|
||||
definitions_by_symbol: IndexVec<ScopedSymbolId, Definitions>,
|
||||
}
|
||||
|
||||
impl<'db> UseDefMapBuilder<'db> {
|
||||
pub(super) fn new() -> Self {
|
||||
Self::default()
|
||||
Self {
|
||||
all_definitions: Vec::new(),
|
||||
definitions_by_use: IndexVec::new(),
|
||||
definitions_by_symbol: IndexVec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn add_symbol(&mut self, symbol: ScopedSymbolId) {
|
||||
let new_symbol = self.definitions_by_symbol.push(SymbolState::unbound());
|
||||
let new_symbol = self.definitions_by_symbol.push(Definitions::unbound());
|
||||
debug_assert_eq!(symbol, new_symbol);
|
||||
}
|
||||
|
||||
@@ -286,15 +227,13 @@ impl<'db> UseDefMapBuilder<'db> {
|
||||
) {
|
||||
// We have a new definition of a symbol; this replaces any previous definitions in this
|
||||
// path.
|
||||
let def_id = self.all_definitions.push(definition);
|
||||
self.definitions_by_symbol[symbol] = SymbolState::with(def_id);
|
||||
}
|
||||
|
||||
pub(super) fn record_constraint(&mut self, constraint: Expression<'db>) {
|
||||
let constraint_id = self.all_constraints.push(constraint);
|
||||
for definitions in &mut self.definitions_by_symbol {
|
||||
definitions.add_constraint(constraint_id);
|
||||
}
|
||||
let def_idx = self.all_definitions.len();
|
||||
self.all_definitions.push(definition);
|
||||
self.definitions_by_symbol[symbol] = Definitions {
|
||||
#[allow(clippy::range_plus_one)]
|
||||
definitions_range: def_idx..(def_idx + 1),
|
||||
may_be_unbound: false,
|
||||
};
|
||||
}
|
||||
|
||||
pub(super) fn record_use(&mut self, symbol: ScopedSymbolId, use_id: ScopedUseId) {
|
||||
@@ -326,15 +265,15 @@ impl<'db> UseDefMapBuilder<'db> {
|
||||
|
||||
// If the snapshot we are restoring is missing some symbols we've recorded since, we need
|
||||
// to fill them in so the symbol IDs continue to line up. Since they don't exist in the
|
||||
// snapshot, the correct state to fill them in with is "unbound".
|
||||
// snapshot, the correct state to fill them in with is "unbound", the default.
|
||||
self.definitions_by_symbol
|
||||
.resize(num_symbols, SymbolState::unbound());
|
||||
.resize(num_symbols, Definitions::unbound());
|
||||
}
|
||||
|
||||
/// Merge the given snapshot into the current state, reflecting that we might have taken either
|
||||
/// path to get here. The new visible-definitions state for each symbol should include
|
||||
/// definitions from both the prior state and the snapshot.
|
||||
pub(super) fn merge(&mut self, snapshot: FlowSnapshot) {
|
||||
pub(super) fn merge(&mut self, snapshot: &FlowSnapshot) {
|
||||
// The tricky thing about merging two Ranges pointing into `all_definitions` is that if the
|
||||
// two Ranges aren't already adjacent in `all_definitions`, we will have to copy at least
|
||||
// one or the other of the ranges to the end of `all_definitions` so as to make them
|
||||
@@ -348,26 +287,66 @@ impl<'db> UseDefMapBuilder<'db> {
|
||||
// greater than the number of known symbols in a previously-taken snapshot.
|
||||
debug_assert!(self.definitions_by_symbol.len() >= snapshot.definitions_by_symbol.len());
|
||||
|
||||
let mut snapshot_definitions_iter = snapshot.definitions_by_symbol.into_iter();
|
||||
for current in &mut self.definitions_by_symbol {
|
||||
if let Some(snapshot) = snapshot_definitions_iter.next() {
|
||||
current.merge(snapshot);
|
||||
} else {
|
||||
for (symbol_id, current) in self.definitions_by_symbol.iter_mut_enumerated() {
|
||||
let Some(snapshot) = snapshot.definitions_by_symbol.get(symbol_id) else {
|
||||
// Symbol not present in snapshot, so it's unbound from that path.
|
||||
current.add_unbound();
|
||||
current.may_be_unbound = true;
|
||||
continue;
|
||||
};
|
||||
|
||||
// If the symbol can be unbound in either predecessor, it can be unbound post-merge.
|
||||
current.may_be_unbound |= snapshot.may_be_unbound;
|
||||
|
||||
// Merge the definition ranges.
|
||||
let current = &mut current.definitions_range;
|
||||
let snapshot = &snapshot.definitions_range;
|
||||
|
||||
// We never create reversed ranges.
|
||||
debug_assert!(current.end >= current.start);
|
||||
debug_assert!(snapshot.end >= snapshot.start);
|
||||
|
||||
if current == snapshot {
|
||||
// Ranges already identical, nothing to do.
|
||||
} else if snapshot.is_empty() {
|
||||
// Merging from an empty range; nothing to do.
|
||||
} else if (*current).is_empty() {
|
||||
// Merging to an empty range; just use the incoming range.
|
||||
*current = snapshot.clone();
|
||||
} else if snapshot.end >= current.start && snapshot.start <= current.end {
|
||||
// Ranges are adjacent or overlapping, merge them in-place.
|
||||
*current = current.start.min(snapshot.start)..current.end.max(snapshot.end);
|
||||
} else if current.end == self.all_definitions.len() {
|
||||
// Ranges are not adjacent or overlapping, `current` is at the end of
|
||||
// `all_definitions`, we need to copy `snapshot` to the end so they are adjacent
|
||||
// and can be merged into one range.
|
||||
self.all_definitions.extend_from_within(snapshot.clone());
|
||||
current.end = self.all_definitions.len();
|
||||
} else if snapshot.end == self.all_definitions.len() {
|
||||
// Ranges are not adjacent or overlapping, `snapshot` is at the end of
|
||||
// `all_definitions`, we need to copy `current` to the end so they are adjacent and
|
||||
// can be merged into one range.
|
||||
self.all_definitions.extend_from_within(current.clone());
|
||||
current.start = snapshot.start;
|
||||
current.end = self.all_definitions.len();
|
||||
} else {
|
||||
// Ranges are not adjacent and neither one is at the end of `all_definitions`, we
|
||||
// have to copy both to the end so they are adjacent and we can merge them.
|
||||
let start = self.all_definitions.len();
|
||||
self.all_definitions.extend_from_within(current.clone());
|
||||
self.all_definitions.extend_from_within(snapshot.clone());
|
||||
current.start = start;
|
||||
current.end = self.all_definitions.len();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn finish(mut self) -> UseDefMap<'db> {
|
||||
self.all_definitions.shrink_to_fit();
|
||||
self.all_constraints.shrink_to_fit();
|
||||
self.definitions_by_symbol.shrink_to_fit();
|
||||
self.definitions_by_use.shrink_to_fit();
|
||||
|
||||
UseDefMap {
|
||||
all_definitions: self.all_definitions,
|
||||
all_constraints: self.all_constraints,
|
||||
definitions_by_use: self.definitions_by_use,
|
||||
public_definitions: self.definitions_by_symbol,
|
||||
}
|
||||
|
||||
@@ -1,228 +0,0 @@
|
||||
/// Ordered set of `u32`.
|
||||
///
|
||||
/// Uses an inline bit-set for small values (up to 64 * B), falls back to heap allocated vector of
|
||||
/// blocks for larger values.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub(super) enum BitSet<const B: usize> {
|
||||
/// Bit-set (in 64-bit blocks) for the first 64 * B entries.
|
||||
Inline([u64; B]),
|
||||
|
||||
/// Overflow beyond 64 * B.
|
||||
Heap(Vec<u64>),
|
||||
}
|
||||
|
||||
impl<const B: usize> Default for BitSet<B> {
|
||||
fn default() -> Self {
|
||||
// B * 64 must fit in a u32, or else we have unusable bits; this assertion makes the
|
||||
// truncating casts to u32 below safe. This would be better as a const assertion, but
|
||||
// that's not possible on stable with const generic params. (B should never really be
|
||||
// anywhere close to this large.)
|
||||
assert!(B * 64 < (u32::MAX as usize));
|
||||
// This implementation requires usize >= 32 bits.
|
||||
static_assertions::const_assert!(usize::BITS >= 32);
|
||||
Self::Inline([0; B])
|
||||
}
|
||||
}
|
||||
|
||||
impl<const B: usize> BitSet<B> {
|
||||
/// Create and return a new [`BitSet`] with a single `value` inserted.
|
||||
pub(super) fn with(value: u32) -> Self {
|
||||
let mut bitset = Self::default();
|
||||
bitset.insert(value);
|
||||
bitset
|
||||
}
|
||||
|
||||
/// Convert from Inline to Heap, if needed, and resize the Heap vector, if needed.
|
||||
fn resize(&mut self, value: u32) {
|
||||
let num_blocks_needed = (value / 64) + 1;
|
||||
match self {
|
||||
Self::Inline(blocks) => {
|
||||
let mut vec = blocks.to_vec();
|
||||
vec.resize(num_blocks_needed as usize, 0);
|
||||
*self = Self::Heap(vec);
|
||||
}
|
||||
Self::Heap(vec) => {
|
||||
vec.resize(num_blocks_needed as usize, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn blocks_mut(&mut self) -> &mut [u64] {
|
||||
match self {
|
||||
Self::Inline(blocks) => blocks.as_mut_slice(),
|
||||
Self::Heap(blocks) => blocks.as_mut_slice(),
|
||||
}
|
||||
}
|
||||
|
||||
fn blocks(&self) -> &[u64] {
|
||||
match self {
|
||||
Self::Inline(blocks) => blocks.as_slice(),
|
||||
Self::Heap(blocks) => blocks.as_slice(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Insert a value into the [`BitSet`].
|
||||
///
|
||||
/// Return true if the value was newly inserted, false if already present.
|
||||
pub(super) fn insert(&mut self, value: u32) -> bool {
|
||||
let value_usize = value as usize;
|
||||
let (block, index) = (value_usize / 64, value_usize % 64);
|
||||
if block >= self.blocks().len() {
|
||||
self.resize(value);
|
||||
}
|
||||
let blocks = self.blocks_mut();
|
||||
let missing = blocks[block] & (1 << index) == 0;
|
||||
blocks[block] |= 1 << index;
|
||||
missing
|
||||
}
|
||||
|
||||
/// Intersect in-place with another [`BitSet`].
|
||||
pub(super) fn intersect(&mut self, other: &BitSet<B>) {
|
||||
let my_blocks = self.blocks_mut();
|
||||
let other_blocks = other.blocks();
|
||||
let min_len = my_blocks.len().min(other_blocks.len());
|
||||
for i in 0..min_len {
|
||||
my_blocks[i] &= other_blocks[i];
|
||||
}
|
||||
for block in my_blocks.iter_mut().skip(min_len) {
|
||||
*block = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// Return an iterator over the values (in ascending order) in this [`BitSet`].
|
||||
pub(super) fn iter(&self) -> BitSetIterator<'_, B> {
|
||||
let blocks = self.blocks();
|
||||
BitSetIterator {
|
||||
blocks,
|
||||
current_block_index: 0,
|
||||
current_block: blocks[0],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Iterator over values in a [`BitSet`].
|
||||
#[derive(Debug)]
|
||||
pub(super) struct BitSetIterator<'a, const B: usize> {
|
||||
/// The blocks we are iterating over.
|
||||
blocks: &'a [u64],
|
||||
|
||||
/// The index of the block we are currently iterating through.
|
||||
current_block_index: usize,
|
||||
|
||||
/// The block we are currently iterating through (and zeroing as we go.)
|
||||
current_block: u64,
|
||||
}
|
||||
|
||||
impl<const B: usize> Iterator for BitSetIterator<'_, B> {
|
||||
type Item = u32;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
while self.current_block == 0 {
|
||||
if self.current_block_index + 1 >= self.blocks.len() {
|
||||
return None;
|
||||
}
|
||||
self.current_block_index += 1;
|
||||
self.current_block = self.blocks[self.current_block_index];
|
||||
}
|
||||
let lowest_bit_set = self.current_block.trailing_zeros();
|
||||
// reset the lowest set bit, without a data dependency on `lowest_bit_set`
|
||||
self.current_block &= self.current_block.wrapping_sub(1);
|
||||
// SAFETY: `lowest_bit_set` cannot be more than 64, `current_block_index` cannot be more
|
||||
// than `B - 1`, and we check above that `B * 64 < u32::MAX`. So both `64 *
|
||||
// current_block_index` and the final value here must fit in u32.
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
Some(lowest_bit_set + (64 * self.current_block_index) as u32)
|
||||
}
|
||||
}
|
||||
|
||||
impl<const B: usize> std::iter::FusedIterator for BitSetIterator<'_, B> {}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::BitSet;
|
||||
|
||||
fn assert_bitset<const B: usize>(bitset: &BitSet<B>, contents: &[u32]) {
|
||||
assert_eq!(bitset.iter().collect::<Vec<_>>(), contents);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn iter() {
|
||||
let mut b = BitSet::<1>::with(3);
|
||||
b.insert(27);
|
||||
b.insert(6);
|
||||
assert!(matches!(b, BitSet::Inline(_)));
|
||||
assert_bitset(&b, &[3, 6, 27]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn iter_overflow() {
|
||||
let mut b = BitSet::<1>::with(140);
|
||||
b.insert(100);
|
||||
b.insert(129);
|
||||
assert!(matches!(b, BitSet::Heap(_)));
|
||||
assert_bitset(&b, &[100, 129, 140]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn intersect() {
|
||||
let mut b1 = BitSet::<1>::with(4);
|
||||
let mut b2 = BitSet::<1>::with(4);
|
||||
b1.insert(23);
|
||||
b2.insert(5);
|
||||
|
||||
b1.intersect(&b2);
|
||||
assert_bitset(&b1, &[4]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn intersect_mixed_1() {
|
||||
let mut b1 = BitSet::<1>::with(4);
|
||||
let mut b2 = BitSet::<1>::with(4);
|
||||
b1.insert(89);
|
||||
b2.insert(5);
|
||||
|
||||
b1.intersect(&b2);
|
||||
assert_bitset(&b1, &[4]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn intersect_mixed_2() {
|
||||
let mut b1 = BitSet::<1>::with(4);
|
||||
let mut b2 = BitSet::<1>::with(4);
|
||||
b1.insert(23);
|
||||
b2.insert(89);
|
||||
|
||||
b1.intersect(&b2);
|
||||
assert_bitset(&b1, &[4]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn intersect_heap() {
|
||||
let mut b1 = BitSet::<1>::with(4);
|
||||
let mut b2 = BitSet::<1>::with(4);
|
||||
b1.insert(89);
|
||||
b2.insert(90);
|
||||
|
||||
b1.intersect(&b2);
|
||||
assert_bitset(&b1, &[4]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn intersect_heap_2() {
|
||||
let mut b1 = BitSet::<1>::with(89);
|
||||
let mut b2 = BitSet::<1>::with(89);
|
||||
b1.insert(91);
|
||||
b2.insert(90);
|
||||
|
||||
b1.intersect(&b2);
|
||||
assert_bitset(&b1, &[89]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multiple_blocks() {
|
||||
let mut b = BitSet::<2>::with(120);
|
||||
b.insert(45);
|
||||
assert!(matches!(b, BitSet::Inline(_)));
|
||||
assert_bitset(&b, &[45, 120]);
|
||||
}
|
||||
}
|
||||
@@ -1,374 +0,0 @@
|
||||
//! Track visible definitions of a symbol, and applicable constraints per definition.
|
||||
//!
|
||||
//! These data structures operate entirely on scope-local newtype-indices for definitions and
|
||||
//! constraints, referring to their location in the `all_definitions` and `all_constraints`
|
||||
//! indexvecs in [`super::UseDefMapBuilder`].
|
||||
//!
|
||||
//! We need to track arbitrary associations between definitions and constraints, not just a single
|
||||
//! set of currently dominating constraints (where "dominating" means "control flow must have
|
||||
//! passed through it to reach this point"), because we can have dominating constraints that apply
|
||||
//! to some definitions but not others, as in this code:
|
||||
//!
|
||||
//! ```python
|
||||
//! x = 1 if flag else None
|
||||
//! if x is not None:
|
||||
//! if flag2:
|
||||
//! x = 2 if flag else None
|
||||
//! x
|
||||
//! ```
|
||||
//!
|
||||
//! The `x is not None` constraint dominates the final use of `x`, but it applies only to the first
|
||||
//! definition of `x`, not the second, so `None` is a possible value for `x`.
|
||||
//!
|
||||
//! And we can't just track, for each definition, an index into a list of dominating constraints,
|
||||
//! either, because we can have definitions which are still visible, but subject to constraints
|
||||
//! that are no longer dominating, as in this code:
|
||||
//!
|
||||
//! ```python
|
||||
//! x = 0
|
||||
//! if flag1:
|
||||
//! x = 1 if flag2 else None
|
||||
//! assert x is not None
|
||||
//! x
|
||||
//! ```
|
||||
//!
|
||||
//! From the point of view of the final use of `x`, the `x is not None` constraint no longer
|
||||
//! dominates, but it does dominate the `x = 1 if flag2 else None` definition, so we have to keep
|
||||
//! track of that.
|
||||
//!
|
||||
//! The data structures used here ([`BitSet`] and [`smallvec::SmallVec`]) optimize for keeping all
|
||||
//! data inline (avoiding lots of scattered allocations) in small-to-medium cases, and falling back
|
||||
//! to heap allocation to be able to scale to arbitrary numbers of definitions and constraints when
|
||||
//! needed.
|
||||
use super::bitset::{BitSet, BitSetIterator};
|
||||
use ruff_index::newtype_index;
|
||||
use smallvec::SmallVec;
|
||||
|
||||
/// A newtype-index for a definition in a particular scope.
|
||||
#[newtype_index]
|
||||
pub(super) struct ScopedDefinitionId;
|
||||
|
||||
/// A newtype-index for a constraint expression in a particular scope.
|
||||
#[newtype_index]
|
||||
pub(super) struct ScopedConstraintId;
|
||||
|
||||
/// Can reference this * 64 total definitions inline; more will fall back to the heap.
|
||||
const INLINE_DEFINITION_BLOCKS: usize = 3;
|
||||
|
||||
/// A [`BitSet`] of [`ScopedDefinitionId`], representing visible definitions of a symbol in a scope.
|
||||
type Definitions = BitSet<INLINE_DEFINITION_BLOCKS>;
|
||||
type DefinitionsIterator<'a> = BitSetIterator<'a, INLINE_DEFINITION_BLOCKS>;
|
||||
|
||||
/// Can reference this * 64 total constraints inline; more will fall back to the heap.
|
||||
const INLINE_CONSTRAINT_BLOCKS: usize = 2;
|
||||
|
||||
/// Can keep inline this many visible definitions per symbol at a given time; more will go to heap.
|
||||
const INLINE_VISIBLE_DEFINITIONS_PER_SYMBOL: usize = 4;
|
||||
|
||||
/// One [`BitSet`] of applicable [`ScopedConstraintId`] per visible definition.
|
||||
type InlineConstraintArray =
|
||||
[BitSet<INLINE_CONSTRAINT_BLOCKS>; INLINE_VISIBLE_DEFINITIONS_PER_SYMBOL];
|
||||
type Constraints = SmallVec<InlineConstraintArray>;
|
||||
type ConstraintsIterator<'a> = std::slice::Iter<'a, BitSet<INLINE_CONSTRAINT_BLOCKS>>;
|
||||
type ConstraintsIntoIterator = smallvec::IntoIter<InlineConstraintArray>;
|
||||
|
||||
/// Visible definitions and narrowing constraints for a single symbol at some point in control flow.
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub(super) struct SymbolState {
|
||||
/// [`BitSet`]: which [`ScopedDefinitionId`] are visible for this symbol?
|
||||
visible_definitions: Definitions,
|
||||
|
||||
/// For each definition, which [`ScopedConstraintId`] apply?
|
||||
///
|
||||
/// This is a [`smallvec::SmallVec`] which should always have one [`BitSet`] of constraints per
|
||||
/// definition in `visible_definitions`.
|
||||
constraints: Constraints,
|
||||
|
||||
/// Could the symbol be unbound at this point?
|
||||
may_be_unbound: bool,
|
||||
}
|
||||
|
||||
/// A single [`ScopedDefinitionId`] with an iterator of its applicable [`ScopedConstraintId`].
|
||||
#[derive(Debug)]
|
||||
pub(super) struct DefinitionIdWithConstraints<'a> {
|
||||
pub(super) definition: ScopedDefinitionId,
|
||||
pub(super) constraint_ids: ConstraintIdIterator<'a>,
|
||||
}
|
||||
|
||||
impl SymbolState {
|
||||
/// Return a new [`SymbolState`] representing an unbound symbol.
|
||||
pub(super) fn unbound() -> Self {
|
||||
Self {
|
||||
visible_definitions: Definitions::default(),
|
||||
constraints: Constraints::default(),
|
||||
may_be_unbound: true,
|
||||
}
|
||||
}
|
||||
|
||||
/// Return a new [`SymbolState`] representing a symbol with a single visible definition.
|
||||
pub(super) fn with(definition_id: ScopedDefinitionId) -> Self {
|
||||
let mut constraints = Constraints::with_capacity(1);
|
||||
constraints.push(BitSet::default());
|
||||
Self {
|
||||
visible_definitions: Definitions::with(definition_id.into()),
|
||||
constraints,
|
||||
may_be_unbound: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Add Unbound as a possibility for this symbol.
|
||||
pub(super) fn add_unbound(&mut self) {
|
||||
self.may_be_unbound = true;
|
||||
}
|
||||
|
||||
/// Add given constraint to all currently-visible definitions.
|
||||
pub(super) fn add_constraint(&mut self, constraint_id: ScopedConstraintId) {
|
||||
for bitset in &mut self.constraints {
|
||||
bitset.insert(constraint_id.into());
|
||||
}
|
||||
}
|
||||
|
||||
/// Merge another [`SymbolState`] into this one.
|
||||
pub(super) fn merge(&mut self, b: SymbolState) {
|
||||
let mut a = Self {
|
||||
visible_definitions: Definitions::default(),
|
||||
constraints: Constraints::default(),
|
||||
may_be_unbound: self.may_be_unbound || b.may_be_unbound,
|
||||
};
|
||||
std::mem::swap(&mut a, self);
|
||||
let mut a_defs_iter = a.visible_definitions.iter();
|
||||
let mut b_defs_iter = b.visible_definitions.iter();
|
||||
let mut a_constraints_iter = a.constraints.into_iter();
|
||||
let mut b_constraints_iter = b.constraints.into_iter();
|
||||
|
||||
let mut opt_a_def: Option<u32> = a_defs_iter.next();
|
||||
let mut opt_b_def: Option<u32> = b_defs_iter.next();
|
||||
|
||||
// Iterate through the definitions from `a` and `b`, always processing the lower definition
|
||||
// ID first, and pushing each definition onto the merged `SymbolState` with its
|
||||
// constraints. If a definition is found in both `a` and `b`, push it with the intersection
|
||||
// of the constraints from the two paths; a constraint that applies from only one possible
|
||||
// path is irrelevant.
|
||||
|
||||
// Helper to push `def`, with constraints in `constraints_iter`, onto `self`.
|
||||
let push = |def, constraints_iter: &mut ConstraintsIntoIterator, merged: &mut Self| {
|
||||
merged.visible_definitions.insert(def);
|
||||
// SAFETY: we only ever create SymbolState with either no definitions and no constraint
|
||||
// bitsets (`::unbound`) or one definition and one constraint bitset (`::with`), and
|
||||
// `::merge` always pushes one definition and one constraint bitset together (just
|
||||
// below), so the number of definitions and the number of constraint bitsets can never
|
||||
// get out of sync.
|
||||
let constraints = constraints_iter
|
||||
.next()
|
||||
.expect("definitions and constraints length mismatch");
|
||||
merged.constraints.push(constraints);
|
||||
};
|
||||
|
||||
loop {
|
||||
match (opt_a_def, opt_b_def) {
|
||||
(Some(a_def), Some(b_def)) => match a_def.cmp(&b_def) {
|
||||
std::cmp::Ordering::Less => {
|
||||
// Next definition ID is only in `a`, push it to `self` and advance `a`.
|
||||
push(a_def, &mut a_constraints_iter, self);
|
||||
opt_a_def = a_defs_iter.next();
|
||||
}
|
||||
std::cmp::Ordering::Greater => {
|
||||
// Next definition ID is only in `b`, push it to `self` and advance `b`.
|
||||
push(b_def, &mut b_constraints_iter, self);
|
||||
opt_b_def = b_defs_iter.next();
|
||||
}
|
||||
std::cmp::Ordering::Equal => {
|
||||
// Next definition is in both; push to `self` and intersect constraints.
|
||||
push(a_def, &mut b_constraints_iter, self);
|
||||
// SAFETY: we only ever create SymbolState with either no definitions and
|
||||
// no constraint bitsets (`::unbound`) or one definition and one constraint
|
||||
// bitset (`::with`), and `::merge` always pushes one definition and one
|
||||
// constraint bitset together (just below), so the number of definitions
|
||||
// and the number of constraint bitsets can never get out of sync.
|
||||
let a_constraints = a_constraints_iter
|
||||
.next()
|
||||
.expect("definitions and constraints length mismatch");
|
||||
// If the same definition is visible through both paths, any constraint
|
||||
// that applies on only one path is irrelevant to the resulting type from
|
||||
// unioning the two paths, so we intersect the constraints.
|
||||
self.constraints
|
||||
.last_mut()
|
||||
.unwrap()
|
||||
.intersect(&a_constraints);
|
||||
opt_a_def = a_defs_iter.next();
|
||||
opt_b_def = b_defs_iter.next();
|
||||
}
|
||||
},
|
||||
(Some(a_def), None) => {
|
||||
// We've exhausted `b`, just push the def from `a` and move on to the next.
|
||||
push(a_def, &mut a_constraints_iter, self);
|
||||
opt_a_def = a_defs_iter.next();
|
||||
}
|
||||
(None, Some(b_def)) => {
|
||||
// We've exhausted `a`, just push the def from `b` and move on to the next.
|
||||
push(b_def, &mut b_constraints_iter, self);
|
||||
opt_b_def = b_defs_iter.next();
|
||||
}
|
||||
(None, None) => break,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Get iterator over visible definitions with constraints.
|
||||
pub(super) fn visible_definitions(&self) -> DefinitionIdWithConstraintsIterator {
|
||||
DefinitionIdWithConstraintsIterator {
|
||||
definitions: self.visible_definitions.iter(),
|
||||
constraints: self.constraints.iter(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Could the symbol be unbound?
|
||||
pub(super) fn may_be_unbound(&self) -> bool {
|
||||
self.may_be_unbound
|
||||
}
|
||||
}
|
||||
|
||||
/// The default state of a symbol (if we've seen no definitions of it) is unbound.
|
||||
impl Default for SymbolState {
|
||||
fn default() -> Self {
|
||||
SymbolState::unbound()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(super) struct DefinitionIdWithConstraintsIterator<'a> {
|
||||
definitions: DefinitionsIterator<'a>,
|
||||
constraints: ConstraintsIterator<'a>,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for DefinitionIdWithConstraintsIterator<'a> {
|
||||
type Item = DefinitionIdWithConstraints<'a>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
match (self.definitions.next(), self.constraints.next()) {
|
||||
(None, None) => None,
|
||||
(Some(def), Some(constraints)) => Some(DefinitionIdWithConstraints {
|
||||
definition: ScopedDefinitionId::from_u32(def),
|
||||
constraint_ids: ConstraintIdIterator {
|
||||
wrapped: constraints.iter(),
|
||||
},
|
||||
}),
|
||||
// SAFETY: see above.
|
||||
_ => unreachable!("definitions and constraints length mismatch"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::iter::FusedIterator for DefinitionIdWithConstraintsIterator<'_> {}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(super) struct ConstraintIdIterator<'a> {
|
||||
wrapped: BitSetIterator<'a, INLINE_CONSTRAINT_BLOCKS>,
|
||||
}
|
||||
|
||||
impl Iterator for ConstraintIdIterator<'_> {
|
||||
type Item = ScopedConstraintId;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.wrapped.next().map(ScopedConstraintId::from_u32)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::iter::FusedIterator for ConstraintIdIterator<'_> {}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{ScopedConstraintId, ScopedDefinitionId, SymbolState};
|
||||
|
||||
impl SymbolState {
|
||||
pub(crate) fn assert(&self, may_be_unbound: bool, expected: &[&str]) {
|
||||
assert_eq!(self.may_be_unbound(), may_be_unbound);
|
||||
let actual = self
|
||||
.visible_definitions()
|
||||
.map(|def_id_with_constraints| {
|
||||
format!(
|
||||
"{}<{}>",
|
||||
def_id_with_constraints.definition.as_u32(),
|
||||
def_id_with_constraints
|
||||
.constraint_ids
|
||||
.map(ScopedConstraintId::as_u32)
|
||||
.map(|idx| idx.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unbound() {
|
||||
let cd = SymbolState::unbound();
|
||||
|
||||
cd.assert(true, &[]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn with() {
|
||||
let cd = SymbolState::with(ScopedDefinitionId::from_u32(0));
|
||||
|
||||
cd.assert(false, &["0<>"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_unbound() {
|
||||
let mut cd = SymbolState::with(ScopedDefinitionId::from_u32(0));
|
||||
cd.add_unbound();
|
||||
|
||||
cd.assert(true, &["0<>"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_constraint() {
|
||||
let mut cd = SymbolState::with(ScopedDefinitionId::from_u32(0));
|
||||
cd.add_constraint(ScopedConstraintId::from_u32(0));
|
||||
|
||||
cd.assert(false, &["0<0>"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn merge() {
|
||||
// merging the same definition with the same constraint keeps the constraint
|
||||
let mut cd0a = SymbolState::with(ScopedDefinitionId::from_u32(0));
|
||||
cd0a.add_constraint(ScopedConstraintId::from_u32(0));
|
||||
|
||||
let mut cd0b = SymbolState::with(ScopedDefinitionId::from_u32(0));
|
||||
cd0b.add_constraint(ScopedConstraintId::from_u32(0));
|
||||
|
||||
cd0a.merge(cd0b);
|
||||
let mut cd0 = cd0a;
|
||||
cd0.assert(false, &["0<0>"]);
|
||||
|
||||
// merging the same definition with differing constraints drops all constraints
|
||||
let mut cd1a = SymbolState::with(ScopedDefinitionId::from_u32(1));
|
||||
cd1a.add_constraint(ScopedConstraintId::from_u32(1));
|
||||
|
||||
let mut cd1b = SymbolState::with(ScopedDefinitionId::from_u32(1));
|
||||
cd1b.add_constraint(ScopedConstraintId::from_u32(2));
|
||||
|
||||
cd1a.merge(cd1b);
|
||||
let cd1 = cd1a;
|
||||
cd1.assert(false, &["1<>"]);
|
||||
|
||||
// merging a constrained definition with unbound keeps both
|
||||
let mut cd2a = SymbolState::with(ScopedDefinitionId::from_u32(2));
|
||||
cd2a.add_constraint(ScopedConstraintId::from_u32(3));
|
||||
|
||||
let cd2b = SymbolState::unbound();
|
||||
|
||||
cd2a.merge(cd2b);
|
||||
let cd2 = cd2a;
|
||||
cd2.assert(true, &["2<3>"]);
|
||||
|
||||
// merging different definitions keeps them each with their existing constraints
|
||||
cd0.merge(cd2);
|
||||
let cd = cd0;
|
||||
cd.assert(true, &["0<0>", "2<3>"]);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
use ruff_db::files::{File, FilePath};
|
||||
use ruff_db::source::line_index;
|
||||
use ruff_python_ast as ast;
|
||||
use ruff_python_ast::{Expr, ExpressionRef};
|
||||
use ruff_python_ast::{Expr, ExpressionRef, StmtClassDef};
|
||||
use ruff_source_file::LineIndex;
|
||||
|
||||
use crate::module_name::ModuleName;
|
||||
@@ -147,24 +147,29 @@ impl HasTy for ast::Expr {
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_definition_has_ty {
|
||||
($ty: ty) => {
|
||||
impl HasTy for $ty {
|
||||
#[inline]
|
||||
fn ty<'db>(&self, model: &SemanticModel<'db>) -> Type<'db> {
|
||||
let index = semantic_index(model.db, model.file);
|
||||
let definition = index.definition(self);
|
||||
definition_ty(model.db, definition)
|
||||
}
|
||||
}
|
||||
};
|
||||
impl HasTy for ast::StmtFunctionDef {
|
||||
fn ty<'db>(&self, model: &SemanticModel<'db>) -> Type<'db> {
|
||||
let index = semantic_index(model.db, model.file);
|
||||
let definition = index.definition(self);
|
||||
definition_ty(model.db, definition)
|
||||
}
|
||||
}
|
||||
|
||||
impl_definition_has_ty!(ast::StmtFunctionDef);
|
||||
impl_definition_has_ty!(ast::StmtClassDef);
|
||||
impl_definition_has_ty!(ast::Alias);
|
||||
impl_definition_has_ty!(ast::Parameter);
|
||||
impl_definition_has_ty!(ast::ParameterWithDefault);
|
||||
impl HasTy for StmtClassDef {
|
||||
fn ty<'db>(&self, model: &SemanticModel<'db>) -> Type<'db> {
|
||||
let index = semantic_index(model.db, model.file);
|
||||
let definition = index.definition(self);
|
||||
definition_ty(model.db, definition)
|
||||
}
|
||||
}
|
||||
|
||||
impl HasTy for ast::Alias {
|
||||
fn ty<'db>(&self, model: &SemanticModel<'db>) -> Type<'db> {
|
||||
let index = semantic_index(model.db, model.file);
|
||||
let definition = index.definition(self);
|
||||
definition_ty(model.db, definition)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
@@ -4,22 +4,15 @@ use ruff_python_ast::name::Name;
|
||||
use crate::builtins::builtins_scope;
|
||||
use crate::semantic_index::definition::Definition;
|
||||
use crate::semantic_index::symbol::{ScopeId, ScopedSymbolId};
|
||||
use crate::semantic_index::{
|
||||
global_scope, symbol_table, use_def_map, DefinitionWithConstraints,
|
||||
DefinitionWithConstraintsIterator,
|
||||
};
|
||||
use crate::types::narrow::narrowing_constraint;
|
||||
use crate::semantic_index::{global_scope, symbol_table, use_def_map};
|
||||
use crate::{Db, FxOrderSet};
|
||||
|
||||
mod builder;
|
||||
mod display;
|
||||
mod infer;
|
||||
mod narrow;
|
||||
|
||||
pub(crate) use self::builder::{IntersectionBuilder, UnionBuilder};
|
||||
pub(crate) use self::infer::{
|
||||
infer_definition_types, infer_expression_types, infer_scope_types, TypeInference,
|
||||
};
|
||||
pub(crate) use self::builder::UnionBuilder;
|
||||
pub(crate) use self::infer::{infer_definition_types, infer_scope_types};
|
||||
|
||||
/// Infer the public type of a symbol (its type as seen from outside its scope).
|
||||
pub(crate) fn symbol_ty<'db>(
|
||||
@@ -89,31 +82,10 @@ pub(crate) fn definition_ty<'db>(db: &'db dyn Db, definition: Definition<'db>) -
|
||||
/// provide an `unbound_ty`.
|
||||
pub(crate) fn definitions_ty<'db>(
|
||||
db: &'db dyn Db,
|
||||
definitions_with_constraints: DefinitionWithConstraintsIterator<'_, 'db>,
|
||||
definitions: &[Definition<'db>],
|
||||
unbound_ty: Option<Type<'db>>,
|
||||
) -> Type<'db> {
|
||||
let def_types = definitions_with_constraints.map(
|
||||
|DefinitionWithConstraints {
|
||||
definition,
|
||||
constraints,
|
||||
}| {
|
||||
let mut constraint_tys =
|
||||
constraints.filter_map(|test| narrowing_constraint(db, test, definition));
|
||||
let definition_ty = definition_ty(db, definition);
|
||||
if let Some(first_constraint_ty) = constraint_tys.next() {
|
||||
let mut builder = IntersectionBuilder::new(db);
|
||||
builder = builder
|
||||
.add_positive(definition_ty)
|
||||
.add_positive(first_constraint_ty);
|
||||
for constraint_ty in constraint_tys {
|
||||
builder = builder.add_positive(constraint_ty);
|
||||
}
|
||||
builder.build()
|
||||
} else {
|
||||
definition_ty
|
||||
}
|
||||
},
|
||||
);
|
||||
let def_types = definitions.iter().map(|def| definition_ty(db, *def));
|
||||
let mut all_types = unbound_ty.into_iter().chain(def_types);
|
||||
|
||||
let Some(first) = all_types.next() else {
|
||||
|
||||
@@ -65,6 +65,7 @@ impl<'db> UnionBuilder<'db> {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct IntersectionBuilder<'db> {
|
||||
// Really this builds a union-of-intersections, because we always keep our set-theoretic types
|
||||
@@ -77,7 +78,8 @@ pub(crate) struct IntersectionBuilder<'db> {
|
||||
}
|
||||
|
||||
impl<'db> IntersectionBuilder<'db> {
|
||||
pub(crate) fn new(db: &'db dyn Db) -> Self {
|
||||
#[allow(dead_code)]
|
||||
fn new(db: &'db dyn Db) -> Self {
|
||||
Self {
|
||||
db,
|
||||
intersections: vec![InnerIntersectionBuilder::new()],
|
||||
@@ -91,7 +93,8 @@ impl<'db> IntersectionBuilder<'db> {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn add_positive(mut self, ty: Type<'db>) -> Self {
|
||||
#[allow(dead_code)]
|
||||
fn add_positive(mut self, ty: Type<'db>) -> Self {
|
||||
if let Type::Union(union) = ty {
|
||||
// Distribute ourself over this union: for each union element, clone ourself and
|
||||
// intersect with that union element, then create a new union-of-intersections with all
|
||||
@@ -119,7 +122,8 @@ impl<'db> IntersectionBuilder<'db> {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn add_negative(mut self, ty: Type<'db>) -> Self {
|
||||
#[allow(dead_code)]
|
||||
fn add_negative(mut self, ty: Type<'db>) -> Self {
|
||||
// See comments above in `add_positive`; this is just the negated version.
|
||||
if let Type::Union(union) = ty {
|
||||
union
|
||||
@@ -138,7 +142,8 @@ impl<'db> IntersectionBuilder<'db> {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn build(mut self) -> Type<'db> {
|
||||
#[allow(dead_code)]
|
||||
fn build(mut self) -> Type<'db> {
|
||||
// Avoid allocating the UnionBuilder unnecessarily if we have just one intersection:
|
||||
if self.intersections.len() == 1 {
|
||||
self.intersections.pop().unwrap().build(self.db)
|
||||
@@ -152,6 +157,7 @@ impl<'db> IntersectionBuilder<'db> {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
#[derive(Debug, Clone, Default)]
|
||||
struct InnerIntersectionBuilder<'db> {
|
||||
positive: FxOrderSet<Type<'db>>,
|
||||
@@ -217,16 +223,6 @@ impl<'db> InnerIntersectionBuilder<'db> {
|
||||
self.positive.retain(Type::is_unbound);
|
||||
self.negative.clear();
|
||||
}
|
||||
|
||||
// None intersects only with object
|
||||
for pos in &self.positive {
|
||||
if let Type::Instance(_) = pos {
|
||||
// could be `object` type
|
||||
} else {
|
||||
self.negative.remove(&Type::None);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn build(mut self, db: &'db dyn Db) -> Type<'db> {
|
||||
@@ -457,15 +453,4 @@ mod tests {
|
||||
|
||||
assert_eq!(ty, Type::IntLiteral(1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_intersection_simplify_negative_none() {
|
||||
let db = setup_db();
|
||||
let ty = IntersectionBuilder::new(&db)
|
||||
.add_negative(Type::None)
|
||||
.add_positive(Type::IntLiteral(1))
|
||||
.build();
|
||||
|
||||
assert_eq!(ty, Type::IntLiteral(1));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ use salsa::plumbing::AsId;
|
||||
use ruff_db::files::File;
|
||||
use ruff_db::parsed::parsed_module;
|
||||
use ruff_python_ast as ast;
|
||||
use ruff_python_ast::{Expr, ExprContext};
|
||||
use ruff_python_ast::{ExprContext, TypeParams};
|
||||
|
||||
use crate::builtins::builtins_scope;
|
||||
use crate::module_name::ModuleName;
|
||||
@@ -294,11 +294,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
);
|
||||
}
|
||||
DefinitionKind::Assignment(assignment) => {
|
||||
self.infer_assignment_definition(
|
||||
assignment.target(),
|
||||
assignment.assignment(),
|
||||
definition,
|
||||
);
|
||||
self.infer_assignment_definition(assignment.assignment(), definition);
|
||||
}
|
||||
DefinitionKind::AnnotatedAssignment(annotated_assignment) => {
|
||||
self.infer_annotated_assignment_definition(annotated_assignment.node(), definition);
|
||||
@@ -323,7 +319,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
}
|
||||
|
||||
fn infer_region_expression(&mut self, expression: Expression<'db>) {
|
||||
self.infer_expression(expression.node_ref(self.db));
|
||||
self.infer_expression(expression.node(self.db));
|
||||
}
|
||||
|
||||
fn infer_module(&mut self, module: &ast::ModModule) {
|
||||
@@ -710,7 +706,6 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
|
||||
fn infer_assignment_definition(
|
||||
&mut self,
|
||||
target: &ast::ExprName,
|
||||
assignment: &ast::StmtAssign,
|
||||
definition: Definition<'db>,
|
||||
) {
|
||||
@@ -720,9 +715,6 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
let value_ty = self
|
||||
.types
|
||||
.expression_ty(assignment.value.scoped_ast_id(self.db, self.scope));
|
||||
self.types
|
||||
.expressions
|
||||
.insert(target.scoped_ast_id(self.db, self.scope), value_ty);
|
||||
self.types.definitions.insert(definition, value_ty);
|
||||
}
|
||||
|
||||
@@ -1007,9 +999,6 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
ast::Expr::NumberLiteral(literal) => self.infer_number_literal_expression(literal),
|
||||
ast::Expr::BooleanLiteral(literal) => self.infer_boolean_literal_expression(literal),
|
||||
ast::Expr::StringLiteral(literal) => self.infer_string_literal_expression(literal),
|
||||
ast::Expr::BytesLiteral(bytes_literal) => {
|
||||
self.infer_bytes_literal_expression(bytes_literal)
|
||||
}
|
||||
ast::Expr::FString(fstring) => self.infer_fstring_expression(fstring),
|
||||
ast::Expr::EllipsisLiteral(literal) => self.infer_ellipsis_literal_expression(literal),
|
||||
ast::Expr::Tuple(tuple) => self.infer_tuple_expression(tuple),
|
||||
@@ -1036,7 +1025,8 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
ast::Expr::Yield(yield_expression) => self.infer_yield_expression(yield_expression),
|
||||
ast::Expr::YieldFrom(yield_from) => self.infer_yield_from_expression(yield_from),
|
||||
ast::Expr::Await(await_expression) => self.infer_await_expression(await_expression),
|
||||
Expr::IpyEscapeCommand(_) => todo!("Implement Ipy escape command support"),
|
||||
|
||||
_ => todo!("expression type resolution for {:?}", expression),
|
||||
};
|
||||
|
||||
let expr_id = expression.scoped_ast_id(self.db, self.scope);
|
||||
@@ -1073,12 +1063,6 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
Type::Unknown
|
||||
}
|
||||
|
||||
#[allow(clippy::unused_self)]
|
||||
fn infer_bytes_literal_expression(&mut self, _literal: &ast::ExprBytesLiteral) -> Type<'db> {
|
||||
// TODO
|
||||
Type::Unknown
|
||||
}
|
||||
|
||||
fn infer_fstring_expression(&mut self, fstring: &ast::ExprFString) -> Type<'db> {
|
||||
let ast::ExprFString { range: _, value } = fstring;
|
||||
|
||||
@@ -1646,7 +1630,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
Type::Unknown
|
||||
}
|
||||
|
||||
fn infer_type_parameters(&mut self, type_parameters: &ast::TypeParams) {
|
||||
fn infer_type_parameters(&mut self, type_parameters: &TypeParams) {
|
||||
let ast::TypeParams {
|
||||
range: _,
|
||||
type_params,
|
||||
@@ -1693,7 +1677,6 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use anyhow::Context;
|
||||
|
||||
use ruff_db::files::{system_path_to_file, File};
|
||||
use ruff_db::parsed::parsed_module;
|
||||
use ruff_db::system::{DbWithTestSystem, SystemPathBuf};
|
||||
@@ -2604,26 +2587,6 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn narrow_not_none() -> anyhow::Result<()> {
|
||||
let mut db = setup_db();
|
||||
|
||||
db.write_dedented(
|
||||
"/src/a.py",
|
||||
"
|
||||
x = None if flag else 1
|
||||
y = 0
|
||||
if x is not None:
|
||||
y = x
|
||||
",
|
||||
)?;
|
||||
|
||||
assert_public_ty(&db, "/src/a.py", "x", "Literal[1] | None");
|
||||
assert_public_ty(&db, "/src/a.py", "y", "Literal[0, 1]");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn while_loop() -> anyhow::Result<()> {
|
||||
let mut db = setup_db();
|
||||
@@ -2721,11 +2684,10 @@ mod tests {
|
||||
|
||||
fn first_public_def<'db>(db: &'db TestDb, file: File, name: &str) -> Definition<'db> {
|
||||
let scope = global_scope(db, file);
|
||||
use_def_map(db, scope)
|
||||
*use_def_map(db, scope)
|
||||
.public_definitions(symbol_table(db, scope).symbol_id_by_name(name).unwrap())
|
||||
.next()
|
||||
.first()
|
||||
.unwrap()
|
||||
.definition
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -1,115 +0,0 @@
|
||||
use crate::semantic_index::ast_ids::HasScopedAstId;
|
||||
use crate::semantic_index::definition::Definition;
|
||||
use crate::semantic_index::expression::Expression;
|
||||
use crate::semantic_index::symbol::{ScopeId, ScopedSymbolId, SymbolTable};
|
||||
use crate::semantic_index::symbol_table;
|
||||
use crate::types::{infer_expression_types, IntersectionBuilder, Type, TypeInference};
|
||||
use crate::Db;
|
||||
use ruff_python_ast as ast;
|
||||
use rustc_hash::FxHashMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Return the type constraint that `test` (if true) would place on `definition`, if any.
|
||||
///
|
||||
/// For example, if we have this code:
|
||||
///
|
||||
/// ```python
|
||||
/// y = 1 if flag else None
|
||||
/// x = 1 if flag else None
|
||||
/// if x is not None:
|
||||
/// ...
|
||||
/// ```
|
||||
///
|
||||
/// The `test` expression `x is not None` places the constraint "not None" on the definition of
|
||||
/// `x`, so in that case we'd return `Some(Type::Intersection(negative=[Type::None]))`.
|
||||
///
|
||||
/// But if we called this with the same `test` expression, but the `definition` of `y`, no
|
||||
/// constraint is applied to that definition, so we'd just return `None`.
|
||||
pub(crate) fn narrowing_constraint<'db>(
|
||||
db: &'db dyn Db,
|
||||
test: Expression<'db>,
|
||||
definition: Definition<'db>,
|
||||
) -> Option<Type<'db>> {
|
||||
all_narrowing_constraints(db, test)
|
||||
.get(&definition.symbol(db))
|
||||
.copied()
|
||||
}
|
||||
|
||||
#[salsa::tracked(return_ref)]
|
||||
fn all_narrowing_constraints<'db>(
|
||||
db: &'db dyn Db,
|
||||
test: Expression<'db>,
|
||||
) -> NarrowingConstraints<'db> {
|
||||
NarrowingConstraintsBuilder::new(db, test).finish()
|
||||
}
|
||||
|
||||
type NarrowingConstraints<'db> = FxHashMap<ScopedSymbolId, Type<'db>>;
|
||||
|
||||
struct NarrowingConstraintsBuilder<'db> {
|
||||
db: &'db dyn Db,
|
||||
expression: Expression<'db>,
|
||||
constraints: NarrowingConstraints<'db>,
|
||||
}
|
||||
|
||||
impl<'db> NarrowingConstraintsBuilder<'db> {
|
||||
fn new(db: &'db dyn Db, expression: Expression<'db>) -> Self {
|
||||
Self {
|
||||
db,
|
||||
expression,
|
||||
constraints: NarrowingConstraints::default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn finish(mut self) -> NarrowingConstraints<'db> {
|
||||
if let ast::Expr::Compare(expr_compare) = self.expression.node_ref(self.db).node() {
|
||||
self.add_expr_compare(expr_compare);
|
||||
}
|
||||
// TODO other test expression kinds
|
||||
|
||||
self.constraints.shrink_to_fit();
|
||||
self.constraints
|
||||
}
|
||||
|
||||
fn symbols(&self) -> Arc<SymbolTable> {
|
||||
symbol_table(self.db, self.scope())
|
||||
}
|
||||
|
||||
fn scope(&self) -> ScopeId<'db> {
|
||||
self.expression.scope(self.db)
|
||||
}
|
||||
|
||||
fn inference(&self) -> &'db TypeInference<'db> {
|
||||
infer_expression_types(self.db, self.expression)
|
||||
}
|
||||
|
||||
fn add_expr_compare(&mut self, expr_compare: &ast::ExprCompare) {
|
||||
let ast::ExprCompare {
|
||||
range: _,
|
||||
left,
|
||||
ops,
|
||||
comparators,
|
||||
} = expr_compare;
|
||||
|
||||
if let ast::Expr::Name(ast::ExprName {
|
||||
range: _,
|
||||
id,
|
||||
ctx: _,
|
||||
}) = left.as_ref()
|
||||
{
|
||||
// SAFETY: we should always have a symbol for every Name node.
|
||||
let symbol = self.symbols().symbol_id_by_name(id).unwrap();
|
||||
let scope = self.scope();
|
||||
let inference = self.inference();
|
||||
for (op, comparator) in std::iter::zip(&**ops, &**comparators) {
|
||||
let comp_ty = inference.expression_ty(comparator.scoped_ast_id(self.db, scope));
|
||||
if matches!(op, ast::CmpOp::IsNot) {
|
||||
let ty = IntersectionBuilder::new(self.db)
|
||||
.add_negative(comp_ty)
|
||||
.build();
|
||||
self.constraints.insert(symbol, ty);
|
||||
};
|
||||
// TODO other comparison types
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,7 +25,7 @@ crossbeam = { workspace = true }
|
||||
jod-thread = { workspace = true }
|
||||
lsp-server = { workspace = true }
|
||||
lsp-types = { workspace = true }
|
||||
rustc-hash = { workspace = true }
|
||||
foldhash = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
shellexpand = { workspace = true }
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use anyhow::Ok;
|
||||
use foldhash::{HashMap, HashMapExt};
|
||||
use lsp_types::NotebookCellKind;
|
||||
use ruff_notebook::CellMetadata;
|
||||
use rustc_hash::{FxBuildHasher, FxHashMap};
|
||||
|
||||
use crate::{PositionEncoding, TextDocument};
|
||||
|
||||
@@ -17,7 +17,7 @@ pub struct NotebookDocument {
|
||||
metadata: ruff_notebook::RawNotebookMetadata,
|
||||
version: DocumentVersion,
|
||||
// Used to quickly find the index of a cell for a given URL.
|
||||
cell_index: FxHashMap<lsp_types::Url, CellId>,
|
||||
cell_index: HashMap<lsp_types::Url, CellId>,
|
||||
}
|
||||
|
||||
/// A single cell within a notebook, which has text contents represented as a `TextDocument`.
|
||||
@@ -35,7 +35,7 @@ impl NotebookDocument {
|
||||
metadata: serde_json::Map<String, serde_json::Value>,
|
||||
cell_documents: Vec<lsp_types::TextDocumentItem>,
|
||||
) -> crate::Result<Self> {
|
||||
let mut cell_contents: FxHashMap<_, _> = cell_documents
|
||||
let mut cell_contents: HashMap<_, _> = cell_documents
|
||||
.into_iter()
|
||||
.map(|document| (document.uri, document.text))
|
||||
.collect();
|
||||
@@ -122,7 +122,7 @@ impl NotebookDocument {
|
||||
// Instead, it only provides that (a) these cell URIs were removed, and (b) these
|
||||
// cell URIs were added.
|
||||
// https://github.com/astral-sh/ruff/issues/12573
|
||||
let mut deleted_cells = FxHashMap::default();
|
||||
let mut deleted_cells = HashMap::default();
|
||||
|
||||
// First, delete the cells and remove them from the index.
|
||||
if delete > 0 {
|
||||
@@ -216,8 +216,8 @@ impl NotebookDocument {
|
||||
self.cells.get_mut(*self.cell_index.get(uri)?)
|
||||
}
|
||||
|
||||
fn make_cell_index(cells: &[NotebookCell]) -> FxHashMap<lsp_types::Url, CellId> {
|
||||
let mut index = FxHashMap::with_capacity_and_hasher(cells.len(), FxBuildHasher);
|
||||
fn make_cell_index(cells: &[NotebookCell]) -> HashMap<lsp_types::Url, CellId> {
|
||||
let mut index = HashMap::with_capacity(cells.len());
|
||||
for (i, cell) in cells.iter().enumerate() {
|
||||
index.insert(cell.url.clone(), i);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::any::TypeId;
|
||||
|
||||
use foldhash::HashMap;
|
||||
use lsp_server::{Notification, RequestId};
|
||||
use rustc_hash::FxHashMap;
|
||||
use serde_json::Value;
|
||||
|
||||
use super::{schedule::Task, ClientSender};
|
||||
@@ -23,7 +23,7 @@ pub(crate) struct Responder(ClientSender);
|
||||
pub(crate) struct Requester<'s> {
|
||||
sender: ClientSender,
|
||||
next_request_id: i32,
|
||||
response_handlers: FxHashMap<lsp_server::RequestId, ResponseBuilder<'s>>,
|
||||
response_handlers: HashMap<lsp_server::RequestId, ResponseBuilder<'s>>,
|
||||
}
|
||||
|
||||
impl<'s> Client<'s> {
|
||||
@@ -34,7 +34,7 @@ impl<'s> Client<'s> {
|
||||
requester: Requester {
|
||||
sender,
|
||||
next_request_id: 1,
|
||||
response_handlers: FxHashMap::default(),
|
||||
response_handlers: HashMap::default(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@ use std::borrow::Cow;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
|
||||
use foldhash::HashMap;
|
||||
use lsp_types::Url;
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
use crate::{
|
||||
edit::{DocumentKey, DocumentVersion, NotebookDocument},
|
||||
@@ -16,10 +16,10 @@ use super::ClientSettings;
|
||||
#[derive(Default, Debug)]
|
||||
pub(crate) struct Index {
|
||||
/// Maps all document file URLs to the associated document controller
|
||||
documents: FxHashMap<Url, DocumentController>,
|
||||
documents: HashMap<Url, DocumentController>,
|
||||
|
||||
/// Maps opaque cell URLs to a notebook URL (document)
|
||||
notebook_cells: FxHashMap<Url, Url>,
|
||||
notebook_cells: HashMap<Url, Url>,
|
||||
|
||||
/// Global settings provided by the client.
|
||||
global_settings: ClientSettings,
|
||||
@@ -28,8 +28,8 @@ pub(crate) struct Index {
|
||||
impl Index {
|
||||
pub(super) fn new(global_settings: ClientSettings) -> Self {
|
||||
Self {
|
||||
documents: FxHashMap::default(),
|
||||
notebook_cells: FxHashMap::default(),
|
||||
documents: HashMap::default(),
|
||||
notebook_cells: HashMap::default(),
|
||||
global_settings,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use foldhash::HashMap;
|
||||
use lsp_types::Url;
|
||||
use rustc_hash::FxHashMap;
|
||||
use serde::Deserialize;
|
||||
|
||||
/// Maps a workspace URI to its associated client settings. Used during server initialization.
|
||||
pub(crate) type WorkspaceSettingsMap = FxHashMap<Url, ClientSettings>;
|
||||
pub(crate) type WorkspaceSettingsMap = HashMap<Url, ClientSettings>;
|
||||
|
||||
/// This is a direct representation of the settings schema sent by the client.
|
||||
#[derive(Debug, Deserialize, Default)]
|
||||
|
||||
@@ -22,7 +22,7 @@ ruff_text_size = { workspace = true }
|
||||
anyhow = { workspace = true }
|
||||
crossbeam = { workspace = true }
|
||||
notify = { workspace = true }
|
||||
rustc-hash = { workspace = true }
|
||||
foldhash = { workspace = true }
|
||||
salsa = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use rustc_hash::FxHashSet;
|
||||
use foldhash::HashSet;
|
||||
|
||||
use ruff_db::files::{system_path_to_file, File, Files};
|
||||
use ruff_db::system::walk_directory::WalkState;
|
||||
@@ -18,13 +18,13 @@ impl RootDatabase {
|
||||
|
||||
let mut workspace_change = false;
|
||||
// Packages that need reloading
|
||||
let mut changed_packages = FxHashSet::default();
|
||||
let mut changed_packages = HashSet::default();
|
||||
// Paths that were added
|
||||
let mut added_paths = FxHashSet::default();
|
||||
let mut added_paths = HashSet::default();
|
||||
|
||||
// Deduplicate the `sync` calls. Many file watchers emit multiple events for the same path.
|
||||
let mut synced_files = FxHashSet::default();
|
||||
let mut synced_recursively = FxHashSet::default();
|
||||
let mut synced_files = HashSet::default();
|
||||
let mut synced_recursively = HashSet::default();
|
||||
|
||||
let mut sync_path = |db: &mut RootDatabase, path: &SystemPath| {
|
||||
if synced_files.insert(path.to_path_buf()) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::{collections::BTreeMap, sync::Arc};
|
||||
|
||||
use rustc_hash::{FxBuildHasher, FxHashSet};
|
||||
use foldhash::{HashMapExt, HashSet, HashSetExt};
|
||||
use salsa::{Durability, Setter as _};
|
||||
|
||||
pub use metadata::{PackageMetadata, WorkspaceMetadata};
|
||||
@@ -74,7 +74,7 @@ pub struct Workspace {
|
||||
/// open files rather than all files in the workspace.
|
||||
#[return_ref]
|
||||
#[default]
|
||||
open_fileset: Option<Arc<FxHashSet<File>>>,
|
||||
open_fileset: Option<Arc<HashSet<File>>>,
|
||||
|
||||
/// The (first-party) packages in this workspace.
|
||||
#[return_ref]
|
||||
@@ -219,7 +219,7 @@ impl Workspace {
|
||||
}
|
||||
|
||||
/// Returns the open files in the workspace or `None` if the entire workspace should be checked.
|
||||
pub fn open_files(self, db: &dyn Db) -> Option<&FxHashSet<File>> {
|
||||
pub fn open_files(self, db: &dyn Db) -> Option<&HashSet<File>> {
|
||||
self.open_fileset(db).as_deref()
|
||||
}
|
||||
|
||||
@@ -227,7 +227,7 @@ impl Workspace {
|
||||
///
|
||||
/// This changes the behavior of `check` to only check the open files rather than all files in the workspace.
|
||||
#[tracing::instrument(level = "debug", skip(self, db))]
|
||||
pub fn set_open_files(self, db: &mut dyn Db, open_files: FxHashSet<File>) {
|
||||
pub fn set_open_files(self, db: &mut dyn Db, open_files: HashSet<File>) {
|
||||
tracing::debug!("Set open workspace files (count: {})", open_files.len());
|
||||
|
||||
self.set_open_fileset(db).to(Some(Arc::new(open_files)));
|
||||
@@ -236,7 +236,7 @@ impl Workspace {
|
||||
/// This takes the open files from the workspace and returns them.
|
||||
///
|
||||
/// This changes the behavior of `check` to check all files in the workspace instead of just the open files.
|
||||
pub fn take_open_files(self, db: &mut dyn Db) -> FxHashSet<File> {
|
||||
pub fn take_open_files(self, db: &mut dyn Db) -> HashSet<File> {
|
||||
tracing::debug!("Take open workspace files");
|
||||
|
||||
// Salsa will cancel any pending queries and remove its own reference to `open_files`
|
||||
@@ -246,7 +246,7 @@ impl Workspace {
|
||||
if let Some(open_files) = open_files {
|
||||
Arc::try_unwrap(open_files).unwrap()
|
||||
} else {
|
||||
FxHashSet::default()
|
||||
HashSet::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -372,7 +372,7 @@ pub(super) fn check_file(db: &dyn Db, file: File) -> Diagnostics {
|
||||
Diagnostics::from(diagnostics)
|
||||
}
|
||||
|
||||
fn discover_package_files(db: &dyn Db, path: &SystemPath) -> FxHashSet<File> {
|
||||
fn discover_package_files(db: &dyn Db, path: &SystemPath) -> HashSet<File> {
|
||||
let paths = std::sync::Mutex::new(Vec::new());
|
||||
|
||||
db.system().walk_directory(path).run(|| {
|
||||
@@ -402,7 +402,7 @@ fn discover_package_files(db: &dyn Db, path: &SystemPath) -> FxHashSet<File> {
|
||||
});
|
||||
|
||||
let paths = paths.into_inner().unwrap();
|
||||
let mut files = FxHashSet::with_capacity_and_hasher(paths.len(), FxBuildHasher);
|
||||
let mut files = HashSet::with_capacity(paths.len());
|
||||
|
||||
for path in paths {
|
||||
// If this returns `None`, then the file was deleted between the `walk_directory` call and now.
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::iter::FusedIterator;
|
||||
use std::ops::Deref;
|
||||
use std::sync::Arc;
|
||||
|
||||
use rustc_hash::FxHashSet;
|
||||
use foldhash::HashSet;
|
||||
use salsa::Setter;
|
||||
|
||||
use ruff_db::files::File;
|
||||
@@ -105,7 +105,7 @@ pub struct LazyFiles<'a> {
|
||||
|
||||
impl<'a> LazyFiles<'a> {
|
||||
/// Sets the indexed files of a package to `files`.
|
||||
pub fn set(mut self, files: FxHashSet<File>) -> IndexedFiles {
|
||||
pub fn set(mut self, files: HashSet<File>) -> IndexedFiles {
|
||||
let files = IndexedFiles::new(files);
|
||||
*self.files = State::Indexed(files.clone());
|
||||
files
|
||||
@@ -127,11 +127,11 @@ impl<'a> LazyFiles<'a> {
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct IndexedFiles {
|
||||
revision: u64,
|
||||
files: Arc<std::sync::Mutex<FxHashSet<File>>>,
|
||||
files: Arc<std::sync::Mutex<HashSet<File>>>,
|
||||
}
|
||||
|
||||
impl IndexedFiles {
|
||||
fn new(files: FxHashSet<File>) -> Self {
|
||||
fn new(files: HashSet<File>) -> Self {
|
||||
Self {
|
||||
files: Arc::new(std::sync::Mutex::new(files)),
|
||||
revision: 0,
|
||||
@@ -155,11 +155,11 @@ impl PartialEq for IndexedFiles {
|
||||
impl Eq for IndexedFiles {}
|
||||
|
||||
pub struct IndexedFilesGuard<'a> {
|
||||
guard: std::sync::MutexGuard<'a, FxHashSet<File>>,
|
||||
guard: std::sync::MutexGuard<'a, HashSet<File>>,
|
||||
}
|
||||
|
||||
impl Deref for IndexedFilesGuard<'_> {
|
||||
type Target = FxHashSet<File>;
|
||||
type Target = HashSet<File>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.guard
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
use red_knot_python_semantic::{
|
||||
HasTy, ProgramSettings, PythonVersion, SearchPathSettings, SemanticModel,
|
||||
};
|
||||
use red_knot_python_semantic::{ProgramSettings, PythonVersion, SearchPathSettings};
|
||||
use red_knot_workspace::db::RootDatabase;
|
||||
use red_knot_workspace::lint::lint_semantic;
|
||||
use red_knot_workspace::workspace::WorkspaceMetadata;
|
||||
use ruff_db::files::{system_path_to_file, File};
|
||||
use ruff_db::parsed::parsed_module;
|
||||
use ruff_db::system::{OsSystem, SystemPath, SystemPathBuf};
|
||||
use ruff_python_ast::visitor::source_order;
|
||||
use ruff_python_ast::visitor::source_order::SourceOrderVisitor;
|
||||
use ruff_python_ast::{Alias, Expr, Parameter, ParameterWithDefault, Stmt};
|
||||
use ruff_db::files::system_path_to_file;
|
||||
use ruff_db::system::{OsSystem, SystemPathBuf};
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
|
||||
@@ -33,100 +28,17 @@ fn setup_db(workspace_root: SystemPathBuf) -> anyhow::Result<RootDatabase> {
|
||||
#[allow(clippy::print_stdout)]
|
||||
fn corpus_no_panic() -> anyhow::Result<()> {
|
||||
let corpus = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("resources/test/corpus");
|
||||
let system_corpus = SystemPath::from_std_path(&corpus).expect("corpus path to be UTF8");
|
||||
let db = setup_db(system_corpus.to_path_buf())?;
|
||||
let system_corpus =
|
||||
SystemPathBuf::from_path_buf(corpus.clone()).expect("corpus path to be UTF8");
|
||||
let db = setup_db(system_corpus.clone())?;
|
||||
|
||||
for path in fs::read_dir(&corpus).expect("corpus to be a directory") {
|
||||
let path = path.expect("path to not be an error").path();
|
||||
println!("checking {path:?}");
|
||||
let path = SystemPathBuf::from_path_buf(path.clone()).expect("path to be UTF-8");
|
||||
// this test is only asserting that we can pull every expression type without a panic
|
||||
// (and some non-expressions that clearly define a single type)
|
||||
// this test is only asserting that we can run the lint without a panic
|
||||
let file = system_path_to_file(&db, path).expect("file to exist");
|
||||
|
||||
pull_types(&db, file);
|
||||
lint_semantic(&db, file);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn pull_types(db: &RootDatabase, file: File) {
|
||||
let mut visitor = PullTypesVisitor::new(db, file);
|
||||
|
||||
let ast = parsed_module(db, file);
|
||||
|
||||
visitor.visit_body(ast.suite());
|
||||
}
|
||||
|
||||
struct PullTypesVisitor<'db> {
|
||||
model: SemanticModel<'db>,
|
||||
}
|
||||
|
||||
impl<'db> PullTypesVisitor<'db> {
|
||||
fn new(db: &'db RootDatabase, file: File) -> Self {
|
||||
Self {
|
||||
model: SemanticModel::new(db, file),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SourceOrderVisitor<'_> for PullTypesVisitor<'_> {
|
||||
fn visit_stmt(&mut self, stmt: &Stmt) {
|
||||
match stmt {
|
||||
Stmt::FunctionDef(function) => {
|
||||
let _ty = function.ty(&self.model);
|
||||
}
|
||||
Stmt::ClassDef(class) => {
|
||||
let _ty = class.ty(&self.model);
|
||||
}
|
||||
Stmt::AnnAssign(_)
|
||||
| Stmt::Return(_)
|
||||
| Stmt::Delete(_)
|
||||
| Stmt::Assign(_)
|
||||
| Stmt::AugAssign(_)
|
||||
| Stmt::TypeAlias(_)
|
||||
| Stmt::For(_)
|
||||
| Stmt::While(_)
|
||||
| Stmt::If(_)
|
||||
| Stmt::With(_)
|
||||
| Stmt::Match(_)
|
||||
| Stmt::Raise(_)
|
||||
| Stmt::Try(_)
|
||||
| Stmt::Assert(_)
|
||||
| Stmt::Import(_)
|
||||
| Stmt::ImportFrom(_)
|
||||
| Stmt::Global(_)
|
||||
| Stmt::Nonlocal(_)
|
||||
| Stmt::Expr(_)
|
||||
| Stmt::Pass(_)
|
||||
| Stmt::Break(_)
|
||||
| Stmt::Continue(_)
|
||||
| Stmt::IpyEscapeCommand(_) => {}
|
||||
}
|
||||
|
||||
source_order::walk_stmt(self, stmt);
|
||||
}
|
||||
|
||||
fn visit_expr(&mut self, expr: &Expr) {
|
||||
let _ty = expr.ty(&self.model);
|
||||
|
||||
source_order::walk_expr(self, expr);
|
||||
}
|
||||
|
||||
fn visit_parameter(&mut self, parameter: &Parameter) {
|
||||
let _ty = parameter.ty(&self.model);
|
||||
|
||||
source_order::walk_parameter(self, parameter);
|
||||
}
|
||||
|
||||
fn visit_parameter_with_default(&mut self, parameter_with_default: &ParameterWithDefault) {
|
||||
let _ty = parameter_with_default.ty(&self.model);
|
||||
|
||||
source_order::walk_parameter_with_default(self, parameter_with_default);
|
||||
}
|
||||
|
||||
fn visit_alias(&mut self, alias: &Alias) {
|
||||
let _ty = alias.ty(&self.model);
|
||||
|
||||
source_order::walk_alias(self, alias);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ notify = { workspace = true }
|
||||
path-absolutize = { workspace = true, features = ["once_cell_cache"] }
|
||||
rayon = { workspace = true }
|
||||
regex = { workspace = true }
|
||||
rustc-hash = { workspace = true }
|
||||
foldhash = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
shellexpand = { workspace = true }
|
||||
|
||||
@@ -9,9 +9,9 @@ use anyhow::{anyhow, bail};
|
||||
use clap::builder::{TypedValueParser, ValueParserFactory};
|
||||
use clap::{command, Parser};
|
||||
use colored::Colorize;
|
||||
use foldhash::HashMap;
|
||||
use path_absolutize::path_dedot;
|
||||
use regex::Regex;
|
||||
use rustc_hash::FxHashMap;
|
||||
use toml;
|
||||
|
||||
use ruff_linter::line_width::LineLength;
|
||||
@@ -1278,7 +1278,7 @@ impl ConfigurationTransformer for ExplicitConfigOverrides {
|
||||
|
||||
/// Convert a list of `PatternPrefixPair` structs to `PerFileIgnore`.
|
||||
pub fn collect_per_file_ignores(pairs: Vec<PatternPrefixPair>) -> Vec<PerFileIgnore> {
|
||||
let mut per_file_ignores: FxHashMap<String, Vec<RuleSelector>> = FxHashMap::default();
|
||||
let mut per_file_ignores: HashMap<String, Vec<RuleSelector>> = HashMap::default();
|
||||
for pair in pairs {
|
||||
per_file_ignores
|
||||
.entry(pair.pattern)
|
||||
|
||||
@@ -9,11 +9,11 @@ use std::time::{Duration, SystemTime};
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use filetime::FileTime;
|
||||
use foldhash::HashMap;
|
||||
use itertools::Itertools;
|
||||
use log::{debug, error};
|
||||
use rayon::iter::ParallelIterator;
|
||||
use rayon::iter::{IntoParallelIterator, ParallelBridge};
|
||||
use rustc_hash::FxHashMap;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tempfile::NamedTempFile;
|
||||
|
||||
@@ -140,7 +140,7 @@ impl Cache {
|
||||
fn empty(path: PathBuf, package_root: PathBuf) -> Self {
|
||||
let package = PackageCache {
|
||||
package_root,
|
||||
files: FxHashMap::default(),
|
||||
files: HashMap::default(),
|
||||
};
|
||||
Cache::new(path, package)
|
||||
}
|
||||
@@ -318,7 +318,7 @@ struct PackageCache {
|
||||
/// single file "packages", e.g. scripts.
|
||||
package_root: PathBuf,
|
||||
/// Mapping of source file path to it's cached data.
|
||||
files: FxHashMap<RelativePathBuf, FileCache>,
|
||||
files: HashMap<RelativePathBuf, FileCache>,
|
||||
}
|
||||
|
||||
/// On disk representation of the cache per source file.
|
||||
@@ -357,9 +357,9 @@ impl FileCache {
|
||||
.collect()
|
||||
};
|
||||
let notebook_indexes = if let Some(notebook_index) = lint.notebook_index.as_ref() {
|
||||
FxHashMap::from_iter([(path.to_string_lossy().to_string(), notebook_index.clone())])
|
||||
HashMap::from_iter([(path.to_string_lossy().to_string(), notebook_index.clone())])
|
||||
} else {
|
||||
FxHashMap::default()
|
||||
HashMap::default()
|
||||
};
|
||||
Diagnostics::new(messages, notebook_indexes)
|
||||
})
|
||||
@@ -493,11 +493,11 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct PackageCacheMap<'a>(FxHashMap<&'a Path, Cache>);
|
||||
pub(crate) struct PackageCacheMap<'a>(HashMap<&'a Path, Cache>);
|
||||
|
||||
impl<'a> PackageCacheMap<'a> {
|
||||
pub(crate) fn init(
|
||||
package_roots: &FxHashMap<&'a Path, Option<&'a Path>>,
|
||||
package_roots: &HashMap<&'a Path, Option<&'a Path>>,
|
||||
resolver: &Resolver,
|
||||
) -> Self {
|
||||
fn init_cache(path: &Path) {
|
||||
|
||||
@@ -5,11 +5,11 @@ use std::time::Instant;
|
||||
|
||||
use anyhow::Result;
|
||||
use colored::Colorize;
|
||||
use foldhash::HashMap;
|
||||
use ignore::Error;
|
||||
use log::{debug, error, warn};
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
use rayon::prelude::*;
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
use ruff_diagnostics::Diagnostic;
|
||||
use ruff_linter::message::Message;
|
||||
@@ -133,7 +133,7 @@ pub(crate) fn check(
|
||||
dummy,
|
||||
TextSize::default(),
|
||||
)],
|
||||
FxHashMap::default(),
|
||||
HashMap::default(),
|
||||
)
|
||||
} else {
|
||||
warn!(
|
||||
@@ -221,7 +221,7 @@ mod test {
|
||||
use std::os::unix::fs::OpenOptionsExt;
|
||||
|
||||
use anyhow::Result;
|
||||
use rustc_hash::FxHashMap;
|
||||
use foldhash::HashMap;
|
||||
use tempfile::TempDir;
|
||||
|
||||
use ruff_linter::message::{Emitter, EmitterContext, TextEmitter};
|
||||
@@ -284,7 +284,7 @@ mod test {
|
||||
.emit(
|
||||
&mut output,
|
||||
&diagnostics.messages,
|
||||
&EmitterContext::new(&FxHashMap::default()),
|
||||
&EmitterContext::new(&HashMap::default()),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
|
||||
@@ -7,11 +7,11 @@ use std::time::Instant;
|
||||
|
||||
use anyhow::Result;
|
||||
use colored::Colorize;
|
||||
use foldhash::HashSet;
|
||||
use itertools::Itertools;
|
||||
use log::{error, warn};
|
||||
use rayon::iter::Either::{Left, Right};
|
||||
use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
|
||||
use rustc_hash::FxHashSet;
|
||||
use thiserror::Error;
|
||||
use tracing::debug;
|
||||
|
||||
@@ -782,7 +782,7 @@ impl Display for FormatCommandError {
|
||||
|
||||
pub(super) fn warn_incompatible_formatter_settings(resolver: &Resolver) {
|
||||
// First, collect all rules that are incompatible regardless of the linter-specific settings.
|
||||
let mut incompatible_rules = FxHashSet::default();
|
||||
let mut incompatible_rules = HashSet::default();
|
||||
for setting in resolver.settings() {
|
||||
for rule in [
|
||||
// The formatter might collapse implicit string concatenation on a single line.
|
||||
|
||||
@@ -9,8 +9,8 @@ use std::path::Path;
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use colored::Colorize;
|
||||
use foldhash::HashMap;
|
||||
use log::{debug, warn};
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
use ruff_diagnostics::Diagnostic;
|
||||
use ruff_linter::codes::Rule;
|
||||
@@ -33,13 +33,13 @@ use crate::cache::{Cache, FileCacheKey, LintCacheData};
|
||||
pub(crate) struct Diagnostics {
|
||||
pub(crate) messages: Vec<Message>,
|
||||
pub(crate) fixed: FixMap,
|
||||
pub(crate) notebook_indexes: FxHashMap<String, NotebookIndex>,
|
||||
pub(crate) notebook_indexes: HashMap<String, NotebookIndex>,
|
||||
}
|
||||
|
||||
impl Diagnostics {
|
||||
pub(crate) fn new(
|
||||
messages: Vec<Message>,
|
||||
notebook_indexes: FxHashMap<String, NotebookIndex>,
|
||||
notebook_indexes: HashMap<String, NotebookIndex>,
|
||||
) -> Self {
|
||||
Self {
|
||||
messages,
|
||||
@@ -72,7 +72,7 @@ impl Diagnostics {
|
||||
source_file,
|
||||
TextSize::default(),
|
||||
)],
|
||||
FxHashMap::default(),
|
||||
HashMap::default(),
|
||||
)
|
||||
} else {
|
||||
match path {
|
||||
@@ -106,7 +106,7 @@ impl Diagnostics {
|
||||
range: TextRange::default(),
|
||||
file: dummy,
|
||||
})],
|
||||
FxHashMap::default(),
|
||||
HashMap::default(),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -132,7 +132,7 @@ impl AddAssign for Diagnostics {
|
||||
|
||||
/// A collection of fixes indexed by file path.
|
||||
#[derive(Debug, Default, PartialEq)]
|
||||
pub(crate) struct FixMap(FxHashMap<String, FixTable>);
|
||||
pub(crate) struct FixMap(HashMap<String, FixTable>);
|
||||
|
||||
impl FixMap {
|
||||
/// Returns `true` if there are no fixes in the map.
|
||||
@@ -314,7 +314,7 @@ pub(crate) fn lint_path(
|
||||
ParseSource::None,
|
||||
);
|
||||
let transformed = source_kind;
|
||||
let fixed = FxHashMap::default();
|
||||
let fixed = HashMap::default();
|
||||
(result, transformed, fixed)
|
||||
}
|
||||
} else {
|
||||
@@ -328,7 +328,7 @@ pub(crate) fn lint_path(
|
||||
ParseSource::None,
|
||||
);
|
||||
let transformed = source_kind;
|
||||
let fixed = FxHashMap::default();
|
||||
let fixed = HashMap::default();
|
||||
(result, transformed, fixed)
|
||||
};
|
||||
|
||||
@@ -357,9 +357,9 @@ pub(crate) fn lint_path(
|
||||
}
|
||||
|
||||
let notebook_indexes = if let SourceKind::IpyNotebook(notebook) = transformed {
|
||||
FxHashMap::from_iter([(path.to_string_lossy().to_string(), notebook.into_index())])
|
||||
HashMap::from_iter([(path.to_string_lossy().to_string(), notebook.into_index())])
|
||||
} else {
|
||||
FxHashMap::default()
|
||||
HashMap::default()
|
||||
};
|
||||
|
||||
Ok(Diagnostics {
|
||||
@@ -456,7 +456,7 @@ pub(crate) fn lint_stdin(
|
||||
}
|
||||
|
||||
let transformed = source_kind;
|
||||
let fixed = FxHashMap::default();
|
||||
let fixed = HashMap::default();
|
||||
(result, transformed, fixed)
|
||||
}
|
||||
} else {
|
||||
@@ -470,17 +470,17 @@ pub(crate) fn lint_stdin(
|
||||
ParseSource::None,
|
||||
);
|
||||
let transformed = source_kind;
|
||||
let fixed = FxHashMap::default();
|
||||
let fixed = HashMap::default();
|
||||
(result, transformed, fixed)
|
||||
};
|
||||
|
||||
let notebook_indexes = if let SourceKind::IpyNotebook(notebook) = transformed {
|
||||
FxHashMap::from_iter([(
|
||||
HashMap::from_iter([(
|
||||
path.map_or_else(|| "-".into(), |path| path.to_string_lossy().to_string()),
|
||||
notebook.into_index(),
|
||||
)])
|
||||
} else {
|
||||
FxHashMap::default()
|
||||
HashMap::default()
|
||||
};
|
||||
|
||||
Ok(Diagnostics {
|
||||
|
||||
@@ -1434,7 +1434,7 @@ def unused(x):
|
||||
|
||||
insta::assert_snapshot!(test_code, @r###"
|
||||
|
||||
def unused(x): # noqa: ANN001, ANN201, D103
|
||||
def unused(x): # noqa: ANN001, ANN201, ARG001, D103
|
||||
pass
|
||||
"###);
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ thiserror = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
tracing-subscriber = { workspace = true, optional = true }
|
||||
tracing-tree = { workspace = true, optional = true }
|
||||
rustc-hash = { workspace = true }
|
||||
foldhash = { workspace = true }
|
||||
|
||||
[target.'cfg(not(target_arch="wasm32"))'.dependencies]
|
||||
zip = { workspace = true, features = ["zstd"] }
|
||||
|
||||
@@ -6,6 +6,7 @@ use dashmap::mapref::entry::Entry;
|
||||
use salsa::{Durability, Setter};
|
||||
|
||||
pub use file_root::{FileRoot, FileRootKind};
|
||||
use foldhash::{HashMapExt, HashSetExt};
|
||||
pub use path::FilePath;
|
||||
use ruff_notebook::{Notebook, NotebookError};
|
||||
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
use std::hash::BuildHasherDefault;
|
||||
|
||||
use rustc_hash::FxHasher;
|
||||
use foldhash::fast::RandomState;
|
||||
|
||||
use crate::files::Files;
|
||||
use crate::system::System;
|
||||
@@ -15,8 +13,8 @@ pub mod system;
|
||||
pub mod testing;
|
||||
pub mod vendored;
|
||||
|
||||
pub type FxDashMap<K, V> = dashmap::DashMap<K, V, BuildHasherDefault<FxHasher>>;
|
||||
pub type FxDashSet<K> = dashmap::DashSet<K, BuildHasherDefault<FxHasher>>;
|
||||
pub type FxDashMap<K, V> = dashmap::DashMap<K, V, RandomState>;
|
||||
pub type FxDashSet<K> = dashmap::DashSet<K, RandomState>;
|
||||
|
||||
/// Most basic database that gives access to files, the host system, source code, and parsed AST.
|
||||
#[salsa::db]
|
||||
|
||||
@@ -4,7 +4,7 @@ use std::sync::{Arc, RwLock, RwLockWriteGuard};
|
||||
|
||||
use camino::{Utf8Path, Utf8PathBuf};
|
||||
use filetime::FileTime;
|
||||
use rustc_hash::FxHashMap;
|
||||
use foldhash::{HashMap, HashMapExt, HashSetExt};
|
||||
|
||||
use ruff_notebook::{Notebook, NotebookError};
|
||||
|
||||
@@ -54,7 +54,7 @@ impl MemoryFileSystem {
|
||||
let fs = Self {
|
||||
inner: Arc::new(MemoryFileSystemInner {
|
||||
by_path: RwLock::new(BTreeMap::default()),
|
||||
virtual_files: RwLock::new(FxHashMap::default()),
|
||||
virtual_files: RwLock::new(HashMap::default()),
|
||||
cwd: cwd.clone(),
|
||||
}),
|
||||
};
|
||||
@@ -385,7 +385,7 @@ impl std::fmt::Debug for MemoryFileSystem {
|
||||
|
||||
struct MemoryFileSystemInner {
|
||||
by_path: RwLock<BTreeMap<Utf8PathBuf, Entry>>,
|
||||
virtual_files: RwLock<FxHashMap<SystemVirtualPathBuf, File>>,
|
||||
virtual_files: RwLock<HashMap<SystemVirtualPathBuf, File>>,
|
||||
cwd: SystemPathBuf,
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ ruff_macros = { workspace = true }
|
||||
ruff_text_size = { workspace = true }
|
||||
|
||||
drop_bomb = { workspace = true }
|
||||
rustc-hash = { workspace = true }
|
||||
foldhash = { workspace = true }
|
||||
schemars = { workspace = true, optional = true }
|
||||
serde = { workspace = true, optional = true }
|
||||
static_assertions = { workspace = true }
|
||||
|
||||
@@ -2,7 +2,7 @@ use super::{write, Arguments, FormatElement};
|
||||
use crate::format_element::Interned;
|
||||
use crate::prelude::LineMode;
|
||||
use crate::{FormatResult, FormatState};
|
||||
use rustc_hash::FxHashMap;
|
||||
use foldhash::HashMap;
|
||||
use std::any::{Any, TypeId};
|
||||
use std::fmt::Debug;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
@@ -349,7 +349,7 @@ pub struct RemoveSoftLinesBuffer<'a, Context> {
|
||||
///
|
||||
/// It's fine to not snapshot the cache. The worst that can happen is that it holds on interned elements
|
||||
/// that are now unused. But there's little harm in that and the cache is cleaned when dropping the buffer.
|
||||
interned_cache: FxHashMap<Interned, Interned>,
|
||||
interned_cache: HashMap<Interned, Interned>,
|
||||
}
|
||||
|
||||
impl<'a, Context> RemoveSoftLinesBuffer<'a, Context> {
|
||||
@@ -357,7 +357,7 @@ impl<'a, Context> RemoveSoftLinesBuffer<'a, Context> {
|
||||
pub fn new(inner: &'a mut dyn Buffer<Context = Context>) -> Self {
|
||||
Self {
|
||||
inner,
|
||||
interned_cache: FxHashMap::default(),
|
||||
interned_cache: HashMap::default(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -370,7 +370,7 @@ impl<'a, Context> RemoveSoftLinesBuffer<'a, Context> {
|
||||
// Extracted to function to avoid monomorphization
|
||||
fn clean_interned(
|
||||
interned: &Interned,
|
||||
interned_cache: &mut FxHashMap<Interned, Interned>,
|
||||
interned_cache: &mut HashMap<Interned, Interned>,
|
||||
) -> Interned {
|
||||
if let Some(cleaned) = interned_cache.get(interned) {
|
||||
cleaned.clone()
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
use std::collections::HashMap;
|
||||
use std::ops::Deref;
|
||||
|
||||
use rustc_hash::FxHashMap;
|
||||
use foldhash::HashMap;
|
||||
|
||||
use crate::format_element::tag::{Condition, DedentMode};
|
||||
use crate::prelude::tag::GroupMode;
|
||||
@@ -57,7 +56,7 @@ impl Document {
|
||||
fn propagate_expands<'a>(
|
||||
elements: &'a [FormatElement],
|
||||
enclosing: &mut Vec<Enclosing<'a>>,
|
||||
checked_interned: &mut FxHashMap<&'a Interned, bool>,
|
||||
checked_interned: &mut HashMap<&'a Interned, bool>,
|
||||
) -> bool {
|
||||
let mut expands = false;
|
||||
for element in elements {
|
||||
@@ -147,7 +146,7 @@ impl Document {
|
||||
} else {
|
||||
self.len().ilog2() as usize
|
||||
});
|
||||
let mut interned = FxHashMap::default();
|
||||
let mut interned = HashMap::default();
|
||||
propagate_expands(self, &mut enclosing, &mut interned);
|
||||
}
|
||||
|
||||
@@ -210,7 +209,7 @@ impl<'a> IrFormatContext<'a> {
|
||||
fn new(source_code: SourceCode<'a>) -> Self {
|
||||
Self {
|
||||
source_code,
|
||||
printed_interned_elements: HashMap::new(),
|
||||
printed_interned_elements: HashMap::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ pep440_rs = { workspace = true, features = ["serde"] }
|
||||
pyproject-toml = { workspace = true }
|
||||
quick-junit = { workspace = true }
|
||||
regex = { workspace = true }
|
||||
rustc-hash = { workspace = true }
|
||||
foldhash = { workspace = true }
|
||||
schemars = { workspace = true, optional = true }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
|
||||
@@ -17,11 +17,6 @@ def test():
|
||||
1 in (1, 2)
|
||||
|
||||
|
||||
def test2():
|
||||
1 in (1, 2)
|
||||
return
|
||||
|
||||
|
||||
data = [x for x in [1, 2, 3] if x in (1, 2)]
|
||||
|
||||
|
||||
|
||||
@@ -4,7 +4,3 @@ from mod import CamelCase as CC
|
||||
|
||||
# OK depending on configured import convention
|
||||
import xml.etree.ElementTree as ET
|
||||
from xml.etree import ElementTree as ET
|
||||
|
||||
# Always an error (relative import)
|
||||
from ..xml.eltree import ElementTree as ET
|
||||
|
||||
@@ -42,9 +42,3 @@ class Foo:
|
||||
@classmethod
|
||||
def graze(cls, x, y, z):
|
||||
pass
|
||||
|
||||
|
||||
class Foo:
|
||||
@staticmethod
|
||||
def __new__(cls, x, y, z): # OK, see https://docs.python.org/3/reference/datamodel.html#basic-customization
|
||||
pass
|
||||
|
||||
@@ -3,7 +3,7 @@ class Fruit:
|
||||
def list_fruits(cls) -> None:
|
||||
cls = "apple" # PLW0642
|
||||
cls: Fruit = "apple" # PLW0642
|
||||
cls += "orange" # OK, augmented assignments are ignored
|
||||
cls += "orange" # PLW0642
|
||||
*cls = "banana" # PLW0642
|
||||
cls, blah = "apple", "orange" # PLW0642
|
||||
blah, (cls, blah2) = "apple", ("orange", "banana") # PLW0642
|
||||
@@ -16,7 +16,7 @@ class Fruit:
|
||||
def print_color(self) -> None:
|
||||
self = "red" # PLW0642
|
||||
self: Self = "red" # PLW0642
|
||||
self += "blue" # OK, augmented assignments are ignored
|
||||
self += "blue" # PLW0642
|
||||
*self = "blue" # PLW0642
|
||||
self, blah = "red", "blue" # PLW0642
|
||||
blah, (self, blah2) = "apple", ("orange", "banana") # PLW0642
|
||||
|
||||
@@ -59,12 +59,3 @@ def negative_cases():
|
||||
# See https://docs.python.org/3/howto/logging-cookbook.html#formatting-styles
|
||||
import logging
|
||||
logging.info("yet {another} non-f-string")
|
||||
|
||||
# See https://fastapi.tiangolo.com/tutorial/path-params/
|
||||
from fastapi import FastAPI
|
||||
app = FastAPI()
|
||||
item_id = 42
|
||||
|
||||
@app.get("/items/{item_id}")
|
||||
async def read_item(item_id):
|
||||
return {"item_id": item_id}
|
||||
|
||||
@@ -78,13 +78,3 @@ async def test():
|
||||
async def test() -> str:
|
||||
vals = [str(val) for val in await async_func(1)]
|
||||
return ",".join(vals)
|
||||
|
||||
|
||||
from fastapi import FastAPI
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
@app.post("/count")
|
||||
async def fastapi_route(): # Ok: FastApi routes can be async without actually using await
|
||||
return 1
|
||||
|
||||
@@ -1112,6 +1112,9 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
}
|
||||
}
|
||||
Stmt::AugAssign(aug_assign @ ast::StmtAugAssign { target, .. }) => {
|
||||
if checker.enabled(Rule::SelfOrClsAssignment) {
|
||||
pylint::rules::self_or_cls_assignment(checker, target);
|
||||
}
|
||||
if checker.enabled(Rule::GlobalStatement) {
|
||||
if let Expr::Name(ast::ExprName { id, .. }) = target.as_ref() {
|
||||
pylint::rules::global_statement(checker, id);
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
use foldhash::HashSet;
|
||||
use itertools::Itertools;
|
||||
use rustc_hash::FxHashSet;
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Edit, Fix};
|
||||
use ruff_python_trivia::CommentRanges;
|
||||
@@ -132,7 +132,7 @@ pub(crate) fn check_noqa(
|
||||
let mut unknown_codes = vec![];
|
||||
let mut unmatched_codes = vec![];
|
||||
let mut valid_codes = vec![];
|
||||
let mut seen_codes = FxHashSet::default();
|
||||
let mut seen_codes = HashSet::default();
|
||||
let mut self_ignore = false;
|
||||
for original_code in directive.iter().map(Code::as_str) {
|
||||
let code = get_redirect_target(original_code).unwrap_or(original_code);
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use itertools::Itertools;
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use foldhash::{HashMap, HashMapExt, HashSet};
|
||||
use ruff_text_size::{Ranged, TextLen, TextRange, TextSize};
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Edit, Fix, IsolationLevel, SourceMap};
|
||||
use ruff_source_file::Locator;
|
||||
@@ -57,8 +57,8 @@ fn apply_fixes<'a>(
|
||||
let mut output = String::with_capacity(locator.len());
|
||||
let mut last_pos: Option<TextSize> = None;
|
||||
let mut applied: BTreeSet<&Edit> = BTreeSet::default();
|
||||
let mut isolated: FxHashSet<u32> = FxHashSet::default();
|
||||
let mut fixed = FxHashMap::default();
|
||||
let mut isolated: HashSet<u32> = HashSet::default();
|
||||
let mut fixed = HashMap::default();
|
||||
let mut source_map = SourceMap::default();
|
||||
|
||||
for (rule, fix) in diagnostics
|
||||
|
||||
@@ -4,8 +4,8 @@ use std::path::Path;
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use colored::Colorize;
|
||||
use foldhash::HashMap;
|
||||
use itertools::Itertools;
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
use ruff_diagnostics::Diagnostic;
|
||||
use ruff_notebook::Notebook;
|
||||
@@ -43,7 +43,7 @@ pub struct LinterResult {
|
||||
pub has_syntax_error: bool,
|
||||
}
|
||||
|
||||
pub type FixTable = FxHashMap<Rule, usize>;
|
||||
pub type FixTable = HashMap<Rule, usize>;
|
||||
|
||||
pub struct FixerResult<'a> {
|
||||
/// The result returned by the linter, after applying any fixes.
|
||||
@@ -476,7 +476,7 @@ pub fn lint_fix<'a>(
|
||||
let mut transformed = Cow::Borrowed(source_kind);
|
||||
|
||||
// Track the number of fixed errors across iterations.
|
||||
let mut fixed = FxHashMap::default();
|
||||
let mut fixed = HashMap::default();
|
||||
|
||||
// As an escape hatch, bail after 100 iterations.
|
||||
let mut iterations = 0;
|
||||
|
||||
@@ -5,10 +5,10 @@ use std::sync::Mutex;
|
||||
use anyhow::Result;
|
||||
use colored::Colorize;
|
||||
use fern;
|
||||
use foldhash::HashSet;
|
||||
use log::Level;
|
||||
use once_cell::sync::Lazy;
|
||||
use ruff_python_parser::{ParseError, ParseErrorType};
|
||||
use rustc_hash::FxHashSet;
|
||||
|
||||
use ruff_source_file::{LineIndex, OneIndexed, SourceCode, SourceLocation};
|
||||
|
||||
@@ -35,7 +35,7 @@ macro_rules! warn_user_once_by_id {
|
||||
};
|
||||
}
|
||||
|
||||
pub static MESSAGES: Lazy<Mutex<FxHashSet<String>>> = Lazy::new(Mutex::default);
|
||||
pub static MESSAGES: Lazy<Mutex<HashSet<String>>> = Lazy::new(Mutex::default);
|
||||
|
||||
/// Warn a user once, if warnings are enabled, with uniqueness determined by the content of the
|
||||
/// message.
|
||||
|
||||
@@ -3,7 +3,7 @@ use std::collections::BTreeMap;
|
||||
use std::io::Write;
|
||||
use std::ops::Deref;
|
||||
|
||||
use rustc_hash::FxHashMap;
|
||||
use foldhash::HashMap;
|
||||
|
||||
pub use azure::AzureEmitter;
|
||||
pub use github::GithubEmitter;
|
||||
@@ -285,11 +285,11 @@ pub trait Emitter {
|
||||
|
||||
/// Context passed to [`Emitter`].
|
||||
pub struct EmitterContext<'a> {
|
||||
notebook_indexes: &'a FxHashMap<String, NotebookIndex>,
|
||||
notebook_indexes: &'a HashMap<String, NotebookIndex>,
|
||||
}
|
||||
|
||||
impl<'a> EmitterContext<'a> {
|
||||
pub fn new(notebook_indexes: &'a FxHashMap<String, NotebookIndex>) -> Self {
|
||||
pub fn new(notebook_indexes: &'a HashMap<String, NotebookIndex>) -> Self {
|
||||
Self { notebook_indexes }
|
||||
}
|
||||
|
||||
@@ -305,7 +305,7 @@ impl<'a> EmitterContext<'a> {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use rustc_hash::FxHashMap;
|
||||
use foldhash::HashMap;
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, DiagnosticKind, Edit, Fix};
|
||||
use ruff_notebook::NotebookIndex;
|
||||
@@ -399,7 +399,7 @@ def fibonacci(n):
|
||||
]
|
||||
}
|
||||
|
||||
pub(super) fn create_notebook_messages() -> (Vec<Message>, FxHashMap<String, NotebookIndex>) {
|
||||
pub(super) fn create_notebook_messages() -> (Vec<Message>, HashMap<String, NotebookIndex>) {
|
||||
let notebook = r"# cell 1
|
||||
import os
|
||||
# cell 2
|
||||
@@ -453,7 +453,7 @@ def foo():
|
||||
|
||||
let notebook_source = SourceFileBuilder::new("notebook.ipynb", notebook).finish();
|
||||
|
||||
let mut notebook_indexes = FxHashMap::default();
|
||||
let mut notebook_indexes = HashMap::default();
|
||||
notebook_indexes.insert(
|
||||
"notebook.ipynb".to_string(),
|
||||
NotebookIndex::new(
|
||||
@@ -510,7 +510,7 @@ def foo():
|
||||
emitter: &mut dyn Emitter,
|
||||
messages: &[Message],
|
||||
) -> String {
|
||||
let notebook_indexes = FxHashMap::default();
|
||||
let notebook_indexes = HashMap::default();
|
||||
let context = EmitterContext::new(¬ebook_indexes);
|
||||
let mut output: Vec<u8> = Vec::new();
|
||||
emitter.emit(&mut output, messages, &context).unwrap();
|
||||
@@ -521,7 +521,7 @@ def foo():
|
||||
pub(super) fn capture_emitter_notebook_output(
|
||||
emitter: &mut dyn Emitter,
|
||||
messages: &[Message],
|
||||
notebook_indexes: &FxHashMap<String, NotebookIndex>,
|
||||
notebook_indexes: &HashMap<String, NotebookIndex>,
|
||||
) -> String {
|
||||
let context = EmitterContext::new(notebook_indexes);
|
||||
let mut output: Vec<u8> = Vec::new();
|
||||
|
||||
@@ -6,15 +6,12 @@ mod fastapi_non_annotated_dependency;
|
||||
mod fastapi_redundant_response_model;
|
||||
mod fastapi_unused_path_parameter;
|
||||
|
||||
use ruff_python_ast as ast;
|
||||
use ruff_python_ast::{Decorator, ExprCall, StmtFunctionDef};
|
||||
use ruff_python_semantic::analyze::typing::resolve_assignment;
|
||||
use ruff_python_semantic::SemanticModel;
|
||||
|
||||
/// Returns `true` if the function is a FastAPI route.
|
||||
pub(crate) fn is_fastapi_route(
|
||||
function_def: &ast::StmtFunctionDef,
|
||||
semantic: &SemanticModel,
|
||||
) -> bool {
|
||||
pub(crate) fn is_fastapi_route(function_def: &StmtFunctionDef, semantic: &SemanticModel) -> bool {
|
||||
return function_def
|
||||
.decorator_list
|
||||
.iter()
|
||||
@@ -23,29 +20,27 @@ pub(crate) fn is_fastapi_route(
|
||||
|
||||
/// Returns `true` if the decorator is indicative of a FastAPI route.
|
||||
pub(crate) fn is_fastapi_route_decorator<'a>(
|
||||
decorator: &'a ast::Decorator,
|
||||
decorator: &'a Decorator,
|
||||
semantic: &'a SemanticModel,
|
||||
) -> Option<&'a ast::ExprCall> {
|
||||
) -> Option<&'a ExprCall> {
|
||||
let call = decorator.expression.as_call_expr()?;
|
||||
is_fastapi_route_call(call, semantic).then_some(call)
|
||||
}
|
||||
|
||||
pub(crate) fn is_fastapi_route_call(call_expr: &ast::ExprCall, semantic: &SemanticModel) -> bool {
|
||||
let ast::Expr::Attribute(ast::ExprAttribute { attr, value, .. }) = &*call_expr.func else {
|
||||
return false;
|
||||
};
|
||||
let decorator_method = call.func.as_attribute_expr()?;
|
||||
let method_name = &decorator_method.attr;
|
||||
|
||||
if !matches!(
|
||||
attr.as_str(),
|
||||
method_name.as_str(),
|
||||
"get" | "post" | "put" | "delete" | "patch" | "options" | "head" | "trace"
|
||||
) {
|
||||
return false;
|
||||
return None;
|
||||
}
|
||||
|
||||
resolve_assignment(value, semantic).is_some_and(|qualified_name| {
|
||||
matches!(
|
||||
qualified_name.segments(),
|
||||
["fastapi", "FastAPI" | "APIRouter"]
|
||||
)
|
||||
})
|
||||
let qualified_name = resolve_assignment(&decorator_method.value, semantic)?;
|
||||
if matches!(
|
||||
qualified_name.segments(),
|
||||
["fastapi", "FastAPI" | "APIRouter"]
|
||||
) {
|
||||
Some(call)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use foldhash::HashSet;
|
||||
use itertools::Itertools;
|
||||
use rustc_hash::FxHashSet;
|
||||
|
||||
use ruff_diagnostics::Edit;
|
||||
use ruff_python_ast::helpers::{
|
||||
@@ -109,7 +109,7 @@ pub(crate) fn auto_return_type(function: &ast::StmtFunctionDef) -> Option<AutoPy
|
||||
pub(crate) enum AutoPythonType {
|
||||
Never,
|
||||
Atom(PythonType),
|
||||
Union(FxHashSet<PythonType>),
|
||||
Union(HashSet<PythonType>),
|
||||
}
|
||||
|
||||
impl AutoPythonType {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use foldhash::{HashMap, HashMapExt, HashSet};
|
||||
use itertools::Itertools;
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
|
||||
use ruff_diagnostics::{AlwaysFixableViolation, Violation};
|
||||
use ruff_diagnostics::{Diagnostic, Edit, Fix};
|
||||
@@ -118,9 +118,9 @@ fn duplicate_handler_exceptions<'a>(
|
||||
checker: &mut Checker,
|
||||
expr: &'a Expr,
|
||||
elts: &'a [Expr],
|
||||
) -> FxHashMap<UnqualifiedName<'a>, &'a Expr> {
|
||||
let mut seen: FxHashMap<UnqualifiedName, &Expr> = FxHashMap::default();
|
||||
let mut duplicates: FxHashSet<UnqualifiedName> = FxHashSet::default();
|
||||
) -> HashMap<UnqualifiedName<'a>, &'a Expr> {
|
||||
let mut seen: HashMap<UnqualifiedName, &Expr> = HashMap::default();
|
||||
let mut duplicates: HashSet<UnqualifiedName> = HashSet::default();
|
||||
let mut unique_elts: Vec<&Expr> = Vec::default();
|
||||
for type_ in elts {
|
||||
if let Some(name) = UnqualifiedName::from_expr(type_) {
|
||||
@@ -171,8 +171,8 @@ fn duplicate_handler_exceptions<'a>(
|
||||
|
||||
/// B025
|
||||
pub(crate) fn duplicate_exceptions(checker: &mut Checker, handlers: &[ExceptHandler]) {
|
||||
let mut seen: FxHashSet<UnqualifiedName> = FxHashSet::default();
|
||||
let mut duplicates: FxHashMap<UnqualifiedName, Vec<&Expr>> = FxHashMap::default();
|
||||
let mut seen: HashSet<UnqualifiedName> = HashSet::default();
|
||||
let mut duplicates: HashMap<UnqualifiedName, Vec<&Expr>> = HashMap::default();
|
||||
for handler in handlers {
|
||||
let ExceptHandler::ExceptHandler(ast::ExceptHandlerExceptHandler {
|
||||
type_: Some(type_),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use anyhow::{Context, Result};
|
||||
use rustc_hash::FxHashSet;
|
||||
use foldhash::HashSet;
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
@@ -48,7 +48,7 @@ impl Violation for DuplicateValue {
|
||||
|
||||
/// B033
|
||||
pub(crate) fn duplicate_value(checker: &mut Checker, set: &ast::ExprSet) {
|
||||
let mut seen_values: FxHashSet<ComparableExpr> = FxHashSet::default();
|
||||
let mut seen_values: HashSet<ComparableExpr> = HashSet::default();
|
||||
for (index, value) in set.iter().enumerate() {
|
||||
if value.is_literal_expr() {
|
||||
let comparable_value = ComparableExpr::from(value);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use foldhash::HashMap;
|
||||
use ruff_python_ast::{self as ast, Expr, ParameterWithDefault};
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
@@ -76,7 +76,7 @@ pub(crate) fn loop_variable_overrides_iterator(checker: &mut Checker, target: &E
|
||||
|
||||
#[derive(Default)]
|
||||
struct NameFinder<'a> {
|
||||
names: FxHashMap<&'a str, &'a Expr>,
|
||||
names: HashMap<&'a str, &'a Expr>,
|
||||
}
|
||||
|
||||
impl<'a> Visitor<'a> for NameFinder<'a> {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use rustc_hash::FxHashMap;
|
||||
use foldhash::HashMap;
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
@@ -70,7 +70,7 @@ pub(crate) fn static_key_dict_comprehension(checker: &mut Checker, dict_comp: &a
|
||||
|
||||
/// Returns `true` if the given expression is a constant in the context of the dictionary
|
||||
/// comprehension.
|
||||
fn is_constant(key: &Expr, names: &FxHashMap<&str, &ast::ExprName>) -> bool {
|
||||
fn is_constant(key: &Expr, names: &HashMap<&str, &ast::ExprName>) -> bool {
|
||||
match key {
|
||||
Expr::Tuple(tuple) => tuple.iter().all(|elem| is_constant(elem, names)),
|
||||
Expr::Name(ast::ExprName { id, .. }) => !names.contains_key(id.as_str()),
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::{Expr, Stmt};
|
||||
use ruff_python_semantic::ScopeKind;
|
||||
use ruff_python_ast::Expr;
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
@@ -34,34 +33,24 @@ use super::super::helpers::at_last_top_level_expression_in_cell;
|
||||
/// ## References
|
||||
/// - [Python documentation: `assert` statement](https://docs.python.org/3/reference/simple_stmts.html#the-assert-statement)
|
||||
#[violation]
|
||||
pub struct UselessComparison {
|
||||
at: ComparisonLocationAt,
|
||||
}
|
||||
pub struct UselessComparison;
|
||||
|
||||
impl Violation for UselessComparison {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
match self.at {
|
||||
ComparisonLocationAt::MiddleBody => format!(
|
||||
"Pointless comparison. Did you mean to assign a value? \
|
||||
Otherwise, prepend `assert` or remove it."
|
||||
),
|
||||
ComparisonLocationAt::EndOfFunction => format!(
|
||||
"Pointless comparison at end of function scope. Did you mean \
|
||||
to return the expression result?"
|
||||
),
|
||||
}
|
||||
format!(
|
||||
"Pointless comparison. Did you mean to assign a value? \
|
||||
Otherwise, prepend `assert` or remove it."
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// B015
|
||||
pub(crate) fn useless_comparison(checker: &mut Checker, expr: &Expr) {
|
||||
if expr.is_compare_expr() {
|
||||
let semantic = checker.semantic();
|
||||
|
||||
if checker.source_type.is_ipynb()
|
||||
&& at_last_top_level_expression_in_cell(
|
||||
semantic,
|
||||
checker.semantic(),
|
||||
checker.locator(),
|
||||
checker.cell_offsets(),
|
||||
)
|
||||
@@ -69,34 +58,8 @@ pub(crate) fn useless_comparison(checker: &mut Checker, expr: &Expr) {
|
||||
return;
|
||||
}
|
||||
|
||||
if let ScopeKind::Function(func_def) = semantic.current_scope().kind {
|
||||
if func_def
|
||||
.body
|
||||
.last()
|
||||
.and_then(Stmt::as_expr_stmt)
|
||||
.is_some_and(|last_stmt| &*last_stmt.value == expr)
|
||||
{
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
UselessComparison {
|
||||
at: ComparisonLocationAt::EndOfFunction,
|
||||
},
|
||||
expr.range(),
|
||||
));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
UselessComparison {
|
||||
at: ComparisonLocationAt::MiddleBody,
|
||||
},
|
||||
expr.range(),
|
||||
));
|
||||
checker
|
||||
.diagnostics
|
||||
.push(Diagnostic::new(UselessComparison, expr.range()));
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||
enum ComparisonLocationAt {
|
||||
MiddleBody,
|
||||
EndOfFunction,
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_bugbear/mod.rs
|
||||
assertion_line: 74
|
||||
---
|
||||
B015.py:3:1: B015 Pointless comparison. Did you mean to assign a value? Otherwise, prepend `assert` or remove it.
|
||||
|
|
||||
@@ -20,7 +19,7 @@ B015.py:7:1: B015 Pointless comparison. Did you mean to assign a value? Otherwis
|
||||
| ^^^^^^^^^^^ B015
|
||||
|
|
||||
|
||||
B015.py:17:5: B015 Pointless comparison at end of function scope. Did you mean to return the expression result?
|
||||
B015.py:17:5: B015 Pointless comparison. Did you mean to assign a value? Otherwise, prepend `assert` or remove it.
|
||||
|
|
||||
15 | assert 1 in (1, 2)
|
||||
16 |
|
||||
@@ -28,17 +27,11 @@ B015.py:17:5: B015 Pointless comparison at end of function scope. Did you mean t
|
||||
| ^^^^^^^^^^^ B015
|
||||
|
|
||||
|
||||
B015.py:21:5: B015 Pointless comparison. Did you mean to assign a value? Otherwise, prepend `assert` or remove it.
|
||||
B015.py:24:5: B015 Pointless comparison. Did you mean to assign a value? Otherwise, prepend `assert` or remove it.
|
||||
|
|
||||
20 | def test2():
|
||||
21 | 1 in (1, 2)
|
||||
| ^^^^^^^^^^^ B015
|
||||
22 | return
|
||||
|
|
||||
|
||||
B015.py:29:5: B015 Pointless comparison. Did you mean to assign a value? Otherwise, prepend `assert` or remove it.
|
||||
|
|
||||
28 | class TestClass:
|
||||
29 | 1 == 1
|
||||
23 | class TestClass:
|
||||
24 | 1 == 1
|
||||
| ^^^^^^ B015
|
||||
|
|
||||
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ mod tests {
|
||||
use std::path::Path;
|
||||
|
||||
use anyhow::Result;
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
use foldhash::{HashMap, HashMapExt, HashSet};
|
||||
|
||||
use crate::assert_messages;
|
||||
use crate::registry::Rule;
|
||||
@@ -28,7 +28,7 @@ mod tests {
|
||||
#[test]
|
||||
fn custom() -> Result<()> {
|
||||
let mut aliases = default_aliases();
|
||||
aliases.extend(FxHashMap::from_iter([
|
||||
aliases.extend(HashMap::from_iter([
|
||||
("dask.array".to_string(), "da".to_string()),
|
||||
("dask.dataframe".to_string(), "dd".to_string()),
|
||||
]));
|
||||
@@ -37,8 +37,8 @@ mod tests {
|
||||
&LinterSettings {
|
||||
flake8_import_conventions: super::settings::Settings {
|
||||
aliases,
|
||||
banned_aliases: FxHashMap::default(),
|
||||
banned_from: FxHashSet::default(),
|
||||
banned_aliases: HashMap::default(),
|
||||
banned_from: HashSet::default(),
|
||||
},
|
||||
..LinterSettings::for_rule(Rule::UnconventionalImportAlias)
|
||||
},
|
||||
@@ -54,7 +54,7 @@ mod tests {
|
||||
&LinterSettings {
|
||||
flake8_import_conventions: super::settings::Settings {
|
||||
aliases: default_aliases(),
|
||||
banned_aliases: FxHashMap::from_iter([
|
||||
banned_aliases: HashMap::from_iter([
|
||||
(
|
||||
"typing".to_string(),
|
||||
BannedAliases::from_iter(["t".to_string(), "ty".to_string()]),
|
||||
@@ -72,7 +72,7 @@ mod tests {
|
||||
BannedAliases::from_iter(["F".to_string()]),
|
||||
),
|
||||
]),
|
||||
banned_from: FxHashSet::default(),
|
||||
banned_from: HashSet::default(),
|
||||
},
|
||||
..LinterSettings::for_rule(Rule::BannedImportAlias)
|
||||
},
|
||||
@@ -88,8 +88,8 @@ mod tests {
|
||||
&LinterSettings {
|
||||
flake8_import_conventions: super::settings::Settings {
|
||||
aliases: default_aliases(),
|
||||
banned_aliases: FxHashMap::default(),
|
||||
banned_from: FxHashSet::from_iter([
|
||||
banned_aliases: HashMap::default(),
|
||||
banned_from: HashSet::from_iter([
|
||||
"logging.config".to_string(),
|
||||
"typing".to_string(),
|
||||
"pandas".to_string(),
|
||||
@@ -108,14 +108,14 @@ mod tests {
|
||||
Path::new("flake8_import_conventions/remove_default.py"),
|
||||
&LinterSettings {
|
||||
flake8_import_conventions: super::settings::Settings {
|
||||
aliases: FxHashMap::from_iter([
|
||||
aliases: HashMap::from_iter([
|
||||
("altair".to_string(), "alt".to_string()),
|
||||
("matplotlib.pyplot".to_string(), "plt".to_string()),
|
||||
("pandas".to_string(), "pd".to_string()),
|
||||
("seaborn".to_string(), "sns".to_string()),
|
||||
]),
|
||||
banned_aliases: FxHashMap::default(),
|
||||
banned_from: FxHashSet::default(),
|
||||
banned_aliases: HashMap::default(),
|
||||
banned_from: HashSet::default(),
|
||||
},
|
||||
..LinterSettings::for_rule(Rule::UnconventionalImportAlias)
|
||||
},
|
||||
@@ -127,7 +127,7 @@ mod tests {
|
||||
#[test]
|
||||
fn override_defaults() -> Result<()> {
|
||||
let mut aliases = default_aliases();
|
||||
aliases.extend(FxHashMap::from_iter([(
|
||||
aliases.extend(HashMap::from_iter([(
|
||||
"numpy".to_string(),
|
||||
"nmp".to_string(),
|
||||
)]));
|
||||
@@ -137,8 +137,8 @@ mod tests {
|
||||
&LinterSettings {
|
||||
flake8_import_conventions: super::settings::Settings {
|
||||
aliases,
|
||||
banned_aliases: FxHashMap::default(),
|
||||
banned_from: FxHashSet::default(),
|
||||
banned_aliases: HashMap::default(),
|
||||
banned_from: HashSet::default(),
|
||||
},
|
||||
..LinterSettings::for_rule(Rule::UnconventionalImportAlias)
|
||||
},
|
||||
@@ -150,7 +150,7 @@ mod tests {
|
||||
#[test]
|
||||
fn from_imports() -> Result<()> {
|
||||
let mut aliases = default_aliases();
|
||||
aliases.extend(FxHashMap::from_iter([
|
||||
aliases.extend(HashMap::from_iter([
|
||||
("xml.dom.minidom".to_string(), "md".to_string()),
|
||||
(
|
||||
"xml.dom.minidom.parseString".to_string(),
|
||||
@@ -163,8 +163,8 @@ mod tests {
|
||||
&LinterSettings {
|
||||
flake8_import_conventions: super::settings::Settings {
|
||||
aliases,
|
||||
banned_aliases: FxHashMap::default(),
|
||||
banned_from: FxHashSet::default(),
|
||||
banned_aliases: HashMap::default(),
|
||||
banned_from: HashSet::default(),
|
||||
},
|
||||
..LinterSettings::for_rule(Rule::UnconventionalImportAlias)
|
||||
},
|
||||
@@ -186,7 +186,7 @@ mod tests {
|
||||
#[test]
|
||||
fn same_name() -> Result<()> {
|
||||
let mut aliases = default_aliases();
|
||||
aliases.extend(FxHashMap::from_iter([(
|
||||
aliases.extend(HashMap::from_iter([(
|
||||
"django.conf.settings".to_string(),
|
||||
"settings".to_string(),
|
||||
)]));
|
||||
@@ -195,8 +195,8 @@ mod tests {
|
||||
&LinterSettings {
|
||||
flake8_import_conventions: super::settings::Settings {
|
||||
aliases,
|
||||
banned_aliases: FxHashMap::default(),
|
||||
banned_from: FxHashSet::default(),
|
||||
banned_aliases: HashMap::default(),
|
||||
banned_from: HashSet::default(),
|
||||
},
|
||||
..LinterSettings::for_rule(Rule::UnconventionalImportAlias)
|
||||
},
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use rustc_hash::FxHashMap;
|
||||
use foldhash::HashMap;
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
@@ -51,7 +51,7 @@ pub(crate) fn banned_import_alias(
|
||||
stmt: &Stmt,
|
||||
name: &str,
|
||||
asname: &str,
|
||||
banned_conventions: &FxHashMap<String, BannedAliases>,
|
||||
banned_conventions: &HashMap<String, BannedAliases>,
|
||||
) -> Option<Diagnostic> {
|
||||
if let Some(banned_aliases) = banned_conventions.get(name) {
|
||||
if banned_aliases
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use foldhash::HashSet;
|
||||
use ruff_python_ast::Stmt;
|
||||
use rustc_hash::FxHashSet;
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
@@ -48,7 +48,7 @@ impl Violation for BannedImportFrom {
|
||||
pub(crate) fn banned_import_from(
|
||||
stmt: &Stmt,
|
||||
name: &str,
|
||||
banned_conventions: &FxHashSet<String>,
|
||||
banned_conventions: &HashSet<String>,
|
||||
) -> Option<Diagnostic> {
|
||||
if banned_conventions.contains(name) {
|
||||
return Some(Diagnostic::new(
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use rustc_hash::FxHashMap;
|
||||
use foldhash::HashMap;
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Fix, FixAvailability, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
@@ -59,7 +59,7 @@ impl Violation for UnconventionalImportAlias {
|
||||
pub(crate) fn unconventional_import_alias(
|
||||
checker: &Checker,
|
||||
binding: &Binding,
|
||||
conventions: &FxHashMap<String, String>,
|
||||
conventions: &HashMap<String, String>,
|
||||
) -> Option<Diagnostic> {
|
||||
let import = binding.as_any_import()?;
|
||||
let qualified_name = import.qualified_name().to_string();
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
use std::fmt::{Display, Formatter};
|
||||
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
use foldhash::{HashMap, HashSet};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use ruff_macros::CacheKey;
|
||||
@@ -60,24 +60,24 @@ impl FromIterator<String> for BannedAliases {
|
||||
|
||||
#[derive(Debug, Clone, CacheKey)]
|
||||
pub struct Settings {
|
||||
pub aliases: FxHashMap<String, String>,
|
||||
pub banned_aliases: FxHashMap<String, BannedAliases>,
|
||||
pub banned_from: FxHashSet<String>,
|
||||
pub aliases: HashMap<String, String>,
|
||||
pub banned_aliases: HashMap<String, BannedAliases>,
|
||||
pub banned_from: HashSet<String>,
|
||||
}
|
||||
|
||||
pub fn default_aliases() -> FxHashMap<String, String> {
|
||||
pub fn default_aliases() -> HashMap<String, String> {
|
||||
CONVENTIONAL_ALIASES
|
||||
.iter()
|
||||
.map(|(k, v)| ((*k).to_string(), (*v).to_string()))
|
||||
.collect::<FxHashMap<_, _>>()
|
||||
.collect::<HashMap<_, _>>()
|
||||
}
|
||||
|
||||
impl Default for Settings {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
aliases: default_aliases(),
|
||||
banned_aliases: FxHashMap::default(),
|
||||
banned_from: FxHashSet::default(),
|
||||
banned_aliases: HashMap::default(),
|
||||
banned_from: HashSet::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use rustc_hash::FxHashSet;
|
||||
use foldhash::HashSet;
|
||||
|
||||
use ruff_diagnostics::Diagnostic;
|
||||
use ruff_diagnostics::{AlwaysFixableViolation, Fix};
|
||||
@@ -51,7 +51,7 @@ impl AlwaysFixableViolation for DuplicateClassFieldDefinition {
|
||||
|
||||
/// PIE794
|
||||
pub(crate) fn duplicate_class_field_definition(checker: &mut Checker, body: &[Stmt]) {
|
||||
let mut seen_targets: FxHashSet<&str> = FxHashSet::default();
|
||||
let mut seen_targets: HashSet<&str> = HashSet::default();
|
||||
for stmt in body {
|
||||
// Extract the property name from the assignment statement.
|
||||
let target = match stmt {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use rustc_hash::FxHashSet;
|
||||
use foldhash::HashSet;
|
||||
|
||||
use ruff_diagnostics::Diagnostic;
|
||||
use ruff_diagnostics::Violation;
|
||||
@@ -68,7 +68,7 @@ pub(crate) fn non_unique_enums(checker: &mut Checker, parent: &Stmt, body: &[Stm
|
||||
return;
|
||||
}
|
||||
|
||||
let mut seen_targets: FxHashSet<ComparableExpr> = FxHashSet::default();
|
||||
let mut seen_targets: HashSet<ComparableExpr> = HashSet::default();
|
||||
for stmt in body {
|
||||
let Stmt::Assign(ast::StmtAssign { value, .. }) = stmt else {
|
||||
continue;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use foldhash::{HashMapExt, HashSet, HashSetExt};
|
||||
use itertools::Itertools;
|
||||
use rustc_hash::{FxBuildHasher, FxHashSet};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
@@ -147,11 +147,9 @@ pub(crate) fn unnecessary_dict_kwargs(checker: &mut Checker, call: &ast::ExprCal
|
||||
|
||||
/// Determine the set of keywords that appear in multiple positions (either directly, as in
|
||||
/// `func(x=1)`, or indirectly, as in `func(**{"x": 1})`).
|
||||
fn duplicates(call: &ast::ExprCall) -> FxHashSet<&str> {
|
||||
let mut seen =
|
||||
FxHashSet::with_capacity_and_hasher(call.arguments.keywords.len(), FxBuildHasher);
|
||||
let mut duplicates =
|
||||
FxHashSet::with_capacity_and_hasher(call.arguments.keywords.len(), FxBuildHasher);
|
||||
fn duplicates(call: &ast::ExprCall) -> HashSet<&str> {
|
||||
let mut seen = HashSet::with_capacity(call.arguments.keywords.len());
|
||||
let mut duplicates = HashSet::with_capacity(call.arguments.keywords.len());
|
||||
for keyword in &*call.arguments.keywords {
|
||||
if let Some(name) = &keyword.arg {
|
||||
if !seen.insert(name.as_str()) {
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
use std::collections::HashSet;
|
||||
|
||||
use rustc_hash::FxHashSet;
|
||||
use foldhash::HashSet;
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, FixAvailability, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
@@ -45,7 +43,7 @@ impl Violation for DuplicateLiteralMember {
|
||||
|
||||
/// PYI062
|
||||
pub(crate) fn duplicate_literal_member<'a>(checker: &mut Checker, expr: &'a Expr) {
|
||||
let mut seen_nodes: HashSet<ComparableExpr<'_>, _> = FxHashSet::default();
|
||||
let mut seen_nodes = HashSet::<ComparableExpr<'_>>::default();
|
||||
let mut diagnostics: Vec<Diagnostic> = Vec::new();
|
||||
|
||||
// Adds a member to `literal_exprs` if it is a `Literal` annotation
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
use std::collections::HashSet;
|
||||
|
||||
use rustc_hash::FxHashSet;
|
||||
use foldhash::HashSet;
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
@@ -52,7 +50,7 @@ impl Violation for DuplicateUnionMember {
|
||||
|
||||
/// PYI016
|
||||
pub(crate) fn duplicate_union_member<'a>(checker: &mut Checker, expr: &'a Expr) {
|
||||
let mut seen_nodes: HashSet<ComparableExpr<'_>, _> = FxHashSet::default();
|
||||
let mut seen_nodes = HashSet::<ComparableExpr<'_>>::default();
|
||||
let mut diagnostics: Vec<Diagnostic> = Vec::new();
|
||||
|
||||
// Adds a member to `literal_exprs` if it is a `Literal` annotation
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::fmt;
|
||||
|
||||
use rustc_hash::FxHashSet;
|
||||
use foldhash::HashSet;
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
@@ -60,7 +60,7 @@ impl Violation for RedundantLiteralUnion {
|
||||
/// PYI051
|
||||
pub(crate) fn redundant_literal_union<'a>(checker: &mut Checker, union: &'a Expr) {
|
||||
let mut typing_literal_exprs = Vec::new();
|
||||
let mut builtin_types_in_union = FxHashSet::default();
|
||||
let mut builtin_types_in_union = HashSet::default();
|
||||
|
||||
// Adds a member to `literal_exprs` for each value in a `Literal`, and any builtin types
|
||||
// to `builtin_types_in_union`.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use rustc_hash::{FxBuildHasher, FxHashMap};
|
||||
use foldhash::{HashMap, HashMapExt, HashSetExt};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
@@ -651,8 +651,7 @@ fn check_duplicates(checker: &mut Checker, values: &Expr) {
|
||||
return;
|
||||
};
|
||||
|
||||
let mut seen: FxHashMap<ComparableExpr, usize> =
|
||||
FxHashMap::with_capacity_and_hasher(elts.len(), FxBuildHasher);
|
||||
let mut seen: HashMap<ComparableExpr, usize> = HashMap::with_capacity(elts.len());
|
||||
let mut prev = None;
|
||||
for (index, element) in elts.iter().enumerate() {
|
||||
let expr = ComparableExpr::from(element);
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
use anyhow::{anyhow, bail, Result};
|
||||
use foldhash::{HashMap, HashMapExt, HashSetExt};
|
||||
use ruff_python_ast::name::Name;
|
||||
use ruff_python_ast::{
|
||||
self as ast, Arguments, CmpOp, Expr, ExprContext, Identifier, Keyword, Stmt, UnaryOp,
|
||||
};
|
||||
use ruff_text_size::TextRange;
|
||||
use rustc_hash::{FxBuildHasher, FxHashMap};
|
||||
|
||||
/// An enum to represent the different types of assertions present in the
|
||||
/// `unittest` module. Note: any variants that can't be replaced with plain
|
||||
@@ -230,7 +230,7 @@ impl UnittestAssert {
|
||||
&'a self,
|
||||
args: &'a [Expr],
|
||||
keywords: &'a [Keyword],
|
||||
) -> Result<FxHashMap<&'a str, &'a Expr>> {
|
||||
) -> Result<HashMap<&'a str, &'a Expr>> {
|
||||
// If we have variable-length arguments, abort.
|
||||
if args.iter().any(Expr::is_starred_expr) || keywords.iter().any(|kw| kw.arg.is_none()) {
|
||||
bail!("Variable-length arguments are not supported");
|
||||
@@ -248,8 +248,8 @@ impl UnittestAssert {
|
||||
}
|
||||
|
||||
// Generate a map from argument name to value.
|
||||
let mut args_map: FxHashMap<&str, &Expr> =
|
||||
FxHashMap::with_capacity_and_hasher(args.len() + keywords.len(), FxBuildHasher);
|
||||
let mut args_map: HashMap<&str, &Expr> =
|
||||
HashMap::with_capacity(args.len() + keywords.len());
|
||||
|
||||
// Process positional arguments.
|
||||
for (arg_name, value) in arg_spec.iter().zip(args.iter()) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use foldhash::HashSet;
|
||||
use ruff_python_ast::{self as ast, ElifElseClause, Expr, Identifier, Stmt};
|
||||
use rustc_hash::FxHashSet;
|
||||
|
||||
use ruff_python_ast::visitor;
|
||||
use ruff_python_ast::visitor::Visitor;
|
||||
@@ -12,7 +12,7 @@ pub(super) struct Stack<'data> {
|
||||
/// The `elif` or `else` statements in the current function.
|
||||
pub(super) elifs_elses: Vec<(&'data [Stmt], &'data ElifElseClause)>,
|
||||
/// The non-local variables in the current function.
|
||||
pub(super) non_locals: FxHashSet<&'data str>,
|
||||
pub(super) non_locals: HashSet<&'data str>,
|
||||
/// The annotated variables in the current function.
|
||||
///
|
||||
/// For example, consider:
|
||||
@@ -27,7 +27,7 @@ pub(super) struct Stack<'data> {
|
||||
/// In this case, the annotation on `x` is used to cast the return value
|
||||
/// of `foo()` to an `int`. Removing the `x = foo()` statement would
|
||||
/// change the return type of the function.
|
||||
pub(super) annotations: FxHashSet<&'data str>,
|
||||
pub(super) annotations: HashSet<&'data str>,
|
||||
/// Whether the current function is a generator.
|
||||
pub(super) is_generator: bool,
|
||||
/// The `assignment`-to-`return` statement pairs in the current function.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use rustc_hash::FxHashSet;
|
||||
use foldhash::HashSet;
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
@@ -94,7 +94,7 @@ pub(crate) fn if_else_block_instead_of_dict_lookup(checker: &mut Checker, stmt_i
|
||||
}
|
||||
|
||||
// The `expr` was checked to be a literal above, so this is safe.
|
||||
let mut literals: FxHashSet<ComparableLiteral> = FxHashSet::default();
|
||||
let mut literals: HashSet<ComparableLiteral> = HashSet::default();
|
||||
literals.insert(literal_expr.into());
|
||||
|
||||
for clause in elif_else_clauses {
|
||||
|
||||
@@ -8,7 +8,7 @@ mod tests {
|
||||
use std::path::Path;
|
||||
|
||||
use anyhow::Result;
|
||||
use rustc_hash::FxHashMap;
|
||||
use foldhash::HashMap;
|
||||
|
||||
use crate::assert_messages;
|
||||
use crate::registry::Rule;
|
||||
@@ -23,7 +23,7 @@ mod tests {
|
||||
Path::new("flake8_tidy_imports/TID251.py"),
|
||||
&LinterSettings {
|
||||
flake8_tidy_imports: flake8_tidy_imports::settings::Settings {
|
||||
banned_api: FxHashMap::from_iter([
|
||||
banned_api: HashMap::from_iter([
|
||||
(
|
||||
"cgi".to_string(),
|
||||
ApiBan {
|
||||
@@ -52,7 +52,7 @@ mod tests {
|
||||
Path::new("flake8_tidy_imports/TID/my_package/sublib/api/application.py"),
|
||||
&LinterSettings {
|
||||
flake8_tidy_imports: flake8_tidy_imports::settings::Settings {
|
||||
banned_api: FxHashMap::from_iter([
|
||||
banned_api: HashMap::from_iter([
|
||||
(
|
||||
"attrs".to_string(),
|
||||
ApiBan {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use rustc_hash::FxHashMap;
|
||||
use foldhash::HashMap;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::{Display, Formatter};
|
||||
|
||||
@@ -42,7 +42,7 @@ impl Display for Strictness {
|
||||
#[derive(Debug, Clone, CacheKey, Default)]
|
||||
pub struct Settings {
|
||||
pub ban_relative_imports: Strictness,
|
||||
pub banned_api: FxHashMap<String, ApiBan>,
|
||||
pub banned_api: HashMap<String, ApiBan>,
|
||||
pub banned_module_level_imports: Vec<String>,
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::borrow::Cow;
|
||||
|
||||
use anyhow::Result;
|
||||
use rustc_hash::FxHashMap;
|
||||
use foldhash::HashMap;
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Fix, FixAvailability, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
@@ -103,7 +103,7 @@ pub(crate) fn runtime_import_in_type_checking_block(
|
||||
diagnostics: &mut Vec<Diagnostic>,
|
||||
) {
|
||||
// Collect all runtime imports by statement.
|
||||
let mut actions: FxHashMap<(NodeId, Action), Vec<ImportBinding>> = FxHashMap::default();
|
||||
let mut actions: HashMap<(NodeId, Action), Vec<ImportBinding>> = HashMap::default();
|
||||
|
||||
for binding_id in scope.binding_ids() {
|
||||
let binding = checker.semantic().binding(binding_id);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::borrow::Cow;
|
||||
|
||||
use anyhow::Result;
|
||||
use rustc_hash::FxHashMap;
|
||||
use foldhash::HashMap;
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, DiagnosticKind, Fix, FixAvailability, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
@@ -248,10 +248,10 @@ pub(crate) fn typing_only_runtime_import(
|
||||
diagnostics: &mut Vec<Diagnostic>,
|
||||
) {
|
||||
// Collect all typing-only imports by statement and import type.
|
||||
let mut errors_by_statement: FxHashMap<(NodeId, ImportType), Vec<ImportBinding>> =
|
||||
FxHashMap::default();
|
||||
let mut ignores_by_statement: FxHashMap<(NodeId, ImportType), Vec<ImportBinding>> =
|
||||
FxHashMap::default();
|
||||
let mut errors_by_statement: HashMap<(NodeId, ImportType), Vec<ImportBinding>> =
|
||||
HashMap::default();
|
||||
let mut ignores_by_statement: HashMap<(NodeId, ImportType), Vec<ImportBinding>> =
|
||||
HashMap::default();
|
||||
|
||||
for binding_id in scope.binding_ids() {
|
||||
let binding = checker.semantic().binding(binding_id);
|
||||
|
||||
@@ -344,7 +344,6 @@ pub(crate) fn unused_arguments(
|
||||
) {
|
||||
function_type::FunctionType::Function => {
|
||||
if checker.enabled(Argumentable::Function.rule_code())
|
||||
&& !function_type::is_stub(function_def, checker.semantic())
|
||||
&& !visibility::is_overload(decorator_list, checker.semantic())
|
||||
{
|
||||
function(
|
||||
|
||||
@@ -32,3 +32,33 @@ ARG.py:13:12: ARG001 Unused function argument: `x`
|
||||
| ^ ARG001
|
||||
14 | print("Hello, world!")
|
||||
|
|
||||
|
||||
ARG.py:17:7: ARG001 Unused function argument: `self`
|
||||
|
|
||||
17 | def f(self, x):
|
||||
| ^^^^ ARG001
|
||||
18 | ...
|
||||
|
|
||||
|
||||
ARG.py:17:13: ARG001 Unused function argument: `x`
|
||||
|
|
||||
17 | def f(self, x):
|
||||
| ^ ARG001
|
||||
18 | ...
|
||||
|
|
||||
|
||||
ARG.py:21:7: ARG001 Unused function argument: `cls`
|
||||
|
|
||||
21 | def f(cls, x):
|
||||
| ^^^ ARG001
|
||||
22 | ...
|
||||
|
|
||||
|
||||
ARG.py:21:12: ARG001 Unused function argument: `x`
|
||||
|
|
||||
21 | def f(cls, x):
|
||||
| ^ ARG001
|
||||
22 | ...
|
||||
|
|
||||
|
||||
|
||||
|
||||
@@ -3,8 +3,8 @@ use std::fmt;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::{fs, iter};
|
||||
|
||||
use foldhash::{HashMap, HashMapExt, HashSet, HashSetExt};
|
||||
use log::debug;
|
||||
use rustc_hash::{FxBuildHasher, FxHashMap, FxHashSet};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use strum_macros::EnumIter;
|
||||
|
||||
@@ -283,7 +283,7 @@ impl KnownModules {
|
||||
third_party: Vec<glob::Pattern>,
|
||||
local_folder: Vec<glob::Pattern>,
|
||||
standard_library: Vec<glob::Pattern>,
|
||||
user_defined: FxHashMap<String, Vec<glob::Pattern>>,
|
||||
user_defined: HashMap<String, Vec<glob::Pattern>>,
|
||||
) -> Self {
|
||||
let known: Vec<(glob::Pattern, ImportSection)> = user_defined
|
||||
.into_iter()
|
||||
@@ -315,7 +315,7 @@ impl KnownModules {
|
||||
.collect();
|
||||
|
||||
// Warn in the case of duplicate modules.
|
||||
let mut seen = FxHashSet::with_capacity_and_hasher(known.len(), FxBuildHasher);
|
||||
let mut seen = HashSet::with_capacity(known.len());
|
||||
for (module, _) in &known {
|
||||
if !seen.insert(module) {
|
||||
warn_user_once!("One or more modules are part of multiple import sections, including: `{module}`");
|
||||
@@ -382,8 +382,8 @@ impl KnownModules {
|
||||
}
|
||||
|
||||
/// Return the list of user-defined modules, indexed by section.
|
||||
pub fn user_defined(&self) -> FxHashMap<&str, Vec<&glob::Pattern>> {
|
||||
let mut user_defined: FxHashMap<&str, Vec<&glob::Pattern>> = FxHashMap::default();
|
||||
pub fn user_defined(&self) -> HashMap<&str, Vec<&glob::Pattern>> {
|
||||
let mut user_defined: HashMap<&str, Vec<&glob::Pattern>> = HashMap::default();
|
||||
for (module, section) in &self.known {
|
||||
if let ImportSection::UserDefined(section_name) = section {
|
||||
user_defined
|
||||
|
||||
@@ -282,9 +282,9 @@ mod tests {
|
||||
use std::path::Path;
|
||||
|
||||
use anyhow::Result;
|
||||
use foldhash::{HashMap, HashMapExt, HashSet, HashSetExt};
|
||||
use ruff_python_semantic::{MemberNameImport, ModuleNameImport, NameImport};
|
||||
use ruff_text_size::Ranged;
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
use test_case::test_case;
|
||||
|
||||
use crate::assert_messages;
|
||||
@@ -378,7 +378,7 @@ mod tests {
|
||||
vec![pattern("foo"), pattern("__future__")],
|
||||
vec![],
|
||||
vec![],
|
||||
FxHashMap::default(),
|
||||
HashMap::default(),
|
||||
),
|
||||
..super::settings::Settings::default()
|
||||
},
|
||||
@@ -402,7 +402,7 @@ mod tests {
|
||||
vec![pattern("foo"), pattern("__future__")],
|
||||
vec![],
|
||||
vec![],
|
||||
FxHashMap::default(),
|
||||
HashMap::default(),
|
||||
),
|
||||
..super::settings::Settings::default()
|
||||
},
|
||||
@@ -426,7 +426,7 @@ mod tests {
|
||||
vec![pattern("foo.bar")],
|
||||
vec![],
|
||||
vec![],
|
||||
FxHashMap::default(),
|
||||
HashMap::default(),
|
||||
),
|
||||
..super::settings::Settings::default()
|
||||
},
|
||||
@@ -465,7 +465,7 @@ mod tests {
|
||||
vec![],
|
||||
vec![pattern("ruff")],
|
||||
vec![],
|
||||
FxHashMap::default(),
|
||||
HashMap::default(),
|
||||
),
|
||||
..super::settings::Settings::default()
|
||||
},
|
||||
@@ -489,7 +489,7 @@ mod tests {
|
||||
vec![],
|
||||
vec![pattern("ruff")],
|
||||
vec![],
|
||||
FxHashMap::default(),
|
||||
HashMap::default(),
|
||||
),
|
||||
relative_imports_order: RelativeImportsOrder::ClosestToFurthest,
|
||||
..super::settings::Settings::default()
|
||||
@@ -527,7 +527,7 @@ mod tests {
|
||||
Path::new("isort").join(path).as_path(),
|
||||
&LinterSettings {
|
||||
isort: super::settings::Settings {
|
||||
force_to_top: FxHashSet::from_iter([
|
||||
force_to_top: HashSet::from_iter([
|
||||
"z".to_string(),
|
||||
"lib1".to_string(),
|
||||
"lib3".to_string(),
|
||||
@@ -607,7 +607,7 @@ mod tests {
|
||||
&LinterSettings {
|
||||
isort: super::settings::Settings {
|
||||
force_single_line: true,
|
||||
single_line_exclusions: FxHashSet::from_iter([
|
||||
single_line_exclusions: HashSet::from_iter([
|
||||
"os".to_string(),
|
||||
"logging.handlers".to_string(),
|
||||
]),
|
||||
@@ -669,7 +669,7 @@ mod tests {
|
||||
&LinterSettings {
|
||||
isort: super::settings::Settings {
|
||||
order_by_type: true,
|
||||
classes: FxHashSet::from_iter([
|
||||
classes: HashSet::from_iter([
|
||||
"SVC".to_string(),
|
||||
"SELU".to_string(),
|
||||
"N_CLASS".to_string(),
|
||||
@@ -697,7 +697,7 @@ mod tests {
|
||||
&LinterSettings {
|
||||
isort: super::settings::Settings {
|
||||
order_by_type: true,
|
||||
constants: FxHashSet::from_iter([
|
||||
constants: HashSet::from_iter([
|
||||
"Const".to_string(),
|
||||
"constant".to_string(),
|
||||
"First".to_string(),
|
||||
@@ -727,7 +727,7 @@ mod tests {
|
||||
&LinterSettings {
|
||||
isort: super::settings::Settings {
|
||||
order_by_type: true,
|
||||
variables: FxHashSet::from_iter([
|
||||
variables: HashSet::from_iter([
|
||||
"VAR".to_string(),
|
||||
"Variable".to_string(),
|
||||
"MyVar".to_string(),
|
||||
@@ -754,7 +754,7 @@ mod tests {
|
||||
&LinterSettings {
|
||||
isort: super::settings::Settings {
|
||||
force_sort_within_sections: true,
|
||||
force_to_top: FxHashSet::from_iter(["z".to_string()]),
|
||||
force_to_top: HashSet::from_iter(["z".to_string()]),
|
||||
..super::settings::Settings::default()
|
||||
},
|
||||
src: vec![test_resource_path("fixtures/isort")],
|
||||
@@ -1010,7 +1010,7 @@ mod tests {
|
||||
vec![],
|
||||
vec![],
|
||||
vec![],
|
||||
FxHashMap::from_iter([("django".to_string(), vec![pattern("django")])]),
|
||||
HashMap::from_iter([("django".to_string(), vec![pattern("django")])]),
|
||||
),
|
||||
section_order: vec![
|
||||
ImportSection::Known(ImportType::Future),
|
||||
@@ -1061,7 +1061,7 @@ mod tests {
|
||||
Path::new("isort").join(path).as_path(),
|
||||
&LinterSettings {
|
||||
isort: super::settings::Settings {
|
||||
no_lines_before: FxHashSet::from_iter([
|
||||
no_lines_before: HashSet::from_iter([
|
||||
ImportSection::Known(ImportType::Future),
|
||||
ImportSection::Known(ImportType::StandardLibrary),
|
||||
ImportSection::Known(ImportType::ThirdParty),
|
||||
@@ -1089,7 +1089,7 @@ mod tests {
|
||||
Path::new("isort").join(path).as_path(),
|
||||
&LinterSettings {
|
||||
isort: super::settings::Settings {
|
||||
no_lines_before: FxHashSet::from_iter([
|
||||
no_lines_before: HashSet::from_iter([
|
||||
ImportSection::Known(ImportType::StandardLibrary),
|
||||
ImportSection::Known(ImportType::LocalFolder),
|
||||
]),
|
||||
@@ -1202,7 +1202,7 @@ mod tests {
|
||||
vec![],
|
||||
vec![],
|
||||
vec![],
|
||||
FxHashMap::from_iter([("django".to_string(), vec![pattern("django")])]),
|
||||
HashMap::from_iter([("django".to_string(), vec![pattern("django")])]),
|
||||
),
|
||||
section_order: vec![
|
||||
ImportSection::Known(ImportType::Future),
|
||||
@@ -1235,7 +1235,7 @@ mod tests {
|
||||
vec![],
|
||||
vec![],
|
||||
vec![],
|
||||
FxHashMap::from_iter([("django".to_string(), vec![pattern("django")])]),
|
||||
HashMap::from_iter([("django".to_string(), vec![pattern("django")])]),
|
||||
),
|
||||
section_order: vec![
|
||||
ImportSection::Known(ImportType::Future),
|
||||
@@ -1267,7 +1267,7 @@ mod tests {
|
||||
vec![],
|
||||
vec![],
|
||||
vec![],
|
||||
FxHashMap::default(),
|
||||
HashMap::default(),
|
||||
),
|
||||
..super::settings::Settings::default()
|
||||
},
|
||||
|
||||
@@ -5,7 +5,7 @@ use std::error::Error;
|
||||
use std::fmt;
|
||||
use std::fmt::{Display, Formatter};
|
||||
|
||||
use rustc_hash::FxHashSet;
|
||||
use foldhash::HashSet;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
@@ -53,17 +53,17 @@ pub struct Settings {
|
||||
pub force_sort_within_sections: bool,
|
||||
pub case_sensitive: bool,
|
||||
pub force_wrap_aliases: bool,
|
||||
pub force_to_top: FxHashSet<String>,
|
||||
pub force_to_top: HashSet<String>,
|
||||
pub known_modules: KnownModules,
|
||||
pub detect_same_package: bool,
|
||||
pub order_by_type: bool,
|
||||
pub relative_imports_order: RelativeImportsOrder,
|
||||
pub single_line_exclusions: FxHashSet<String>,
|
||||
pub single_line_exclusions: HashSet<String>,
|
||||
pub split_on_trailing_comma: bool,
|
||||
pub classes: FxHashSet<String>,
|
||||
pub constants: FxHashSet<String>,
|
||||
pub variables: FxHashSet<String>,
|
||||
pub no_lines_before: FxHashSet<ImportSection>,
|
||||
pub classes: HashSet<String>,
|
||||
pub constants: HashSet<String>,
|
||||
pub variables: HashSet<String>,
|
||||
pub no_lines_before: HashSet<ImportSection>,
|
||||
pub lines_after_imports: isize,
|
||||
pub lines_between_types: usize,
|
||||
pub forced_separate: Vec<String>,
|
||||
@@ -85,16 +85,16 @@ impl Default for Settings {
|
||||
detect_same_package: true,
|
||||
case_sensitive: false,
|
||||
force_wrap_aliases: false,
|
||||
force_to_top: FxHashSet::default(),
|
||||
force_to_top: HashSet::default(),
|
||||
known_modules: KnownModules::default(),
|
||||
order_by_type: true,
|
||||
relative_imports_order: RelativeImportsOrder::default(),
|
||||
single_line_exclusions: FxHashSet::default(),
|
||||
single_line_exclusions: HashSet::default(),
|
||||
split_on_trailing_comma: true,
|
||||
classes: FxHashSet::default(),
|
||||
constants: FxHashSet::default(),
|
||||
variables: FxHashSet::default(),
|
||||
no_lines_before: FxHashSet::default(),
|
||||
classes: HashSet::default(),
|
||||
constants: HashSet::default(),
|
||||
variables: HashSet::default(),
|
||||
no_lines_before: HashSet::default(),
|
||||
lines_after_imports: -1,
|
||||
lines_between_types: 0,
|
||||
forced_separate: Vec::new(),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::borrow::Cow;
|
||||
|
||||
use rustc_hash::FxHashMap;
|
||||
use foldhash::HashMap;
|
||||
|
||||
use ruff_python_ast::helpers::format_import_from;
|
||||
|
||||
@@ -73,7 +73,7 @@ impl<'a> Importable<'a> for ImportFromData<'a> {
|
||||
#[derive(Debug, Default)]
|
||||
pub(crate) struct ImportFromStatement<'a> {
|
||||
pub(crate) comments: ImportFromCommentSet<'a>,
|
||||
pub(crate) aliases: FxHashMap<AliasData<'a>, ImportFromCommentSet<'a>>,
|
||||
pub(crate) aliases: HashMap<AliasData<'a>, ImportFromCommentSet<'a>>,
|
||||
pub(crate) trailing_comma: TrailingComma,
|
||||
}
|
||||
|
||||
@@ -81,17 +81,17 @@ pub(crate) struct ImportFromStatement<'a> {
|
||||
pub(crate) struct ImportBlock<'a> {
|
||||
// Set of (name, asname), used to track regular imports.
|
||||
// Ex) `import module`
|
||||
pub(crate) import: FxHashMap<AliasData<'a>, ImportCommentSet<'a>>,
|
||||
pub(crate) import: HashMap<AliasData<'a>, ImportCommentSet<'a>>,
|
||||
// Map from (module, level) to `AliasData`, used to track 'from' imports.
|
||||
// Ex) `from module import member`
|
||||
pub(crate) import_from: FxHashMap<ImportFromData<'a>, ImportFromStatement<'a>>,
|
||||
pub(crate) import_from: HashMap<ImportFromData<'a>, ImportFromStatement<'a>>,
|
||||
// Set of (module, level, name, asname), used to track re-exported 'from' imports.
|
||||
// Ex) `from module import member as member`
|
||||
pub(crate) import_from_as:
|
||||
FxHashMap<(ImportFromData<'a>, AliasData<'a>), ImportFromStatement<'a>>,
|
||||
HashMap<(ImportFromData<'a>, AliasData<'a>), ImportFromStatement<'a>>,
|
||||
// Map from (module, level) to `AliasData`, used to track star imports.
|
||||
// Ex) `from module import *`
|
||||
pub(crate) import_from_star: FxHashMap<ImportFromData<'a>, ImportFromStatement<'a>>,
|
||||
pub(crate) import_from_star: HashMap<ImportFromData<'a>, ImportFromStatement<'a>>,
|
||||
}
|
||||
|
||||
type Import<'a> = (AliasData<'a>, ImportCommentSet<'a>);
|
||||
|
||||
@@ -74,7 +74,14 @@ pub(crate) fn camelcase_imported_as_acronym(
|
||||
}
|
||||
|
||||
// Ignore names that follow a community-agreed import convention.
|
||||
if is_ignored_because_of_import_convention(asname, stmt, alias, checker) {
|
||||
if checker
|
||||
.settings
|
||||
.flake8_import_conventions
|
||||
.aliases
|
||||
.get(&*alias.name)
|
||||
.map(String::as_str)
|
||||
== Some(asname)
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
@@ -90,34 +97,3 @@ pub(crate) fn camelcase_imported_as_acronym(
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn is_ignored_because_of_import_convention(
|
||||
asname: &str,
|
||||
stmt: &Stmt,
|
||||
alias: &Alias,
|
||||
checker: &Checker,
|
||||
) -> bool {
|
||||
let full_name = if let Some(import_from) = stmt.as_import_from_stmt() {
|
||||
// Never test relative imports for exclusion because we can't resolve the full-module name.
|
||||
let Some(module) = import_from.module.as_ref() else {
|
||||
return false;
|
||||
};
|
||||
|
||||
if import_from.level != 0 {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::borrow::Cow::Owned(format!("{module}.{}", alias.name))
|
||||
} else {
|
||||
std::borrow::Cow::Borrowed(&*alias.name)
|
||||
};
|
||||
|
||||
// Ignore names that follow a community-agreed import convention.
|
||||
checker
|
||||
.settings
|
||||
.flake8_import_conventions
|
||||
.aliases
|
||||
.get(&*full_name)
|
||||
.map(String::as_str)
|
||||
== Some(asname)
|
||||
}
|
||||
|
||||
@@ -15,9 +15,4 @@ N817.py:2:17: N817 CamelCase `CamelCase` imported as acronym `CC`
|
||||
| ^^^^^^^^^^^^^^^ N817
|
||||
|
|
||||
|
||||
N817.py:10:26: N817 CamelCase `ElementTree` imported as acronym `ET`
|
||||
|
|
||||
9 | # Always an error (relative import)
|
||||
10 | from ..xml.eltree import ElementTree as ET
|
||||
| ^^^^^^^^^^^^^^^^^ N817
|
||||
|
|
||||
|
||||
|
||||
@@ -20,22 +20,4 @@ N817.py:6:8: N817 CamelCase `ElementTree` imported as acronym `ET`
|
||||
5 | # OK depending on configured import convention
|
||||
6 | import xml.etree.ElementTree as ET
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ N817
|
||||
7 | from xml.etree import ElementTree as ET
|
||||
|
|
||||
|
||||
N817.py:7:23: N817 CamelCase `ElementTree` imported as acronym `ET`
|
||||
|
|
||||
5 | # OK depending on configured import convention
|
||||
6 | import xml.etree.ElementTree as ET
|
||||
7 | from xml.etree import ElementTree as ET
|
||||
| ^^^^^^^^^^^^^^^^^ N817
|
||||
8 |
|
||||
9 | # Always an error (relative import)
|
||||
|
|
||||
|
||||
N817.py:10:26: N817 CamelCase `ElementTree` imported as acronym `ET`
|
||||
|
|
||||
9 | # Always an error (relative import)
|
||||
10 | from ..xml.eltree import ElementTree as ET
|
||||
| ^^^^^^^^^^^^^^^^^ N817
|
||||
|
|
||||
|
||||
@@ -15,29 +15,21 @@ use crate::settings::types::PythonVersion;
|
||||
/// Exception handling via `try`-`except` blocks incurs some performance
|
||||
/// overhead, regardless of whether an exception is raised.
|
||||
///
|
||||
/// To optimize your code, two techniques are possible:
|
||||
/// 1. Refactor your code to put the entire loop into the `try`-`except` block,
|
||||
/// rather than wrapping each iteration in a separate `try`-`except` block.
|
||||
/// 2. Use "Look Before You Leap" idioms that attempt to avoid exceptions
|
||||
/// being raised in the first place, avoiding the need to use `try`-`except`
|
||||
/// blocks in the first place.
|
||||
/// When possible, refactor your code to put the entire loop into the
|
||||
/// `try`-`except` block, rather than wrapping each iteration in a separate
|
||||
/// `try`-`except` block.
|
||||
///
|
||||
/// This rule is only enforced for Python versions prior to 3.11, which
|
||||
/// introduced "zero-cost" exception handling. However, note that even on
|
||||
/// Python 3.11 and newer, refactoring your code to avoid exception handling in
|
||||
/// tight loops can provide a significant speedup in some cases, as zero-cost
|
||||
/// exception handling is only zero-cost in the "happy path" where no exception
|
||||
/// is raised in the `try`-`except` block.
|
||||
/// introduced "zero cost" exception handling.
|
||||
///
|
||||
/// As with all `perflint` rules, this is only intended as a
|
||||
/// micro-optimization. In many cases, it will have a negligible impact on
|
||||
/// performance.
|
||||
/// Note that, as with all `perflint` rules, this is only intended as a
|
||||
/// micro-optimization, and will have a negligible impact on performance in
|
||||
/// most cases.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// string_numbers: list[str] = ["1", "2", "three", "4", "5"]
|
||||
///
|
||||
/// # `try`/`except` that could be moved out of the loop:
|
||||
/// int_numbers: list[int] = []
|
||||
/// for num in string_numbers:
|
||||
/// try:
|
||||
@@ -45,16 +37,6 @@ use crate::settings::types::PythonVersion;
|
||||
/// except ValueError as e:
|
||||
/// print(f"Couldn't convert to integer: {e}")
|
||||
/// break
|
||||
///
|
||||
/// # `try`/`except` used when "look before you leap" idioms could be used:
|
||||
/// number_names: dict[int, str] = {1: "one", 3: "three", 4: "four"}
|
||||
/// for number in range(5):
|
||||
/// try:
|
||||
/// name = number_names[number]
|
||||
/// except KeyError:
|
||||
/// continue
|
||||
/// else:
|
||||
/// print(f"The name of {number} is {name}")
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
@@ -67,12 +49,6 @@ use crate::settings::types::PythonVersion;
|
||||
/// int_numbers.append(int(num))
|
||||
/// except ValueError as e:
|
||||
/// print(f"Couldn't convert to integer: {e}")
|
||||
///
|
||||
/// number_names: dict[int, str] = {1: "one", 3: "three", 4: "four"}
|
||||
/// for number in range(5):
|
||||
/// name = number_names.get(number)
|
||||
/// if name is not None:
|
||||
/// print(f"The name of {number} is {name}")
|
||||
/// ```
|
||||
///
|
||||
/// ## Options
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use rustc_hash::FxHashMap;
|
||||
use foldhash::HashMap;
|
||||
|
||||
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
@@ -175,7 +175,7 @@ pub(crate) fn literal_comparisons(checker: &mut Checker, compare: &ast::ExprComp
|
||||
// through the list of operators, we apply "dummy" fixes for each error,
|
||||
// then replace the entire expression at the end with one "real" fix, to
|
||||
// avoid conflicts.
|
||||
let mut bad_ops: FxHashMap<usize, CmpOp> = FxHashMap::default();
|
||||
let mut bad_ops: HashMap<usize, CmpOp> = HashMap::default();
|
||||
let mut diagnostics: Vec<Diagnostic> = vec![];
|
||||
|
||||
// Check `left`.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use foldhash::HashSet;
|
||||
use itertools::Itertools;
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
use rustc_hash::FxHashSet;
|
||||
use std::ops::Add;
|
||||
|
||||
use ruff_diagnostics::{AlwaysFixableViolation, Violation};
|
||||
@@ -1779,13 +1779,13 @@ fn common_section(
|
||||
blanks_and_section_underline(checker, docstring, context);
|
||||
}
|
||||
|
||||
fn missing_args(checker: &mut Checker, docstring: &Docstring, docstrings_args: &FxHashSet<String>) {
|
||||
fn missing_args(checker: &mut Checker, docstring: &Docstring, docstrings_args: &HashSet<String>) {
|
||||
let Some(function) = docstring.definition.as_function_def() else {
|
||||
return;
|
||||
};
|
||||
|
||||
// Look for arguments that weren't included in the docstring.
|
||||
let mut missing_arg_names: FxHashSet<String> = FxHashSet::default();
|
||||
let mut missing_arg_names: HashSet<String> = HashSet::default();
|
||||
|
||||
// If this is a non-static method, skip `cls` or `self`.
|
||||
for ParameterWithDefault {
|
||||
@@ -1847,10 +1847,10 @@ fn missing_args(checker: &mut Checker, docstring: &Docstring, docstrings_args: &
|
||||
static GOOGLE_ARGS_REGEX: Lazy<Regex> =
|
||||
Lazy::new(|| Regex::new(r"^\s*(\*?\*?\w+)\s*(\(.*?\))?\s*:(\r\n|\n)?\s*.+").unwrap());
|
||||
|
||||
fn args_section(context: &SectionContext) -> FxHashSet<String> {
|
||||
fn args_section(context: &SectionContext) -> HashSet<String> {
|
||||
let mut following_lines = context.following_lines().peekable();
|
||||
let Some(first_line) = following_lines.next() else {
|
||||
return FxHashSet::default();
|
||||
return HashSet::default();
|
||||
};
|
||||
|
||||
// Normalize leading whitespace, by removing any lines with less indentation
|
||||
@@ -1896,12 +1896,12 @@ fn args_section(context: &SectionContext) -> FxHashSet<String> {
|
||||
matches
|
||||
.iter()
|
||||
.filter_map(|captures| captures.get(1).map(|arg_name| arg_name.as_str().to_owned()))
|
||||
.collect::<FxHashSet<String>>()
|
||||
.collect::<HashSet<String>>()
|
||||
}
|
||||
|
||||
fn parameters_section(checker: &mut Checker, docstring: &Docstring, context: &SectionContext) {
|
||||
// Collect the list of arguments documented in the docstring.
|
||||
let mut docstring_args: FxHashSet<String> = FxHashSet::default();
|
||||
let mut docstring_args: HashSet<String> = HashSet::default();
|
||||
let section_level_indent = leading_space(context.summary_line());
|
||||
|
||||
// Join line continuations, then resplit by line.
|
||||
@@ -2026,7 +2026,7 @@ fn parse_google_sections(
|
||||
|
||||
if checker.enabled(Rule::UndocumentedParam) {
|
||||
let mut has_args = false;
|
||||
let mut documented_args: FxHashSet<String> = FxHashSet::default();
|
||||
let mut documented_args: HashSet<String> = HashSet::default();
|
||||
for section_context in section_contexts {
|
||||
// Checks occur at the section level. Since two sections (args/keyword args and their
|
||||
// variants) can list arguments, we need to unify the sets of arguments mentioned in both
|
||||
|
||||
@@ -2,22 +2,22 @@
|
||||
use std::convert::TryFrom;
|
||||
use std::str::FromStr;
|
||||
|
||||
use foldhash::HashSet;
|
||||
use ruff_python_literal::cformat::{
|
||||
CFormatError, CFormatPart, CFormatPrecision, CFormatQuantity, CFormatSpec, CFormatString,
|
||||
};
|
||||
use rustc_hash::FxHashSet;
|
||||
|
||||
pub(crate) struct CFormatSummary {
|
||||
pub(crate) starred: bool,
|
||||
pub(crate) num_positional: usize,
|
||||
pub(crate) keywords: FxHashSet<String>,
|
||||
pub(crate) keywords: HashSet<String>,
|
||||
}
|
||||
|
||||
impl From<&CFormatString> for CFormatSummary {
|
||||
fn from(format_string: &CFormatString) -> Self {
|
||||
let mut starred = false;
|
||||
let mut num_positional = 0;
|
||||
let mut keywords = FxHashSet::default();
|
||||
let mut keywords = HashSet::default();
|
||||
|
||||
for format_part in format_string.iter() {
|
||||
let CFormatPart::Spec(CFormatSpec {
|
||||
|
||||
@@ -10,8 +10,8 @@ mod tests {
|
||||
use std::path::Path;
|
||||
|
||||
use anyhow::Result;
|
||||
use foldhash::HashMap;
|
||||
use regex::Regex;
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
use test_case::test_case;
|
||||
|
||||
@@ -261,7 +261,7 @@ mod tests {
|
||||
vec![],
|
||||
vec![],
|
||||
vec![],
|
||||
FxHashMap::default(),
|
||||
HashMap::default(),
|
||||
),
|
||||
..isort::settings::Settings::default()
|
||||
},
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use rustc_hash::{FxBuildHasher, FxHashMap, FxHashSet};
|
||||
use foldhash::{HashMap, HashMapExt, HashSet, HashSetExt};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
@@ -129,8 +129,8 @@ impl Violation for MultiValueRepeatedKeyVariable {
|
||||
/// F601, F602
|
||||
pub(crate) fn repeated_keys(checker: &mut Checker, dict: &ast::ExprDict) {
|
||||
// Generate a map from key to (index, value).
|
||||
let mut seen: FxHashMap<ComparableExpr, FxHashSet<ComparableExpr>> =
|
||||
FxHashMap::with_capacity_and_hasher(dict.len(), FxBuildHasher);
|
||||
let mut seen: HashMap<ComparableExpr, HashSet<ComparableExpr>> =
|
||||
HashMap::with_capacity(dict.len());
|
||||
|
||||
// Detect duplicate keys.
|
||||
for (i, ast::DictItem { key, value }) in dict.iter().enumerate() {
|
||||
@@ -142,7 +142,7 @@ pub(crate) fn repeated_keys(checker: &mut Checker, dict: &ast::ExprDict) {
|
||||
let comparable_value = ComparableExpr::from(value);
|
||||
|
||||
let Some(seen_values) = seen.get_mut(&comparable_key) else {
|
||||
seen.insert(comparable_key, FxHashSet::from_iter([comparable_value]));
|
||||
seen.insert(comparable_key, HashSet::from_iter([comparable_value]));
|
||||
continue;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::string::ToString;
|
||||
|
||||
use rustc_hash::FxHashSet;
|
||||
use foldhash::HashSet;
|
||||
|
||||
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Fix, FixAvailability, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
@@ -637,7 +637,7 @@ pub(crate) fn percent_format_missing_arguments(
|
||||
return; // contains **x splat
|
||||
}
|
||||
|
||||
let mut keywords = FxHashSet::default();
|
||||
let mut keywords = HashSet::default();
|
||||
for key in dict.iter_keys().flatten() {
|
||||
match key {
|
||||
Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) => {
|
||||
@@ -859,7 +859,7 @@ pub(crate) fn string_dot_format_missing_argument(
|
||||
return;
|
||||
}
|
||||
|
||||
let keywords: FxHashSet<_> = keywords
|
||||
let keywords: HashSet<_> = keywords
|
||||
.iter()
|
||||
.filter_map(|k| {
|
||||
let Keyword { arg, .. } = &k;
|
||||
|
||||
@@ -8,8 +8,8 @@ mod tests {
|
||||
use std::path::Path;
|
||||
|
||||
use anyhow::Result;
|
||||
use foldhash::HashSet;
|
||||
use regex::Regex;
|
||||
use rustc_hash::FxHashSet;
|
||||
use test_case::test_case;
|
||||
|
||||
use crate::registry::Rule;
|
||||
@@ -220,7 +220,7 @@ mod tests {
|
||||
Path::new("pylint").join(path).as_path(),
|
||||
&LinterSettings {
|
||||
pylint: pylint::settings::Settings {
|
||||
allow_dunder_method_names: FxHashSet::from_iter([
|
||||
allow_dunder_method_names: HashSet::from_iter([
|
||||
"__special_custom_magic__".to_string()
|
||||
]),
|
||||
..pylint::settings::Settings::default()
|
||||
|
||||
@@ -91,18 +91,13 @@ pub(crate) fn bad_staticmethod_argument(
|
||||
return;
|
||||
};
|
||||
|
||||
match (name.as_str(), self_or_cls.name.as_str()) {
|
||||
("__new__", "cls") => {
|
||||
return;
|
||||
}
|
||||
(_, "self" | "cls") => {}
|
||||
_ => return,
|
||||
if matches!(self_or_cls.name.as_str(), "self" | "cls") {
|
||||
let diagnostic = Diagnostic::new(
|
||||
BadStaticmethodArgument {
|
||||
argument_name: self_or_cls.name.to_string(),
|
||||
},
|
||||
self_or_cls.range(),
|
||||
);
|
||||
diagnostics.push(diagnostic);
|
||||
}
|
||||
|
||||
diagnostics.push(Diagnostic::new(
|
||||
BadStaticmethodArgument {
|
||||
argument_name: self_or_cls.name.to_string(),
|
||||
},
|
||||
self_or_cls.range(),
|
||||
));
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::fmt;
|
||||
|
||||
use foldhash::HashSet;
|
||||
use ruff_python_ast::{self as ast, Expr};
|
||||
use rustc_hash::FxHashSet;
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
@@ -124,7 +124,7 @@ impl fmt::Display for RemovalKind {
|
||||
/// escapes.
|
||||
fn has_duplicates(s: &ast::StringLiteralValue) -> bool {
|
||||
let mut escaped = false;
|
||||
let mut seen = FxHashSet::default();
|
||||
let mut seen = HashSet::default();
|
||||
for ch in s.chars() {
|
||||
if escaped {
|
||||
escaped = false;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user