Compare commits

..

11 Commits

Author SHA1 Message Date
Zanie Blue
c3b4fab764 [ty] Track narrowing unions to prevent exponential blowup
When inferring a call expression with a union type context, we try narrowing
to each element of the union. If nested calls have the same union as their
parameter type, this would lead to exponential blowup. By tracking which
unions we're already narrowing against, we skip redundant nested narrowing.

On a synthetic benchmark with nested `list_schema()` calls:
- 4-level nesting: >120s → 0.13s
2025-12-18 20:24:33 -06:00
Zanie Blue
abf17c6ef4 [ty] Add short-circuits for union type relation checks
- Short-circuit union-vs-union comparisons when both sides are identical
- Add fast membership check before full relation checking when comparing
  a type against a union
2025-12-18 20:24:18 -06:00
Zanie Blue
b9f65213d0 [ty] Cache Type::is_assignable_to
Add salsa caching to `Type::is_assignable_to` to memoize repeated
assignability checks.
2025-12-18 20:24:03 -06:00
Zanie Blue
ff2553665c [ty] Cache ClassType::is_subclass_of
Add salsa caching to `ClassType::is_subclass_of` to avoid repeated MRO
traversals when checking class relationships.
2025-12-18 20:23:28 -06:00
Zanie Blue
8ebbe6b0f6 Invert case 2025-12-18 18:35:38 -06:00
Zanie Blue
6bc88c90b2 [ty] Cache Type::is_disjoint_from 2025-12-18 18:35:38 -06:00
Zanie Blue
3c694c7d86 [ty] Cache Type::is_subtype_of 2025-12-18 18:35:38 -06:00
Zanie Blue
1603948aae [ty] Cache ClassType::nearest_disjoint_base 2025-12-18 18:35:38 -06:00
Micha Reiser
cb6ba23b0a More lazy negated computations 2025-12-18 18:32:39 -06:00
Micha Reiser
a918833d19 Defer insertion 2025-12-18 18:32:39 -06:00
Micha Reiser
bcf9295973 [ty] Small union builder nits 2025-12-18 18:32:39 -06:00
271 changed files with 4530 additions and 10676 deletions

1
.gitattributes vendored
View File

@@ -22,7 +22,6 @@ 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
crates/ty_python_semantic/resources/mdtest/external/*.lock linguist-generated=true
ruff.schema.json -diff linguist-generated=true text=auto eol=lf
ty.schema.json -diff linguist-generated=true text=auto eol=lf

View File

@@ -4,12 +4,10 @@
self-hosted-runner:
# Various runners we use that aren't recognized out-of-the-box by actionlint:
labels:
- depot-ubuntu-24.04-4
- depot-ubuntu-latest-8
- depot-ubuntu-22.04-16
- depot-ubuntu-22.04-32
- depot-windows-2022-16
- depot-ubuntu-22.04-arm-4
- github-windows-2025-x86_64-8
- github-windows-2025-x86_64-16
- codspeed-macro

View File

@@ -952,7 +952,7 @@ jobs:
tool: cargo-codspeed
- name: "Build benchmarks"
run: cargo codspeed build --features "codspeed,ruff_instrumented" --profile profiling --no-default-features -p ruff_benchmark --bench formatter --bench lexer --bench linter --bench parser
run: cargo codspeed build --features "codspeed,instrumented" --profile profiling --no-default-features -p ruff_benchmark --bench formatter --bench lexer --bench linter --bench parser
- name: "Run benchmarks"
uses: CodSpeedHQ/action@346a2d8a8d9d38909abd0bc3d23f773110f076ad # v4.4.1
@@ -960,9 +960,9 @@ jobs:
mode: simulation
run: cargo codspeed run
benchmarks-instrumented-ty-build:
name: "benchmarks instrumented ty (build)"
runs-on: depot-ubuntu-24.04-4
benchmarks-instrumented-ty:
name: "benchmarks instrumented (ty)"
runs-on: ubuntu-24.04
needs: determine_changes
if: |
github.repository == 'astral-sh/ruff' &&
@@ -971,6 +971,9 @@ jobs:
needs.determine_changes.outputs.ty == 'true'
)
timeout-minutes: 20
permissions:
contents: read # required for actions/checkout
id-token: write # required for OIDC authentication with CodSpeed
steps:
- name: "Checkout Branch"
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
@@ -980,6 +983,7 @@ jobs:
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
- uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
- name: "Install Rust toolchain"
run: rustup show
@@ -990,64 +994,28 @@ jobs:
tool: cargo-codspeed
- name: "Build benchmarks"
run: cargo codspeed build -m instrumentation --features "codspeed,ty_instrumented" --profile profiling --no-default-features -p ruff_benchmark --bench ty
- name: "Upload benchmark binary"
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: benchmarks-instrumented-ty-binary
path: target/codspeed/simulation/ruff_benchmark
retention-days: 1
benchmarks-instrumented-ty-run:
name: "benchmarks instrumented ty (${{ matrix.benchmark }})"
runs-on: ubuntu-24.04
needs: benchmarks-instrumented-ty-build
timeout-minutes: 20
permissions:
contents: read # required for actions/checkout
id-token: write # required for OIDC authentication with CodSpeed
strategy:
fail-fast: false
matrix:
benchmark:
- "check_file|micro|anyio"
- "attrs|hydra|datetype"
steps:
- name: "Checkout Branch"
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
persist-credentials: false
- uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
- name: "Install codspeed"
uses: taiki-e/install-action@3575e532701a5fc614b0c842e4119af4cc5fd16d # v2.62.60
with:
tool: cargo-codspeed
- name: "Download benchmark binary"
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v4.3.0
with:
name: benchmarks-instrumented-ty-binary
path: target/codspeed/simulation/ruff_benchmark
- name: "Restore binary permissions"
run: chmod +x target/codspeed/simulation/ruff_benchmark/ty
run: cargo codspeed build --features "codspeed,instrumented" --profile profiling --no-default-features -p ruff_benchmark --bench ty
- name: "Run benchmarks"
uses: CodSpeedHQ/action@346a2d8a8d9d38909abd0bc3d23f773110f076ad # v4.4.1
with:
mode: simulation
run: cargo codspeed run --bench ty "${{ matrix.benchmark }}"
run: cargo codspeed run
benchmarks-walltime-build:
name: "benchmarks walltime (build)"
# We only run this job if `github.repository == 'astral-sh/ruff'`,
# so hardcoding depot here is fine
runs-on: depot-ubuntu-22.04-arm-4
benchmarks-walltime:
name: "benchmarks walltime (${{ matrix.benchmarks }})"
runs-on: codspeed-macro
needs: determine_changes
if: ${{ github.repository == 'astral-sh/ruff' && !contains(github.event.pull_request.labels.*.name, 'no-test') && (needs.determine_changes.outputs.ty == 'true' || github.ref == 'refs/heads/main') }}
timeout-minutes: 20
permissions:
contents: read # required for actions/checkout
id-token: write # required for OIDC authentication with CodSpeed
strategy:
matrix:
benchmarks:
- "medium|multithreaded"
- "small|large"
steps:
- name: "Checkout Branch"
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
@@ -1068,51 +1036,7 @@ jobs:
tool: cargo-codspeed
- name: "Build benchmarks"
run: cargo codspeed build -m walltime --features "codspeed,ty_walltime" --profile profiling --no-default-features -p ruff_benchmark
- name: "Upload benchmark binary"
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: benchmarks-walltime-binary
path: target/codspeed/walltime/ruff_benchmark
retention-days: 1
benchmarks-walltime-run:
name: "benchmarks walltime (${{ matrix.benchmark }})"
runs-on: codspeed-macro
needs: benchmarks-walltime-build
timeout-minutes: 20
permissions:
contents: read # required for actions/checkout
id-token: write # required for OIDC authentication with CodSpeed
strategy:
matrix:
benchmark:
- colour_science
- "pandas|tanjun|altair"
- "static_frame|sympy"
- "pydantic|multithreaded|freqtrade"
steps:
- name: "Checkout Branch"
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
persist-credentials: false
- uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
- name: "Install codspeed"
uses: taiki-e/install-action@3575e532701a5fc614b0c842e4119af4cc5fd16d # v2.62.60
with:
tool: cargo-codspeed
- name: "Download benchmark binary"
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
with:
name: benchmarks-walltime-binary
path: target/codspeed/walltime/ruff_benchmark
- name: "Restore binary permissions"
run: chmod +x target/codspeed/walltime/ruff_benchmark/ty_walltime
run: cargo codspeed build --features "codspeed,walltime" --profile profiling --no-default-features -p ruff_benchmark
- name: "Run benchmarks"
uses: CodSpeedHQ/action@346a2d8a8d9d38909abd0bc3d23f773110f076ad # v4.4.1
@@ -1123,4 +1047,4 @@ jobs:
CODSPEED_PERF_ENABLED: false
with:
mode: walltime
run: cargo codspeed run --bench ty_walltime -m walltime "${{ matrix.benchmark }}"
run: cargo codspeed run --bench ty_walltime "${{ matrix.benchmarks }}"

View File

@@ -62,7 +62,7 @@ jobs:
name: Create an issue if the daily fuzz surfaced any bugs
runs-on: ubuntu-latest
needs: fuzz
if: ${{ github.repository == 'astral-sh/ruff' && always() && github.event_name == 'schedule' && needs.fuzz.result != 'success' }}
if: ${{ github.repository == 'astral-sh/ruff' && always() && github.event_name == 'schedule' && needs.fuzz.result == 'failure' }}
permissions:
issues: write
steps:

View File

@@ -6,11 +6,6 @@ on:
pull_request:
paths:
- "crates/ty*/**"
- "!crates/ty_ide/**"
- "!crates/ty_server/**"
- "!crates/ty_test/**"
- "!crates/ty_completion_eval/**"
- "!crates/ty_wasm/**"
- "crates/ruff_db"
- "crates/ruff_python_ast"
- "crates/ruff_python_parser"

View File

@@ -16,7 +16,8 @@ name: Sync typeshed
# 3. Once the Windows worker is done, a MacOS worker:
# a. Checks out the branch created by the Linux worker
# b. Syncs all docstrings available on MacOS that are not available on Linux or Windows
# c. Formats the code again
# c. Attempts to update any snapshots that might have changed
# (this sub-step is allowed to fail)
# d. Commits the changes and pushes them to the same upstream branch
# e. Creates a PR against the `main` branch using the branch all three workers have pushed to
# 4. If any of steps 1-3 failed, an issue is created in the `astral-sh/ruff` repository
@@ -197,6 +198,42 @@ jobs:
run: |
rm "${VENDORED_TYPESHED}/pyproject.toml"
git commit -am "Remove pyproject.toml file"
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
- name: "Install Rust toolchain"
if: ${{ success() }}
run: rustup show
- name: "Install mold"
if: ${{ success() }}
uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
- name: "Install cargo nextest"
if: ${{ success() }}
uses: taiki-e/install-action@3575e532701a5fc614b0c842e4119af4cc5fd16d # v2.62.60
with:
tool: cargo-nextest
- name: "Install cargo insta"
if: ${{ success() }}
uses: taiki-e/install-action@3575e532701a5fc614b0c842e4119af4cc5fd16d # v2.62.60
with:
tool: cargo-insta
- name: Update snapshots
if: ${{ success() }}
run: |
cargo r \
--profile=profiling \
-p ty_completion_eval \
-- all --tasks ./crates/ty_completion_eval/completion-evaluation-tasks.csv
# The `cargo insta` docs indicate that `--unreferenced=delete` might be a good option,
# but from local testing it appears to just revert all changes made by `cargo insta test --accept`.
#
# If there were only snapshot-related failures, `cargo insta test --accept` will have exit code 0,
# but if there were also other mdtest failures (for example), it will return a nonzero exit code.
# We don't care about other tests failing here, we just want snapshots updated where possible,
# so we use `|| true` here to ignore the exit code.
cargo insta test --accept --color=always --all-features --test-runner=nextest || true
- name: Commit snapshot changes
if: ${{ success() }}
run: git commit -am "Update snapshots" || echo "No snapshot changes to commit"
- name: Push changes upstream and create a PR
if: ${{ success() }}
run: |
@@ -208,7 +245,7 @@ jobs:
name: Create an issue if the typeshed sync failed
runs-on: ubuntu-latest
needs: [sync, docstrings-windows, docstrings-macos-and-pr]
if: ${{ github.repository == 'astral-sh/ruff' && always() && github.event_name == 'schedule' && (needs.sync.result != 'success' || needs.docstrings-windows.result != 'success' || needs.docstrings-macos-and-pr.result != 'success') }}
if: ${{ github.repository == 'astral-sh/ruff' && always() && github.event_name == 'schedule' && (needs.sync.result == 'failure' || needs.docstrings-windows.result == 'failure' || needs.docstrings-macos-and-pr.result == 'failure') }}
permissions:
issues: write
steps:

View File

@@ -17,6 +17,7 @@ env:
RUSTUP_MAX_RETRIES: 10
RUST_BACKTRACE: 1
REF_NAME: ${{ github.ref_name }}
CF_API_TOKEN_EXISTS: ${{ secrets.CF_API_TOKEN != '' }}
jobs:
ty-ecosystem-analyzer:
@@ -111,13 +112,22 @@ jobs:
cat diff-statistics.md >> "$GITHUB_STEP_SUMMARY"
# NOTE: astral-sh-bot uses this artifact to post comments on PRs.
# Make sure to update the bot if you rename the artifact.
- name: "Upload full report"
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
- name: "Deploy to Cloudflare Pages"
if: ${{ env.CF_API_TOKEN_EXISTS == 'true' }}
id: deploy
uses: cloudflare/wrangler-action@da0e0dfe58b7a431659754fdf3f186c529afbe65 # v3.14.1
with:
name: full-report
path: dist/
apiToken: ${{ secrets.CF_API_TOKEN }}
accountId: ${{ secrets.CF_ACCOUNT_ID }}
command: pages deploy dist --project-name=ty-ecosystem --branch ${{ github.head_ref }} --commit-hash ${GITHUB_SHA}
- name: "Append deployment URL"
if: ${{ env.CF_API_TOKEN_EXISTS == 'true' }}
env:
DEPLOYMENT_URL: ${{ steps.deploy.outputs.pages-deployment-alias-url }}
run: |
echo >> comment.md
echo "**[Full report with detailed diff]($DEPLOYMENT_URL/diff)** ([timing results]($DEPLOYMENT_URL/timing))" >> comment.md
# NOTE: astral-sh-bot uses this artifact to post comments on PRs.
# Make sure to update the bot if you rename the artifact.

View File

@@ -14,6 +14,7 @@ env:
CARGO_TERM_COLOR: always
RUSTUP_MAX_RETRIES: 10
RUST_BACKTRACE: 1
CF_API_TOKEN_EXISTS: ${{ secrets.CF_API_TOKEN != '' }}
jobs:
ty-ecosystem-report:
@@ -30,12 +31,12 @@ jobs:
- name: Install the latest version of uv
uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
with:
enable-cache: true
enable-cache: true # zizmor: ignore[cache-poisoning] acceptable risk for CloudFlare pages artifact
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
with:
workspaces: "ruff"
lookup-only: false
lookup-only: false # zizmor: ignore[cache-poisoning] acceptable risk for CloudFlare pages artifact
- name: Install Rust toolchain
run: rustup show
@@ -69,10 +70,11 @@ jobs:
ecosystem-diagnostics.json \
--output dist/index.html
# NOTE: astral-sh-bot uses this artifact to publish the ecosystem report.
# Make sure to update the bot if you rename the artifact.
- name: "Upload ecosystem report"
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
- name: "Deploy to Cloudflare Pages"
if: ${{ env.CF_API_TOKEN_EXISTS == 'true' }}
id: deploy
uses: cloudflare/wrangler-action@da0e0dfe58b7a431659754fdf3f186c529afbe65 # v3.14.1
with:
name: full-report
path: dist/
apiToken: ${{ secrets.CF_API_TOKEN }}
accountId: ${{ secrets.CF_ACCOUNT_ID }}
command: pages deploy dist --project-name=ty-ecosystem --branch main --commit-hash ${GITHUB_SHA}

View File

@@ -6,11 +6,6 @@ on:
pull_request:
paths:
- "crates/ty*/**"
- "!crates/ty_ide/**"
- "!crates/ty_server/**"
- "!crates/ty_test/**"
- "!crates/ty_completion_eval/**"
- "!crates/ty_wasm/**"
- "crates/ruff_db"
- "crates/ruff_python_ast"
- "crates/ruff_python_parser"

48
Cargo.lock generated
View File

