Compare commits
5 Commits
micha/try-
...
zb/cache-u
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6c2d87ed80 | ||
|
|
7d4831597b | ||
|
|
81f39680d2 | ||
|
|
3c2f534480 | ||
|
|
592a674447 |
2
.github/actionlint.yaml
vendored
2
.github/actionlint.yaml
vendored
@@ -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
|
||||
|
||||
122
.github/workflows/ci.yaml
vendored
122
.github/workflows/ci.yaml
vendored
@@ -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 }}"
|
||||
|
||||
18
.github/workflows/ty-ecosystem-report.yaml
vendored
18
.github/workflows/ty-ecosystem-report.yaml
vendored
@@ -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}
|
||||
|
||||
48
Cargo.lock
generated
48
Cargo.lock
generated
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 -----
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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
|
||||
})
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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>`.
|
||||
|
||||
@@ -52,7 +52,7 @@ impl AzureRenderer<'_> {
|
||||
f,
|
||||
"code={code};]{body}",
|
||||
code = diag.secondary_code_or_id(),
|
||||
body = diag.concise_message(),
|
||||
body = diag.body(),
|
||||
)?;
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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(())
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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>,
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -55,7 +55,7 @@ impl PylintRenderer<'_> {
|
||||
f,
|
||||
"{path}:{row}: [{code}] {body}",
|
||||
path = filename,
|
||||
body = diagnostic.concise_message()
|
||||
body = diagnostic.body()
|
||||
)?;
|
||||
}
|
||||
|
||||
|
||||
@@ -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>>,
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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"] }
|
||||
|
||||
377
crates/ty/docs/configuration.md
generated
377
crates/ty/docs/configuration.md
generated
@@ -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"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
|
||||
244
crates/ty/docs/rules.md
generated
244
crates/ty/docs/rules.md
generated
@@ -8,7 +8,7 @@
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.20">0.0.1-alpha.20</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20ambiguous-protocol-member" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L538" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L511" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -80,7 +80,7 @@ def test(): -> "int":
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20call-non-callable" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L137" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L135" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -98,44 +98,13 @@ Calling a non-callable object will raise a `TypeError` at runtime.
|
||||
4() # TypeError: 'int' object is not callable
|
||||
```
|
||||
|
||||
## `call-top-callable`
|
||||
|
||||
<small>
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.7">0.0.7</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20call-top-callable" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L155" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
**What it does**
|
||||
|
||||
Checks for calls to objects typed as `Top[Callable[..., T]]` (the infinite union of all
|
||||
callable types with return type `T`).
|
||||
|
||||
**Why is this bad?**
|
||||
|
||||
When an object is narrowed to `Top[Callable[..., object]]` (e.g., via `callable(x)` or
|
||||
`isinstance(x, Callable)`), we know the object is callable, but we don't know its
|
||||
precise signature. This type represents the set of all possible callable types
|
||||
(including, e.g., functions that take no arguments and functions that require arguments),
|
||||
so no specific set of arguments can be guaranteed to be valid.
|
||||
|
||||
**Examples**
|
||||
|
||||
```python
|
||||
def f(x: object):
|
||||
if callable(x):
|
||||
x() # error: We know `x` is callable, but not what arguments it accepts
|
||||
```
|
||||
|
||||
## `conflicting-argument-forms`
|
||||
|
||||
<small>
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-argument-forms" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L206" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L179" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -167,7 +136,7 @@ f(int) # error
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-declarations" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L232" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L205" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -198,7 +167,7 @@ a = 1
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-metaclass" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L257" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L230" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -230,7 +199,7 @@ class C(A, B): ...
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20cyclic-class-definition" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L283" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L256" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -262,7 +231,7 @@ class B(A): ...
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Preview (since <a href="https://github.com/astral-sh/ty/releases/tag/1.0.0">1.0.0</a>) ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20cyclic-type-alias-definition" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L309" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L282" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -290,7 +259,7 @@ type B = A
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.16">0.0.1-alpha.16</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20deprecated" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L353" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L326" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -317,7 +286,7 @@ old_func() # emits [deprecated] diagnostic
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'ignore'."><code>ignore</code></a> ·
|
||||
Preview (since <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a>) ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20division-by-zero" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L331" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L304" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -346,7 +315,7 @@ false positives it can produce.
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20duplicate-base" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L374" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L347" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -373,7 +342,7 @@ class B(A, A): ...
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.12">0.0.1-alpha.12</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20duplicate-kw-only" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L395" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L368" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -411,24 +380,11 @@ class A: # Crash at runtime
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20escape-character-in-forward-annotation" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fstring_annotation.rs#L154" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fstring_annotation.rs#L120" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
**What it does**
|
||||
|
||||
Checks for forward annotations that contain escape characters.
|
||||
|
||||
**Why is this bad?**
|
||||
|
||||
Static analysis tools like ty can't analyze type annotations that contain escape characters.
|
||||
|
||||
**Example**
|
||||
|
||||
|
||||
```python
|
||||
def foo() -> "intt\b": ...
|
||||
```
|
||||
TODO #14889
|
||||
|
||||
## `fstring-type-annotation`
|
||||
|
||||
@@ -467,7 +423,7 @@ def test(): -> "int":
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20ignore-comment-unknown-rule" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Fsuppression.rs#L50" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Fsuppression.rs#L47" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -529,7 +485,7 @@ def test(): -> "Literal[5]":
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20inconsistent-mro" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L621" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L594" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -559,7 +515,7 @@ class C(A, B): ...
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20index-out-of-bounds" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L645" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L618" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -585,7 +541,7 @@ t[3] # IndexError: tuple index out of range
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.12">0.0.1-alpha.12</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20instance-layout-conflict" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L427" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L400" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -674,7 +630,7 @@ an atypical memory layout.
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-argument-type" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L699" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L672" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -701,7 +657,7 @@ func("foo") # error: [invalid-argument-type]
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-assignment" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L739" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L712" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -729,7 +685,7 @@ a: int = ''
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-attribute-access" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2042" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2015" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -763,7 +719,7 @@ C.instance_var = 3 # error: Cannot assign to instance variable
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.19">0.0.1-alpha.19</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-await" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L761" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L734" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -799,7 +755,7 @@ asyncio.run(main())
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-base" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L791" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L764" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -823,7 +779,7 @@ class A(42): ... # error: [invalid-base]
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-context-manager" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L842" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L815" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -850,7 +806,7 @@ with 1:
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-declaration" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L863" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L836" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -879,7 +835,7 @@ a: str
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-exception-caught" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L886" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L859" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -923,7 +879,7 @@ except ZeroDivisionError:
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.28">0.0.1-alpha.28</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-explicit-override" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1712" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1685" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -965,7 +921,7 @@ class D(A):
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.35">0.0.1-alpha.35</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-frozen-dataclass-subclass" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2268" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2241" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1009,7 +965,7 @@ class NonFrozenChild(FrozenBase): # Error raised here
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-generic-class" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L922" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L895" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1047,7 +1003,7 @@ class D(Generic[U, T]): ...
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-ignore-comment" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Fsuppression.rs#L75" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Fsuppression.rs#L72" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1077,7 +1033,7 @@ a = 20 / 0 # type: ignore
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.17">0.0.1-alpha.17</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-key" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L666" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L639" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1116,7 +1072,7 @@ carol = Person(name="Carol", age=25) # typo!
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-legacy-type-variable" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L953" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L926" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1151,7 +1107,7 @@ def f(t: TypeVar("U")): ...
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-metaclass" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1050" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1023" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1185,7 +1141,7 @@ class B(metaclass=f): ...
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.20">0.0.1-alpha.20</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-method-override" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2170" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2143" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1292,7 +1248,7 @@ Correct use of `@override` is enforced by ty's `invalid-explicit-override` rule.
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.19">0.0.1-alpha.19</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-named-tuple" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L573" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L546" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1346,7 +1302,7 @@ AttributeError: Cannot overwrite NamedTuple attribute _asdict
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Preview (since <a href="https://github.com/astral-sh/ty/releases/tag/1.0.0">1.0.0</a>) ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-newtype" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1026" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L999" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1376,7 +1332,7 @@ Baz = NewType("Baz", int | str) # error: invalid base for `typing.NewType`
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-overload" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1077" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1050" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1426,7 +1382,7 @@ def foo(x: int) -> int: ...
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-parameter-default" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1176" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1149" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1452,7 +1408,7 @@ def f(a: int = ''): ...
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-paramspec" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L981" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L954" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1483,7 +1439,7 @@ P2 = ParamSpec("S2") # error: ParamSpec name must match the variable it's assig
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-protocol" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L509" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L482" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1517,7 +1473,7 @@ TypeError: Protocols can only inherit from other protocols, got <class 'int'>
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-raise" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1196" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1169" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1566,7 +1522,7 @@ def g():
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-return-type" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L720" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L693" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1591,7 +1547,7 @@ def func() -> int:
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-super-argument" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1239" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1212" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1641,45 +1597,7 @@ Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.
|
||||
</small>
|
||||
|
||||
|
||||
**What it does**
|
||||
|
||||
Checks for string-literal annotations where the string cannot be
|
||||
parsed as a Python expression.
|
||||
|
||||
**Why is this bad?**
|
||||
|
||||
Type annotations are expected to be Python expressions that
|
||||
describe the expected type of a variable, parameter, attribute or
|
||||
`return` statement.
|
||||
|
||||
Type annotations are permitted to be string-literal expressions, in
|
||||
order to enable forward references to names not yet defined.
|
||||
However, it must be possible to parse the contents of that string
|
||||
literal as a normal Python expression.
|
||||
|
||||
**Example**
|
||||
|
||||
|
||||
```python
|
||||
def foo() -> "intstance of C":
|
||||
return 42
|
||||
|
||||
class C: ...
|
||||
```
|
||||
|
||||
Use instead:
|
||||
|
||||
```python
|
||||
def foo() -> "C":
|
||||
return 42
|
||||
|
||||
class C: ...
|
||||
```
|
||||
|
||||
**References**
|
||||
|
||||
- [Typing spec: The meaning of annotations](https://typing.python.org/en/latest/spec/annotations.html#the-meaning-of-annotations)
|
||||
- [Typing spec: String annotations](https://typing.python.org/en/latest/spec/annotations.html#string-annotations)
|
||||
TODO #14889
|
||||
|
||||
## `invalid-type-alias-type`
|
||||
|
||||
@@ -1687,7 +1605,7 @@ class C: ...
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.6">0.0.1-alpha.6</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-alias-type" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1005" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L978" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1714,7 +1632,7 @@ NewAlias = TypeAliasType(get_name(), int) # error: TypeAliasType name mus
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.29">0.0.1-alpha.29</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-arguments" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1471" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1444" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1761,7 +1679,7 @@ Bar[int] # error: too few arguments
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-checking-constant" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1278" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1251" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1791,7 +1709,7 @@ TYPE_CHECKING = ''
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-form" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1302" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1275" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1821,7 +1739,7 @@ b: Annotated[int] # `Annotated` expects at least two arguments
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.11">0.0.1-alpha.11</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-guard-call" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1354" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1327" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1855,7 +1773,7 @@ f(10) # Error
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.11">0.0.1-alpha.11</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-guard-definition" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1326" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1299" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1889,7 +1807,7 @@ class C:
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-variable-constraints" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1382" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1355" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1924,7 +1842,7 @@ T = TypeVar('T', bound=str) # valid bound TypeVar
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20missing-argument" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1411" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1384" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1949,7 +1867,7 @@ func() # TypeError: func() missing 1 required positional argument: 'x'
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.20">0.0.1-alpha.20</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20missing-typed-dict-key" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2143" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2116" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1982,7 +1900,7 @@ alice["age"] # KeyError
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20no-matching-overload" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1430" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1403" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2011,7 +1929,7 @@ func("string") # error: [no-matching-overload]
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20non-subscriptable" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1453" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1426" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2035,7 +1953,7 @@ Subscripting an object that does not support it will raise a `TypeError` at runt
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20not-iterable" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1512" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1485" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2061,7 +1979,7 @@ for i in 34: # TypeError: 'int' object is not iterable
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.29">0.0.1-alpha.29</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20override-of-final-method" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1685" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1658" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2094,7 +2012,7 @@ class B(A):
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20parameter-already-assigned" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1563" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1536" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2121,7 +2039,7 @@ f(1, x=2) # Error raised here
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.22">0.0.1-alpha.22</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20positional-only-parameter-as-kwarg" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1896" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1869" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2148,7 +2066,7 @@ f(x=1) # Error raised here
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.22">0.0.1-alpha.22</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-missing-attribute" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1584" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1557" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2176,7 +2094,7 @@ A.c # AttributeError: type object 'A' has no attribute 'c'
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.22">0.0.1-alpha.22</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-missing-implicit-call" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L180" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L153" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2208,7 +2126,7 @@ A()[0] # TypeError: 'A' object is not subscriptable
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'ignore'."><code>ignore</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.22">0.0.1-alpha.22</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-missing-import" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1606" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1579" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2245,7 +2163,7 @@ from module import a # ImportError: cannot import name 'a' from 'module'
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'ignore'."><code>ignore</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unresolved-reference" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1636" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1609" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2309,7 +2227,7 @@ def test(): -> "int":
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20redundant-cast" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2070" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2043" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2336,7 +2254,7 @@ cast(int, f()) # Redundant
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20static-assert-error" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2018" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1991" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2366,7 +2284,7 @@ static_assert(int(2.0 * 3.0) == 6) # error: does not have a statically known tr
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20subclass-of-final-class" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1662" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1635" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2395,7 +2313,7 @@ class B(A): ... # Error raised here
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Preview (since <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.30">0.0.1-alpha.30</a>) ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20super-call-in-named-tuple-method" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1830" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1803" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2429,7 +2347,7 @@ class F(NamedTuple):
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20too-many-positional-arguments" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1770" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1743" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2456,7 +2374,7 @@ f("foo") # Error raised here
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20type-assertion-failure" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1748" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1721" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2484,7 +2402,7 @@ def _(x: int):
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unavailable-implicit-super-arguments" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1791" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1764" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2530,7 +2448,7 @@ class A:
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20undefined-reveal" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1857" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1830" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2554,7 +2472,7 @@ reveal_type(1) # NameError: name 'reveal_type' is not defined
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unknown-argument" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1875" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1848" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2581,7 +2499,7 @@ f(x=1, y=2) # Error raised here
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-attribute" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1917" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1890" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2609,7 +2527,7 @@ A().foo # AttributeError: 'A' object has no attribute 'foo'
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.15">0.0.1-alpha.15</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-global" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2091" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2064" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2667,7 +2585,7 @@ def g():
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-import" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1939" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1912" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2692,7 +2610,7 @@ import foo # ModuleNotFoundError: No module named 'foo'
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-reference" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1958" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1931" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2717,7 +2635,7 @@ print(x) # NameError: name 'x' is not defined
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.7">0.0.1-alpha.7</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-base" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L809" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L782" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2756,7 +2674,7 @@ class D(C): ... # error: [unsupported-base]
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-bool-conversion" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1532" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1505" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2793,7 +2711,7 @@ b1 < b2 < b1 # exception raised here
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-operator" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1977" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1950" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2821,7 +2739,7 @@ A() + A() # TypeError: unsupported operand type(s) for +: 'A' and 'A'
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'ignore'."><code>ignore</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unused-ignore-comment" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Fsuppression.rs#L25" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Fsuppression.rs#L22" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2852,7 +2770,7 @@ a = 20 / 2
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.22">0.0.1-alpha.22</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20useless-overload-body" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1120" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1093" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2915,7 +2833,7 @@ def foo(x: int | str) -> int | str:
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20zero-stepsize-in-slice" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1999" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1972" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
@@ -122,9 +121,7 @@ fn run_check(args: CheckCommand) -> anyhow::Result<ExitStatus> {
|
||||
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)?,
|
||||
};
|
||||
|
||||
@@ -228,11 +225,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 +234,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 +241,8 @@ impl MainLoop {
|
||||
watcher: None,
|
||||
project_options_overrides,
|
||||
printer,
|
||||
cancellation_token,
|
||||
},
|
||||
MainLoopCancellationToken {
|
||||
sender,
|
||||
source: cancellation_token_source,
|
||||
},
|
||||
MainLoopCancellationToken { sender },
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
@@ -514,12 +496,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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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 -----
|
||||
");
|
||||
|
||||
@@ -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"
|
||||
);
|
||||
|
||||
|
||||
@@ -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 }
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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::*;
|
||||
|
||||
@@ -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
|
||||
"#);
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,9 @@
|
||||
//! TODO: Need to properly handle Annotated expressions. All type arguments other
|
||||
//! than the first should be treated as value expressions, not as type expressions.
|
||||
//!
|
||||
//! TODO: An identifier that resolves to a parameter when used within a function
|
||||
//! should be classified as a parameter, selfParameter, or clsParameter token.
|
||||
//!
|
||||
//! TODO: Properties (or perhaps more generally, descriptor objects?) should be
|
||||
//! classified as property tokens rather than just variables.
|
||||
//!
|
||||
@@ -227,11 +230,6 @@ impl<'db> SemanticTokenVisitor<'db> {
|
||||
modifiers: SemanticTokenModifier,
|
||||
) {
|
||||
let range = ranged.range();
|
||||
|
||||
if range.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Only emit tokens that intersect with the range filter, if one is specified
|
||||
if let Some(range_filter) = self.range_filter {
|
||||
// Only include ranges that have a non-empty overlap. Adjacent ranges
|
||||
@@ -709,15 +707,15 @@ impl SourceOrderVisitor<'_> for SemanticTokenVisitor<'_> {
|
||||
}
|
||||
ast::Stmt::Import(import) => {
|
||||
for alias in &import.names {
|
||||
// Create separate tokens for each part of a dotted module name
|
||||
self.add_dotted_name_tokens(&alias.name, SemanticTokenType::Namespace);
|
||||
|
||||
if let Some(asname) = &alias.asname {
|
||||
self.add_token(
|
||||
asname.range(),
|
||||
SemanticTokenType::Namespace,
|
||||
SemanticTokenModifier::empty(),
|
||||
);
|
||||
} else {
|
||||
// Create separate tokens for each part of a dotted module name
|
||||
self.add_dotted_name_tokens(&alias.name, SemanticTokenType::Namespace);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1133,7 +1131,7 @@ mod tests {
|
||||
use ty_project::ProjectMetadata;
|
||||
|
||||
#[test]
|
||||
fn semantic_tokens_basic() {
|
||||
fn test_semantic_tokens_basic() {
|
||||
let test = SemanticTokenTest::new("def foo(): pass");
|
||||
|
||||
let tokens = test.highlight_file();
|
||||
@@ -1144,7 +1142,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn semantic_tokens_class() {
|
||||
fn test_semantic_tokens_class() {
|
||||
let test = SemanticTokenTest::new("class MyClass: pass");
|
||||
|
||||
let tokens = test.highlight_file();
|
||||
@@ -1155,7 +1153,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn semantic_tokens_class_args() {
|
||||
fn test_semantic_tokens_class_args() {
|
||||
// This used to cause a panic because of an incorrect
|
||||
// insertion-order when visiting arguments inside
|
||||
// class definitions.
|
||||
@@ -1171,7 +1169,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn semantic_tokens_variables() {
|
||||
fn test_semantic_tokens_variables() {
|
||||
let test = SemanticTokenTest::new(
|
||||
"
|
||||
x = 42
|
||||
@@ -1190,7 +1188,7 @@ y = 'hello'
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn semantic_tokens_walrus() {
|
||||
fn test_semantic_tokens_walrus() {
|
||||
let test = SemanticTokenTest::new(
|
||||
"
|
||||
if x := 42:
|
||||
@@ -1209,7 +1207,7 @@ if x := 42:
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn semantic_tokens_self_parameter() {
|
||||
fn test_semantic_tokens_self_parameter() {
|
||||
let test = SemanticTokenTest::new(
|
||||
"
|
||||
class MyClass:
|
||||
@@ -1240,7 +1238,7 @@ class MyClass:
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn semantic_tokens_cls_parameter() {
|
||||
fn test_semantic_tokens_cls_parameter() {
|
||||
let test = SemanticTokenTest::new(
|
||||
"
|
||||
class MyClass:
|
||||
@@ -1263,7 +1261,7 @@ class MyClass:
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn semantic_tokens_staticmethod_parameter() {
|
||||
fn test_semantic_tokens_staticmethod_parameter() {
|
||||
let test = SemanticTokenTest::new(
|
||||
"
|
||||
class MyClass:
|
||||
@@ -1284,7 +1282,7 @@ class MyClass:
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn semantic_tokens_custom_self_cls_names() {
|
||||
fn test_semantic_tokens_custom_self_cls_names() {
|
||||
let test = SemanticTokenTest::new(
|
||||
"
|
||||
class MyClass:
|
||||
@@ -1319,7 +1317,7 @@ class MyClass:
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn semantic_tokens_modifiers() {
|
||||
fn test_semantic_tokens_modifiers() {
|
||||
let test = SemanticTokenTest::new(
|
||||
"
|
||||
class MyClass:
|
||||
@@ -1340,7 +1338,7 @@ class MyClass:
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn semantic_classification_vs_heuristic() {
|
||||
fn test_semantic_classification_vs_heuristic() {
|
||||
let test = SemanticTokenTest::new(
|
||||
"
|
||||
import sys
|
||||
@@ -1374,7 +1372,7 @@ z = sys.version
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn builtin_constants() {
|
||||
fn test_builtin_constants() {
|
||||
let test = SemanticTokenTest::new(
|
||||
"
|
||||
x = True
|
||||
@@ -1396,7 +1394,7 @@ z = None
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn builtin_constants_in_expressions() {
|
||||
fn test_builtin_constants_in_expressions() {
|
||||
let test = SemanticTokenTest::new(
|
||||
"
|
||||
def check(value):
|
||||
@@ -1424,7 +1422,7 @@ result = check(None)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn builtin_types() {
|
||||
fn test_builtin_types() {
|
||||
let test = SemanticTokenTest::new(
|
||||
r#"
|
||||
type U = str | int
|
||||
@@ -1469,7 +1467,7 @@ result = check(None)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn semantic_tokens_range() {
|
||||
fn test_semantic_tokens_range() {
|
||||
let test = SemanticTokenTest::new(
|
||||
"
|
||||
def function1():
|
||||
@@ -1534,7 +1532,7 @@ def function2():
|
||||
/// When a token starts right at where the requested range ends,
|
||||
/// don't include it in the semantic tokens.
|
||||
#[test]
|
||||
fn semantic_tokens_range_excludes_boundary_tokens() {
|
||||
fn test_semantic_tokens_range_excludes_boundary_tokens() {
|
||||
let test = SemanticTokenTest::new(
|
||||
"
|
||||
x = 1
|
||||
@@ -1557,7 +1555,7 @@ z = 3
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dotted_module_names() {
|
||||
fn test_dotted_module_names() {
|
||||
let test = SemanticTokenTest::new(
|
||||
"
|
||||
import os.path
|
||||
@@ -1584,7 +1582,7 @@ from collections.abc import Mapping
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn module_type_classification() {
|
||||
fn test_module_type_classification() {
|
||||
let test = SemanticTokenTest::new(
|
||||
"
|
||||
import os
|
||||
@@ -1612,7 +1610,7 @@ y = sys
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn import_classification() {
|
||||
fn test_import_classification() {
|
||||
let test = SemanticTokenTest::new(
|
||||
"
|
||||
from os import path
|
||||
@@ -1643,7 +1641,7 @@ from mymodule import CONSTANT, my_function, MyClass
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn str_annotation() {
|
||||
fn test_str_annotation() {
|
||||
let test = SemanticTokenTest::new(
|
||||
r#"
|
||||
x: int = 1
|
||||
@@ -1687,7 +1685,7 @@ w5: "float
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn attribute_classification() {
|
||||
fn test_attribute_classification() {
|
||||
let test = SemanticTokenTest::new(
|
||||
"
|
||||
import os
|
||||
@@ -1761,7 +1759,7 @@ u = List.__name__ # __name__ should be variable
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn attribute_fallback_classification() {
|
||||
fn test_attribute_fallback_classification() {
|
||||
let test = SemanticTokenTest::new(
|
||||
"
|
||||
class MyClass:
|
||||
@@ -1792,7 +1790,7 @@ y = obj.unknown_attr # Should fall back to variable
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn constant_name_detection() {
|
||||
fn test_constant_name_detection() {
|
||||
let test = SemanticTokenTest::new(
|
||||
"
|
||||
class MyClass:
|
||||
@@ -1839,7 +1837,7 @@ w = obj.A # Should not have readonly modifier (length == 1)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn type_annotations() {
|
||||
fn test_type_annotations() {
|
||||
let test = SemanticTokenTest::new(
|
||||
r#"
|
||||
from typing import List, Optional
|
||||
@@ -2327,7 +2325,7 @@ class MyClass:
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn debug_int_classification() {
|
||||
fn test_debug_int_classification() {
|
||||
let test = SemanticTokenTest::new(
|
||||
"
|
||||
x: int = 42
|
||||
@@ -2344,7 +2342,7 @@ x: int = 42
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn debug_user_defined_type_classification() {
|
||||
fn test_debug_user_defined_type_classification() {
|
||||
let test = SemanticTokenTest::new(
|
||||
"
|
||||
class MyClass:
|
||||
@@ -2365,7 +2363,7 @@ x: MyClass = MyClass()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn type_annotation_vs_variable_classification() {
|
||||
fn test_type_annotation_vs_variable_classification() {
|
||||
let test = SemanticTokenTest::new(
|
||||
"
|
||||
from typing import List, Optional
|
||||
@@ -2415,7 +2413,7 @@ def test_function(param: int, other: MyClass) -> Optional[List[str]]:
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn protocol_types_in_annotations() {
|
||||
fn test_protocol_types_in_annotations() {
|
||||
let test = SemanticTokenTest::new(
|
||||
"
|
||||
from typing import Protocol
|
||||
@@ -2446,7 +2444,7 @@ def test_function(param: MyProtocol) -> None:
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn protocol_type_annotation_vs_value_context() {
|
||||
fn test_protocol_type_annotation_vs_value_context() {
|
||||
let test = SemanticTokenTest::new(
|
||||
"
|
||||
from typing import Protocol
|
||||
@@ -2532,7 +2530,7 @@ def test_function(param: my_type_alias): ...
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn type_parameters_pep695() {
|
||||
fn test_type_parameters_pep695() {
|
||||
let test = SemanticTokenTest::new(
|
||||
"
|
||||
# Test Python 3.12 PEP 695 type parameter syntax
|
||||
@@ -2656,7 +2654,7 @@ class BoundedContainer[T: int, U = str]:
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn type_parameters_usage_in_function_body() {
|
||||
fn test_type_parameters_usage_in_function_body() {
|
||||
let test = SemanticTokenTest::new(
|
||||
"
|
||||
def generic_function[T](value: T) -> T:
|
||||
@@ -2685,7 +2683,7 @@ def generic_function[T](value: T) -> T:
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decorator_classification() {
|
||||
fn test_decorator_classification() {
|
||||
let test = SemanticTokenTest::new(
|
||||
r#"
|
||||
@staticmethod
|
||||
@@ -2715,7 +2713,7 @@ class MyClass:
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn constant_variations() {
|
||||
fn test_constant_variations() {
|
||||
let test = SemanticTokenTest::new(
|
||||
r#"
|
||||
A = 1
|
||||
@@ -2758,7 +2756,7 @@ A_1 = 1
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn implicitly_concatenated_strings() {
|
||||
fn test_implicitly_concatenated_strings() {
|
||||
let test = SemanticTokenTest::new(
|
||||
r#"x = "hello" "world"
|
||||
y = ("multi"
|
||||
@@ -2785,7 +2783,7 @@ z = 'single' "mixed" 'quotes'"#,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bytes_literals() {
|
||||
fn test_bytes_literals() {
|
||||
let test = SemanticTokenTest::new(
|
||||
r#"x = b"hello" b"world"
|
||||
y = (b"multi"
|
||||
@@ -2812,7 +2810,7 @@ z = b'single' b"mixed" b'quotes'"#,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mixed_string_and_bytes_literals() {
|
||||
fn test_mixed_string_and_bytes_literals() {
|
||||
let test = SemanticTokenTest::new(
|
||||
r#"# Test mixed string and bytes literals
|
||||
string_concat = "hello" "world"
|
||||
@@ -2848,7 +2846,7 @@ regular_bytes = b"just bytes""#,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fstring_with_mixed_literals() {
|
||||
fn test_fstring_with_mixed_literals() {
|
||||
let test = SemanticTokenTest::new(
|
||||
r#"
|
||||
# Test f-strings with various literal types
|
||||
@@ -2900,7 +2898,7 @@ complex_fstring = f"User: {name.upper()}, Count: {len(data)}, Hex: {value:x}"
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nonlocal_and_global_statements() {
|
||||
fn test_nonlocal_and_global_statements() {
|
||||
let test = SemanticTokenTest::new(
|
||||
r#"
|
||||
x = "global_value"
|
||||
@@ -2962,7 +2960,7 @@ def outer():
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nonlocal_global_edge_cases() {
|
||||
fn test_nonlocal_global_edge_cases() {
|
||||
let test = SemanticTokenTest::new(
|
||||
r#"
|
||||
# Single variable statements
|
||||
@@ -3002,7 +3000,7 @@ def test():
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pattern_matching() {
|
||||
fn test_pattern_matching() {
|
||||
let test = SemanticTokenTest::new(
|
||||
r#"
|
||||
def process_data(data):
|
||||
@@ -3058,7 +3056,7 @@ def process_data(data):
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exception_handlers() {
|
||||
fn test_exception_handlers() {
|
||||
let test = SemanticTokenTest::new(
|
||||
r#"
|
||||
try:
|
||||
@@ -3097,7 +3095,7 @@ finally:
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn self_attribute_expression() {
|
||||
fn test_self_attribute_expression() {
|
||||
let test = SemanticTokenTest::new(
|
||||
r#"
|
||||
from typing import Self
|
||||
@@ -3137,7 +3135,7 @@ class C:
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn augmented_assignment() {
|
||||
fn test_augmented_assignment() {
|
||||
let test = SemanticTokenTest::new(
|
||||
r#"
|
||||
x = 0
|
||||
@@ -3156,7 +3154,7 @@ x += 1
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn type_alias() {
|
||||
fn test_type_alias() {
|
||||
let test = SemanticTokenTest::new("type MyList[T] = list[T]");
|
||||
|
||||
let tokens = test.highlight_file();
|
||||
@@ -3170,7 +3168,7 @@ x += 1
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn for_stmt() {
|
||||
fn test_for_stmt() {
|
||||
let test = SemanticTokenTest::new(
|
||||
r#"
|
||||
for item in []:
|
||||
@@ -3192,7 +3190,7 @@ else:
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn with_stmt() {
|
||||
fn test_with_stmt() {
|
||||
let test = SemanticTokenTest::new(
|
||||
r#"
|
||||
with open("file.txt") as f:
|
||||
@@ -3212,7 +3210,7 @@ with open("file.txt") as f:
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn comprehensions() {
|
||||
fn test_comprehensions() {
|
||||
let test = SemanticTokenTest::new(
|
||||
r#"
|
||||
list_comp = [x for x in range(10) if x % 2 == 0]
|
||||
@@ -3257,7 +3255,7 @@ generator = (x for x in range(10))
|
||||
|
||||
/// Regression test for <https://github.com/astral-sh/ty/issues/1406>
|
||||
#[test]
|
||||
fn invalid_kwargs() {
|
||||
fn test_invalid_kwargs() {
|
||||
let test = SemanticTokenTest::new(
|
||||
r#"
|
||||
def foo(self, **key, value=10):
|
||||
@@ -3276,24 +3274,6 @@ def foo(self, **key, value=10):
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn import_as() {
|
||||
let test = SemanticTokenTest::new(
|
||||
r#"
|
||||
import pathlib as path
|
||||
from pathlib import Path
|
||||
"#,
|
||||
);
|
||||
|
||||
let tokens = test.highlight_file();
|
||||
assert_snapshot!(test.to_snapshot(&tokens), @r#"
|
||||
"pathlib" @ 8..15: Namespace
|
||||
"path" @ 19..23: Namespace
|
||||
"pathlib" @ 29..36: Namespace
|
||||
"Path" @ 44..48: Class
|
||||
"#);
|
||||
}
|
||||
|
||||
pub(super) struct SemanticTokenTest {
|
||||
pub(super) db: ty_project::TestDb,
|
||||
file: 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;
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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 {}
|
||||
}
|
||||
@@ -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),
|
||||
}
|
||||
@@ -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 }
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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" }
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
```
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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`
|
||||
|
||||
@@ -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)
|
||||
```
|
||||
|
||||
@@ -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"]
|
||||
```
|
||||
|
||||
@@ -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: ...
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -144,7 +144,7 @@ class Dict[T](TypedDict):
|
||||
|
||||
type DictInt = Dict[int]
|
||||
|
||||
# error: [non-subscriptable] "Cannot subscript non-generic type alias: `Dict[int]` is already specialized"
|
||||
# error: [non-subscriptable] "Cannot subscript non-generic type alias: `Dict` is already specialized"
|
||||
def _(x: DictInt[int]):
|
||||
reveal_type(x) # revealed: Unknown
|
||||
|
||||
|
||||
@@ -75,7 +75,6 @@ class CanIndex(Protocol[S]):
|
||||
def __getitem__(self, index: int, /) -> S: ...
|
||||
|
||||
class ExplicitlyImplements[T](CanIndex[T]): ...
|
||||
class SubProtocol[T](CanIndex[T], Protocol): ...
|
||||
|
||||
def takes_in_list[T](x: list[T]) -> list[T]:
|
||||
return x
|
||||
@@ -99,18 +98,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[T](x: type[T]) -> type[T]:
|
||||
return x
|
||||
|
||||
|
||||
@@ -1179,34 +1179,6 @@ def _(
|
||||
reveal_type(subclass_of_p) # revealed: type[P]
|
||||
```
|
||||
|
||||
Using `type[]` with a union type alias distributes the `type[]` over the union elements:
|
||||
|
||||
```py
|
||||
from typing import Union
|
||||
|
||||
class C: ...
|
||||
class D: ...
|
||||
|
||||
UnionAlias1 = C | D
|
||||
UnionAlias2 = Union[C, D]
|
||||
|
||||
SubclassOfUnionAlias1 = type[UnionAlias1]
|
||||
SubclassOfUnionAlias2 = type[UnionAlias2]
|
||||
|
||||
reveal_type(SubclassOfUnionAlias1) # revealed: <special-form 'type[C | D]'>
|
||||
reveal_type(SubclassOfUnionAlias2) # revealed: <special-form 'type[C | D]'>
|
||||
|
||||
def _(
|
||||
subclass_of_union_alias1: SubclassOfUnionAlias1,
|
||||
subclass_of_union_alias2: SubclassOfUnionAlias2,
|
||||
):
|
||||
reveal_type(subclass_of_union_alias1) # revealed: type[C] | type[D]
|
||||
reveal_type(subclass_of_union_alias1()) # revealed: C | D
|
||||
|
||||
reveal_type(subclass_of_union_alias2) # revealed: type[C] | type[D]
|
||||
reveal_type(subclass_of_union_alias2()) # revealed: C | D
|
||||
```
|
||||
|
||||
Invalid uses result in diagnostics:
|
||||
|
||||
```py
|
||||
|
||||
@@ -76,63 +76,3 @@ def reveal_type(obj, /): ...
|
||||
```py
|
||||
reveal_type(foo) # revealed: Unknown
|
||||
```
|
||||
|
||||
## Builtins imported from custom project-level stubs
|
||||
|
||||
The project can add or replace builtins with the `__builtins__.pyi` stub. They will take precedence
|
||||
over the typeshed ones.
|
||||
|
||||
```py
|
||||
reveal_type(foo) # revealed: int
|
||||
reveal_type(bar) # revealed: str
|
||||
reveal_type(quux(1)) # revealed: int
|
||||
b = baz # error: [unresolved-reference]
|
||||
|
||||
reveal_type(ord(100)) # revealed: bool
|
||||
a = ord("a") # error: [invalid-argument-type]
|
||||
|
||||
bar = int(123)
|
||||
reveal_type(bar) # revealed: int
|
||||
```
|
||||
|
||||
`__builtins__.pyi`:
|
||||
|
||||
```pyi
|
||||
foo: int = ...
|
||||
bar: str = ...
|
||||
|
||||
def quux(value: int) -> int: ...
|
||||
|
||||
unused: str = ...
|
||||
|
||||
def ord(x: int) -> bool: ...
|
||||
```
|
||||
|
||||
Builtins stubs are searched relative to the project root, not the file using them.
|
||||
|
||||
`under/some/folder.py`:
|
||||
|
||||
```py
|
||||
reveal_type(foo) # revealed: int
|
||||
reveal_type(bar) # revealed: str
|
||||
```
|
||||
|
||||
## Assigning custom builtins
|
||||
|
||||
```py
|
||||
import builtins
|
||||
|
||||
builtins.foo = 123
|
||||
builtins.bar = 456 # error: [unresolved-attribute]
|
||||
builtins.baz = 789 # error: [invalid-assignment]
|
||||
builtins.chr = lambda x: str(x) # error: [invalid-assignment]
|
||||
builtins.chr = 10
|
||||
```
|
||||
|
||||
`__builtins__.pyi`:
|
||||
|
||||
```pyi
|
||||
foo: int
|
||||
baz: str
|
||||
chr: int
|
||||
```
|
||||
|
||||
@@ -24,16 +24,6 @@ string = "hello"
|
||||
reveal_type(f"{string!r}") # revealed: str
|
||||
```
|
||||
|
||||
## Debug Specifier
|
||||
|
||||
The `=` specifier causes the expression text and value to be included in the output:
|
||||
|
||||
```py
|
||||
# f"{1=}" evaluates to "1=1", but we fall back to `str` for now
|
||||
reveal_type(f"{1=}") # revealed: str
|
||||
reveal_type(f"value: {42=}") # revealed: str
|
||||
```
|
||||
|
||||
## Format Specifiers
|
||||
|
||||
```py
|
||||
|
||||
@@ -266,10 +266,10 @@ class Person(NamedTuple):
|
||||
age: int | None = None
|
||||
|
||||
reveal_type(Person._field_defaults) # revealed: dict[str, Any]
|
||||
reveal_type(Person._fields) # revealed: tuple[Literal["name"], Literal["age"]]
|
||||
reveal_type(Person._fields) # revealed: tuple[str, ...]
|
||||
reveal_type(Person._make) # revealed: bound method <class 'Person'>._make(iterable: Iterable[Any]) -> Person
|
||||
reveal_type(Person._asdict) # revealed: def _asdict(self) -> dict[str, Any]
|
||||
reveal_type(Person._replace) # revealed: (self: Self, *, name: str = ..., age: int | None = ...) -> Self
|
||||
reveal_type(Person._replace) # revealed: def _replace(self, **kwargs: Any) -> Self@_replace
|
||||
|
||||
reveal_type(Person._make(("Alice", 42))) # revealed: Person
|
||||
|
||||
@@ -277,10 +277,6 @@ person = Person("Alice", 42)
|
||||
|
||||
reveal_type(person._asdict()) # revealed: dict[str, Any]
|
||||
reveal_type(person._replace(name="Bob")) # revealed: Person
|
||||
|
||||
# Invalid keyword arguments are detected:
|
||||
# error: [unknown-argument] "Argument `invalid` does not match any known parameter"
|
||||
person._replace(invalid=42)
|
||||
```
|
||||
|
||||
When accessing them on child classes of generic `NamedTuple`s, the return type is specialized
|
||||
@@ -347,7 +343,7 @@ satisfy:
|
||||
def expects_named_tuple(x: typing.NamedTuple):
|
||||
reveal_type(x) # revealed: tuple[object, ...] & NamedTupleLike
|
||||
reveal_type(x._make) # revealed: bound method type[NamedTupleLike]._make(iterable: Iterable[Any]) -> NamedTupleLike
|
||||
reveal_type(x._replace) # revealed: bound method NamedTupleLike._replace(...) -> NamedTupleLike
|
||||
reveal_type(x._replace) # revealed: bound method NamedTupleLike._replace(**kwargs) -> NamedTupleLike
|
||||
# revealed: Overload[(value: tuple[object, ...], /) -> tuple[object, ...], (value: tuple[_T@__add__, ...], /) -> tuple[object, ...]]
|
||||
reveal_type(x.__add__)
|
||||
reveal_type(x.__iter__) # revealed: bound method tuple[object, ...].__iter__() -> Iterator[object]
|
||||
@@ -359,9 +355,8 @@ def _(y: type[typing.NamedTuple]):
|
||||
def _(z: typing.NamedTuple[int]): ...
|
||||
```
|
||||
|
||||
NamedTuples are assignable to `NamedTupleLike`. The `NamedTupleLike._replace` method is typed with
|
||||
`(*args, **kwargs)`, which type checkers treat as equivalent to `...` (per the typing spec), making
|
||||
all NamedTuple implementations automatically compatible:
|
||||
Any instance of a `NamedTuple` class can therefore be passed for a function parameter that is
|
||||
annotated with `NamedTuple`:
|
||||
|
||||
```py
|
||||
from typing import NamedTuple, Protocol, Iterable, Any
|
||||
@@ -373,15 +368,12 @@ class Point(NamedTuple):
|
||||
|
||||
reveal_type(Point._make) # revealed: bound method <class 'Point'>._make(iterable: Iterable[Any]) -> Point
|
||||
reveal_type(Point._asdict) # revealed: def _asdict(self) -> dict[str, Any]
|
||||
reveal_type(Point._replace) # revealed: (self: Self, *, x: int = ..., y: int = ...) -> Self
|
||||
reveal_type(Point._replace) # revealed: def _replace(self, **kwargs: Any) -> Self@_replace
|
||||
|
||||
# Point is assignable to NamedTuple.
|
||||
static_assert(is_assignable_to(Point, NamedTuple))
|
||||
|
||||
# NamedTuple instances can be passed to functions expecting NamedTupleLike.
|
||||
expects_named_tuple(Point(x=42, y=56))
|
||||
expects_named_tuple(Point(x=42, y=56)) # fine
|
||||
|
||||
# But plain tuples are not NamedTupleLike (they don't have _make, _asdict, _replace, etc.).
|
||||
# error: [invalid-argument-type] "Argument to function `expects_named_tuple` is incorrect: Expected `tuple[object, ...] & NamedTupleLike`, found `tuple[Literal[1], Literal[2]]`"
|
||||
expects_named_tuple((1, 2))
|
||||
```
|
||||
|
||||
@@ -1,95 +0,0 @@
|
||||
# Narrowing for `callable()`
|
||||
|
||||
## Basic narrowing
|
||||
|
||||
The `callable()` builtin returns `TypeIs[Callable[..., object]]`, which narrows the type to the
|
||||
intersection with `Top[Callable[..., object]]`. The `Top[...]` wrapper indicates this is a fully
|
||||
static type representing the top materialization of a gradual callable.
|
||||
|
||||
Since all callable types are subtypes of `Top[Callable[..., object]]`, intersections with `Top[...]`
|
||||
simplify to just the original callable type.
|
||||
|
||||
```py
|
||||
from typing import Any, Callable
|
||||
|
||||
def f(x: Callable[..., Any] | None):
|
||||
if callable(x):
|
||||
# The intersection simplifies because `(...) -> Any` is a subtype of
|
||||
# `Top[(...) -> object]` - all callables are subtypes of the top materialization.
|
||||
reveal_type(x) # revealed: (...) -> Any
|
||||
else:
|
||||
# Since `(...) -> Any` is a subtype of `Top[(...) -> object]`, the intersection
|
||||
# with the negation is empty (Never), leaving just None.
|
||||
reveal_type(x) # revealed: None
|
||||
```
|
||||
|
||||
## Narrowing with other callable types
|
||||
|
||||
```py
|
||||
from typing import Any, Callable
|
||||
|
||||
def g(x: Callable[[int], str] | None):
|
||||
if callable(x):
|
||||
# All callables are subtypes of `Top[(...) -> object]`, so the intersection simplifies.
|
||||
reveal_type(x) # revealed: (int, /) -> str
|
||||
else:
|
||||
reveal_type(x) # revealed: None
|
||||
|
||||
def h(x: Callable[..., int] | None):
|
||||
if callable(x):
|
||||
reveal_type(x) # revealed: (...) -> int
|
||||
else:
|
||||
reveal_type(x) # revealed: None
|
||||
```
|
||||
|
||||
## Narrowing from object
|
||||
|
||||
```py
|
||||
from typing import Callable
|
||||
|
||||
def f(x: object):
|
||||
if callable(x):
|
||||
reveal_type(x) # revealed: Top[(...) -> object]
|
||||
else:
|
||||
reveal_type(x) # revealed: ~Top[(...) -> object]
|
||||
```
|
||||
|
||||
## Calling narrowed callables
|
||||
|
||||
The narrowed type `Top[Callable[..., object]]` represents the set of all possible callable types
|
||||
(including, e.g., functions that take no arguments and functions that require arguments). While such
|
||||
objects *are* callable (they pass `callable()`), no specific set of arguments can be guaranteed to
|
||||
be valid.
|
||||
|
||||
```py
|
||||
import typing as t
|
||||
|
||||
def call_with_args(y: object, a: int, b: str) -> object:
|
||||
if isinstance(y, t.Callable):
|
||||
# error: [call-top-callable]
|
||||
return y(a, b)
|
||||
return None
|
||||
```
|
||||
|
||||
## Assignability of narrowed callables
|
||||
|
||||
A narrowed callable `Top[Callable[..., object]]` should be assignable to `Callable[..., Any]`. This
|
||||
is important for decorators and other patterns where we need to pass the narrowed callable to
|
||||
functions expecting gradual callables.
|
||||
|
||||
```py
|
||||
from typing import Any, Callable, TypeVar
|
||||
from ty_extensions import static_assert, Top, is_assignable_to
|
||||
|
||||
static_assert(is_assignable_to(Top[Callable[..., bool]], Callable[..., int]))
|
||||
|
||||
F = TypeVar("F", bound=Callable[..., Any])
|
||||
|
||||
def wrap(f: F) -> F:
|
||||
return f
|
||||
|
||||
def f(x: object):
|
||||
if callable(x):
|
||||
# x has type `Top[(...) -> object]`, which should be assignable to `Callable[..., Any]`
|
||||
wrap(x)
|
||||
```
|
||||
@@ -197,86 +197,15 @@ def _(t1: tuple[int | None, int | None], t2: tuple[int, int] | tuple[None, None]
|
||||
|
||||
n = 0
|
||||
if t1[n] is not None:
|
||||
# Narrowing the individual element type with a non-literal subscript is not supported
|
||||
# Non-literal subscript narrowing are currently not supported, as well as mypy, pyright
|
||||
reveal_type(t1[0]) # revealed: int | None
|
||||
reveal_type(t1[n]) # revealed: int | None
|
||||
reveal_type(t1[1]) # revealed: int | None
|
||||
|
||||
# However, we can still discriminate between tuples in a union using a variable index:
|
||||
if t2[n] is not None:
|
||||
reveal_type(t2) # revealed: tuple[int, int]
|
||||
|
||||
if t2[0] is not None:
|
||||
reveal_type(t2) # revealed: tuple[int, int]
|
||||
reveal_type(t2[0]) # revealed: int
|
||||
reveal_type(t2[1]) # revealed: int
|
||||
else:
|
||||
reveal_type(t2) # revealed: tuple[None, None]
|
||||
reveal_type(t2[0]) # revealed: None
|
||||
reveal_type(t2[1]) # revealed: None
|
||||
|
||||
if t2[0] is None:
|
||||
reveal_type(t2) # revealed: tuple[None, None]
|
||||
else:
|
||||
reveal_type(t2) # revealed: tuple[int, int]
|
||||
|
||||
def _(t3: tuple[int, str] | tuple[None, None] | tuple[bool, bytes]):
|
||||
# Narrow to tuples where first element is not None
|
||||
if t3[0] is not None:
|
||||
reveal_type(t3) # revealed: tuple[int, str] | tuple[bool, bytes]
|
||||
|
||||
# Narrow to tuples where first element is None
|
||||
if t3[0] is None:
|
||||
reveal_type(t3) # revealed: tuple[None, None]
|
||||
|
||||
def _(t4: tuple[bool, int] | tuple[bool, str]):
|
||||
# Both tuples have bool at index 0, which is not disjoint from True,
|
||||
# so neither gets filtered out when checking `is True`
|
||||
if t4[0] is True:
|
||||
reveal_type(t4) # revealed: tuple[bool, int] | tuple[bool, str]
|
||||
|
||||
def _(t5: tuple[int, None] | tuple[None, int]):
|
||||
# Narrow on second element (index 1)
|
||||
if t5[1] is not None:
|
||||
reveal_type(t5) # revealed: tuple[None, int]
|
||||
else:
|
||||
reveal_type(t5) # revealed: tuple[int, None]
|
||||
|
||||
# Negative index
|
||||
if t5[-1] is None:
|
||||
reveal_type(t5) # revealed: tuple[int, None]
|
||||
|
||||
def _(t6: tuple[int, ...] | tuple[None, None]):
|
||||
# Variadic tuple at index 0 has element type `int` (not a union),
|
||||
# so `tuple[None, None]` gets filtered out
|
||||
if t6[0] is not None:
|
||||
reveal_type(t6) # revealed: tuple[int, ...]
|
||||
|
||||
def _(t6b: tuple[int, ...] | tuple[None, ...]):
|
||||
# Both variadic: `int` is disjoint from None, `None` is not disjoint from None
|
||||
if t6b[0] is not None:
|
||||
reveal_type(t6b) # revealed: tuple[int, ...]
|
||||
else:
|
||||
reveal_type(t6b) # revealed: tuple[None, ...]
|
||||
|
||||
def _(t7: tuple[int, int] | tuple[None, None]):
|
||||
# Index out of range for both tuples - no narrowing, but errors are emitted
|
||||
# error: [index-out-of-bounds] "Index 5 is out of bounds for tuple `tuple[int, int]` with length 2"
|
||||
# error: [index-out-of-bounds] "Index 5 is out of bounds for tuple `tuple[None, None]` with length 2"
|
||||
if t7[5] is not None:
|
||||
reveal_type(t7) # revealed: tuple[int, int] | tuple[None, None]
|
||||
|
||||
def _(t8: tuple[int, int, int] | tuple[None, None]):
|
||||
# Index in range for first tuple but out of range for second
|
||||
# error: [index-out-of-bounds] "Index 2 is out of bounds for tuple `tuple[None, None]` with length 2"
|
||||
if t8[2] is not None:
|
||||
reveal_type(t8) # revealed: tuple[int, int, int] | tuple[None, None]
|
||||
|
||||
def _(t9: tuple[int | None, str] | tuple[str, int]):
|
||||
# When the element type is a union (like `int | None`), we can't filter
|
||||
# out the tuple.
|
||||
if t9[0] is not None:
|
||||
reveal_type(t9) # revealed: tuple[int | None, str] | tuple[str, int]
|
||||
# TODO: should be int
|
||||
reveal_type(t2[1]) # revealed: int | None
|
||||
```
|
||||
|
||||
### String subscript
|
||||
|
||||
@@ -238,20 +238,3 @@ def _(s: LiteralString | None, t: LiteralString | Any):
|
||||
# TODO could be `Literal["foo"] | Any`
|
||||
reveal_type(t) # revealed: LiteralString | Any
|
||||
```
|
||||
|
||||
## Narrowing with tuple types
|
||||
|
||||
We assume that tuple subclasses don't override `tuple.__eq__`, which only returns True for other
|
||||
tuples. So they are excluded from the narrowed type when comparing to non-tuple values.
|
||||
|
||||
```py
|
||||
from typing import Literal
|
||||
|
||||
def _(x: Literal["a", "b"] | tuple[int, int]):
|
||||
if x == "a":
|
||||
# tuple type is excluded because it's disjoint from the string literal
|
||||
reveal_type(x) # revealed: Literal["a"]
|
||||
else:
|
||||
# tuple type remains in the else branch
|
||||
reveal_type(x) # revealed: Literal["b"] | tuple[int, int]
|
||||
```
|
||||
|
||||
@@ -191,20 +191,3 @@ def test(x: Status | int):
|
||||
else:
|
||||
reveal_type(x) # revealed: Literal[Status.REJECTED] | int
|
||||
```
|
||||
|
||||
## Union with tuple and `Literal`
|
||||
|
||||
We assume that tuple subclasses don't override `tuple.__eq__`, which only returns True for other
|
||||
tuples. So they are excluded from the narrowed type when disjoint from the RHS values.
|
||||
|
||||
```py
|
||||
from typing import Literal
|
||||
|
||||
def test(x: Literal["none", "auto", "required"] | tuple[list[str], Literal["auto", "required"]]):
|
||||
if x in ("auto", "required"):
|
||||
# tuple type is excluded because it's disjoint from the string literals
|
||||
reveal_type(x) # revealed: Literal["auto", "required"]
|
||||
else:
|
||||
# tuple type remains in the else branch
|
||||
reveal_type(x) # revealed: Literal["none"] | tuple[list[str], Literal["auto", "required"]]
|
||||
```
|
||||
|
||||
@@ -213,7 +213,8 @@ def f(x: dict[str, int] | list[str], y: object):
|
||||
reveal_type(x) # revealed: list[str]
|
||||
|
||||
if isinstance(y, t.Callable):
|
||||
reveal_type(y) # revealed: Top[(...) -> object]
|
||||
# TODO: a better top-materialization for `Callable`s (https://github.com/astral-sh/ty/issues/1426)
|
||||
reveal_type(y) # revealed: () -> object
|
||||
```
|
||||
|
||||
## Class types
|
||||
|
||||
@@ -183,12 +183,9 @@ class LiskovViolatingButNotOverrideViolating(Parent):
|
||||
@override
|
||||
def my_property1(self) -> int: ...
|
||||
|
||||
# TODO: This maybe shouldn't be a Liskov violation? 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 `cls`, which is not a concern of Liskov.
|
||||
@staticmethod
|
||||
@override
|
||||
def class_method1() -> int: ... # error: [invalid-method-override]
|
||||
def class_method1() -> int: ...
|
||||
|
||||
@classmethod
|
||||
@override
|
||||
@@ -431,7 +428,6 @@ class Spam:
|
||||
def baz(self, x: str) -> str: ...
|
||||
@overload
|
||||
def baz(self, x: int) -> int: ...
|
||||
|
||||
# error: [invalid-overload] "`@override` decorator should be applied only to the overload implementation"
|
||||
# error: [invalid-explicit-override]
|
||||
def baz(self, x: str | int) -> str | int:
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
---
|
||||
source: crates/ty_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: attributes.md - Attributes - Diagnostic for function attribute accessed on `Callable` type
|
||||
mdtest path: crates/ty_python_semantic/resources/mdtest/attributes.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | from typing import Callable
|
||||
2 |
|
||||
3 | def f(x: Callable):
|
||||
4 | x.__name__ # error: [unresolved-attribute]
|
||||
5 | x.__annotate__ # error: [unresolved-attribute]
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[unresolved-attribute]: Object of type `(...) -> Unknown` has no attribute `__name__`
|
||||
--> src/mdtest_snippet.py:4:5
|
||||
|
|
||||
3 | def f(x: Callable):
|
||||
4 | x.__name__ # error: [unresolved-attribute]
|
||||
| ^^^^^^^^^^
|
||||
5 | x.__annotate__ # error: [unresolved-attribute]
|
||||
|
|
||||
help: Function objects have a `__name__` attribute, but not all callable objects are functions
|
||||
info: rule `unresolved-attribute` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[unresolved-attribute]: Object of type `(...) -> Unknown` has no attribute `__annotate__`
|
||||
--> src/mdtest_snippet.py:5:5
|
||||
|
|
||||
3 | def f(x: Callable):
|
||||
4 | x.__name__ # error: [unresolved-attribute]
|
||||
5 | x.__annotate__ # error: [unresolved-attribute]
|
||||
| ^^^^^^^^^^^^^^
|
||||
|
|
||||
help: Function objects have an `__annotate__` attribute, but not all callable objects are functions
|
||||
info: rule `unresolved-attribute` is enabled by default
|
||||
|
||||
```
|
||||
@@ -1,113 +0,0 @@
|
||||
---
|
||||
source: crates/ty_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: del.md - `del` statement - Delete items - TypedDict deletion
|
||||
mdtest path: crates/ty_python_semantic/resources/mdtest/del.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | from typing_extensions import TypedDict, NotRequired
|
||||
2 |
|
||||
3 | class Movie(TypedDict):
|
||||
4 | name: str
|
||||
5 | year: int
|
||||
6 |
|
||||
7 | class PartialMovie(TypedDict, total=False):
|
||||
8 | name: str
|
||||
9 | year: int
|
||||
10 |
|
||||
11 | class MixedMovie(TypedDict):
|
||||
12 | name: str
|
||||
13 | year: NotRequired[int]
|
||||
14 |
|
||||
15 | m: Movie = {"name": "Blade Runner", "year": 1982}
|
||||
16 | p: PartialMovie = {"name": "Test"}
|
||||
17 | mixed: MixedMovie = {"name": "Test"}
|
||||
18 |
|
||||
19 | # Required keys cannot be deleted.
|
||||
20 | # error: [invalid-argument-type]
|
||||
21 | del m["name"]
|
||||
22 |
|
||||
23 | # In a partial TypedDict (`total=False`), all keys can be deleted.
|
||||
24 | del p["name"]
|
||||
25 |
|
||||
26 | # `NotRequired` keys can always be deleted.
|
||||
27 | del mixed["year"]
|
||||
28 |
|
||||
29 | # But required keys in mixed `TypedDict` still cannot be deleted.
|
||||
30 | # error: [invalid-argument-type]
|
||||
31 | del mixed["name"]
|
||||
32 |
|
||||
33 | # And keys that don't exist cannot be deleted.
|
||||
34 | # error: [invalid-argument-type]
|
||||
35 | del mixed["non_existent"]
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[invalid-argument-type]: Cannot delete required key "name" from TypedDict `Movie`
|
||||
--> src/mdtest_snippet.py:21:7
|
||||
|
|
||||
19 | # Required keys cannot be deleted.
|
||||
20 | # error: [invalid-argument-type]
|
||||
21 | del m["name"]
|
||||
| ^^^^^^
|
||||
22 |
|
||||
23 | # In a partial TypedDict (`total=False`), all keys can be deleted.
|
||||
|
|
||||
info: Field defined here
|
||||
--> src/mdtest_snippet.py:4:5
|
||||
|
|
||||
3 | class Movie(TypedDict):
|
||||
4 | name: str
|
||||
| --------- `name` declared as required here; consider making it `NotRequired`
|
||||
5 | year: int
|
||||
|
|
||||
info: Only keys marked as `NotRequired` (or in a TypedDict with `total=False`) can be deleted
|
||||
info: rule `invalid-argument-type` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[invalid-argument-type]: Cannot delete required key "name" from TypedDict `MixedMovie`
|
||||
--> src/mdtest_snippet.py:31:11
|
||||
|
|
||||
29 | # But required keys in mixed `TypedDict` still cannot be deleted.
|
||||
30 | # error: [invalid-argument-type]
|
||||
31 | del mixed["name"]
|
||||
| ^^^^^^
|
||||
32 |
|
||||
33 | # And keys that don't exist cannot be deleted.
|
||||
|
|
||||
info: Field defined here
|
||||
--> src/mdtest_snippet.py:12:5
|
||||
|
|
||||
11 | class MixedMovie(TypedDict):
|
||||
12 | name: str
|
||||
| --------- `name` declared as required here; consider making it `NotRequired`
|
||||
13 | year: NotRequired[int]
|
||||
|
|
||||
info: Only keys marked as `NotRequired` (or in a TypedDict with `total=False`) can be deleted
|
||||
info: rule `invalid-argument-type` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[invalid-argument-type]: Cannot delete unknown key "non_existent" from TypedDict `MixedMovie`
|
||||
--> src/mdtest_snippet.py:35:11
|
||||
|
|
||||
33 | # And keys that don't exist cannot be deleted.
|
||||
34 | # error: [invalid-argument-type]
|
||||
35 | del mixed["non_existent"]
|
||||
| ^^^^^^^^^^^^^^
|
||||
|
|
||||
info: rule `invalid-argument-type` is enabled by default
|
||||
|
||||
```
|
||||
@@ -1,6 +1,5 @@
|
||||
---
|
||||
source: crates/ty_test/src/lib.rs
|
||||
assertion_line: 623
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
@@ -56,29 +55,28 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/final.md
|
||||
41 | #
|
||||
42 | # TODO: we should emit a Liskov violation here too
|
||||
43 | # error: [override-of-final-method]
|
||||
44 | method4 = 42
|
||||
45 | unrelated = 56 # fmt: skip
|
||||
46 |
|
||||
47 | # Possible overrides of possibly `@final` methods...
|
||||
48 | class C(A):
|
||||
49 | if coinflip():
|
||||
50 | def method1(self) -> None: ... # error: [override-of-final-method]
|
||||
51 | else:
|
||||
52 | pass
|
||||
53 |
|
||||
54 | if coinflip():
|
||||
55 | def method2(self) -> None: ... # error: [override-of-final-method]
|
||||
56 | else:
|
||||
57 | def method2(self) -> None: ...
|
||||
58 |
|
||||
59 | if coinflip():
|
||||
60 | def method3(self) -> None: ... # error: [override-of-final-method]
|
||||
61 |
|
||||
62 | # TODO: we should emit Liskov violations here too:
|
||||
63 | if coinflip():
|
||||
64 | method4 = 42 # error: [override-of-final-method]
|
||||
65 | else:
|
||||
66 | method4 = 56
|
||||
44 | method4 = 42; unrelated = 56 # fmt: skip
|
||||
45 |
|
||||
46 | # Possible overrides of possibly `@final` methods...
|
||||
47 | class C(A):
|
||||
48 | if coinflip():
|
||||
49 | def method1(self) -> None: ... # error: [override-of-final-method]
|
||||
50 | else:
|
||||
51 | pass
|
||||
52 |
|
||||
53 | if coinflip():
|
||||
54 | def method2(self) -> None: ... # error: [override-of-final-method]
|
||||
55 | else:
|
||||
56 | def method2(self) -> None: ...
|
||||
57 |
|
||||
58 | if coinflip():
|
||||
59 | def method3(self) -> None: ... # error: [override-of-final-method]
|
||||
60 |
|
||||
61 | # TODO: we should emit Liskov violations here too:
|
||||
62 | if coinflip():
|
||||
63 | method4 = 42 # error: [override-of-final-method]
|
||||
64 | else:
|
||||
65 | method4 = 56
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
@@ -197,9 +195,10 @@ error[override-of-final-method]: Cannot override `A.method4`
|
||||
|
|
||||
42 | # TODO: we should emit a Liskov violation here too
|
||||
43 | # error: [override-of-final-method]
|
||||
44 | method4 = 42
|
||||
44 | method4 = 42; unrelated = 56 # fmt: skip
|
||||
| ^^^^^^^ Overrides a definition from superclass `A`
|
||||
45 | unrelated = 56 # fmt: skip
|
||||
45 |
|
||||
46 | # Possible overrides of possibly `@final` methods...
|
||||
|
|
||||
info: `A.method4` is decorated with `@final`, forbidding overrides
|
||||
--> src/mdtest_snippet.py:29:9
|
||||
@@ -220,14 +219,14 @@ info: rule `override-of-final-method` is enabled by default
|
||||
|
||||
```
|
||||
error[override-of-final-method]: Cannot override `A.method1`
|
||||
--> src/mdtest_snippet.py:50:13
|
||||
--> src/mdtest_snippet.py:49:13
|
||||
|
|
||||
48 | class C(A):
|
||||
49 | if coinflip():
|
||||
50 | def method1(self) -> None: ... # error: [override-of-final-method]
|
||||
47 | class C(A):
|
||||
48 | if coinflip():
|
||||
49 | def method1(self) -> None: ... # error: [override-of-final-method]
|
||||
| ^^^^^^^ Overrides a definition from superclass `A`
|
||||
51 | else:
|
||||
52 | pass
|
||||
50 | else:
|
||||
51 | pass
|
||||
|
|
||||
info: `A.method1` is decorated with `@final`, forbidding overrides
|
||||
--> src/mdtest_snippet.py:8:9
|
||||
@@ -248,13 +247,13 @@ info: rule `override-of-final-method` is enabled by default
|
||||
|
||||
```
|
||||
error[override-of-final-method]: Cannot override `A.method2`
|
||||
--> src/mdtest_snippet.py:55:13
|
||||
--> src/mdtest_snippet.py:54:13
|
||||
|
|
||||
54 | if coinflip():
|
||||
55 | def method2(self) -> None: ... # error: [override-of-final-method]
|
||||
53 | if coinflip():
|
||||
54 | def method2(self) -> None: ... # error: [override-of-final-method]
|
||||
| ^^^^^^^ Overrides a definition from superclass `A`
|
||||
56 | else:
|
||||
57 | def method2(self) -> None: ...
|
||||
55 | else:
|
||||
56 | def method2(self) -> None: ...
|
||||
|
|
||||
info: `A.method2` is decorated with `@final`, forbidding overrides
|
||||
--> src/mdtest_snippet.py:16:9
|
||||
@@ -275,13 +274,13 @@ info: rule `override-of-final-method` is enabled by default
|
||||
|
||||
```
|
||||
error[override-of-final-method]: Cannot override `A.method3`
|
||||
--> src/mdtest_snippet.py:60:13
|
||||
--> src/mdtest_snippet.py:59:13
|
||||
|
|
||||
59 | if coinflip():
|
||||
60 | def method3(self) -> None: ... # error: [override-of-final-method]
|
||||
58 | if coinflip():
|
||||
59 | def method3(self) -> None: ... # error: [override-of-final-method]
|
||||
| ^^^^^^^ Overrides a definition from superclass `A`
|
||||
61 |
|
||||
62 | # TODO: we should emit Liskov violations here too:
|
||||
60 |
|
||||
61 | # TODO: we should emit Liskov violations here too:
|
||||
|
|
||||
info: `A.method3` is decorated with `@final`, forbidding overrides
|
||||
--> src/mdtest_snippet.py:20:9
|
||||
@@ -301,14 +300,14 @@ info: rule `override-of-final-method` is enabled by default
|
||||
|
||||
```
|
||||
error[override-of-final-method]: Cannot override `A.method4`
|
||||
--> src/mdtest_snippet.py:64:9
|
||||
--> src/mdtest_snippet.py:63:9
|
||||
|
|
||||
62 | # TODO: we should emit Liskov violations here too:
|
||||
63 | if coinflip():
|
||||
64 | method4 = 42 # error: [override-of-final-method]
|
||||
61 | # TODO: we should emit Liskov violations here too:
|
||||
62 | if coinflip():
|
||||
63 | method4 = 42 # error: [override-of-final-method]
|
||||
| ^^^^^^^ Overrides a definition from superclass `A`
|
||||
65 | else:
|
||||
66 | method4 = 56
|
||||
64 | else:
|
||||
65 | method4 = 56
|
||||
|
|
||||
info: `A.method4` is decorated with `@final`, forbidding overrides
|
||||
--> src/mdtest_snippet.py:29:9
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
---
|
||||
source: crates/ty_test/src/lib.rs
|
||||
assertion_line: 623
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
@@ -96,30 +95,30 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/final.md
|
||||
81 | class OtherChild(Parent): ...
|
||||
82 |
|
||||
83 | class Grandchild(OtherChild):
|
||||
84 | # TODO: The Liskov violation here maybe shouldn't be emitted? Whether called on the
|
||||
85 | # type or on an instance, it will behave the same from the caller's perspective. The only
|
||||
86 | # difference is whether the method body gets access to `self`, which is not a
|
||||
87 | # concern of Liskov.
|
||||
88 | @staticmethod
|
||||
89 | # error: [override-of-final-method]
|
||||
90 | # error: [invalid-method-override]
|
||||
91 | def foo(): ...
|
||||
92 | @property
|
||||
93 | # TODO: we should emit a Liskov violation here too
|
||||
94 | # error: [override-of-final-method]
|
||||
95 | def my_property1(self) -> str: ...
|
||||
96 | # TODO: we should emit a Liskov violation here too
|
||||
97 | # error: [override-of-final-method]
|
||||
98 | class_method1 = None
|
||||
84 | @staticmethod
|
||||
85 | # TODO: we should emit a Liskov violation here too
|
||||
86 | # error: [override-of-final-method]
|
||||
87 | def foo(): ...
|
||||
88 | @property
|
||||
89 | # TODO: we should emit a Liskov violation here too
|
||||
90 | # error: [override-of-final-method]
|
||||
91 | def my_property1(self) -> str: ...
|
||||
92 | # TODO: we should emit a Liskov violation here too
|
||||
93 | # error: [override-of-final-method]
|
||||
94 | class_method1 = None
|
||||
95 |
|
||||
96 | # Diagnostic edge case: `final` is very far away from the method definition in the source code:
|
||||
97 |
|
||||
98 | T = TypeVar("T")
|
||||
99 |
|
||||
100 | # Diagnostic edge case: `final` is very far away from the method definition in the source code:
|
||||
100 | def identity(x: T) -> T: ...
|
||||
101 |
|
||||
102 | T = TypeVar("T")
|
||||
103 |
|
||||
104 | def identity(x: T) -> T: ...
|
||||
105 |
|
||||
106 | class Foo:
|
||||
107 | @final
|
||||
102 | class Foo:
|
||||
103 | @final
|
||||
104 | @identity
|
||||
105 | @identity
|
||||
106 | @identity
|
||||
107 | @identity
|
||||
108 | @identity
|
||||
109 | @identity
|
||||
110 | @identity
|
||||
@@ -134,14 +133,10 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/final.md
|
||||
119 | @identity
|
||||
120 | @identity
|
||||
121 | @identity
|
||||
122 | @identity
|
||||
123 | @identity
|
||||
124 | @identity
|
||||
125 | @identity
|
||||
126 | def bar(self): ...
|
||||
127 |
|
||||
128 | class Baz(Foo):
|
||||
129 | def bar(self): ... # error: [override-of-final-method]
|
||||
122 | def bar(self): ...
|
||||
123 |
|
||||
124 | class Baz(Foo):
|
||||
125 | def bar(self): ... # error: [override-of-final-method]
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
@@ -411,42 +406,16 @@ note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[invalid-method-override]: Invalid override of method `foo`
|
||||
--> src/mdtest_snippet.pyi:91:9
|
||||
|
|
||||
89 | # error: [override-of-final-method]
|
||||
90 | # error: [invalid-method-override]
|
||||
91 | def foo(): ...
|
||||
| ^^^^^ Definition is incompatible with `Parent.foo`
|
||||
92 | @property
|
||||
93 | # TODO: we should emit a Liskov violation here too
|
||||
|
|
||||
::: src/mdtest_snippet.pyi:7:9
|
||||
|
|
||||
5 | class Parent:
|
||||
6 | @final
|
||||
7 | def foo(self): ...
|
||||
| --------- `Parent.foo` defined here
|
||||
8 |
|
||||
9 | @final
|
||||
|
|
||||
info: `Grandchild.foo` is a staticmethod but `Parent.foo` is an instance method
|
||||
info: This violates the Liskov Substitution Principle
|
||||
info: rule `invalid-method-override` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[override-of-final-method]: Cannot override `Parent.foo`
|
||||
--> src/mdtest_snippet.pyi:91:9
|
||||
--> src/mdtest_snippet.pyi:87:9
|
||||
|
|
||||
89 | # error: [override-of-final-method]
|
||||
90 | # error: [invalid-method-override]
|
||||
91 | def foo(): ...
|
||||
85 | # TODO: we should emit a Liskov violation here too
|
||||
86 | # error: [override-of-final-method]
|
||||
87 | def foo(): ...
|
||||
| ^^^ Overrides a definition from superclass `Parent`
|
||||
92 | @property
|
||||
93 | # TODO: we should emit a Liskov violation here too
|
||||
88 | @property
|
||||
89 | # TODO: we should emit a Liskov violation here too
|
||||
|
|
||||
info: `Parent.foo` is decorated with `@final`, forbidding overrides
|
||||
--> src/mdtest_snippet.pyi:6:5
|
||||
@@ -461,31 +430,31 @@ info: `Parent.foo` is decorated with `@final`, forbidding overrides
|
||||
|
|
||||
help: Remove the override of `foo`
|
||||
info: rule `override-of-final-method` is enabled by default
|
||||
85 | # type or on an instance, it will behave the same from the caller's perspective. The only
|
||||
86 | # difference is whether the method body gets access to `self`, which is not a
|
||||
87 | # concern of Liskov.
|
||||
81 | class OtherChild(Parent): ...
|
||||
82 |
|
||||
83 | class Grandchild(OtherChild):
|
||||
- @staticmethod
|
||||
- # TODO: we should emit a Liskov violation here too
|
||||
- # error: [override-of-final-method]
|
||||
- # error: [invalid-method-override]
|
||||
- def foo(): ...
|
||||
88 +
|
||||
89 | @property
|
||||
90 | # TODO: we should emit a Liskov violation here too
|
||||
91 | # error: [override-of-final-method]
|
||||
84 +
|
||||
85 | @property
|
||||
86 | # TODO: we should emit a Liskov violation here too
|
||||
87 | # error: [override-of-final-method]
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[override-of-final-method]: Cannot override `Parent.my_property1`
|
||||
--> src/mdtest_snippet.pyi:95:9
|
||||
--> src/mdtest_snippet.pyi:91:9
|
||||
|
|
||||
93 | # TODO: we should emit a Liskov violation here too
|
||||
94 | # error: [override-of-final-method]
|
||||
95 | def my_property1(self) -> str: ...
|
||||
89 | # TODO: we should emit a Liskov violation here too
|
||||
90 | # error: [override-of-final-method]
|
||||
91 | def my_property1(self) -> str: ...
|
||||
| ^^^^^^^^^^^^ Overrides a definition from superclass `Parent`
|
||||
96 | # TODO: we should emit a Liskov violation here too
|
||||
97 | # error: [override-of-final-method]
|
||||
92 | # TODO: we should emit a Liskov violation here too
|
||||
93 | # error: [override-of-final-method]
|
||||
|
|
||||
info: `Parent.my_property1` is decorated with `@final`, forbidding overrides
|
||||
--> src/mdtest_snippet.pyi:9:5
|
||||
@@ -507,15 +476,15 @@ info: rule `override-of-final-method` is enabled by default
|
||||
|
||||
```
|
||||
error[override-of-final-method]: Cannot override `Parent.class_method1`
|
||||
--> src/mdtest_snippet.pyi:98:5
|
||||
|
|
||||
96 | # TODO: we should emit a Liskov violation here too
|
||||
97 | # error: [override-of-final-method]
|
||||
98 | class_method1 = None
|
||||
| ^^^^^^^^^^^^^ Overrides a definition from superclass `Parent`
|
||||
99 |
|
||||
100 | # Diagnostic edge case: `final` is very far away from the method definition in the source code:
|
||||
|
|
||||
--> src/mdtest_snippet.pyi:94:5
|
||||
|
|
||||
92 | # TODO: we should emit a Liskov violation here too
|
||||
93 | # error: [override-of-final-method]
|
||||
94 | class_method1 = None
|
||||
| ^^^^^^^^^^^^^ Overrides a definition from superclass `Parent`
|
||||
95 |
|
||||
96 | # Diagnostic edge case: `final` is very far away from the method definition in the source code:
|
||||
|
|
||||
info: `Parent.class_method1` is decorated with `@final`, forbidding overrides
|
||||
--> src/mdtest_snippet.pyi:21:5
|
||||
|
|
||||
@@ -536,37 +505,37 @@ info: rule `override-of-final-method` is enabled by default
|
||||
|
||||
```
|
||||
error[override-of-final-method]: Cannot override `Foo.bar`
|
||||
--> src/mdtest_snippet.pyi:129:9
|
||||
--> src/mdtest_snippet.pyi:125:9
|
||||
|
|
||||
128 | class Baz(Foo):
|
||||
129 | def bar(self): ... # error: [override-of-final-method]
|
||||
124 | class Baz(Foo):
|
||||
125 | def bar(self): ... # error: [override-of-final-method]
|
||||
| ^^^ Overrides a definition from superclass `Foo`
|
||||
|
|
||||
info: `Foo.bar` is decorated with `@final`, forbidding overrides
|
||||
--> src/mdtest_snippet.pyi:107:5
|
||||
--> src/mdtest_snippet.pyi:103:5
|
||||
|
|
||||
106 | class Foo:
|
||||
107 | @final
|
||||
102 | class Foo:
|
||||
103 | @final
|
||||
| ------
|
||||
108 | @identity
|
||||
109 | @identity
|
||||
104 | @identity
|
||||
105 | @identity
|
||||
|
|
||||
::: src/mdtest_snippet.pyi:126:9
|
||||
::: src/mdtest_snippet.pyi:122:9
|
||||
|
|
||||
124 | @identity
|
||||
125 | @identity
|
||||
126 | def bar(self): ...
|
||||
120 | @identity
|
||||
121 | @identity
|
||||
122 | def bar(self): ...
|
||||
| --- `Foo.bar` defined here
|
||||
127 |
|
||||
128 | class Baz(Foo):
|
||||
123 |
|
||||
124 | class Baz(Foo):
|
||||
|
|
||||
help: Remove the override of `bar`
|
||||
info: rule `override-of-final-method` is enabled by default
|
||||
126 | def bar(self): ...
|
||||
127 |
|
||||
128 | class Baz(Foo):
|
||||
122 | def bar(self): ...
|
||||
123 |
|
||||
124 | class Baz(Foo):
|
||||
- def bar(self): ... # error: [override-of-final-method]
|
||||
129 + pass # error: [override-of-final-method]
|
||||
125 + pass # error: [override-of-final-method]
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
```
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
---
|
||||
source: crates/ty_test/src/lib.rs
|
||||
assertion_line: 623
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
@@ -85,58 +84,53 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/final.md
|
||||
14 | def f(self, x: str) -> str: ...
|
||||
15 | @overload
|
||||
16 | def f(self, x: int) -> int: ...
|
||||
17 |
|
||||
18 | # error: [override-of-final-method]
|
||||
19 | def f(self, x: int | str) -> int | str:
|
||||
20 | return x
|
||||
21 |
|
||||
22 | class Bad:
|
||||
23 | @overload
|
||||
24 | @final
|
||||
25 | def f(self, x: str) -> str: ...
|
||||
26 | @overload
|
||||
27 | def f(self, x: int) -> int: ...
|
||||
28 |
|
||||
29 | # error: [invalid-overload]
|
||||
30 | def f(self, x: int | str) -> int | str:
|
||||
31 | return x
|
||||
32 |
|
||||
33 | @final
|
||||
17 | # error: [override-of-final-method]
|
||||
18 | def f(self, x: int | str) -> int | str:
|
||||
19 | return x
|
||||
20 |
|
||||
21 | class Bad:
|
||||
22 | @overload
|
||||
23 | @final
|
||||
24 | def f(self, x: str) -> str: ...
|
||||
25 | @overload
|
||||
26 | def f(self, x: int) -> int: ...
|
||||
27 | # error: [invalid-overload]
|
||||
28 | def f(self, x: int | str) -> int | str:
|
||||
29 | return x
|
||||
30 |
|
||||
31 | @final
|
||||
32 | @overload
|
||||
33 | def g(self, x: str) -> str: ...
|
||||
34 | @overload
|
||||
35 | def g(self, x: str) -> str: ...
|
||||
36 | @overload
|
||||
37 | def g(self, x: int) -> int: ...
|
||||
38 |
|
||||
39 | # error: [invalid-overload]
|
||||
40 | def g(self, x: int | str) -> int | str:
|
||||
41 | return x
|
||||
42 |
|
||||
43 | @overload
|
||||
44 | def h(self, x: str) -> str: ...
|
||||
45 | @overload
|
||||
46 | @final
|
||||
47 | def h(self, x: int) -> int: ...
|
||||
35 | def g(self, x: int) -> int: ...
|
||||
36 | # error: [invalid-overload]
|
||||
37 | def g(self, x: int | str) -> int | str:
|
||||
38 | return x
|
||||
39 |
|
||||
40 | @overload
|
||||
41 | def h(self, x: str) -> str: ...
|
||||
42 | @overload
|
||||
43 | @final
|
||||
44 | def h(self, x: int) -> int: ...
|
||||
45 | # error: [invalid-overload]
|
||||
46 | def h(self, x: int | str) -> int | str:
|
||||
47 | return x
|
||||
48 |
|
||||
49 | # error: [invalid-overload]
|
||||
50 | def h(self, x: int | str) -> int | str:
|
||||
51 | return x
|
||||
52 |
|
||||
53 | @overload
|
||||
54 | def i(self, x: str) -> str: ...
|
||||
55 | @final
|
||||
56 | @overload
|
||||
57 | def i(self, x: int) -> int: ...
|
||||
58 |
|
||||
59 | # error: [invalid-overload]
|
||||
60 | def i(self, x: int | str) -> int | str:
|
||||
61 | return x
|
||||
62 |
|
||||
63 | class ChildOfBad(Bad):
|
||||
64 | # TODO: these should all cause us to emit Liskov violations as well
|
||||
65 | f = None # error: [override-of-final-method]
|
||||
66 | g = None # error: [override-of-final-method]
|
||||
67 | h = None # error: [override-of-final-method]
|
||||
68 | i = None # error: [override-of-final-method]
|
||||
49 | @overload
|
||||
50 | def i(self, x: str) -> str: ...
|
||||
51 | @final
|
||||
52 | @overload
|
||||
53 | def i(self, x: int) -> int: ...
|
||||
54 | # error: [invalid-overload]
|
||||
55 | def i(self, x: int | str) -> int | str:
|
||||
56 | return x
|
||||
57 |
|
||||
58 | class ChildOfBad(Bad):
|
||||
59 | # TODO: these should all cause us to emit Liskov violations as well
|
||||
60 | f = None # error: [override-of-final-method]
|
||||
61 | g = None # error: [override-of-final-method]
|
||||
62 | h = None # error: [override-of-final-method]
|
||||
63 | i = None # error: [override-of-final-method]
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
@@ -337,12 +331,13 @@ note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
```
|
||||
error[override-of-final-method]: Cannot override `Good.f`
|
||||
--> src/main.py:19:9
|
||||
--> src/main.py:18:9
|
||||
|
|
||||
18 | # error: [override-of-final-method]
|
||||
19 | def f(self, x: int | str) -> int | str:
|
||||
16 | def f(self, x: int) -> int: ...
|
||||
17 | # error: [override-of-final-method]
|
||||
18 | def f(self, x: int | str) -> int | str:
|
||||
| ^ Overrides a definition from superclass `Good`
|
||||
20 | return x
|
||||
19 | return x
|
||||
|
|
||||
info: `Good.f` is decorated with `@final`, forbidding overrides
|
||||
--> src/main.py:8:5
|
||||
@@ -366,28 +361,28 @@ info: rule `override-of-final-method` is enabled by default
|
||||
- def f(self, x: int) -> int: ...
|
||||
13 + pass
|
||||
14 + pass
|
||||
15 |
|
||||
16 | # error: [override-of-final-method]
|
||||
15 | # error: [override-of-final-method]
|
||||
- def f(self, x: int | str) -> int | str:
|
||||
- return x
|
||||
17 + pass
|
||||
18 |
|
||||
19 | class Bad:
|
||||
20 | @overload
|
||||
16 + pass
|
||||
17 |
|
||||
18 | class Bad:
|
||||
19 | @overload
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[invalid-overload]: `@final` decorator should be applied only to the overload implementation
|
||||
--> src/main.py:30:9
|
||||
--> src/main.py:28:9
|
||||
|
|
||||
29 | # error: [invalid-overload]
|
||||
30 | def f(self, x: int | str) -> int | str:
|
||||
26 | def f(self, x: int) -> int: ...
|
||||
27 | # error: [invalid-overload]
|
||||
28 | def f(self, x: int | str) -> int | str:
|
||||
| -
|
||||
| |
|
||||
| Implementation defined here
|
||||
31 | return x
|
||||
29 | return x
|
||||
|
|
||||
info: rule `invalid-overload` is enabled by default
|
||||
|
||||
@@ -395,14 +390,15 @@ info: rule `invalid-overload` is enabled by default
|
||||
|
||||
```
|
||||
error[invalid-overload]: `@final` decorator should be applied only to the overload implementation
|
||||
--> src/main.py:40:9
|
||||
--> src/main.py:37:9
|
||||
|
|
||||
39 | # error: [invalid-overload]
|
||||
40 | def g(self, x: int | str) -> int | str:
|
||||
35 | def g(self, x: int) -> int: ...
|
||||
36 | # error: [invalid-overload]
|
||||
37 | def g(self, x: int | str) -> int | str:
|
||||
| -
|
||||
| |
|
||||
| Implementation defined here
|
||||
41 | return x
|
||||
38 | return x
|
||||
|
|
||||
info: rule `invalid-overload` is enabled by default
|
||||
|
||||
@@ -410,14 +406,15 @@ info: rule `invalid-overload` is enabled by default
|
||||
|
||||
```
|
||||
error[invalid-overload]: `@final` decorator should be applied only to the overload implementation
|
||||
--> src/main.py:50:9
|
||||
--> src/main.py:46:9
|
||||
|
|
||||
49 | # error: [invalid-overload]
|
||||
50 | def h(self, x: int | str) -> int | str:
|
||||
44 | def h(self, x: int) -> int: ...
|
||||
45 | # error: [invalid-overload]
|
||||
46 | def h(self, x: int | str) -> int | str:
|
||||
| -
|
||||
| |
|
||||
| Implementation defined here
|
||||
51 | return x
|
||||
47 | return x
|
||||
|
|
||||
info: rule `invalid-overload` is enabled by default
|
||||
|
||||
@@ -425,14 +422,15 @@ info: rule `invalid-overload` is enabled by default
|
||||
|
||||
```
|
||||
error[invalid-overload]: `@final` decorator should be applied only to the overload implementation
|
||||
--> src/main.py:60:9
|
||||
--> src/main.py:55:9
|
||||
|
|
||||
59 | # error: [invalid-overload]
|
||||
60 | def i(self, x: int | str) -> int | str:
|
||||
53 | def i(self, x: int) -> int: ...
|
||||
54 | # error: [invalid-overload]
|
||||
55 | def i(self, x: int | str) -> int | str:
|
||||
| -
|
||||
| |
|
||||
| Implementation defined here
|
||||
61 | return x
|
||||
56 | return x
|
||||
|
|
||||
info: rule `invalid-overload` is enabled by default
|
||||
|
||||
@@ -440,22 +438,23 @@ info: rule `invalid-overload` is enabled by default
|
||||
|
||||
```
|
||||
error[override-of-final-method]: Cannot override `Bad.f`
|
||||
--> src/main.py:65:5
|
||||
--> src/main.py:60:5
|
||||
|
|
||||
63 | class ChildOfBad(Bad):
|
||||
64 | # TODO: these should all cause us to emit Liskov violations as well
|
||||
65 | f = None # error: [override-of-final-method]
|
||||
58 | class ChildOfBad(Bad):
|
||||
59 | # TODO: these should all cause us to emit Liskov violations as well
|
||||
60 | f = None # error: [override-of-final-method]
|
||||
| ^ Overrides a definition from superclass `Bad`
|
||||
66 | g = None # error: [override-of-final-method]
|
||||
67 | h = None # error: [override-of-final-method]
|
||||
61 | g = None # error: [override-of-final-method]
|
||||
62 | h = None # error: [override-of-final-method]
|
||||
|
|
||||
info: `Bad.f` is decorated with `@final`, forbidding overrides
|
||||
--> src/main.py:30:9
|
||||
--> src/main.py:28:9
|
||||
|
|
||||
29 | # error: [invalid-overload]
|
||||
30 | def f(self, x: int | str) -> int | str:
|
||||
26 | def f(self, x: int) -> int: ...
|
||||
27 | # error: [invalid-overload]
|
||||
28 | def f(self, x: int | str) -> int | str:
|
||||
| - `Bad.f` defined here
|
||||
31 | return x
|
||||
29 | return x
|
||||
|
|
||||
help: Remove the override of `f`
|
||||
info: rule `override-of-final-method` is enabled by default
|
||||
@@ -464,22 +463,23 @@ info: rule `override-of-final-method` is enabled by default
|
||||
|
||||
```
|
||||
error[override-of-final-method]: Cannot override `Bad.g`
|
||||
--> src/main.py:66:5
|
||||
--> src/main.py:61:5
|
||||
|
|
||||
64 | # TODO: these should all cause us to emit Liskov violations as well
|
||||
65 | f = None # error: [override-of-final-method]
|
||||
66 | g = None # error: [override-of-final-method]
|
||||
59 | # TODO: these should all cause us to emit Liskov violations as well
|
||||
60 | f = None # error: [override-of-final-method]
|
||||
61 | g = None # error: [override-of-final-method]
|
||||
| ^ Overrides a definition from superclass `Bad`
|
||||
67 | h = None # error: [override-of-final-method]
|
||||
68 | i = None # error: [override-of-final-method]
|
||||
62 | h = None # error: [override-of-final-method]
|
||||
63 | i = None # error: [override-of-final-method]
|
||||
|
|
||||
info: `Bad.g` is decorated with `@final`, forbidding overrides
|
||||
--> src/main.py:40:9
|
||||
--> src/main.py:37:9
|
||||
|
|
||||
39 | # error: [invalid-overload]
|
||||
40 | def g(self, x: int | str) -> int | str:
|
||||
35 | def g(self, x: int) -> int: ...
|
||||
36 | # error: [invalid-overload]
|
||||
37 | def g(self, x: int | str) -> int | str:
|
||||
| - `Bad.g` defined here
|
||||
41 | return x
|
||||
38 | return x
|
||||
|
|
||||
help: Remove the override of `g`
|
||||
info: rule `override-of-final-method` is enabled by default
|
||||
@@ -488,21 +488,22 @@ info: rule `override-of-final-method` is enabled by default
|
||||
|
||||
```
|
||||
error[override-of-final-method]: Cannot override `Bad.h`
|
||||
--> src/main.py:67:5
|
||||
--> src/main.py:62:5
|
||||
|
|
||||
65 | f = None # error: [override-of-final-method]
|
||||
66 | g = None # error: [override-of-final-method]
|
||||
67 | h = None # error: [override-of-final-method]
|
||||
60 | f = None # error: [override-of-final-method]
|
||||
61 | g = None # error: [override-of-final-method]
|
||||
62 | h = None # error: [override-of-final-method]
|
||||
| ^ Overrides a definition from superclass `Bad`
|
||||
68 | i = None # error: [override-of-final-method]
|
||||
63 | i = None # error: [override-of-final-method]
|
||||
|
|
||||
info: `Bad.h` is decorated with `@final`, forbidding overrides
|
||||
--> src/main.py:50:9
|
||||
--> src/main.py:46:9
|
||||
|
|
||||
49 | # error: [invalid-overload]
|
||||
50 | def h(self, x: int | str) -> int | str:
|
||||
44 | def h(self, x: int) -> int: ...
|
||||
45 | # error: [invalid-overload]
|
||||
46 | def h(self, x: int | str) -> int | str:
|
||||
| - `Bad.h` defined here
|
||||
51 | return x
|
||||
47 | return x
|
||||
|
|
||||
help: Remove the override of `h`
|
||||
info: rule `override-of-final-method` is enabled by default
|
||||
@@ -511,20 +512,21 @@ info: rule `override-of-final-method` is enabled by default
|
||||
|
||||
```
|
||||
error[override-of-final-method]: Cannot override `Bad.i`
|
||||
--> src/main.py:68:5
|
||||
--> src/main.py:63:5
|
||||
|
|
||||
66 | g = None # error: [override-of-final-method]
|
||||
67 | h = None # error: [override-of-final-method]
|
||||
68 | i = None # error: [override-of-final-method]
|
||||
61 | g = None # error: [override-of-final-method]
|
||||
62 | h = None # error: [override-of-final-method]
|
||||
63 | i = None # error: [override-of-final-method]
|
||||
| ^ Overrides a definition from superclass `Bad`
|
||||
|
|
||||
info: `Bad.i` is decorated with `@final`, forbidding overrides
|
||||
--> src/main.py:60:9
|
||||
--> src/main.py:55:9
|
||||
|
|
||||
59 | # error: [invalid-overload]
|
||||
60 | def i(self, x: int | str) -> int | str:
|
||||
53 | def i(self, x: int) -> int: ...
|
||||
54 | # error: [invalid-overload]
|
||||
55 | def i(self, x: int | str) -> int | str:
|
||||
| - `Bad.i` defined here
|
||||
61 | return x
|
||||
56 | return x
|
||||
|
|
||||
help: Remove the override of `i`
|
||||
info: rule `override-of-final-method` is enabled by default
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
---
|
||||
source: crates/ty_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: invalid.md - Tests for invalid types in type expressions - Diagnostics for common errors - Special-cased diagnostic for `callable` used in a type expression
|
||||
mdtest path: crates/ty_python_semantic/resources/mdtest/annotations/invalid.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | # error: [invalid-type-form]
|
||||
2 | # error: [invalid-type-form]
|
||||
3 | def decorator(fn: callable) -> callable:
|
||||
4 | return fn
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[invalid-type-form]: Function `callable` is not valid in a type expression
|
||||
--> src/mdtest_snippet.py:3:19
|
||||
|
|
||||
1 | # error: [invalid-type-form]
|
||||
2 | # error: [invalid-type-form]
|
||||
3 | def decorator(fn: callable) -> callable:
|
||||
| ^^^^^^^^ Did you mean `collections.abc.Callable`?
|
||||
4 | return fn
|
||||
|
|
||||
info: rule `invalid-type-form` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[invalid-type-form]: Function `callable` is not valid in a type expression
|
||||
--> src/mdtest_snippet.py:3:32
|
||||
|
|
||||
1 | # error: [invalid-type-form]
|
||||
2 | # error: [invalid-type-form]
|
||||
3 | def decorator(fn: callable) -> callable:
|
||||
| ^^^^^^^^ Did you mean `collections.abc.Callable`?
|
||||
4 | return fn
|
||||
|
|
||||
info: rule `invalid-type-form` is enabled by default
|
||||
|
||||
```
|
||||
@@ -53,7 +53,7 @@ warning[unsupported-base]: Unsupported class base
|
||||
18 |
|
||||
19 | reveal_mro(Foo) # revealed: (<class 'Foo'>, Unknown, <class 'object'>)
|
||||
|
|
||||
info: ty cannot resolve a consistent method resolution order (MRO) for class `Foo` due to this base
|
||||
info: ty cannot resolve a consistent MRO for class `Foo` due to this base
|
||||
info: Only class objects or `Any` are supported as class bases
|
||||
info: rule `unsupported-base` is enabled by default
|
||||
|
||||
@@ -68,7 +68,7 @@ warning[unsupported-base]: Unsupported class base
|
||||
27 | class D(C): ... # error: [unsupported-base]
|
||||
| ^ Has type `<class 'mdtest_snippet.<locals of function 'f'>.C @ src/mdtest_snippet.py:23'> | <class 'mdtest_snippet.<locals of function 'f'>.C @ src/mdtest_snippet.py:25'>`
|
||||
|
|
||||
info: ty cannot resolve a consistent method resolution order (MRO) for class `D` due to this base
|
||||
info: ty cannot resolve a consistent MRO for class `D` due to this base
|
||||
info: Only class objects or `Any` are supported as class bases
|
||||
info: rule `unsupported-base` is enabled by default
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@ warning[unsupported-base]: Unsupported class base
|
||||
7 | class Bad1:
|
||||
8 | def __mro_entries__(self, bases, extra_arg):
|
||||
|
|
||||
info: ty cannot resolve a consistent method resolution order (MRO) for class `Bar` due to this base
|
||||
info: ty cannot resolve a consistent MRO for class `Bar` due to this base
|
||||
info: Only class objects or `Any` are supported as class bases
|
||||
info: rule `unsupported-base` is enabled by default
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
---
|
||||
source: crates/ty_test/src/lib.rs
|
||||
assertion_line: 623
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
@@ -187,25 +186,25 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/override.md
|
||||
172 | @override
|
||||
173 | def my_property1(self) -> int: ...
|
||||
174 |
|
||||
175 | # TODO: This maybe shouldn't be a Liskov violation? Whether called on the type or
|
||||
176 | # on an instance, it will behave the same from the caller's perspective. The only difference
|
||||
177 | # is whether the method body gets access to `cls`, which is not a concern of Liskov.
|
||||
178 | @staticmethod
|
||||
179 | @override
|
||||
180 | def class_method1() -> int: ... # error: [invalid-method-override]
|
||||
181 |
|
||||
182 | @classmethod
|
||||
183 | @override
|
||||
184 | def static_method1(cls) -> int: ...
|
||||
185 |
|
||||
186 | # Diagnostic edge case: `override` is very far away from the method definition in the source code:
|
||||
187 |
|
||||
188 | T = TypeVar("T")
|
||||
189 |
|
||||
190 | def identity(x: T) -> T: ...
|
||||
191 |
|
||||
192 | class Foo:
|
||||
193 | @override
|
||||
175 | @staticmethod
|
||||
176 | @override
|
||||
177 | def class_method1() -> int: ...
|
||||
178 |
|
||||
179 | @classmethod
|
||||
180 | @override
|
||||
181 | def static_method1(cls) -> int: ...
|
||||
182 |
|
||||
183 | # Diagnostic edge case: `override` is very far away from the method definition in the source code:
|
||||
184 |
|
||||
185 | T = TypeVar("T")
|
||||
186 |
|
||||
187 | def identity(x: T) -> T: ...
|
||||
188 |
|
||||
189 | class Foo:
|
||||
190 | @override
|
||||
191 | @identity
|
||||
192 | @identity
|
||||
193 | @identity
|
||||
194 | @identity
|
||||
195 | @identity
|
||||
196 | @identity
|
||||
@@ -221,10 +220,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/override.md
|
||||
206 | @identity
|
||||
207 | @identity
|
||||
208 | @identity
|
||||
209 | @identity
|
||||
210 | @identity
|
||||
211 | @identity
|
||||
212 | def bar(self): ... # error: [invalid-explicit-override]
|
||||
209 | def bar(self): ... # error: [invalid-explicit-override]
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
@@ -371,47 +367,22 @@ info: rule `invalid-explicit-override` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[invalid-method-override]: Invalid override of method `class_method1`
|
||||
--> src/mdtest_snippet.pyi:180:9
|
||||
|
|
||||
178 | @staticmethod
|
||||
179 | @override
|
||||
180 | def class_method1() -> int: ... # error: [invalid-method-override]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^ Definition is incompatible with `Parent.class_method1`
|
||||
181 |
|
||||
182 | @classmethod
|
||||
|
|
||||
::: src/mdtest_snippet.pyi:21:9
|
||||
|
|
||||
20 | @classmethod
|
||||
21 | def class_method1(cls) -> int: ...
|
||||
| ------------------------- `Parent.class_method1` defined here
|
||||
22 |
|
||||
23 | @staticmethod
|
||||
|
|
||||
info: `LiskovViolatingButNotOverrideViolating.class_method1` is a staticmethod but `Parent.class_method1` is a classmethod
|
||||
info: This violates the Liskov Substitution Principle
|
||||
info: rule `invalid-method-override` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[invalid-explicit-override]: Method `bar` is decorated with `@override` but does not override anything
|
||||
--> src/mdtest_snippet.pyi:212:9
|
||||
--> src/mdtest_snippet.pyi:209:9
|
||||
|
|
||||
210 | @identity
|
||||
211 | @identity
|
||||
212 | def bar(self): ... # error: [invalid-explicit-override]
|
||||
207 | @identity
|
||||
208 | @identity
|
||||
209 | def bar(self): ... # error: [invalid-explicit-override]
|
||||
| ^^^
|
||||
|
|
||||
::: src/mdtest_snippet.pyi:193:5
|
||||
::: src/mdtest_snippet.pyi:190:5
|
||||
|
|
||||
192 | class Foo:
|
||||
193 | @override
|
||||
189 | class Foo:
|
||||
190 | @override
|
||||
| ---------
|
||||
194 | @identity
|
||||
195 | @identity
|
||||
191 | @identity
|
||||
192 | @identity
|
||||
|
|
||||
info: No `bar` definitions were found on any superclasses of `Foo`
|
||||
info: rule `invalid-explicit-override` is enabled by default
|
||||
|
||||
@@ -213,31 +213,3 @@ including module docstrings.
|
||||
a = 10 / 0 # error: [division-by-zero]
|
||||
b = a / 0 # error: [division-by-zero]
|
||||
```
|
||||
|
||||
## `respect-type-ignore-comments=false`
|
||||
|
||||
ty ignore `type-ignore` comments if `respect-type-ignore-comments` is set to false.
|
||||
|
||||
```toml
|
||||
[analysis]
|
||||
respect-type-ignore-comments = false
|
||||
```
|
||||
|
||||
`type: ignore` comments can't be used to suppress an error:
|
||||
|
||||
```py
|
||||
# error: [unresolved-reference]
|
||||
a = b + 10 # type: ignore
|
||||
```
|
||||
|
||||
ty doesn't report or remove unused `type: ignore` comments:
|
||||
|
||||
```py
|
||||
a = 10 + 5 # type: ignore
|
||||
```
|
||||
|
||||
ty doesn't report invalid `type: ignore` comments:
|
||||
|
||||
```py
|
||||
a = 10 + 4 # type: ignoreee
|
||||
```
|
||||
|
||||
@@ -71,29 +71,6 @@ reveal_type(constrained(str)) # revealed: str
|
||||
constrained(A)
|
||||
```
|
||||
|
||||
`type[T]` with a union upper bound `T: A | B` represents the metatype of a type variable `T` where
|
||||
`T` can be solved to any subtype of `A` or any subtype of `B`. It behaves similarly to a type
|
||||
variable that can be solved to any subclass of `A` or any subclass of `B`. Since all classes are
|
||||
instances of `type`, attributes on instances of `type` like `__name__` and `__qualname__` should
|
||||
still be accessible:
|
||||
|
||||
```py
|
||||
class Replace: ...
|
||||
class Multiply: ...
|
||||
|
||||
def union_bound[T: Replace | Multiply](x: type[T]) -> T:
|
||||
reveal_type(x) # revealed: type[T@union_bound]
|
||||
# All classes have __name__ and __qualname__ from type's metaclass
|
||||
reveal_type(x.__name__) # revealed: str
|
||||
reveal_type(x.__qualname__) # revealed: str
|
||||
reveal_type(x()) # revealed: T@union_bound
|
||||
|
||||
return x()
|
||||
|
||||
reveal_type(union_bound(Replace)) # revealed: Replace
|
||||
reveal_type(union_bound(Multiply)) # revealed: Multiply
|
||||
```
|
||||
|
||||
## Union
|
||||
|
||||
```py
|
||||
|
||||
@@ -178,48 +178,6 @@ def _(top: Top[C3], bottom: Bottom[C3]) -> None:
|
||||
reveal_type(bottom)
|
||||
```
|
||||
|
||||
## Callable with gradual parameters
|
||||
|
||||
For callables with gradual parameters (the `...` form), the top materialization preserves the
|
||||
gradual form since we cannot know what parameters are required. The bottom materialization
|
||||
simplifies to the bottom callable `(*args: object, **kwargs: object) -> Never` since this is the
|
||||
most specific type that is a subtype of all possible callable materializations.
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.12"
|
||||
```
|
||||
|
||||
```py
|
||||
from typing import Any, Callable, Never, Protocol
|
||||
from ty_extensions import Bottom, Top, is_equivalent_to, is_subtype_of, static_assert
|
||||
|
||||
type GradualCallable = Callable[..., Any]
|
||||
|
||||
def _(top: Top[GradualCallable], bottom: Bottom[GradualCallable]) -> None:
|
||||
# The top materialization keeps the gradual parameters wrapped
|
||||
reveal_type(top) # revealed: Top[(...) -> object]
|
||||
|
||||
# The bottom materialization simplifies to the fully static bottom callable
|
||||
reveal_type(bottom) # revealed: (*args: object, **kwargs: object) -> Never
|
||||
|
||||
# The bottom materialization of a gradual callable is a subtype of (and supertype of)
|
||||
# a protocol with `__call__(self, *args: object, **kwargs: object) -> Never`
|
||||
class EquivalentToBottom(Protocol):
|
||||
def __call__(self, *args: object, **kwargs: object) -> Never: ...
|
||||
|
||||
static_assert(is_subtype_of(EquivalentToBottom, Bottom[Callable[..., Never]]))
|
||||
static_assert(is_subtype_of(Bottom[Callable[..., Never]], EquivalentToBottom))
|
||||
|
||||
# TODO: is_equivalent_to only considers types of the same kind equivalent (Callable vs ProtocolInstance),
|
||||
# so this fails even though mutual subtyping proves semantic equivalence.
|
||||
static_assert(is_equivalent_to(Bottom[Callable[..., Never]], EquivalentToBottom)) # error: [static-assert-error]
|
||||
|
||||
# Top-materialized callables are not equivalent to non-top-materialized callables, even if their
|
||||
# signatures would otherwise be equivalent after materialization.
|
||||
static_assert(not is_equivalent_to(Top[Callable[..., object]], Callable[..., object]))
|
||||
```
|
||||
|
||||
## Tuple
|
||||
|
||||
All positions in a tuple are covariant.
|
||||
|
||||
@@ -1446,7 +1446,7 @@ class TaggedData(TypedDict, Generic[T]):
|
||||
p1: TaggedData[int] = {"data": 42, "tag": "number"}
|
||||
p2: TaggedData[str] = {"data": "Hello", "tag": "text"}
|
||||
|
||||
# error: [invalid-argument-type] "Invalid argument to key "data" with declared type `int` on TypedDict `TaggedData[int]`: value of type `Literal["not a number"]`"
|
||||
# error: [invalid-argument-type] "Invalid argument to key "data" with declared type `int` on TypedDict `TaggedData`: value of type `Literal["not a number"]`"
|
||||
p3: TaggedData[int] = {"data": "not a number", "tag": "number"}
|
||||
|
||||
class Items(TypedDict, Generic[T]):
|
||||
@@ -1488,7 +1488,7 @@ class TaggedData[T](TypedDict):
|
||||
p1: TaggedData[int] = {"data": 42, "tag": "number"}
|
||||
p2: TaggedData[str] = {"data": "Hello", "tag": "text"}
|
||||
|
||||
# error: [invalid-argument-type] "Invalid argument to key "data" with declared type `int` on TypedDict `TaggedData[int]`: value of type `Literal["not a number"]`"
|
||||
# error: [invalid-argument-type] "Invalid argument to key "data" with declared type `int` on TypedDict `TaggedData`: value of type `Literal["not a number"]`"
|
||||
p3: TaggedData[int] = {"data": "not a number", "tag": "number"}
|
||||
|
||||
class Items[T](TypedDict):
|
||||
@@ -2019,138 +2019,5 @@ static_assert(is_disjoint_from(TD, dict[str, int])) # error: [static-assert-err
|
||||
static_assert(is_disjoint_from(TD, dict[str, str])) # error: [static-assert-error]
|
||||
```
|
||||
|
||||
## Narrowing tagged unions of `TypedDict`s
|
||||
|
||||
In a tagged union of `TypedDict`s, a common field in each member (often `"type"` or `"tag"`) is
|
||||
given a distinct `Literal` type/value. We can narrow the union by constraining this field:
|
||||
|
||||
```py
|
||||
from typing import TypedDict, Literal
|
||||
|
||||
class Foo(TypedDict):
|
||||
tag: Literal["foo"]
|
||||
|
||||
class Bar(TypedDict):
|
||||
tag: Literal[42]
|
||||
|
||||
class Baz(TypedDict):
|
||||
tag: Literal[b"baz"] # `BytesLiteral` is supported.
|
||||
|
||||
class Bing(TypedDict):
|
||||
tag: Literal["bing"]
|
||||
|
||||
def _(u: Foo | Bar | Baz | Bing):
|
||||
if u["tag"] == "foo":
|
||||
reveal_type(u) # revealed: Foo
|
||||
elif u["tag"] == 42:
|
||||
reveal_type(u) # revealed: Bar
|
||||
elif u["tag"] == b"baz":
|
||||
reveal_type(u) # revealed: Baz
|
||||
else:
|
||||
reveal_type(u) # revealed: Bing
|
||||
```
|
||||
|
||||
We can descend into intersections to discover `TypedDict` types that need narrowing:
|
||||
|
||||
```py
|
||||
from collections.abc import Mapping
|
||||
from ty_extensions import Intersection
|
||||
|
||||
def _(u: Foo | Intersection[Bar, Mapping[str, int]]):
|
||||
if u["tag"] == "foo":
|
||||
reveal_type(u) # revealed: Foo
|
||||
else:
|
||||
reveal_type(u) # revealed: Bar & Mapping[str, int]
|
||||
```
|
||||
|
||||
We can also narrow a single `TypedDict` type to `Never`:
|
||||
|
||||
```py
|
||||
def _(u: Foo):
|
||||
if u["tag"] == "foo":
|
||||
reveal_type(u) # revealed: Foo
|
||||
else:
|
||||
reveal_type(u) # revealed: Never
|
||||
```
|
||||
|
||||
Narrowing is restricted to `Literal` tags, though, because `x == "foo"` doesn't generally tell us
|
||||
anything about the type of `x`. Here's an example where narrowing would be tempting but unsound:
|
||||
|
||||
```py
|
||||
from ty_extensions import is_assignable_to, static_assert
|
||||
|
||||
class NonLiteralTD(TypedDict):
|
||||
tag: int
|
||||
|
||||
def _(u: Foo | NonLiteralTD):
|
||||
if u["tag"] == "foo":
|
||||
# We can't narrow the union here...
|
||||
reveal_type(u) # revealed: Foo | NonLiteralTD
|
||||
else:
|
||||
# ...(even though we can here)...
|
||||
reveal_type(u) # revealed: NonLiteralTD
|
||||
|
||||
# ...because `NonLiteralTD["tag"]` could be assigned to with one of these, which would make the
|
||||
# first condition above true at runtime!
|
||||
class WackyInt(int):
|
||||
def __eq__(self, other):
|
||||
return True
|
||||
|
||||
_: NonLiteralTD = {"tag": WackyInt(99)} # allowed
|
||||
```
|
||||
|
||||
We can still narrow `Literal` tags even when non-`TypedDict` types are present in the union:
|
||||
|
||||
```py
|
||||
def _(u: Foo | Bar | dict):
|
||||
if u["tag"] == "foo":
|
||||
# TODO: `dict & ~<TypedDict ...>` should simplify to `dict` here, but that's currently a
|
||||
# false negative in `is_disjoint_impl`.
|
||||
reveal_type(u) # revealed: Foo | (dict[Unknown, Unknown] & ~<TypedDict with items 'tag'>)
|
||||
|
||||
# The negation(s) will simplify out if we add something to the union that doesn't inherit from
|
||||
# `dict`. It just needs to support indexing with a string key.
|
||||
class NotADict:
|
||||
def __getitem__(self, key): ...
|
||||
|
||||
def _(u: Foo | Bar | NotADict):
|
||||
if u["tag"] == 42:
|
||||
reveal_type(u) # revealed: Bar | NotADict
|
||||
```
|
||||
|
||||
It would be nice if we could also narrow `TypedDict` unions by checking whether a key (which only
|
||||
shows up in a subset of the union members) is present, but that isn't generally correct, because
|
||||
"extra items" are allowed by default. For example, even though `Bar` here doesn't define a `"foo"`
|
||||
field, it could be *assigned to* with another `TypedDict` that does:
|
||||
|
||||
```py
|
||||
class Foo(TypedDict):
|
||||
foo: int
|
||||
|
||||
class Bar(TypedDict):
|
||||
bar: int
|
||||
|
||||
def disappointment(u: Foo | Bar):
|
||||
if "foo" in u:
|
||||
# We can't narrow the union here...
|
||||
reveal_type(u) # revealed: Foo | Bar
|
||||
else:
|
||||
# ...(even though we *can* narrow it here)...
|
||||
# TODO: This should narrow to `Bar`, because "foo" is required in `Foo`.
|
||||
reveal_type(u) # revealed: Foo | Bar
|
||||
|
||||
# ...because `u` could turn out to be one of these.
|
||||
class FooBar(TypedDict):
|
||||
foo: int
|
||||
bar: int
|
||||
|
||||
static_assert(is_assignable_to(FooBar, Foo))
|
||||
static_assert(is_assignable_to(FooBar, Bar))
|
||||
```
|
||||
|
||||
TODO: The narrowing that we didn't do above will become possible when we add support for
|
||||
`closed=True`. This is [one of the main use cases][closed] that motivated the `closed` feature.
|
||||
|
||||
[closed]: https://peps.python.org/pep-0728/#disallowing-extra-items-explicitly
|
||||
[subtyping section]: https://typing.python.org/en/latest/spec/typeddict.html#subtyping-between-typeddict-types
|
||||
[`typeddict`]: https://typing.python.org/en/latest/spec/typeddict.html
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
use crate::AnalysisSettings;
|
||||
use crate::lint::{LintRegistry, RuleSelection};
|
||||
use ruff_db::Db as SourceDb;
|
||||
use ruff_db::files::File;
|
||||
use ty_module_resolver::Db as ModuleResolverDb;
|
||||
|
||||
/// Database giving access to semantic information about a Python program.
|
||||
#[salsa::db]
|
||||
pub trait Db: ModuleResolverDb {
|
||||
pub trait Db: SourceDb {
|
||||
/// Returns `true` if the file should be checked.
|
||||
fn should_check_file(&self, file: File) -> bool;
|
||||
|
||||
@@ -14,8 +13,6 @@ pub trait Db: ModuleResolverDb {
|
||||
|
||||
fn lint_registry(&self) -> &LintRegistry;
|
||||
|
||||
fn analysis_settings(&self) -> &AnalysisSettings;
|
||||
|
||||
/// Whether ty is running with logging verbosity INFO or higher (`-v` or more).
|
||||
fn verbose(&self) -> bool;
|
||||
}
|
||||
@@ -24,12 +21,11 @@ pub trait Db: ModuleResolverDb {
|
||||
pub(crate) mod tests {
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use crate::program::Program;
|
||||
use crate::program::{Program, SearchPathSettings};
|
||||
use crate::{
|
||||
AnalysisSettings, ProgramSettings, PythonPlatform, PythonVersionSource,
|
||||
PythonVersionWithSource, default_lint_registry,
|
||||
ProgramSettings, PythonPlatform, PythonVersionSource, PythonVersionWithSource,
|
||||
default_lint_registry,
|
||||
};
|
||||
use ty_module_resolver::SearchPathSettings;
|
||||
|
||||
use super::Db;
|
||||
use crate::lint::{LintRegistry, RuleSelection};
|
||||
@@ -41,8 +37,6 @@ pub(crate) mod tests {
|
||||
};
|
||||
use ruff_db::vendored::VendoredFileSystem;
|
||||
use ruff_python_ast::PythonVersion;
|
||||
use ty_module_resolver::Db as ModuleResolverDb;
|
||||
use ty_module_resolver::SearchPaths;
|
||||
|
||||
type Events = Arc<Mutex<Vec<salsa::Event>>>;
|
||||
|
||||
@@ -55,7 +49,6 @@ pub(crate) mod tests {
|
||||
vendored: VendoredFileSystem,
|
||||
events: Events,
|
||||
rule_selection: Arc<RuleSelection>,
|
||||
analysis_settings: Arc<AnalysisSettings>,
|
||||
}
|
||||
|
||||
impl TestDb {
|
||||
@@ -75,7 +68,6 @@ pub(crate) mod tests {
|
||||
events,
|
||||
files: Files::default(),
|
||||
rule_selection: Arc::new(RuleSelection::from_registry(default_lint_registry())),
|
||||
analysis_settings: AnalysisSettings::default().into(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,22 +130,11 @@ pub(crate) mod tests {
|
||||
default_lint_registry()
|
||||
}
|
||||
|
||||
fn analysis_settings(&self) -> &AnalysisSettings {
|
||||
&self.analysis_settings
|
||||
}
|
||||
|
||||
fn verbose(&self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
#[salsa::db]
|
||||
impl ModuleResolverDb for TestDb {
|
||||
fn search_paths(&self) -> &SearchPaths {
|
||||
Program::get(self).search_paths(self)
|
||||
}
|
||||
}
|
||||
|
||||
#[salsa::db]
|
||||
impl salsa::Database for TestDb {}
|
||||
|
||||
|
||||
@@ -5,11 +5,10 @@ use ruff_db::parsed::parsed_module;
|
||||
use ruff_python_ast::name::Name;
|
||||
use ruff_python_ast::statement_visitor::{StatementVisitor, walk_stmt};
|
||||
use ruff_python_ast::{self as ast};
|
||||
use ty_module_resolver::{ModuleName, resolve_module};
|
||||
|
||||
use crate::Db;
|
||||
use crate::semantic_index::{SemanticIndex, semantic_index};
|
||||
use crate::types::{Truthiness, Type, TypeContext, infer_expression_types};
|
||||
use crate::{Db, ModuleName, resolve_module};
|
||||
|
||||
fn dunder_all_names_cycle_initial(
|
||||
_db: &dyn Db,
|
||||
|
||||
@@ -10,8 +10,15 @@ use crate::suppression::{
|
||||
};
|
||||
pub use db::Db;
|
||||
pub use diagnostic::add_inferred_python_version_hint_to_diagnostic;
|
||||
pub use module_name::{ModuleName, ModuleNameResolutionError};
|
||||
pub use module_resolver::{
|
||||
KnownModule, Module, SearchPath, SearchPathValidationError, SearchPaths, all_modules,
|
||||
list_modules, resolve_module, resolve_module_confident, resolve_real_module,
|
||||
resolve_real_module_confident, resolve_real_shadowable_module, system_module_search_paths,
|
||||
};
|
||||
pub use program::{
|
||||
Program, ProgramSettings, PythonVersionFileSource, PythonVersionSource, PythonVersionWithSource,
|
||||
MisconfigurationMode, Program, ProgramSettings, PythonVersionFileSource, PythonVersionSource,
|
||||
PythonVersionWithSource, SearchPathSettings,
|
||||
};
|
||||
pub use python_platform::PythonPlatform;
|
||||
use rustc_hash::FxHasher;
|
||||
@@ -20,7 +27,6 @@ pub use semantic_model::{
|
||||
};
|
||||
pub use site_packages::{PythonEnvironment, SitePackagesPaths, SysPrefixPathOrigin};
|
||||
pub use suppression::create_suppression_fix;
|
||||
pub use ty_module_resolver::MisconfigurationMode;
|
||||
pub use types::DisplaySettings;
|
||||
pub use types::ide_support::{
|
||||
ImportAliasResolution, ResolvedDefinition, definitions_for_attribute, definitions_for_bin_op,
|
||||
@@ -33,6 +39,8 @@ mod db;
|
||||
mod dunder_all;
|
||||
pub mod lint;
|
||||
pub(crate) mod list;
|
||||
mod module_name;
|
||||
mod module_resolver;
|
||||
mod node_key;
|
||||
pub(crate) mod place;
|
||||
mod program;
|
||||
@@ -73,23 +81,3 @@ pub fn register_lints(registry: &mut LintRegistryBuilder) {
|
||||
registry.register_lint(&IGNORE_COMMENT_UNKNOWN_RULE);
|
||||
registry.register_lint(&INVALID_IGNORE_COMMENT);
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, get_size2::GetSize)]
|
||||
pub struct AnalysisSettings {
|
||||
/// Whether errors can be suppressed with `type: ignore` comments.
|
||||
///
|
||||
/// If set to false, ty won't:
|
||||
///
|
||||
/// * allow suppressing errors with `type: ignore` comments
|
||||
/// * report unused `type: ignore` comments
|
||||
/// * report invalid `type: ignore` comments
|
||||
pub respect_type_ignore_comments: bool,
|
||||
}
|
||||
|
||||
impl Default for AnalysisSettings {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
respect_type_ignore_comments: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user