Compare commits

..

3 Commits

Author SHA1 Message Date
Douglas Creager
7c976dc570 Merge branch 'main' into dcreager/function-enum
* main:
  Update pre-commit dependencies (#17506)
  [red-knot] Simplify visibility constraint handling for `*`-import definitions (#17486)
  [red-knot] Detect (some) invalid protocols (#17488)
  [red-knot] Correctly identify protocol classes (#17487)
  Update dependency ruff to v0.11.6 (#17516)
  Update Rust crate shellexpand to v3.1.1 (#17512)
  Update Rust crate proc-macro2 to v1.0.95 (#17510)
  Update Rust crate rand to v0.9.1 (#17511)
  Update Rust crate libc to v0.2.172 (#17509)
  Update Rust crate jiff to v0.2.9 (#17508)
  Update Rust crate clap to v4.5.37 (#17507)
  Update astral-sh/setup-uv action to v5.4.2 (#17504)
  Update taiki-e/install-action digest to 09dc018 (#17503)
  [red-knot] infer attribute assignments bound in comprehensions (#17396)
  [red-knot] simplify gradually-equivalent types out of unions and intersections (#17467)
  [red-knot] pull primer projects to run from file (#17473)
2025-04-21 13:18:36 -04:00
Douglas Creager
b44fb47f25 create generic context lazily 2025-04-21 13:15:09 -04:00
Douglas Creager
0a4dec0323 start pulling out enum 2025-04-18 17:04:26 -04:00
2018 changed files with 15211 additions and 34785 deletions

6
.gitattributes vendored
View File

@@ -12,12 +12,6 @@ crates/ruff_python_parser/resources/invalid/re_lexing/line_continuation_windows_
crates/ruff_python_parser/resources/invalid/re_lex_logical_token_windows_eol.py text eol=crlf
crates/ruff_python_parser/resources/invalid/re_lex_logical_token_mac_eol.py text eol=cr
crates/ruff_linter/resources/test/fixtures/ruff/RUF046_CR.py text eol=cr
crates/ruff_linter/resources/test/fixtures/ruff/RUF046_LF.py text eol=lf
crates/ruff_linter/resources/test/fixtures/pyupgrade/UP018_CR.py text eol=cr
crates/ruff_linter/resources/test/fixtures/pyupgrade/UP018_LF.py text eol=lf
crates/ruff_python_parser/resources/inline linguist-generated=true
ruff.schema.json -diff linguist-generated=true text=auto eol=lf

10
.github/CODEOWNERS vendored
View File

@@ -14,11 +14,11 @@
# flake8-pyi
/crates/ruff_linter/src/rules/flake8_pyi/ @AlexWaygood
# Script for fuzzing the parser/ty etc.
# Script for fuzzing the parser/red-knot etc.
/python/py-fuzzer/ @AlexWaygood
# ty
/crates/ty* @carljm @MichaReiser @AlexWaygood @sharkdp @dcreager
# red-knot
/crates/red_knot* @carljm @MichaReiser @AlexWaygood @sharkdp @dcreager
/crates/ruff_db/ @carljm @MichaReiser @AlexWaygood @sharkdp @dcreager
/scripts/ty_benchmark/ @carljm @MichaReiser @AlexWaygood @sharkdp @dcreager
/crates/ty_python_semantic @carljm @AlexWaygood @sharkdp @dcreager
/scripts/knot_benchmark/ @carljm @MichaReiser @AlexWaygood @sharkdp @dcreager
/crates/red_knot_python_semantic @carljm @AlexWaygood @sharkdp @dcreager

View File

@@ -1,8 +1,5 @@
blank_issues_enabled: true
contact_links:
- name: Report an issue with ty
url: https://github.com/astral-sh/ty/issues/new/choose
about: Please report issues for our type checker ty in the ty repository.
- name: Documentation
url: https://docs.astral.sh/ruff
about: Please consult the documentation before creating an issue.

View File

@@ -6,6 +6,5 @@ self-hosted-runner:
labels:
- depot-ubuntu-latest-8
- depot-ubuntu-22.04-16
- depot-ubuntu-22.04-32
- github-windows-2025-x86_64-8
- github-windows-2025-x86_64-16

View File

@@ -43,7 +43,7 @@ jobs:
with:
submodules: recursive
persist-credentials: false
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
- uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55 # v5.5.0
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: "Prep README.md"
@@ -72,7 +72,7 @@ jobs:
with:
submodules: recursive
persist-credentials: false
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
- uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55 # v5.5.0
with:
python-version: ${{ env.PYTHON_VERSION }}
architecture: x64
@@ -114,7 +114,7 @@ jobs:
with:
submodules: recursive
persist-credentials: false
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
- uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55 # v5.5.0
with:
python-version: ${{ env.PYTHON_VERSION }}
architecture: arm64
@@ -170,7 +170,7 @@ jobs:
with:
submodules: recursive
persist-credentials: false
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
- uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55 # v5.5.0
with:
python-version: ${{ env.PYTHON_VERSION }}
architecture: ${{ matrix.platform.arch }}
@@ -223,7 +223,7 @@ jobs:
with:
submodules: recursive
persist-credentials: false
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
- uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55 # v5.5.0
with:
python-version: ${{ env.PYTHON_VERSION }}
architecture: x64
@@ -298,7 +298,7 @@ jobs:
with:
submodules: recursive
persist-credentials: false
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
- uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55 # v5.5.0
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: "Prep README.md"
@@ -363,7 +363,7 @@ jobs:
with:
submodules: recursive
persist-credentials: false
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
- uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55 # v5.5.0
with:
python-version: ${{ env.PYTHON_VERSION }}
architecture: x64
@@ -429,7 +429,7 @@ jobs:
with:
submodules: recursive
persist-credentials: false
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
- uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55 # v5.5.0
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: "Prep README.md"

View File

@@ -79,7 +79,7 @@ jobs:
# Adapted from https://docs.docker.com/build/ci/github-actions/multi-platform/
- name: Build and push by digest
id: build
uses: docker/build-push-action@14487ce63c7a62a4a324b0bfb37086795e31c6c1 # v6
uses: docker/build-push-action@471d1dc4e07e5cdedd4c2171150001c434f0b7a4 # v6
with:
context: .
platforms: ${{ matrix.platform }}
@@ -113,7 +113,7 @@ jobs:
if: ${{ inputs.plan != '' && !fromJson(inputs.plan).announcement_tag_is_implicit }}
steps:
- name: Download digests
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e # v4.2.1
with:
path: /tmp/digests
pattern: digests-*
@@ -231,7 +231,7 @@ jobs:
${{ env.TAG_PATTERNS }}
- name: Build and push
uses: docker/build-push-action@14487ce63c7a62a4a324b0bfb37086795e31c6c1 # v6
uses: docker/build-push-action@471d1dc4e07e5cdedd4c2171150001c434f0b7a4 # v6
with:
context: .
platforms: linux/amd64,linux/arm64
@@ -256,7 +256,7 @@ jobs:
if: ${{ inputs.plan != '' && !fromJson(inputs.plan).announcement_tag_is_implicit }}
steps:
- name: Download digests
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e # v4.2.1
with:
path: /tmp/digests
pattern: digests-*

View File

@@ -36,8 +36,8 @@ jobs:
code: ${{ steps.check_code.outputs.changed }}
# Flag that is raised when any code that affects the fuzzer is changed
fuzz: ${{ steps.check_fuzzer.outputs.changed }}
# Flag that is set to "true" when code related to ty changes.
ty: ${{ steps.check_ty.outputs.changed }}
# Flag that is set to "true" when code related to red-knot changes.
red_knot: ${{ steps.check_red_knot.outputs.changed }}
# Flag that is set to "true" when code related to the playground changes.
playground: ${{ steps.check_playground.outputs.changed }}
@@ -84,7 +84,7 @@ jobs:
if git diff --quiet "${MERGE_BASE}...HEAD" -- ':Cargo.toml' \
':Cargo.lock' \
':crates/**' \
':!crates/ty*/**' \
':!crates/red_knot*/**' \
':!crates/ruff_python_formatter/**' \
':!crates/ruff_formatter/**' \
':!crates/ruff_dev/**' \
@@ -145,7 +145,7 @@ jobs:
run: |
if git diff --quiet "${MERGE_BASE}...HEAD" -- ':**' \
':!**/*.md' \
':crates/ty_python_semantic/resources/mdtest/**/*.md' \
':crates/red_knot_python_semantic/resources/mdtest/**/*.md' \
':!docs/**' \
':!assets/**' \
':.github/workflows/ci.yaml' \
@@ -168,15 +168,15 @@ jobs:
echo "changed=true" >> "$GITHUB_OUTPUT"
fi
- name: Check if the ty code changed
id: check_ty
- name: Check if the red-knot code changed
id: check_red_knot
env:
MERGE_BASE: ${{ steps.merge_base.outputs.sha }}
run: |
if git diff --quiet "${MERGE_BASE}...HEAD" -- \
':Cargo.toml' \
':Cargo.lock' \
':crates/ty*/**' \
':crates/red_knot*/**' \
':crates/ruff_db/**' \
':crates/ruff_annotate_snippets/**' \
':crates/ruff_python_ast/**' \
@@ -221,7 +221,7 @@ jobs:
- name: "Clippy"
run: cargo clippy --workspace --all-targets --all-features --locked -- -D warnings
- name: "Clippy (wasm)"
run: cargo clippy -p ruff_wasm -p ty_wasm --target wasm32-unknown-unknown --all-features --locked -- -D warnings
run: cargo clippy -p ruff_wasm -p red_knot_wasm --target wasm32-unknown-unknown --all-features --locked -- -D warnings
cargo-test-linux:
name: "cargo test (linux)"
@@ -239,21 +239,21 @@ jobs:
- name: "Install mold"
uses: rui314/setup-mold@e16410e7f8d9e167b74ad5697a9089a35126eb50 # v1
- name: "Install cargo nextest"
uses: taiki-e/install-action@86c23eed46c17b80677df6d8151545ce3e236c61 # v2
uses: taiki-e/install-action@09dc018eee06ae1c9e0409786563f534210ceb83 # v2
with:
tool: cargo-nextest
- name: "Install cargo insta"
uses: taiki-e/install-action@86c23eed46c17b80677df6d8151545ce3e236c61 # v2
uses: taiki-e/install-action@09dc018eee06ae1c9e0409786563f534210ceb83 # v2
with:
tool: cargo-insta
- name: ty mdtests (GitHub annotations)
if: ${{ needs.determine_changes.outputs.ty == 'true' }}
- name: Red-knot mdtests (GitHub annotations)
if: ${{ needs.determine_changes.outputs.red_knot == 'true' }}
env:
NO_COLOR: 1
MDTEST_GITHUB_ANNOTATIONS_FORMAT: 1
# Ignore errors if this step fails; we want to continue to later steps in the workflow anyway.
# This step is just to get nice GitHub annotations on the PR diff in the files-changed tab.
run: cargo test -p ty_python_semantic --test mdtest || true
run: cargo test -p red_knot_python_semantic --test mdtest || true
- name: "Run tests"
shell: bash
env:
@@ -268,7 +268,7 @@ jobs:
# sync, not just public items. Eventually we should do this for all
# crates; for now add crates here as they are warning-clean to prevent
# regression.
- run: cargo doc --no-deps -p ty_python_semantic -p ty -p ty_test -p ruff_db --document-private-items
- run: cargo doc --no-deps -p red_knot_python_semantic -p red_knot -p red_knot_test -p ruff_db --document-private-items
env:
# Setting RUSTDOCFLAGS because `cargo doc --check` isn't yet implemented (https://github.com/rust-lang/cargo/issues/10025).
RUSTDOCFLAGS: "-D warnings"
@@ -276,10 +276,6 @@ jobs:
with:
name: ruff
path: target/debug/ruff
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: ty
path: target/debug/ty
cargo-test-linux-release:
name: "cargo test (linux, release)"
@@ -297,11 +293,11 @@ jobs:
- name: "Install mold"
uses: rui314/setup-mold@e16410e7f8d9e167b74ad5697a9089a35126eb50 # v1
- name: "Install cargo nextest"
uses: taiki-e/install-action@86c23eed46c17b80677df6d8151545ce3e236c61 # v2
uses: taiki-e/install-action@09dc018eee06ae1c9e0409786563f534210ceb83 # v2
with:
tool: cargo-nextest
- name: "Install cargo insta"
uses: taiki-e/install-action@86c23eed46c17b80677df6d8151545ce3e236c61 # v2
uses: taiki-e/install-action@09dc018eee06ae1c9e0409786563f534210ceb83 # v2
with:
tool: cargo-insta
- name: "Run tests"
@@ -324,7 +320,7 @@ jobs:
- name: "Install Rust toolchain"
run: rustup show
- name: "Install cargo nextest"
uses: taiki-e/install-action@86c23eed46c17b80677df6d8151545ce3e236c61 # v2
uses: taiki-e/install-action@09dc018eee06ae1c9e0409786563f534210ceb83 # v2
with:
tool: cargo-nextest
- name: "Run tests"
@@ -350,7 +346,7 @@ jobs:
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
- name: "Install Rust toolchain"
run: rustup target add wasm32-unknown-unknown
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
- uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4.3.0
with:
node-version: 20
cache: "npm"
@@ -362,9 +358,9 @@ jobs:
run: |
cd crates/ruff_wasm
wasm-pack test --node
- name: "Test ty_wasm"
- name: "Test red_knot_wasm"
run: |
cd crates/ty_wasm
cd crates/red_knot_wasm
wasm-pack test --node
cargo-build-release:
@@ -407,11 +403,11 @@ jobs:
- name: "Install mold"
uses: rui314/setup-mold@e16410e7f8d9e167b74ad5697a9089a35126eb50 # v1
- name: "Install cargo nextest"
uses: taiki-e/install-action@86c23eed46c17b80677df6d8151545ce3e236c61 # v2
uses: taiki-e/install-action@09dc018eee06ae1c9e0409786563f534210ceb83 # v2
with:
tool: cargo-nextest
- name: "Install cargo insta"
uses: taiki-e/install-action@86c23eed46c17b80677df6d8151545ce3e236c61 # v2
uses: taiki-e/install-action@09dc018eee06ae1c9e0409786563f534210ceb83 # v2
with:
tool: cargo-insta
- name: "Run tests"
@@ -460,7 +456,7 @@ jobs:
with:
persist-credentials: false
- uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5.4.2
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
- uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e # v4.2.1
name: Download Ruff binary to test
id: download-cached-binary
with:
@@ -525,11 +521,11 @@ jobs:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
- uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55 # v5.5.0
with:
python-version: ${{ env.PYTHON_VERSION }}
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
- uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e # v4.2.1
name: Download comparison Ruff binary
id: ruff-target
with:
@@ -636,53 +632,6 @@ jobs:
name: ecosystem-result
path: ecosystem-result
fuzz-ty:
name: "Fuzz for new ty panics"
runs-on: depot-ubuntu-22.04-16
needs:
- cargo-test-linux
- determine_changes
# Only runs on pull requests, since that is the only we way we can find the base version for comparison.
if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-test') && github.event_name == 'pull_request' && needs.determine_changes.outputs.ty == 'true' }}
timeout-minutes: 20
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
name: Download new ty binary
id: ty-new
with:
name: ty
path: target/debug
- uses: dawidd6/action-download-artifact@20319c5641d495c8a52e688b7dc5fada6c3a9fbc # v8
name: Download baseline ty binary
with:
name: ty
branch: ${{ github.event.pull_request.base.ref }}
workflow: "ci.yaml"
check_artifacts: true
- uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5.4.2
- name: Fuzz
env:
FORCE_COLOR: 1
NEW_TY: ${{ steps.ty-new.outputs.download-path }}
run: |
# Make executable, since artifact download doesn't preserve this
chmod +x "${PWD}/ty" "${NEW_TY}/ty"
(
uvx \
--python="${PYTHON_VERSION}" \
--from=./python/py-fuzzer \
fuzz \
--test-executable="${NEW_TY}/ty" \
--baseline-executable="${PWD}/ty" \
--only-new-bugs \
--bin=ty \
0-500
)
cargo-shear:
name: "cargo shear"
runs-on: ubuntu-latest
@@ -705,7 +654,7 @@ jobs:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
- uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55 # v5.5.0
with:
python-version: ${{ env.PYTHON_VERSION }}
architecture: x64
@@ -759,7 +708,7 @@ jobs:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
- uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55 # v5.5.0
with:
python-version: "3.13"
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
@@ -830,12 +779,12 @@ jobs:
persist-credentials: false
repository: "astral-sh/ruff-lsp"
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
- uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55 # v5.5.0
with:
# installation fails on 3.13 and newer
python-version: "3.12"
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
- uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e # v4.2.1
name: Download development ruff binary
id: ruff-target
with:
@@ -872,7 +821,7 @@ jobs:
- name: "Install Rust toolchain"
run: rustup target add wasm32-unknown-unknown
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
- uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4.3.0
with:
node-version: 22
cache: "npm"
@@ -908,7 +857,7 @@ jobs:
run: rustup show
- name: "Install codspeed"
uses: taiki-e/install-action@86c23eed46c17b80677df6d8151545ce3e236c61 # v2
uses: taiki-e/install-action@09dc018eee06ae1c9e0409786563f534210ceb83 # v2
with:
tool: cargo-codspeed

View File

@@ -38,17 +38,17 @@ jobs:
- name: "Install mold"
uses: rui314/setup-mold@e16410e7f8d9e167b74ad5697a9089a35126eb50 # v1
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
- name: Build ty
- name: Build Red Knot
# A release build takes longer (2 min vs 1 min), but the property tests run much faster in release
# mode (1.5 min vs 14 min), so the overall time is shorter with a release build.
run: cargo build --locked --release --package ty_python_semantic --tests
run: cargo build --locked --release --package red_knot_python_semantic --tests
- name: Run property tests
shell: bash
run: |
export QUICKCHECK_TESTS=100000
for _ in {1..5}; do
cargo test --locked --release --package ty_python_semantic -- --ignored list::property_tests
cargo test --locked --release --package ty_python_semantic -- --ignored types::property_tests::stable
cargo test --locked --release --package red_knot_python_semantic -- --ignored list::property_tests
cargo test --locked --release --package red_knot_python_semantic -- --ignored types::property_tests::stable
done
create-issue-on-failure:
@@ -68,5 +68,5 @@ jobs:
repo: "ruff",
title: `Daily property test run failed on ${new Date().toDateString()}`,
body: "Run listed here: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}",
labels: ["bug", "ty", "testing"],
labels: ["bug", "red-knot", "testing"],
})

View File

@@ -5,7 +5,7 @@ permissions: {}
on:
pull_request:
paths:
- "crates/ty*/**"
- "crates/red_knot*/**"
- "crates/ruff_db"
- "crates/ruff_python_ast"
- "crates/ruff_python_parser"
@@ -21,12 +21,11 @@ env:
CARGO_NET_RETRY: 10
CARGO_TERM_COLOR: always
RUSTUP_MAX_RETRIES: 10
RUST_BACKTRACE: 1
jobs:
mypy_primer:
name: Run mypy_primer
runs-on: depot-ubuntu-22.04-32
runs-on: depot-ubuntu-22.04-16
timeout-minutes: 20
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
@@ -41,16 +40,19 @@ jobs:
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
with:
workspaces: "ruff"
- name: Install Rust toolchain
run: rustup show
- name: Install mypy_primer
run: |
uv tool install "git+https://github.com/astral-sh/mypy_primer.git@add-red-knot-support-v5"
- name: Run mypy_primer
shell: bash
run: |
cd ruff
PRIMER_SELECTOR="$(paste -s -d'|' crates/ty_python_semantic/resources/primer/good.txt)"
PRIMER_SELECTOR="$(paste -s -d'|' crates/red_knot_python_semantic/resources/primer/good.txt)"
echo "new commit"
git rev-list --format=%s --max-count=1 "$GITHUB_SHA"
@@ -64,11 +66,9 @@ jobs:
echo "Project selector: $PRIMER_SELECTOR"
# Allow the exit code to be 0 or 1, only fail for actual mypy_primer crashes/bugs
uvx \
--from="git+https://github.com/hauntsaninja/mypy_primer@4b15cf3b07db69db67bbfaebfffb2a8a28040933" \
mypy_primer \
uvx mypy_primer \
--repo ruff \
--type-checker ty \
--type-checker knot \
--old base_commit \
--new "$GITHUB_SHA" \
--project-selector "/($PRIMER_SELECTOR)\$" \

View File

@@ -28,7 +28,7 @@ jobs:
ref: ${{ inputs.ref }}
persist-credentials: true
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
- uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55 # v5.5.0
with:
python-version: 3.12

View File

@@ -1,5 +1,5 @@
# Publish the ty playground.
name: "[ty Playground] Release"
# Publish the Red Knot playground.
name: "[Knot Playground] Release"
permissions: {}
@@ -7,12 +7,12 @@ on:
push:
branches: [main]
paths:
- "crates/ty*/**"
- "crates/red_knot*/**"
- "crates/ruff_db/**"
- "crates/ruff_python_ast/**"
- "crates/ruff_python_parser/**"
- "playground/**"
- ".github/workflows/publish-ty-playground.yml"
- ".github/workflows/publish-knot-playground.yml"
concurrency:
group: ${{ github.workflow }}-${{ github.ref_name }}
@@ -35,7 +35,7 @@ jobs:
persist-credentials: false
- name: "Install Rust toolchain"
run: rustup target add wasm32-unknown-unknown
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
- uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4.3.0
with:
node-version: 22
- uses: jetli/wasm-bindgen-action@20b33e20595891ab1a0ed73145d8a21fc96e7c29 # v0.2.0
@@ -45,8 +45,8 @@ jobs:
- name: "Run TypeScript checks"
run: npm run check
working-directory: playground
- name: "Build ty playground"
run: npm run build --workspace ty-playground
- name: "Build Knot playground"
run: npm run build --workspace knot-playground
working-directory: playground
- name: "Deploy to Cloudflare Pages"
if: ${{ env.CF_API_TOKEN_EXISTS == 'true' }}
@@ -55,4 +55,4 @@ jobs:
apiToken: ${{ secrets.CF_API_TOKEN }}
accountId: ${{ secrets.CF_ACCOUNT_ID }}
# `github.head_ref` is only set during pull requests and for manual runs or tags we use `main` to deploy to production
command: pages deploy playground/ty/dist --project-name=ty-playground --branch ${{ github.head_ref || 'main' }} --commit-hash ${GITHUB_SHA}
command: pages deploy playground/knot/dist --project-name=knot-playground --branch ${{ github.head_ref || 'main' }} --commit-hash ${GITHUB_SHA}

View File

@@ -29,7 +29,7 @@ jobs:
persist-credentials: false
- name: "Install Rust toolchain"
run: rustup target add wasm32-unknown-unknown
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
- uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4.3.0
with:
node-version: 22
cache: "npm"

View File

@@ -23,7 +23,7 @@ jobs:
steps:
- name: "Install uv"
uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5.4.2
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
- uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e # v4.2.1
with:
pattern: wheels-*
path: wheels

View File

@@ -45,7 +45,7 @@ jobs:
jq '.name="@astral-sh/ruff-wasm-${{ matrix.target }}"' crates/ruff_wasm/pkg/package.json > /tmp/package.json
mv /tmp/package.json crates/ruff_wasm/pkg
- run: cp LICENSE crates/ruff_wasm/pkg # wasm-pack does not put the LICENSE file in the pkg
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
- uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4.3.0
with:
node-version: 20
registry-url: "https://registry.npmjs.org"

View File

@@ -40,7 +40,6 @@ permissions:
# If there's a prerelease-style suffix to the version, then the release(s)
# will be marked as a prerelease.
on:
pull_request:
workflow_dispatch:
inputs:
tag:
@@ -61,7 +60,7 @@ jobs:
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@85e6279cec87321a52edac9c87bce653a07cf6c2
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
persist-credentials: false
submodules: recursive
@@ -69,9 +68,9 @@ jobs:
# we specify bash to get pipefail; it guards against the `curl` command
# failing. otherwise `sh` won't catch that `curl` returned non-0
shell: bash
run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/astral-sh/cargo-dist/releases/download/v0.28.5-prerelease.1/cargo-dist-installer.sh | sh"
run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/astral-sh/cargo-dist/releases/download/v0.28.4-prerelease.1/cargo-dist-installer.sh | sh"
- name: Cache dist
uses: actions/upload-artifact@6027e3dd177782cd8ab9af838c04fd81a07f1d47
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
with:
name: cargo-dist-cache
path: ~/.cargo/bin/dist
@@ -87,7 +86,7 @@ jobs:
cat plan-dist-manifest.json
echo "manifest=$(jq -c "." plan-dist-manifest.json)" >> "$GITHUB_OUTPUT"
- name: "Upload dist-manifest.json"
uses: actions/upload-artifact@6027e3dd177782cd8ab9af838c04fd81a07f1d47
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
with:
name: artifacts-plan-dist-manifest
path: plan-dist-manifest.json
@@ -124,19 +123,19 @@ jobs:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BUILD_MANIFEST_NAME: target/distrib/global-dist-manifest.json
steps:
- uses: actions/checkout@85e6279cec87321a52edac9c87bce653a07cf6c2
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
persist-credentials: false
submodules: recursive
- name: Install cached dist
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093
uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e
with:
name: cargo-dist-cache
path: ~/.cargo/bin/
- run: chmod +x ~/.cargo/bin/dist
# Get all the local artifacts for the global tasks to use (for e.g. checksums)
- name: Fetch local artifacts
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093
uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e
with:
pattern: artifacts-*
path: target/distrib/
@@ -154,7 +153,7 @@ jobs:
cp dist-manifest.json "$BUILD_MANIFEST_NAME"
- name: "Upload artifacts"
uses: actions/upload-artifact@6027e3dd177782cd8ab9af838c04fd81a07f1d47
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
with:
name: artifacts-build-global
path: |
@@ -175,19 +174,19 @@ jobs:
outputs:
val: ${{ steps.host.outputs.manifest }}
steps:
- uses: actions/checkout@85e6279cec87321a52edac9c87bce653a07cf6c2
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
persist-credentials: false
submodules: recursive
- name: Install cached dist
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093
uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e
with:
name: cargo-dist-cache
path: ~/.cargo/bin/
- run: chmod +x ~/.cargo/bin/dist
# Fetch artifacts from scratch-storage
- name: Fetch artifacts
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093
uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e
with:
pattern: artifacts-*
path: target/distrib/
@@ -201,7 +200,7 @@ jobs:
cat dist-manifest.json
echo "manifest=$(jq -c "." dist-manifest.json)" >> "$GITHUB_OUTPUT"
- name: "Upload dist-manifest.json"
uses: actions/upload-artifact@6027e3dd177782cd8ab9af838c04fd81a07f1d47
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
with:
# Overwrite the previous copy
name: artifacts-dist-manifest
@@ -251,13 +250,13 @@ jobs:
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@85e6279cec87321a52edac9c87bce653a07cf6c2
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
persist-credentials: false
submodules: recursive
# Create a GitHub Release while uploading all files to it
- name: "Download GitHub Artifacts"
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093
uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e
with:
pattern: artifacts-*
path: artifacts

View File

@@ -39,13 +39,13 @@ jobs:
- name: Sync typeshed
id: sync
run: |
rm -rf ruff/crates/ty_vendored/vendor/typeshed
mkdir ruff/crates/ty_vendored/vendor/typeshed
cp typeshed/README.md ruff/crates/ty_vendored/vendor/typeshed
cp typeshed/LICENSE ruff/crates/ty_vendored/vendor/typeshed
cp -r typeshed/stdlib ruff/crates/ty_vendored/vendor/typeshed/stdlib
rm -rf ruff/crates/ty_vendored/vendor/typeshed/stdlib/@tests
git -C typeshed rev-parse HEAD > ruff/crates/ty_vendored/vendor/typeshed/source_commit.txt
rm -rf ruff/crates/red_knot_vendored/vendor/typeshed
mkdir ruff/crates/red_knot_vendored/vendor/typeshed
cp typeshed/README.md ruff/crates/red_knot_vendored/vendor/typeshed
cp typeshed/LICENSE ruff/crates/red_knot_vendored/vendor/typeshed
cp -r typeshed/stdlib ruff/crates/red_knot_vendored/vendor/typeshed/stdlib
rm -rf ruff/crates/red_knot_vendored/vendor/typeshed/stdlib/@tests
git -C typeshed rev-parse HEAD > ruff/crates/red_knot_vendored/vendor/typeshed/source_commit.txt
- name: Commit the changes
id: commit
if: ${{ steps.sync.outcome == 'success' }}
@@ -79,5 +79,5 @@ jobs:
repo: "ruff",
title: `Automated typeshed sync failed on ${new Date().toDateString()}`,
body: "Run listed here: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}",
labels: ["bug", "ty"],
labels: ["bug", "red-knot"],
})

View File

@@ -3,8 +3,8 @@ fail_fast: false
exclude: |
(?x)^(
.github/workflows/release.yml|
crates/ty_vendored/vendor/.*|
crates/ty_project/resources/.*|
crates/red_knot_vendored/vendor/.*|
crates/red_knot_project/resources/.*|
crates/ruff_benchmark/resources/.*|
crates/ruff_linter/resources/.*|
crates/ruff_linter/src/rules/.*/snapshots/.*|
@@ -65,7 +65,7 @@ repos:
- black==25.1.0
- repo: https://github.com/crate-ci/typos
rev: v1.32.0
rev: v1.31.1
hooks:
- id: typos
@@ -79,7 +79,7 @@ repos:
pass_filenames: false # This makes it a lot faster
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.11.8
rev: v0.11.6
hooks:
- id: ruff-format
- id: ruff

View File

@@ -1,66 +1,5 @@
# Changelog
## 0.11.8
### Preview features
- \[`airflow`\] Apply auto fixes to cases where the names have changed in Airflow 3 (`AIR302`, `AIR311`) ([#17553](https://github.com/astral-sh/ruff/pull/17553), [#17570](https://github.com/astral-sh/ruff/pull/17570), [#17571](https://github.com/astral-sh/ruff/pull/17571))
- \[`airflow`\] Extend `AIR301` rule ([#17598](https://github.com/astral-sh/ruff/pull/17598))
- \[`airflow`\] Update existing `AIR302` rules with better suggestions ([#17542](https://github.com/astral-sh/ruff/pull/17542))
- \[`refurb`\] Mark fix as safe for `readlines-in-for` (`FURB129`) ([#17644](https://github.com/astral-sh/ruff/pull/17644))
- [syntax-errors] `nonlocal` declaration at module level ([#17559](https://github.com/astral-sh/ruff/pull/17559))
- [syntax-errors] Detect single starred expression assignment `x = *y` ([#17624](https://github.com/astral-sh/ruff/pull/17624))
### Bug fixes
- \[`flake8-pyi`\] Ensure `Literal[None,] | Literal[None,]` is not autofixed to `None | None` (`PYI061`) ([#17659](https://github.com/astral-sh/ruff/pull/17659))
- \[`flake8-use-pathlib`\] Avoid suggesting `Path.iterdir()` for `os.listdir` with file descriptor (`PTH208`) ([#17715](https://github.com/astral-sh/ruff/pull/17715))
- \[`flake8-use-pathlib`\] Fix `PTH104` false positive when `rename` is passed a file descriptor ([#17712](https://github.com/astral-sh/ruff/pull/17712))
- \[`flake8-use-pathlib`\] Fix `PTH116` false positive when `stat` is passed a file descriptor ([#17709](https://github.com/astral-sh/ruff/pull/17709))
- \[`flake8-use-pathlib`\] Fix `PTH123` false positive when `open` is passed a file descriptor from a function call ([#17705](https://github.com/astral-sh/ruff/pull/17705))
- \[`pycodestyle`\] Fix duplicated diagnostic in `E712` ([#17651](https://github.com/astral-sh/ruff/pull/17651))
- \[`pylint`\] Detect `global` declarations in module scope (`PLE0118`) ([#17411](https://github.com/astral-sh/ruff/pull/17411))
- [syntax-errors] Make `async-comprehension-in-sync-comprehension` more specific ([#17460](https://github.com/astral-sh/ruff/pull/17460))
### Configuration
- Add option to disable `typing_extensions` imports ([#17611](https://github.com/astral-sh/ruff/pull/17611))
### Documentation
- Fix example syntax for the `lint.pydocstyle.ignore-var-parameters` option ([#17740](https://github.com/astral-sh/ruff/pull/17740))
- Add fix safety sections (`ASYNC116`, `FLY002`, `D200`, `RUF005`, `RUF017`, `RUF027`, `RUF028`, `RUF057`) ([#17497](https://github.com/astral-sh/ruff/pull/17497), [#17496](https://github.com/astral-sh/ruff/pull/17496), [#17502](https://github.com/astral-sh/ruff/pull/17502), [#17484](https://github.com/astral-sh/ruff/pull/17484), [#17480](https://github.com/astral-sh/ruff/pull/17480), [#17485](https://github.com/astral-sh/ruff/pull/17485), [#17722](https://github.com/astral-sh/ruff/pull/17722), [#17483](https://github.com/astral-sh/ruff/pull/17483))
### Other changes
- Add Python 3.14 to configuration options ([#17647](https://github.com/astral-sh/ruff/pull/17647))
- Make syntax error for unparenthesized except tuples version specific to before 3.14 ([#17660](https://github.com/astral-sh/ruff/pull/17660))
## 0.11.7
### Preview features
- \[`airflow`\] Apply auto fixes to cases where the names have changed in Airflow 3 (`AIR301`) ([#17355](https://github.com/astral-sh/ruff/pull/17355))
- \[`perflint`\] Implement fix for `manual-dict-comprehension` (`PERF403`) ([#16719](https://github.com/astral-sh/ruff/pull/16719))
- [syntax-errors] Make duplicate parameter names a semantic error ([#17131](https://github.com/astral-sh/ruff/pull/17131))
### Bug fixes
- \[`airflow`\] Fix typos in provider package names (`AIR302`, `AIR312`) ([#17574](https://github.com/astral-sh/ruff/pull/17574))
- \[`flake8-type-checking`\] Visit keyword arguments in checks involving `typing.cast`/`typing.NewType` arguments ([#17538](https://github.com/astral-sh/ruff/pull/17538))
- \[`pyupgrade`\] Preserve parenthesis when fixing native literals containing newlines (`UP018`) ([#17220](https://github.com/astral-sh/ruff/pull/17220))
- \[`refurb`\] Mark the `FURB161` fix unsafe except for integers and booleans ([#17240](https://github.com/astral-sh/ruff/pull/17240))
### Rule changes
- \[`perflint`\] Allow list function calls to be replaced with a comprehension (`PERF401`) ([#17519](https://github.com/astral-sh/ruff/pull/17519))
- \[`pycodestyle`\] Auto-fix redundant boolean comparison (`E712`) ([#17090](https://github.com/astral-sh/ruff/pull/17090))
- \[`pylint`\] make fix unsafe if delete comments (`PLR1730`) ([#17459](https://github.com/astral-sh/ruff/pull/17459))
### Documentation
- Add fix safety sections to docs for several rules ([#17410](https://github.com/astral-sh/ruff/pull/17410),[#17440](https://github.com/astral-sh/ruff/pull/17440),[#17441](https://github.com/astral-sh/ruff/pull/17441),[#17443](https://github.com/astral-sh/ruff/pull/17443),[#17444](https://github.com/astral-sh/ruff/pull/17444))
## 0.11.6
### Preview features

View File

@@ -71,7 +71,8 @@ representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at <hey@astral.sh>.
reported to the community leaders responsible for enforcement at
<charlie.r.marsh@gmail.com>.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the

View File

@@ -366,15 +366,6 @@ uvx --from ./python/ruff-ecosystem ruff-ecosystem format ruff "./target/debug/ru
See the [ruff-ecosystem package](https://github.com/astral-sh/ruff/tree/main/python/ruff-ecosystem) for more details.
## Upgrading Rust
1. Change the `channel` in `./rust-toolchain.toml` to the new Rust version (`<latest>`)
1. Change the `rust-version` in the `./Cargo.toml` to `<latest> - 2` (e.g. 1.84 if the latest is 1.86)
1. Run `cargo clippy --fix --allow-dirty --allow-staged` to fix new clippy warnings
1. Create and merge the PR
1. Bump the Rust version in Ruff's conda forge recipe. See [this PR](https://github.com/conda-forge/ruff-feedstock/pull/266) for an example.
1. Enjoy the new Rust version!
## Benchmarking and Profiling
We have several ways of benchmarking and profiling Ruff:

603
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -35,14 +35,14 @@ ruff_python_trivia = { path = "crates/ruff_python_trivia" }
ruff_server = { path = "crates/ruff_server" }
ruff_source_file = { path = "crates/ruff_source_file" }
ruff_text_size = { path = "crates/ruff_text_size" }
red_knot_vendored = { path = "crates/red_knot_vendored" }
ruff_workspace = { path = "crates/ruff_workspace" }
ty_ide = { path = "crates/ty_ide" }
ty_project = { path = "crates/ty_project", default-features = false }
ty_python_semantic = { path = "crates/ty_python_semantic" }
ty_server = { path = "crates/ty_server" }
ty_test = { path = "crates/ty_test" }
ty_vendored = { path = "crates/ty_vendored" }
red_knot_ide = { path = "crates/red_knot_ide" }
red_knot_project = { path = "crates/red_knot_project", default-features = false }
red_knot_python_semantic = { path = "crates/red_knot_python_semantic" }
red_knot_server = { path = "crates/red_knot_server" }
red_knot_test = { path = "crates/red_knot_test" }
aho-corasick = { version = "1.1.3" }
anstream = { version = "0.6.18" }
@@ -124,7 +124,7 @@ rayon = { version = "1.10.0" }
regex = { version = "1.10.2" }
rustc-hash = { version = "2.0.0" }
# When updating salsa, make sure to also update the revision in `fuzz/Cargo.toml`
salsa = { git = "https://github.com/salsa-rs/salsa.git", rev = "ef93d3830ffb8dc621fef1ba7b234ccef9dc3639" }
salsa = { git = "https://github.com/salsa-rs/salsa.git", rev = "87bf6b6c2d5f6479741271da73bd9d30c2580c26" }
schemars = { version = "0.8.16" }
seahash = { version = "4.1.0" }
serde = { version = "1.0.197", features = ["derive"] }
@@ -268,3 +268,73 @@ debug = 1
# The profile that 'cargo dist' will build with.
[profile.dist]
inherits = "release"
# Config for 'dist'
[workspace.metadata.dist]
# The preferred dist version to use in CI (Cargo.toml SemVer syntax)
cargo-dist-version = "0.28.4-prerelease.1"
# CI backends to support
ci = "github"
# The installers to generate for each app
installers = ["shell", "powershell"]
# The archive format to use for windows builds (defaults .zip)
windows-archive = ".zip"
# The archive format to use for non-windows builds (defaults .tar.xz)
unix-archive = ".tar.gz"
# Target platforms to build apps for (Rust target-triple syntax)
targets = [
"aarch64-apple-darwin",
"aarch64-pc-windows-msvc",
"aarch64-unknown-linux-gnu",
"aarch64-unknown-linux-musl",
"arm-unknown-linux-musleabihf",
"armv7-unknown-linux-gnueabihf",
"armv7-unknown-linux-musleabihf",
"i686-pc-windows-msvc",
"i686-unknown-linux-gnu",
"i686-unknown-linux-musl",
"powerpc64-unknown-linux-gnu",
"powerpc64le-unknown-linux-gnu",
"s390x-unknown-linux-gnu",
"x86_64-apple-darwin",
"x86_64-pc-windows-msvc",
"x86_64-unknown-linux-gnu",
"x86_64-unknown-linux-musl",
]
# Whether to auto-include files like READMEs, LICENSEs, and CHANGELOGs (default true)
auto-includes = false
# Whether dist should create a Github Release or use an existing draft
create-release = true
# Which actions to run on pull requests
pr-run-mode = "skip"
# Whether CI should trigger releases with dispatches instead of tag pushes
dispatch-releases = true
# Which phase dist should use to create the GitHub release
github-release = "announce"
# Whether CI should include auto-generated code to build local artifacts
build-local-artifacts = false
# Local artifacts jobs to run in CI
local-artifacts-jobs = ["./build-binaries", "./build-docker"]
# Publish jobs to run in CI
publish-jobs = ["./publish-pypi", "./publish-wasm"]
# Post-announce jobs to run in CI
post-announce-jobs = [
"./notify-dependents",
"./publish-docs",
"./publish-playground",
]
# Custom permissions for GitHub Jobs
github-custom-job-permissions = { "build-docker" = { packages = "write", contents = "read" }, "publish-wasm" = { contents = "read", id-token = "write", packages = "write" } }
# Whether to install an updater program
install-updater = false
# Path that installers should place binaries in
install-path = ["$XDG_BIN_HOME/", "$XDG_DATA_HOME/../bin", "~/.local/bin"]
[workspace.metadata.dist.github-custom-runners]
global = "depot-ubuntu-latest-4"
[workspace.metadata.dist.github-action-commits]
"actions/checkout" = "11bd71901bbe5b1630ceea73d27597364c9af683" # v4
"actions/upload-artifact" = "ea165f8d65b6e75b540449e92b4886f43607fa02" # v4.6.2
"actions/download-artifact" = "95815c38cf2ff2164869cbab79da8d1f422bc89e" # v4.2.1
"actions/attest-build-provenance" = "c074443f1aee8d4aeeae555aebba3282517141b2" #v2.2.3

View File

@@ -149,8 +149,8 @@ curl -LsSf https://astral.sh/ruff/install.sh | sh
powershell -c "irm https://astral.sh/ruff/install.ps1 | iex"
# For a specific version.
curl -LsSf https://astral.sh/ruff/0.11.8/install.sh | sh
powershell -c "irm https://astral.sh/ruff/0.11.8/install.ps1 | iex"
curl -LsSf https://astral.sh/ruff/0.11.6/install.sh | sh
powershell -c "irm https://astral.sh/ruff/0.11.6/install.ps1 | iex"
```
You can also install Ruff via [Homebrew](https://formulae.brew.sh/formula/ruff), [Conda](https://anaconda.org/conda-forge/ruff),
@@ -183,7 +183,7 @@ Ruff can also be used as a [pre-commit](https://pre-commit.com/) hook via [`ruff
```yaml
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.11.8
rev: v0.11.6
hooks:
# Run the linter.
- id: ruff

View File

@@ -1,7 +1,7 @@
[files]
# https://github.com/crate-ci/typos/issues/868
extend-exclude = [
"crates/ty_vendored/vendor/**/*",
"crates/red_knot_vendored/vendor/**/*",
"**/resources/**/*",
"**/snapshots/**/*",
]

View File

@@ -1,28 +1,26 @@
[package]
name = "ty"
name = "red_knot"
version = "0.0.0"
edition.workspace = true
rust-version.workspace = true
homepage.workspace = true
documentation.workspace = true
# Releases occur in this other repository!
repository = "https://github.com/astral-sh/ty/"
repository.workspace = true
authors.workspace = true
license.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
red_knot_python_semantic = { workspace = true }
red_knot_project = { workspace = true, features = ["zstd"] }
red_knot_server = { workspace = true }
ruff_db = { workspace = true, features = ["os", "cache"] }
ruff_python_ast = { workspace = true }
ty_python_semantic = { workspace = true }
ty_project = { workspace = true, features = ["zstd"] }
ty_server = { workspace = true }
anyhow = { workspace = true }
argfile = { workspace = true }
clap = { workspace = true, features = ["wrap_help", "string"] }
clap_complete_command = { workspace = true }
clap = { workspace = true, features = ["wrap_help"] }
colored = { workspace = true }
countme = { workspace = true, features = ["enable"] }
crossbeam = { workspace = true }

25
crates/red_knot/README.md Normal file
View File

@@ -0,0 +1,25 @@
# Red Knot
Red Knot is an extremely fast type checker.
Currently, it is a work-in-progress and not ready for user testing.
Red Knot is designed to prioritize good type inference, even in unannotated code,
and aims to avoid false positives.
While Red Knot will produce similar results to mypy and pyright on many codebases,
100% compatibility with these tools is a non-goal.
On some codebases, Red Knot's design decisions lead to different outcomes
than you would get from running one of these more established tools.
## Contributing
Core type checking tests are written as Markdown code blocks.
They can be found in [`red_knot_python_semantic/resources/mdtest`][resources-mdtest].
See [`red_knot_test/README.md`][mdtest-readme] for more information
on the test framework itself.
The list of open issues can be found [here][open-issues].
[mdtest-readme]: ../red_knot_test/README.md
[open-issues]: https://github.com/astral-sh/ruff/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20label%3Ared-knot
[resources-mdtest]: ../red_knot_python_semantic/resources/mdtest

View File

@@ -8,43 +8,16 @@ fn main() {
// The workspace root directory is not available without walking up the tree
// https://github.com/rust-lang/cargo/issues/3946
let workspace_root = Path::new(&std::env::var("CARGO_MANIFEST_DIR").unwrap())
.join("..")
.join("..")
.join("..");
version_info(&workspace_root);
commit_info(&workspace_root);
#[allow(clippy::disallowed_methods)]
let target = std::env::var("TARGET").unwrap();
println!("cargo::rustc-env=RUST_HOST_TARGET={target}");
}
/// Retrieve the version from the `dist-workspace.toml` file and set `TY_VERSION`.
fn version_info(workspace_root: &Path) {
let dist_file = workspace_root.join("dist-workspace.toml");
if !dist_file.exists() {
return;
}
println!("cargo:rerun-if-changed={}", dist_file.display());
let dist_file = fs::read_to_string(dist_file);
if let Ok(dist_file) = dist_file {
let lines = dist_file.lines();
for line in lines {
if line.starts_with("version =") {
let (_key, version) = line.split_once('=').unwrap();
println!(
"cargo::rustc-env=TY_VERSION={}",
version.trim().trim_matches('"')
);
break;
}
}
}
}
/// Retrieve commit information from the Git repository.
fn commit_info(workspace_root: &Path) {
// If not in a git repository, do not attempt to retrieve commit information
let git_dir = workspace_root.join(".git");
@@ -76,8 +49,7 @@ fn commit_info(workspace_root: &Path) {
.arg("-1")
.arg("--date=short")
.arg("--abbrev=9")
.arg("--format=%H %h %cd %(describe:tags)")
.current_dir(workspace_root)
.arg("--format=%H %h %cd %(describe)")
.output()
{
Ok(output) if output.status.success() => output,
@@ -87,20 +59,18 @@ fn commit_info(workspace_root: &Path) {
let mut parts = stdout.split_whitespace();
let mut next = || parts.next().unwrap();
let _commit_hash = next();
println!("cargo::rustc-env=TY_COMMIT_SHORT_HASH={}", next());
println!("cargo::rustc-env=TY_COMMIT_DATE={}", next());
println!("cargo::rustc-env=RED_KNOT_COMMIT_SHORT_HASH={}", next());
println!("cargo::rustc-env=RED_KNOT_COMMIT_DATE={}", next());
// Describe can fail for some commits
// https://git-scm.com/docs/pretty-formats#Documentation/pretty-formats.txt-emdescribeoptionsem
if let Some(describe) = parts.next() {
let mut describe_parts = describe.split('-');
let last_tag = describe_parts.next().unwrap();
println!("cargo::rustc-env=TY_LAST_TAG={last_tag}");
let _last_tag = describe_parts.next().unwrap();
// If this is the tagged commit, this component will be missing
println!(
"cargo::rustc-env=TY_LAST_TAG_DISTANCE={}",
"cargo::rustc-env=RED_KNOT_LAST_TAG_DISTANCE={}",
describe_parts.next().unwrap_or("0")
);
}

View File

@@ -2,16 +2,16 @@
## Basics
`mypy_primer` can be run using `uvx --from "…" mypy_primer`. For example, to see the help message, run:
For now, we use our own [fork of mypy primer]. It can be run using `uvx --from "…" mypy_primer`. For example, to see the help message, run:
```sh
uvx --from "git+https://github.com/hauntsaninja/mypy_primer" mypy_primer -h
uvx --from "git+https://github.com/astral-sh/mypy_primer.git@add-red-knot-support" mypy_primer -h
```
Alternatively, you can install the forked version of `mypy_primer` using:
```sh
uv tool install "git+https://github.com/hauntsaninja/mypy_primer"
uv tool install "git+https://github.com/astral-sh/mypy_primer.git@add-red-knot-support"
```
and then run it using `uvx mypy_primer` or just `mypy_primer`, if your `PATH` is set up accordingly (see: [Tool executables]).
@@ -22,7 +22,7 @@ To show the diagnostics diff between two Git revisions (e.g. your feature branch
```sh
mypy_primer \
--type-checker ty \
--type-checker knot \
--old origin/main \
--new my/feature \
--debug \
@@ -31,15 +31,15 @@ mypy_primer \
```
This will show the diagnostics diff for the `black` project between the `main` branch and your `my/feature` branch. To run the
diff for all projects we currently enable in CI, use `--project-selector "/($(paste -s -d'|' crates/ty_python_semantic/resources/primer/good.txt))\$"`.
diff for all projects we currently enable in CI, use `--project-selector "/($(paste -s -d'|' crates/red_knot_python_semantic/resources/primer/good.txt))\$"`.
You can also take a look at the [full list of ecosystem projects]. Note that some of them might still need a `ty_paths` configuration
You can also take a look at the [full list of ecosystem projects]. Note that some of them might still need a `knot_paths` configuration
option to work correctly.
## Avoiding recompilation
If you want to run `mypy_primer` repeatedly, e.g. for different projects, but for the same combination of `--old` and `--new`, you
can use set the `MYPY_PRIMER_NO_REBUILD` environment variable to avoid recompilation of ty:
can use set the `MYPY_PRIMER_NO_REBUILD` environment variable to avoid recompilation of Red Knot:
```sh
MYPY_PRIMER_NO_REBUILD=1 mypy_primer …
@@ -56,5 +56,6 @@ mypy_primer --repo /path/to/ruff --old origin/main --new my/local-branch …
Note that you might need to clean up `/tmp/mypy_primer` in order for this to work correctly.
[full list of ecosystem projects]: https://github.com/hauntsaninja/mypy_primer/blob/master/mypy_primer/projects.py
[fork of mypy primer]: https://github.com/astral-sh/mypy_primer/tree/add-red-knot-support
[full list of ecosystem projects]: https://github.com/astral-sh/mypy_primer/blob/add-red-knot-support/mypy_primer/projects.py
[tool executables]: https://docs.astral.sh/uv/concepts/tools/#tool-executables

View File

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 40 KiB

View File

@@ -1,7 +1,6 @@
# Tracing
Traces are a useful tool to narrow down the location of a bug or, at least, to understand why the compiler is doing a
particular thing.
Traces are a useful tool to narrow down the location of a bug or, at least, to understand why the compiler is doing a particular thing.
Note, tracing messages with severity `debug` or greater are user-facing. They should be phrased accordingly.
Tracing spans are only shown when using `-vvv`.
@@ -10,28 +9,20 @@ Tracing spans are only shown when using `-vvv`.
The CLI supports different verbosity levels.
- default: Only show errors and warnings.
- `-v` activates `info!`: Show generally useful information such as paths of configuration files, detected platform,
etc., but it's not a lot of messages, it's something you'll activate in CI by default. cargo build e.g. shows you
which packages are fresh.
- `-vv` activates `debug!` and timestamps: This should be enough information to get to the bottom of bug reports. When
you're processing many packages or files, you'll get pages and pages of output, but each line is link to a specific
action or state change.
- `-vvv` activates `trace!` (only in debug builds) and shows tracing-spans: At this level, you're logging everything.
Most of this is wasted, it's really slow, we dump e.g. the entire resolution graph. Only useful to developers, and you
almost certainly want to use `TY_LOG` to filter it down to the area your investigating.
- `-v` activates `info!`: Show generally useful information such as paths of configuration files, detected platform, etc., but it's not a lot of messages, it's something you'll activate in CI by default. cargo build e.g. shows you which packages are fresh.
- `-vv` activates `debug!` and timestamps: This should be enough information to get to the bottom of bug reports. When you're processing many packages or files, you'll get pages and pages of output, but each line is link to a specific action or state change.
- `-vvv` activates `trace!` (only in debug builds) and shows tracing-spans: At this level, you're logging everything. Most of this is wasted, it's really slow, we dump e.g. the entire resolution graph. Only useful to developers, and you almost certainly want to use `RED_KNOT_LOG` to filter it down to the area your investigating.
## Better logging with `TY_LOG` and `TY_MAX_PARALLELISM`
## Better logging with `RED_KNOT_LOG` and `RAYON_NUM_THREADS`
By default, the CLI shows messages from the `ruff` and `ty` crates. Tracing messages from other crates are not shown.
The `TY_LOG` environment variable allows you to customize which messages are shown by specifying one
or
more [filter directives](https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html#directives).
By default, the CLI shows messages from the `ruff` and `red_knot` crates. Tracing messages from other crates are not shown.
The `RED_KNOT_LOG` environment variable allows you to customize which messages are shown by specifying one
or more [filter directives](https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html#directives).
The `TY_MAX_PARALLELISM` environment variable, meanwhile, can be used to control the level of parallelism ty uses.
By default, ty will attempt to parallelize its work so that multiple files are checked simultaneously,
but this can result in a confused logging output where messages from different threads are intertwined and non
determinism.
To switch off parallelism entirely and have more readable logs, use `TY_MAX_PARALLELISM=1` (or `RAYON_NUM_THREADS=1`).
The `RAYON_NUM_THREADS` environment variable, meanwhile, can be used to control the level of concurrency red-knot uses.
By default, red-knot will attempt to parallelize its work so that multiple files are checked simultaneously,
but this can result in a confused logging output where messages from different threads are intertwined.
To switch off concurrency entirely and have more readable logs, use `RAYON_NUM_THREADS=1`.
### Examples
@@ -40,23 +31,23 @@ To switch off parallelism entirely and have more readable logs, use `TY_MAX_PARA
Shows debug messages from all crates.
```bash
TY_LOG=debug
RED_KNOT_LOG=debug
```
#### Show salsa query execution messages
Show the salsa `execute: my_query` messages in addition to all ty messages.
Show the salsa `execute: my_query` messages in addition to all red knot messages.
```bash
TY_LOG=ruff=trace,ty=trace,salsa=info
RED_KNOT_LOG=ruff=trace,red_knot=trace,salsa=info
```
#### Show typing traces
Only show traces for the `ty_python_semantic::types` module.
Only show traces for the `red_knot_python_semantic::types` module.
```bash
TY_LOG="ty_python_semantic::types"
RED_KNOT_LOG="red_knot_python_semantic::types"
```
Note: Ensure that you use `-vvv` to see tracing spans.
@@ -66,7 +57,7 @@ Note: Ensure that you use `-vvv` to see tracing spans.
Shows all messages that are inside of a span for a specific file.
```bash
TY_LOG=ty[{file=/home/micha/astral/test/x.py}]=trace
RED_KNOT_LOG=red_knot[{file=/home/micha/astral/test/x.py}]=trace
```
**Note**: Tracing still shows all spans because tracing can't know at the time of entering the span
@@ -88,24 +79,22 @@ query to return the failure as part of the query's result or use a Salsa accumul
## Tracing in tests
You can use `ruff_db::testing::setup_logging` or `ruff_db::testing::setup_logging_with_filter` to set up logging in
tests.
You can use `ruff_db::testing::setup_logging` or `ruff_db::testing::setup_logging_with_filter` to set up logging in tests.
```rust
use ruff_db::testing::setup_logging;
#[test]
fn test() {
let _logging = setup_logging();
let _logging = setup_logging();
tracing::info!("This message will be printed to stderr");
tracing::info!("This message will be printed to stderr");
}
```
Note: Most test runners capture stderr and only show its output when a test fails.
Note also that `setup_logging` only sets up logging for the current thread because
[`set_global_default`](https://docs.rs/tracing/latest/tracing/subscriber/fn.set_global_default.html) can only be
Note also that `setup_logging` only sets up logging for the current thread because [`set_global_default`](https://docs.rs/tracing/latest/tracing/subscriber/fn.set_global_default.html) can only be
called **once**.
## Release builds
@@ -114,11 +103,10 @@ called **once**.
## Profiling
ty generates a folded stack trace to the current directory named `tracing.folded` when setting the environment variable
`TY_LOG_PROFILE` to `1` or `true`.
Red Knot generates a folded stack trace to the current directory named `tracing.folded` when setting the environment variable `RED_KNOT_LOG_PROFILE` to `1` or `true`.
```bash
TY_LOG_PROFILE=1 ty -- --current-directory=../test -vvv
RED_KNOT_LOG_PROFILE=1 red_knot -- --current-directory=../test -vvv
```
You can convert the textual representation into a visual one using `inferno`.

View File

@@ -1,14 +1,18 @@
use crate::logging::Verbosity;
use crate::python_version::PythonVersion;
use clap::{ArgAction, ArgMatches, Error, Parser};
use red_knot_project::metadata::options::{EnvironmentOptions, Options, TerminalOptions};
use red_knot_project::metadata::value::{RangedValue, RelativePathBuf};
use red_knot_python_semantic::lint;
use ruff_db::system::SystemPathBuf;
use ty_project::metadata::options::{EnvironmentOptions, Options, TerminalOptions};
use ty_project::metadata::value::{RangedValue, RelativePathBuf};
use ty_python_semantic::lint;
#[derive(Debug, Parser)]
#[command(author, name = "ty", about = "An extremely fast Python type checker.")]
#[command(long_version = crate::version::version())]
#[command(
author,
name = "red-knot",
about = "An extremely fast Python type checker."
)]
#[command(version)]
pub(crate) struct Args {
#[command(subcommand)]
pub(crate) command: Command,
@@ -22,12 +26,8 @@ pub(crate) enum Command {
/// Start the language server
Server,
/// Display ty's version
/// Display Red Knot's version
Version,
/// Generate shell completion
#[clap(hide = true)]
GenerateShellCompletion { shell: clap_complete_command::Shell },
}
#[derive(Debug, Parser)]
@@ -48,11 +48,11 @@ pub(crate) struct CheckCommand {
#[arg(long, value_name = "PROJECT")]
pub(crate) project: Option<SystemPathBuf>,
/// Path to the Python installation from which ty resolves type information and third-party dependencies.
/// Path to the Python installation from which Red Knot resolves type information and third-party dependencies.
///
/// If not specified, ty will look at the `VIRTUAL_ENV` environment variable.
/// If not specified, Red Knot will look at the `VIRTUAL_ENV` environment variable.
///
/// ty will search in the path's `site-packages` directories for type information and
/// Red Knot will search in the path's `site-packages` directories for type information and
/// third-party imports.
///
/// This option is commonly used to specify the path to a virtual environment.
@@ -105,19 +105,6 @@ pub(crate) struct CheckCommand {
/// Watch files for changes and recheck files related to the changed files.
#[arg(long, short = 'W')]
pub(crate) watch: bool,
/// Respect file exclusions via `.gitignore` and other standard ignore files.
/// Use `--no-respect-gitignore` to disable.
#[arg(
long,
overrides_with("no_respect_ignore_files"),
help_heading = "File selection",
default_missing_value = "true",
num_args = 0..1
)]
respect_ignore_files: Option<bool>,
#[clap(long, overrides_with("respect_ignore_files"), hide = true)]
no_respect_ignore_files: bool,
}
impl CheckCommand {
@@ -133,13 +120,6 @@ impl CheckCommand {
)
};
// --no-respect-gitignore defaults to false and is set true by CLI flag. If passed, override config file
// Otherwise, only pass this through if explicitly set (don't default to anything here to
// make sure that doesn't take precedence over an explicitly-set config file value)
let respect_ignore_files = self
.no_respect_ignore_files
.then_some(false)
.or(self.respect_ignore_files);
Options {
environment: Some(EnvironmentOptions {
python_version: self
@@ -164,7 +144,6 @@ impl CheckCommand {
error_on_warning: self.error_on_warning,
}),
rules,
respect_ignore_files,
..Default::default()
}
}

View File

@@ -1,4 +1,4 @@
//! Sets up logging for ty
//! Sets up logging for Red Knot
use anyhow::Context;
use colored::Colorize;
@@ -42,14 +42,14 @@ impl Verbosity {
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
pub(crate) enum VerbosityLevel {
/// Default output level. Only shows Ruff and ty events up to the [`WARN`](tracing::Level::WARN).
/// Default output level. Only shows Ruff and Red Knot events up to the [`WARN`](tracing::Level::WARN).
Default,
/// Enables verbose output. Emits Ruff and ty events up to the [`INFO`](tracing::Level::INFO).
/// Enables verbose output. Emits Ruff and Red Knot events up to the [`INFO`](tracing::Level::INFO).
/// Corresponds to `-v`.
Verbose,
/// Enables a more verbose tracing format and emits Ruff and ty events up to [`DEBUG`](tracing::Level::DEBUG).
/// Enables a more verbose tracing format and emits Ruff and Red Knot events up to [`DEBUG`](tracing::Level::DEBUG).
/// Corresponds to `-vv`
ExtraVerbose,
@@ -79,11 +79,11 @@ impl VerbosityLevel {
pub(crate) fn setup_tracing(level: VerbosityLevel) -> anyhow::Result<TracingGuard> {
use tracing_subscriber::prelude::*;
// The `TY_LOG` environment variable overrides the default log level.
let filter = if let Ok(log_env_variable) = std::env::var("TY_LOG") {
// The `RED_KNOT_LOG` environment variable overrides the default log level.
let filter = if let Ok(log_env_variable) = std::env::var("RED_KNOT_LOG") {
EnvFilter::builder()
.parse(log_env_variable)
.context("Failed to parse directives specified in TY_LOG environment variable.")?
.context("Failed to parse directives specified in RED_KNOT_LOG environment variable.")?
} else {
match level {
VerbosityLevel::Default => {
@@ -93,9 +93,9 @@ pub(crate) fn setup_tracing(level: VerbosityLevel) -> anyhow::Result<TracingGuar
level => {
let level_filter = level.level_filter();
// Show info|debug|trace events, but allow `TY_LOG` to override
// Show info|debug|trace events, but allow `RED_KNOT_LOG` to override
let filter = EnvFilter::default().add_directive(
format!("ty={level_filter}")
format!("red_knot={level_filter}")
.parse()
.expect("Hardcoded directive to be valid"),
);
@@ -131,7 +131,7 @@ pub(crate) fn setup_tracing(level: VerbosityLevel) -> anyhow::Result<TracingGuar
} else {
let subscriber = registry.with(
tracing_subscriber::fmt::layer()
.event_format(TyFormat {
.event_format(RedKnotFormat {
display_level: true,
display_timestamp: level.is_extra_verbose(),
show_spans: false,
@@ -147,7 +147,7 @@ pub(crate) fn setup_tracing(level: VerbosityLevel) -> anyhow::Result<TracingGuar
})
}
#[expect(clippy::type_complexity)]
#[allow(clippy::type_complexity)]
fn setup_profile<S>() -> (
Option<tracing_flame::FlameLayer<S, BufWriter<File>>>,
Option<tracing_flame::FlushGuard<BufWriter<File>>>,
@@ -155,7 +155,7 @@ fn setup_profile<S>() -> (
where
S: Subscriber + for<'span> LookupSpan<'span>,
{
if let Ok("1" | "true") = std::env::var("TY_LOG_PROFILE").as_deref() {
if let Ok("1" | "true") = std::env::var("RED_KNOT_LOG_PROFILE").as_deref() {
let (layer, guard) = tracing_flame::FlameLayer::with_file("tracing.folded")
.expect("Flame layer to be created");
(Some(layer), Some(guard))
@@ -168,14 +168,14 @@ pub(crate) struct TracingGuard {
_flame_guard: Option<tracing_flame::FlushGuard<BufWriter<File>>>,
}
struct TyFormat {
struct RedKnotFormat {
display_timestamp: bool,
display_level: bool,
show_spans: bool,
}
/// See <https://docs.rs/tracing-subscriber/0.3.18/src/tracing_subscriber/fmt/format/mod.rs.html#1026-1156>
impl<S, N> FormatEvent<S, N> for TyFormat
impl<S, N> FormatEvent<S, N> for RedKnotFormat
where
S: Subscriber + for<'a> LookupSpan<'a>,
N: for<'a> FormatFields<'a> + 'static,

View File

@@ -7,37 +7,34 @@ use std::sync::Mutex;
use crate::args::{Args, CheckCommand, Command, TerminalColor};
use crate::logging::setup_tracing;
use anyhow::{anyhow, Context};
use clap::{CommandFactory, Parser};
use clap::Parser;
use colored::Colorize;
use crossbeam::channel as crossbeam_channel;
use rayon::ThreadPoolBuilder;
use red_knot_project::metadata::options::Options;
use red_knot_project::watch::ProjectWatcher;
use red_knot_project::{watch, Db};
use red_knot_project::{ProjectDatabase, ProjectMetadata};
use red_knot_server::run_server;
use ruff_db::diagnostic::{Diagnostic, DisplayDiagnosticConfig, Severity};
use ruff_db::max_parallelism;
use ruff_db::system::{OsSystem, SystemPath, SystemPathBuf};
use salsa::plumbing::ZalsaDatabase;
use ty_project::metadata::options::Options;
use ty_project::watch::ProjectWatcher;
use ty_project::{watch, Db};
use ty_project::{ProjectDatabase, ProjectMetadata};
use ty_server::run_server;
mod args;
mod logging;
mod python_version;
mod version;
#[allow(clippy::print_stdout, clippy::unnecessary_wraps, clippy::print_stderr)]
pub fn main() -> ExitStatus {
setup_rayon();
run().unwrap_or_else(|error| {
use std::io::Write;
// Use `writeln` instead of `eprintln` to avoid panicking when the stderr pipe is broken.
let mut stderr = std::io::stderr().lock();
// This communicates that this isn't a linter error but ty itself hard-errored for
// This communicates that this isn't a linter error but Red Knot itself hard-errored for
// some reason (e.g. failed to resolve the configuration)
writeln!(stderr, "{}", "ty failed".red().bold()).ok();
writeln!(stderr, "{}", "Red Knot failed".red().bold()).ok();
// Currently we generally only see one error, but e.g. with io errors when resolving
// the configuration it is help to chain errors ("resolving configuration failed" ->
// "failed to read file: subdir/pyproject.toml")
@@ -68,17 +65,13 @@ fn run() -> anyhow::Result<ExitStatus> {
Command::Server => run_server().map(|()| ExitStatus::Success),
Command::Check(check_args) => run_check(check_args),
Command::Version => version().map(|()| ExitStatus::Success),
Command::GenerateShellCompletion { shell } => {
shell.generate(&mut Args::command(), &mut stdout());
Ok(ExitStatus::Success)
}
}
}
pub(crate) fn version() -> Result<()> {
let mut stdout = BufWriter::new(io::stdout().lock());
let version_info = crate::version::version();
writeln!(stdout, "ty {}", &version_info)?;
writeln!(stdout, "red knot {}", &version_info)?;
Ok(())
}
@@ -97,7 +90,7 @@ fn run_check(args: CheckCommand) -> anyhow::Result<ExitStatus> {
SystemPathBuf::from_path_buf(cwd)
.map_err(|path| {
anyhow!(
"The current working directory `{}` contains non-Unicode characters. ty only supports Unicode paths.",
"The current working directory `{}` contains non-Unicode characters. Red Knot only supports Unicode paths.",
path.display()
)
})?
@@ -176,12 +169,8 @@ pub enum ExitStatus {
/// Checking was successful but there were errors.
Failure = 1,
/// Checking failed due to an invocation error (e.g. the current directory no longer exists, incorrect CLI arguments, ...)
/// Checking failed.
Error = 2,
/// Internal ty error (panic, or any other error that isn't due to the user using the
/// program incorrectly or transient environment errors).
InternalError = 101,
}
impl Termination for ExitStatus {
@@ -257,16 +246,11 @@ impl MainLoop {
// Spawn a new task that checks the project. This needs to be done in a separate thread
// to prevent blocking the main loop here.
rayon::spawn(move || {
match db.check() {
Ok(result) => {
// Send the result back to the main loop for printing.
sender
.send(MainLoopMessage::CheckCompleted { result, revision })
.unwrap();
}
Err(cancelled) => {
tracing::debug!("Check has been cancelled: {cancelled:?}");
}
if let Ok(result) = db.check() {
// Send the result back to the main loop for printing.
sender
.send(MainLoopMessage::CheckCompleted { result, revision })
.unwrap();
}
});
}
@@ -280,6 +264,12 @@ impl MainLoop {
.format(terminal_settings.output_format)
.color(colored::control::SHOULD_COLORIZE.should_colorize());
let min_error_severity = if terminal_settings.error_on_warning {
Severity::Warning
} else {
Severity::Error
};
if check_revision == revision {
if db.project().files(db).is_empty() {
tracing::warn!("No python files found under the given path(s)");
@@ -294,13 +284,13 @@ impl MainLoop {
return Ok(ExitStatus::Success);
}
} else {
let mut max_severity = Severity::Info;
let mut failed = false;
let diagnostics_count = result.len();
for diagnostic in result {
write!(stdout, "{}", diagnostic.display(db, &display_config))?;
max_severity = max_severity.max(diagnostic.severity());
failed |= diagnostic.severity() >= min_error_severity;
}
writeln!(
@@ -310,22 +300,11 @@ impl MainLoop {
if diagnostics_count > 1 { "s" } else { "" }
)?;
if max_severity.is_fatal() {
tracing::warn!("A fatal error occurred while checking some files. Not all project files were analyzed. See the diagnostics list above for details.");
}
if self.watcher.is_none() {
return Ok(match max_severity {
Severity::Info => ExitStatus::Success,
Severity::Warning => {
if terminal_settings.error_on_warning {
ExitStatus::Failure
} else {
ExitStatus::Success
}
}
Severity::Error => ExitStatus::Failure,
Severity::Fatal => ExitStatus::InternalError,
return Ok(if failed {
ExitStatus::Failure
} else {
ExitStatus::Success
});
}
}
@@ -404,17 +383,3 @@ fn set_colored_override(color: Option<TerminalColor>) {
}
}
}
/// Initializes the global rayon thread pool to never use more than `TY_MAX_PARALLELISM` threads.
fn setup_rayon() {
ThreadPoolBuilder::default()
.num_threads(max_parallelism().get())
// Use a reasonably large stack size to avoid running into stack overflows too easily. The
// size was chosen in such a way as to still be able to handle large expressions involving
// binary operators (x + x + … + x) both during the AST walk in semantic index building as
// well as during type checking. Using this stack size, we can handle handle expressions
// that are several times larger than the corresponding limits in existing type checkers.
.stack_size(16 * 1024 * 1024)
.build_global()
.unwrap();
}

View File

@@ -1,17 +1,16 @@
//! Code for representing ty's release version number.
//! Code for representing Red Knot's release version number.
use std::fmt;
/// Information about the git repository where ty was built from.
/// Information about the git repository where Red Knot was built from.
pub(crate) struct CommitInfo {
short_commit_hash: String,
commit_date: String,
commits_since_last_tag: u32,
last_tag: Option<String>,
}
/// ty's version.
/// Red Knot's version.
pub(crate) struct VersionInfo {
/// ty's version, such as "0.5.1"
/// Red Knot's version, such as "0.5.1"
version: String,
/// Information about the git commit we may have been built from.
///
@@ -35,13 +34,7 @@ impl fmt::Display for VersionInfo {
}
}
impl From<VersionInfo> for clap::builder::Str {
fn from(val: VersionInfo) -> Self {
val.to_string().into()
}
}
/// Returns information about ty's version.
/// Returns information about Red Knot's version.
pub(crate) fn version() -> VersionInfo {
// Environment variables are only read at compile-time
macro_rules! option_env_str {
@@ -50,30 +43,18 @@ pub(crate) fn version() -> VersionInfo {
};
}
// Commit info is pulled from git and set by `build.rs`
let commit_info = option_env_str!("TY_COMMIT_SHORT_HASH").map(|short_commit_hash| CommitInfo {
short_commit_hash,
commit_date: option_env_str!("TY_COMMIT_DATE").unwrap(),
commits_since_last_tag: option_env_str!("TY_LAST_TAG_DISTANCE")
.as_deref()
.map_or(0, |value| value.parse::<u32>().unwrap_or(0)),
last_tag: option_env_str!("TY_LAST_TAG"),
});
// This version is pulled from Cargo.toml and set by Cargo
let version = option_env_str!("CARGO_PKG_VERSION").unwrap();
// The version is pulled from `dist-workspace.toml` and set by `build.rs`
let version = option_env_str!("TY_VERSION").unwrap_or_else(|| {
// If missing, using the last tag
commit_info
.as_ref()
.and_then(|info| {
info.last_tag.as_ref().map(|tag| {
tag.strip_prefix("v")
.map(std::string::ToString::to_string)
.unwrap_or(tag.clone())
})
})
.unwrap_or("unknown".to_string())
});
// Commit info is pulled from git and set by `build.rs`
let commit_info =
option_env_str!("RED_KNOT_COMMIT_SHORT_HASH").map(|short_commit_hash| CommitInfo {
short_commit_hash,
commit_date: option_env_str!("RED_KNOT_COMMIT_DATE").unwrap(),
commits_since_last_tag: option_env_str!("RED_KNOT_LAST_TAG_DISTANCE")
.as_deref()
.map_or(0, |value| value.parse::<u32>().unwrap_or(0)),
});
VersionInfo {
version,
@@ -104,7 +85,6 @@ mod tests {
short_commit_hash: "53b0f5d92".to_string(),
commit_date: "2023-10-19".to_string(),
commits_since_last_tag: 0,
last_tag: None,
}),
};
assert_snapshot!(version, @"0.0.0 (53b0f5d92 2023-10-19)");
@@ -118,7 +98,6 @@ mod tests {
short_commit_hash: "53b0f5d92".to_string(),
commit_date: "2023-10-19".to_string(),
commits_since_last_tag: 24,
last_tag: None,
}),
};
assert_snapshot!(version, @"0.0.0+24 (53b0f5d92 2023-10-19)");

View File

@@ -1,121 +1,10 @@
use anyhow::Context;
use insta::internals::SettingsBindDropGuard;
use insta_cmd::{assert_cmd_snapshot, get_cargo_bin};
use std::fmt::Write;
use std::path::{Path, PathBuf};
use std::process::Command;
use tempfile::TempDir;
#[test]
fn test_run_in_sub_directory() -> anyhow::Result<()> {
let case = TestCase::with_files([("test.py", "~"), ("subdir/nothing", "")])?;
assert_cmd_snapshot!(case.command().current_dir(case.root().join("subdir")).arg(".."), @r"
success: false
exit_code: 1
----- stdout -----
error: invalid-syntax
--> <temp_dir>/test.py:1:2
|
1 | ~
| ^ Expected an expression
|
Found 1 diagnostic
----- stderr -----
");
Ok(())
}
#[test]
fn test_include_hidden_files_by_default() -> anyhow::Result<()> {
let case = TestCase::with_files([(".test.py", "~")])?;
assert_cmd_snapshot!(case.command(), @r"
success: false
exit_code: 1
----- stdout -----
error: invalid-syntax
--> .test.py:1:2
|
1 | ~
| ^ Expected an expression
|
Found 1 diagnostic
----- stderr -----
");
Ok(())
}
#[test]
fn test_respect_ignore_files() -> anyhow::Result<()> {
// First test that the default option works correctly (the file is skipped)
let case = TestCase::with_files([(".ignore", "test.py"), ("test.py", "~")])?;
assert_cmd_snapshot!(case.command(), @r"
success: true
exit_code: 0
----- stdout -----
All checks passed!
----- stderr -----
WARN No python files found under the given path(s)
");
// Test that we can set to false via CLI
assert_cmd_snapshot!(case.command().arg("--no-respect-ignore-files"), @r"
success: false
exit_code: 1
----- stdout -----
error: invalid-syntax
--> test.py:1:2
|
1 | ~
| ^ Expected an expression
|
Found 1 diagnostic
----- stderr -----
");
// Test that we can set to false via config file
case.write_file("ty.toml", "respect-ignore-files = false")?;
assert_cmd_snapshot!(case.command(), @r"
success: false
exit_code: 1
----- stdout -----
error: invalid-syntax
--> test.py:1:2
|
1 | ~
| ^ Expected an expression
|
Found 1 diagnostic
----- stderr -----
");
// Ensure CLI takes precedence
case.write_file("ty.toml", "respect-ignore-files = true")?;
assert_cmd_snapshot!(case.command().arg("--no-respect-ignore-files"), @r"
success: false
exit_code: 1
----- stdout -----
error: invalid-syntax
--> test.py:1:2
|
1 | ~
| ^ Expected an expression
|
Found 1 diagnostic
----- stderr -----
");
Ok(())
}
/// Specifying an option on the CLI should take precedence over the same setting in the
/// project's configuration. Here, this is tested for the Python version.
#[test]
@@ -124,7 +13,7 @@ fn config_override_python_version() -> anyhow::Result<()> {
(
"pyproject.toml",
r#"
[tool.ty.environment]
[tool.knot.environment]
python-version = "3.11"
"#,
),
@@ -143,14 +32,13 @@ fn config_override_python_version() -> anyhow::Result<()> {
success: false
exit_code: 1
----- stdout -----
error: lint:unresolved-attribute: Type `<module 'sys'>` has no attribute `last_exc`
--> test.py:5:7
error: lint:unresolved-attribute
--> <temp_dir>/test.py:5:7
|
4 | # Access `sys.last_exc` that was only added in Python 3.12
5 | print(sys.last_exc)
| ^^^^^^^^^^^^
| ^^^^^^^^^^^^ Type `<module 'sys'>` has no attribute `last_exc`
|
info: `lint:unresolved-attribute` is enabled by default
Found 1 diagnostic
@@ -176,7 +64,7 @@ fn config_override_python_platform() -> anyhow::Result<()> {
(
"pyproject.toml",
r#"
[tool.ty.environment]
[tool.knot.environment]
python-platform = "linux"
"#,
),
@@ -196,7 +84,7 @@ fn config_override_python_platform() -> anyhow::Result<()> {
exit_code: 0
----- stdout -----
info: revealed-type: Revealed type
--> test.py:5:1
--> <temp_dir>/test.py:5:1
|
3 | from typing_extensions import reveal_type
4 |
@@ -214,7 +102,7 @@ fn config_override_python_platform() -> anyhow::Result<()> {
exit_code: 0
----- stdout -----
info: revealed-type: Revealed type
--> test.py:5:1
--> <temp_dir>/test.py:5:1
|
3 | from typing_extensions import reveal_type
4 |
@@ -251,7 +139,7 @@ fn cli_arguments_are_relative_to_the_current_directory() -> anyhow::Result<()> {
(
"pyproject.toml",
r#"
[tool.ty.environment]
[tool.knot.environment]
python-version = "3.11"
"#,
),
@@ -277,15 +165,14 @@ fn cli_arguments_are_relative_to_the_current_directory() -> anyhow::Result<()> {
success: false
exit_code: 1
----- stdout -----
error: lint:unresolved-import: Cannot resolve imported module `utils`
--> test.py:2:6
error: lint:unresolved-import
--> <temp_dir>/child/test.py:2:6
|
2 | from utils import add
| ^^^^^
| ^^^^^ Cannot resolve import `utils`
3 |
4 | stat = add(10, 15)
|
info: `lint:unresolved-import` is enabled by default
Found 1 diagnostic
@@ -323,7 +210,7 @@ fn paths_in_configuration_files_are_relative_to_the_project_root() -> anyhow::Re
(
"pyproject.toml",
r#"
[tool.ty.environment]
[tool.knot.environment]
python-version = "3.11"
extra-paths = ["libs"]
"#,
@@ -378,25 +265,23 @@ fn configuration_rule_severity() -> anyhow::Result<()> {
success: false
exit_code: 1
----- stdout -----
error: lint:division-by-zero: Cannot divide object of type `Literal[4]` by zero
--> test.py:2:5
error: lint:division-by-zero
--> <temp_dir>/test.py:2:5
|
2 | y = 4 / 0
| ^^^^^
| ^^^^^ Cannot divide object of type `Literal[4]` by zero
3 |
4 | for a in range(0, int(y)):
|
info: `lint:division-by-zero` is enabled by default
warning: lint:possibly-unresolved-reference: Name `x` used when possibly not defined
--> test.py:7:7
warning: lint:possibly-unresolved-reference
--> <temp_dir>/test.py:7:7
|
5 | x = a
6 |
7 | print(x) # possibly-unresolved-reference
| ^
| ^ Name `x` used when possibly not defined
|
info: `lint:possibly-unresolved-reference` is enabled by default
Found 2 diagnostics
@@ -406,7 +291,7 @@ fn configuration_rule_severity() -> anyhow::Result<()> {
case.write_file(
"pyproject.toml",
r#"
[tool.ty.rules]
[tool.knot.rules]
division-by-zero = "warn" # demote to warn
possibly-unresolved-reference = "ignore"
"#,
@@ -416,15 +301,14 @@ fn configuration_rule_severity() -> anyhow::Result<()> {
success: true
exit_code: 0
----- stdout -----
warning: lint:division-by-zero: Cannot divide object of type `Literal[4]` by zero
--> test.py:2:5
warning: lint:division-by-zero
--> <temp_dir>/test.py:2:5
|
2 | y = 4 / 0
| ^^^^^
| ^^^^^ Cannot divide object of type `Literal[4]` by zero
3 |
4 | for a in range(0, int(y)):
|
info: `lint:division-by-zero` was selected in the configuration file
Found 1 diagnostic
@@ -457,37 +341,34 @@ fn cli_rule_severity() -> anyhow::Result<()> {
success: false
exit_code: 1
----- stdout -----
error: lint:unresolved-import: Cannot resolve imported module `does_not_exit`
--> test.py:2:8
error: lint:unresolved-import
--> <temp_dir>/test.py:2:8
|
2 | import does_not_exit
| ^^^^^^^^^^^^^
| ^^^^^^^^^^^^^ Cannot resolve import `does_not_exit`
3 |
4 | y = 4 / 0
|
info: `lint:unresolved-import` is enabled by default
error: lint:division-by-zero: Cannot divide object of type `Literal[4]` by zero
--> test.py:4:5
error: lint:division-by-zero
--> <temp_dir>/test.py:4:5
|
2 | import does_not_exit
3 |
4 | y = 4 / 0
| ^^^^^
| ^^^^^ Cannot divide object of type `Literal[4]` by zero
5 |
6 | for a in range(0, int(y)):
|
info: `lint:division-by-zero` is enabled by default
warning: lint:possibly-unresolved-reference: Name `x` used when possibly not defined
--> test.py:9:7
warning: lint:possibly-unresolved-reference
--> <temp_dir>/test.py:9:7
|
7 | x = a
8 |
9 | print(x) # possibly-unresolved-reference
| ^
| ^ Name `x` used when possibly not defined
|
info: `lint:possibly-unresolved-reference` is enabled by default
Found 3 diagnostics
@@ -507,27 +388,25 @@ fn cli_rule_severity() -> anyhow::Result<()> {
success: true
exit_code: 0
----- stdout -----
warning: lint:unresolved-import: Cannot resolve imported module `does_not_exit`
--> test.py:2:8
warning: lint:unresolved-import
--> <temp_dir>/test.py:2:8
|
2 | import does_not_exit
| ^^^^^^^^^^^^^
| ^^^^^^^^^^^^^ Cannot resolve import `does_not_exit`
3 |
4 | y = 4 / 0
|
info: `lint:unresolved-import` was selected on the command line
warning: lint:division-by-zero: Cannot divide object of type `Literal[4]` by zero
--> test.py:4:5
warning: lint:division-by-zero
--> <temp_dir>/test.py:4:5
|
2 | import does_not_exit
3 |
4 | y = 4 / 0
| ^^^^^
| ^^^^^ Cannot divide object of type `Literal[4]` by zero
5 |
6 | for a in range(0, int(y)):
|
info: `lint:division-by-zero` was selected on the command line
Found 2 diagnostics
@@ -560,25 +439,23 @@ fn cli_rule_severity_precedence() -> anyhow::Result<()> {
success: false
exit_code: 1
----- stdout -----
error: lint:division-by-zero: Cannot divide object of type `Literal[4]` by zero
--> test.py:2:5
error: lint:division-by-zero
--> <temp_dir>/test.py:2:5
|
2 | y = 4 / 0
| ^^^^^
| ^^^^^ Cannot divide object of type `Literal[4]` by zero
3 |
4 | for a in range(0, int(y)):
|
info: `lint:division-by-zero` is enabled by default
warning: lint:possibly-unresolved-reference: Name `x` used when possibly not defined
--> test.py:7:7
warning: lint:possibly-unresolved-reference
--> <temp_dir>/test.py:7:7
|
5 | x = a
6 |
7 | print(x) # possibly-unresolved-reference
| ^
| ^ Name `x` used when possibly not defined
|
info: `lint:possibly-unresolved-reference` is enabled by default
Found 2 diagnostics
@@ -599,15 +476,14 @@ fn cli_rule_severity_precedence() -> anyhow::Result<()> {
success: true
exit_code: 0
----- stdout -----
warning: lint:division-by-zero: Cannot divide object of type `Literal[4]` by zero
--> test.py:2:5
warning: lint:division-by-zero
--> <temp_dir>/test.py:2:5
|
2 | y = 4 / 0
| ^^^^^
| ^^^^^ Cannot divide object of type `Literal[4]` by zero
3 |
4 | for a in range(0, int(y)):
|
info: `lint:division-by-zero` was selected on the command line
Found 1 diagnostic
@@ -618,14 +494,14 @@ fn cli_rule_severity_precedence() -> anyhow::Result<()> {
Ok(())
}
/// ty warns about unknown rules specified in a configuration file
/// Red Knot warns about unknown rules specified in a configuration file
#[test]
fn configuration_unknown_rules() -> anyhow::Result<()> {
let case = TestCase::with_files([
(
"pyproject.toml",
r#"
[tool.ty.rules]
[tool.knot.rules]
division-by-zer = "warn" # incorrect rule name
"#,
),
@@ -637,9 +513,9 @@ fn configuration_unknown_rules() -> anyhow::Result<()> {
exit_code: 0
----- stdout -----
warning: unknown-rule
--> pyproject.toml:3:1
--> <temp_dir>/pyproject.toml:3:1
|
2 | [tool.ty.rules]
2 | [tool.knot.rules]
3 | division-by-zer = "warn" # incorrect rule name
| ^^^^^^^^^^^^^^^ Unknown lint rule `division-by-zer`
|
@@ -652,7 +528,7 @@ fn configuration_unknown_rules() -> anyhow::Result<()> {
Ok(())
}
/// ty warns about unknown rules specified in a CLI argument
/// Red Knot warns about unknown rules specified in a CLI argument
#[test]
fn cli_unknown_rules() -> anyhow::Result<()> {
let case = TestCase::with_file("test.py", "print(10)")?;
@@ -679,13 +555,12 @@ fn exit_code_only_warnings() -> anyhow::Result<()> {
success: true
exit_code: 0
----- stdout -----
warning: lint:unresolved-reference: Name `x` used when not defined
--> test.py:1:7
warning: lint:unresolved-reference
--> <temp_dir>/test.py:1:7
|
1 | print(x) # [unresolved-reference]
| ^
| ^ Name `x` used when not defined
|
info: `lint:unresolved-reference` is enabled by default
Found 1 diagnostic
@@ -710,7 +585,7 @@ fn exit_code_only_info() -> anyhow::Result<()> {
exit_code: 0
----- stdout -----
info: revealed-type: Revealed type
--> test.py:3:1
--> <temp_dir>/test.py:3:1
|
2 | from typing_extensions import reveal_type
3 | reveal_type(1)
@@ -740,7 +615,7 @@ fn exit_code_only_info_and_error_on_warning_is_true() -> anyhow::Result<()> {
exit_code: 0
----- stdout -----
info: revealed-type: Revealed type
--> test.py:3:1
--> <temp_dir>/test.py:3:1
|
2 | from typing_extensions import reveal_type
3 | reveal_type(1)
@@ -763,13 +638,12 @@ fn exit_code_no_errors_but_error_on_warning_is_true() -> anyhow::Result<()> {
success: false
exit_code: 1
----- stdout -----
warning: lint:unresolved-reference: Name `x` used when not defined
--> test.py:1:7
warning: lint:unresolved-reference
--> <temp_dir>/test.py:1:7
|
1 | print(x) # [unresolved-reference]
| ^
| ^ Name `x` used when not defined
|
info: `lint:unresolved-reference` is enabled by default
Found 1 diagnostic
@@ -784,7 +658,7 @@ fn exit_code_no_errors_but_error_on_warning_is_enabled_in_configuration() -> any
let case = TestCase::with_files([
("test.py", r"print(x) # [unresolved-reference]"),
(
"ty.toml",
"knot.toml",
r#"
[terminal]
error-on-warning = true
@@ -796,13 +670,12 @@ fn exit_code_no_errors_but_error_on_warning_is_enabled_in_configuration() -> any
success: false
exit_code: 1
----- stdout -----
warning: lint:unresolved-reference: Name `x` used when not defined
--> test.py:1:7
warning: lint:unresolved-reference
--> <temp_dir>/test.py:1:7
|
1 | print(x) # [unresolved-reference]
| ^
| ^ Name `x` used when not defined
|
info: `lint:unresolved-reference` is enabled by default
Found 1 diagnostic
@@ -826,23 +699,21 @@ fn exit_code_both_warnings_and_errors() -> anyhow::Result<()> {
success: false
exit_code: 1
----- stdout -----
warning: lint:unresolved-reference: Name `x` used when not defined
--> test.py:2:7
warning: lint:unresolved-reference
--> <temp_dir>/test.py:2:7
|
2 | print(x) # [unresolved-reference]
| ^
| ^ Name `x` used when not defined
3 | print(4[1]) # [non-subscriptable]
|
info: `lint:unresolved-reference` is enabled by default
error: lint:non-subscriptable: Cannot subscript object of type `Literal[4]` with no `__getitem__` method
--> test.py:3:7
error: lint:non-subscriptable
--> <temp_dir>/test.py:3:7
|
2 | print(x) # [unresolved-reference]
3 | print(4[1]) # [non-subscriptable]
| ^
| ^ Cannot subscript object of type `Literal[4]` with no `__getitem__` method
|
info: `lint:non-subscriptable` is enabled by default
Found 2 diagnostics
@@ -866,23 +737,21 @@ fn exit_code_both_warnings_and_errors_and_error_on_warning_is_true() -> anyhow::
success: false
exit_code: 1
----- stdout -----
warning: lint:unresolved-reference: Name `x` used when not defined
--> test.py:2:7
warning: lint:unresolved-reference
--> <temp_dir>/test.py:2:7
|
2 | print(x) # [unresolved-reference]
| ^
| ^ Name `x` used when not defined
3 | print(4[1]) # [non-subscriptable]
|
info: `lint:unresolved-reference` is enabled by default
error: lint:non-subscriptable: Cannot subscript object of type `Literal[4]` with no `__getitem__` method
--> test.py:3:7
error: lint:non-subscriptable
--> <temp_dir>/test.py:3:7
|
2 | print(x) # [unresolved-reference]
3 | print(4[1]) # [non-subscriptable]
| ^
| ^ Cannot subscript object of type `Literal[4]` with no `__getitem__` method
|
info: `lint:non-subscriptable` is enabled by default
Found 2 diagnostics
@@ -906,23 +775,21 @@ fn exit_code_exit_zero_is_true() -> anyhow::Result<()> {
success: true
exit_code: 0
----- stdout -----
warning: lint:unresolved-reference: Name `x` used when not defined
--> test.py:2:7
warning: lint:unresolved-reference
--> <temp_dir>/test.py:2:7
|
2 | print(x) # [unresolved-reference]
| ^
| ^ Name `x` used when not defined
3 | print(4[1]) # [non-subscriptable]
|
info: `lint:unresolved-reference` is enabled by default
error: lint:non-subscriptable: Cannot subscript object of type `Literal[4]` with no `__getitem__` method
--> test.py:3:7
error: lint:non-subscriptable
--> <temp_dir>/test.py:3:7
|
2 | print(x) # [unresolved-reference]
3 | print(4[1]) # [non-subscriptable]
| ^
| ^ Cannot subscript object of type `Literal[4]` with no `__getitem__` method
|
info: `lint:non-subscriptable` is enabled by default
Found 2 diagnostics
@@ -936,7 +803,7 @@ fn exit_code_exit_zero_is_true() -> anyhow::Result<()> {
fn user_configuration() -> anyhow::Result<()> {
let case = TestCase::with_files([
(
"project/ty.toml",
"project/knot.toml",
r#"
[rules]
division-by-zero = "warn"
@@ -968,25 +835,23 @@ fn user_configuration() -> anyhow::Result<()> {
success: true
exit_code: 0
----- stdout -----
warning: lint:division-by-zero: Cannot divide object of type `Literal[4]` by zero
--> main.py:2:5
warning: lint:division-by-zero
--> <temp_dir>/project/main.py:2:5
|
2 | y = 4 / 0
| ^^^^^
| ^^^^^ Cannot divide object of type `Literal[4]` by zero
3 |
4 | for a in range(0, int(y)):
|
info: `lint:division-by-zero` was selected in the configuration file
warning: lint:possibly-unresolved-reference: Name `x` used when possibly not defined
--> main.py:7:7
warning: lint:possibly-unresolved-reference
--> <temp_dir>/project/main.py:7:7
|
5 | x = a
6 |
7 | print(x)
| ^
| ^ Name `x` used when possibly not defined
|
info: `lint:possibly-unresolved-reference` is enabled by default
Found 2 diagnostics
@@ -998,7 +863,7 @@ fn user_configuration() -> anyhow::Result<()> {
// Changing the level for `division-by-zero` has no effect, because the project-level configuration
// has higher precedence.
case.write_file(
config_directory.join("ty/ty.toml"),
config_directory.join("knot/knot.toml"),
r#"
[rules]
division-by-zero = "error"
@@ -1012,25 +877,23 @@ fn user_configuration() -> anyhow::Result<()> {
success: false
exit_code: 1
----- stdout -----
warning: lint:division-by-zero: Cannot divide object of type `Literal[4]` by zero
--> main.py:2:5
warning: lint:division-by-zero
--> <temp_dir>/project/main.py:2:5
|
2 | y = 4 / 0
| ^^^^^
| ^^^^^ Cannot divide object of type `Literal[4]` by zero
3 |
4 | for a in range(0, int(y)):
|
info: `lint:division-by-zero` was selected in the configuration file
error: lint:possibly-unresolved-reference: Name `x` used when possibly not defined
--> main.py:7:7
error: lint:possibly-unresolved-reference
--> <temp_dir>/project/main.py:7:7
|
5 | x = a
6 |
7 | print(x)
| ^
| ^ Name `x` used when possibly not defined
|
info: `lint:possibly-unresolved-reference` was selected in the configuration file
Found 2 diagnostics
@@ -1072,31 +935,28 @@ fn check_specific_paths() -> anyhow::Result<()> {
success: false
exit_code: 1
----- stdout -----
error: lint:division-by-zero: Cannot divide object of type `Literal[4]` by zero
--> project/main.py:2:5
error: lint:unresolved-import
--> <temp_dir>/project/tests/test_main.py:2:8
|
2 | import does_not_exist # error: unresolved-import
| ^^^^^^^^^^^^^^ Cannot resolve import `does_not_exist`
|
error: lint:division-by-zero
--> <temp_dir>/project/main.py:2:5
|
2 | y = 4 / 0 # error: division-by-zero
| ^^^^^
| ^^^^^ Cannot divide object of type `Literal[4]` by zero
|
info: `lint:division-by-zero` is enabled by default
error: lint:unresolved-import: Cannot resolve imported module `main2`
--> project/other.py:2:6
error: lint:unresolved-import
--> <temp_dir>/project/other.py:2:6
|
2 | from main2 import z # error: unresolved-import
| ^^^^^
| ^^^^^ Cannot resolve import `main2`
3 |
4 | print(z)
|
info: `lint:unresolved-import` is enabled by default
error: lint:unresolved-import: Cannot resolve imported module `does_not_exist`
--> project/tests/test_main.py:2:8
|
2 | import does_not_exist # error: unresolved-import
| ^^^^^^^^^^^^^^
|
info: `lint:unresolved-import` is enabled by default
Found 3 diagnostics
@@ -1112,23 +972,21 @@ fn check_specific_paths() -> anyhow::Result<()> {
success: false
exit_code: 1
----- stdout -----
error: lint:unresolved-import: Cannot resolve imported module `main2`
--> project/other.py:2:6
error: lint:unresolved-import
--> <temp_dir>/project/tests/test_main.py:2:8
|
2 | import does_not_exist # error: unresolved-import
| ^^^^^^^^^^^^^^ Cannot resolve import `does_not_exist`
|
error: lint:unresolved-import
--> <temp_dir>/project/other.py:2:6
|
2 | from main2 import z # error: unresolved-import
| ^^^^^
| ^^^^^ Cannot resolve import `main2`
3 |
4 | print(z)
|
info: `lint:unresolved-import` is enabled by default
error: lint:unresolved-import: Cannot resolve imported module `does_not_exist`
--> project/tests/test_main.py:2:8
|
2 | import does_not_exist # error: unresolved-import
| ^^^^^^^^^^^^^^
|
info: `lint:unresolved-import` is enabled by default
Found 2 diagnostics
@@ -1184,8 +1042,8 @@ fn concise_diagnostics() -> anyhow::Result<()> {
success: false
exit_code: 1
----- stdout -----
warning[lint:unresolved-reference] test.py:2:7: Name `x` used when not defined
error[lint:non-subscriptable] test.py:3:7: Cannot subscript object of type `Literal[4]` with no `__getitem__` method
warning[lint:unresolved-reference] <temp_dir>/test.py:2:7: Name `x` used when not defined
error[lint:non-subscriptable] <temp_dir>/test.py:3:7: Cannot subscript object of type `Literal[4]` with no `__getitem__` method
Found 2 diagnostics
----- stderr -----
@@ -1218,7 +1076,7 @@ fn concise_revealed_type() -> anyhow::Result<()> {
success: true
exit_code: 0
----- stdout -----
info[revealed-type] test.py:5:1: Revealed type: `Literal["hello"]`
info[revealed-type] <temp_dir>/test.py:5:1: Revealed type: `Literal["hello"]`
Found 1 diagnostic
----- stderr -----
@@ -1227,42 +1085,6 @@ fn concise_revealed_type() -> anyhow::Result<()> {
Ok(())
}
#[test]
fn can_handle_large_binop_expressions() -> anyhow::Result<()> {
let mut content = String::new();
writeln!(
&mut content,
"
from typing_extensions import reveal_type
total = 1{plus_one_repeated}
reveal_type(total)
",
plus_one_repeated = " + 1".repeat(2000 - 1)
)?;
let case = TestCase::with_file("test.py", &ruff_python_trivia::textwrap::dedent(&content))?;
assert_cmd_snapshot!(case.command(), @r"
success: true
exit_code: 0
----- stdout -----
info: revealed-type: Revealed type
--> test.py:4:1
|
2 | from typing_extensions import reveal_type
3 | total = 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1...
4 | reveal_type(total)
| ^^^^^^^^^^^^^^^^^^ `Literal[2000]`
|
Found 1 diagnostic
----- stderr -----
");
Ok(())
}
struct TestCase {
_temp_dir: TempDir,
_settings_scope: SettingsBindDropGuard,
@@ -1335,7 +1157,7 @@ impl TestCase {
}
fn command(&self) -> Command {
let mut command = Command::new(get_cargo_bin("ty"));
let mut command = Command::new(get_cargo_bin("red_knot"));
command.current_dir(&self.project_dir).arg("check");
command
}

View File

@@ -3,6 +3,12 @@ use std::io::Write;
use std::time::{Duration, Instant};
use anyhow::{anyhow, Context};
use red_knot_project::metadata::options::{EnvironmentOptions, Options};
use red_knot_project::metadata::pyproject::{PyProject, Tool};
use red_knot_project::metadata::value::{RangedValue, RelativePathBuf};
use red_knot_project::watch::{directory_watcher, ChangeEvent, ProjectWatcher};
use red_knot_project::{Db, ProjectDatabase, ProjectMetadata};
use red_knot_python_semantic::{resolve_module, ModuleName, PythonPlatform};
use ruff_db::files::{system_path_to_file, File, FileError};
use ruff_db::source::source_text;
use ruff_db::system::{
@@ -10,12 +16,6 @@ use ruff_db::system::{
};
use ruff_db::{Db as _, Upcast};
use ruff_python_ast::PythonVersion;
use ty_project::metadata::options::{EnvironmentOptions, Options};
use ty_project::metadata::pyproject::{PyProject, Tool};
use ty_project::metadata::value::{RangedValue, RelativePathBuf};
use ty_project::watch::{directory_watcher, ChangeEvent, ProjectWatcher};
use ty_project::{Db, ProjectDatabase, ProjectMetadata};
use ty_python_semantic::{resolve_module, ModuleName, PythonPlatform};
struct TestCase {
db: ProjectDatabase,
@@ -173,7 +173,9 @@ impl TestCase {
self.project_path("pyproject.toml").as_std_path(),
toml::to_string(&PyProject {
project: None,
tool: Some(Tool { ty: Some(options) }),
tool: Some(Tool {
knot: Some(options),
}),
})
.context("Failed to serialize options")?,
)
@@ -380,7 +382,9 @@ where
project_path.join("pyproject.toml").as_std_path(),
toml::to_string(&PyProject {
project: None,
tool: Some(Tool { ty: Some(options) }),
tool: Some(Tool {
knot: Some(options),
}),
})
.context("Failed to serialize options")?,
)
@@ -1542,7 +1546,7 @@ mod unix {
//
// I further tested how good editor support is for symlinked files and it is not good ;)
// * VS Code doesn't update the file content if a file gets changed through a symlink
// * PyCharm doesn't update diagnostics if a symlinked module is changed (same as ty).
// * PyCharm doesn't update diagnostics if a symlinked module is changed (same as red knot).
//
// That's why I think it's fine to not support this case for now.
@@ -1654,7 +1658,7 @@ mod unix {
// It would be nice if this is supported but the underlying file system watchers
// only emit a single event. For reference
// * VS Code doesn't update the file content if a file gets changed through a symlink
// * PyCharm doesn't update diagnostics if a symlinked module is changed (same as ty).
// * PyCharm doesn't update diagnostics if a symlinked module is changed (same as red knot).
// We could add support for it by keeping a reverse map from `real_path` to symlinked path but
// it doesn't seem worth doing considering that as prominent tools like PyCharm don't support it.
// Pyright does support it, thanks to chokidar.
@@ -1678,7 +1682,7 @@ fn nested_projects_delete_root() -> anyhow::Result<()> {
[project]
name = "inner"
[tool.ty]
[tool.knot]
"#,
)?;
@@ -1688,7 +1692,7 @@ fn nested_projects_delete_root() -> anyhow::Result<()> {
[project]
name = "outer"
[tool.ty]
[tool.knot]
"#,
)?;
@@ -1728,9 +1732,9 @@ fn changes_to_user_configuration() -> anyhow::Result<()> {
)?;
let config_directory = context.join_root_path("home/.config");
std::fs::create_dir_all(config_directory.join("ty").as_std_path())?;
std::fs::create_dir_all(config_directory.join("knot").as_std_path())?;
std::fs::write(
config_directory.join("ty/ty.toml").as_std_path(),
config_directory.join("knot/knot.toml").as_std_path(),
r#"
[rules]
division-by-zero = "ignore"
@@ -1761,14 +1765,14 @@ fn changes_to_user_configuration() -> anyhow::Result<()> {
// Enable division-by-zero in the user configuration with warning severity
update_file(
case.root_path().join("home/.config/ty/ty.toml"),
case.root_path().join("home/.config/knot/knot.toml"),
r#"
[rules]
division-by-zero = "warn"
"#,
)?;
let changes = case.stop_watch(event_for_file("ty.toml"));
let changes = case.stop_watch(event_for_file("knot.toml"));
case.apply_changes(changes);
@@ -1789,7 +1793,7 @@ fn changes_to_user_configuration() -> anyhow::Result<()> {
///
/// This test currently fails on case-insensitive systems because `Files` is case-sensitive
/// but the `System::metadata` call isn't. This means that
/// ty considers both `Lib.py` and `lib.py` to exist when only `lib.py` does
/// Red Knot considers both `Lib.py` and `lib.py` to exist when only `lib.py` does
///
/// The incoming change events then are no-ops because they don't change either file's
/// status nor does it update their last modified time (renaming a file doesn't bump it's
@@ -1801,7 +1805,7 @@ fn changes_to_user_configuration() -> anyhow::Result<()> {
/// `System` calls should be case sensitive. This would be the most consistent
/// but might be hard to pull off.
///
/// What the right solution is also depends on if ty itself should be case
/// What the right solution is also depends on if Red Knot itself should be case
/// sensitive or not. E.g. should `include="src"` be case sensitive on all systems
/// or only on case-sensitive systems?
///

View File

@@ -1,5 +1,5 @@
[package]
name = "ty_ide"
name = "red_knot_ide"
version = "0.0.0"
publish = false
authors = { workspace = true }
@@ -15,7 +15,7 @@ ruff_db = { workspace = true }
ruff_python_ast = { workspace = true }
ruff_python_parser = { workspace = true }
ruff_text_size = { workspace = true }
ty_python_semantic = { workspace = true }
red_knot_python_semantic = { workspace = true }
rustc-hash = { workspace = true }
salsa = { workspace = true }
@@ -23,7 +23,7 @@ smallvec = { workspace = true }
tracing = { workspace = true }
[dev-dependencies]
ty_vendored = { workspace = true }
red_knot_vendored = { workspace = true }
insta = { workspace = true, features = ["filters"] }

View File

@@ -1,22 +1,20 @@
use red_knot_python_semantic::Db as SemanticDb;
use ruff_db::{Db as SourceDb, Upcast};
use ty_python_semantic::Db as SemanticDb;
#[salsa::db]
pub trait Db: SemanticDb + Upcast<dyn SemanticDb> + Upcast<dyn SourceDb> {}
#[cfg(test)]
pub(crate) mod tests {
use std::sync::{Arc, Mutex};
use std::sync::Arc;
use super::Db;
use red_knot_python_semantic::lint::{LintRegistry, RuleSelection};
use red_knot_python_semantic::{default_lint_registry, Db as SemanticDb, Program};
use ruff_db::files::{File, Files};
use ruff_db::system::{DbWithTestSystem, System, TestSystem};
use ruff_db::vendored::VendoredFileSystem;
use ruff_db::{Db as SourceDb, Upcast};
use ty_python_semantic::lint::{LintRegistry, RuleSelection};
use ty_python_semantic::{default_lint_registry, Db as SemanticDb, Program};
type Events = Arc<Mutex<Vec<salsa::Event>>>;
#[salsa::db]
#[derive(Clone)]
@@ -25,35 +23,31 @@ pub(crate) mod tests {
files: Files,
system: TestSystem,
vendored: VendoredFileSystem,
events: Events,
events: Arc<std::sync::Mutex<Vec<salsa::Event>>>,
rule_selection: Arc<RuleSelection>,
}
#[expect(dead_code)]
#[allow(dead_code)]
impl TestDb {
pub(crate) fn new() -> Self {
let events = Events::default();
Self {
storage: salsa::Storage::new(Some(Box::new({
let events = events.clone();
move |event| {
tracing::trace!("event: {event:?}");
let mut events = events.lock().unwrap();
events.push(event);
}
}))),
storage: salsa::Storage::default(),
system: TestSystem::default(),
vendored: ty_vendored::file_system().clone(),
events,
vendored: red_knot_vendored::file_system().clone(),
events: Arc::default(),
files: Files::default(),
rule_selection: Arc::new(RuleSelection::from_registry(default_lint_registry())),
}
}
/// Takes the salsa events.
///
/// ## Panics
/// If there are any pending salsa snapshots.
pub(crate) fn take_salsa_events(&mut self) -> Vec<salsa::Event> {
let mut events = self.events.lock().unwrap();
let inner = Arc::get_mut(&mut self.events).expect("no pending salsa snapshots");
let events = inner.get_mut().unwrap();
std::mem::take(&mut *events)
}
@@ -133,5 +127,12 @@ pub(crate) mod tests {
impl Db for TestDb {}
#[salsa::db]
impl salsa::Database for TestDb {}
impl salsa::Database for TestDb {
fn salsa_event(&self, event: &dyn Fn() -> salsa::Event) {
let event = event();
tracing::trace!("event: {event:?}");
let mut events = self.events.lock().unwrap();
events.push(event);
}
}
}

View File

@@ -1,12 +1,12 @@
use crate::find_node::covering_node;
use crate::{Db, HasNavigationTargets, NavigationTargets, RangedValue};
use red_knot_python_semantic::types::Type;
use red_knot_python_semantic::{HasType, SemanticModel};
use ruff_db::files::{File, FileRange};
use ruff_db::parsed::{parsed_module, ParsedModule};
use ruff_python_ast::{self as ast, AnyNodeRef};
use ruff_python_parser::TokenKind;
use ruff_text_size::{Ranged, TextRange, TextSize};
use ty_python_semantic::types::Type;
use ty_python_semantic::{HasType, SemanticModel};
pub fn goto_type_definition(
db: &dyn Db,
@@ -272,9 +272,9 @@ mod tests {
"#,
);
assert_snapshot!(test.goto_type_definition(), @r"
assert_snapshot!(test.goto_type_definition(), @r###"
info: lint:goto-type-definition: Type definition
--> main.py:2:19
--> /main.py:2:19
|
2 | class Test: ...
| ^^^^
@@ -282,14 +282,14 @@ mod tests {
4 | ab = Test()
|
info: Source
--> main.py:4:13
--> /main.py:4:13
|
2 | class Test: ...
3 |
4 | ab = Test()
| ^^
|
");
"###);
}
#[test]
@@ -304,9 +304,9 @@ mod tests {
"#,
);
assert_snapshot!(test.goto_type_definition(), @r"
assert_snapshot!(test.goto_type_definition(), @r###"
info: lint:goto-type-definition: Type definition
--> main.py:2:17
--> /main.py:2:17
|
2 | def foo(a, b): ...
| ^^^
@@ -314,14 +314,14 @@ mod tests {
4 | ab = foo
|
info: Source
--> main.py:6:13
--> /main.py:6:13
|
4 | ab = foo
5 |
6 | ab
| ^^
|
");
"###);
}
#[test]
@@ -344,7 +344,7 @@ mod tests {
assert_snapshot!(test.goto_type_definition(), @r"
info: lint:goto-type-definition: Type definition
--> main.py:3:17
--> /main.py:3:17
|
3 | def foo(a, b): ...
| ^^^
@@ -352,7 +352,7 @@ mod tests {
5 | def bar(a, b): ...
|
info: Source
--> main.py:12:13
--> /main.py:12:13
|
10 | a = bar
11 |
@@ -361,7 +361,7 @@ mod tests {
|
info: lint:goto-type-definition: Type definition
--> main.py:5:17
--> /main.py:5:17
|
3 | def foo(a, b): ...
4 |
@@ -371,7 +371,7 @@ mod tests {
7 | if random.choice():
|
info: Source
--> main.py:12:13
--> /main.py:12:13
|
10 | a = bar
11 |
@@ -395,13 +395,13 @@ mod tests {
assert_snapshot!(test.goto_type_definition(), @r"
info: lint:goto-type-definition: Type definition
--> lib.py:1:1
--> /lib.py:1:1
|
1 | a = 10
| ^^^^^^
|
info: Source
--> main.py:4:13
--> /main.py:4:13
|
2 | import lib
3 |
@@ -433,7 +433,7 @@ mod tests {
440 | def __new__(cls, object: object = ...) -> Self: ...
|
info: Source
--> main.py:4:13
--> /main.py:4:13
|
2 | a: str = "test"
3 |
@@ -462,7 +462,7 @@ mod tests {
440 | def __new__(cls, object: object = ...) -> Self: ...
|
info: Source
--> main.py:2:22
--> /main.py:2:22
|
2 | a: str = "test"
| ^^^^^^
@@ -478,20 +478,20 @@ mod tests {
"#,
);
assert_snapshot!(test.goto_type_definition(), @r"
assert_snapshot!(test.goto_type_definition(), @r###"
info: lint:goto-type-definition: Type definition
--> main.py:2:24
--> /main.py:2:24
|
2 | type Alias[T: int = bool] = list[T]
| ^
|
info: Source
--> main.py:2:46
--> /main.py:2:46
|
2 | type Alias[T: int = bool] = list[T]
| ^
|
");
"###);
}
#[test]
@@ -544,7 +544,7 @@ mod tests {
440 | def __new__(cls, object: object = ...) -> Self: ...
|
info: Source
--> main.py:4:18
--> /main.py:4:18
|
2 | def test(a: str): ...
3 |
@@ -579,7 +579,7 @@ mod tests {
233 | def __new__(cls, x: ConvertibleToInt = ..., /) -> Self: ...
|
info: Source
--> main.py:4:18
--> /main.py:4:18
|
2 | def test(a: str): ...
3 |
@@ -613,7 +613,7 @@ f(**kwargs<CURSOR>)
1088 | # Also multiprocessing.managers.SyncManager.dict()
|
info: Source
--> main.py:6:5
--> /main.py:6:5
|
4 | kwargs = { "name": "test"}
5 |
@@ -644,7 +644,7 @@ f(**kwargs<CURSOR>)
440 | def __new__(cls, object: object = ...) -> Self: ...
|
info: Source
--> main.py:3:17
--> /main.py:3:17
|
2 | def foo(a: str):
3 | a
@@ -666,23 +666,23 @@ f(**kwargs<CURSOR>)
"#,
);
assert_snapshot!(test.goto_type_definition(), @r"
assert_snapshot!(test.goto_type_definition(), @r###"
info: lint:goto-type-definition: Type definition
--> main.py:2:19
--> /main.py:2:19
|
2 | class X:
| ^
3 | def foo(a, b): ...
|
info: Source
--> main.py:7:13
--> /main.py:7:13
|
5 | x = X()
6 |
7 | x.foo()
| ^
|
");
"###);
}
#[test]
@@ -695,9 +695,9 @@ f(**kwargs<CURSOR>)
"#,
);
assert_snapshot!(test.goto_type_definition(), @r"
assert_snapshot!(test.goto_type_definition(), @r###"
info: lint:goto-type-definition: Type definition
--> main.py:2:17
--> /main.py:2:17
|
2 | def foo(a, b): ...
| ^^^
@@ -705,14 +705,14 @@ f(**kwargs<CURSOR>)
4 | foo()
|
info: Source
--> main.py:4:13
--> /main.py:4:13
|
2 | def foo(a, b): ...
3 |
4 | foo()
| ^^^
|
");
"###);
}
#[test]
@@ -737,7 +737,7 @@ f(**kwargs<CURSOR>)
440 | def __new__(cls, object: object = ...) -> Self: ...
|
info: Source
--> main.py:4:27
--> /main.py:4:27
|
2 | def foo(a: str | None, b):
3 | if a is not None:
@@ -767,7 +767,7 @@ f(**kwargs<CURSOR>)
672 | def __bool__(self) -> Literal[False]: ...
|
info: Source
--> main.py:3:17
--> /main.py:3:17
|
2 | def foo(a: str | None, b):
3 | a
@@ -785,7 +785,7 @@ f(**kwargs<CURSOR>)
440 | def __new__(cls, object: object = ...) -> Self: ...
|
info: Source
--> main.py:3:17
--> /main.py:3:17
|
2 | def foo(a: str | None, b):
3 | a

View File

@@ -1,12 +1,12 @@
use crate::goto::{find_goto_target, GotoTarget};
use crate::{Db, MarkupKind, RangedValue};
use red_knot_python_semantic::types::Type;
use red_knot_python_semantic::SemanticModel;
use ruff_db::files::{File, FileRange};
use ruff_db::parsed::parsed_module;
use ruff_text_size::{Ranged, TextSize};
use std::fmt;
use std::fmt::Formatter;
use ty_python_semantic::types::Type;
use ty_python_semantic::SemanticModel;
pub fn hover(db: &dyn Db, file: File, offset: TextSize) -> Option<RangedValue<Hover>> {
let parsed = parsed_module(db.upcast(), file);
@@ -156,7 +156,7 @@ mod tests {
```
---------------------------------------------
info: lint:hover: Hovered content is
--> main.py:4:9
--> /main.py:4:9
|
2 | a = 10
3 |
@@ -192,7 +192,7 @@ mod tests {
```
---------------------------------------------
info: lint:hover: Hovered content is
--> main.py:10:9
--> /main.py:10:9
|
9 | foo = Foo()
10 | foo.a
@@ -214,7 +214,7 @@ mod tests {
"#,
);
assert_snapshot!(test.hover(), @r"
assert_snapshot!(test.hover(), @r###"
def foo(a, b) -> Unknown
---------------------------------------------
```text
@@ -222,7 +222,7 @@ mod tests {
```
---------------------------------------------
info: lint:hover: Hovered content is
--> main.py:4:13
--> /main.py:4:13
|
2 | def foo(a, b): ...
3 |
@@ -231,7 +231,7 @@ mod tests {
| |
| source
|
");
"###);
}
#[test]
@@ -251,7 +251,7 @@ mod tests {
```
---------------------------------------------
info: lint:hover: Hovered content is
--> main.py:3:17
--> /main.py:3:17
|
2 | def foo(a: int, b: int, c: int):
3 | a + b == c
@@ -282,7 +282,7 @@ mod tests {
```
---------------------------------------------
info: lint:hover: Hovered content is
--> main.py:4:18
--> /main.py:4:18
|
2 | def test(a: int): ...
3 |
@@ -312,7 +312,7 @@ mod tests {
"#,
);
assert_snapshot!(test.hover(), @r"
assert_snapshot!(test.hover(), @r###"
(def foo(a, b) -> Unknown) | (def bar(a, b) -> Unknown)
---------------------------------------------
```text
@@ -320,7 +320,7 @@ mod tests {
```
---------------------------------------------
info: lint:hover: Hovered content is
--> main.py:12:13
--> /main.py:12:13
|
10 | a = bar
11 |
@@ -329,7 +329,7 @@ mod tests {
| |
| source
|
");
"###);
}
#[test]
@@ -352,7 +352,7 @@ mod tests {
```
---------------------------------------------
info: lint:hover: Hovered content is
--> main.py:4:13
--> /main.py:4:13
|
2 | import lib
3 |
@@ -381,7 +381,7 @@ mod tests {
```
---------------------------------------------
info: lint:hover: Hovered content is
--> main.py:2:46
--> /main.py:2:46
|
2 | type Alias[T: int = bool] = list[T]
| ^- Cursor offset
@@ -407,7 +407,7 @@ mod tests {
```
---------------------------------------------
info: lint:hover: Hovered content is
--> main.py:2:53
--> /main.py:2:53
|
2 | type Alias[**P = [int, str]] = Callable[P, int]
| ^- Cursor offset
@@ -433,7 +433,7 @@ mod tests {
```
---------------------------------------------
info: lint:hover: Hovered content is
--> main.py:2:43
--> /main.py:2:43
|
2 | type Alias[*Ts = ()] = tuple[*Ts]
| ^^- Cursor offset
@@ -444,129 +444,7 @@ mod tests {
}
#[test]
fn hover_variable_assignment() {
let test = cursor_test(
r#"
value<CURSOR> = 1
"#,
);
assert_snapshot!(test.hover(), @r"
Literal[1]
---------------------------------------------
```text
Literal[1]
```
---------------------------------------------
info: lint:hover: Hovered content is
--> main.py:2:13
|
2 | value = 1
| ^^^^^- Cursor offset
| |
| source
|
");
}
#[test]
fn hover_augmented_assignment() {
let test = cursor_test(
r#"
value = 1
value<CURSOR> += 2
"#,
);
// We currently show the *previous* value of the variable (1), not the new one (3).
// Showing the new value might be more intuitive for some users, but the actual 'use'
// of the `value` symbol here in read-context is `1`. This comment mainly exists to
// signal that it might be okay to revisit this in the future and reveal 3 instead.
assert_snapshot!(test.hover(), @r"
Literal[1]
---------------------------------------------
```text
Literal[1]
```
---------------------------------------------
info: lint:hover: Hovered content is
--> main.py:3:13
|
2 | value = 1
3 | value += 2
| ^^^^^- Cursor offset
| |
| source
|
");
}
#[test]
fn hover_attribute_assignment() {
let test = cursor_test(
r#"
class C:
attr: int = 1
C.attr<CURSOR> = 2
"#,
);
assert_snapshot!(test.hover(), @r"
Literal[2]
---------------------------------------------
```text
Literal[2]
```
---------------------------------------------
info: lint:hover: Hovered content is
--> main.py:5:13
|
3 | attr: int = 1
4 |
5 | C.attr = 2
| ^^^^^^- Cursor offset
| |
| source
|
");
}
#[test]
fn hover_augmented_attribute_assignment() {
let test = cursor_test(
r#"
class C:
attr = 1
C.attr<CURSOR> += 2
"#,
);
// See the comment in the `hover_augmented_assignment` test above. The same
// reasoning applies here.
assert_snapshot!(test.hover(), @r"
Unknown | Literal[1]
---------------------------------------------
```text
Unknown | Literal[1]
```
---------------------------------------------
info: lint:hover: Hovered content is
--> main.py:5:13
|
3 | attr = 1
4 |
5 | C.attr += 2
| ^^^^^^- Cursor offset
| |
| source
|
");
}
#[test]
fn hover_annotated_assignment() {
fn hover_class_member_declaration() {
let test = cursor_test(
r#"
class Foo:
@@ -574,15 +452,16 @@ mod tests {
"#,
);
// TODO: This should be int and not `Never`, https://github.com/astral-sh/ruff/issues/17122
assert_snapshot!(test.hover(), @r"
int
Never
---------------------------------------------
```text
int
Never
```
---------------------------------------------
info: lint:hover: Hovered content is
--> main.py:3:13
--> /main.py:3:13
|
2 | class Foo:
3 | a: int
@@ -593,64 +472,6 @@ mod tests {
");
}
#[test]
fn hover_annotated_assignment_with_rhs() {
let test = cursor_test(
r#"
class Foo:
a<CURSOR>: int = 1
"#,
);
assert_snapshot!(test.hover(), @r"
Literal[1]
---------------------------------------------
```text
Literal[1]
```
---------------------------------------------
info: lint:hover: Hovered content is
--> main.py:3:13
|
2 | class Foo:
3 | a: int = 1
| ^- Cursor offset
| |
| source
|
");
}
#[test]
fn hover_annotated_attribute_assignment() {
let test = cursor_test(
r#"
class Foo:
def __init__(self, a: int):
self.a<CURSOR>: int = a
"#,
);
assert_snapshot!(test.hover(), @r"
int
---------------------------------------------
```text
int
```
---------------------------------------------
info: lint:hover: Hovered content is
--> main.py:4:17
|
2 | class Foo:
3 | def __init__(self, a: int):
4 | self.a: int = a
| ^^^^^^- Cursor offset
| |
| source
|
");
}
#[test]
fn hover_type_narrowing() {
let test = cursor_test(
@@ -669,7 +490,7 @@ mod tests {
```
---------------------------------------------
info: lint:hover: Hovered content is
--> main.py:4:27
--> /main.py:4:27
|
2 | def foo(a: str | None, b):
3 | if a is not None:

View File

@@ -1,4 +1,6 @@
use crate::Db;
use red_knot_python_semantic::types::Type;
use red_knot_python_semantic::{HasType, SemanticModel};
use ruff_db::files::File;
use ruff_db::parsed::parsed_module;
use ruff_python_ast::visitor::source_order::{self, SourceOrderVisitor, TraversalSignal};
@@ -6,8 +8,6 @@ use ruff_python_ast::{AnyNodeRef, Expr, Stmt};
use ruff_text_size::{Ranged, TextRange, TextSize};
use std::fmt;
use std::fmt::Formatter;
use ty_python_semantic::types::Type;
use ty_python_semantic::{HasType, SemanticModel};
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct InlayHint<'db> {
@@ -157,11 +157,11 @@ mod tests {
use crate::db::tests::TestDb;
use ruff_db::system::{DbWithWritableSystem, SystemPathBuf};
use ruff_python_ast::PythonVersion;
use ty_python_semantic::{
use red_knot_python_semantic::{
Program, ProgramSettings, PythonPath, PythonPlatform, SearchPathSettings,
};
use ruff_db::system::{DbWithWritableSystem, SystemPathBuf};
use ruff_python_ast::PythonVersion;
pub(super) fn inlay_hint_test(source: &str) -> InlayHintTest {
const START: &str = "<START>";

View File

@@ -1,4 +1,3 @@
mod completion;
mod db;
mod find_node;
mod goto;
@@ -6,7 +5,6 @@ mod hover;
mod inlay_hints;
mod markup;
pub use completion::completion;
pub use db::Db;
pub use goto::goto_type_definition;
pub use hover::hover;
@@ -16,9 +14,9 @@ pub use markup::MarkupKind;
use rustc_hash::FxHashSet;
use std::ops::{Deref, DerefMut};
use red_knot_python_semantic::types::{Type, TypeDefinition};
use ruff_db::files::{File, FileRange};
use ruff_text_size::{Ranged, TextRange};
use ty_python_semantic::types::{Type, TypeDefinition};
/// Information associated with a text range.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
@@ -201,14 +199,14 @@ impl HasNavigationTargets for TypeDefinition<'_> {
mod tests {
use crate::db::tests::TestDb;
use insta::internals::SettingsBindDropGuard;
use red_knot_python_semantic::{
Program, ProgramSettings, PythonPath, PythonPlatform, SearchPathSettings,
};
use ruff_db::diagnostic::{Diagnostic, DiagnosticFormat, DisplayDiagnosticConfig};
use ruff_db::files::{system_path_to_file, File};
use ruff_db::system::{DbWithWritableSystem, SystemPath, SystemPathBuf};
use ruff_python_ast::PythonVersion;
use ruff_text_size::TextSize;
use ty_python_semantic::{
Program, ProgramSettings, PythonPath, PythonPlatform, SearchPathSettings,
};
pub(super) fn cursor_test(source: &str) -> CursorTest {
let mut db = TestDb::new();

View File

@@ -1,5 +1,5 @@
[package]
name = "ty_project"
name = "red_knot_project"
version = "0.0.0"
edition.workspace = true
rust-version.workspace = true
@@ -18,9 +18,9 @@ ruff_macros = { workspace = true }
ruff_python_ast = { workspace = true, features = ["serde"] }
ruff_python_formatter = { workspace = true, optional = true }
ruff_text_size = { workspace = true }
ty_ide = { workspace = true }
ty_python_semantic = { workspace = true, features = ["serde"] }
ty_vendored = { workspace = true }
red_knot_ide = { workspace = true }
red_knot_python_semantic = { workspace = true, features = ["serde"] }
red_knot_vendored = { workspace = true }
anyhow = { workspace = true }
crossbeam = { workspace = true }
@@ -43,13 +43,13 @@ insta = { workspace = true, features = ["redactions", "ron"] }
[features]
default = ["zstd"]
deflate = ["ty_vendored/deflate"]
deflate = ["red_knot_vendored/deflate"]
schemars = [
"dep:schemars",
"ruff_db/schemars",
"ty_python_semantic/schemars",
"red_knot_python_semantic/schemars",
]
zstd = ["ty_vendored/zstd"]
zstd = ["red_knot_vendored/zstd"]
format = ["ruff_python_formatter"]
[lints]

Some files were not shown because too many files have changed in this diff Show More