@@ -3129,7 +3129,6 @@ dependencies = [
"salsa",
"schemars",
"serde",
"ty_module_resolver",
"ty_python_semantic",
"zip",
]
@@ -3620,8 +3619,8 @@ checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
[[package]]
name = "salsa"
version = "0.25.2"
source = "git+https://github.com/salsa-rs/salsa.git?rev=ba547145493e310727a323f462c73f8324bf79a5#ba547145493e310727a323f462c73f8324bf79a5"
version = "0.24.0"
source = "git+https://github.com/salsa-rs/salsa.git?rev=55e5e7d32fa3fc189276f35bb04c9438f9aedbd1#55e5e7d32fa3fc189276f35bb04c9438f9aedbd1"
dependencies = [
"boxcar",
"compact_str",
@@ -3645,13 +3644,13 @@ dependencies = [
[[package]]
name = "salsa-macro-rules"
version = "0.25.2"
source = "git+https://github.com/salsa-rs/salsa.git?rev=ba547145493e310727a323f462c73f8324bf79a5#ba547145493e310727a323f462c73f8324bf79a5"
version = "0.24.0"
source = "git+https://github.com/salsa-rs/salsa.git?rev=55e5e7d32fa3fc189276f35bb04c9438f9aedbd1#55e5e7d32fa3fc189276f35bb04c9438f9aedbd1"
[[package]]
name = "salsa-macros"
version = "0.25.2"
source = "git+https://github.com/salsa-rs/salsa.git?rev=ba547145493e310727a323f462c73f8324bf79a5#ba547145493e310727a323f462c73f8324bf79a5"
version = "0.24.0"
source = "git+https://github.com/salsa-rs/salsa.git?rev=55e5e7d32fa3fc189276f35bb04c9438f9aedbd1#55e5e7d32fa3fc189276f35bb04c9438f9aedbd1"
dependencies = [
"proc-macro2",
"quote",
@@ -4376,7 +4375,6 @@ dependencies = [
"tracing-flame",
"tracing-subscriber",
"ty_combine",
"ty_module_resolver",
"ty_project",
"ty_python_semantic",
"ty_server",
@@ -4391,6 +4389,7 @@ dependencies = [
"ordermap",
"ruff_db",
"ruff_python_ast",
"ty_python_semantic",
]
[[package]]
@@ -4408,8 +4407,8 @@ dependencies = [
"tempfile",
"toml",
"ty_ide",
"ty_module_resolver",
"ty_project",
"ty_python_semantic",
"walkdir",
]
@@ -4439,35 +4438,11 @@ dependencies = [
"salsa",
"smallvec",
"tracing",
"ty_module_resolver",
"ty_project",
"ty_python_semantic",
"ty_vendored",
]
[[package]]
name = "ty_module_resolver"
version = "0.0.0"
dependencies = [
"anyhow",
"camino",
"compact_str",
"get-size2",
"insta",
"ruff_db",
"ruff_memory_usage",
"ruff_python_ast",
"ruff_python_stdlib",
"rustc-hash",
"salsa",
"strum",
"strum_macros",
"tempfile",
"thiserror 2.0.17",
"tracing",
"ty_vendored",
]
[[package]]
name = "ty_project"
version = "0.0.0"
@@ -4502,7 +4477,6 @@ dependencies = [
"toml",
"tracing",
"ty_combine",
"ty_module_resolver",
"ty_python_semantic",
"ty_static",
"ty_vendored",
@@ -4555,11 +4529,11 @@ dependencies = [
"strsim",
"strum",
"strum_macros",
"tempfile",
"test-case",
"thiserror 2.0.17",
"tracing",
"ty_combine",
"ty_module_resolver",
"ty_python_semantic",
"ty_static",
"ty_test",
"ty_vendored",
@@ -4598,7 +4572,6 @@ dependencies = [
"tracing-subscriber",
"ty_combine",
"ty_ide",
"ty_module_resolver",
"ty_project",
"ty_python_semantic",
]
@@ -4639,7 +4612,6 @@ dependencies = [
"thiserror 2.0.17",
"toml",
"tracing",
"ty_module_resolver",
"ty_python_semantic",
"ty_static",
"ty_vendored",

View File

@@ -45,7 +45,6 @@ ty = { path = "crates/ty" }
ty_combine = { path = "crates/ty_combine" }
ty_completion_eval = { path = "crates/ty_completion_eval" }
ty_ide = { path = "crates/ty_ide" }
ty_module_resolver = { path = "crates/ty_module_resolver" }
ty_project = { path = "crates/ty_project", default-features = false }
ty_python_semantic = { path = "crates/ty_python_semantic" }
ty_server = { path = "crates/ty_server" }
@@ -147,7 +146,7 @@ regex-automata = { version = "0.4.9" }
rustc-hash = { version = "2.0.0" }
rustc-stable-hash = { version = "0.1.2" }
# When updating salsa, make sure to also update the revision in `fuzz/Cargo.toml`
salsa = { git = "https://github.com/salsa-rs/salsa.git", rev = "ba547145493e310727a323f462c73f8324bf79a5", default-features = false, features = [
salsa = { git = "https://github.com/salsa-rs/salsa.git", rev = "55e5e7d32fa3fc189276f35bb04c9438f9aedbd1", default-features = false, features = [
"compact_str",
"macros",
"salsa_unstable",

View File

@@ -14,6 +14,6 @@ info:
success: false
exit_code: 1
----- stdout -----
::error title=Ruff (unformatted),file=[TMP]/input.py,line=1,endLine=2::input.py:1:1: unformatted: File would be reformatted
::error title=Ruff (unformatted),file=[TMP]/input.py,line=1,col=1,endLine=2,endColumn=1::input.py:1:1: unformatted: File would be reformatted
----- stderr -----

View File

@@ -19,32 +19,32 @@ doctest = false
[[bench]]
name = "linter"
harness = false
required-features = ["ruff_instrumented"]
required-features = ["instrumented"]
[[bench]]
name = "lexer"
harness = false
required-features = ["ruff_instrumented"]
required-features = ["instrumented"]
[[bench]]
name = "parser"
harness = false
required-features = ["ruff_instrumented"]
required-features = ["instrumented"]
[[bench]]
name = "formatter"
harness = false
required-features = ["ruff_instrumented"]
required-features = ["instrumented"]
[[bench]]
name = "ty"
harness = false
required-features = ["ty_instrumented"]
required-features = ["instrumented"]
[[bench]]
name = "ty_walltime"
harness = false
required-features = ["ty_walltime"]
required-features = ["walltime"]
[dependencies]
ruff_db = { workspace = true, features = ["testing"] }
@@ -67,32 +67,25 @@ tracing = { workspace = true }
workspace = true
[features]
default = ["ty_instrumented", "ty_walltime", "ruff_instrumented"]
# Enables the ruff instrumented benchmarks
ruff_instrumented = [
default = ["instrumented", "walltime"]
# Enables the benchmark that should only run with codspeed's instrumented runner
instrumented = [
"criterion",
"ruff_linter",
"ruff_python_formatter",
"ruff_python_parser",
"ruff_python_trivia",
"mimalloc",
"tikv-jemallocator",
]
# Enables the ty instrumented benchmarks
ty_instrumented = [
"criterion",
"ty_project",
"ruff_python_trivia",
]
codspeed = ["codspeed-criterion-compat"]
# Enables the ty_walltime benchmarks
ty_walltime = ["ruff_db/os", "ty_project", "divan"]
# Enables benchmark that should only run with codspeed's walltime runner.
walltime = ["ruff_db/os", "ty_project", "divan"]
[target.'cfg(target_os = "windows")'.dependencies]
mimalloc = { workspace = true, optional = true }
[target.'cfg(target_os = "windows")'.dev-dependencies]
mimalloc = { workspace = true }
[target.'cfg(all(not(target_os = "windows"), not(target_os = "openbsd"), any(target_arch = "x86_64", target_arch = "aarch64", target_arch = "powerpc64", target_arch = "riscv64")))'.dependencies]
tikv-jemallocator = { workspace = true, optional = true }
[target.'cfg(all(not(target_os = "windows"), not(target_os = "openbsd"), any(target_arch = "x86_64", target_arch = "aarch64", target_arch = "powerpc64", target_arch = "riscv64")))'.dev-dependencies]
tikv-jemallocator = { workspace = true }
[dev-dependencies]
rustc-hash = { workspace = true }

View File

@@ -194,7 +194,7 @@ static SYMPY: Benchmark = Benchmark::new(
max_dep_date: "2025-06-17",
python_version: PythonVersion::PY312,
},
13106,
13100,
);
static TANJUN: Benchmark = Benchmark::new(
@@ -235,55 +235,30 @@ fn run_single_threaded(bencher: Bencher, benchmark: &Benchmark) {
});
}
#[bench(sample_size = 2, sample_count = 3)]
fn altair(bencher: Bencher) {
run_single_threaded(bencher, &ALTAIR);
#[bench(args=[&ALTAIR, &FREQTRADE, &TANJUN], sample_size=2, sample_count=3)]
fn small(bencher: Bencher, benchmark: &Benchmark) {
run_single_threaded(bencher, benchmark);
}
#[bench(sample_size = 2, sample_count = 3)]
fn freqtrade(bencher: Bencher) {
run_single_threaded(bencher, &FREQTRADE);
#[bench(args=[&COLOUR_SCIENCE, &PANDAS, &STATIC_FRAME], sample_size=1, sample_count=3)]
fn medium(bencher: Bencher, benchmark: &Benchmark) {
run_single_threaded(bencher, benchmark);
}
#[bench(sample_size = 2, sample_count = 3)]
fn tanjun(bencher: Bencher) {
run_single_threaded(bencher, &TANJUN);
#[bench(args=[&SYMPY, &PYDANTIC], sample_size=1, sample_count=2)]
fn large(bencher: Bencher, benchmark: &Benchmark) {
run_single_threaded(bencher, benchmark);
}
#[bench(sample_size = 2, sample_count = 3)]
fn pydantic(bencher: Bencher) {
run_single_threaded(bencher, &PYDANTIC);
}
#[bench(sample_size = 1, sample_count = 3)]
fn static_frame(bencher: Bencher) {
run_single_threaded(bencher, &STATIC_FRAME);
}
#[bench(sample_size = 1, sample_count = 2)]
fn colour_science(bencher: Bencher) {
run_single_threaded(bencher, &COLOUR_SCIENCE);
}
#[bench(sample_size = 1, sample_count = 2)]
fn pandas(bencher: Bencher) {
run_single_threaded(bencher, &PANDAS);
}
#[bench(sample_size = 1, sample_count = 2)]
fn sympy(bencher: Bencher) {
run_single_threaded(bencher, &SYMPY);
}
#[bench(sample_size = 3, sample_count = 8)]
fn multithreaded(bencher: Bencher) {
#[bench(args=[&ALTAIR], sample_size=3, sample_count=8)]
fn multithreaded(bencher: Bencher, benchmark: &Benchmark) {
let thread_pool = ThreadPoolBuilder::new().build().unwrap();
bencher
.with_inputs(|| ALTAIR.setup_iteration())
.with_inputs(|| benchmark.setup_iteration())
.bench_local_values(|db| {
thread_pool.install(|| {
check_project(&db, ALTAIR.project.name, ALTAIR.max_diagnostics);
check_project(&db, benchmark.project.name, benchmark.max_diagnostics);
db
})
});

View File

@@ -1,6 +1,6 @@
use std::path::PathBuf;
#[cfg(any(feature = "ty_instrumented", feature = "ruff_instrumented"))]
#[cfg(feature = "instrumented")]
pub mod criterion;
pub mod real_world_projects;

View File

@@ -1,51 +0,0 @@
use std::sync::Arc;
use std::sync::atomic::AtomicBool;
/// Signals a [`CancellationToken`] that it should be canceled.
#[derive(Debug, Clone)]
pub struct CancellationTokenSource {
cancelled: Arc<AtomicBool>,
}
impl Default for CancellationTokenSource {
fn default() -> Self {
Self::new()
}
}
impl CancellationTokenSource {
pub fn new() -> Self {
Self {
cancelled: Arc::new(AtomicBool::new(false)),
}
}
pub fn is_cancellation_requested(&self) -> bool {
self.cancelled.load(std::sync::atomic::Ordering::Relaxed)
}
/// Creates a new token that uses this source.
pub fn token(&self) -> CancellationToken {
CancellationToken {
cancelled: self.cancelled.clone(),
}
}
/// Requests cancellation for operations using this token.
pub fn cancel(&self) {
self.cancelled
.store(true, std::sync::atomic::Ordering::Relaxed);
}
}
/// Token signals whether an operation should be canceled.
#[derive(Debug, Clone)]
pub struct CancellationToken {
cancelled: Arc<AtomicBool>,
}
impl CancellationToken {
pub fn is_cancelled(&self) -> bool {
self.cancelled.load(std::sync::atomic::Ordering::Relaxed)
}
}

View File

@@ -1,4 +1,4 @@
use std::{borrow::Cow, fmt::Formatter, path::Path, sync::Arc};
use std::{fmt::Formatter, path::Path, sync::Arc};
use ruff_diagnostics::{Applicability, Fix};
use ruff_source_file::{LineColumn, SourceCode, SourceFile};
@@ -11,7 +11,6 @@ pub use self::render::{
ceil_char_boundary,
github::{DisplayGithubDiagnostics, GithubRenderer},
};
use crate::cancellation::CancellationToken;
use crate::{Db, files::File};
mod render;
@@ -411,6 +410,11 @@ impl Diagnostic {
self.id().is_invalid_syntax()
}
/// Returns the message body to display to the user.
pub fn body(&self) -> &str {
self.primary_message()
}
/// Returns the message of the first sub-diagnostic with a `Help` severity.
///
/// Note that this is used as the fix title/suggestion for some of Ruff's output formats, but in
@@ -1308,8 +1312,6 @@ pub struct DisplayDiagnosticConfig {
show_fix_diff: bool,
/// The lowest applicability that should be shown when reporting diagnostics.
fix_applicability: Applicability,
cancellation_token: Option<CancellationToken>,
}
impl DisplayDiagnosticConfig {
@@ -1383,20 +1385,6 @@ impl DisplayDiagnosticConfig {
pub fn fix_applicability(&self) -> Applicability {
self.fix_applicability
}
pub fn with_cancellation_token(
mut self,
token: Option<CancellationToken>,
) -> DisplayDiagnosticConfig {
self.cancellation_token = token;
self
}
pub fn is_canceled(&self) -> bool {
self.cancellation_token
.as_ref()
.is_some_and(|token| token.is_cancelled())
}
}
impl Default for DisplayDiagnosticConfig {
@@ -1410,7 +1398,6 @@ impl Default for DisplayDiagnosticConfig {
show_fix_status: false,
show_fix_diff: false,
fix_applicability: Applicability::Safe,
cancellation_token: None,
}
}
}
@@ -1487,15 +1474,6 @@ pub enum ConciseMessage<'a> {
Custom(&'a str),
}
impl<'a> ConciseMessage<'a> {
pub fn to_str(&self) -> Cow<'a, str> {
match self {
ConciseMessage::MainDiagnostic(s) | ConciseMessage::Custom(s) => Cow::Borrowed(s),
ConciseMessage::Both { .. } => Cow::Owned(self.to_string()),
}
}
}
impl std::fmt::Display for ConciseMessage<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match *self {
@@ -1512,16 +1490,6 @@ impl std::fmt::Display for ConciseMessage<'_> {
}
}
#[cfg(feature = "serde")]
impl serde::Serialize for ConciseMessage<'_> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.collect_str(self)
}
}
/// A diagnostic message string.
///
/// This is, for all intents and purposes, equivalent to a `Box<str>`.

View File

@@ -52,7 +52,7 @@ impl AzureRenderer<'_> {
f,
"code={code};]{body}",
code = diag.secondary_code_or_id(),
body = diag.concise_message(),
body = diag.body(),
)?;
}

View File

@@ -28,10 +28,6 @@ impl<'a> ConciseRenderer<'a> {
let sep = fmt_styled(":", stylesheet.separator);
for diag in diagnostics {
if self.config.is_canceled() {
return Ok(());
}
if let Some(span) = diag.primary_span() {
write!(
f,

View File

@@ -53,10 +53,6 @@ impl<'a> FullRenderer<'a> {
.hyperlink(stylesheet.hyperlink);
for diag in diagnostics {
if self.config.is_canceled() {
return Ok(());
}
let resolved = Resolved::new(self.resolver, diag, self.config);
let renderable = resolved.to_renderable(self.config.context);
for diag in renderable.diagnostics.iter() {

View File

@@ -49,26 +49,14 @@ impl<'a> GithubRenderer<'a> {
}
.unwrap_or_default();
// GitHub Actions workflow commands have constraints on error annotations:
// - `col` and `endColumn` cannot be set if `line` and `endLine` are different
// See: https://github.com/astral-sh/ruff/issues/22074
if start_location.line == end_location.line {
write!(
f,
",line={row},col={column},endLine={end_row},endColumn={end_column}::",
row = start_location.line,
column = start_location.column,
end_row = end_location.line,
end_column = end_location.column,
)?;
} else {
write!(
f,
",line={row},endLine={end_row}::",
row = start_location.line,
end_row = end_location.line,
)?;
}
write!(
f,
",line={row},col={column},endLine={end_row},endColumn={end_column}::",
row = start_location.line,
column = start_location.column,
end_row = end_location.line,
end_column = end_location.column,
)?;
write!(
f,
@@ -87,7 +75,7 @@ impl<'a> GithubRenderer<'a> {
write!(f, "{id}:", id = diagnostic.id())?;
}
writeln!(f, " {}", diagnostic.concise_message())?;
writeln!(f, " {}", diagnostic.body())?;
}
Ok(())

View File

@@ -98,7 +98,7 @@ impl Serialize for SerializedMessages<'_> {
}
fingerprints.insert(message_fingerprint);
let description = diagnostic.concise_message();
let description = diagnostic.body();
let check_name = diagnostic.secondary_code_or_id();
let severity = match diagnostic.severity() {
Severity::Info => "info",

View File

@@ -6,7 +6,7 @@ use ruff_notebook::NotebookIndex;
use ruff_source_file::{LineColumn, OneIndexed};
use ruff_text_size::Ranged;
use crate::diagnostic::{ConciseMessage, Diagnostic, DiagnosticSource, DisplayDiagnosticConfig};
use crate::diagnostic::{Diagnostic, DiagnosticSource, DisplayDiagnosticConfig};
use super::FileResolver;
@@ -101,7 +101,7 @@ pub(super) fn diagnostic_to_json<'a>(
JsonDiagnostic {
code: diagnostic.secondary_code_or_id(),
url: diagnostic.documentation_url(),
message: diagnostic.concise_message(),
message: diagnostic.body(),
fix,
cell: notebook_cell_index,
location: start_location.map(JsonLocation::from),
@@ -113,7 +113,7 @@ pub(super) fn diagnostic_to_json<'a>(
JsonDiagnostic {
code: diagnostic.secondary_code_or_id(),
url: diagnostic.documentation_url(),
message: diagnostic.concise_message(),
message: diagnostic.body(),
fix,
cell: notebook_cell_index,
location: Some(start_location.unwrap_or_default().into()),
@@ -226,7 +226,7 @@ pub(crate) struct JsonDiagnostic<'a> {
filename: Option<&'a str>,
fix: Option<JsonFix<'a>>,
location: Option<JsonLocation>,
message: ConciseMessage<'a>,
message: &'a str,
noqa_row: Option<OneIndexed>,
url: Option<&'a str>,
}

View File

@@ -56,17 +56,17 @@ impl<'a> JunitRenderer<'a> {
start_location: location,
} = diagnostic;
let mut status = TestCaseStatus::non_success(NonSuccessKind::Failure);
status.set_message(diagnostic.concise_message().to_str());
status.set_message(diagnostic.body());
if let Some(location) = location {
status.set_description(format!(
"line {row}, col {col}, {body}",
row = location.line,
col = location.column,
body = diagnostic.concise_message()
body = diagnostic.body()
));
} else {
status.set_description(diagnostic.concise_message().to_str());
status.set_description(diagnostic.body());
}
let code = diagnostic

View File

@@ -55,7 +55,7 @@ impl PylintRenderer<'_> {
f,
"{path}:{row}: [{code}] {body}",
path = filename,
body = diagnostic.concise_message()
body = diagnostic.body()
)?;
}

View File

@@ -5,7 +5,7 @@ use ruff_diagnostics::{Edit, Fix};
use ruff_source_file::{LineColumn, SourceCode};
use ruff_text_size::Ranged;
use crate::diagnostic::{ConciseMessage, Diagnostic};
use crate::diagnostic::Diagnostic;
use super::FileResolver;
@@ -76,7 +76,7 @@ fn diagnostic_to_rdjson<'a>(
let edits = diagnostic.fix().map(Fix::edits).unwrap_or_default();
RdjsonDiagnostic {
message: diagnostic.concise_message(),
message: diagnostic.body(),
location,
code: RdjsonCode {
value: diagnostic
@@ -155,7 +155,7 @@ struct RdjsonDiagnostic<'a> {
code: RdjsonCode<'a>,
#[serde(skip_serializing_if = "Option::is_none")]
location: Option<RdjsonLocation<'a>>,
message: ConciseMessage<'a>,
message: &'a str,
#[serde(skip_serializing_if = "Vec::is_empty")]
suggestions: Vec<RdjsonSuggestion<'a>>,
}

View File

@@ -2,5 +2,5 @@
source: crates/ruff_db/src/diagnostic/render/github.rs
expression: env.render_diagnostics(&diagnostics)
---
::error title=ty (invalid-syntax),file=/syntax_errors.py,line=1,endLine=2::syntax_errors.py:1:15: invalid-syntax: Expected one or more symbol names after import
::error title=ty (invalid-syntax),file=/syntax_errors.py,line=3,endLine=4::syntax_errors.py:3:12: invalid-syntax: Expected ')', found newline
::error title=ty (invalid-syntax),file=/syntax_errors.py,line=1,col=15,endLine=2,endColumn=1::syntax_errors.py:1:15: invalid-syntax: Expected one or more symbol names after import
::error title=ty (invalid-syntax),file=/syntax_errors.py,line=3,col=12,endLine=4,endColumn=1::syntax_errors.py:3:12: invalid-syntax: Expected ')', found newline

View File

@@ -12,7 +12,6 @@ use std::hash::BuildHasherDefault;
use std::num::NonZeroUsize;
use ty_static::EnvVars;
pub mod cancellation;
pub mod diagnostic;
pub mod display;
pub mod file_revision;

View File

@@ -275,16 +275,16 @@ impl OsSystem {
/// instead of at least one system call for each component between `path` and `prefix`.
///
/// However, using `canonicalize` to resolve the path's casing doesn't work in two cases:
/// * if `path` is a symlink, `canonicalize` returns the symlink's target and not the symlink's source path.
/// * on Windows: If `path` is a mapped network drive, `canonicalize` returns the UNC path
/// (e.g. `Z:\` is mapped to `\\server\share` and `canonicalize` returns `\\?\UNC\server\share`).
/// * if `path` is a symlink because `canonicalize` then returns the symlink's target and not the symlink's source path.
/// * on Windows: If `path` is a mapped network drive because `canonicalize` then returns the UNC path
/// (e.g. `Z:\` is mapped to `\\server\share` and `canonicalize` then returns `\\?\UNC\server\share`).
///
/// Symlinks and mapped network drives should be rare enough that this fast path is worth trying first,
/// even if it comes at a cost for those rare use cases.
fn path_exists_case_sensitive_fast(&self, path: &SystemPath) -> Option<bool> {
// This is a more forgiving version of `dunce::simplified` that removes all `\\?\` prefixes on Windows.
// We use this more forgiving version because we don't intend on using either path for anything other than comparison
// and the prefix is only relevant when passing the path to other programs and it's longer than 200 something
// and the prefix is only relevant when passing the path to other programs and its longer than 200 something
// characters.
fn simplify_ignore_verbatim(path: &SystemPath) -> &SystemPath {
if cfg!(windows) {
@@ -298,7 +298,9 @@ impl OsSystem {
}
}
let Ok(canonicalized) = path.as_std_path().canonicalize() else {
let simplified = simplify_ignore_verbatim(path);
let Ok(canonicalized) = simplified.as_std_path().canonicalize() else {
// The path doesn't exist or can't be accessed. The path doesn't exist.
return Some(false);
};
@@ -307,13 +309,12 @@ impl OsSystem {
// The original path is valid UTF8 but the canonicalized path isn't. This definitely suggests
// that a symlink is involved. Fall back to the slow path.
tracing::debug!(
"Falling back to the slow case-sensitive path existence check because the canonicalized path of `{path}` is not valid UTF-8"
"Falling back to the slow case-sensitive path existence check because the canonicalized path of `{simplified}` is not valid UTF-8"
);
return None;
};
let simplified_canonicalized = simplify_ignore_verbatim(&canonicalized);
let simplified = simplify_ignore_verbatim(path);
// Test if the paths differ by anything other than casing. If so, that suggests that
// `path` pointed to a symlink (or some other none reversible path normalization happened).

View File

@@ -1,13 +1,11 @@
//! Generate a Markdown-compatible listing of configuration options for `pyproject.toml`.
use std::borrow::Cow;
use std::{fmt::Write, path::PathBuf};
use anyhow::bail;
use itertools::Itertools;
use pretty_assertions::StrComparison;
use std::{fmt::Write, path::PathBuf};
use ruff_options_metadata::{OptionField, OptionSet, OptionsMetadata, Visit};
use ruff_python_trivia::textwrap;
use ty_project::metadata::Options;
use crate::{
@@ -167,69 +165,62 @@ fn emit_field(output: &mut String, name: &str, field: &OptionField, parents: &[S
let _ = writeln!(output, "**Default value**: `{}`", field.default);
output.push('\n');
let _ = writeln!(output, "**Type**: `{}`", field.value_type);
output.push('\n');
output.push_str("**Example usage**:\n\n");
output.push_str(&format_example(
"pyproject.toml",
&format_header(
field.scope,
field.example,
parents,
ConfigurationFile::PyprojectToml,
),
field.example,
));
output.push('\n');
}
for configuration_file in [ConfigurationFile::PyprojectToml, ConfigurationFile::TyToml] {
let (header, example) =
format_snippet(field.scope, field.example, parents, configuration_file);
output.push_str(&format_tab(configuration_file.name(), &header, &example));
output.push('\n');
fn format_example(title: &str, header: &str, content: &str) -> String {
if header.is_empty() {
format!("```toml title=\"{title}\"\n{content}\n```\n",)
} else {
format!("```toml title=\"{title}\"\n{header}\n{content}\n```\n",)
}
}
fn format_tab(tab_name: &str, header: &str, content: &str) -> String {
let header = if header.is_empty() {
String::new()
} else {
format!("\n {header}")
};
format!(
"=== \"{}\"\n\n ```toml{}\n{}\n ```\n",
tab_name,
header,
textwrap::indent(content, " ")
)
}
/// Format the TOML header for the example usage for a given option.
///
/// For example: `[tool.ty.rules]`.
fn format_snippet<'a>(
/// For example: `[tool.ruff.format]` or `[tool.ruff.lint.isort]`.
fn format_header(
scope: Option<&str>,
example: &'a str,
example: &str,
parents: &[Set],
configuration: ConfigurationFile,
) -> (String, Cow<'a, str>) {
let mut example = Cow::Borrowed(example);
) -> String {
let tool_parent = match configuration {
ConfigurationFile::PyprojectToml => Some("tool.ty"),
ConfigurationFile::TyToml => None,
};
let header = configuration
.parent_table()
let header = tool_parent
.into_iter()
.chain(parents.iter().filter_map(|parent| parent.name()))
.chain(scope)
.join(".");
// Rewrite examples starting with `[tool.ty]` or `[[tool.ty]]` to their `ty.toml` equivalent.
if matches!(configuration, ConfigurationFile::TyToml) {
example = example.replace("[tool.ty.", "[").into();
}
// Ex) `[[tool.ty.xx]]`
if example.starts_with(&format!("[[{header}")) {
return (String::new(), example);
return String::new();
}
// Ex) `[tool.ty.rules]`
if example.starts_with(&format!("[{header}")) {
return (String::new(), example);
return String::new();
}
if header.is_empty() {
(String::new(), example)
String::new()
} else {
(format!("[{header}]"), example)
format!("[{header}]")
}
}
@@ -252,25 +243,10 @@ impl Visit for CollectOptionsVisitor {
#[derive(Debug, Copy, Clone)]
enum ConfigurationFile {
PyprojectToml,
#[expect(dead_code)]
TyToml,
}
impl ConfigurationFile {
const fn name(self) -> &'static str {
match self {
Self::PyprojectToml => "pyproject.toml",
Self::TyToml => "ty.toml",
}
}
const fn parent_table(self) -> Option<&'static str> {
match self {
Self::PyprojectToml => Some("tool.ty"),
Self::TyToml => None,
}
}
}
#[cfg(test)]
mod tests {
use anyhow::Result;

View File

@@ -63,7 +63,12 @@ fn generate_markdown() -> String {
let _ = writeln!(&mut output, "# Rules\n");
let mut lints: Vec<_> = registry.lints().iter().collect();
lints.sort_by_key(|a| a.name());
lints.sort_by(|a, b| {
a.default_level()
.cmp(&b.default_level())
.reverse()
.then_with(|| a.name().cmp(&b.name()))
});
for lint in lints {
let _ = writeln!(&mut output, "## `{rule_name}`\n", rule_name = lint.name());
@@ -114,7 +119,7 @@ fn generate_markdown() -> String {
let _ = writeln!(
&mut output,
r#"<small>
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of '{level}'."><code>{level}</code></a> ·
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of '{level}'."><code>{level}</code></a> ·
{status_text} ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20{encoded_name}" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/{file}#L{line}" target="_blank">View source</a>

View File

@@ -16,7 +16,6 @@ ruff_linter = { workspace = true }
ruff_macros = { workspace = true }
ruff_python_ast = { workspace = true }
ruff_python_parser = { workspace = true }
ty_module_resolver = { workspace = true }
ty_python_semantic = { workspace = true }
anyhow = { workspace = true }

View File

@@ -3,7 +3,7 @@ use ruff_python_ast::visitor::source_order::{
SourceOrderVisitor, walk_expr, walk_module, walk_stmt,
};
use ruff_python_ast::{self as ast, Expr, Mod, Stmt};
use ty_module_resolver::ModuleName;
use ty_python_semantic::ModuleName;
/// Collect all imports for a given Python file.
#[derive(Default, Debug)]

View File

@@ -7,11 +7,10 @@ use ruff_db::files::{File, Files};
use ruff_db::system::{OsSystem, System, SystemPathBuf};
use ruff_db::vendored::{VendoredFileSystem, VendoredFileSystemBuilder};
use ruff_python_ast::PythonVersion;
use ty_module_resolver::{SearchPathSettings, SearchPaths};
use ty_python_semantic::lint::{LintRegistry, RuleSelection};
use ty_python_semantic::{
AnalysisSettings, Db, Program, ProgramSettings, PythonEnvironment, PythonPlatform,
PythonVersionSource, PythonVersionWithSource, SysPrefixPathOrigin, default_lint_registry,
Db, Program, ProgramSettings, PythonEnvironment, PythonPlatform, PythonVersionSource,
PythonVersionWithSource, SearchPathSettings, SysPrefixPathOrigin, default_lint_registry,
};
static EMPTY_VENDORED: std::sync::LazyLock<VendoredFileSystem> = std::sync::LazyLock::new(|| {
@@ -27,7 +26,6 @@ pub struct ModuleDb {
files: Files,
system: OsSystem,
rule_selection: Arc<RuleSelection>,
analysis_settings: Arc<AnalysisSettings>,
}
impl ModuleDb {
@@ -87,13 +85,6 @@ impl SourceDb for ModuleDb {
}
}
#[salsa::db]
impl ty_module_resolver::Db for ModuleDb {
fn search_paths(&self) -> &SearchPaths {
Program::get(self).search_paths(self)
}
}
#[salsa::db]
impl Db for ModuleDb {
fn should_check_file(&self, file: File) -> bool {
@@ -111,10 +102,6 @@ impl Db for ModuleDb {
fn verbose(&self) -> bool {
false
}
fn analysis_settings(&self) -> &AnalysisSettings {
&self.analysis_settings
}
}
#[salsa::db]

View File

@@ -1,6 +1,6 @@
use ruff_db::files::{File, FilePath, system_path_to_file};
use ruff_db::system::SystemPath;
use ty_module_resolver::{
use ty_python_semantic::{
ModuleName, resolve_module, resolve_module_confident, resolve_real_module,
resolve_real_module_confident,
};

View File

@@ -197,7 +197,7 @@ impl Display for RuleCodeAndBody<'_> {
f,
"{fix}{body}",
fix = format_args!("[{}] ", "*".cyan()),
body = self.message.concise_message(),
body = self.message.body(),
);
}
}
@@ -208,14 +208,14 @@ impl Display for RuleCodeAndBody<'_> {
f,
"{code} {body}",
code = code.red().bold(),
body = self.message.concise_message(),
body = self.message.body(),
)
} else {
write!(
f,
"{code}: {body}",
code = self.message.id().as_str().red().bold(),
body = self.message.concise_message(),
body = self.message.body(),
)
}
}

View File

@@ -334,7 +334,7 @@ impl<'a> SarifResult<'a> {
rule_id: RuleCode::from(diagnostic),
level: "error".to_string(),
message: SarifMessage {
text: diagnostic.concise_message().to_string(),
text: diagnostic.body().to_string(),
},
fixes: Self::fix(diagnostic, &uri).into_iter().collect(),
locations: vec![SarifLocation {

View File

@@ -37,11 +37,10 @@ use crate::{Fix, FixAvailability, Violation};
/// import logging
///
/// logging.basicConfig(level=logging.INFO)
/// logger = logging.getLogger(__name__)
///
///
/// def sum_less_than_four(a, b):
/// logger.debug("Calling sum_less_than_four")
/// logging.debug("Calling sum_less_than_four")
/// return a + b < 4
/// ```
///

View File

@@ -243,7 +243,7 @@ fn to_lsp_diagnostic(
) -> (usize, lsp_types::Diagnostic) {
let diagnostic_range = diagnostic.range().unwrap_or_default();
let name = diagnostic.name();
let body = diagnostic.concise_message().to_string();
let body = diagnostic.body().to_string();
let fix = diagnostic.fix();
let suggestion = diagnostic.first_help_text();
let code = diagnostic.secondary_code();

View File

@@ -241,7 +241,7 @@ impl Workspace {
let range = msg.range().unwrap_or_default();
ExpandedMessage {
code: msg.secondary_code_or_id().to_string(),
message: msg.concise_message().to_string(),
message: msg.body().to_string(),
start_location: source_code
.source_location(range.start(), self.position_encoding)
.into(),

View File

@@ -42,7 +42,6 @@ wild = { workspace = true }
[dev-dependencies]
ruff_db = { workspace = true, features = ["testing"] }
ruff_python_trivia = { workspace = true }
ty_module_resolver = { workspace = true }
dunce = { workspace = true }
insta = { workspace = true, features = ["filters"] }

1
crates/ty/docs/cli.md generated
View File

@@ -56,7 +56,6 @@ over all configuration files.</p>
</dd><dt id="ty-check--exit-zero"><a href="#ty-check--exit-zero"><code>--exit-zero</code></a></dt><dd><p>Always use exit code 0, even when there are error-level diagnostics</p>
</dd><dt id="ty-check--extra-search-path"><a href="#ty-check--extra-search-path"><code>--extra-search-path</code></a> <i>path</i></dt><dd><p>Additional path to use as a module-resolution source (can be passed multiple times).</p>
<p>This is an advanced option that should usually only be used for first-party or third-party modules that are not installed into your Python environment in a conventional way. Use <code>--python</code> to point ty to your Python environment if it is in an unusual location.</p>
</dd><dt id="ty-check--force-exclude"><a href="#ty-check--force-exclude"><code>--force-exclude</code></a></dt><dd><p>Enforce exclusions, even for paths passed to ty directly on the command-line. Use <code>--no-force-exclude</code> to disable</p>
</dd><dt id="ty-check--help"><a href="#ty-check--help"><code>--help</code></a>, <code>-h</code></dt><dd><p>Print help (see a summary with '-h')</p>
</dd><dt id="ty-check--ignore"><a href="#ty-check--ignore"><code>--ignore</code></a> <i>rule</i></dt><dd><p>Disables the rule. Can be specified multiple times.</p>
</dd><dt id="ty-check--no-progress"><a href="#ty-check--no-progress"><code>--no-progress</code></a></dt><dd><p>Hide all progress outputs.</p>

View File

@@ -20,59 +20,11 @@ Valid severities are:
**Example usage**:
=== "pyproject.toml"
```toml
[tool.ty.rules]
possibly-unresolved-reference = "warn"
division-by-zero = "ignore"
```
=== "ty.toml"
```toml
[rules]
possibly-unresolved-reference = "warn"
division-by-zero = "ignore"
```
---
## `analysis`
### `respect-type-ignore-comments`
Whether ty should respect `type: ignore` comments.
When set to `false`, `type: ignore` comments are treated like any other normal
comment and can't be used to suppress ty errors (you have to use `ty: ignore` instead).
Setting this option can be useful when using ty alongside other type checkers or when
you prefer using `ty: ignore` over `type: ignore`.
Defaults to `true`.
**Default value**: `true`
**Type**: `bool`
**Example usage**:
=== "pyproject.toml"
```toml
[tool.ty.analysis]
# Disable support for `type: ignore` comments
respect-type-ignore-comments = false
```
=== "ty.toml"
```toml
[analysis]
# Disable support for `type: ignore` comments
respect-type-ignore-comments = false
```
```toml title="pyproject.toml"
[tool.ty.rules]
possibly-unresolved-reference = "warn"
division-by-zero = "ignore"
```
---
@@ -95,19 +47,10 @@ configuration setting.
**Example usage**:
=== "pyproject.toml"
```toml
[tool.ty.environment]
extra-paths = ["./shared/my-search-path"]
```
=== "ty.toml"
```toml
[environment]
extra-paths = ["./shared/my-search-path"]
```
```toml title="pyproject.toml"
[tool.ty.environment]
extra-paths = ["./shared/my-search-path"]
```
---
@@ -135,19 +78,10 @@ This option can be used to point to virtual or system Python environments.
**Example usage**:
=== "pyproject.toml"
```toml
[tool.ty.environment]
python = "./custom-venv-location/.venv"
```
=== "ty.toml"
```toml
[environment]
python = "./custom-venv-location/.venv"
```
```toml title="pyproject.toml"
[tool.ty.environment]
python = "./custom-venv-location/.venv"
```
---
@@ -171,21 +105,11 @@ If no platform is specified, ty will use the current platform:
**Example usage**:
=== "pyproject.toml"
```toml
[tool.ty.environment]
# Tailor type stubs and conditionalized type definitions to windows.
python-platform = "win32"
```
=== "ty.toml"
```toml
[environment]
# Tailor type stubs and conditionalized type definitions to windows.
python-platform = "win32"
```
```toml title="pyproject.toml"
[tool.ty.environment]
# Tailor type stubs and conditionalized type definitions to windows.
python-platform = "win32"
```
---
@@ -215,19 +139,10 @@ to reflect the differing contents of the standard library across Python versions
**Example usage**:
=== "pyproject.toml"
```toml
[tool.ty.environment]
python-version = "3.12"
```
=== "ty.toml"
```toml
[environment]
python-version = "3.12"
```
```toml title="pyproject.toml"
[tool.ty.environment]
python-version = "3.12"
```
---
@@ -252,21 +167,11 @@ it will also be included in the first party search path.
**Example usage**:
=== "pyproject.toml"
```toml
[tool.ty.environment]
# Multiple directories (priority order)
root = ["./src", "./lib", "./vendor"]
```
=== "ty.toml"
```toml
[environment]
# Multiple directories (priority order)
root = ["./src", "./lib", "./vendor"]
```
```toml title="pyproject.toml"
[tool.ty.environment]
# Multiple directories (priority order)
root = ["./src", "./lib", "./vendor"]
```
---
@@ -282,19 +187,10 @@ bundled as a zip file in the binary
**Example usage**:
=== "pyproject.toml"
```toml
[tool.ty.environment]
typeshed = "/path/to/custom/typeshed"
```
=== "ty.toml"
```toml
[environment]
typeshed = "/path/to/custom/typeshed"
```
```toml title="pyproject.toml"
[tool.ty.environment]
typeshed = "/path/to/custom/typeshed"
```
---
@@ -344,29 +240,15 @@ If not specified, defaults to `[]` (excludes no files).
**Example usage**:
=== "pyproject.toml"
```toml
[[tool.ty.overrides]]
exclude = [
"generated",
"*.proto",
"tests/fixtures/**",
"!tests/fixtures/important.py" # Include this one file
]
```
=== "ty.toml"
```toml
[[overrides]]
exclude = [
"generated",
"*.proto",
"tests/fixtures/**",
"!tests/fixtures/important.py" # Include this one file
]
```
```toml title="pyproject.toml"
[[tool.ty.overrides]]
exclude = [
"generated",
"*.proto",
"tests/fixtures/**",
"!tests/fixtures/important.py" # Include this one file
]
```
---
@@ -386,25 +268,13 @@ If not specified, defaults to `["**"]` (matches all files).
**Example usage**:
=== "pyproject.toml"
```toml
[[tool.ty.overrides]]
include = [
"src",
"tests",
]
```
=== "ty.toml"
```toml
[[overrides]]
include = [
"src",
"tests",
]
```
```toml title="pyproject.toml"
[[tool.ty.overrides]]
include = [
"src",
"tests",
]
```
---
@@ -422,25 +292,13 @@ severity levels or disable them entirely.
**Example usage**:
=== "pyproject.toml"
```toml title="pyproject.toml"
[[tool.ty.overrides]]
include = ["src"]
```toml
[[tool.ty.overrides]]
include = ["src"]
[tool.ty.overrides.rules]
possibly-unresolved-reference = "ignore"
```
=== "ty.toml"
```toml
[[overrides]]
include = ["src"]
[overrides.rules]
possibly-unresolved-reference = "ignore"
```
[tool.ty.overrides.rules]
possibly-unresolved-reference = "ignore"
```
---
@@ -500,29 +358,15 @@ to re-include `dist` use `exclude = ["!dist"]`
**Example usage**:
=== "pyproject.toml"
```toml
[tool.ty.src]
exclude = [
"generated",
"*.proto",
"tests/fixtures/**",
"!tests/fixtures/important.py" # Include this one file
]
```
=== "ty.toml"
```toml
[src]
exclude = [
"generated",
"*.proto",
"tests/fixtures/**",
"!tests/fixtures/important.py" # Include this one file
]
```
```toml title="pyproject.toml"
[tool.ty.src]
exclude = [
"generated",
"*.proto",
"tests/fixtures/**",
"!tests/fixtures/important.py" # Include this one file
]
```
---
@@ -555,25 +399,13 @@ matches `<project_root>/src` and not `<project_root>/test/src`).
**Example usage**:
=== "pyproject.toml"
```toml
[tool.ty.src]
include = [
"src",
"tests",
]
```
=== "ty.toml"
```toml
[src]
include = [
"src",
"tests",
]
```
```toml title="pyproject.toml"
[tool.ty.src]
include = [
"src",
"tests",
]
```
---
@@ -589,19 +421,10 @@ Enabled by default.
**Example usage**:
=== "pyproject.toml"
```toml
[tool.ty.src]
respect-ignore-files = false
```
=== "ty.toml"
```toml
[src]
respect-ignore-files = false
```
```toml title="pyproject.toml"
[tool.ty.src]
respect-ignore-files = false
```
---
@@ -627,19 +450,10 @@ it will also be included in the first party search path.
**Example usage**:
=== "pyproject.toml"
```toml
[tool.ty.src]
root = "./app"
```
=== "ty.toml"
```toml
[src]
root = "./app"
```
```toml title="pyproject.toml"
[tool.ty.src]
root = "./app"
```
---
@@ -657,21 +471,11 @@ Defaults to `false`.
**Example usage**:
=== "pyproject.toml"
```toml
[tool.ty.terminal]
# Error if ty emits any warning-level diagnostics.
error-on-warning = true
```
=== "ty.toml"
```toml
[terminal]
# Error if ty emits any warning-level diagnostics.
error-on-warning = true
```
```toml title="pyproject.toml"
[tool.ty.terminal]
# Error if ty emits any warning-level diagnostics.
error-on-warning = true
```
---
@@ -687,19 +491,10 @@ Defaults to `full`.
**Example usage**:
=== "pyproject.toml"
```toml
[tool.ty.terminal]
output-format = "concise"
```
=== "ty.toml"
```toml
[terminal]
output-format = "concise"
```
```toml title="pyproject.toml"
[tool.ty.terminal]
output-format = "concise"
```
---

1317
crates/ty/docs/rules.md generated

File diff suppressed because it is too large Load Diff

View File

@@ -154,17 +154,6 @@ pub(crate) struct CheckCommand {
#[clap(long, overrides_with("respect_ignore_files"), hide = true)]
no_respect_ignore_files: bool,
/// Enforce exclusions, even for paths passed to ty directly on the command-line.
/// Use `--no-force-exclude` to disable.
#[arg(
long,
overrides_with("no_force_exclude"),
help_heading = "File selection"
)]
force_exclude: bool,
#[clap(long, overrides_with("force_exclude"), hide = true)]
no_force_exclude: bool,
/// Glob patterns for files to exclude from type checking.
///
/// Uses gitignore-style syntax to exclude files and directories from type checking.
@@ -189,10 +178,6 @@ pub(crate) struct CheckCommand {
}
impl CheckCommand {
pub(crate) fn force_exclude(&self) -> bool {
resolve_bool_arg(self.force_exclude, self.no_force_exclude).unwrap_or_default()
}
pub(crate) fn into_options(self) -> Options {
let rules = if self.rules.is_empty() {
None
@@ -450,12 +435,3 @@ impl ConfigsArg {
self.0
}
}
fn resolve_bool_arg(yes: bool, no: bool) -> Option<bool> {
match (yes, no) {
(true, false) => Some(true),
(false, true) => Some(false),
(false, false) => None,
(..) => unreachable!("Clap should make this impossible"),
}
}

View File

@@ -10,9 +10,9 @@ use ty_static::EnvVars;
use std::fmt::Write;
use std::process::{ExitCode, Termination};
use std::sync::Mutex;
use anyhow::Result;
use std::sync::Mutex;
use crate::args::{CheckCommand, Command, TerminalColor};
use crate::logging::{VerbosityLevel, setup_tracing};
@@ -22,7 +22,6 @@ use clap::{CommandFactory, Parser};
use colored::Colorize;
use crossbeam::channel as crossbeam_channel;
use rayon::ThreadPoolBuilder;
use ruff_db::cancellation::{CancellationToken, CancellationTokenSource};
use ruff_db::diagnostic::{
Diagnostic, DiagnosticId, DisplayDiagnosticConfig, DisplayDiagnostics, Severity,
};
@@ -119,12 +118,9 @@ fn run_check(args: CheckCommand) -> anyhow::Result<ExitStatus> {
.config_file
.as_ref()
.map(|path| SystemPath::absolute(path, &cwd));
let force_exclude = args.force_exclude();
let mut project_metadata = match &config_file {
Some(config_file) => {
ProjectMetadata::from_config_file(config_file.clone(), &project_path, &system)?
}
Some(config_file) => ProjectMetadata::from_config_file(config_file.clone(), &system)?,
None => ProjectMetadata::discover(&project_path, &system)?,
};
@@ -134,13 +130,11 @@ fn run_check(args: CheckCommand) -> anyhow::Result<ExitStatus> {
project_metadata.apply_overrides(&project_options_overrides);
let mut db = ProjectDatabase::new(project_metadata, system)?;
let project = db.project();
project.set_verbose(&mut db, verbosity >= VerbosityLevel::Verbose);
project.set_force_exclude(&mut db, force_exclude);
db.project()
.set_verbose(&mut db, verbosity >= VerbosityLevel::Verbose);
if !check_paths.is_empty() {
project.set_included_paths(&mut db, check_paths);
db.project().set_included_paths(&mut db, check_paths);
}
let (main_loop, main_loop_cancellation_token) =
@@ -228,11 +222,6 @@ struct MainLoop {
printer: Printer,
project_options_overrides: ProjectOptionsOverrides,
/// Cancellation token that gets set by Ctrl+C.
/// Used for long-running operations on the main thread. Operations on background threads
/// use Salsa's cancellation mechanism.
cancellation_token: CancellationToken,
}
impl MainLoop {
@@ -242,9 +231,6 @@ impl MainLoop {
) -> (Self, MainLoopCancellationToken) {
let (sender, receiver) = crossbeam_channel::bounded(10);
let cancellation_token_source = CancellationTokenSource::new();
let cancellation_token = cancellation_token_source.token();
(
Self {
sender: sender.clone(),
@@ -252,12 +238,8 @@ impl MainLoop {
watcher: None,
project_options_overrides,
printer,
cancellation_token,
},
MainLoopCancellationToken {
sender,
source: cancellation_token_source,
},
MainLoopCancellationToken { sender },
)
}
@@ -291,6 +273,9 @@ impl MainLoop {
let mut revision = 0u64;
while let Ok(message) = self.receiver.recv() {
if self.watcher.is_some() {
Printer::clear_screen()?;
}
match message {
MainLoopMessage::CheckWorkspace => {
let db = db.clone();
@@ -329,7 +314,6 @@ impl MainLoop {
let display_config = DisplayDiagnosticConfig::default()
.format(terminal_settings.output_format.into())
.color(colored::control::SHOULD_COLORIZE.should_colorize())
.with_cancellation_token(Some(self.cancellation_token.clone()))
.show_fix_diff(true);
if check_revision == revision {
@@ -373,21 +357,19 @@ impl MainLoop {
)?;
}
if !self.cancellation_token.is_cancelled() {
if is_human_readable {
writeln!(
self.printer.stream_for_failure_summary(),
"Found {} diagnostic{}",
diagnostics_count,
if diagnostics_count > 1 { "s" } else { "" }
)?;
}
if is_human_readable {
writeln!(
self.printer.stream_for_failure_summary(),
"Found {} diagnostic{}",
diagnostics_count,
if diagnostics_count > 1 { "s" } else { "" }
)?;
}
if exit_status.is_internal_error() {
tracing::warn!(
"A fatal error occurred while checking some files. Not all project files were analyzed. See the diagnostics list above for details."
);
}
if exit_status.is_internal_error() {
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() {
@@ -402,15 +384,12 @@ impl MainLoop {
}
MainLoopMessage::ApplyChanges(changes) => {
Printer::clear_screen()?;
revision += 1;
// Automatically cancels any pending queries and waits for them to complete.
db.apply_changes(changes, Some(&self.project_options_overrides));
if let Some(watcher) = self.watcher.as_mut() {
watcher.update(db);
}
self.sender.send(MainLoopMessage::CheckWorkspace).unwrap();
}
MainLoopMessage::Exit => {
@@ -514,12 +493,10 @@ impl ty_project::ProgressReporter for IndicatifReporter {
#[derive(Debug)]
struct MainLoopCancellationToken {
sender: crossbeam_channel::Sender<MainLoopMessage>,
source: CancellationTokenSource,
}
impl MainLoopCancellationToken {
fn stop(self) {
self.source.cancel();
self.sender.send(MainLoopMessage::Exit).unwrap();
}
}

View File

@@ -1,43 +0,0 @@
use insta_cmd::assert_cmd_snapshot;
use crate::CliTest;
/// ty ignores `type: ignore` comments when setting `respect-type-ignore-comments=false`
#[test]
fn respect_type_ignore_comments_is_turned_off() -> anyhow::Result<()> {
let case = CliTest::with_file(
"test.py",
r#"
y = a + 5 # type: ignore
"#,
)?;
// Assert that there's an `unresolved-reference` diagnostic (error).
assert_cmd_snapshot!(case.command(), @r"
success: true
exit_code: 0
----- stdout -----
All checks passed!
----- stderr -----
");
assert_cmd_snapshot!(case.command().arg("--config").arg("analysis.respect-type-ignore-comments=false"), @r"
success: false
exit_code: 1
----- stdout -----
error[unresolved-reference]: Name `a` used when not defined
--> test.py:2:5
|
2 | y = a + 5 # type: ignore
| ^
|
info: rule `unresolved-reference` is enabled by default
Found 1 diagnostic
----- stderr -----
");
Ok(())
}

View File

@@ -133,7 +133,7 @@ fn cli_config_args_invalid_option() -> anyhow::Result<()> {
|
1 | bad-option=true
| ^^^^^^^^^^
unknown field `bad-option`, expected one of `environment`, `src`, `rules`, `terminal`, `analysis`, `overrides`
unknown field `bad-option`, expected one of `environment`, `src`, `rules`, `terminal`, `overrides`
Usage: ty <COMMAND>

View File

@@ -589,128 +589,6 @@ fn explicit_path_overrides_exclude() -> anyhow::Result<()> {
Ok(())
}
/// Test behavior when explicitly checking a path that matches an exclude pattern and `--force-exclude` is provided
#[test]
fn explicit_path_overrides_exclude_force_exclude() -> anyhow::Result<()> {
let case = CliTest::with_files([
(
"src/main.py",
r#"
print(undefined_var) # error: unresolved-reference
"#,
),
(
"tests/generated.py",
r#"
print(dist_undefined_var) # error: unresolved-reference
"#,
),
(
"dist/other.py",
r#"
print(other_undefined_var) # error: unresolved-reference
"#,
),
(
"ty.toml",
r#"
[src]
exclude = ["tests/generated.py"]
"#,
),
])?;
// Explicitly checking a file in an excluded directory should still check that file
assert_cmd_snapshot!(case.command().arg("tests/generated.py").arg("src/main.py"), @r"
success: false
exit_code: 1
----- stdout -----
error[unresolved-reference]: Name `undefined_var` used when not defined
--> src/main.py:2:7
|
2 | print(undefined_var) # error: unresolved-reference
| ^^^^^^^^^^^^^
|
info: rule `unresolved-reference` is enabled by default
error[unresolved-reference]: Name `dist_undefined_var` used when not defined
--> tests/generated.py:2:7
|
2 | print(dist_undefined_var) # error: unresolved-reference
| ^^^^^^^^^^^^^^^^^^
|
info: rule `unresolved-reference` is enabled by default
Found 2 diagnostics
----- stderr -----
");
// Except when `--force-exclude` is set.
assert_cmd_snapshot!(case.command().arg("tests/generated.py").arg("src/main.py").arg("--force-exclude"), @r"
success: false
exit_code: 1
----- stdout -----
error[unresolved-reference]: Name `undefined_var` used when not defined
--> src/main.py:2:7
|
2 | print(undefined_var) # error: unresolved-reference
| ^^^^^^^^^^^^^
|
info: rule `unresolved-reference` is enabled by default
Found 1 diagnostic
----- stderr -----
");
// Explicitly checking the entire excluded directory should check all files in it
assert_cmd_snapshot!(case.command().arg("dist/").arg("src/main.py"), @r"
success: false
exit_code: 1
----- stdout -----
error[unresolved-reference]: Name `other_undefined_var` used when not defined
--> dist/other.py:2:7
|
2 | print(other_undefined_var) # error: unresolved-reference
| ^^^^^^^^^^^^^^^^^^^
|
info: rule `unresolved-reference` is enabled by default
error[unresolved-reference]: Name `undefined_var` used when not defined
--> src/main.py:2:7
|
2 | print(undefined_var) # error: unresolved-reference
| ^^^^^^^^^^^^^
|
info: rule `unresolved-reference` is enabled by default
Found 2 diagnostics
----- stderr -----
");
// Except when using `--force-exclude`
assert_cmd_snapshot!(case.command().arg("dist/").arg("src/main.py").arg("--force-exclude"), @r"
success: false
exit_code: 1
----- stdout -----
error[unresolved-reference]: Name `undefined_var` used when not defined
--> src/main.py:2:7
|
2 | print(undefined_var) # error: unresolved-reference
| ^^^^^^^^^^^^^
|
info: rule `unresolved-reference` is enabled by default
Found 1 diagnostic
----- stderr -----
");
Ok(())
}
#[test]
fn cli_and_configuration_exclude() -> anyhow::Result<()> {
let case = CliTest::with_files([

View File

@@ -1,4 +1,3 @@
mod analysis_options;
mod config_option;
mod exit_code;
mod file_selection;
@@ -659,8 +658,6 @@ fn gitlab_diagnostics() -> anyhow::Result<()> {
r#"
print(x) # [unresolved-reference]
print(4[1]) # [non-subscriptable]
from typing_extensions import reveal_type
reveal_type('str'.lower()) # [revealed-type]
"#,
)?;
@@ -711,25 +708,6 @@ fn gitlab_diagnostics() -> anyhow::Result<()> {
}
}
}
},
{
"check_name": "revealed-type",
"description": "revealed-type: Revealed type: `LiteralString`",
"severity": "info",
"fingerprint": "[FINGERPRINT]",
"location": {
"path": "test.py",
"positions": {
"begin": {
"line": 5,
"column": 13
},
"end": {
"line": 5,
"column": 26
}
}
}
}
]
----- stderr -----
@@ -745,8 +723,6 @@ fn github_diagnostics() -> anyhow::Result<()> {
r#"
print(x) # [unresolved-reference]
print(4[1]) # [non-subscriptable]
from typing_extensions import reveal_type
reveal_type('str'.lower()) # [revealed-type]
"#,
)?;
@@ -756,7 +732,6 @@ fn github_diagnostics() -> anyhow::Result<()> {
----- stdout -----
::warning title=ty (unresolved-reference),file=<temp_dir>/test.py,line=2,col=7,endLine=2,endColumn=8::test.py:2:7: unresolved-reference: Name `x` used when not defined
::error title=ty (non-subscriptable),file=<temp_dir>/test.py,line=3,col=7,endLine=3,endColumn=11::test.py:3:7: non-subscriptable: Cannot subscript object of type `Literal[4]` with no `__getitem__` method
::notice title=ty (revealed-type),file=<temp_dir>/test.py,line=5,col=13,endLine=5,endColumn=26::test.py:5:13: revealed-type: Revealed type: `LiteralString`
----- stderr -----
");

View File

@@ -10,13 +10,12 @@ use ruff_db::system::{
OsSystem, System, SystemPath, SystemPathBuf, UserConfigDirectoryOverrideGuard, file_time_now,
};
use ruff_python_ast::PythonVersion;
use ty_module_resolver::{Module, ModuleName, resolve_module_confident};
use ty_project::metadata::options::{EnvironmentOptions, Options, ProjectOptionsOverrides};
use ty_project::metadata::pyproject::{PyProject, Tool};
use ty_project::metadata::value::{RangedValue, RelativePathBuf};
use ty_project::watch::{ChangeEvent, ProjectWatcher, directory_watcher};
use ty_project::{Db, ProjectDatabase, ProjectMetadata};
use ty_python_semantic::PythonPlatform;
use ty_python_semantic::{Module, ModuleName, PythonPlatform, resolve_module_confident};
struct TestCase {
db: ProjectDatabase,
@@ -1020,7 +1019,7 @@ fn search_path() -> anyhow::Result<()> {
let site_packages = case.root_path().join("site_packages");
assert_eq!(
resolve_module_confident(case.db(), &ModuleName::new_static("a").unwrap()),
resolve_module_confident(case.db(), &ModuleName::new("a").unwrap()),
None
);
@@ -1193,7 +1192,7 @@ fn changed_versions_file() -> anyhow::Result<()> {
// Unset the custom typeshed directory.
assert_eq!(
resolve_module_confident(case.db(), &ModuleName::new_static("os").unwrap()),
resolve_module_confident(case.db(), &ModuleName::new("os").unwrap()),
None
);
@@ -1208,7 +1207,7 @@ fn changed_versions_file() -> anyhow::Result<()> {
case.apply_changes(changes, None);
assert!(resolve_module_confident(case.db(), &ModuleName::new_static("os").unwrap()).is_some());
assert!(resolve_module_confident(case.db(), &ModuleName::new("os").unwrap()).is_some());
Ok(())
}
@@ -1875,11 +1874,11 @@ fn rename_files_casing_only() -> anyhow::Result<()> {
let mut case = setup([("lib.py", "class Foo: ...")])?;
assert!(
resolve_module_confident(case.db(), &ModuleName::new_static("lib").unwrap()).is_some(),
resolve_module_confident(case.db(), &ModuleName::new("lib").unwrap()).is_some(),
"Expected `lib` module to exist."
);
assert_eq!(
resolve_module_confident(case.db(), &ModuleName::new_static("Lib").unwrap()),
resolve_module_confident(case.db(), &ModuleName::new("Lib").unwrap()),
None,
"Expected `Lib` module not to exist"
);
@@ -1912,13 +1911,13 @@ fn rename_files_casing_only() -> anyhow::Result<()> {
// Resolving `lib` should now fail but `Lib` should now succeed
assert_eq!(
resolve_module_confident(case.db(), &ModuleName::new_static("lib").unwrap()),
resolve_module_confident(case.db(), &ModuleName::new("lib").unwrap()),
None,
"Expected `lib` module to no longer exist."
);
assert!(
resolve_module_confident(case.db(), &ModuleName::new_static("Lib").unwrap()).is_some(),
resolve_module_confident(case.db(), &ModuleName::new("Lib").unwrap()).is_some(),
"Expected `Lib` module to exist"
);

View File

@@ -12,6 +12,7 @@ license.workspace = true
[dependencies]
ruff_db = { workspace = true }
ruff_python_ast = { workspace = true }
ty_python_semantic = { workspace = true }
ordermap = { workspace = true }

View File

@@ -8,6 +8,7 @@ use std::{collections::HashMap, hash::BuildHasher};
use ordermap::OrderMap;
use ruff_db::system::SystemPathBuf;
use ruff_python_ast::PythonVersion;
use ty_python_semantic::PythonPlatform;
/// Combine two values, preferring the values in `self`.
///
@@ -144,6 +145,7 @@ macro_rules! impl_noop_combine {
}
impl_noop_combine!(SystemPathBuf);
impl_noop_combine!(PythonPlatform);
impl_noop_combine!(PythonVersion);
// std types

View File

@@ -15,8 +15,8 @@ ruff_db = { workspace = true, features = ["os"] }
ruff_text_size = { workspace = true }
ty_ide = { workspace = true }
ty_module_resolver = { workspace = true }
ty_project = { workspace = true }
ty_python_semantic = { workspace = true }
anyhow = { workspace = true }
bstr = { workspace = true }

View File

@@ -28,4 +28,4 @@ scope-simple-long-identifier,main.py,0,1
tstring-completions,main.py,0,1
ty-extensions-lower-stdlib,main.py,0,9
type-var-typing-over-ast,main.py,0,3
type-var-typing-over-ast,main.py,1,253
type-var-typing-over-ast,main.py,1,251
1 name file index rank
28 tstring-completions main.py 0 1
29 ty-extensions-lower-stdlib main.py 0 9
30 type-var-typing-over-ast main.py 0 3
31 type-var-typing-over-ast main.py 1 253 251

View File

@@ -15,11 +15,11 @@ use regex::bytes::Regex;
use ruff_db::files::system_path_to_file;
use ruff_db::system::{OsSystem, SystemPath, SystemPathBuf};
use ty_ide::Completion;
use ty_module_resolver::ModuleName;
use ty_project::metadata::Options;
use ty_project::metadata::options::EnvironmentOptions;
use ty_project::metadata::value::RelativePathBuf;
use ty_project::{ProjectDatabase, ProjectMetadata};
use ty_python_semantic::ModuleName;
#[derive(Debug, clap::Parser)]
#[command(

View File

@@ -22,7 +22,6 @@ ruff_python_importer = { workspace = true }
ruff_python_trivia = { workspace = true }
ruff_source_file = { workspace = true }
ruff_text_size = { workspace = true }
ty_module_resolver = { workspace = true }
ty_python_semantic = { workspace = true }
ty_project = { workspace = true, features = ["testing"] }
ty_vendored = { workspace = true }

View File

@@ -1,6 +1,6 @@
use ruff_db::files::File;
use ty_module_resolver::{Module, ModuleName, all_modules, resolve_real_shadowable_module};
use ty_project::Db;
use ty_python_semantic::{Module, ModuleName, all_modules, resolve_real_shadowable_module};
use crate::{
SymbolKind,
@@ -21,7 +21,7 @@ pub fn all_symbols<'db>(
return Vec::new();
}
let typing_extensions = ModuleName::new_static("typing_extensions").unwrap();
let typing_extensions = ModuleName::new("typing_extensions").unwrap();
let is_typing_extensions_available = importing_from.is_stub(db)
|| resolve_real_shadowable_module(db, importing_from, &typing_extensions).is_some();

View File

@@ -11,10 +11,9 @@ use ruff_python_ast::{self as ast, AnyNodeRef};
use ruff_python_codegen::Stylist;
use ruff_text_size::{Ranged, TextRange, TextSize};
use rustc_hash::FxHashSet;
use ty_module_resolver::{KnownModule, ModuleName};
use ty_python_semantic::types::UnionType;
use ty_python_semantic::{
Completion as SemanticCompletion, NameKind, SemanticModel,
Completion as SemanticCompletion, KnownModule, ModuleName, NameKind, SemanticModel,
types::{CycleDetector, KnownClass, Type},
};
@@ -2107,7 +2106,7 @@ mod tests {
use insta::assert_snapshot;
use ruff_python_ast::token::{TokenKind, Tokens};
use ruff_python_parser::{Mode, ParseOptions};
use ty_module_resolver::ModuleName;
use ty_python_semantic::ModuleName;
use crate::completion::{Completion, completion};
use crate::tests::{CursorTest, CursorTestBuilder};

View File

@@ -1339,13 +1339,13 @@ f(**kwargs<CURSOR>)
| ^^^^^^ Clicking here
|
info: Found 1 type definition
--> stdlib/builtins.pyi:2947:7
--> stdlib/builtins.pyi:2920:7
|
2946 | @disjoint_base
2947 | class dict(MutableMapping[_KT, _VT]):
2919 | @disjoint_base
2920 | class dict(MutableMapping[_KT, _VT]):
| ----
2948 | """dict() -> new empty dictionary
2949 | dict(mapping) -> new dictionary initialized from a mapping object's
2921 | """dict() -> new empty dictionary
2922 | dict(mapping) -> new dictionary initialized from a mapping object's
|
"#);
}

View File

@@ -2956,295 +2956,6 @@ def function():
assert_snapshot!(test.hover(), @"Hover provided no content");
}
#[test]
fn hover_func_with_concat_docstring() {
let test = cursor_test(
r#"
def a<CURSOR>b():
"""wow cool docs""" """and docs"""
return
"#,
);
assert_snapshot!(test.hover(), @r#"
def ab() -> Unknown
---------------------------------------------
wow cool docsand docs
---------------------------------------------
```python
def ab() -> Unknown
```
---
wow cool docsand docs
---------------------------------------------
info[hover]: Hovered content is
--> main.py:2:5
|
2 | def ab():
| ^-
| ||
| |Cursor offset
| source
3 | """wow cool docs""" """and docs"""
4 | return
|
"#);
}
#[test]
fn hover_func_with_plus_docstring() {
let test = cursor_test(
r#"
def a<CURSOR>b():
"""wow cool docs""" + """and docs"""
return
"#,
);
assert_snapshot!(test.hover(), @r#"
def ab() -> Unknown
---------------------------------------------
```python
def ab() -> Unknown
```
---------------------------------------------
info[hover]: Hovered content is
--> main.py:2:5
|
2 | def ab():
| ^-
| ||
| |Cursor offset
| source
3 | """wow cool docs""" + """and docs"""
4 | return
|
"#);
}
#[test]
fn hover_func_with_slash_docstring() {
let test = cursor_test(
r#"
def a<CURSOR>b():
"""wow cool docs""" \
"""and docs"""
return
"#,
);
assert_snapshot!(test.hover(), @r#"
def ab() -> Unknown
---------------------------------------------
wow cool docsand docs
---------------------------------------------
```python
def ab() -> Unknown
```
---
wow cool docsand docs
---------------------------------------------
info[hover]: Hovered content is
--> main.py:2:5
|
2 | def ab():
| ^-
| ||
| |Cursor offset
| source
3 | """wow cool docs""" \
4 | """and docs"""
|
"#);
}
#[test]
fn hover_func_with_sameline_commented_docstring() {
let test = cursor_test(
r#"
def a<CURSOR>b():
"""wow cool docs""" # and a comment
"""and docs""" # that shouldn't be included
return
"#,
);
assert_snapshot!(test.hover(), @r#"
def ab() -> Unknown
---------------------------------------------
wow cool docs
---------------------------------------------
```python
def ab() -> Unknown
```
---
wow cool docs
---------------------------------------------
info[hover]: Hovered content is
--> main.py:2:5
|
2 | def ab():
| ^-
| ||
| |Cursor offset
| source
3 | """wow cool docs""" # and a comment
4 | """and docs""" # that shouldn't be included
|
"#);
}
#[test]
fn hover_func_with_nextline_commented_docstring() {
let test = cursor_test(
r#"
def a<CURSOR>b():
"""wow cool docs"""
# and a comment that shouldn't be included
"""and docs"""
return
"#,
);
assert_snapshot!(test.hover(), @r#"
def ab() -> Unknown
---------------------------------------------
wow cool docs
---------------------------------------------
```python
def ab() -> Unknown
```
---
wow cool docs
---------------------------------------------
info[hover]: Hovered content is
--> main.py:2:5
|
2 | def ab():
| ^-
| ||
| |Cursor offset
| source
3 | """wow cool docs"""
4 | # and a comment that shouldn't be included
|
"#);
}
#[test]
fn hover_func_with_parens_docstring() {
let test = cursor_test(
r#"
def a<CURSOR>b():
(
"""wow cool docs"""
"""and docs"""
)
return
"#,
);
assert_snapshot!(test.hover(), @r#"
def ab() -> Unknown
---------------------------------------------
wow cool docsand docs
---------------------------------------------
```python
def ab() -> Unknown
```
---
wow cool docsand docs
---------------------------------------------
info[hover]: Hovered content is
--> main.py:2:5
|
2 | def ab():
| ^-
| ||
| |Cursor offset
| source
3 | (
4 | """wow cool docs"""
|
"#);
}
#[test]
fn hover_func_with_nextline_commented_parens_docstring() {
let test = cursor_test(
r#"
def a<CURSOR>b():
(
"""wow cool docs"""
# and a comment that shouldn't be included
"""and docs"""
)
return
"#,
);
assert_snapshot!(test.hover(), @r#"
def ab() -> Unknown
---------------------------------------------
wow cool docsand docs
---------------------------------------------
```python
def ab() -> Unknown
```
---
wow cool docsand docs
---------------------------------------------
info[hover]: Hovered content is
--> main.py:2:5
|
2 | def ab():
| ^-
| ||
| |Cursor offset
| source
3 | (
4 | """wow cool docs"""
|
"#);
}
#[test]
fn hover_attribute_docstring_spill() {
let test = cursor_test(
r#"
if True:
a<CURSOR>b = 1
"this shouldn't be a docstring but also it doesn't matter much"
"#,
);
assert_snapshot!(test.hover(), @r#"
Literal[1]
---------------------------------------------
```python
Literal[1]
```
---------------------------------------------
info[hover]: Hovered content is
--> main.py:3:5
|
2 | if True:
3 | ab = 1
| ^-
| ||
| |Cursor offset
| source
4 | "this shouldn't be a docstring but also it doesn't matter much"
|
"#);
}
#[test]
fn hover_class_typevar_variance() {
let test = cursor_test(

View File

@@ -29,11 +29,10 @@ use ruff_python_ast::visitor::source_order::{SourceOrderVisitor, TraversalSignal
use ruff_python_codegen::Stylist;
use ruff_python_importer::Insertion;
use ruff_text_size::{Ranged, TextRange, TextSize};
use ty_module_resolver::ModuleName;
use ty_project::Db;
use ty_python_semantic::semantic_index::definition::DefinitionKind;
use ty_python_semantic::types::Type;
use ty_python_semantic::{MemberDefinition, SemanticModel};
use ty_python_semantic::{MemberDefinition, ModuleName, SemanticModel};
pub(crate) struct Importer<'a> {
/// The ty Salsa database.
@@ -881,10 +880,10 @@ mod tests {
use ruff_python_codegen::Stylist;
use ruff_python_trivia::textwrap::dedent;
use ruff_text_size::TextSize;
use ty_module_resolver::SearchPathSettings;
use ty_project::ProjectMetadata;
use ty_python_semantic::{
Program, ProgramSettings, PythonPlatform, PythonVersionWithSource, SemanticModel,
Program, ProgramSettings, PythonPlatform, PythonVersionWithSource, SearchPathSettings,
SemanticModel,
};
use super::*;

View File

@@ -1241,12 +1241,12 @@ mod tests {
---------------------------------------------
info[inlay-hint-location]: Inlay Hint Target
--> stdlib/builtins.pyi:2722:7
--> stdlib/builtins.pyi:2695:7
|
2721 | @disjoint_base
2722 | class tuple(Sequence[_T_co]):
2694 | @disjoint_base
2695 | class tuple(Sequence[_T_co]):
| ^^^^^
2723 | """Built-in immutable sequence.
2696 | """Built-in immutable sequence.
|
info: Source
--> main2.py:8:5
@@ -1335,12 +1335,12 @@ mod tests {
|
info[inlay-hint-location]: Inlay Hint Target
--> stdlib/builtins.pyi:2722:7
--> stdlib/builtins.pyi:2695:7
|
2721 | @disjoint_base
2722 | class tuple(Sequence[_T_co]):
2694 | @disjoint_base
2695 | class tuple(Sequence[_T_co]):
| ^^^^^
2723 | """Built-in immutable sequence.
2696 | """Built-in immutable sequence.
|
info: Source
--> main2.py:9:5
@@ -1391,12 +1391,12 @@ mod tests {
|
info[inlay-hint-location]: Inlay Hint Target
--> stdlib/builtins.pyi:2722:7
--> stdlib/builtins.pyi:2695:7
|
2721 | @disjoint_base
2722 | class tuple(Sequence[_T_co]):
2694 | @disjoint_base
2695 | class tuple(Sequence[_T_co]):
| ^^^^^
2723 | """Built-in immutable sequence.
2696 | """Built-in immutable sequence.
|
info: Source
--> main2.py:10:5
@@ -2217,12 +2217,12 @@ mod tests {
---------------------------------------------
info[inlay-hint-location]: Inlay Hint Target
--> stdlib/builtins.pyi:2829:7
--> stdlib/builtins.pyi:2802:7
|
2828 | @disjoint_base
2829 | class list(MutableSequence[_T]):
2801 | @disjoint_base
2802 | class list(MutableSequence[_T]):
| ^^^^
2830 | """Built-in mutable sequence.
2803 | """Built-in mutable sequence.
|
info: Source
--> main2.py:2:5
@@ -2270,12 +2270,12 @@ mod tests {
|
info[inlay-hint-location]: Inlay Hint Target
--> stdlib/builtins.pyi:2829:7
--> stdlib/builtins.pyi:2802:7
|
2828 | @disjoint_base
2829 | class list(MutableSequence[_T]):
2801 | @disjoint_base
2802 | class list(MutableSequence[_T]):
| ^^^^
2830 | """Built-in mutable sequence.
2803 | """Built-in mutable sequence.
|
info: Source
--> main2.py:3:5
@@ -2325,12 +2325,12 @@ mod tests {
|
info[inlay-hint-location]: Inlay Hint Target
--> stdlib/builtins.pyi:2829:7
--> stdlib/builtins.pyi:2802:7
|
2828 | @disjoint_base
2829 | class list(MutableSequence[_T]):
2801 | @disjoint_base
2802 | class list(MutableSequence[_T]):
| ^^^^
2830 | """Built-in mutable sequence.
2803 | """Built-in mutable sequence.
|
info: Source
--> main2.py:4:5
@@ -2364,13 +2364,13 @@ mod tests {
|
info[inlay-hint-location]: Inlay Hint Target
--> stdlib/builtins.pyi:2618:7
--> stdlib/builtins.pyi:2591:7
|
2617 | @final
2618 | class bool(int):
2590 | @final
2591 | class bool(int):
| ^^^^
2619 | """Returns True when the argument is true, False otherwise.
2620 | The builtins True and False are the only two instances of the class bool.
2592 | """Returns True when the argument is true, False otherwise.
2593 | The builtins True and False are the only two instances of the class bool.
|
info: Source
--> main2.py:4:20
@@ -2384,12 +2384,12 @@ mod tests {
|
info[inlay-hint-location]: Inlay Hint Target
--> stdlib/builtins.pyi:2829:7
--> stdlib/builtins.pyi:2802:7
|
2828 | @disjoint_base
2829 | class list(MutableSequence[_T]):
2801 | @disjoint_base
2802 | class list(MutableSequence[_T]):
| ^^^^
2830 | """Built-in mutable sequence.
2803 | """Built-in mutable sequence.
|
info: Source
--> main2.py:5:5
@@ -2443,12 +2443,12 @@ mod tests {
|
info[inlay-hint-location]: Inlay Hint Target
--> stdlib/builtins.pyi:2829:7
--> stdlib/builtins.pyi:2802:7
|
2828 | @disjoint_base
2829 | class list(MutableSequence[_T]):
2801 | @disjoint_base
2802 | class list(MutableSequence[_T]):
| ^^^^
2830 | """Built-in mutable sequence.
2803 | """Built-in mutable sequence.
|
info: Source
--> main2.py:6:5
@@ -2502,12 +2502,12 @@ mod tests {
|
info[inlay-hint-location]: Inlay Hint Target
--> stdlib/builtins.pyi:2829:7
--> stdlib/builtins.pyi:2802:7
|
2828 | @disjoint_base
2829 | class list(MutableSequence[_T]):
2801 | @disjoint_base
2802 | class list(MutableSequence[_T]):
| ^^^^
2830 | """Built-in mutable sequence.
2803 | """Built-in mutable sequence.
|
info: Source
--> main2.py:7:5
@@ -2561,12 +2561,12 @@ mod tests {
|
info[inlay-hint-location]: Inlay Hint Target
--> stdlib/builtins.pyi:2829:7
--> stdlib/builtins.pyi:2802:7
|
2828 | @disjoint_base
2829 | class list(MutableSequence[_T]):
2801 | @disjoint_base
2802 | class list(MutableSequence[_T]):
| ^^^^
2830 | """Built-in mutable sequence.
2803 | """Built-in mutable sequence.
|
info: Source
--> main2.py:8:5
@@ -2620,12 +2620,12 @@ mod tests {
|
info[inlay-hint-location]: Inlay Hint Target
--> stdlib/builtins.pyi:2829:7
--> stdlib/builtins.pyi:2802:7
|
2828 | @disjoint_base
2829 | class list(MutableSequence[_T]):
2801 | @disjoint_base
2802 | class list(MutableSequence[_T]):
| ^^^^
2830 | """Built-in mutable sequence.
2803 | """Built-in mutable sequence.
|
info: Source
--> main2.py:9:5
@@ -2678,12 +2678,12 @@ mod tests {
|
info[inlay-hint-location]: Inlay Hint Target
--> stdlib/builtins.pyi:2829:7
--> stdlib/builtins.pyi:2802:7
|
2828 | @disjoint_base
2829 | class list(MutableSequence[_T]):
2801 | @disjoint_base
2802 | class list(MutableSequence[_T]):
| ^^^^
2830 | """Built-in mutable sequence.
2803 | """Built-in mutable sequence.
|
info: Source
--> main2.py:10:5
@@ -2737,12 +2737,12 @@ mod tests {
|
info[inlay-hint-location]: Inlay Hint Target
--> stdlib/builtins.pyi:2829:7
--> stdlib/builtins.pyi:2802:7
|
2828 | @disjoint_base
2829 | class list(MutableSequence[_T]):
2801 | @disjoint_base
2802 | class list(MutableSequence[_T]):
| ^^^^
2830 | """Built-in mutable sequence.
2803 | """Built-in mutable sequence.
|
info: Source
--> main2.py:11:5
@@ -2811,12 +2811,12 @@ mod tests {
|
info[inlay-hint-location]: Inlay Hint Target
--> stdlib/builtins.pyi:2829:7
--> stdlib/builtins.pyi:2802:7
|
2828 | @disjoint_base
2829 | class list(MutableSequence[_T]):
2801 | @disjoint_base
2802 | class list(MutableSequence[_T]):
| ^^^^
2830 | """Built-in mutable sequence.
2803 | """Built-in mutable sequence.
|
info: Source
--> main2.py:12:5
@@ -2925,12 +2925,12 @@ mod tests {
---------------------------------------------
info[inlay-hint-location]: Inlay Hint Target
--> stdlib/builtins.pyi:2722:7
--> stdlib/builtins.pyi:2695:7
|
2721 | @disjoint_base
2722 | class tuple(Sequence[_T_co]):
2694 | @disjoint_base
2695 | class tuple(Sequence[_T_co]):
| ^^^^^
2723 | """Built-in immutable sequence.
2696 | """Built-in immutable sequence.
|
info: Source
--> main2.py:7:5
@@ -3092,12 +3092,12 @@ mod tests {
---------------------------------------------
info[inlay-hint-location]: Inlay Hint Target
--> stdlib/builtins.pyi:2829:7
--> stdlib/builtins.pyi:2802:7
|
2828 | @disjoint_base
2829 | class list(MutableSequence[_T]):
2801 | @disjoint_base
2802 | class list(MutableSequence[_T]):
| ^^^^
2830 | """Built-in mutable sequence.
2803 | """Built-in mutable sequence.
|
info: Source
--> main2.py:4:18
@@ -3110,12 +3110,12 @@ mod tests {
|
info[inlay-hint-location]: Inlay Hint Target
--> stdlib/builtins.pyi:2722:7
--> stdlib/builtins.pyi:2695:7
|
2721 | @disjoint_base
2722 | class tuple(Sequence[_T_co]):
2694 | @disjoint_base
2695 | class tuple(Sequence[_T_co]):
| ^^^^^
2723 | """Built-in immutable sequence.
2696 | """Built-in immutable sequence.
|
info: Source
--> main2.py:5:18
@@ -3248,12 +3248,12 @@ mod tests {
|
info[inlay-hint-location]: Inlay Hint Target
--> stdlib/builtins.pyi:2722:7
--> stdlib/builtins.pyi:2695:7
|
2721 | @disjoint_base
2722 | class tuple(Sequence[_T_co]):
2694 | @disjoint_base
2695 | class tuple(Sequence[_T_co]):
| ^^^^^
2723 | """Built-in immutable sequence.
2696 | """Built-in immutable sequence.
|
info: Source
--> main2.py:8:5
@@ -4250,12 +4250,12 @@ mod tests {
foo([x=]y[0])
---------------------------------------------
info[inlay-hint-location]: Inlay Hint Target
--> stdlib/builtins.pyi:2829:7
--> stdlib/builtins.pyi:2802:7
|
2828 | @disjoint_base
2829 | class list(MutableSequence[_T]):
2801 | @disjoint_base
2802 | class list(MutableSequence[_T]):
| ^^^^
2830 | """Built-in mutable sequence.
2803 | """Built-in mutable sequence.
|
info: Source
--> main2.py:3:5
@@ -4303,12 +4303,12 @@ mod tests {
|
info[inlay-hint-location]: Inlay Hint Target
--> stdlib/builtins.pyi:2829:7
--> stdlib/builtins.pyi:2802:7
|
2828 | @disjoint_base
2829 | class list(MutableSequence[_T]):
2801 | @disjoint_base
2802 | class list(MutableSequence[_T]):
| ^^^^
2830 | """Built-in mutable sequence.
2803 | """Built-in mutable sequence.
|
info: Source
--> main2.py:4:5
@@ -5609,12 +5609,12 @@ mod tests {
|
info[inlay-hint-location]: Inlay Hint Target
--> stdlib/builtins.pyi:2829:7
--> stdlib/builtins.pyi:2802:7
|
2828 | @disjoint_base
2829 | class list(MutableSequence[_T]):
2801 | @disjoint_base
2802 | class list(MutableSequence[_T]):
| ^^^^
2830 | """Built-in mutable sequence.
2803 | """Built-in mutable sequence.
|
info: Source
--> main2.py:3:14
@@ -6046,13 +6046,13 @@ mod tests {
|
info[inlay-hint-location]: Inlay Hint Target
--> stdlib/builtins.pyi:2618:7
--> stdlib/builtins.pyi:2591:7
|
2617 | @final
2618 | class bool(int):
2590 | @final
2591 | class bool(int):
| ^^^^
2619 | """Returns True when the argument is true, False otherwise.
2620 | The builtins True and False are the only two instances of the class bool.
2592 | """Returns True when the argument is true, False otherwise.
2593 | The builtins True and False are the only two instances of the class bool.
|
info: Source
--> main2.py:4:25
@@ -6100,12 +6100,12 @@ mod tests {
|
info[inlay-hint-location]: Inlay Hint Target
--> stdlib/builtins.pyi:2829:7
--> stdlib/builtins.pyi:2802:7
|
2828 | @disjoint_base
2829 | class list(MutableSequence[_T]):
2801 | @disjoint_base
2802 | class list(MutableSequence[_T]):
| ^^^^
2830 | """Built-in mutable sequence.
2803 | """Built-in mutable sequence.
|
info: Source
--> main2.py:4:49
@@ -6614,6 +6614,13 @@ mod tests {
3 | y[: type[T@f]] = x
| ^^^
|
---------------------------------------------
info[inlay-hint-edit]: File after edits
info: Source
def f[T](x: type[T]):
y: type[T@f] = x
"#);
}
@@ -6732,14 +6739,14 @@ mod tests {
A = TypeAliasType([name=]'A', [value=]str)
---------------------------------------------
info[inlay-hint-location]: Inlay Hint Target
--> stdlib/typing.pyi:2037:26
--> stdlib/typing.pyi:2032:26
|
2035 | """
2036 |
2037 | def __new__(cls, name: str, value: Any, *, type_params: tuple[_TypeParameter, ...] = ()) -> Self: ...
2030 | """
2031 |
2032 | def __new__(cls, name: str, value: Any, *, type_params: tuple[_TypeParameter, ...] = ()) -> Self: ...
| ^^^^
2038 | @property
2039 | def __value__(self) -> Any: ... # AnnotationForm
2033 | @property
2034 | def __value__(self) -> Any: ... # AnnotationForm
|
info: Source
--> main2.py:3:20
@@ -6750,14 +6757,14 @@ mod tests {
|
info[inlay-hint-location]: Inlay Hint Target
--> stdlib/typing.pyi:2037:37
--> stdlib/typing.pyi:2032:37
|
2035 | """
2036 |
2037 | def __new__(cls, name: str, value: Any, *, type_params: tuple[_TypeParameter, ...] = ()) -> Self: ...
2030 | """
2031 |
2032 | def __new__(cls, name: str, value: Any, *, type_params: tuple[_TypeParameter, ...] = ()) -> Self: ...
| ^^^^^
2038 | @property
2039 | def __value__(self) -> Any: ... # AnnotationForm
2033 | @property
2034 | def __value__(self) -> Any: ... # AnnotationForm
|
info: Source
--> main2.py:3:32

File diff suppressed because it is too large Load Diff

View File

@@ -14,8 +14,8 @@ use ruff_python_ast::name::{Name, UnqualifiedName};
use ruff_python_ast::visitor::source_order::{self, SourceOrderVisitor};
use ruff_text_size::{Ranged, TextRange};
use rustc_hash::{FxHashMap, FxHashSet};
use ty_module_resolver::{ModuleName, resolve_module};
use ty_project::Db;
use ty_python_semantic::{ModuleName, resolve_module};
use crate::completion::CompletionKind;

View File

@@ -1,38 +0,0 @@
[package]
name = "ty_module_resolver"
version = "0.0.0"
publish = false
authors = { workspace = true }
edition = { workspace = true }
rust-version = { workspace = true }
homepage = { workspace = true }
documentation = { workspace = true }
repository = { workspace = true }
license = { workspace = true }
[dependencies]
ruff_db = { workspace = true }
ruff_memory_usage = { workspace = true }
ruff_python_ast = { workspace = true, features = ["salsa"] }
ruff_python_stdlib = { workspace = true }
camino = { workspace = true }
compact_str = { workspace = true }
get-size2 = { workspace = true }
rustc-hash = { workspace = true }
salsa = { workspace = true }
thiserror = { workspace = true }
tracing = { workspace = true }
strum = { workspace = true }
strum_macros = { workspace = true }
[dev-dependencies]
ruff_db = { workspace = true, features = ["testing", "os"] }
ty_vendored = { workspace = true }
anyhow = { workspace = true }
insta = { workspace = true, features = ["filters"] }
tempfile = { workspace = true }
[lints]
workspace = true

View File

@@ -1,124 +0,0 @@
use ruff_db::Db as SourceDb;
use crate::resolve::SearchPaths;
#[salsa::db]
pub trait Db: SourceDb {
/// Returns the search paths for module resolution.
fn search_paths(&self) -> &SearchPaths;
}
#[cfg(test)]
pub(crate) mod tests {
use std::sync::{Arc, Mutex};
use ruff_db::Db as SourceDb;
use ruff_db::files::Files;
use ruff_db::system::{DbWithTestSystem, TestSystem};
use ruff_db::vendored::VendoredFileSystem;
use ruff_python_ast::PythonVersion;
use super::Db;
use crate::resolve::SearchPaths;
type Events = Arc<Mutex<Vec<salsa::Event>>>;
#[salsa::db]
#[derive(Clone)]
pub(crate) struct TestDb {
storage: salsa::Storage<Self>,
files: Files,
system: TestSystem,
vendored: VendoredFileSystem,
search_paths: Arc<SearchPaths>,
python_version: PythonVersion,
events: Events,
}
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);
}
}))),
system: TestSystem::default(),
vendored: ty_vendored::file_system().clone(),
files: Files::default(),
search_paths: Arc::new(SearchPaths::empty(ty_vendored::file_system())),
python_version: PythonVersion::default(),
events,
}
}
pub(crate) fn with_search_paths(mut self, search_paths: SearchPaths) -> Self {
self.set_search_paths(search_paths);
self
}
pub(crate) fn with_python_version(mut self, python_version: PythonVersion) -> Self {
self.python_version = python_version;
self
}
pub(crate) fn set_search_paths(&mut self, search_paths: SearchPaths) {
search_paths.try_register_static_roots(self);
self.search_paths = Arc::new(search_paths);
}
/// Takes the salsa events.
pub(crate) fn take_salsa_events(&mut self) -> Vec<salsa::Event> {
let mut events = self.events.lock().unwrap();
std::mem::take(&mut *events)
}
/// Clears the salsa events.
pub(crate) fn clear_salsa_events(&mut self) {
self.take_salsa_events();
}
}
impl DbWithTestSystem for TestDb {
fn test_system(&self) -> &TestSystem {
&self.system
}
fn test_system_mut(&mut self) -> &mut TestSystem {
&mut self.system
}
}
#[salsa::db]
impl SourceDb for TestDb {
fn vendored(&self) -> &VendoredFileSystem {
&self.vendored
}
fn system(&self) -> &dyn ruff_db::system::System {
&self.system
}
fn files(&self) -> &Files {
&self.files
}
fn python_version(&self) -> PythonVersion {
self.python_version
}
}
#[salsa::db]
impl Db for TestDb {
fn search_paths(&self) -> &SearchPaths {
&self.search_paths
}
}
#[salsa::db]
impl salsa::Database for TestDb {}
}

View File

@@ -1,105 +0,0 @@
//! Search path configuration settings.
use ruff_db::system::{System, SystemPathBuf};
use ruff_db::vendored::VendoredFileSystem;
use crate::path::SearchPathError;
use crate::resolve::SearchPaths;
use crate::typeshed::TypeshedVersionsParseError;
/// How to handle apparent misconfiguration
#[derive(PartialEq, Eq, Debug, Copy, Clone, Default, get_size2::GetSize)]
pub enum MisconfigurationMode {
/// Settings Failure Is Not An Error.
///
/// This is used by the default database, which we are incentivized to make infallible,
/// while still trying to "do our best" to set things up properly where we can.
UseDefault,
/// Settings Failure Is An Error.
#[default]
Fail,
}
/// Configures the search paths for module resolution.
#[derive(Eq, PartialEq, Debug, Clone)]
pub struct SearchPathSettings {
/// List of user-provided paths that should take first priority in the module resolution.
/// Examples in other type checkers are mypy's MYPYPATH environment variable,
/// or pyright's stubPath configuration setting.
pub extra_paths: Vec<SystemPathBuf>,
/// The root of the project, used for finding first-party modules.
pub src_roots: Vec<SystemPathBuf>,
/// Optional path to a "custom typeshed" directory on disk for us to use for standard-library types.
/// If this is not provided, we will fallback to our vendored typeshed stubs for the stdlib,
/// bundled as a zip file in the binary
pub custom_typeshed: Option<SystemPathBuf>,
/// List of site packages paths to use.
pub site_packages_paths: Vec<SystemPathBuf>,
/// Option path to the real stdlib on the system, and not some instance of typeshed.
///
/// We should ideally only ever use this for things like goto-definition,
/// where typeshed isn't the right answer.
pub real_stdlib_path: Option<SystemPathBuf>,
/// How to handle apparent misconfiguration
pub misconfiguration_mode: MisconfigurationMode,
}
impl SearchPathSettings {
pub fn new(src_roots: Vec<SystemPathBuf>) -> Self {
Self {
src_roots,
..SearchPathSettings::empty()
}
}
pub fn empty() -> Self {
SearchPathSettings {
src_roots: vec![],
extra_paths: vec![],
custom_typeshed: None,
site_packages_paths: vec![],
real_stdlib_path: None,
misconfiguration_mode: MisconfigurationMode::Fail,
}
}
pub fn to_search_paths(
&self,
system: &dyn System,
vendored: &VendoredFileSystem,
) -> Result<SearchPaths, SearchPathSettingsError> {
SearchPaths::from_settings(self, system, vendored)
}
}
/// Enumeration describing the various ways in which validation of the search paths options might fail.
///
/// If validation fails for a search path derived from the user settings,
/// a message must be displayed to the user,
/// as type checking cannot be done reliably in these circumstances.
#[derive(Debug, thiserror::Error)]
pub enum SearchPathSettingsError {
#[error(transparent)]
InvalidSearchPath(#[from] SearchPathError),
/// The typeshed path provided by the user is a directory,
/// but `stdlib/VERSIONS` could not be read.
/// (This is only relevant for stdlib search paths.)
#[error("Failed to read the custom typeshed versions file '{path}'")]
FailedToReadVersionsFile {
path: SystemPathBuf,
#[source]
error: std::io::Error,
},
/// The path provided by the user is a directory,
/// and a `stdlib/VERSIONS` file exists, but it fails to parse.
/// (This is only relevant for stdlib search paths.)
#[error(transparent)]
VersionsParseError(#[from] TypeshedVersionsParseError),
}

View File

@@ -21,7 +21,6 @@ ruff_python_ast = { workspace = true, features = ["serde"] }
ruff_python_formatter = { workspace = true, optional = true }
ruff_text_size = { workspace = true }
ty_combine = { workspace = true }
ty_module_resolver = { workspace = true }
ty_python_semantic = { workspace = true, features = ["serde"] }
ty_static = { workspace = true }
ty_vendored = { workspace = true }

View File

@@ -14,9 +14,8 @@ use ruff_db::files::{File, Files};
use ruff_db::system::System;
use ruff_db::vendored::VendoredFileSystem;
use salsa::{Database, Event, Setter};
use ty_module_resolver::SearchPaths;
use ty_python_semantic::lint::{LintRegistry, RuleSelection};
use ty_python_semantic::{AnalysisSettings, Db as SemanticDb, Program};
use ty_python_semantic::{Db as SemanticDb, Program};
mod changes;
@@ -447,13 +446,6 @@ impl SalsaMemoryDump {
}
}
#[salsa::db]
impl ty_module_resolver::Db for ProjectDatabase {
fn search_paths(&self) -> &SearchPaths {
Program::get(self).search_paths(self)
}
}
#[salsa::db]
impl SemanticDb for ProjectDatabase {
fn should_check_file(&self, file: File) -> bool {
@@ -470,10 +462,6 @@ impl SemanticDb for ProjectDatabase {
ty_python_semantic::default_lint_registry()
}
fn analysis_settings(&self) -> &AnalysisSettings {
self.project().settings(self).analysis()
}
fn verbose(&self) -> bool {
self.project().verbose(self)
}
@@ -535,10 +523,9 @@ pub(crate) mod tests {
use ruff_db::files::{FileRootKind, Files};
use ruff_db::system::{DbWithTestSystem, System, TestSystem};
use ruff_db::vendored::VendoredFileSystem;
use ty_module_resolver::SearchPathSettings;
use ty_python_semantic::lint::{LintRegistry, RuleSelection};
use ty_python_semantic::{
AnalysisSettings, Program, ProgramSettings, PythonPlatform, PythonVersionWithSource,
Program, ProgramSettings, PythonPlatform, PythonVersionWithSource, SearchPathSettings,
};
use crate::db::Db;
@@ -640,13 +627,6 @@ pub(crate) mod tests {
}
}
#[salsa::db]
impl ty_module_resolver::Db for TestDb {
fn search_paths(&self) -> &ty_module_resolver::SearchPaths {
Program::get(self).search_paths(self)
}
}
#[salsa::db]
impl ty_python_semantic::Db for TestDb {
fn should_check_file(&self, file: ruff_db::files::File) -> bool {
@@ -661,10 +641,6 @@ pub(crate) mod tests {
ty_python_semantic::default_lint_registry()
}
fn analysis_settings(&self) -> &AnalysisSettings {
self.project().settings(self).analysis()
}
fn verbose(&self) -> bool {
false
}

View File

@@ -245,9 +245,7 @@ impl ProjectDatabase {
if result.project_changed {
let new_project_metadata = match config_file_override {
Some(config_file) => {
ProjectMetadata::from_config_file(config_file, &project_root, self.system())
}
Some(config_file) => ProjectMetadata::from_config_file(config_file, self.system()),
None => ProjectMetadata::discover(&project_root, self.system()),
};
match new_project_metadata {

View File

@@ -115,10 +115,6 @@ pub struct Project {
#[default]
verbose_flag: bool,
/// Whether to enforce exclusion rules even to files explicitly passed to ty on the command line.
#[default]
force_exclude_flag: bool,
}
/// A progress reporter.
@@ -384,16 +380,6 @@ impl Project {
self.verbose_flag(db)
}
pub fn set_force_exclude(self, db: &mut dyn Db, force: bool) {
if self.force_exclude_flag(db) != force {
self.set_force_exclude_flag(db).to(force);
}
}
pub fn force_exclude(self, db: &dyn Db) -> bool {
self.force_exclude_flag(db)
}
/// Returns the paths that should be checked.
///
/// The default is to check the entire project in which case this method returns

View File

@@ -56,7 +56,6 @@ impl ProjectMetadata {
pub fn from_config_file(
path: SystemPathBuf,
root: &SystemPath,
system: &dyn System,
) -> Result<Self, ProjectMetadataError> {
tracing::debug!("Using overridden configuration file at '{path}'");
@@ -71,8 +70,8 @@ impl ProjectMetadata {
let options = config_file.into_options();
Ok(Self {
name: Name::new(root.file_name().unwrap_or("root")),
root: root.to_path_buf(),
name: Name::new(system.current_directory().file_name().unwrap_or("root")),
root: system.current_directory().to_path_buf(),
options,
extra_configuration_paths: vec![path],
misconfiguration_mode: MisconfigurationMode::Fail,

View File

@@ -28,12 +28,11 @@ use std::ops::Deref;
use std::sync::Arc;
use thiserror::Error;
use ty_combine::Combine;
use ty_module_resolver::{SearchPathSettings, SearchPathSettingsError, SearchPaths};
use ty_python_semantic::lint::{Level, LintSource, RuleSelection};
use ty_python_semantic::{
AnalysisSettings, MisconfigurationMode, ProgramSettings, PythonEnvironment, PythonPlatform,
PythonVersionFileSource, PythonVersionSource, PythonVersionWithSource, SitePackagesPaths,
SysPrefixPathOrigin,
MisconfigurationMode, ProgramSettings, PythonEnvironment, PythonPlatform,
PythonVersionFileSource, PythonVersionSource, PythonVersionWithSource, SearchPathSettings,
SearchPathValidationError, SearchPaths, SitePackagesPaths, SysPrefixPathOrigin,
};
use ty_static::EnvVars;
@@ -87,10 +86,6 @@ pub struct Options {
#[option_group]
pub terminal: Option<TerminalOptions>,
#[serde(skip_serializing_if = "Option::is_none")]
#[option_group]
pub analysis: Option<AnalysisOptions>,
/// Override configurations for specific file patterns.
///
/// Each override specifies include/exclude patterns and rule configurations
@@ -263,7 +258,7 @@ impl Options {
system: &dyn System,
vendored: &VendoredFileSystem,
misconfiguration_mode: MisconfigurationMode,
) -> Result<SearchPaths, SearchPathSettingsError> {
) -> Result<SearchPaths, SearchPathValidationError> {
let environment = self.environment.or_default();
let src = self.src.or_default();
@@ -439,8 +434,6 @@ impl Options {
color: colored::control::SHOULD_COLORIZE.should_colorize(),
})?;
let analysis = self.analysis.or_default().to_settings();
let overrides = self
.to_overrides_settings(db, project_root, &mut diagnostics)
.map_err(|err| ToSettingsError {
@@ -453,7 +446,6 @@ impl Options {
rules: Arc::new(rules),
terminal,
src,
analysis,
overrides,
};
@@ -883,7 +875,10 @@ impl Rules {
let lint_source = match source {
ValueSource::File(_) => LintSource::File,
ValueSource::Cli => LintSource::Cli,
ValueSource::Editor => LintSource::Editor,
ValueSource::Editor => {
unreachable!("Can't configure rules from the user's editor")
}
};
if let Ok(severity) = Severity::try_from(**level) {
selection.enable(lint, severity, lint_source);
@@ -1019,12 +1014,7 @@ fn build_include_filter(
SubDiagnosticSeverity::Info,
"The pattern was specified on the CLI",
)),
ValueSource::Editor => {
diagnostic.sub(SubDiagnostic::new(
SubDiagnosticSeverity::Info,
"The pattern was specified in the editor settings.",
))
}
ValueSource::Editor => unreachable!("Can't configure includes from the user's editor"),
}
})?;
}
@@ -1107,10 +1097,9 @@ fn build_exclude_filter(
SubDiagnosticSeverity::Info,
"The pattern was specified on the CLI",
)),
ValueSource::Editor => diagnostic.sub(SubDiagnostic::new(
SubDiagnosticSeverity::Info,
"The pattern was specified in the editor settings",
))
ValueSource::Editor => unreachable!(
"Can't configure excludes from the user's editor"
)
}
})?;
}
@@ -1261,55 +1250,6 @@ pub struct TerminalOptions {
pub error_on_warning: Option<bool>,
}
#[derive(
Debug,
Default,
Clone,
Eq,
PartialEq,
Combine,
Serialize,
Deserialize,
OptionsMetadata,
get_size2::GetSize,
)]
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct AnalysisOptions {
/// Whether ty should respect `type: ignore` comments.
///
/// When set to `false`, `type: ignore` comments are treated like any other normal
/// comment and can't be used to suppress ty errors (you have to use `ty: ignore` instead).
///
/// Setting this option can be useful when using ty alongside other type checkers or when
/// you prefer using `ty: ignore` over `type: ignore`.
///
/// Defaults to `true`.
#[option(
default = r#"true"#,
value_type = "bool",
example = r#"
# Disable support for `type: ignore` comments
respect-type-ignore-comments = false
"#
)]
respect_type_ignore_comments: Option<bool>,
}
impl AnalysisOptions {
fn to_settings(&self) -> AnalysisSettings {
let AnalysisSettings {
respect_type_ignore_comments: respect_type_ignore_default,
} = AnalysisSettings::default();
AnalysisSettings {
respect_type_ignore_comments: self
.respect_type_ignore_comments
.unwrap_or(respect_type_ignore_default),
}
}
}
/// Configuration override that applies to specific files based on glob patterns.
///
/// An override allows you to apply different rule configurations to specific

View File

@@ -2,7 +2,6 @@ use std::sync::Arc;
use ruff_db::files::File;
use ty_combine::Combine;
use ty_python_semantic::AnalysisSettings;
use ty_python_semantic::lint::RuleSelection;
use crate::metadata::options::{InnerOverrideOptions, OutputFormat};
@@ -26,7 +25,6 @@ pub struct Settings {
pub(super) rules: Arc<RuleSelection>,
pub(super) terminal: TerminalSettings,
pub(super) src: SrcSettings,
pub(super) analysis: AnalysisSettings,
/// Settings for configuration overrides that apply to specific file patterns.
///
@@ -56,10 +54,6 @@ impl Settings {
pub fn overrides(&self) -> &[Override] {
&self.overrides
}
pub fn analysis(&self) -> &AnalysisSettings {
&self.analysis
}
}
#[derive(Debug, Clone, PartialEq, Eq, Default, get_size2::GetSize)]

View File

@@ -111,8 +111,6 @@ pub(crate) struct ProjectFilesWalker<'a> {
walker: WalkDirectoryBuilder,
filter: ProjectFilesFilter<'a>,
force_exclude: bool,
}
impl<'a> ProjectFilesWalker<'a> {
@@ -160,11 +158,7 @@ impl<'a> ProjectFilesWalker<'a> {
walker = walker.add(path);
}
Some(Self {
walker,
filter,
force_exclude: db.project().force_exclude(db),
})
Some(Self { walker, filter })
}
/// Walks the project paths and collects the paths of all files that
@@ -185,7 +179,7 @@ impl<'a> ProjectFilesWalker<'a> {
// Skip excluded directories unless they were explicitly passed to the walker
// (which is the case passed to `ty check <paths>`).
if entry.file_type().is_directory() {
if entry.depth() > 0 || self.force_exclude {
if entry.depth() > 0 {
let directory_included = filter
.is_directory_included(entry.path(), GlobFilterCheckMode::TopDown);
return match directory_included {
@@ -224,7 +218,7 @@ impl<'a> ProjectFilesWalker<'a> {
// For all files, except the ones that were explicitly passed to the walker (CLI),
// check if they're included in the project.
if entry.depth() > 0 || self.force_exclude {
if entry.depth() > 0 {
match filter
.is_file_included(entry.path(), GlobFilterCheckMode::TopDown)
{

View File

@@ -5,7 +5,7 @@ use tracing::info;
use ruff_cache::{CacheKey, CacheKeyHasher};
use ruff_db::system::{SystemPath, SystemPathBuf};
use ty_module_resolver::system_module_search_paths;
use ty_python_semantic::system_module_search_paths;
use crate::db::{Db, ProjectDatabase};
use crate::watch::Watcher;

View File

@@ -24,8 +24,6 @@ ruff_source_file = { workspace = true }
ruff_text_size = { workspace = true }
ruff_python_literal = { workspace = true }
ruff_python_trivia = { workspace = true }
ty_module_resolver = { workspace = true }
ty_combine = { workspace = true }
ty_static = { workspace = true }
anyhow = { workspace = true }
@@ -58,6 +56,7 @@ strsim = "0.11.1"
[dev-dependencies]
ruff_db = { workspace = true, features = ["testing", "os"] }
ruff_python_parser = { workspace = true }
ty_python_semantic = { workspace = true, features = ["testing"] }
ty_static = { workspace = true }
ty_test = { workspace = true }
ty_vendored = { workspace = true }
@@ -68,6 +67,7 @@ glob = { workspace = true }
indoc = { workspace = true }
insta = { workspace = true }
pretty_assertions = { workspace = true }
tempfile = { workspace = true }
quickcheck = { version = "1.0.3", default-features = false }
quickcheck_macros = { version = "1.0.0" }

View File

@@ -38,14 +38,8 @@ class MDTestRunner:
console: Console
filters: list[str]
enable_external: bool
upgrade_lockfiles: bool
def __init__(
self,
filters: list[str] | None,
enable_external: bool,
upgrade_lockfiles: bool,
) -> None:
def __init__(self, filters: list[str] | None, enable_external: bool) -> None:
self.mdtest_executable = None
self.console = Console()
self.filters = [
@@ -53,7 +47,6 @@ class MDTestRunner:
for f in (filters or [])
]
self.enable_external = enable_external
self.upgrade_lockfiles = upgrade_lockfiles
def _run_cargo_test(self, *, message_format: Literal["human", "json"]) -> str:
return subprocess.check_output(
@@ -130,7 +123,6 @@ class MDTestRunner:
INSTA_FORCE_PASS="1",
INSTA_OUTPUT="none",
MDTEST_EXTERNAL="1" if self.enable_external else "0",
MDTEST_UPGRADE_LOCKFILES="1" if self.upgrade_lockfiles else "0",
),
capture_output=capture_output,
text=True,
@@ -268,20 +260,12 @@ def main() -> None:
action="store_true",
help="Enable tests with external dependencies",
)
parser.add_argument(
"--no-lockfile-upgrades",
action="store_true",
help="By default, lockfiles will be upgraded when dependency requirements in the Markdown test change."
+ " Set this flag to never upgrade any lockfiles.",
)
args = parser.parse_args()
try:
runner = MDTestRunner(
filters=args.filters,
enable_external=args.enable_external,
upgrade_lockfiles=not args.no_lockfile_upgrades,
filters=args.filters, enable_external=args.enable_external
)
runner.watch()
except KeyboardInterrupt:

View File

@@ -1,10 +0,0 @@
# regression test for https://github.com/astral-sh/ty/issues/2085
class Foo:
def __init__(self, x: int):
self.left = x
self.right = x
def method(self):
self.left, self.right = self.right, self.left
if self.right:
self.right = self.right

View File

@@ -1,8 +0,0 @@
# Regression test for https://github.com/astral-sh/ty/issues/1848
T = tuple[int, 'U']
class C(set['U']):
pass
type U = T | C

View File

@@ -266,12 +266,3 @@ def _(
) -> (int, str): # error: [invalid-type-form]
return x
```
### Special-cased diagnostic for `callable` used in a type expression
```py
# error: [invalid-type-form]
# error: [invalid-type-form]
def decorator(fn: callable) -> callable:
return fn
```

View File

@@ -193,7 +193,8 @@ class B:
reveal_type(B().name_does_not_matter()) # revealed: B
reveal_type(B().positional_only(1)) # revealed: B
reveal_type(B().keyword_only(x=1)) # revealed: B
reveal_type(B().decorated_method()) # revealed: B
# TODO: This should deally be `B`
reveal_type(B().decorated_method()) # revealed: Self@decorated_method
reveal_type(B().a_property) # revealed: B

View File

@@ -511,7 +511,7 @@ def f(self, dt: dict[str, Any], key: str):
```toml
[environment]
python-version = "3.14"
python-version = "3.12"
```
```py
@@ -550,7 +550,7 @@ g: list[Any] | dict[Any, Any] = f3(1)
reveal_type(g) # revealed: list[int] | dict[int, int]
```
We only prefer the declared type if it is in non-covariant position.
We currently prefer the generic declared type regardless of its variance:
```py
class Bivariant[T]:
@@ -594,22 +594,12 @@ x6: Covariant[Any] = covariant(1)
x7: Contravariant[Any] = contravariant(1)
x8: Invariant[Any] = invariant(1)
reveal_type(x5) # revealed: Bivariant[Literal[1]]
reveal_type(x6) # revealed: Covariant[Literal[1]]
reveal_type(x5) # revealed: Bivariant[Any]
reveal_type(x6) # revealed: Covariant[Any]
reveal_type(x7) # revealed: Contravariant[Any]
reveal_type(x8) # revealed: Invariant[Any]
```
```py
class X[T]:
def __init__(self: X[None]): ...
def pop(self) -> T:
raise NotImplementedError
x1: X[int | None] = X()
reveal_type(x1) # revealed: X[None]
```
## Narrow generic unions
```toml

View File

@@ -2775,23 +2775,6 @@ reveal_type(foo.bar) # revealed: Unknown
reveal_type(baz.bar) # revealed: Unknown
```
## Diagnostic for function attribute accessed on `Callable` type
<!-- snapshot-diagnostics -->
```toml
[environment]
python-version = "3.14"
```
```py
from typing import Callable
def f(x: Callable):
x.__name__ # error: [unresolved-attribute]
x.__annotate__ # error: [unresolved-attribute]
```
## References
Some of the tests in the *Class and instance variables* section draw inspiration from

View File

@@ -55,13 +55,10 @@ class TD(TypedDict):
d1 = {"x": 1}
d2: TD = {"x": 1}
d3: dict[str, int] = {"x": 1}
d4: TD = dict(x=1)
d5: TD = dict(x="1") # error: [invalid-argument-type]
reveal_type(d1) # revealed: dict[Unknown | str, Unknown | int]
reveal_type(d2) # revealed: TD
reveal_type(d3) # revealed: dict[str, int]
reveal_type(d4) # revealed: TD
def _() -> TD:
return {"x": 1}
@@ -271,8 +268,8 @@ class A:
A(f(1))
# error: [invalid-argument-type] "Argument to function `__new__` is incorrect: Expected `list[int | str]`, found `list[list[Unknown]]`"
# error: [invalid-argument-type] "Argument to bound method `__init__` is incorrect: Expected `list[int | None]`, found `list[list[Unknown]]`"
# error: [invalid-argument-type] "Argument to function `__new__` is incorrect: Expected `list[int | str]`, found `list[int | None | list[Unknown]] & list[int | str | list[Unknown]] & list[list[Unknown]]`"
# error: [invalid-argument-type] "Argument to bound method `__init__` is incorrect: Expected `list[int | None]`, found `list[int | None | list[Unknown]] & list[int | str | list[Unknown]] & list[list[Unknown]]`"
A(f([]))
```

View File

@@ -588,31 +588,6 @@ reveal_type(C.f2(1)) # revealed: str
reveal_type(C().f2(1)) # revealed: str
```
When a `@staticmethod` is decorated with `@contextmanager`, accessing it from an instance should not
bind `self`:
```py
from contextlib import contextmanager
from collections.abc import Iterator
class D:
@staticmethod
@contextmanager
def ctx(num: int) -> Iterator[int]:
yield num
def use_ctx(self) -> None:
# Accessing via self should not bind self
with self.ctx(10) as x:
reveal_type(x) # revealed: int
# Accessing via class works
reveal_type(D.ctx(5)) # revealed: _GeneratorContextManager[int, None, None]
# Accessing via instance should also work (no self-binding)
reveal_type(D().ctx(5)) # revealed: _GeneratorContextManager[int, None, None]
```
### `__new__`
`__new__` is an implicit `@staticmethod`; accessing it on an instance does not bind the `cls`

View File

@@ -71,38 +71,3 @@ e = a.__replace__(x="wrong") # error: [invalid-argument-type]
# TODO: this should ideally also be emit an error
e = replace(a, x="wrong")
```
### NamedTuples
NamedTuples also support the `__replace__` protocol:
```py
from typing import NamedTuple
from copy import replace
class Point(NamedTuple):
x: int
y: int
reveal_type(Point.__replace__) # revealed: (self: Self, *, x: int = ..., y: int = ...) -> Self
```
The `__replace__` method can either be called directly or through the `replace` function:
```py
a = Point(1, 2)
b = a.__replace__(x=3, y=4)
reveal_type(b) # revealed: Point
b = replace(a, x=3, y=4)
# TODO: this should be `Point`, once we support specialization of generic protocols
reveal_type(b) # revealed: Unknown
```
Invalid calls to `__replace__` will raise an error:
```py
# error: [unknown-argument] "Argument `z` does not match any known parameter"
a.__replace__(z=42)
```

View File

@@ -171,8 +171,6 @@ reveal_type(c.x) # revealed: int
## Delete items
### Basic item deletion
Deleting an item also invalidates the narrowing by the assignment, but accessing the item itself is
still valid.
@@ -191,93 +189,3 @@ def f(l: list[int]):
del l[0]
reveal_type(l[0]) # revealed: int
```
### `__delitem__` without `__getitem__`
A class or protocol that only defines `__delitem__` (without `__getitem__`) should still support
item deletion. The `__delitem__` method is independent of `__getitem__`.
```py
from typing import Protocol, TypeVar
KT = TypeVar("KT")
class CanDelItem(Protocol[KT]):
def __delitem__(self, k: KT, /) -> None: ...
def f(x: CanDelItem[int], k: int):
# This should be valid - the object has __delitem__
del x[k]
class OnlyDelItem:
def __delitem__(self, key: int) -> None:
pass
d = OnlyDelItem()
del d[0] # OK
# error: [non-subscriptable] "Cannot subscript object of type `OnlyDelItem` with no `__getitem__` method"
d[0]
```
### `__getitem__` without `__delitem__`
A class that only defines `__getitem__` (without `__delitem__`) should not support item deletion.
```py
class OnlyGetItem:
def __getitem__(self, key: int) -> str:
return "value"
g = OnlyGetItem()
reveal_type(g[0]) # revealed: str
# error: [non-subscriptable] "Cannot delete subscript on object of type `OnlyGetItem` with no `__delitem__` method"
del g[0]
```
### TypedDict deletion
Deleting a required key from a TypedDict is a type error because it would make the object no longer
a valid instance of that TypedDict type. However, deleting `NotRequired` keys (or keys in
`total=False` TypedDicts) is allowed.
<!-- snapshot-diagnostics -->
```py
from typing_extensions import TypedDict, NotRequired
class Movie(TypedDict):
name: str
year: int
class PartialMovie(TypedDict, total=False):
name: str
year: int
class MixedMovie(TypedDict):
name: str
year: NotRequired[int]
m: Movie = {"name": "Blade Runner", "year": 1982}
p: PartialMovie = {"name": "Test"}
mixed: MixedMovie = {"name": "Test"}
# Required keys cannot be deleted.
# error: [invalid-argument-type]
del m["name"]
# In a partial TypedDict (`total=False`), all keys can be deleted.
del p["name"]
# `NotRequired` keys can always be deleted.
del mixed["year"]
# But required keys in mixed `TypedDict` still cannot be deleted.
# error: [invalid-argument-type]
del mixed["name"]
# And keys that don't exist cannot be deleted.
# error: [invalid-argument-type]
del mixed["non_existent"]
```

View File

@@ -451,10 +451,6 @@ class Answer(Enum):
# revealed: tuple[Literal["YES"], Literal["NO"]]
reveal_type(enum_members(Answer))
# `nonmember` attributes are unwrapped to the inner value type when accessed.
# revealed: int
reveal_type(Answer.OTHER)
```
`member` can also be used as a decorator:

View File

@@ -1,23 +0,0 @@
version = 1
revision = 3
requires-python = "==3.13.*"
[[package]]
name = "attrs"
version = "25.4.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/6b/5c/685e6633917e101e5dcb62b9dd76946cbb57c26e133bae9e0cd36033c0a9/attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11", size = 934251, upload-time = "2025-10-06T13:54:44.725Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373", size = 67615, upload-time = "2025-10-06T13:54:43.17Z" },
]
[[package]]
name = "mdtest-deps"
version = "0.1.0"
source = { virtual = "." }
dependencies = [
{ name = "attrs" },
]
[package.metadata]
requires-dist = [{ name = "attrs", specifier = "==25.4.0" }]

View File

@@ -1,44 +0,0 @@
version = 1
revision = 3
requires-python = "==3.13.*"
[[package]]
name = "mdtest-deps"
version = "0.1.0"
source = { virtual = "." }
dependencies = [
{ name = "numpy" },
]
[package.metadata]
requires-dist = [{ name = "numpy", specifier = "==2.3.0" }]
[[package]]
name = "numpy"
version = "2.3.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/f3/db/8e12381333aea300890829a0a36bfa738cac95475d88982d538725143fd9/numpy-2.3.0.tar.gz", hash = "sha256:581f87f9e9e9db2cba2141400e160e9dd644ee248788d6f90636eeb8fd9260a6", size = 20382813, upload-time = "2025-06-07T14:54:32.608Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/73/fc/1d67f751fd4dbafc5780244fe699bc4084268bad44b7c5deb0492473127b/numpy-2.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5754ab5595bfa2c2387d241296e0381c21f44a4b90a776c3c1d39eede13a746a", size = 20889633, upload-time = "2025-06-07T14:44:06.839Z" },
{ url = "https://files.pythonhosted.org/packages/e8/95/73ffdb69e5c3f19ec4530f8924c4386e7ba097efc94b9c0aff607178ad94/numpy-2.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d11fa02f77752d8099573d64e5fe33de3229b6632036ec08f7080f46b6649959", size = 14151683, upload-time = "2025-06-07T14:44:28.847Z" },
{ url = "https://files.pythonhosted.org/packages/64/d5/06d4bb31bb65a1d9c419eb5676173a2f90fd8da3c59f816cc54c640ce265/numpy-2.3.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:aba48d17e87688a765ab1cd557882052f238e2f36545dfa8e29e6a91aef77afe", size = 5102683, upload-time = "2025-06-07T14:44:38.417Z" },
{ url = "https://files.pythonhosted.org/packages/12/8b/6c2cef44f8ccdc231f6b56013dff1d71138c48124334aded36b1a1b30c5a/numpy-2.3.0-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:4dc58865623023b63b10d52f18abaac3729346a7a46a778381e0e3af4b7f3beb", size = 6640253, upload-time = "2025-06-07T14:44:49.359Z" },
{ url = "https://files.pythonhosted.org/packages/62/aa/fca4bf8de3396ddb59544df9b75ffe5b73096174de97a9492d426f5cd4aa/numpy-2.3.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:df470d376f54e052c76517393fa443758fefcdd634645bc9c1f84eafc67087f0", size = 14258658, upload-time = "2025-06-07T14:45:10.156Z" },
{ url = "https://files.pythonhosted.org/packages/1c/12/734dce1087eed1875f2297f687e671cfe53a091b6f2f55f0c7241aad041b/numpy-2.3.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:87717eb24d4a8a64683b7a4e91ace04e2f5c7c77872f823f02a94feee186168f", size = 16628765, upload-time = "2025-06-07T14:45:35.076Z" },
{ url = "https://files.pythonhosted.org/packages/48/03/ffa41ade0e825cbcd5606a5669962419528212a16082763fc051a7247d76/numpy-2.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d8fa264d56882b59dcb5ea4d6ab6f31d0c58a57b41aec605848b6eb2ef4a43e8", size = 15564335, upload-time = "2025-06-07T14:45:58.797Z" },
{ url = "https://files.pythonhosted.org/packages/07/58/869398a11863310aee0ff85a3e13b4c12f20d032b90c4b3ee93c3b728393/numpy-2.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e651756066a0eaf900916497e20e02fe1ae544187cb0fe88de981671ee7f6270", size = 18360608, upload-time = "2025-06-07T14:46:25.687Z" },
{ url = "https://files.pythonhosted.org/packages/2f/8a/5756935752ad278c17e8a061eb2127c9a3edf4ba2c31779548b336f23c8d/numpy-2.3.0-cp313-cp313-win32.whl", hash = "sha256:e43c3cce3b6ae5f94696669ff2a6eafd9a6b9332008bafa4117af70f4b88be6f", size = 6310005, upload-time = "2025-06-07T14:50:13.138Z" },
{ url = "https://files.pythonhosted.org/packages/08/60/61d60cf0dfc0bf15381eaef46366ebc0c1a787856d1db0c80b006092af84/numpy-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:81ae0bf2564cf475f94be4a27ef7bcf8af0c3e28da46770fc904da9abd5279b5", size = 12729093, upload-time = "2025-06-07T14:50:31.82Z" },
{ url = "https://files.pythonhosted.org/packages/66/31/2f2f2d2b3e3c32d5753d01437240feaa32220b73258c9eef2e42a0832866/numpy-2.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:c8738baa52505fa6e82778580b23f945e3578412554d937093eac9205e845e6e", size = 9885689, upload-time = "2025-06-07T14:50:47.888Z" },
{ url = "https://files.pythonhosted.org/packages/f1/89/c7828f23cc50f607ceb912774bb4cff225ccae7131c431398ad8400e2c98/numpy-2.3.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:39b27d8b38942a647f048b675f134dd5a567f95bfff481f9109ec308515c51d8", size = 20986612, upload-time = "2025-06-07T14:46:56.077Z" },
{ url = "https://files.pythonhosted.org/packages/dd/46/79ecf47da34c4c50eedec7511e53d57ffdfd31c742c00be7dc1d5ffdb917/numpy-2.3.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:0eba4a1ea88f9a6f30f56fdafdeb8da3774349eacddab9581a21234b8535d3d3", size = 14298953, upload-time = "2025-06-07T14:47:18.053Z" },
{ url = "https://files.pythonhosted.org/packages/59/44/f6caf50713d6ff4480640bccb2a534ce1d8e6e0960c8f864947439f0ee95/numpy-2.3.0-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:b0f1f11d0a1da54927436505a5a7670b154eac27f5672afc389661013dfe3d4f", size = 5225806, upload-time = "2025-06-07T14:47:27.524Z" },
{ url = "https://files.pythonhosted.org/packages/a6/43/e1fd1aca7c97e234dd05e66de4ab7a5be54548257efcdd1bc33637e72102/numpy-2.3.0-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:690d0a5b60a47e1f9dcec7b77750a4854c0d690e9058b7bef3106e3ae9117808", size = 6735169, upload-time = "2025-06-07T14:47:38.057Z" },
{ url = "https://files.pythonhosted.org/packages/84/89/f76f93b06a03177c0faa7ca94d0856c4e5c4bcaf3c5f77640c9ed0303e1c/numpy-2.3.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:8b51ead2b258284458e570942137155978583e407babc22e3d0ed7af33ce06f8", size = 14330701, upload-time = "2025-06-07T14:47:59.113Z" },
{ url = "https://files.pythonhosted.org/packages/aa/f5/4858c3e9ff7a7d64561b20580cf7cc5d085794bd465a19604945d6501f6c/numpy-2.3.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:aaf81c7b82c73bd9b45e79cfb9476cb9c29e937494bfe9092c26aece812818ad", size = 16692983, upload-time = "2025-06-07T14:48:24.196Z" },
{ url = "https://files.pythonhosted.org/packages/08/17/0e3b4182e691a10e9483bcc62b4bb8693dbf9ea5dc9ba0b77a60435074bb/numpy-2.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:f420033a20b4f6a2a11f585f93c843ac40686a7c3fa514060a97d9de93e5e72b", size = 15641435, upload-time = "2025-06-07T14:48:47.712Z" },
{ url = "https://files.pythonhosted.org/packages/4e/d5/463279fda028d3c1efa74e7e8d507605ae87f33dbd0543cf4c4527c8b882/numpy-2.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d344ca32ab482bcf8735d8f95091ad081f97120546f3d250240868430ce52555", size = 18433798, upload-time = "2025-06-07T14:49:14.866Z" },
{ url = "https://files.pythonhosted.org/packages/0e/1e/7a9d98c886d4c39a2b4d3a7c026bffcf8fbcaf518782132d12a301cfc47a/numpy-2.3.0-cp313-cp313t-win32.whl", hash = "sha256:48a2e8eaf76364c32a1feaa60d6925eaf32ed7a040183b807e02674305beef61", size = 6438632, upload-time = "2025-06-07T14:49:25.67Z" },
{ url = "https://files.pythonhosted.org/packages/fe/ab/66fc909931d5eb230107d016861824f335ae2c0533f422e654e5ff556784/numpy-2.3.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ba17f93a94e503551f154de210e4d50c5e3ee20f7e7a1b5f6ce3f22d419b93bb", size = 12868491, upload-time = "2025-06-07T14:49:44.898Z" },
{ url = "https://files.pythonhosted.org/packages/ee/e8/2c8a1c9e34d6f6d600c83d5ce5b71646c32a13f34ca5c518cc060639841c/numpy-2.3.0-cp313-cp313t-win_arm64.whl", hash = "sha256:f14e016d9409680959691c109be98c436c6249eaf7f118b424679793607b5944", size = 9935345, upload-time = "2025-06-07T14:50:02.311Z" },
]

View File

@@ -1,88 +0,0 @@
version = 1
revision = 3
requires-python = "==3.12.*"
[[package]]
name = "annotated-types"
version = "0.7.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" },
]
[[package]]
name = "mdtest-deps"
version = "0.1.0"
source = { virtual = "." }
dependencies = [
{ name = "pydantic" },
]
[package.metadata]
requires-dist = [{ name = "pydantic", specifier = "==2.12.2" }]
[[package]]
name = "pydantic"
version = "2.12.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "annotated-types" },
{ name = "pydantic-core" },
{ name = "typing-extensions" },
{ name = "typing-inspection" },
]
sdist = { url = "https://files.pythonhosted.org/packages/8d/35/d319ed522433215526689bad428a94058b6dd12190ce7ddd78618ac14b28/pydantic-2.12.2.tar.gz", hash = "sha256:7b8fa15b831a4bbde9d5b84028641ac3080a4ca2cbd4a621a661687e741624fd", size = 816358, upload-time = "2025-10-14T15:02:21.842Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/6c/98/468cb649f208a6f1279448e6e5247b37ae79cf5e4041186f1e2ef3d16345/pydantic-2.12.2-py3-none-any.whl", hash = "sha256:25ff718ee909acd82f1ff9b1a4acfd781bb23ab3739adaa7144f19a6a4e231ae", size = 460628, upload-time = "2025-10-14T15:02:19.623Z" },
]
[[package]]
name = "pydantic-core"
version = "2.41.4"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/df/18/d0944e8eaaa3efd0a91b0f1fc537d3be55ad35091b6a87638211ba691964/pydantic_core-2.41.4.tar.gz", hash = "sha256:70e47929a9d4a1905a67e4b687d5946026390568a8e952b92824118063cee4d5", size = 457557, upload-time = "2025-10-14T10:23:47.909Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/e9/81/d3b3e95929c4369d30b2a66a91db63c8ed0a98381ae55a45da2cd1cc1288/pydantic_core-2.41.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:ab06d77e053d660a6faaf04894446df7b0a7e7aba70c2797465a0a1af00fc887", size = 2099043, upload-time = "2025-10-14T10:20:28.561Z" },
{ url = "https://files.pythonhosted.org/packages/58/da/46fdac49e6717e3a94fc9201403e08d9d61aa7a770fab6190b8740749047/pydantic_core-2.41.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c53ff33e603a9c1179a9364b0a24694f183717b2e0da2b5ad43c316c956901b2", size = 1910699, upload-time = "2025-10-14T10:20:30.217Z" },
{ url = "https://files.pythonhosted.org/packages/1e/63/4d948f1b9dd8e991a5a98b77dd66c74641f5f2e5225fee37994b2e07d391/pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:304c54176af2c143bd181d82e77c15c41cbacea8872a2225dd37e6544dce9999", size = 1952121, upload-time = "2025-10-14T10:20:32.246Z" },
{ url = "https://files.pythonhosted.org/packages/b2/a7/e5fc60a6f781fc634ecaa9ecc3c20171d238794cef69ae0af79ac11b89d7/pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:025ba34a4cf4fb32f917d5d188ab5e702223d3ba603be4d8aca2f82bede432a4", size = 2041590, upload-time = "2025-10-14T10:20:34.332Z" },
{ url = "https://files.pythonhosted.org/packages/70/69/dce747b1d21d59e85af433428978a1893c6f8a7068fa2bb4a927fba7a5ff/pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b9f5f30c402ed58f90c70e12eff65547d3ab74685ffe8283c719e6bead8ef53f", size = 2219869, upload-time = "2025-10-14T10:20:35.965Z" },
{ url = "https://files.pythonhosted.org/packages/83/6a/c070e30e295403bf29c4df1cb781317b6a9bac7cd07b8d3acc94d501a63c/pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dd96e5d15385d301733113bcaa324c8bcf111275b7675a9c6e88bfb19fc05e3b", size = 2345169, upload-time = "2025-10-14T10:20:37.627Z" },
{ url = "https://files.pythonhosted.org/packages/f0/83/06d001f8043c336baea7fd202a9ac7ad71f87e1c55d8112c50b745c40324/pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98f348cbb44fae6e9653c1055db7e29de67ea6a9ca03a5fa2c2e11a47cff0e47", size = 2070165, upload-time = "2025-10-14T10:20:39.246Z" },
{ url = "https://files.pythonhosted.org/packages/14/0a/e567c2883588dd12bcbc110232d892cf385356f7c8a9910311ac997ab715/pydantic_core-2.41.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ec22626a2d14620a83ca583c6f5a4080fa3155282718b6055c2ea48d3ef35970", size = 2189067, upload-time = "2025-10-14T10:20:41.015Z" },
{ url = "https://files.pythonhosted.org/packages/f4/1d/3d9fca34273ba03c9b1c5289f7618bc4bd09c3ad2289b5420481aa051a99/pydantic_core-2.41.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3a95d4590b1f1a43bf33ca6d647b990a88f4a3824a8c4572c708f0b45a5290ed", size = 2132997, upload-time = "2025-10-14T10:20:43.106Z" },
{ url = "https://files.pythonhosted.org/packages/52/70/d702ef7a6cd41a8afc61f3554922b3ed8d19dd54c3bd4bdbfe332e610827/pydantic_core-2.41.4-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:f9672ab4d398e1b602feadcffcdd3af44d5f5e6ddc15bc7d15d376d47e8e19f8", size = 2307187, upload-time = "2025-10-14T10:20:44.849Z" },
{ url = "https://files.pythonhosted.org/packages/68/4c/c06be6e27545d08b802127914156f38d10ca287a9e8489342793de8aae3c/pydantic_core-2.41.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:84d8854db5f55fead3b579f04bda9a36461dab0730c5d570e1526483e7bb8431", size = 2305204, upload-time = "2025-10-14T10:20:46.781Z" },
{ url = "https://files.pythonhosted.org/packages/b0/e5/35ae4919bcd9f18603419e23c5eaf32750224a89d41a8df1a3704b69f77e/pydantic_core-2.41.4-cp312-cp312-win32.whl", hash = "sha256:9be1c01adb2ecc4e464392c36d17f97e9110fbbc906bcbe1c943b5b87a74aabd", size = 1972536, upload-time = "2025-10-14T10:20:48.39Z" },
{ url = "https://files.pythonhosted.org/packages/1e/c2/49c5bb6d2a49eb2ee3647a93e3dae7080c6409a8a7558b075027644e879c/pydantic_core-2.41.4-cp312-cp312-win_amd64.whl", hash = "sha256:d682cf1d22bab22a5be08539dca3d1593488a99998f9f412137bc323179067ff", size = 2031132, upload-time = "2025-10-14T10:20:50.421Z" },
{ url = "https://files.pythonhosted.org/packages/06/23/936343dbcba6eec93f73e95eb346810fc732f71ba27967b287b66f7b7097/pydantic_core-2.41.4-cp312-cp312-win_arm64.whl", hash = "sha256:833eebfd75a26d17470b58768c1834dfc90141b7afc6eb0429c21fc5a21dcfb8", size = 1969483, upload-time = "2025-10-14T10:20:52.35Z" },
{ url = "https://files.pythonhosted.org/packages/c4/48/ae937e5a831b7c0dc646b2ef788c27cd003894882415300ed21927c21efa/pydantic_core-2.41.4-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:4f5d640aeebb438517150fdeec097739614421900e4a08db4a3ef38898798537", size = 2112087, upload-time = "2025-10-14T10:22:56.818Z" },
{ url = "https://files.pythonhosted.org/packages/5e/db/6db8073e3d32dae017da7e0d16a9ecb897d0a4d92e00634916e486097961/pydantic_core-2.41.4-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:4a9ab037b71927babc6d9e7fc01aea9e66dc2a4a34dff06ef0724a4049629f94", size = 1920387, upload-time = "2025-10-14T10:22:59.342Z" },
{ url = "https://files.pythonhosted.org/packages/0d/c1/dd3542d072fcc336030d66834872f0328727e3b8de289c662faa04aa270e/pydantic_core-2.41.4-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4dab9484ec605c3016df9ad4fd4f9a390bc5d816a3b10c6550f8424bb80b18c", size = 1951495, upload-time = "2025-10-14T10:23:02.089Z" },
{ url = "https://files.pythonhosted.org/packages/2b/c6/db8d13a1f8ab3f1eb08c88bd00fd62d44311e3456d1e85c0e59e0a0376e7/pydantic_core-2.41.4-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd8a5028425820731d8c6c098ab642d7b8b999758e24acae03ed38a66eca8335", size = 2139008, upload-time = "2025-10-14T10:23:04.539Z" },
]
[[package]]
name = "typing-extensions"
version = "4.15.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" },
]
[[package]]
name = "typing-inspection"
version = "0.4.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" },
]

View File

@@ -1,75 +0,0 @@
version = 1
revision = 3
requires-python = "==3.13.*"
[[package]]
name = "colorama"
version = "0.4.6"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
]
[[package]]
name = "iniconfig"
version = "2.3.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" },
]
[[package]]
name = "mdtest-deps"
version = "0.1.0"
source = { virtual = "." }
dependencies = [
{ name = "pytest" },
]
[package.metadata]
requires-dist = [{ name = "pytest", specifier = "==9.0.1" }]
[[package]]
name = "packaging"
version = "25.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" },
]
[[package]]
name = "pluggy"
version = "1.6.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" },
]
[[package]]
name = "pygments"
version = "2.19.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" },
]
[[package]]
name = "pytest"
version = "9.0.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "colorama", marker = "sys_platform == 'win32'" },
{ name = "iniconfig" },
{ name = "packaging" },
{ name = "pluggy" },
{ name = "pygments" },
]
sdist = { url = "https://files.pythonhosted.org/packages/07/56/f013048ac4bc4c1d9be45afd4ab209ea62822fb1598f40687e6bf45dcea4/pytest-9.0.1.tar.gz", hash = "sha256:3e9c069ea73583e255c3b21cf46b8d3c56f6e3a1a8f6da94ccb0fcf57b9d73c8", size = 1564125, upload-time = "2025-11-12T13:05:09.333Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/0b/8b/6300fb80f858cda1c51ffa17075df5d846757081d11ab4aa35cef9e6258b/pytest-9.0.1-py3-none-any.whl", hash = "sha256:67be0030d194df2dfa7b556f2e56fb3c3315bd5c8822c6951162b92b32ce7dad", size = 373668, upload-time = "2025-11-12T13:05:07.379Z" },
]

View File

@@ -1,60 +0,0 @@
version = 1
revision = 3
requires-python = "==3.13.*"
[[package]]
name = "greenlet"
version = "3.3.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/c7/e5/40dbda2736893e3e53d25838e0f19a2b417dfc122b9989c91918db30b5d3/greenlet-3.3.0.tar.gz", hash = "sha256:a82bb225a4e9e4d653dd2fb7b8b2d36e4fb25bc0165422a11e48b88e9e6f78fb", size = 190651, upload-time = "2025-12-04T14:49:44.05Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/02/2f/28592176381b9ab2cafa12829ba7b472d177f3acc35d8fbcf3673d966fff/greenlet-3.3.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:a1e41a81c7e2825822f4e068c48cb2196002362619e2d70b148f20a831c00739", size = 275140, upload-time = "2025-12-04T14:23:01.282Z" },
{ url = "https://files.pythonhosted.org/packages/2c/80/fbe937bf81e9fca98c981fe499e59a3f45df2a04da0baa5c2be0dca0d329/greenlet-3.3.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9f515a47d02da4d30caaa85b69474cec77b7929b2e936ff7fb853d42f4bf8808", size = 599219, upload-time = "2025-12-04T14:50:08.309Z" },
{ url = "https://files.pythonhosted.org/packages/c2/ff/7c985128f0514271b8268476af89aee6866df5eec04ac17dcfbc676213df/greenlet-3.3.0-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7d2d9fd66bfadf230b385fdc90426fcd6eb64db54b40c495b72ac0feb5766c54", size = 610211, upload-time = "2025-12-04T14:57:43.968Z" },
{ url = "https://files.pythonhosted.org/packages/79/07/c47a82d881319ec18a4510bb30463ed6891f2ad2c1901ed5ec23d3de351f/greenlet-3.3.0-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:30a6e28487a790417d036088b3bcb3f3ac7d8babaa7d0139edbaddebf3af9492", size = 624311, upload-time = "2025-12-04T15:07:14.697Z" },
{ url = "https://files.pythonhosted.org/packages/fd/8e/424b8c6e78bd9837d14ff7df01a9829fc883ba2ab4ea787d4f848435f23f/greenlet-3.3.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:087ea5e004437321508a8d6f20efc4cfec5e3c30118e1417ea96ed1d93950527", size = 612833, upload-time = "2025-12-04T14:26:03.669Z" },
{ url = "https://files.pythonhosted.org/packages/b5/ba/56699ff9b7c76ca12f1cdc27a886d0f81f2189c3455ff9f65246780f713d/greenlet-3.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ab97cf74045343f6c60a39913fa59710e4bd26a536ce7ab2397adf8b27e67c39", size = 1567256, upload-time = "2025-12-04T15:04:25.276Z" },
{ url = "https://files.pythonhosted.org/packages/1e/37/f31136132967982d698c71a281a8901daf1a8fbab935dce7c0cf15f942cc/greenlet-3.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5375d2e23184629112ca1ea89a53389dddbffcf417dad40125713d88eb5f96e8", size = 1636483, upload-time = "2025-12-04T14:27:30.804Z" },
{ url = "https://files.pythonhosted.org/packages/7e/71/ba21c3fb8c5dce83b8c01f458a42e99ffdb1963aeec08fff5a18588d8fd7/greenlet-3.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:9ee1942ea19550094033c35d25d20726e4f1c40d59545815e1128ac58d416d38", size = 301833, upload-time = "2025-12-04T14:32:23.929Z" },
]
[[package]]
name = "mdtest-deps"
version = "0.1.0"
source = { virtual = "." }
dependencies = [
{ name = "sqlalchemy" },
]
[package.metadata]
requires-dist = [{ name = "sqlalchemy", specifier = "==2.0.44" }]
[[package]]
name = "sqlalchemy"
version = "2.0.44"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "greenlet", marker = "platform_machine == 'AMD64' or platform_machine == 'WIN32' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'ppc64le' or platform_machine == 'win32' or platform_machine == 'x86_64'" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/f0/f2/840d7b9496825333f532d2e3976b8eadbf52034178aac53630d09fe6e1ef/sqlalchemy-2.0.44.tar.gz", hash = "sha256:0ae7454e1ab1d780aee69fd2aae7d6b8670a581d8847f2d1e0f7ddfbf47e5a22", size = 9819830, upload-time = "2025-10-10T14:39:12.935Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/45/d3/c67077a2249fdb455246e6853166360054c331db4613cda3e31ab1cadbef/sqlalchemy-2.0.44-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ff486e183d151e51b1d694c7aa1695747599bb00b9f5f604092b54b74c64a8e1", size = 2135479, upload-time = "2025-10-10T16:03:37.671Z" },
{ url = "https://files.pythonhosted.org/packages/2b/91/eabd0688330d6fd114f5f12c4f89b0d02929f525e6bf7ff80aa17ca802af/sqlalchemy-2.0.44-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0b1af8392eb27b372ddb783b317dea0f650241cea5bd29199b22235299ca2e45", size = 2123212, upload-time = "2025-10-10T16:03:41.755Z" },
{ url = "https://files.pythonhosted.org/packages/b0/bb/43e246cfe0e81c018076a16036d9b548c4cc649de241fa27d8d9ca6f85ab/sqlalchemy-2.0.44-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2b61188657e3a2b9ac4e8f04d6cf8e51046e28175f79464c67f2fd35bceb0976", size = 3255353, upload-time = "2025-10-10T15:35:31.221Z" },
{ url = "https://files.pythonhosted.org/packages/b9/96/c6105ed9a880abe346b64d3b6ddef269ddfcab04f7f3d90a0bf3c5a88e82/sqlalchemy-2.0.44-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b87e7b91a5d5973dda5f00cd61ef72ad75a1db73a386b62877d4875a8840959c", size = 3260222, upload-time = "2025-10-10T15:43:50.124Z" },
{ url = "https://files.pythonhosted.org/packages/44/16/1857e35a47155b5ad927272fee81ae49d398959cb749edca6eaa399b582f/sqlalchemy-2.0.44-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:15f3326f7f0b2bfe406ee562e17f43f36e16167af99c4c0df61db668de20002d", size = 3189614, upload-time = "2025-10-10T15:35:32.578Z" },
{ url = "https://files.pythonhosted.org/packages/88/ee/4afb39a8ee4fc786e2d716c20ab87b5b1fb33d4ac4129a1aaa574ae8a585/sqlalchemy-2.0.44-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1e77faf6ff919aa8cd63f1c4e561cac1d9a454a191bb864d5dd5e545935e5a40", size = 3226248, upload-time = "2025-10-10T15:43:51.862Z" },
{ url = "https://files.pythonhosted.org/packages/32/d5/0e66097fc64fa266f29a7963296b40a80d6a997b7ac13806183700676f86/sqlalchemy-2.0.44-cp313-cp313-win32.whl", hash = "sha256:ee51625c2d51f8baadf2829fae817ad0b66b140573939dd69284d2ba3553ae73", size = 2101275, upload-time = "2025-10-10T15:03:26.096Z" },
{ url = "https://files.pythonhosted.org/packages/03/51/665617fe4f8c6450f42a6d8d69243f9420f5677395572c2fe9d21b493b7b/sqlalchemy-2.0.44-cp313-cp313-win_amd64.whl", hash = "sha256:c1c80faaee1a6c3428cecf40d16a2365bcf56c424c92c2b6f0f9ad204b899e9e", size = 2127901, upload-time = "2025-10-10T15:03:27.548Z" },
{ url = "https://files.pythonhosted.org/packages/9c/5e/6a29fa884d9fb7ddadf6b69490a9d45fded3b38541713010dad16b77d015/sqlalchemy-2.0.44-py3-none-any.whl", hash = "sha256:19de7ca1246fbef9f9d1bff8f1ab25641569df226364a0e40457dc5457c54b05", size = 1928718, upload-time = "2025-10-10T15:29:45.32Z" },
]
[[package]]
name = "typing-extensions"
version = "4.15.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" },
]

View File

@@ -1,134 +0,0 @@
version = 1
revision = 3
requires-python = "==3.13.*"
[[package]]
name = "annotated-types"
version = "0.7.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" },
]
[[package]]
name = "greenlet"
version = "3.3.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/c7/e5/40dbda2736893e3e53d25838e0f19a2b417dfc122b9989c91918db30b5d3/greenlet-3.3.0.tar.gz", hash = "sha256:a82bb225a4e9e4d653dd2fb7b8b2d36e4fb25bc0165422a11e48b88e9e6f78fb", size = 190651, upload-time = "2025-12-04T14:49:44.05Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/02/2f/28592176381b9ab2cafa12829ba7b472d177f3acc35d8fbcf3673d966fff/greenlet-3.3.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:a1e41a81c7e2825822f4e068c48cb2196002362619e2d70b148f20a831c00739", size = 275140, upload-time = "2025-12-04T14:23:01.282Z" },
{ url = "https://files.pythonhosted.org/packages/2c/80/fbe937bf81e9fca98c981fe499e59a3f45df2a04da0baa5c2be0dca0d329/greenlet-3.3.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9f515a47d02da4d30caaa85b69474cec77b7929b2e936ff7fb853d42f4bf8808", size = 599219, upload-time = "2025-12-04T14:50:08.309Z" },
{ url = "https://files.pythonhosted.org/packages/c2/ff/7c985128f0514271b8268476af89aee6866df5eec04ac17dcfbc676213df/greenlet-3.3.0-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7d2d9fd66bfadf230b385fdc90426fcd6eb64db54b40c495b72ac0feb5766c54", size = 610211, upload-time = "2025-12-04T14:57:43.968Z" },
{ url = "https://files.pythonhosted.org/packages/79/07/c47a82d881319ec18a4510bb30463ed6891f2ad2c1901ed5ec23d3de351f/greenlet-3.3.0-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:30a6e28487a790417d036088b3bcb3f3ac7d8babaa7d0139edbaddebf3af9492", size = 624311, upload-time = "2025-12-04T15:07:14.697Z" },
{ url = "https://files.pythonhosted.org/packages/fd/8e/424b8c6e78bd9837d14ff7df01a9829fc883ba2ab4ea787d4f848435f23f/greenlet-3.3.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:087ea5e004437321508a8d6f20efc4cfec5e3c30118e1417ea96ed1d93950527", size = 612833, upload-time = "2025-12-04T14:26:03.669Z" },
{ url = "https://files.pythonhosted.org/packages/b5/ba/56699ff9b7c76ca12f1cdc27a886d0f81f2189c3455ff9f65246780f713d/greenlet-3.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ab97cf74045343f6c60a39913fa59710e4bd26a536ce7ab2397adf8b27e67c39", size = 1567256, upload-time = "2025-12-04T15:04:25.276Z" },
{ url = "https://files.pythonhosted.org/packages/1e/37/f31136132967982d698c71a281a8901daf1a8fbab935dce7c0cf15f942cc/greenlet-3.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5375d2e23184629112ca1ea89a53389dddbffcf417dad40125713d88eb5f96e8", size = 1636483, upload-time = "2025-12-04T14:27:30.804Z" },
{ url = "https://files.pythonhosted.org/packages/7e/71/ba21c3fb8c5dce83b8c01f458a42e99ffdb1963aeec08fff5a18588d8fd7/greenlet-3.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:9ee1942ea19550094033c35d25d20726e4f1c40d59545815e1128ac58d416d38", size = 301833, upload-time = "2025-12-04T14:32:23.929Z" },
]
[[package]]
name = "mdtest-deps"
version = "0.1.0"
source = { virtual = "." }
dependencies = [
{ name = "sqlmodel" },
]
[package.metadata]
requires-dist = [{ name = "sqlmodel", specifier = "==0.0.27" }]
[[package]]
name = "pydantic"
version = "2.12.5"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "annotated-types" },
{ name = "pydantic-core" },
{ name = "typing-extensions" },
{ name = "typing-inspection" },
]
sdist = { url = "https://files.pythonhosted.org/packages/69/44/36f1a6e523abc58ae5f928898e4aca2e0ea509b5aa6f6f392a5d882be928/pydantic-2.12.5.tar.gz", hash = "sha256:4d351024c75c0f085a9febbb665ce8c0c6ec5d30e903bdb6394b7ede26aebb49", size = 821591, upload-time = "2025-11-26T15:11:46.471Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl", hash = "sha256:e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d", size = 463580, upload-time = "2025-11-26T15:11:44.605Z" },
]
[[package]]
name = "pydantic-core"
version = "2.41.5"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/71/70/23b021c950c2addd24ec408e9ab05d59b035b39d97cdc1130e1bce647bb6/pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e", size = 460952, upload-time = "2025-11-04T13:43:49.098Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/87/06/8806241ff1f70d9939f9af039c6c35f2360cf16e93c2ca76f184e76b1564/pydantic_core-2.41.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:941103c9be18ac8daf7b7adca8228f8ed6bb7a1849020f643b3a14d15b1924d9", size = 2120403, upload-time = "2025-11-04T13:40:25.248Z" },
{ url = "https://files.pythonhosted.org/packages/94/02/abfa0e0bda67faa65fef1c84971c7e45928e108fe24333c81f3bfe35d5f5/pydantic_core-2.41.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:112e305c3314f40c93998e567879e887a3160bb8689ef3d2c04b6cc62c33ac34", size = 1896206, upload-time = "2025-11-04T13:40:27.099Z" },
{ url = "https://files.pythonhosted.org/packages/15/df/a4c740c0943e93e6500f9eb23f4ca7ec9bf71b19e608ae5b579678c8d02f/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cbaad15cb0c90aa221d43c00e77bb33c93e8d36e0bf74760cd00e732d10a6a0", size = 1919307, upload-time = "2025-11-04T13:40:29.806Z" },
{ url = "https://files.pythonhosted.org/packages/9a/e3/6324802931ae1d123528988e0e86587c2072ac2e5394b4bc2bc34b61ff6e/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:03ca43e12fab6023fc79d28ca6b39b05f794ad08ec2feccc59a339b02f2b3d33", size = 2063258, upload-time = "2025-11-04T13:40:33.544Z" },
{ url = "https://files.pythonhosted.org/packages/c9/d4/2230d7151d4957dd79c3044ea26346c148c98fbf0ee6ebd41056f2d62ab5/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc799088c08fa04e43144b164feb0c13f9a0bc40503f8df3e9fde58a3c0c101e", size = 2214917, upload-time = "2025-11-04T13:40:35.479Z" },
{ url = "https://files.pythonhosted.org/packages/e6/9f/eaac5df17a3672fef0081b6c1bb0b82b33ee89aa5cec0d7b05f52fd4a1fa/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:97aeba56665b4c3235a0e52b2c2f5ae9cd071b8a8310ad27bddb3f7fb30e9aa2", size = 2332186, upload-time = "2025-11-04T13:40:37.436Z" },
{ url = "https://files.pythonhosted.org/packages/cf/4e/35a80cae583a37cf15604b44240e45c05e04e86f9cfd766623149297e971/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:406bf18d345822d6c21366031003612b9c77b3e29ffdb0f612367352aab7d586", size = 2073164, upload-time = "2025-11-04T13:40:40.289Z" },
{ url = "https://files.pythonhosted.org/packages/bf/e3/f6e262673c6140dd3305d144d032f7bd5f7497d3871c1428521f19f9efa2/pydantic_core-2.41.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b93590ae81f7010dbe380cdeab6f515902ebcbefe0b9327cc4804d74e93ae69d", size = 2179146, upload-time = "2025-11-04T13:40:42.809Z" },
{ url = "https://files.pythonhosted.org/packages/75/c7/20bd7fc05f0c6ea2056a4565c6f36f8968c0924f19b7d97bbfea55780e73/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:01a3d0ab748ee531f4ea6c3e48ad9dac84ddba4b0d82291f87248f2f9de8d740", size = 2137788, upload-time = "2025-11-04T13:40:44.752Z" },
{ url = "https://files.pythonhosted.org/packages/3a/8d/34318ef985c45196e004bc46c6eab2eda437e744c124ef0dbe1ff2c9d06b/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:6561e94ba9dacc9c61bce40e2d6bdc3bfaa0259d3ff36ace3b1e6901936d2e3e", size = 2340133, upload-time = "2025-11-04T13:40:46.66Z" },
{ url = "https://files.pythonhosted.org/packages/9c/59/013626bf8c78a5a5d9350d12e7697d3d4de951a75565496abd40ccd46bee/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:915c3d10f81bec3a74fbd4faebe8391013ba61e5a1a8d48c4455b923bdda7858", size = 2324852, upload-time = "2025-11-04T13:40:48.575Z" },
{ url = "https://files.pythonhosted.org/packages/1a/d9/c248c103856f807ef70c18a4f986693a46a8ffe1602e5d361485da502d20/pydantic_core-2.41.5-cp313-cp313-win32.whl", hash = "sha256:650ae77860b45cfa6e2cdafc42618ceafab3a2d9a3811fcfbd3bbf8ac3c40d36", size = 1994679, upload-time = "2025-11-04T13:40:50.619Z" },
{ url = "https://files.pythonhosted.org/packages/9e/8b/341991b158ddab181cff136acd2552c9f35bd30380422a639c0671e99a91/pydantic_core-2.41.5-cp313-cp313-win_amd64.whl", hash = "sha256:79ec52ec461e99e13791ec6508c722742ad745571f234ea6255bed38c6480f11", size = 2019766, upload-time = "2025-11-04T13:40:52.631Z" },
{ url = "https://files.pythonhosted.org/packages/73/7d/f2f9db34af103bea3e09735bb40b021788a5e834c81eedb541991badf8f5/pydantic_core-2.41.5-cp313-cp313-win_arm64.whl", hash = "sha256:3f84d5c1b4ab906093bdc1ff10484838aca54ef08de4afa9de0f5f14d69639cd", size = 1981005, upload-time = "2025-11-04T13:40:54.734Z" },
]
[[package]]
name = "sqlalchemy"
version = "2.0.45"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "greenlet", marker = "platform_machine == 'AMD64' or platform_machine == 'WIN32' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'ppc64le' or platform_machine == 'win32' or platform_machine == 'x86_64'" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/be/f9/5e4491e5ccf42f5d9cfc663741d261b3e6e1683ae7812114e7636409fcc6/sqlalchemy-2.0.45.tar.gz", hash = "sha256:1632a4bda8d2d25703fdad6363058d882541bdaaee0e5e3ddfa0cd3229efce88", size = 9869912, upload-time = "2025-12-09T21:05:16.737Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/6a/c8/7cc5221b47a54edc72a0140a1efa56e0a2730eefa4058d7ed0b4c4357ff8/sqlalchemy-2.0.45-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fe187fc31a54d7fd90352f34e8c008cf3ad5d064d08fedd3de2e8df83eb4a1cf", size = 3277082, upload-time = "2025-12-09T22:11:06.167Z" },
{ url = "https://files.pythonhosted.org/packages/0e/50/80a8d080ac7d3d321e5e5d420c9a522b0aa770ec7013ea91f9a8b7d36e4a/sqlalchemy-2.0.45-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:672c45cae53ba88e0dad74b9027dddd09ef6f441e927786b05bec75d949fbb2e", size = 3293131, upload-time = "2025-12-09T22:13:52.626Z" },
{ url = "https://files.pythonhosted.org/packages/da/4c/13dab31266fc9904f7609a5dc308a2432a066141d65b857760c3bef97e69/sqlalchemy-2.0.45-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:470daea2c1ce73910f08caf10575676a37159a6d16c4da33d0033546bddebc9b", size = 3225389, upload-time = "2025-12-09T22:11:08.093Z" },
{ url = "https://files.pythonhosted.org/packages/74/04/891b5c2e9f83589de202e7abaf24cd4e4fa59e1837d64d528829ad6cc107/sqlalchemy-2.0.45-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9c6378449e0940476577047150fd09e242529b761dc887c9808a9a937fe990c8", size = 3266054, upload-time = "2025-12-09T22:13:54.262Z" },
{ url = "https://files.pythonhosted.org/packages/f1/24/fc59e7f71b0948cdd4cff7a286210e86b0443ef1d18a23b0d83b87e4b1f7/sqlalchemy-2.0.45-cp313-cp313-win32.whl", hash = "sha256:4b6bec67ca45bc166c8729910bd2a87f1c0407ee955df110d78948f5b5827e8a", size = 2110299, upload-time = "2025-12-09T21:39:33.486Z" },
{ url = "https://files.pythonhosted.org/packages/c0/c5/d17113020b2d43073412aeca09b60d2009442420372123b8d49cc253f8b8/sqlalchemy-2.0.45-cp313-cp313-win_amd64.whl", hash = "sha256:afbf47dc4de31fa38fd491f3705cac5307d21d4bb828a4f020ee59af412744ee", size = 2136264, upload-time = "2025-12-09T21:39:36.801Z" },
{ url = "https://files.pythonhosted.org/packages/3d/8d/bb40a5d10e7a5f2195f235c0b2f2c79b0bf6e8f00c0c223130a4fbd2db09/sqlalchemy-2.0.45-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:83d7009f40ce619d483d26ac1b757dfe3167b39921379a8bd1b596cf02dab4a6", size = 3521998, upload-time = "2025-12-09T22:13:28.622Z" },
{ url = "https://files.pythonhosted.org/packages/75/a5/346128b0464886f036c039ea287b7332a410aa2d3fb0bb5d404cb8861635/sqlalchemy-2.0.45-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d8a2ca754e5415cde2b656c27900b19d50ba076aa05ce66e2207623d3fe41f5a", size = 3473434, upload-time = "2025-12-09T22:13:30.188Z" },
{ url = "https://files.pythonhosted.org/packages/bf/e1/3ccb13c643399d22289c6a9786c1a91e3dcbb68bce4beb44926ac2c557bf/sqlalchemy-2.0.45-py3-none-any.whl", hash = "sha256:5225a288e4c8cc2308dbdd874edad6e7d0fd38eac1e9e5f23503425c8eee20d0", size = 1936672, upload-time = "2025-12-09T21:54:52.608Z" },
]
[[package]]
name = "sqlmodel"
version = "0.0.27"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pydantic" },
{ name = "sqlalchemy" },
]
sdist = { url = "https://files.pythonhosted.org/packages/90/5a/693d90866233e837d182da76082a6d4c2303f54d3aaaa5c78e1238c5d863/sqlmodel-0.0.27.tar.gz", hash = "sha256:ad1227f2014a03905aef32e21428640848ac09ff793047744a73dfdd077ff620", size = 118053, upload-time = "2025-10-08T16:39:11.938Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/8c/92/c35e036151fe53822893979f8a13e6f235ae8191f4164a79ae60a95d66aa/sqlmodel-0.0.27-py3-none-any.whl", hash = "sha256:667fe10aa8ff5438134668228dc7d7a08306f4c5c4c7e6ad3ad68defa0e7aa49", size = 29131, upload-time = "2025-10-08T16:39:10.917Z" },
]
[[package]]
name = "typing-extensions"
version = "4.15.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" },
]
[[package]]
name = "typing-inspection"
version = "0.4.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" },
]

View File

@@ -6,7 +6,11 @@ python-version = "3.13"
python-platform = "linux"
[project]
dependencies = ["sqlmodel==0.0.27"]
dependencies = [
"sqlmodel==0.0.27",
# TODO: remove this pin, once we have a lockfile
"sqlalchemy==2.0.44"
]
```
## Basic model

View File

@@ -1,90 +0,0 @@
version = 1
revision = 3
requires-python = "==3.13.*"
[[package]]
name = "graphql-core"
version = "3.2.7"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/ac/9b/037a640a2983b09aed4a823f9cf1729e6d780b0671f854efa4727a7affbe/graphql_core-3.2.7.tar.gz", hash = "sha256:27b6904bdd3b43f2a0556dad5d579bdfdeab1f38e8e8788e555bdcb586a6f62c", size = 513484, upload-time = "2025-11-01T22:30:40.436Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/0a/14/933037032608787fb92e365883ad6a741c235e0ff992865ec5d904a38f1e/graphql_core-3.2.7-py3-none-any.whl", hash = "sha256:17fc8f3ca4a42913d8e24d9ac9f08deddf0a0b2483076575757f6c412ead2ec0", size = 207262, upload-time = "2025-11-01T22:30:38.912Z" },
]
[[package]]
name = "lia-web"
version = "0.2.3"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/1e/4e/847404ca9d36e3f5468c9e460aed565a02cbca0fdf81247da9f87fabc1b8/lia_web-0.2.3.tar.gz", hash = "sha256:ccc9d24cdc200806ea96a20b22fb68f4759e6becdb901bd36024df7921e848d7", size = 156761, upload-time = "2025-08-11T10:23:21.003Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/00/f2/c68a97c727c795119f1056ad2b7e716c23f26f004292517c435accf90b5c/lia_web-0.2.3-py3-none-any.whl", hash = "sha256:237c779c943cd4341527fc0adfcc3d8068f992ee051f4ef059b8474ee087f641", size = 13965, upload-time = "2025-08-11T10:23:20.215Z" },
]
[[package]]
name = "mdtest-deps"
version = "0.1.0"
source = { virtual = "." }
dependencies = [
{ name = "strawberry-graphql" },
]
[package.metadata]
requires-dist = [{ name = "strawberry-graphql", specifier = "==0.283.3" }]
[[package]]
name = "packaging"
version = "25.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" },
]
[[package]]
name = "python-dateutil"
version = "2.9.0.post0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "six" },
]
sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" },
]
[[package]]
name = "six"
version = "1.17.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" },
]
[[package]]
name = "strawberry-graphql"
version = "0.283.3"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "graphql-core" },
{ name = "lia-web" },
{ name = "packaging" },
{ name = "python-dateutil" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/30/74/729c227b1e7fce28678290a5013ddceb543f350b6c14ae83400ab2c727d1/strawberry_graphql-0.283.3.tar.gz", hash = "sha256:375e545856b7587debd4e0f1e2a6fca19d09cc126238a07b9e5164e5eb09342a", size = 212141, upload-time = "2025-10-10T20:03:46.985Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/23/1b/aa358ef730d727d2e42810bf943542a8cc4c15aa2401f8629d356643a06f/strawberry_graphql-0.283.3-py3-none-any.whl", hash = "sha256:3751d86a219d81a16a13f335bb7d2fa3f57a85fab83d7d284b8ea88e2261d68b", size = 309885, upload-time = "2025-10-10T20:03:44.051Z" },
]
[[package]]
name = "typing-extensions"
version = "4.15.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" },
]

View File

@@ -118,13 +118,9 @@ class Child(Parent):
class OtherChild(Parent): ...
class Grandchild(OtherChild):
# TODO: The Liskov violation here maybe shouldn't be emitted? Whether called on the
# type or on an instance, it will behave the same from the caller's perspective. The only
# difference is whether the method body gets access to `self`, which is not a
# concern of Liskov.
@staticmethod
# TODO: we should emit a Liskov violation here too
# error: [override-of-final-method]
# error: [invalid-method-override]
def foo(): ...
@property
# TODO: we should emit a Liskov violation here too
@@ -271,7 +267,6 @@ class ChildOfGood(Good):
def f(self, x: str) -> str: ...
@overload
def f(self, x: int) -> int: ...
# error: [override-of-final-method]
def f(self, x: int | str) -> int | str:
return x
@@ -282,7 +277,6 @@ class Bad:
def f(self, x: str) -> str: ...
@overload
def f(self, x: int) -> int: ...
# error: [invalid-overload]
def f(self, x: int | str) -> int | str:
return x
@@ -292,7 +286,6 @@ class Bad:
def g(self, x: str) -> str: ...
@overload
def g(self, x: int) -> int: ...
# error: [invalid-overload]
def g(self, x: int | str) -> int | str:
return x
@@ -302,7 +295,6 @@ class Bad:
@overload
@final
def h(self, x: int) -> int: ...
# error: [invalid-overload]
def h(self, x: int | str) -> int | str:
return x
@@ -312,7 +304,6 @@ class Bad:
@final
@overload
def i(self, x: int) -> int: ...
# error: [invalid-overload]
def i(self, x: int | str) -> int | str:
return x
@@ -487,8 +478,7 @@ class B(A):
#
# TODO: we should emit a Liskov violation here too
# error: [override-of-final-method]
method4 = 42
unrelated = 56 # fmt: skip
method4 = 42; unrelated = 56 # fmt: skip
# Possible overrides of possibly `@final` methods...
class C(A):
@@ -552,7 +542,6 @@ class Child(Parent):
else:
# Fine because this doesn't override any reachable definitions
def foooo(self) -> None: ...
# There are `@final` definitions being overridden here,
# but the definitions that override them are unreachable
def spam(self) -> None: ...

View File

@@ -80,7 +80,6 @@ class CanIndex(Protocol[T]):
def __getitem__(self, index: int, /) -> T: ...
class ExplicitlyImplements(CanIndex[T]): ...
class SubProtocol(CanIndex[T], Protocol): ...
def takes_in_list(x: list[T]) -> list[T]:
return x
@@ -104,18 +103,6 @@ def deep_explicit(x: ExplicitlyImplements[str]) -> None:
def deeper_explicit(x: ExplicitlyImplements[set[str]]) -> None:
reveal_type(takes_in_protocol(x)) # revealed: set[str]
def deep_subprotocol(x: SubProtocol[str]) -> None:
reveal_type(takes_in_protocol(x)) # revealed: str
def deeper_subprotocol(x: SubProtocol[set[str]]) -> None:
reveal_type(takes_in_protocol(x)) # revealed: set[str]
def itself(x: CanIndex[str]) -> None:
reveal_type(takes_in_protocol(x)) # revealed: str
def deep_itself(x: CanIndex[set[str]]) -> None:
reveal_type(takes_in_protocol(x)) # revealed: set[str]
def takes_in_type(x: type[T]) -> type[T]:
return x

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