Compare commits

..

1 Commits

Author SHA1 Message Date
Charlie Marsh
400732a655 Use FoldHash 2024-08-16 18:24:38 -04:00
144 changed files with 935 additions and 2044 deletions

View File

@@ -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

View File

@@ -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
View File

@@ -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",

View File

@@ -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" }

View File

@@ -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 }

View File

@@ -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")
};

View File

@@ -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 {

View File

@@ -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)]

View File

@@ -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>>,

View File

@@ -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,
}

View File

@@ -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]);
}
}

View File

@@ -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>"]);
}
}

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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));
}
}

View File

@@ -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]

View File

@@ -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
}
}
}
}

View File

@@ -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 }

View File

@@ -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);
}

View File

@@ -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(),
},
}
}

View File

@@ -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,
}
}

View File

@@ -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)]

View File

@@ -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 }

View File

@@ -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()) {

View File

@@ -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.

View File

@@ -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

View File

@@ -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);
}
}

View File

@@ -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 }

View File

@@ -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)

View File

@@ -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) {

View File

@@ -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();

View File

@@ -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.

View File

@@ -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 {

View File

@@ -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
"###);

View File

@@ -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"] }

View File

@@ -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};

View File

@@ -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]

View File

@@ -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,
}

View File

@@ -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 }

View File

@@ -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()

View File

@@ -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(),
}
}
}

View File

@@ -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 }

View File

@@ -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)]

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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}

View File

@@ -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

View File

@@ -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);

View File

@@ -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);

View File

@@ -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

View File

@@ -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;

View File

@@ -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.

View File

@@ -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(&notebook_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();

View File

@@ -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
}
}

View File

@@ -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 {

View File

@@ -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_),

View File

@@ -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);

View File

@@ -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> {

View File

@@ -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()),

View File

@@ -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,
}

View File

@@ -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
|

View File

@@ -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)
},

View File

@@ -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

View File

@@ -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(

View File

@@ -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();

View File

@@ -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(),
}
}
}

View File

@@ -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 {

View File

@@ -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;

View File

@@ -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()) {

View File

@@ -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

View File

@@ -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

View File

@@ -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`.

View File

@@ -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);

View File

@@ -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()) {

View File

@@ -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.

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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>,
}

View File

@@ -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);

View File

@@ -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);

View File

@@ -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(

View File

@@ -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 | ...
|

View File

@@ -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

View File

@@ -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()
},

View File

@@ -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(),

View File

@@ -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>);

View File

@@ -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)
}

View File

@@ -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
|

View File

@@ -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
|

View File

@@ -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

View File

@@ -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`.

View File

@@ -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

View File

@@ -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 {

View File

@@ -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()
},

View File

@@ -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;
};

View File

@@ -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;

View File

@@ -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()

View File

@@ -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(),
));
}

View File

@@ -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