Compare commits

...

32 Commits

Author SHA1 Message Date
Douglas Creager
832131f382 wohops 2026-01-05 10:12:14 -05:00
Douglas Creager
7da9a8ebcf add tracing 2026-01-05 10:12:14 -05:00
Douglas Creager
10c910b5ac some vacuous implications 2026-01-05 10:12:14 -05:00
Douglas Creager
dc3e36fb06 this extra ordering is counterproductive 2026-01-05 10:12:14 -05:00
Douglas Creager
42aabc8b99 separate lower and upper bounds 2026-01-05 10:12:14 -05:00
Alex Waygood
6b3de1517a [ty] Improve tracebacks when installing dependencies fails in ty_benchmark (#22399) 2026-01-05 14:55:08 +00:00
Alex Waygood
f3dea6e5c9 [ty] Optimize IntersectionType for the common case of a single negated element (#22344)
Co-authored-by: Micha Reiser <micha@reiser.io>
2026-01-05 13:41:50 +00:00
Micha Reiser
24dd149e03 [ty] Extract relation module from types.rs (#22232) 2026-01-05 13:16:49 +00:00
Alex Waygood
b8d527ff46 [ty] Optimize and simplify UnionElement::try_reduce (#22339) 2026-01-05 12:54:44 +00:00
Aria Desires
e63cf978ae [ty] Implement support for explicit markdown code fences in docstring rendering (#22373)
* Fixes https://github.com/astral-sh/ty/issues/2291
2026-01-05 07:13:24 -05:00
Rob Hand
3dab4ff8ad [ty] (docs) - Note insta is required for working with ty tests in ty CONTRIBUTING.md (#22332) 2026-01-05 11:05:13 +01:00
Jason K Hall
24580e2ee8 flake8-simplify: avoid unnecessary builtins import for SIM105 (#22358) 2026-01-05 10:58:46 +01:00
renovate[bot]
3d3af6f7c8 Update pre-commit dependencies (#22393)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Micha Reiser <micha@reiser.io>
2026-01-05 10:24:40 +01:00
renovate[bot]
7cc34c081a Update CodSpeedHQ/action action to v4.5.1 (#22389)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-05 09:10:06 +01:00
renovate[bot]
7a95013f56 Update Rust crate serde_json to v1.0.148 (#22387)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-05 09:09:23 +01:00
renovate[bot]
2395954d9a Update Rust crate schemars to v1.2.0 (#22391)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Micha Reiser <micha@reiser.io>
2026-01-05 09:08:58 +01:00
renovate[bot]
670bd01fb5 Update Rust crate arc-swap to v1.8.0 (#22390)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-05 09:08:32 +01:00
renovate[bot]
eae5c685f8 Update Rust crate proc-macro2 to v1.0.104 (#22386)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-05 09:05:21 +01:00
renovate[bot]
994f05f3ca Update Rust crate tempfile to v3.24.0 (#22392)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-05 09:04:59 +01:00
renovate[bot]
8dcecf323b Update taiki-e/install-action action to v2.65.6 (#22388)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-05 08:03:31 +00:00
renovate[bot]
b12c94e411 Update Rust crate jiff to v0.2.17 (#22384)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-05 07:57:34 +00:00
renovate[bot]
a9c3ea9674 Update Rust crate matchit to v0.9.1 (#22385)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-05 07:56:54 +00:00
renovate[bot]
704c57f491 Update Rust crate insta to v1.45.1 (#22383)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-05 07:56:36 +00:00
renovate[bot]
7a27662eca Update cargo-bins/cargo-binstall action to v1.16.6 (#22380)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-05 08:30:34 +01:00
renovate[bot]
ce2490ee93 Update dependency @cloudflare/workers-types to v4.20251229.0 (#22381)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-05 08:30:16 +01:00
Charlie Marsh
92a2f2c992 [ty] Apply class decorators via try_call() (#22375)
## Summary

Decorators are now called with the class as an argument, and the return
type becomes the class's type. This mirrors how function decorators
already work.

Closes https://github.com/astral-sh/ty/issues/2313.
2026-01-04 17:11:00 -05:00
Charlie Marsh
11b551c2be Add a CLAUDE.md (#22370)
## Summary

This is a starting point based on my own experiments. Feedback and
changes welcome -- I think we should iterate on this a lot as we go.
2026-01-04 15:00:31 -05:00
Micha Reiser
b85c0190c5 [ty] Use upstream GetSize implementation for OrderMap and OrderSet (#22374) 2026-01-04 19:54:03 +00:00
Micha Reiser
46a4bfc478 [ty] Use default HashSet for TypeCollector (#22368) 2026-01-04 18:58:30 +00:00
Alex Waygood
0c53395917 [ty] Add a second benchmark for enums with many members (#22364) 2026-01-04 17:58:20 +00:00
Alex Waygood
8464aca795 Bump docstring-adder pin (#22361) 2026-01-03 20:21:45 +00:00
Alex Waygood
e1439beab2 [ty] Use UnionType helper methods more consistently (#22357) 2026-01-03 14:19:06 +00:00
38 changed files with 3787 additions and 3109 deletions

View File

@@ -281,11 +281,11 @@ jobs:
- name: "Install mold"
uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
- name: "Install cargo nextest"
uses: taiki-e/install-action@b9c5db3aef04caffaf95a1d03931de10fb2a140f # v2.65.1
uses: taiki-e/install-action@28a9d316db64b78a951f3f8587a5d08cc97ad8eb # v2.65.6
with:
tool: cargo-nextest
- name: "Install cargo insta"
uses: taiki-e/install-action@b9c5db3aef04caffaf95a1d03931de10fb2a140f # v2.65.1
uses: taiki-e/install-action@28a9d316db64b78a951f3f8587a5d08cc97ad8eb # v2.65.6
with:
tool: cargo-insta
- name: "Install uv"
@@ -343,7 +343,7 @@ jobs:
- name: "Install mold"
uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
- name: "Install cargo nextest"
uses: taiki-e/install-action@b9c5db3aef04caffaf95a1d03931de10fb2a140f # v2.65.1
uses: taiki-e/install-action@28a9d316db64b78a951f3f8587a5d08cc97ad8eb # v2.65.6
with:
tool: cargo-nextest
- name: "Install uv"
@@ -376,7 +376,7 @@ jobs:
- name: "Install Rust toolchain"
run: rustup show
- name: "Install cargo nextest"
uses: taiki-e/install-action@b9c5db3aef04caffaf95a1d03931de10fb2a140f # v2.65.1
uses: taiki-e/install-action@28a9d316db64b78a951f3f8587a5d08cc97ad8eb # v2.65.6
with:
tool: cargo-nextest
- name: "Install uv"
@@ -468,7 +468,7 @@ jobs:
- name: "Install mold"
uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
- name: "Install cargo-binstall"
uses: cargo-bins/cargo-binstall@4a9028576ed64318f7b24193a62695e96dcbe015 # v1.16.5
uses: cargo-bins/cargo-binstall@80aaafe04903087c333980fa2686259ddd34b2d9 # v1.16.6
- name: "Install cargo-fuzz"
# Download the latest version from quick install and not the github releases because github releases only has MUSL targets.
run: cargo binstall cargo-fuzz --force --disable-strategies crate-meta-data --no-confirm
@@ -713,7 +713,7 @@ jobs:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
persist-credentials: false
- uses: cargo-bins/cargo-binstall@4a9028576ed64318f7b24193a62695e96dcbe015 # v1.16.5
- uses: cargo-bins/cargo-binstall@80aaafe04903087c333980fa2686259ddd34b2d9 # v1.16.6
- run: cargo binstall --no-confirm cargo-shear
- run: cargo shear
@@ -972,7 +972,7 @@ jobs:
run: rustup show
- name: "Install codspeed"
uses: taiki-e/install-action@b9c5db3aef04caffaf95a1d03931de10fb2a140f # v2.65.1
uses: taiki-e/install-action@28a9d316db64b78a951f3f8587a5d08cc97ad8eb # v2.65.6
with:
tool: cargo-codspeed
@@ -980,7 +980,7 @@ jobs:
run: cargo codspeed build --features "codspeed,ruff_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
uses: CodSpeedHQ/action@972e3437949c89e1357ebd1a2dbc852fcbc57245 # v4.5.1
with:
mode: simulation
run: cargo codspeed run
@@ -1011,7 +1011,7 @@ jobs:
run: rustup show
- name: "Install codspeed"
uses: taiki-e/install-action@b9c5db3aef04caffaf95a1d03931de10fb2a140f # v2.65.1
uses: taiki-e/install-action@28a9d316db64b78a951f3f8587a5d08cc97ad8eb # v2.65.6
with:
tool: cargo-codspeed
@@ -1047,7 +1047,7 @@ jobs:
- uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6
- name: "Install codspeed"
uses: taiki-e/install-action@b9c5db3aef04caffaf95a1d03931de10fb2a140f # v2.65.1
uses: taiki-e/install-action@28a9d316db64b78a951f3f8587a5d08cc97ad8eb # v2.65.6
with:
tool: cargo-codspeed
@@ -1061,7 +1061,7 @@ jobs:
run: chmod +x target/codspeed/simulation/ruff_benchmark/ty
- name: "Run benchmarks"
uses: CodSpeedHQ/action@346a2d8a8d9d38909abd0bc3d23f773110f076ad # v4.4.1
uses: CodSpeedHQ/action@972e3437949c89e1357ebd1a2dbc852fcbc57245 # v4.5.1
with:
mode: simulation
run: cargo codspeed run --bench ty "${{ matrix.benchmark }}"
@@ -1098,7 +1098,7 @@ jobs:
run: rustup show
- name: "Install codspeed"
uses: taiki-e/install-action@b9c5db3aef04caffaf95a1d03931de10fb2a140f # v2.65.1
uses: taiki-e/install-action@28a9d316db64b78a951f3f8587a5d08cc97ad8eb # v2.65.6
with:
tool: cargo-codspeed
@@ -1136,7 +1136,7 @@ jobs:
- uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6
- name: "Install codspeed"
uses: taiki-e/install-action@b9c5db3aef04caffaf95a1d03931de10fb2a140f # v2.65.1
uses: taiki-e/install-action@28a9d316db64b78a951f3f8587a5d08cc97ad8eb # v2.65.6
with:
tool: cargo-codspeed
@@ -1150,7 +1150,7 @@ jobs:
run: chmod +x target/codspeed/walltime/ruff_benchmark/ty_walltime
- name: "Run benchmarks"
uses: CodSpeedHQ/action@346a2d8a8d9d38909abd0bc3d23f773110f076ad # v4.4.1
uses: CodSpeedHQ/action@972e3437949c89e1357ebd1a2dbc852fcbc57245 # v4.5.1
env:
# enabling walltime flamegraphs adds ~6 minutes to the CI time, and they don't
# appear to provide much useful insight for our walltime benchmarks right now

View File

@@ -32,13 +32,13 @@ repos:
- id: validate-pyproject
- repo: https://github.com/executablebooks/mdformat
rev: 0.7.22
rev: 1.0.0
hooks:
- id: mdformat
language: python # means renovate will also update `additional_dependencies`
additional_dependencies:
- mdformat-mkdocs==4.0.0
- mdformat-footnote==0.1.1
- mdformat-mkdocs==5.0.0
- mdformat-footnote==0.1.2
exclude: |
(?x)^(
docs/formatter/black\.md

64
CLAUDE.md Normal file
View File

@@ -0,0 +1,64 @@
# Ruff Repository
This repository contains both Ruff (a Python linter and formatter) and ty (a Python type checker). The crates follow a naming convention: `ruff_*` for Ruff-specific code and `ty_*` for ty-specific code. ty reuses several Ruff crates, including the Python parser (`ruff_python_parser`) and AST definitions (`ruff_python_ast`).
## Running Tests
Run all tests (using `nextest` for faster execution):
```sh
cargo nextest run
```
Run tests for a specific crate:
```sh
cargo nextest run -p ty_python_semantic
```
Run a specific mdtest (use a substring of the test name):
```sh
MDTEST_TEST_FILTER="<filter>" cargo nextest run -p ty_python_semantic mdtest
```
Update snapshots after running tests:
```sh
cargo insta accept
```
## Running Clippy
```sh
cargo clippy --workspace --all-targets --all-features -- -D warnings
```
## Running Debug Builds
Use debug builds (not `--release`) when developing, as release builds lack debug assertions and have slower compile times.
Run Ruff:
```sh
cargo run --bin ruff -- check path/to/file.py
```
Run ty:
```sh
cargo run --bin ty -- check path/to/file.py
```
## Pull Requests
When working on ty, PR titles should start with `[ty]` and be tagged with the `ty` GitHub label.
## Development Guidelines
- All changes must be tested. If you're not testing your changes, you're not done.
- Get your tests to pass. If you didn't run the tests, your code does not work.
- Follow existing code style. Check neighboring files for patterns.
- Always run `uvx pre-commit run -a` at the end of a task.
- Avoid writing significant amounts of new code. This is often a sign that we're missing an existing method or mechanism that could help solve the problem. Look for existing utilities first.
- Avoid falling back to patterns that require `panic!`, `unreachable!`, or `.unwrap()`. Instead, try to encode those constraints in the type system.

68
Cargo.lock generated
View File

@@ -146,9 +146,12 @@ dependencies = [
[[package]]
name = "arc-swap"
version = "1.7.1"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457"
checksum = "51d03449bb8ca2cc2ef70869af31463d1ae5ccc8fa3e334b307203fbf815207e"
dependencies = [
"rustversion",
]
[[package]]
name = "argfile"
@@ -1030,7 +1033,7 @@ dependencies = [
"libc",
"option-ext",
"redox_users",
"windows-sys 0.59.0",
"windows-sys 0.60.2",
]
[[package]]
@@ -1122,7 +1125,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
dependencies = [
"libc",
"windows-sys 0.59.0",
"windows-sys 0.52.0",
]
[[package]]
@@ -1645,9 +1648,9 @@ dependencies = [
[[package]]
name = "insta"
version = "1.45.0"
version = "1.45.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b76866be74d68b1595eb8060cb9191dca9c021db2316558e52ddc5d55d41b66c"
checksum = "983e3b24350c84ab8a65151f537d67afbbf7153bb9f1110e03e9fa9b07f67a5c"
dependencies = [
"console 0.15.11",
"once_cell",
@@ -1778,9 +1781,9 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
[[package]]
name = "jiff"
version = "0.2.16"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49cce2b81f2098e7e3efc35bc2e0a6b7abec9d34128283d7a26fa8f32a6dbb35"
checksum = "a87d9b8105c23642f50cbbae03d1f75d8422c5cb98ce7ee9271f7ff7505be6b8"
dependencies = [
"jiff-static",
"jiff-tzdb-platform",
@@ -1788,14 +1791,14 @@ dependencies = [
"portable-atomic",
"portable-atomic-util",
"serde_core",
"windows-sys 0.59.0",
"windows-sys 0.52.0",
]
[[package]]
name = "jiff-static"
version = "0.2.16"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "980af8b43c3ad5d8d349ace167ec8170839f753a42d233ba19e08afe1850fa69"
checksum = "b787bebb543f8969132630c51fd0afab173a86c6abae56ff3b9e5e3e3f9f6e58"
dependencies = [
"proc-macro2",
"quote",
@@ -2057,9 +2060,9 @@ checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5"
[[package]]
name = "matchit"
version = "0.9.0"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ea5f97102eb9e54ab99fb70bb175589073f554bdadfb74d9bd656482ea73e2a"
checksum = "b3eede3bdf92f3b4f9dc04072a9ce5ab557d5ec9038773bf9ffcd5588b3cc05b"
[[package]]
name = "memchr"
@@ -2631,9 +2634,9 @@ dependencies = [
[[package]]
name = "proc-macro2"
version = "1.0.103"
version = "1.0.104"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8"
checksum = "9695f8df41bb4f3d222c95a67532365f569318332d03d5f3f67f37b20e6ebdf0"
dependencies = [
"unicode-ident",
]
@@ -3246,7 +3249,6 @@ name = "ruff_memory_usage"
version = "0.0.0"
dependencies = [
"get-size2",
"ordermap",
]
[[package]]
@@ -3617,15 +3619,15 @@ checksum = "781442f29170c5c93b7185ad559492601acdc71d5bb0706f5868094f45cfcd08"
[[package]]
name = "rustix"
version = "1.1.2"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e"
checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34"
dependencies = [
"bitflags 2.10.0",
"errno",
"libc",
"linux-raw-sys",
"windows-sys 0.59.0",
"windows-sys 0.52.0",
]
[[package]]
@@ -3692,9 +3694,9 @@ dependencies = [
[[package]]
name = "schemars"
version = "1.1.0"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9558e172d4e8533736ba97870c4b2cd63f84b382a3d6eb063da41b91cce17289"
checksum = "54e910108742c57a770f492731f99be216a52fadd361b06c8fb59d74ccc267d2"
dependencies = [
"dyn-clone",
"ref-cast",
@@ -3705,9 +3707,9 @@ dependencies = [
[[package]]
name = "schemars_derive"
version = "1.1.0"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "301858a4023d78debd2353c7426dc486001bddc91ae31a76fb1f55132f7e2633"
checksum = "4908ad288c5035a8eb12cfdf0d49270def0a268ee162b75eeee0f85d155a7c45"
dependencies = [
"proc-macro2",
"quote",
@@ -3781,15 +3783,15 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.146"
version = "1.0.148"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "217ca874ae0207aac254aa02c957ded05585a90892cc8d87f9e5fa49669dadd8"
checksum = "3084b546a1dd6289475996f182a22aba973866ea8e8b02c51d9f46b1336a22da"
dependencies = [
"itoa",
"memchr",
"ryu",
"serde",
"serde_core",
"zmij",
]
[[package]]
@@ -4019,15 +4021,15 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
[[package]]
name = "tempfile"
version = "3.23.0"
version = "3.24.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16"
checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c"
dependencies = [
"fastrand",
"getrandom 0.3.4",
"once_cell",
"rustix",
"windows-sys 0.59.0",
"windows-sys 0.52.0",
]
[[package]]
@@ -5131,7 +5133,7 @@ version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
dependencies = [
"windows-sys 0.59.0",
"windows-sys 0.52.0",
]
[[package]]
@@ -5523,6 +5525,12 @@ dependencies = [
"zstd",
]
[[package]]
name = "zmij"
version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30e0d8dffbae3d840f64bda38e28391faef673a7b5a6017840f2a106c8145868"
[[package]]
name = "zstd"
version = "0.11.2+zstd.1.5.2"

View File

@@ -93,6 +93,7 @@ get-size2 = { version = "0.7.3", features = [
"smallvec",
"hashbrown",
"compact-str",
"ordermap"
] }
getrandom = { version = "0.3.1" }
glob = { version = "0.3.1" }

View File

@@ -557,6 +557,60 @@ fn benchmark_many_enum_members(criterion: &mut Criterion) {
});
}
fn benchmark_many_enum_members_2(criterion: &mut Criterion) {
const NUM_ENUM_MEMBERS: usize = 48;
setup_rayon();
let mut code = "\
from enum import Enum
from typing_extensions import assert_never
class E(Enum):
"
.to_string();
for i in 0..NUM_ENUM_MEMBERS {
writeln!(&mut code, " m{i} = {i}").ok();
}
code.push_str(
"
def method(self):
match self:",
);
for i in 0..NUM_ENUM_MEMBERS {
write!(
&mut code,
"
case E.m{i}:
pass"
)
.ok();
}
write!(
&mut code,
"
case _:
assert_never(self)"
)
.ok();
criterion.bench_function("ty_micro[many_enum_members_2]", |b| {
b.iter_batched_ref(
|| setup_micro_case(&code),
|case| {
let Case { db, .. } = case;
let result = db.check();
assert_eq!(result.len(), 0);
},
BatchSize::SmallInput,
);
});
}
struct ProjectBenchmark<'a> {
project: InstalledProject<'a>,
fs: MemoryFileSystem,
@@ -717,6 +771,7 @@ criterion_group!(
benchmark_complex_constrained_attributes_2,
benchmark_complex_constrained_attributes_3,
benchmark_many_enum_members,
benchmark_many_enum_members_2,
);
criterion_group!(project, anyio, attrs, hydra, datetype);
criterion_main!(check_file, micro, project);

View File

@@ -157,14 +157,11 @@ pub(crate) fn suppressible_exception(
let mut rest: Vec<Edit> = Vec::new();
let content: String;
if exception == "BaseException" && handler_names.is_empty() {
let (import_exception, binding_exception) =
checker.importer().get_or_import_symbol(
&ImportRequest::import("builtins", &exception),
stmt.start(),
checker.semantic(),
)?;
let (import_exception, binding_exception) = checker
.importer()
.get_or_import_builtin_symbol(&exception, stmt.start(), checker.semantic())?;
content = format!("with {binding}({binding_exception})");
rest.push(import_exception);
rest.extend(import_exception);
} else {
content = format!("with {binding}({exception})");
}

View File

@@ -104,22 +104,21 @@ SIM105 [*] Use `contextlib.suppress(BaseException)` instead of `try`-`except`-`p
|
help: Replace `try`-`except`-`pass` with `with contextlib.suppress(BaseException): ...`
1 + import contextlib
2 + import builtins
3 | def foo():
4 | pass
5 |
2 | def foo():
3 | pass
4 |
--------------------------------------------------------------------------------
24 | pass
25 |
26 | # SIM105
23 | pass
24 |
25 | # SIM105
- try:
27 + with contextlib.suppress(builtins.BaseException):
28 | foo()
26 + with contextlib.suppress(BaseException):
27 | foo()
- except:
- pass
29 |
30 | # SIM105
31 | try:
28 |
29 | # SIM105
30 | try:
note: This is an unsafe fix and may change runtime behavior
SIM105 [*] Use `contextlib.suppress(a.Error, b.Error)` instead of `try`-`except`-`pass`

View File

@@ -12,7 +12,6 @@ license = { workspace = true }
[dependencies]
get-size2 = { workspace = true }
ordermap = { workspace = true }
[lints]
workspace = true

View File

@@ -1,7 +1,6 @@
use std::cell::RefCell;
use get_size2::{GetSize, StandardTracker};
use ordermap::{OrderMap, OrderSet};
thread_local! {
pub static TRACKER: RefCell<Option<StandardTracker>>= const { RefCell::new(None) };
@@ -42,16 +41,3 @@ pub fn heap_size<T: GetSize>(value: &T) -> usize {
}
})
}
/// An implementation of [`GetSize::get_heap_size`] for [`OrderSet`].
pub fn order_set_heap_size<T: GetSize, S>(set: &OrderSet<T, S>) -> usize {
(set.capacity() * T::get_stack_size()) + set.iter().map(heap_size).sum::<usize>()
}
/// An implementation of [`GetSize::get_heap_size`] for [`OrderMap`].
pub fn order_map_heap_size<K: GetSize, V: GetSize, S>(map: &OrderMap<K, V, S>) -> usize {
(map.capacity() * (K::get_stack_size() + V::get_stack_size()))
+ (map.iter())
.map(|(k, v)| heap_size(k) + heap_size(v))
.sum::<usize>()
}

View File

@@ -25,6 +25,12 @@ that are ready for contributions.
ty is written in Rust. You'll need to install the
[Rust toolchain](https://www.rust-lang.org/tools/install) for development.
You'll also need [Insta](https://insta.rs/docs/) to update snapshot tests:
```shell
cargo install cargo-insta
```
You'll need [uv](https://docs.astral.sh/uv/getting-started/installation/) (or `pipx` and `pip`) to
run Python utility commands.
@@ -68,6 +74,13 @@ will save you time and expedite the merge process.
If you're using VS Code, you can also install the recommended [rust-analyzer](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer) extension to get these checks while editing.
Note that many code changes also require updating the snapshot tests, which is done interactively
after running `cargo test` like so:
```shell
cargo insta review
```
Include the text `[ty]` at the beginning of your pull request title, to distinguish ty pull requests
from Ruff ones.

View File

@@ -196,6 +196,7 @@ fn render_markdown(docstring: &str) -> String {
let mut first_line = true;
let mut block_indent = 0;
let mut in_doctest = false;
let mut in_markdown_with_fence = None;
let mut starting_literal = None;
let mut in_literal = false;
let mut in_any_code = false;
@@ -254,6 +255,34 @@ fn render_markdown(docstring: &str) -> String {
output.push_str("python\n");
}
// If we're not in a codeblock and we see a markdown codefence, start one
let has_tick_fence = line.starts_with("```");
let has_tilde_fence = line.starts_with("~~~");
if !in_any_code && (has_tick_fence || has_tilde_fence) {
let without_leading_fence = if has_tick_fence {
line.trim_start_matches('`')
} else {
line.trim_start_matches('~')
};
let fence_len = line.len() - without_leading_fence.len();
let fence = &line[..fence_len];
// If we don't see this amount of ticks again on the line, assume we're opening a markdown block
// (We *don't* want to consider ```hello``` as a codefence, that's inline code!)
if !without_leading_fence.contains(fence) {
// Unlike other blocks we don't need to emit fences because it's already markdown
block_indent = line_indent;
in_any_code = true;
in_markdown_with_fence = Some(fence.to_owned());
}
// If we're in a markdown code fence and this line seems to terminate it, end the block
} else if let Some(fence) = &in_markdown_with_fence
&& line.starts_with(fence)
{
in_any_code = false;
block_indent = 0;
in_markdown_with_fence = None;
}
// If we're not in a codeblock and we see something that signals a literal block, start one
let parsed_lit = line
// first check for a line ending with `::`
@@ -423,7 +452,11 @@ fn render_markdown(docstring: &str) -> String {
// Flush codeblock
if in_any_code {
output.push('\n');
output.push_str(FENCE);
if let Some(fence) = &in_markdown_with_fence {
output.push_str(fence);
} else {
output.push_str(FENCE);
}
}
output
@@ -1095,6 +1128,174 @@ mod tests {
");
}
// We should not parse the contents of a markdown codefence
#[test]
fn explicit_markdown_block_with_ps1_contents() {
let docstring = r#"
My cool func:
```python
>>> thing.do_thing()
wow it did the thing
>>> thing.do_other_thing()
it sure did the thing
```
"#;
let docstring = Docstring::new(docstring.to_owned());
assert_snapshot!(docstring.render_markdown(), @r"
My cool func:
```python
>>> thing.do_thing()
wow it did the thing
>>> thing.do_other_thing()
it sure did the thing
```
");
}
// We should not parse the contents of a markdown codefence
#[test]
fn explicit_markdown_block_with_underscore_contents_tick() {
let docstring = r#"
My cool func:
`````python
x_y = thing_do();
``` # this should't close the fence!
a_b = other_thing();
`````
"#;
let docstring = Docstring::new(docstring.to_owned());
assert_snapshot!(docstring.render_markdown(), @r"
My cool func:
`````python
x_y = thing_do();
``` # this should't close the fence!
a_b = other_thing();
`````
");
}
// `~~~` also starts a markdown codefence
#[test]
fn explicit_markdown_block_with_underscore_contents_tilde() {
let docstring = r#"
My cool func:
~~~~~python
x_y = thing_do();
~~~ # this should't close the fence!
a_b = other_thing();
~~~~~
"#;
let docstring = Docstring::new(docstring.to_owned());
assert_snapshot!(docstring.render_markdown(), @r"
My cool func:
~~~~~python
x_y = thing_do();
~~~ # this should't close the fence!
a_b = other_thing();
~~~~~
");
}
// What do we do when we hit the end of the docstring with an unclosed markdown block?
#[test]
fn explicit_markdown_block_with_unclosed_fence_tick() {
let docstring = r#"
My cool func:
````python
x_y = thing_do();
"#;
let docstring = Docstring::new(docstring.to_owned());
assert_snapshot!(docstring.render_markdown(), @r"
My cool func:
````python
x_y = thing_do();
````
");
}
// What do we do when we hit the end of the docstring with an unclosed markdown block?
#[test]
fn explicit_markdown_block_with_unclosed_fence_tilde() {
let docstring = r#"
My cool func:
~~~~~python
x_y = thing_do();
"#;
let docstring = Docstring::new(docstring.to_owned());
assert_snapshot!(docstring.render_markdown(), @r"
My cool func:
~~~~~python
x_y = thing_do();
~~~~~
");
}
// Demonstration of where we're unreasonably lax about markdown block parsing.
// It's fine to break this test, it's not particularly intentional behaviour.
#[test]
fn explicit_markdown_block_messy_corners_tick() {
let docstring = r#"
My cool func:
``````we still think this is a codefence```
x_y = thing_do();
```````````` and are sloppy as heck with indentation and closing shrugggg
"#;
let docstring = Docstring::new(docstring.to_owned());
assert_snapshot!(docstring.render_markdown(), @r"
My cool func:
``````we still think this is a codefence```
x_y = thing_do();
```````````` and are sloppy as heck with indentation and closing shrugggg
");
}
// Demonstration of where we're unreasonably lax about markdown block parsing.
// It's fine to break this test, it's not particularly intentional behaviour.
#[test]
fn explicit_markdown_block_messy_corners_tilde() {
let docstring = r#"
My cool func:
~~~~~~we still think this is a codefence~~~
x_y = thing_do();
~~~~~~~~~~~~~ and are sloppy as heck with indentation and closing shrugggg
"#;
let docstring = Docstring::new(docstring.to_owned());
assert_snapshot!(docstring.render_markdown(), @r"
My cool func:
~~~~~~we still think this is a codefence~~~
x_y = thing_do();
~~~~~~~~~~~~~ and are sloppy as heck with indentation and closing shrugggg
");
}
// `.. code::` is a literal block and the `.. code::` should be deleted
#[test]
fn code_block() {

View File

@@ -234,3 +234,38 @@ def takes_no_argument() -> str:
@takes_no_argument
def g(x): ...
```
## Class decorators
Class decorator calls are validated, emitting diagnostics for invalid arguments:
```py
def takes_int(x: int) -> int:
return x
# error: [invalid-argument-type]
@takes_int
class Foo: ...
```
Using `None` as a decorator is an error:
```py
# error: [call-non-callable]
@None
class Bar: ...
```
A decorator can enforce type constraints on the class being decorated:
```py
def decorator(cls: type[int]) -> type[int]:
return cls
# error: [invalid-argument-type]
@decorator
class Baz: ...
# TODO: the revealed type should ideally be `type[int]` (the decorator's return type)
reveal_type(Baz) # revealed: <class 'Baz'>
```

File diff suppressed because it is too large Load Diff

View File

@@ -40,8 +40,8 @@
use crate::types::enums::{enum_member_literals, enum_metadata};
use crate::types::type_ordering::union_or_intersection_elements_ordering;
use crate::types::{
BytesLiteralType, IntersectionType, KnownClass, StringLiteralType, Type,
TypeVarBoundOrConstraints, UnionType,
BytesLiteralType, IntersectionType, KnownClass, NegativeIntersectionElements,
StringLiteralType, Type, TypeVarBoundOrConstraints, UnionType,
};
use crate::{Db, FxOrderSet};
use rustc_hash::FxHashSet;
@@ -99,92 +99,77 @@ impl<'db> UnionElement<'db> {
/// Try reducing this `UnionElement` given the presence in the same union of `other_type`.
fn try_reduce(&mut self, db: &'db dyn Db, other_type: Type<'db>) -> ReduceResult<'db> {
match self {
let mut other_type_negated_cache = None;
let mut other_type_negated =
|| *other_type_negated_cache.get_or_insert_with(|| other_type.negate(db));
let mut collapse = false;
let mut ignore = false;
// A closure called for each element in a set of literals
// to determine whether the element should be retained in the set.
//
// If `ignore` or `collapse` is `true` for any element in the set,
// we no longer need to do any expensive subtyping checks for any
// further elements in the set:
//
// - if `ignore` is `true`, this indicates that `other_type` is a
// subtype of one of the literals in this set. Given this fact,
// it cannot be possible for any other literals in this set to be
// a subtype of `other_type`.
// - if `collapse` is `true`, all literals of this kind will be
// removed from the union, so it's irrelevant to answer the
// question of which literals should remain in this set.
//
// We therefore only ask if `ty` is a subtype of `other_type` if
// both `ignore` and `collapse` are `false`. If either is `true`,
// we skip the expensive subtype check and return `true`.
let mut should_retain_type = |ty| {
if ignore || other_type.is_subtype_of(db, ty) {
ignore = true;
return true;
}
if collapse || other_type_negated().is_subtype_of(db, ty) {
collapse = true;
return true;
}
!ty.is_subtype_of(db, other_type)
};
let should_keep = match self {
UnionElement::IntLiterals(literals) => {
if other_type.splits_literals(db, LiteralKind::Int) {
let mut collapse = false;
let mut ignore = false;
let negated = other_type.negate(db);
literals.retain(|literal| {
let ty = Type::IntLiteral(*literal);
if negated.is_subtype_of(db, ty) {
collapse = true;
}
if other_type.is_subtype_of(db, ty) {
ignore = true;
}
!ty.is_subtype_of(db, other_type)
});
if ignore {
ReduceResult::Ignore
} else if collapse {
ReduceResult::CollapseToObject
} else {
ReduceResult::KeepIf(!literals.is_empty())
}
literals.retain(|literal| should_retain_type(Type::IntLiteral(*literal)));
!literals.is_empty()
} else {
ReduceResult::KeepIf(
!Type::IntLiteral(literals[0]).is_subtype_of(db, other_type),
)
!Type::IntLiteral(literals[0]).is_subtype_of(db, other_type)
}
}
UnionElement::StringLiterals(literals) => {
if other_type.splits_literals(db, LiteralKind::String) {
let mut collapse = false;
let mut ignore = false;
let negated = other_type.negate(db);
literals.retain(|literal| {
let ty = Type::StringLiteral(*literal);
if negated.is_subtype_of(db, ty) {
collapse = true;
}
if other_type.is_subtype_of(db, ty) {
ignore = true;
}
!ty.is_subtype_of(db, other_type)
});
if ignore {
ReduceResult::Ignore
} else if collapse {
ReduceResult::CollapseToObject
} else {
ReduceResult::KeepIf(!literals.is_empty())
}
literals.retain(|literal| should_retain_type(Type::StringLiteral(*literal)));
!literals.is_empty()
} else {
ReduceResult::KeepIf(
!Type::StringLiteral(literals[0]).is_subtype_of(db, other_type),
)
!Type::StringLiteral(literals[0]).is_subtype_of(db, other_type)
}
}
UnionElement::BytesLiterals(literals) => {
if other_type.splits_literals(db, LiteralKind::Bytes) {
let mut collapse = false;
let mut ignore = false;
let negated = other_type.negate(db);
literals.retain(|literal| {
let ty = Type::BytesLiteral(*literal);
if negated.is_subtype_of(db, ty) {
collapse = true;
}
if other_type.is_subtype_of(db, ty) {
ignore = true;
}
!ty.is_subtype_of(db, other_type)
});
if ignore {
ReduceResult::Ignore
} else if collapse {
ReduceResult::CollapseToObject
} else {
ReduceResult::KeepIf(!literals.is_empty())
}
literals.retain(|literal| should_retain_type(Type::BytesLiteral(*literal)));
!literals.is_empty()
} else {
ReduceResult::KeepIf(
!Type::BytesLiteral(literals[0]).is_subtype_of(db, other_type),
)
!Type::BytesLiteral(literals[0]).is_subtype_of(db, other_type)
}
}
UnionElement::Type(existing) => ReduceResult::Type(*existing),
UnionElement::Type(existing) => return ReduceResult::Type(*existing),
};
if ignore {
ReduceResult::Ignore
} else if collapse {
ReduceResult::CollapseToObject
} else {
ReduceResult::KeepIf(should_keep)
}
}
}
@@ -960,7 +945,7 @@ impl<'db> IntersectionBuilder<'db> {
#[derive(Debug, Clone, Default)]
struct InnerIntersectionBuilder<'db> {
positive: FxOrderSet<Type<'db>>,
negative: FxOrderSet<Type<'db>>,
negative: NegativeIntersectionElements<'db>,
}
impl<'db> InnerIntersectionBuilder<'db> {

View File

@@ -1226,7 +1226,10 @@ impl<'db> Bindings<'db> {
else {
return;
};
let constraints = ConstraintSet::range(db, *lower, *typevar, *upper);
// XXX: Replace this with lower, upper, and equality
let lower = ConstraintSet::lower_bound_constraint(db, *typevar, *lower);
let upper = ConstraintSet::upper_bound_constraint(db, *typevar, *upper);
let constraints = lower.and(db, || upper);
let tracked = TrackedConstraintSet::new(db, constraints);
overload.set_return_type(Type::KnownInstance(
KnownInstanceType::ConstraintSet(tracked),

View File

@@ -30,6 +30,9 @@ use crate::types::generics::{
};
use crate::types::infer::{infer_expression_type, infer_unpack_types, nearest_enclosing_class};
use crate::types::member::{Member, class_member};
use crate::types::relation::{
HasRelationToVisitor, IsDisjointVisitor, IsEquivalentVisitor, TypeRelation,
};
use crate::types::signatures::{CallableSignature, Parameter, Parameters, Signature};
use crate::types::tuple::{TupleSpec, TupleType};
use crate::types::typed_dict::typed_dict_params_from_class_def;
@@ -37,11 +40,10 @@ use crate::types::visitor::{TypeCollector, TypeVisitor, walk_type_with_recursion
use crate::types::{
ApplyTypeMappingVisitor, Binding, BindingContext, BoundSuperType, CallableType,
CallableTypeKind, CallableTypes, DATACLASS_FLAGS, DataclassFlags, DataclassParams,
DeprecatedInstance, FindLegacyTypeVarsVisitor, HasRelationToVisitor, IntersectionType,
IsDisjointVisitor, IsEquivalentVisitor, KnownInstanceType, ManualPEP695TypeAliasType,
MaterializationKind, NormalizedVisitor, PropertyInstanceType, TypeAliasType, TypeContext,
TypeMapping, TypeRelation, TypedDictParams, UnionBuilder, VarianceInferable, binding_type,
declaration_type, determine_upper_bound,
DeprecatedInstance, FindLegacyTypeVarsVisitor, IntersectionType, KnownInstanceType,
ManualPEP695TypeAliasType, MaterializationKind, NormalizedVisitor, PropertyInstanceType,
TypeAliasType, TypeContext, TypeMapping, TypedDictParams, UnionBuilder, VarianceInferable,
binding_type, declaration_type, determine_upper_bound,
};
use crate::{
Db, FxIndexMap, FxIndexSet, FxOrderSet, Program,

File diff suppressed because it is too large Load Diff

View File

@@ -57,6 +57,7 @@ use ruff_db::files::{File, FileRange};
use ruff_db::parsed::{ParsedModuleRef, parsed_module};
use ruff_python_ast::{self as ast, ParameterWithDefault};
use ruff_text_size::Ranged;
use ty_module_resolver::{KnownModule, ModuleName, file_to_module, resolve_module};
use crate::place::{Definedness, Place, place_from_bindings};
use crate::semantic_index::ast_ids::HasScopedUseId;
@@ -76,18 +77,19 @@ use crate::types::generics::{GenericContext, InferableTypeVars, typing_self};
use crate::types::infer::nearest_enclosing_class;
use crate::types::list_members::all_members;
use crate::types::narrow::ClassInfoConstraintFunction;
use crate::types::relation::{
HasRelationToVisitor, IsDisjointVisitor, IsEquivalentVisitor, TypeRelation,
};
use crate::types::signatures::{CallableSignature, Signature};
use crate::types::visitor::any_over_type;
use crate::types::{
ApplyTypeMappingVisitor, BoundMethodType, BoundTypeVarInstance, CallableType, CallableTypeKind,
ClassBase, ClassLiteral, ClassType, DeprecatedInstance, DynamicType, FindLegacyTypeVarsVisitor,
HasRelationToVisitor, IsDisjointVisitor, IsEquivalentVisitor, KnownClass, KnownInstanceType,
NormalizedVisitor, SpecialFormType, SubclassOfInner, SubclassOfType, Truthiness, Type,
TypeContext, TypeMapping, TypeRelation, TypeVarBoundOrConstraints, UnionBuilder, binding_type,
definition_expression_type, infer_definition_types, walk_signature,
KnownClass, KnownInstanceType, NormalizedVisitor, SpecialFormType, SubclassOfInner,
SubclassOfType, Truthiness, Type, TypeContext, TypeMapping, TypeVarBoundOrConstraints,
UnionBuilder, binding_type, definition_expression_type, infer_definition_types, walk_signature,
};
use crate::{Db, FxOrderSet};
use ty_module_resolver::{KnownModule, ModuleName, file_to_module, resolve_module};
/// A collection of useful spans for annotating functions.
///

View File

@@ -12,19 +12,21 @@ use crate::semantic_index::scope::{FileScopeId, NodeWithScopeKind, ScopeId};
use crate::semantic_index::{SemanticIndex, semantic_index};
use crate::types::class::ClassType;
use crate::types::class_base::ClassBase;
use crate::types::constraints::{ConstraintSet, IteratorConstraintsExtension};
use crate::types::constraints::{ConstraintBound, ConstraintSet, IteratorConstraintsExtension};
use crate::types::instance::{Protocol, ProtocolInstanceType};
use crate::types::relation::{
HasRelationToVisitor, IsDisjointVisitor, IsEquivalentVisitor, TypeRelation,
};
use crate::types::signatures::Parameters;
use crate::types::tuple::{TupleSpec, TupleType, walk_tuple_type};
use crate::types::variance::VarianceInferable;
use crate::types::visitor::{TypeCollector, TypeVisitor, walk_type_with_recursion_guard};
use crate::types::{
ApplyTypeMappingVisitor, BindingContext, BoundTypeVarIdentity, BoundTypeVarInstance,
ClassLiteral, FindLegacyTypeVarsVisitor, HasRelationToVisitor, IntersectionType,
IsDisjointVisitor, IsEquivalentVisitor, KnownClass, KnownInstanceType, MaterializationKind,
NormalizedVisitor, Type, TypeContext, TypeMapping, TypeRelation, TypeVarBoundOrConstraints,
TypeVarIdentity, TypeVarInstance, TypeVarKind, TypeVarVariance, UnionType, declaration_type,
walk_type_var_bounds,
ClassLiteral, FindLegacyTypeVarsVisitor, IntersectionType, KnownClass, KnownInstanceType,
MaterializationKind, NormalizedVisitor, Type, TypeContext, TypeMapping,
TypeVarBoundOrConstraints, TypeVarIdentity, TypeVarInstance, TypeVarKind, TypeVarVariance,
UnionType, declaration_type, walk_type_var_bounds,
};
use crate::{Db, FxOrderMap, FxOrderSet};
@@ -202,7 +204,7 @@ impl<'a, 'db> InferableTypeVars<'a, 'db> {
/// # Ordering
/// Ordering is based on the context's salsa-assigned id and not on its values.
/// The id may change between runs, or when the context was garbage collected and recreated.
#[salsa::interned(debug, constructor=new_internal, heap_size=GenericContext::heap_size)]
#[salsa::interned(debug, constructor=new_internal, heap_size=ruff_memory_usage::heap_size)]
#[derive(PartialOrd, Ord)]
pub struct GenericContext<'db> {
#[returns(ref)]
@@ -689,12 +691,6 @@ impl<'db> GenericContext<'db> {
Self::from_typevar_instances(db, variables)
}
fn heap_size(
(variables,): &(FxOrderMap<BoundTypeVarIdentity<'db>, BoundTypeVarInstance<'db>>,),
) -> usize {
ruff_memory_usage::order_map_heap_size(variables)
}
}
fn inferable_typevars_cycle_initial<'db>(
@@ -1647,20 +1643,23 @@ impl<'db> SpecializationBuilder<'db> {
mappings.clear();
for (constraint, _) in path {
let typevar = constraint.typevar(self.db);
let lower = constraint.lower(self.db);
let upper = constraint.upper(self.db);
let bound = constraint.bound(self.db);
let bounds = mappings.entry(typevar).or_default();
bounds.lower.insert(lower);
bounds.upper.insert(upper);
if let Type::TypeVar(lower_bound_typevar) = lower {
let bounds = mappings.entry(lower_bound_typevar).or_default();
bounds.upper.insert(Type::TypeVar(typevar));
}
if let Type::TypeVar(upper_bound_typevar) = upper {
let bounds = mappings.entry(upper_bound_typevar).or_default();
bounds.lower.insert(Type::TypeVar(typevar));
match bound {
ConstraintBound::Lower(lower) => {
bounds.lower.insert(lower);
if let Type::TypeVar(lower_bound_typevar) = lower {
let bounds = mappings.entry(lower_bound_typevar).or_default();
bounds.upper.insert(Type::TypeVar(typevar));
}
}
ConstraintBound::Upper(upper) => {
bounds.upper.insert(upper);
if let Type::TypeVar(upper_bound_typevar) = upper {
let bounds = mappings.entry(upper_bound_typevar).or_default();
bounds.lower.insert(Type::TypeVar(typevar));
}
}
}
}

View File

@@ -2797,6 +2797,8 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
body: _,
} = class_node;
let mut decorator_types_and_nodes: Vec<(Type<'db>, &ast::Decorator)> =
Vec::with_capacity(decorator_list.len());
let mut deprecated = None;
let mut type_check_only = false;
let mut dataclass_params = None;
@@ -2831,6 +2833,20 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
continue;
}
// Skip identity decorators to avoid salsa cycles on typeshed.
if decorator_ty.as_function_literal().is_some_and(|function| {
matches!(
function.known(self.db()),
Some(
KnownFunction::Final
| KnownFunction::DisjointBase
| KnownFunction::RuntimeCheckable
)
)
}) {
continue;
}
if let Type::FunctionLiteral(f) = decorator_ty {
// We do not yet detect or flag `@dataclass_transform` applied to more than one
// overload, or an overload and the implementation both. Nevertheless, this is not
@@ -2852,6 +2868,8 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
dataclass_transformer_params = Some(params);
continue;
}
decorator_types_and_nodes.push((decorator_ty, decorator));
}
let body_scope = self
@@ -2868,7 +2886,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
)
};
let ty = match (maybe_known_class, &*name.id) {
let inferred_ty = match (maybe_known_class, &*name.id) {
(None, "NamedTuple") if in_typing_module() => {
Type::SpecialForm(SpecialFormType::NamedTuple)
}
@@ -2885,10 +2903,19 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
)),
};
// Validate decorator calls (but don't use return types yet).
for (decorator_ty, decorator_node) in decorator_types_and_nodes.iter().rev() {
if let Err(CallError(_, bindings)) =
decorator_ty.try_call(self.db(), &CallArguments::positional([inferred_ty]))
{
bindings.report_diagnostics(&self.context, (*decorator_node).into());
}
}
self.add_declaration_with_binding(
class_node.into(),
definition,
&DeclaredAndInferredType::are_the_same_type(ty),
&DeclaredAndInferredType::are_the_same_type(inferred_ty),
);
// if there are type parameters, then the keywords and bases are within that scope

View File

@@ -1083,13 +1083,9 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
&mut self.inner_expression_inference_state,
InnerExpressionInferenceState::Get,
);
let union = union
.elements(self.db())
.iter()
.fold(UnionBuilder::new(self.db()), |builder, elem| {
builder.add(self.infer_subscript_type_expression(subscript, *elem))
})
.build();
let union = union.map(self.db(), |element| {
self.infer_subscript_type_expression(subscript, *element)
});
self.inner_expression_inference_state = previous_slice_inference_state;
union
}

View File

@@ -11,14 +11,15 @@ use crate::types::constraints::{ConstraintSet, IteratorConstraintsExtension};
use crate::types::enums::is_single_member_enum;
use crate::types::generics::{InferableTypeVars, walk_specialization};
use crate::types::protocol_class::{ProtocolClass, walk_protocol_interface};
use crate::types::relation::{
HasRelationToVisitor, IsDisjointVisitor, IsEquivalentVisitor, TypeRelation,
};
use crate::types::tuple::{TupleSpec, TupleType, walk_tuple_type};
use crate::types::{
ApplyTypeMappingVisitor, ClassBase, ClassLiteral, FindLegacyTypeVarsVisitor,
HasRelationToVisitor, IsDisjointVisitor, IsEquivalentVisitor, NormalizedVisitor, TypeContext,
TypeMapping, TypeRelation, VarianceInferable,
ApplyTypeMappingVisitor, ClassBase, ClassLiteral, FindLegacyTypeVarsVisitor, NormalizedVisitor,
TypeContext, TypeMapping, VarianceInferable,
};
use crate::{Db, FxOrderSet};
pub(super) use synthesized_protocol::SynthesizedProtocolType;
impl<'db> Type<'db> {

View File

@@ -926,10 +926,7 @@ impl<'db, 'ast> NarrowingConstraintsBuilder<'db, 'ast> {
.build();
// Keep order: first literal complement, then broader arms.
let result = UnionBuilder::new(self.db)
.add(narrowed_single)
.add(rest_union)
.build();
let result = UnionType::from_elements(self.db, [narrowed_single, rest_union]);
Some(result)
} else {
None

View File

@@ -6,6 +6,7 @@ use itertools::Itertools;
use ruff_python_ast::name::Name;
use rustc_hash::FxHashMap;
use crate::types::relation::{HasRelationToVisitor, IsDisjointVisitor, TypeRelation};
use crate::types::{CallableTypeKind, TypeContext};
use crate::{
Db, FxOrderSet,
@@ -13,10 +14,9 @@ use crate::{
semantic_index::{definition::Definition, place::ScopedPlaceId, place_table, use_def_map},
types::{
ApplyTypeMappingVisitor, BoundTypeVarInstance, CallableType, ClassBase, ClassLiteral,
ClassType, FindLegacyTypeVarsVisitor, HasRelationToVisitor,
InstanceFallbackShadowsNonDataDescriptor, IsDisjointVisitor, KnownFunction,
MemberLookupPolicy, NormalizedVisitor, PropertyInstanceType, Signature, Type, TypeMapping,
TypeQualifiers, TypeRelation, TypeVarVariance, VarianceInferable,
ClassType, FindLegacyTypeVarsVisitor, InstanceFallbackShadowsNonDataDescriptor,
KnownFunction, MemberLookupPolicy, NormalizedVisitor, PropertyInstanceType, Signature,
Type, TypeMapping, TypeQualifiers, TypeVarVariance, VarianceInferable,
constraints::{ConstraintSet, IteratorConstraintsExtension, OptionConstraintsExtension},
context::InferContext,
diagnostic::report_undeclared_protocol_member,

File diff suppressed because it is too large Load Diff

View File

@@ -23,11 +23,13 @@ use crate::types::constraints::{
};
use crate::types::generics::{GenericContext, InferableTypeVars, walk_generic_context};
use crate::types::infer::{infer_deferred_types, infer_scope_types};
use crate::types::relation::{
HasRelationToVisitor, IsDisjointVisitor, IsEquivalentVisitor, TypeRelation,
};
use crate::types::{
ApplyTypeMappingVisitor, BindingContext, BoundTypeVarInstance, CallableType, CallableTypeKind,
FindLegacyTypeVarsVisitor, HasRelationToVisitor, IsDisjointVisitor, IsEquivalentVisitor,
KnownClass, MaterializationKind, NormalizedVisitor, ParamSpecAttrKind, TypeContext,
TypeMapping, TypeRelation, VarianceInferable, todo_type,
FindLegacyTypeVarsVisitor, KnownClass, MaterializationKind, NormalizedVisitor,
ParamSpecAttrKind, TypeContext, TypeMapping, VarianceInferable, todo_type,
};
use crate::{Db, FxOrderSet};
use ruff_python_ast::{self as ast, name::Name};
@@ -403,11 +405,10 @@ impl<'db> CallableSignature<'db> {
Some((self_bound_typevar, self_return_type)),
Some((other_bound_typevar, other_return_type)),
) => {
let param_spec_matches = ConstraintSet::constrain_typevar(
let param_spec_matches = ConstraintSet::equality_constraint(
db,
self_bound_typevar,
Type::TypeVar(other_bound_typevar),
Type::TypeVar(other_bound_typevar),
);
let return_types_match = self_return_type.zip(other_return_type).when_some_and(
|(self_return_type, other_return_type)| {
@@ -433,12 +434,8 @@ impl<'db> CallableSignature<'db> {
)),
CallableTypeKind::ParamSpecValue,
));
let param_spec_matches = ConstraintSet::constrain_typevar(
db,
self_bound_typevar,
Type::Never,
upper,
);
let param_spec_matches =
ConstraintSet::upper_bound_constraint(db, self_bound_typevar, upper);
let return_types_match = self_return_type.when_some_and(|self_return_type| {
other_signatures
.iter()
@@ -466,12 +463,8 @@ impl<'db> CallableSignature<'db> {
)),
CallableTypeKind::ParamSpecValue,
));
let param_spec_matches = ConstraintSet::constrain_typevar(
db,
other_bound_typevar,
lower,
Type::object(),
);
let param_spec_matches =
ConstraintSet::lower_bound_constraint(db, other_bound_typevar, lower);
let return_types_match = other_return_type.when_some_and(|other_return_type| {
self_signatures
.iter()
@@ -1118,7 +1111,7 @@ impl<'db> Signature<'db> {
CallableTypeKind::ParamSpecValue,
));
let param_spec_matches =
ConstraintSet::constrain_typevar(db, self_bound_typevar, Type::Never, upper);
ConstraintSet::upper_bound_constraint(db, self_bound_typevar, upper);
let return_types_match = self.return_ty.when_some_and(|self_return_type| {
other
.overloads
@@ -1360,11 +1353,10 @@ impl<'db> Signature<'db> {
// the other signature.
match (self_is_paramspec, other_is_paramspec) {
(Some(self_bound_typevar), Some(other_bound_typevar)) => {
let param_spec_matches = ConstraintSet::constrain_typevar(
let param_spec_matches = ConstraintSet::equality_constraint(
db,
self_bound_typevar,
Type::TypeVar(other_bound_typevar),
Type::TypeVar(other_bound_typevar),
);
result.intersect(db, param_spec_matches);
return result;
@@ -1376,12 +1368,8 @@ impl<'db> Signature<'db> {
CallableSignature::single(Signature::new(other.parameters.clone(), None)),
CallableTypeKind::ParamSpecValue,
));
let param_spec_matches = ConstraintSet::constrain_typevar(
db,
self_bound_typevar,
Type::Never,
upper,
);
let param_spec_matches =
ConstraintSet::upper_bound_constraint(db, self_bound_typevar, upper);
result.intersect(db, param_spec_matches);
return result;
}
@@ -1392,12 +1380,8 @@ impl<'db> Signature<'db> {
CallableSignature::single(Signature::new(self.parameters.clone(), None)),
CallableTypeKind::ParamSpecValue,
));
let param_spec_matches = ConstraintSet::constrain_typevar(
db,
other_bound_typevar,
lower,
Type::object(),
);
let param_spec_matches =
ConstraintSet::lower_bound_constraint(db, other_bound_typevar, lower);
result.intersect(db, param_spec_matches);
return result;
}
@@ -1860,7 +1844,7 @@ impl<'db> Parameters<'db> {
///
/// Internally, this is represented as `(*Any, **Any)` that accepts parameters of type [`Any`].
///
/// [`Any`]: crate::types::DynamicType::Any
/// [`Any`]: DynamicType::Any
pub(crate) fn gradual_form() -> Self {
Self {
value: vec![

View File

@@ -1,19 +1,19 @@
use super::TypeVarVariance;
use crate::place::PlaceAndQualifiers;
use crate::semantic_index::definition::Definition;
use crate::types::constraints::ConstraintSet;
use crate::types::generics::InferableTypeVars;
use crate::types::protocol_class::ProtocolClass;
use crate::types::relation::{HasRelationToVisitor, IsDisjointVisitor, TypeRelation};
use crate::types::variance::VarianceInferable;
use crate::types::{
ApplyTypeMappingVisitor, BoundTypeVarInstance, ClassType, DynamicType,
FindLegacyTypeVarsVisitor, HasRelationToVisitor, IsDisjointVisitor, KnownClass,
MaterializationKind, MemberLookupPolicy, NormalizedVisitor, SpecialFormType, Type, TypeContext,
TypeMapping, TypeRelation, TypeVarBoundOrConstraints, TypedDictType, UnionType, todo_type,
FindLegacyTypeVarsVisitor, KnownClass, MaterializationKind, MemberLookupPolicy,
NormalizedVisitor, SpecialFormType, Type, TypeContext, TypeMapping, TypeVarBoundOrConstraints,
TypedDictType, UnionType, todo_type,
};
use crate::{Db, FxOrderSet};
use super::TypeVarVariance;
/// A type that represents `type[C]`, i.e. the class object `C` and class objects that are subclasses of `C`.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, salsa::Update, get_size2::GetSize)]
pub struct SubclassOfType<'db> {

View File

@@ -28,10 +28,12 @@ use crate::types::builder::RecursivelyDefined;
use crate::types::class::{ClassType, KnownClass};
use crate::types::constraints::{ConstraintSet, IteratorConstraintsExtension};
use crate::types::generics::InferableTypeVars;
use crate::types::relation::{
HasRelationToVisitor, IsDisjointVisitor, IsEquivalentVisitor, TypeRelation,
};
use crate::types::{
ApplyTypeMappingVisitor, BoundTypeVarInstance, FindLegacyTypeVarsVisitor, HasRelationToVisitor,
IntersectionType, IsDisjointVisitor, IsEquivalentVisitor, NormalizedVisitor, Type, TypeMapping,
TypeRelation, UnionBuilder, UnionType,
ApplyTypeMappingVisitor, BoundTypeVarInstance, FindLegacyTypeVarsVisitor, IntersectionType,
NormalizedVisitor, Type, TypeMapping, UnionBuilder, UnionType,
};
use crate::types::{Truthiness, TypeContext};
use crate::{Db, FxOrderSet, Program};

View File

@@ -3,6 +3,7 @@ use std::collections::BTreeMap;
use std::ops::{Deref, DerefMut};
use bitflags::bitflags;
use ordermap::OrderSet;
use ruff_db::diagnostic::{Annotation, Diagnostic, Span, SubDiagnostic, SubDiagnosticSeverity};
use ruff_db::parsed::parsed_module;
use ruff_python_ast::Arguments;
@@ -21,12 +22,10 @@ use crate::semantic_index::definition::Definition;
use crate::types::class::FieldKind;
use crate::types::constraints::{ConstraintSet, IteratorConstraintsExtension};
use crate::types::generics::InferableTypeVars;
use crate::types::{
HasRelationToVisitor, IsDisjointVisitor, IsEquivalentVisitor, NormalizedVisitor, TypeContext,
TypeRelation,
use crate::types::relation::{
HasRelationToVisitor, IsDisjointVisitor, IsEquivalentVisitor, TypeRelation,
};
use ordermap::OrderSet;
use crate::types::{NormalizedVisitor, TypeContext};
bitflags! {
/// Used for `TypedDict` class parameters.

View File

@@ -1,5 +1,7 @@
use rustc_hash::FxHashSet;
use crate::{
Db, FxIndexSet,
Db,
types::{
BoundMethodType, BoundSuperType, BoundTypeVarInstance, CallableType, GenericAlias,
IntersectionType, KnownBoundMethodType, KnownInstanceType, NominalInstanceType,
@@ -277,7 +279,7 @@ pub(crate) fn walk_type_with_recursion_guard<'db>(
}
#[derive(Default, Debug)]
pub(crate) struct TypeCollector<'db>(RefCell<FxIndexSet<Type<'db>>>);
pub(crate) struct TypeCollector<'db>(RefCell<FxHashSet<Type<'db>>>);
impl<'db> TypeCollector<'db> {
pub(crate) fn type_was_already_seen(&self, ty: Type<'db>) -> bool {

View File

@@ -118,9 +118,9 @@
}
},
"node_modules/@cloudflare/workers-types": {
"version": "4.20251225.0",
"resolved": "https://registry.npmjs.org/@cloudflare/workers-types/-/workers-types-4.20251225.0.tgz",
"integrity": "sha512-ZZl0cNLFcsBRFKtMftKWOsfAybUYSeiTMzpQV1NlTVlByHAs1rGQt45Jw/qz8LrfHoq9PGTieSj9W350Gi4Pvg==",
"version": "4.20251229.0",
"resolved": "https://registry.npmjs.org/@cloudflare/workers-types/-/workers-types-4.20251229.0.tgz",
"integrity": "sha512-LgzxDZaT9bQhycQInf7S/fcZCQRTvWWQPE9xnEyedI+CXxWsXAD7hg84kvVyr+KUz+W9Oblzo75g6XZ3HdI5Yg==",
"dev": true,
"license": "MIT OR Apache-2.0",
"peer": true
@@ -1814,6 +1814,7 @@
"dev": true,
"hasInstallScript": true,
"license": "Apache-2.0",
"peer": true,
"bin": {
"workerd": "bin/workerd"
},

32
ruff.schema.json generated
View File

@@ -1880,6 +1880,38 @@
"object",
"null"
],
"properties": {
"first-party": {
"type": "array",
"items": {
"type": "string"
}
},
"future": {
"type": "array",
"items": {
"type": "string"
}
},
"local-folder": {
"type": "array",
"items": {
"type": "string"
}
},
"standard-library": {
"type": "array",
"items": {
"type": "string"
}
},
"third-party": {
"type": "array",
"items": {
"type": "string"
}
}
},
"additionalProperties": {
"type": "array",
"items": {

View File

@@ -18,7 +18,7 @@
set -eu
docstring_adder="git+https://github.com/astral-sh/docstring-adder.git@1a0fb336fdc85014b22daeb34c862b695aef07d4"
docstring_adder="git+https://github.com/astral-sh/docstring-adder.git@e98a04941d5a6b8b9240e40392de15990b8cb8be"
stdlib_path="./crates/ty_vendored/vendor/typeshed/stdlib"
for python_version in 3.14 3.13 3.12 3.11 3.10 3.9

View File

@@ -92,7 +92,7 @@ class Project(NamedTuple):
)
except subprocess.CalledProcessError as e:
raise RuntimeError(f"Failed to clone {self.name}: {e.stderr}")
raise RuntimeError(f"Failed to clone {self.name}:\n\n{e.stderr}") from e
logging.info(f"Cloned {self.name} to {checkout_dir}.")

View File

@@ -147,7 +147,9 @@ def main() -> None:
cwd = Path(tempdir)
project.clone(cwd)
venv = Venv.create(cwd, project.python_version)
venv = Venv.create(
project=project.name, parent=cwd, python_version=project.python_version
)
venv.install(project.install_arguments)
commands = []

View File

@@ -42,7 +42,9 @@ def project_setup(
cwd = Path(tempdir)
project.clone(cwd)
venv = Venv.create(cwd, project.python_version)
venv = Venv.create(
project=project.name, parent=cwd, python_version=project.python_version
)
venv.install(project.install_arguments)
yield project, venv

View File

@@ -3,15 +3,15 @@ from __future__ import annotations
import logging
import subprocess
import sys
from dataclasses import dataclass
from pathlib import Path
@dataclass(frozen=True, kw_only=True, slots=True)
class Venv:
project_name: str
project_path: Path
def __init__(self, path: Path):
self.project_path = path
@property
def path(self) -> Path:
return self.project_path / "venv"
@@ -36,7 +36,7 @@ class Venv:
return self.bin / f"{name}{extension}"
@staticmethod
def create(parent: Path, python_version: str) -> Venv:
def create(*, project: str, parent: Path, python_version: str) -> Venv:
"""Creates a new, empty virtual environment."""
command = [
@@ -53,9 +53,10 @@ class Venv:
command, cwd=parent, check=True, capture_output=True, text=True
)
except subprocess.CalledProcessError as e:
raise RuntimeError(f"Failed to create venv: {e.stderr}")
msg = f"Failed to create venv for {project}:\n\n{e.stderr}"
raise RuntimeError(msg) from e
return Venv(parent)
return Venv(project_name=project, project_path=parent)
def install(self, pip_install_args: list[str]) -> None:
"""Installs the dependencies required to type check the project."""
@@ -87,4 +88,7 @@ class Venv:
text=True,
)
except subprocess.CalledProcessError as e:
raise RuntimeError(f"Failed to install dependencies: {e.stderr}")
msg = (
f"Failed to install dependencies for {self.project_name}:\n\n{e.stderr}"
)
raise RuntimeError(msg) from e