Compare commits
13 Commits
alex/optim
...
charlie/su
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fd02fb9c76 | ||
|
|
92a2f2c992 | ||
|
|
11b551c2be | ||
|
|
b85c0190c5 | ||
|
|
46a4bfc478 | ||
|
|
0c53395917 | ||
|
|
8464aca795 | ||
|
|
e1439beab2 | ||
|
|
fd86e699b5 | ||
|
|
d0f841bff2 | ||
|
|
74978cfff2 | ||
|
|
10a417aaf6 | ||
|
|
a2e0ff57c3 |
64
CLAUDE.md
Normal file
64
CLAUDE.md
Normal 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.
|
||||
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -3246,7 +3246,6 @@ name = "ruff_memory_usage"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"get-size2",
|
||||
"ordermap",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
43
Cargo.toml
43
Cargo.toml
@@ -58,8 +58,8 @@ anstream = { version = "0.6.18" }
|
||||
anstyle = { version = "1.0.10" }
|
||||
anyhow = { version = "1.0.80" }
|
||||
arc-swap = { version = "1.7.1" }
|
||||
assert_fs = { version = "1.1.0" }
|
||||
argfile = { version = "0.2.0" }
|
||||
assert_fs = { version = "1.1.0" }
|
||||
bincode = { version = "2.0.0" }
|
||||
bitflags = { version = "2.5.0" }
|
||||
bitvec = { version = "1.0.1", default-features = false, features = [
|
||||
@@ -71,30 +71,31 @@ camino = { version = "1.1.7" }
|
||||
clap = { version = "4.5.3", features = ["derive"] }
|
||||
clap_complete_command = { version = "0.6.0" }
|
||||
clearscreen = { version = "4.0.0" }
|
||||
csv = { version = "1.3.1" }
|
||||
divan = { package = "codspeed-divan-compat", version = "4.0.4" }
|
||||
codspeed-criterion-compat = { version = "4.0.4", default-features = false }
|
||||
colored = { version = "3.0.0" }
|
||||
compact_str = "0.9.0"
|
||||
console_error_panic_hook = { version = "0.1.7" }
|
||||
console_log = { version = "1.0.0" }
|
||||
countme = { version = "3.0.1" }
|
||||
compact_str = "0.9.0"
|
||||
criterion = { version = "0.8.0", default-features = false }
|
||||
crossbeam = { version = "0.8.4" }
|
||||
csv = { version = "1.3.1" }
|
||||
dashmap = { version = "6.0.1" }
|
||||
datatest-stable = { version = "0.3.3" }
|
||||
dunce = { version = "1.0.5" }
|
||||
divan = { package = "codspeed-divan-compat", version = "4.0.4" }
|
||||
drop_bomb = { version = "0.1.5" }
|
||||
dunce = { version = "1.0.5" }
|
||||
etcetera = { version = "0.11.0" }
|
||||
fern = { version = "0.7.0" }
|
||||
filetime = { version = "0.2.23" }
|
||||
getrandom = { version = "0.3.1" }
|
||||
get-size2 = { version = "0.7.3", features = [
|
||||
"derive",
|
||||
"smallvec",
|
||||
"hashbrown",
|
||||
"compact-str",
|
||||
"ordermap"
|
||||
] }
|
||||
getrandom = { version = "0.3.1" }
|
||||
glob = { version = "0.3.1" }
|
||||
globset = { version = "0.4.14" }
|
||||
globwalk = { version = "0.9.1" }
|
||||
@@ -116,8 +117,8 @@ is-macro = { version = "0.3.5" }
|
||||
is-wsl = { version = "0.4.0" }
|
||||
itertools = { version = "0.14.0" }
|
||||
jiff = { version = "0.2.0" }
|
||||
js-sys = { version = "0.3.69" }
|
||||
jod-thread = { version = "1.0.0" }
|
||||
js-sys = { version = "0.3.69" }
|
||||
libc = { version = "0.2.153" }
|
||||
libcst = { version = "1.8.4", default-features = false }
|
||||
log = { version = "0.4.17" }
|
||||
@@ -138,9 +139,9 @@ pep440_rs = { version = "0.7.1" }
|
||||
pretty_assertions = "1.3.0"
|
||||
proc-macro2 = { version = "1.0.79" }
|
||||
pyproject-toml = { version = "0.13.4" }
|
||||
quickcheck = { version = "1.0.3", default-features = false}
|
||||
quickcheck_macros = { version = "1.0.0" }
|
||||
quick-junit = { version = "0.5.0" }
|
||||
quickcheck = { version = "1.0.3", default-features = false }
|
||||
quickcheck_macros = { version = "1.0.0" }
|
||||
quote = { version = "1.0.23" }
|
||||
rand = { version = "0.9.0" }
|
||||
rayon = { version = "1.10.0" }
|
||||
@@ -197,9 +198,9 @@ tryfn = { version = "0.2.1" }
|
||||
typed-arena = { version = "2.0.2" }
|
||||
unic-ucd-category = { version = "0.9" }
|
||||
unicode-ident = { version = "1.0.12" }
|
||||
unicode-normalization = { version = "0.1.23" }
|
||||
unicode-width = { version = "0.2.0" }
|
||||
unicode_names2 = { version = "1.2.2" }
|
||||
unicode-normalization = { version = "0.1.23" }
|
||||
url = { version = "2.5.0" }
|
||||
uuid = { version = "1.6.1", features = ["v4", "fast-rng", "macro-diagnostics"] }
|
||||
walkdir = { version = "2.3.2" }
|
||||
@@ -209,8 +210,13 @@ wild = { version = "2" }
|
||||
zip = { version = "0.6.6", default-features = false }
|
||||
|
||||
[workspace.metadata.cargo-shear]
|
||||
ignored = ["getrandom", "ruff_options_metadata", "uuid", "get-size2", "ty_completion_eval"]
|
||||
|
||||
ignored = [
|
||||
"getrandom",
|
||||
"ruff_options_metadata",
|
||||
"uuid",
|
||||
"get-size2",
|
||||
"ty_completion_eval",
|
||||
]
|
||||
|
||||
[workspace.lints.rust]
|
||||
unsafe_code = "warn"
|
||||
@@ -270,17 +276,10 @@ if_not_else = "allow"
|
||||
# Diagnostics are not actionable: Enable once https://github.com/rust-lang/rust-clippy/issues/13774 is resolved.
|
||||
large_stack_arrays = "allow"
|
||||
|
||||
|
||||
[profile.release]
|
||||
lto = "fat"
|
||||
codegen-units = 16
|
||||
|
||||
# Profile to build a minimally sized binary for ruff/ty
|
||||
[profile.minimal-size]
|
||||
inherits = "release"
|
||||
opt-level = "z"
|
||||
codegen-units = 1
|
||||
|
||||
# Some crates don't change as much but benefit more from
|
||||
# more expensive optimization passes, so we selectively
|
||||
# decrease codegen-units in some cases.
|
||||
@@ -291,6 +290,12 @@ codegen-units = 1
|
||||
[profile.release.package.salsa]
|
||||
codegen-units = 1
|
||||
|
||||
# Profile to build a minimally sized binary for ruff/ty
|
||||
[profile.minimal-size]
|
||||
inherits = "release"
|
||||
opt-level = "z"
|
||||
codegen-units = 1
|
||||
|
||||
[profile.dev.package.insta]
|
||||
opt-level = 3
|
||||
|
||||
|
||||
@@ -12,6 +12,13 @@ license = { workspace = true }
|
||||
readme = "../../README.md"
|
||||
default-run = "ruff"
|
||||
|
||||
[package.metadata.cargo-shear]
|
||||
# Used via macro expansion.
|
||||
ignored = ["jiff"]
|
||||
|
||||
[package.metadata.dist]
|
||||
dist = true
|
||||
|
||||
[dependencies]
|
||||
ruff_cache = { workspace = true }
|
||||
ruff_db = { workspace = true, default-features = false, features = ["os"] }
|
||||
@@ -61,6 +68,12 @@ tracing = { workspace = true, features = ["log"] }
|
||||
walkdir = { workspace = true }
|
||||
wild = { workspace = true }
|
||||
|
||||
[target.'cfg(all(not(target_os = "windows"), not(target_os = "openbsd"), not(target_os = "aix"), not(target_os = "android"), any(target_arch = "x86_64", target_arch = "aarch64", target_arch = "powerpc64", target_arch = "riscv64")))'.dependencies]
|
||||
tikv-jemallocator = { workspace = true }
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dependencies]
|
||||
mimalloc = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
# Enable test rules during development
|
||||
ruff_linter = { workspace = true, features = ["clap", "test-rules"] }
|
||||
@@ -76,18 +89,5 @@ ruff_python_trivia = { workspace = true }
|
||||
tempfile = { workspace = true }
|
||||
test-case = { workspace = true }
|
||||
|
||||
[package.metadata.cargo-shear]
|
||||
# Used via macro expansion.
|
||||
ignored = ["jiff"]
|
||||
|
||||
[package.metadata.dist]
|
||||
dist = true
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dependencies]
|
||||
mimalloc = { workspace = true }
|
||||
|
||||
[target.'cfg(all(not(target_os = "windows"), not(target_os = "openbsd"), not(target_os = "aix"), not(target_os = "android"), any(target_arch = "x86_64", target_arch = "aarch64", target_arch = "powerpc64", target_arch = "riscv64")))'.dependencies]
|
||||
tikv-jemallocator = { workspace = true }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
@@ -12,10 +12,6 @@ license = "MIT OR Apache-2.0"
|
||||
|
||||
[lib]
|
||||
|
||||
[features]
|
||||
default = []
|
||||
testing-colors = []
|
||||
|
||||
[dependencies]
|
||||
anstyle = { workspace = true }
|
||||
memchr = { workspace = true }
|
||||
@@ -23,12 +19,17 @@ unicode-width = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
ruff_annotate_snippets = { workspace = true, features = ["testing-colors"] }
|
||||
|
||||
anstream = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
snapbox = { workspace = true, features = ["diff", "term-svg", "cmd", "examples"] }
|
||||
toml = { workspace = true }
|
||||
tryfn = { workspace = true }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
testing-colors = []
|
||||
|
||||
[[test]]
|
||||
name = "fixtures"
|
||||
harness = false
|
||||
|
||||
@@ -16,6 +16,51 @@ bench = false
|
||||
test = false
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
ruff_db = { workspace = true, features = ["testing"] }
|
||||
ruff_linter = { workspace = true, optional = true }
|
||||
ruff_python_ast = { workspace = true }
|
||||
ruff_python_formatter = { workspace = true, optional = true }
|
||||
ruff_python_parser = { workspace = true, optional = true }
|
||||
ruff_python_trivia = { workspace = true, optional = true }
|
||||
ty_project = { workspace = true, optional = true }
|
||||
|
||||
anyhow = { workspace = true }
|
||||
codspeed-criterion-compat = { workspace = true, default-features = false, optional = true }
|
||||
criterion = { workspace = true, default-features = false, optional = true }
|
||||
divan = { workspace = true, optional = true }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
tracing = { 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(target_os = "windows")'.dependencies]
|
||||
mimalloc = { workspace = true, optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
rayon = { workspace = true }
|
||||
rustc-hash = { workspace = true }
|
||||
|
||||
[features]
|
||||
default = ["ty_instrumented", "ty_walltime", "ruff_instrumented"]
|
||||
# Enables the ruff instrumented benchmarks
|
||||
ruff_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"]
|
||||
|
||||
[[bench]]
|
||||
name = "linter"
|
||||
harness = false
|
||||
@@ -46,54 +91,5 @@ name = "ty_walltime"
|
||||
harness = false
|
||||
required-features = ["ty_walltime"]
|
||||
|
||||
[dependencies]
|
||||
ruff_db = { workspace = true, features = ["testing"] }
|
||||
ruff_python_ast = { workspace = true }
|
||||
ruff_linter = { workspace = true, optional = true }
|
||||
ruff_python_formatter = { workspace = true, optional = true }
|
||||
ruff_python_parser = { workspace = true, optional = true }
|
||||
ruff_python_trivia = { workspace = true, optional = true }
|
||||
ty_project = { workspace = true, optional = true }
|
||||
|
||||
divan = { workspace = true, optional = true }
|
||||
anyhow = { workspace = true }
|
||||
codspeed-criterion-compat = { workspace = true, default-features = false, optional = true }
|
||||
criterion = { workspace = true, default-features = false, optional = true }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[features]
|
||||
default = ["ty_instrumented", "ty_walltime", "ruff_instrumented"]
|
||||
# Enables the ruff instrumented benchmarks
|
||||
ruff_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"]
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dependencies]
|
||||
mimalloc = { 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")))'.dependencies]
|
||||
tikv-jemallocator = { workspace = true, optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
rustc-hash = { workspace = true }
|
||||
rayon = { workspace = true }
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -11,11 +11,11 @@ repository = { workspace = true }
|
||||
license = { workspace = true }
|
||||
|
||||
[dependencies]
|
||||
filetime = { workspace = true }
|
||||
glob = { workspace = true }
|
||||
globset = { workspace = true }
|
||||
itertools = { workspace = true }
|
||||
regex = { workspace = true }
|
||||
filetime = { workspace = true }
|
||||
seahash = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
|
||||
@@ -48,12 +48,12 @@ tracing = { workspace = true }
|
||||
tracing-subscriber = { workspace = true, optional = true }
|
||||
zip = { workspace = true }
|
||||
|
||||
[target.'cfg(target_arch="wasm32")'.dependencies]
|
||||
web-time = { version = "1.1.0" }
|
||||
|
||||
[target.'cfg(not(target_arch="wasm32"))'.dependencies]
|
||||
etcetera = { workspace = true, optional = true }
|
||||
|
||||
[target.'cfg(target_arch="wasm32")'.dependencies]
|
||||
web-time = { version = "1.1.0" }
|
||||
|
||||
[dev-dependencies]
|
||||
insta = { workspace = true, features = ["filters"] }
|
||||
tempfile = { workspace = true }
|
||||
|
||||
@@ -11,10 +11,6 @@ repository = { workspace = true }
|
||||
license = { workspace = true }
|
||||
|
||||
[dependencies]
|
||||
ty = { workspace = true }
|
||||
ty_project = { workspace = true, features = ["schemars"] }
|
||||
ty_python_semantic = { workspace = true }
|
||||
ty_static = { workspace = true }
|
||||
ruff = { workspace = true }
|
||||
ruff_formatter = { workspace = true }
|
||||
ruff_linter = { workspace = true, features = ["schemars"] }
|
||||
@@ -26,6 +22,10 @@ ruff_python_formatter = { workspace = true }
|
||||
ruff_python_parser = { workspace = true }
|
||||
ruff_python_trivia = { workspace = true }
|
||||
ruff_workspace = { workspace = true, features = ["schemars"] }
|
||||
ty = { workspace = true }
|
||||
ty_project = { workspace = true, features = ["schemars"] }
|
||||
ty_python_semantic = { workspace = true }
|
||||
ty_static = { workspace = true }
|
||||
|
||||
anyhow = { workspace = true }
|
||||
clap = { workspace = true, features = ["wrap_help"] }
|
||||
|
||||
@@ -10,6 +10,10 @@ documentation = { workspace = true }
|
||||
repository = { workspace = true }
|
||||
license = { workspace = true }
|
||||
|
||||
[package.metadata.cargo-shear]
|
||||
# Used via `CacheKey` macro expansion.
|
||||
ignored = ["ruff_cache"]
|
||||
|
||||
[dependencies]
|
||||
ruff_cache = { workspace = true }
|
||||
ruff_macros = { workspace = true }
|
||||
@@ -25,10 +29,6 @@ unicode-width = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
|
||||
[package.metadata.cargo-shear]
|
||||
# Used via `CacheKey` macro expansion.
|
||||
ignored = ["ruff_cache"]
|
||||
|
||||
[features]
|
||||
serde = ["dep:serde", "ruff_text_size/serde"]
|
||||
schemars = ["dep:schemars", "ruff_text_size/schemars"]
|
||||
|
||||
@@ -9,6 +9,10 @@ repository.workspace = true
|
||||
authors.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[package.metadata.cargo-shear]
|
||||
# Used via `CacheKey` macro expansion.
|
||||
ignored = ["ruff_cache"]
|
||||
|
||||
[dependencies]
|
||||
ruff_cache = { workspace = true }
|
||||
ruff_db = { workspace = true, features = ["os", "serde"] }
|
||||
@@ -29,7 +33,3 @@ zip = { workspace = true, features = [] }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[package.metadata.cargo-shear]
|
||||
# Used via `CacheKey` macro expansion.
|
||||
ignored = ["ruff_cache"]
|
||||
|
||||
@@ -16,17 +16,17 @@ license = { workspace = true }
|
||||
ruff_cache = { workspace = true }
|
||||
ruff_db = { workspace = true, features = ["junit", "serde"] }
|
||||
ruff_diagnostics = { workspace = true, features = ["serde"] }
|
||||
ruff_notebook = { workspace = true }
|
||||
ruff_macros = { workspace = true }
|
||||
ruff_notebook = { workspace = true }
|
||||
ruff_python_ast = { workspace = true, features = ["serde", "cache"] }
|
||||
ruff_python_codegen = { workspace = true }
|
||||
ruff_python_importer = { workspace = true }
|
||||
ruff_python_index = { workspace = true }
|
||||
ruff_python_literal = { workspace = true }
|
||||
ruff_python_parser = { workspace = true }
|
||||
ruff_python_semantic = { workspace = true }
|
||||
ruff_python_stdlib = { workspace = true }
|
||||
ruff_python_trivia = { workspace = true }
|
||||
ruff_python_parser = { workspace = true }
|
||||
ruff_source_file = { workspace = true, features = ["serde"] }
|
||||
ruff_text_size = { workspace = true }
|
||||
|
||||
@@ -44,8 +44,8 @@ imperative = { workspace = true }
|
||||
is-macro = { workspace = true }
|
||||
is-wsl = { workspace = true }
|
||||
itertools = { workspace = true }
|
||||
libcst = { workspace = true }
|
||||
jiff = { workspace = true }
|
||||
libcst = { workspace = true }
|
||||
log = { workspace = true }
|
||||
memchr = { workspace = true }
|
||||
natord = { workspace = true }
|
||||
@@ -67,17 +67,17 @@ strum_macros = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
toml = { workspace = true }
|
||||
typed-arena = { workspace = true }
|
||||
unicode-normalization = { workspace = true }
|
||||
unicode-width = { workspace = true }
|
||||
unicode_names2 = { workspace = true }
|
||||
unicode-normalization = { workspace = true }
|
||||
url = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
insta = { workspace = true, features = ["filters", "json", "redactions"] }
|
||||
test-case = { workspace = true }
|
||||
# Disable colored output in tests
|
||||
colored = { workspace = true, features = ["no-color"] }
|
||||
insta = { workspace = true, features = ["filters", "json", "redactions"] }
|
||||
tempfile = { workspace = true }
|
||||
test-case = { workspace = true }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
use ruff_db::diagnostic::Diagnostic;
|
||||
use ruff_python_ast::name::QualifiedName;
|
||||
use ruff_python_ast::{self as ast, Expr};
|
||||
use ruff_python_semantic::SemanticModel;
|
||||
use ruff_python_semantic::analyze::function_type::is_subject_to_liskov_substitution_principle;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::settings::LinterSettings;
|
||||
@@ -191,3 +193,27 @@ pub(super) fn allow_boolean_trap(call: &ast::ExprCall, checker: &Checker) -> boo
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
pub(super) fn add_liskov_substitution_principle_help(
|
||||
diagnostic: &mut Diagnostic,
|
||||
function_name: &str,
|
||||
decorator_list: &[ast::Decorator],
|
||||
checker: &Checker,
|
||||
) {
|
||||
let semantic = checker.semantic();
|
||||
let parent_scope = semantic.current_scope();
|
||||
let pep8_settings = &checker.settings().pep8_naming;
|
||||
if is_subject_to_liskov_substitution_principle(
|
||||
function_name,
|
||||
decorator_list,
|
||||
parent_scope,
|
||||
semantic,
|
||||
&pep8_settings.classmethod_decorators,
|
||||
&pep8_settings.staticmethod_decorators,
|
||||
) {
|
||||
diagnostic.help(
|
||||
"Consider adding `@typing.override` if changing the function signature \
|
||||
would violate the Liskov Substitution Principle",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,9 @@ use ruff_python_semantic::analyze::visibility;
|
||||
|
||||
use crate::Violation;
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::rules::flake8_boolean_trap::helpers::is_allowed_func_def;
|
||||
use crate::rules::flake8_boolean_trap::helpers::{
|
||||
add_liskov_substitution_principle_help, is_allowed_func_def,
|
||||
};
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for the use of boolean positional arguments in function definitions,
|
||||
@@ -139,7 +141,9 @@ pub(crate) fn boolean_default_value_positional_argument(
|
||||
return;
|
||||
}
|
||||
|
||||
checker.report_diagnostic(BooleanDefaultValuePositionalArgument, param.identifier());
|
||||
let mut diagnostic = checker
|
||||
.report_diagnostic(BooleanDefaultValuePositionalArgument, param.identifier());
|
||||
add_liskov_substitution_principle_help(&mut diagnostic, name, decorator_list, checker);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,9 @@ use ruff_python_semantic::analyze::visibility;
|
||||
|
||||
use crate::Violation;
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::rules::flake8_boolean_trap::helpers::is_allowed_func_def;
|
||||
use crate::rules::flake8_boolean_trap::helpers::{
|
||||
add_liskov_substitution_principle_help, is_allowed_func_def,
|
||||
};
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for the use of boolean positional arguments in function definitions,
|
||||
@@ -149,7 +151,10 @@ pub(crate) fn boolean_type_hint_positional_argument(
|
||||
return;
|
||||
}
|
||||
|
||||
checker.report_diagnostic(BooleanTypeHintPositionalArgument, parameter.identifier());
|
||||
let mut diagnostic =
|
||||
checker.report_diagnostic(BooleanTypeHintPositionalArgument, parameter.identifier());
|
||||
|
||||
add_liskov_substitution_principle_help(&mut diagnostic, name, decorator_list, checker);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -97,6 +97,7 @@ FBT001 Boolean-typed positional argument in function definition
|
||||
| ^^^^^
|
||||
91 | pass
|
||||
|
|
||||
help: Consider adding `@typing.override` if changing the function signature would violate the Liskov Substitution Principle
|
||||
|
||||
FBT001 Boolean-typed positional argument in function definition
|
||||
--> FBT.py:100:10
|
||||
|
||||
@@ -130,10 +130,16 @@ pub(crate) fn invalid_function_name(
|
||||
return;
|
||||
}
|
||||
|
||||
checker.report_diagnostic(
|
||||
let mut diagnostic = checker.report_diagnostic(
|
||||
InvalidFunctionName {
|
||||
name: name.to_string(),
|
||||
},
|
||||
stmt.identifier(),
|
||||
);
|
||||
if parent_class.is_some() {
|
||||
diagnostic.help(
|
||||
"Consider adding `@typing.override` if this method \
|
||||
overrides a method from a superclass",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,6 +42,7 @@ N802 Function name `testTest` should be lowercase
|
||||
| ^^^^^^^^
|
||||
41 | assert True
|
||||
|
|
||||
help: Consider adding `@typing.override` if this method overrides a method from a superclass
|
||||
|
||||
N802 Function name `bad_Name` should be lowercase
|
||||
--> N802.py:65:9
|
||||
@@ -52,6 +53,7 @@ N802 Function name `bad_Name` should be lowercase
|
||||
| ^^^^^^^^
|
||||
66 | pass
|
||||
|
|
||||
help: Consider adding `@typing.override` if this method overrides a method from a superclass
|
||||
|
||||
N802 Function name `dont_GET` should be lowercase
|
||||
--> N802.py:84:9
|
||||
@@ -62,6 +64,7 @@ N802 Function name `dont_GET` should be lowercase
|
||||
| ^^^^^^^^
|
||||
85 | pass
|
||||
|
|
||||
help: Consider adding `@typing.override` if this method overrides a method from a superclass
|
||||
|
||||
N802 Function name `dont_OPTIONS` should be lowercase
|
||||
--> N802.py:95:9
|
||||
@@ -72,6 +75,7 @@ N802 Function name `dont_OPTIONS` should be lowercase
|
||||
| ^^^^^^^^^^^^
|
||||
96 | pass
|
||||
|
|
||||
help: Consider adding `@typing.override` if this method overrides a method from a superclass
|
||||
|
||||
N802 Function name `dont_OPTIONS` should be lowercase
|
||||
--> N802.py:106:9
|
||||
@@ -82,3 +86,4 @@ N802 Function name `dont_OPTIONS` should be lowercase
|
||||
| ^^^^^^^^^^^^
|
||||
107 | pass
|
||||
|
|
||||
help: Consider adding `@typing.override` if this method overrides a method from a superclass
|
||||
|
||||
@@ -20,3 +20,4 @@ N802 Function name `stillBad` should be lowercase
|
||||
| ^^^^^^^^
|
||||
14 | return super().tearDown()
|
||||
|
|
||||
help: Consider adding `@typing.override` if this method overrides a method from a superclass
|
||||
|
||||
@@ -146,11 +146,15 @@ pub(crate) fn no_self_use(checker: &Checker, scope_id: ScopeId, scope: &Scope) {
|
||||
.map(|binding_id| semantic.binding(binding_id))
|
||||
.is_some_and(|binding| binding.kind.is_argument() && binding.is_unused())
|
||||
{
|
||||
checker.report_diagnostic(
|
||||
let mut diagnostic = checker.report_diagnostic(
|
||||
NoSelfUse {
|
||||
method_name: name.to_string(),
|
||||
},
|
||||
func.identifier(),
|
||||
);
|
||||
diagnostic.help(
|
||||
"Consider adding `@typing.override` if this method overrides \
|
||||
a method from a superclass",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast as ast;
|
||||
use ruff_python_ast::identifier::Identifier;
|
||||
use ruff_python_semantic::analyze::function_type::is_subject_to_liskov_substitution_principle;
|
||||
use ruff_python_semantic::analyze::{function_type, visibility};
|
||||
|
||||
use crate::Violation;
|
||||
@@ -121,11 +122,24 @@ pub(crate) fn too_many_arguments(checker: &Checker, function_def: &ast::StmtFunc
|
||||
return;
|
||||
}
|
||||
|
||||
checker.report_diagnostic(
|
||||
let mut diagnostic = checker.report_diagnostic(
|
||||
TooManyArguments {
|
||||
c_args: num_arguments,
|
||||
max_args: checker.settings().pylint.max_args,
|
||||
},
|
||||
function_def.identifier(),
|
||||
);
|
||||
if is_subject_to_liskov_substitution_principle(
|
||||
&function_def.name,
|
||||
&function_def.decorator_list,
|
||||
semantic.current_scope(),
|
||||
semantic,
|
||||
&checker.settings().pep8_naming.classmethod_decorators,
|
||||
&checker.settings().pep8_naming.staticmethod_decorators,
|
||||
) {
|
||||
diagnostic.help(
|
||||
"Consider adding `@typing.override` if changing the function signature \
|
||||
would violate the Liskov Substitution Principle",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::{self as ast, identifier::Identifier};
|
||||
use ruff_python_semantic::analyze::function_type::is_subject_to_liskov_substitution_principle;
|
||||
use ruff_python_semantic::analyze::{function_type, visibility};
|
||||
|
||||
use crate::Violation;
|
||||
@@ -125,11 +126,24 @@ pub(crate) fn too_many_positional_arguments(
|
||||
return;
|
||||
}
|
||||
|
||||
checker.report_diagnostic(
|
||||
let mut diagnostic = checker.report_diagnostic(
|
||||
TooManyPositionalArguments {
|
||||
c_pos: num_positional_args,
|
||||
max_pos: checker.settings().pylint.max_positional_args,
|
||||
},
|
||||
function_def.identifier(),
|
||||
);
|
||||
if is_subject_to_liskov_substitution_principle(
|
||||
&function_def.name,
|
||||
&function_def.decorator_list,
|
||||
semantic.current_scope(),
|
||||
semantic,
|
||||
&checker.settings().pep8_naming.classmethod_decorators,
|
||||
&checker.settings().pep8_naming.staticmethod_decorators,
|
||||
) {
|
||||
diagnostic.help(
|
||||
"Consider adding `@typing.override` if changing the function signature \
|
||||
would violate the Liskov Substitution Principle",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,6 +49,7 @@ PLR0913 Too many arguments in function definition (8 > 5)
|
||||
| ^
|
||||
52 | pass
|
||||
|
|
||||
help: Consider adding `@typing.override` if changing the function signature would violate the Liskov Substitution Principle
|
||||
|
||||
PLR0913 Too many arguments in function definition (8 > 5)
|
||||
--> too_many_arguments.py:58:9
|
||||
@@ -58,6 +59,7 @@ PLR0913 Too many arguments in function definition (8 > 5)
|
||||
| ^
|
||||
59 | pass
|
||||
|
|
||||
help: Consider adding `@typing.override` if changing the function signature would violate the Liskov Substitution Principle
|
||||
|
||||
PLR0913 Too many arguments in function definition (8 > 5)
|
||||
--> too_many_arguments.py:66:9
|
||||
@@ -67,6 +69,7 @@ PLR0913 Too many arguments in function definition (8 > 5)
|
||||
| ^
|
||||
67 | pass
|
||||
|
|
||||
help: Consider adding `@typing.override` if changing the function signature would violate the Liskov Substitution Principle
|
||||
|
||||
PLR0913 Too many arguments in function definition (6 > 5)
|
||||
--> too_many_arguments.py:70:9
|
||||
@@ -76,3 +79,4 @@ PLR0913 Too many arguments in function definition (6 > 5)
|
||||
| ^
|
||||
71 | pass
|
||||
|
|
||||
help: Consider adding `@typing.override` if changing the function signature would violate the Liskov Substitution Principle
|
||||
|
||||
@@ -34,6 +34,7 @@ PLR0917 Too many positional arguments (6/5)
|
||||
| ^
|
||||
44 | pass
|
||||
|
|
||||
help: Consider adding `@typing.override` if changing the function signature would violate the Liskov Substitution Principle
|
||||
|
||||
PLR0917 Too many positional arguments (6/5)
|
||||
--> too_many_positional_arguments.py:47:9
|
||||
@@ -43,3 +44,4 @@ PLR0917 Too many positional arguments (6/5)
|
||||
| ^
|
||||
48 | pass
|
||||
|
|
||||
help: Consider adding `@typing.override` if changing the function signature would violate the Liskov Substitution Principle
|
||||
|
||||
@@ -9,6 +9,7 @@ PLR6301 Method `developer_greeting` could be a function, class method, or static
|
||||
| ^^^^^^^^^^^^^^^^^^
|
||||
8 | print(f"Greetings {name}!")
|
||||
|
|
||||
help: Consider adding `@typing.override` if this method overrides a method from a superclass
|
||||
|
||||
PLR6301 Method `greeting_1` could be a function, class method, or static method
|
||||
--> no_self_use.py:10:9
|
||||
@@ -19,6 +20,7 @@ PLR6301 Method `greeting_1` could be a function, class method, or static method
|
||||
| ^^^^^^^^^^
|
||||
11 | print("Hello!")
|
||||
|
|
||||
help: Consider adding `@typing.override` if this method overrides a method from a superclass
|
||||
|
||||
PLR6301 Method `greeting_2` could be a function, class method, or static method
|
||||
--> no_self_use.py:13:9
|
||||
@@ -29,6 +31,7 @@ PLR6301 Method `greeting_2` could be a function, class method, or static method
|
||||
| ^^^^^^^^^^
|
||||
14 | print("Hi!")
|
||||
|
|
||||
help: Consider adding `@typing.override` if this method overrides a method from a superclass
|
||||
|
||||
PLR6301 Method `validate_y` could be a function, class method, or static method
|
||||
--> no_self_use.py:103:9
|
||||
@@ -39,6 +42,7 @@ PLR6301 Method `validate_y` could be a function, class method, or static method
|
||||
104 | if value <= 0:
|
||||
105 | raise ValueError("y must be a positive integer")
|
||||
|
|
||||
help: Consider adding `@typing.override` if this method overrides a method from a superclass
|
||||
|
||||
PLR6301 Method `non_simple_assignment` could be a function, class method, or static method
|
||||
--> no_self_use.py:128:9
|
||||
@@ -50,6 +54,7 @@ PLR6301 Method `non_simple_assignment` could be a function, class method, or sta
|
||||
129 | msg = foo = ""
|
||||
130 | raise NotImplementedError(msg)
|
||||
|
|
||||
help: Consider adding `@typing.override` if this method overrides a method from a superclass
|
||||
|
||||
PLR6301 Method `non_simple_assignment_2` could be a function, class method, or static method
|
||||
--> no_self_use.py:132:9
|
||||
@@ -61,6 +66,7 @@ PLR6301 Method `non_simple_assignment_2` could be a function, class method, or s
|
||||
133 | msg[0] = ""
|
||||
134 | raise NotImplementedError(msg)
|
||||
|
|
||||
help: Consider adding `@typing.override` if this method overrides a method from a superclass
|
||||
|
||||
PLR6301 Method `unused_message` could be a function, class method, or static method
|
||||
--> no_self_use.py:136:9
|
||||
@@ -72,6 +78,7 @@ PLR6301 Method `unused_message` could be a function, class method, or static met
|
||||
137 | msg = ""
|
||||
138 | raise NotImplementedError("")
|
||||
|
|
||||
help: Consider adding `@typing.override` if this method overrides a method from a superclass
|
||||
|
||||
PLR6301 Method `unused_message_2` could be a function, class method, or static method
|
||||
--> no_self_use.py:140:9
|
||||
@@ -83,6 +90,7 @@ PLR6301 Method `unused_message_2` could be a function, class method, or static m
|
||||
141 | msg = ""
|
||||
142 | raise NotImplementedError(x)
|
||||
|
|
||||
help: Consider adding `@typing.override` if this method overrides a method from a superclass
|
||||
|
||||
PLR6301 Method `developer_greeting` could be a function, class method, or static method
|
||||
--> no_self_use.py:145:9
|
||||
@@ -92,6 +100,7 @@ PLR6301 Method `developer_greeting` could be a function, class method, or static
|
||||
| ^^^^^^^^^^^^^^^^^^
|
||||
146 | print(t"Greetings {name}!")
|
||||
|
|
||||
help: Consider adding `@typing.override` if this method overrides a method from a superclass
|
||||
|
||||
PLR6301 Method `tstring` could be a function, class method, or static method
|
||||
--> no_self_use.py:151:9
|
||||
@@ -103,3 +112,4 @@ PLR6301 Method `tstring` could be a function, class method, or static method
|
||||
152 | msg = t"{x}"
|
||||
153 | raise NotImplementedError(msg)
|
||||
|
|
||||
help: Consider adding `@typing.override` if this method overrides a method from a superclass
|
||||
|
||||
@@ -18,10 +18,10 @@ doctest = false
|
||||
ruff_python_trivia = { workspace = true }
|
||||
|
||||
heck = { workspace = true }
|
||||
itertools = { workspace = true }
|
||||
proc-macro2 = { workspace = true }
|
||||
quote = { workspace = true }
|
||||
syn = { workspace = true, features = ["derive", "parsing", "extra-traits", "full"] }
|
||||
itertools = { workspace = true }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
@@ -12,7 +12,6 @@ license = { workspace = true }
|
||||
|
||||
[dependencies]
|
||||
get-size2 = { workspace = true }
|
||||
ordermap = { workspace = true }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
@@ -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>()
|
||||
}
|
||||
|
||||
@@ -20,12 +20,12 @@ ruff_text_size = { workspace = true }
|
||||
|
||||
anyhow = { workspace = true }
|
||||
itertools = { workspace = true }
|
||||
rand = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
serde_with = { workspace = true, default-features = false, features = ["macros"] }
|
||||
thiserror = { workspace = true }
|
||||
uuid = { workspace = true }
|
||||
rand = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
test-case = { workspace = true }
|
||||
|
||||
@@ -10,6 +10,10 @@ documentation = { workspace = true }
|
||||
repository = { workspace = true }
|
||||
license = { workspace = true }
|
||||
|
||||
[package.metadata.cargo-shear]
|
||||
# Used via `CacheKey` macro expansion.
|
||||
ignored = ["ruff_cache"]
|
||||
|
||||
[lib]
|
||||
|
||||
[dependencies]
|
||||
@@ -42,14 +46,7 @@ serde = [
|
||||
"dep:ruff_cache",
|
||||
"compact_str/serde",
|
||||
]
|
||||
get-size = [
|
||||
"dep:get-size2",
|
||||
"ruff_text_size/get-size"
|
||||
]
|
||||
get-size = ["dep:get-size2", "ruff_text_size/get-size"]
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[package.metadata.cargo-shear]
|
||||
# Used via `CacheKey` macro expansion.
|
||||
ignored = ["ruff_cache"]
|
||||
|
||||
@@ -12,8 +12,8 @@ license.workspace = true
|
||||
[dependencies]
|
||||
|
||||
[dev-dependencies]
|
||||
ruff_python_parser = { workspace = true }
|
||||
ruff_python_ast = { workspace = true }
|
||||
ruff_python_parser = { workspace = true }
|
||||
ruff_python_trivia = { workspace = true }
|
||||
ruff_text_size = { workspace = true }
|
||||
|
||||
|
||||
@@ -10,6 +10,10 @@ documentation = { workspace = true }
|
||||
repository = { workspace = true }
|
||||
license = { workspace = true }
|
||||
|
||||
[package.metadata.cargo-shear]
|
||||
# Used via `CacheKey` macro expansion.
|
||||
ignored = ["ruff_cache"]
|
||||
|
||||
[lib]
|
||||
doctest = false
|
||||
|
||||
@@ -18,10 +22,10 @@ ruff_cache = { workspace = true }
|
||||
ruff_db = { workspace = true }
|
||||
ruff_formatter = { workspace = true }
|
||||
ruff_macros = { workspace = true }
|
||||
ruff_python_trivia = { workspace = true }
|
||||
ruff_source_file = { workspace = true }
|
||||
ruff_python_ast = { workspace = true }
|
||||
ruff_python_parser = { workspace = true }
|
||||
ruff_python_trivia = { workspace = true }
|
||||
ruff_source_file = { workspace = true }
|
||||
ruff_text_size = { workspace = true }
|
||||
|
||||
anyhow = { workspace = true }
|
||||
@@ -32,8 +36,8 @@ memchr = { workspace = true }
|
||||
regex = { workspace = true }
|
||||
rustc-hash = { workspace = true }
|
||||
salsa = { workspace = true }
|
||||
serde = { workspace = true, optional = true }
|
||||
schemars = { workspace = true, optional = true }
|
||||
serde = { workspace = true, optional = true }
|
||||
serde_json = { workspace = true, optional = true }
|
||||
smallvec = { workspace = true }
|
||||
static_assertions = { workspace = true }
|
||||
@@ -50,16 +54,6 @@ serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
similar = { workspace = true }
|
||||
|
||||
[package.metadata.cargo-shear]
|
||||
# Used via `CacheKey` macro expansion.
|
||||
ignored = ["ruff_cache"]
|
||||
|
||||
[[test]]
|
||||
name = "fixtures"
|
||||
harness = false
|
||||
test = true
|
||||
required-features = ["serde"]
|
||||
|
||||
[features]
|
||||
default = ["serde"]
|
||||
serde = [
|
||||
@@ -70,5 +64,11 @@ serde = [
|
||||
]
|
||||
schemars = ["dep:schemars", "dep:serde_json", "ruff_formatter/schemars"]
|
||||
|
||||
[[test]]
|
||||
name = "fixtures"
|
||||
harness = false
|
||||
test = true
|
||||
required-features = ["serde"]
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
@@ -12,10 +12,6 @@ license = { workspace = true }
|
||||
|
||||
[lib]
|
||||
|
||||
[[test]]
|
||||
name = "fixtures"
|
||||
harness = false
|
||||
|
||||
[dependencies]
|
||||
ruff_python_ast = { workspace = true, features = ["get-size"] }
|
||||
ruff_python_trivia = { workspace = true }
|
||||
@@ -29,8 +25,8 @@ memchr = { workspace = true }
|
||||
rustc-hash = { workspace = true }
|
||||
static_assertions = { workspace = true }
|
||||
unicode-ident = { workspace = true }
|
||||
unicode_names2 = { workspace = true }
|
||||
unicode-normalization = { workspace = true }
|
||||
unicode_names2 = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
ruff_annotate_snippets = { workspace = true }
|
||||
@@ -45,5 +41,9 @@ serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
walkdir = { workspace = true }
|
||||
|
||||
[[test]]
|
||||
name = "fixtures"
|
||||
harness = false
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
@@ -10,6 +10,10 @@ documentation = { workspace = true }
|
||||
repository = { workspace = true }
|
||||
license = { workspace = true }
|
||||
|
||||
[package.metadata.cargo-shear]
|
||||
# Used via `CacheKey` macro expansion.
|
||||
ignored = ["ruff_cache"]
|
||||
|
||||
[dependencies]
|
||||
ruff_cache = { workspace = true }
|
||||
ruff_index = { workspace = true }
|
||||
@@ -28,12 +32,8 @@ smallvec = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
insta = { workspace = true, features = ["filters", "json", "redactions"] }
|
||||
test-case = { workspace = true }
|
||||
ruff_python_parser = { workspace = true }
|
||||
test-case = { workspace = true }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[package.metadata.cargo-shear]
|
||||
# Used via `CacheKey` macro expansion.
|
||||
ignored = ["ruff_cache"]
|
||||
|
||||
@@ -47,6 +47,35 @@ pub fn classify(
|
||||
}
|
||||
}
|
||||
|
||||
/// Return `true` if this function is subject to the Liskov Substitution Principle.
|
||||
///
|
||||
/// Type checkers will check nearly all methods for compliance with the Liskov Substitution
|
||||
/// Principle, but some methods are exempt.
|
||||
pub fn is_subject_to_liskov_substitution_principle(
|
||||
function_name: &str,
|
||||
decorator_list: &[Decorator],
|
||||
parent_scope: &Scope,
|
||||
semantic: &SemanticModel,
|
||||
classmethod_decorators: &[String],
|
||||
staticmethod_decorators: &[String],
|
||||
) -> bool {
|
||||
let kind = classify(
|
||||
function_name,
|
||||
decorator_list,
|
||||
parent_scope,
|
||||
semantic,
|
||||
classmethod_decorators,
|
||||
staticmethod_decorators,
|
||||
);
|
||||
|
||||
match (kind, function_name) {
|
||||
(FunctionType::Function | FunctionType::NewMethod, _) => false,
|
||||
(FunctionType::Method, "__init__" | "__post_init__" | "__replace__") => false,
|
||||
(_, "__init_subclass__") => false,
|
||||
(FunctionType::Method | FunctionType::ClassMethod | FunctionType::StaticMethod, _) => true,
|
||||
}
|
||||
}
|
||||
|
||||
/// Return `true` if a [`Decorator`] is indicative of a static method.
|
||||
/// Note: Implicit static methods like `__new__` are not considered.
|
||||
fn is_static_method(
|
||||
|
||||
@@ -13,8 +13,8 @@ license = { workspace = true }
|
||||
[lib]
|
||||
|
||||
[dependencies]
|
||||
ruff_text_size = { workspace = true }
|
||||
ruff_source_file = { workspace = true }
|
||||
ruff_text_size = { workspace = true }
|
||||
|
||||
itertools = { workspace = true }
|
||||
unicode-ident = { workspace = true }
|
||||
|
||||
@@ -44,12 +44,12 @@ tracing = { workspace = true }
|
||||
tracing-log = { workspace = true }
|
||||
tracing-subscriber = { workspace = true, features = ["chrono"] }
|
||||
|
||||
[dev-dependencies]
|
||||
insta = { workspace = true }
|
||||
|
||||
[target.'cfg(target_vendor = "apple")'.dependencies]
|
||||
libc = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
insta = { workspace = true }
|
||||
|
||||
[features]
|
||||
test-uv = []
|
||||
|
||||
|
||||
@@ -11,9 +11,9 @@ repository = { workspace = true }
|
||||
license = { workspace = true }
|
||||
|
||||
[dependencies]
|
||||
serde = { workspace = true, optional = true }
|
||||
get-size2 = { workspace = true, optional = true }
|
||||
schemars = { workspace = true, optional = true }
|
||||
serde = { workspace = true, optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
serde_test = { workspace = true }
|
||||
|
||||
@@ -15,14 +15,11 @@ description = "WebAssembly bindings for Ruff"
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
doctest = false
|
||||
|
||||
[features]
|
||||
default = ["console_error_panic_hook"]
|
||||
|
||||
[dependencies]
|
||||
ruff_formatter = { workspace = true }
|
||||
ruff_linter = { workspace = true }
|
||||
ruff_python_ast = { workspace = true }
|
||||
ruff_python_codegen = { workspace = true }
|
||||
ruff_formatter = { workspace = true }
|
||||
ruff_python_formatter = { workspace = true }
|
||||
ruff_python_index = { workspace = true }
|
||||
ruff_python_parser = { workspace = true }
|
||||
@@ -33,19 +30,22 @@ ruff_workspace = { workspace = true }
|
||||
|
||||
console_error_panic_hook = { workspace = true, optional = true }
|
||||
console_log = { workspace = true }
|
||||
js-sys = { workspace = true }
|
||||
log = { workspace = true }
|
||||
# Not a direct dependency but required to enable the `wasm_js` feature.
|
||||
# See https://docs.rs/getrandom/latest/getrandom/#webassembly-support
|
||||
getrandom = { workspace = true, features = ["wasm_js"] }
|
||||
js-sys = { workspace = true }
|
||||
log = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
serde-wasm-bindgen = { workspace = true }
|
||||
wasm-bindgen = { workspace = true }
|
||||
# Not a direct dependency but required to compile for Wasm.
|
||||
uuid = { workspace = true, features = ["js"] }
|
||||
wasm-bindgen = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
wasm-bindgen-test = { workspace = true }
|
||||
|
||||
[features]
|
||||
default = ["console_error_panic_hook"]
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
@@ -10,6 +10,10 @@ documentation = { workspace = true }
|
||||
repository = { workspace = true }
|
||||
license = { workspace = true }
|
||||
|
||||
[package.metadata.cargo-shear]
|
||||
# Used via macro expansion.
|
||||
ignored = ["colored"]
|
||||
|
||||
[lib]
|
||||
|
||||
[dependencies]
|
||||
@@ -27,14 +31,14 @@ ruff_source_file = { workspace = true }
|
||||
|
||||
anyhow = { workspace = true }
|
||||
colored = { workspace = true }
|
||||
glob = { workspace = true }
|
||||
globset = { workspace = true }
|
||||
ignore = { workspace = true }
|
||||
indexmap = { workspace = true }
|
||||
is-macro = { workspace = true }
|
||||
itertools = { workspace = true }
|
||||
log = { workspace = true }
|
||||
matchit = { workspace = true }
|
||||
glob = { workspace = true }
|
||||
globset = { workspace = true }
|
||||
path-absolutize = { workspace = true }
|
||||
path-slash = { workspace = true }
|
||||
pep440_rs = { workspace = true }
|
||||
@@ -56,10 +60,6 @@ etcetera = { workspace = true }
|
||||
ruff_linter = { workspace = true, features = ["clap", "test-rules"] }
|
||||
tempfile = { workspace = true }
|
||||
|
||||
[package.metadata.cargo-shear]
|
||||
# Used via macro expansion.
|
||||
ignored = ["colored"]
|
||||
|
||||
[features]
|
||||
default = []
|
||||
schemars = [
|
||||
|
||||
@@ -17,8 +17,8 @@ license.workspace = true
|
||||
ruff_db = { workspace = true, features = ["os", "cache"] }
|
||||
ruff_python_ast = { workspace = true }
|
||||
ty_combine = { workspace = true }
|
||||
ty_python_semantic = { workspace = true }
|
||||
ty_project = { workspace = true, features = ["zstd"] }
|
||||
ty_python_semantic = { workspace = true }
|
||||
ty_server = { workspace = true }
|
||||
ty_static = { workspace = true }
|
||||
|
||||
@@ -35,19 +35,22 @@ jiff = { workspace = true }
|
||||
rayon = { workspace = true }
|
||||
salsa = { workspace = true }
|
||||
tracing = { workspace = true, features = ["release_max_level_debug"] }
|
||||
tracing-subscriber = { workspace = true }
|
||||
tracing-flame = { workspace = true }
|
||||
tracing-subscriber = { workspace = true }
|
||||
wild = { workspace = true }
|
||||
|
||||
[target.'cfg(all(not(target_os = "macos"), not(target_os = "windows"), not(target_os = "openbsd"), not(target_os = "aix"), not(target_os = "android"), any(target_arch = "x86_64", target_arch = "aarch64", target_arch = "powerpc64", target_arch = "riscv64")))'.dependencies]
|
||||
tikv-jemallocator = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
ruff_db = { workspace = true, features = ["testing"] }
|
||||
ruff_python_trivia = { workspace = true }
|
||||
ty_module_resolver = { workspace = true }
|
||||
|
||||
dunce = { workspace = true }
|
||||
filetime = { workspace = true }
|
||||
insta = { workspace = true, features = ["filters"] }
|
||||
insta-cmd = { workspace = true }
|
||||
filetime = { workspace = true }
|
||||
regex = { workspace = true }
|
||||
tempfile = { workspace = true }
|
||||
toml = { workspace = true }
|
||||
@@ -55,8 +58,5 @@ toml = { workspace = true }
|
||||
[features]
|
||||
default = []
|
||||
|
||||
[target.'cfg(all(not(target_os = "macos"), not(target_os = "windows"), not(target_os = "openbsd"), not(target_os = "aix"), not(target_os = "android"), any(target_arch = "x86_64", target_arch = "aarch64", target_arch = "powerpc64", target_arch = "riscv64")))'.dependencies]
|
||||
tikv-jemallocator = { workspace = true }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
@@ -23,8 +23,8 @@ 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_python_semantic = { workspace = true }
|
||||
ty_vendored = { workspace = true }
|
||||
|
||||
get-size2 = { workspace = true }
|
||||
|
||||
@@ -21,10 +21,10 @@ 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 }
|
||||
thiserror = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
ruff_db = { workspace = true, features = ["testing", "os"] }
|
||||
|
||||
@@ -33,8 +33,8 @@ crossbeam = { workspace = true }
|
||||
get-size2 = { workspace = true }
|
||||
globset = { workspace = true }
|
||||
notify = { workspace = true }
|
||||
pep440_rs = { workspace = true, features = ["version-ranges"] }
|
||||
ordermap = { workspace = true, features = ["serde"] }
|
||||
pep440_rs = { workspace = true, features = ["version-ranges"] }
|
||||
rayon = { workspace = true }
|
||||
regex = { workspace = true }
|
||||
regex-automata = { workspace = true }
|
||||
@@ -48,8 +48,8 @@ toml = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
ruff_db = { workspace = true, features = ["testing"] }
|
||||
insta = { workspace = true, features = ["redactions", "ron"] }
|
||||
ruff_db = { workspace = true, features = ["testing"] }
|
||||
|
||||
[features]
|
||||
default = ["zstd"]
|
||||
|
||||
@@ -11,21 +11,21 @@ repository = { workspace = true }
|
||||
license = { workspace = true }
|
||||
|
||||
[dependencies]
|
||||
ruff_db = { workspace = true }
|
||||
ruff_annotate_snippets = { workspace = true }
|
||||
ruff_db = { workspace = true }
|
||||
ruff_diagnostics = { workspace = true }
|
||||
ruff_index = { workspace = true, features = ["salsa"] }
|
||||
ruff_macros = { workspace = true }
|
||||
ruff_memory_usage = { workspace = true }
|
||||
ruff_python_ast = { workspace = true, features = ["salsa"] }
|
||||
ruff_python_literal = { workspace = true }
|
||||
ruff_python_parser = { workspace = true }
|
||||
ruff_python_stdlib = { workspace = true }
|
||||
ruff_python_trivia = { workspace = true }
|
||||
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_module_resolver = { workspace = true }
|
||||
ty_static = { workspace = true }
|
||||
|
||||
anyhow = { workspace = true }
|
||||
@@ -36,24 +36,24 @@ colored = { workspace = true }
|
||||
compact_str = { workspace = true }
|
||||
drop_bomb = { workspace = true }
|
||||
get-size2 = { workspace = true, features = ["indexmap", "ordermap"] }
|
||||
hashbrown = { workspace = true }
|
||||
indexmap = { workspace = true }
|
||||
itertools = { workspace = true }
|
||||
memchr = { workspace = true }
|
||||
ordermap = { workspace = true }
|
||||
salsa = { workspace = true, features = ["compact_str", "ordermap"] }
|
||||
thiserror = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
rustc-hash = { workspace = true }
|
||||
hashbrown = { workspace = true }
|
||||
salsa = { workspace = true, features = ["compact_str", "ordermap"] }
|
||||
schemars = { workspace = true, optional = true }
|
||||
serde = { workspace = true, optional = true }
|
||||
serde_json = { workspace = true, optional = true }
|
||||
smallvec = { workspace = true }
|
||||
static_assertions = { workspace = true }
|
||||
test-case = { workspace = true }
|
||||
memchr = { workspace = true }
|
||||
strsim = "0.11.1"
|
||||
strum = { workspace = true }
|
||||
strum_macros = { workspace = true }
|
||||
strsim = "0.11.1"
|
||||
test-case = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
ruff_db = { workspace = true, features = ["testing", "os"] }
|
||||
@@ -69,7 +69,7 @@ indoc = { workspace = true }
|
||||
insta = { workspace = true }
|
||||
pretty_assertions = { workspace = true }
|
||||
quickcheck = { workspace = true }
|
||||
quickcheck_macros = { workspace = true}
|
||||
quickcheck_macros = { workspace = true }
|
||||
|
||||
[features]
|
||||
schemars = ["dep:schemars", "dep:serde_json"]
|
||||
|
||||
@@ -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'>
|
||||
```
|
||||
|
||||
@@ -172,6 +172,25 @@ def _(x: X, y: tuple[Literal[1], Literal[3]]):
|
||||
reveal_type(x < y) # revealed: Literal[True]
|
||||
```
|
||||
|
||||
## In subscripts
|
||||
|
||||
Subscript operations should work through type aliases.
|
||||
|
||||
```py
|
||||
class C:
|
||||
def __getitem__(self, index: int) -> int:
|
||||
return 1
|
||||
|
||||
class D:
|
||||
def __getitem__(self, index: int) -> int:
|
||||
return 1
|
||||
|
||||
type CD = C | D
|
||||
|
||||
def _(x: CD):
|
||||
reveal_type(x[1]) # revealed: int
|
||||
```
|
||||
|
||||
## `TypeAliasType` properties
|
||||
|
||||
Two `TypeAliasType`s are distinct and disjoint, even if they refer to the same type
|
||||
|
||||
@@ -2124,20 +2124,26 @@ shows up in a subset of the union members) is present, but that isn't generally
|
||||
field, it could be *assigned to* with another `TypedDict` that does:
|
||||
|
||||
```py
|
||||
from typing_extensions import Literal
|
||||
|
||||
class Foo(TypedDict):
|
||||
foo: int
|
||||
|
||||
class Bar(TypedDict):
|
||||
bar: int
|
||||
|
||||
def disappointment(u: Foo | Bar):
|
||||
def disappointment(u: Foo | Bar, v: Literal["foo"]):
|
||||
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: Bar
|
||||
|
||||
if v in u:
|
||||
reveal_type(u) # revealed: Foo | Bar
|
||||
else:
|
||||
reveal_type(u) # revealed: Bar
|
||||
|
||||
# ...because `u` could turn out to be one of these.
|
||||
class FooBar(TypedDict):
|
||||
@@ -2148,6 +2154,39 @@ static_assert(is_assignable_to(FooBar, Foo))
|
||||
static_assert(is_assignable_to(FooBar, Bar))
|
||||
```
|
||||
|
||||
`not in` works in the opposite way to `in`: we can narrow in the positive case, but we cannot narrow
|
||||
in the negative case. The following snippet also tests our narrowing behaviour for intersections
|
||||
that contain `TypedDict`s, and unions that contain intersections that contain `TypedDict`s:
|
||||
|
||||
```py
|
||||
from typing_extensions import Literal, Any
|
||||
from ty_extensions import Intersection, is_assignable_to, static_assert
|
||||
|
||||
def _(t: Bar, u: Foo | Intersection[Bar, Any], v: Intersection[Bar, Any], w: Literal["bar"]):
|
||||
reveal_type(u) # revealed: Foo | (Bar & Any)
|
||||
reveal_type(v) # revealed: Bar & Any
|
||||
|
||||
if "bar" not in t:
|
||||
reveal_type(t) # revealed: Never
|
||||
else:
|
||||
reveal_type(t) # revealed: Bar
|
||||
|
||||
if "bar" not in u:
|
||||
reveal_type(u) # revealed: Foo
|
||||
else:
|
||||
reveal_type(u) # revealed: Foo | (Bar & Any)
|
||||
|
||||
if "bar" not in v:
|
||||
reveal_type(v) # revealed: Never
|
||||
else:
|
||||
reveal_type(v) # revealed: Bar & Any
|
||||
|
||||
if w not in u:
|
||||
reveal_type(u) # revealed: Foo
|
||||
else:
|
||||
reveal_type(u) # revealed: Foo | (Bar & Any)
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use compact_str::CompactString;
|
||||
use get_size2::GetSize;
|
||||
use infer::nearest_enclosing_class;
|
||||
use itertools::{Either, Itertools};
|
||||
use ruff_diagnostics::{Edit, Fix};
|
||||
@@ -7268,10 +7267,7 @@ impl<'db> Type<'db> {
|
||||
}
|
||||
|
||||
(Some(Place::Defined(new_method, ..)), Place::Defined(init_method, ..)) => {
|
||||
let callable = UnionBuilder::new(db)
|
||||
.add(*new_method)
|
||||
.add(*init_method)
|
||||
.build();
|
||||
let callable = UnionType::from_elements(db, [new_method, init_method]);
|
||||
|
||||
let new_method_bindings = new_method
|
||||
.bindings(db)
|
||||
@@ -10759,11 +10755,7 @@ fn walk_type_var_constraints<'db, V: visitor::TypeVisitor<'db> + ?Sized>(
|
||||
|
||||
impl<'db> TypeVarConstraints<'db> {
|
||||
fn as_type(self, db: &'db dyn Db) -> Type<'db> {
|
||||
let mut builder = UnionBuilder::new(db);
|
||||
for ty in self.elements(db) {
|
||||
builder = builder.add(*ty);
|
||||
}
|
||||
builder.build()
|
||||
UnionType::from_elements(db, self.elements(db))
|
||||
}
|
||||
|
||||
fn to_instance(self, db: &'db dyn Db) -> Option<TypeVarConstraints<'db>> {
|
||||
@@ -14372,7 +14364,7 @@ impl KnownUnion {
|
||||
}
|
||||
}
|
||||
|
||||
#[salsa::interned(debug, heap_size=IntersectionType::heap_size)]
|
||||
#[salsa::interned(debug, heap_size=ruff_memory_usage::heap_size)]
|
||||
pub struct IntersectionType<'db> {
|
||||
/// The intersection type includes only values in all of these types.
|
||||
#[returns(ref)]
|
||||
@@ -14384,7 +14376,7 @@ pub struct IntersectionType<'db> {
|
||||
/// narrowing along with intersections (e.g. `if not isinstance(...)`), so we represent them
|
||||
/// directly in intersections rather than as a separate type.
|
||||
#[returns(ref)]
|
||||
negative: smallvec::SmallVec<[Type<'db>; 1]>,
|
||||
negative: FxOrderSet<Type<'db>>,
|
||||
}
|
||||
|
||||
// The Salsa heap is tracked separately.
|
||||
@@ -14424,22 +14416,25 @@ impl<'db> IntersectionType<'db> {
|
||||
}
|
||||
|
||||
pub(crate) fn normalized_impl(self, db: &'db dyn Db, visitor: &NormalizedVisitor<'db>) -> Self {
|
||||
let mut positive: FxOrderSet<Type<'db>> = self
|
||||
.positive(db)
|
||||
.iter()
|
||||
.map(|ty| ty.normalized_impl(db, visitor))
|
||||
.collect();
|
||||
fn normalized_set<'db>(
|
||||
db: &'db dyn Db,
|
||||
elements: &FxOrderSet<Type<'db>>,
|
||||
visitor: &NormalizedVisitor<'db>,
|
||||
) -> FxOrderSet<Type<'db>> {
|
||||
let mut elements: FxOrderSet<Type<'db>> = elements
|
||||
.iter()
|
||||
.map(|ty| ty.normalized_impl(db, visitor))
|
||||
.collect();
|
||||
|
||||
let mut negative: smallvec::SmallVec<[Type<'db>; 1]> = self
|
||||
.negative(db)
|
||||
.iter()
|
||||
.map(|n| n.normalized_impl(db, visitor))
|
||||
.collect();
|
||||
elements.sort_unstable_by(|l, r| union_or_intersection_elements_ordering(db, l, r));
|
||||
elements
|
||||
}
|
||||
|
||||
positive.sort_unstable_by(|l, r| union_or_intersection_elements_ordering(db, l, r));
|
||||
negative.sort_unstable_by(|l, r| union_or_intersection_elements_ordering(db, l, r));
|
||||
|
||||
IntersectionType::new(db, positive, negative)
|
||||
IntersectionType::new(
|
||||
db,
|
||||
normalized_set(db, self.positive(db), visitor),
|
||||
normalized_set(db, self.negative(db), visitor),
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn recursive_type_normalized_impl(
|
||||
@@ -14448,34 +14443,42 @@ impl<'db> IntersectionType<'db> {
|
||||
div: Type<'db>,
|
||||
nested: bool,
|
||||
) -> Option<Self> {
|
||||
let positive: FxOrderSet<Type<'db>> = if nested {
|
||||
self.positive(db)
|
||||
fn opt_normalized_set<'db>(
|
||||
db: &'db dyn Db,
|
||||
elements: &FxOrderSet<Type<'db>>,
|
||||
div: Type<'db>,
|
||||
nested: bool,
|
||||
) -> Option<FxOrderSet<Type<'db>>> {
|
||||
elements
|
||||
.iter()
|
||||
.map(|ty| ty.recursive_type_normalized_impl(db, div, nested))
|
||||
.collect::<Option<_>>()?
|
||||
} else {
|
||||
self.positive(db)
|
||||
.iter()
|
||||
.map(|ty| {
|
||||
ty.recursive_type_normalized_impl(db, div, nested)
|
||||
.unwrap_or(div)
|
||||
})
|
||||
.collect()
|
||||
};
|
||||
}
|
||||
|
||||
let negative: smallvec::SmallVec<[Type<'db>; 1]> = if nested {
|
||||
self.positive(db)
|
||||
.iter()
|
||||
.map(|ty| ty.recursive_type_normalized_impl(db, div, nested))
|
||||
.collect::<Option<_>>()?
|
||||
} else {
|
||||
self.positive(db)
|
||||
fn normalized_set<'db>(
|
||||
db: &'db dyn Db,
|
||||
elements: &FxOrderSet<Type<'db>>,
|
||||
div: Type<'db>,
|
||||
nested: bool,
|
||||
) -> FxOrderSet<Type<'db>> {
|
||||
elements
|
||||
.iter()
|
||||
.map(|ty| {
|
||||
ty.recursive_type_normalized_impl(db, div, nested)
|
||||
.unwrap_or(div)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
let positive = if nested {
|
||||
opt_normalized_set(db, self.positive(db), div, nested)?
|
||||
} else {
|
||||
normalized_set(db, self.positive(db), div, nested)
|
||||
};
|
||||
let negative = if nested {
|
||||
opt_normalized_set(db, self.negative(db), div, nested)?
|
||||
} else {
|
||||
normalized_set(db, self.negative(db), div, nested)
|
||||
};
|
||||
|
||||
Some(IntersectionType::new(db, positive, negative))
|
||||
@@ -14650,12 +14653,6 @@ impl<'db> IntersectionType<'db> {
|
||||
pub(crate) fn is_simple_negation(self, db: &'db dyn Db) -> bool {
|
||||
self.positive(db).is_empty() && self.negative(db).len() == 1
|
||||
}
|
||||
|
||||
fn heap_size(
|
||||
(positive, negative): &(FxOrderSet<Type<'db>>, smallvec::SmallVec<[Type<'db>; 1]>),
|
||||
) -> usize {
|
||||
positive.get_heap_size() + negative.get_heap_size()
|
||||
}
|
||||
}
|
||||
|
||||
/// # Ordering
|
||||
|
||||
@@ -960,7 +960,7 @@ impl<'db> IntersectionBuilder<'db> {
|
||||
#[derive(Debug, Clone, Default)]
|
||||
struct InnerIntersectionBuilder<'db> {
|
||||
positive: FxOrderSet<Type<'db>>,
|
||||
negative: NegativeIntersectionElementsBuilder<'db>,
|
||||
negative: FxOrderSet<Type<'db>>,
|
||||
}
|
||||
|
||||
impl<'db> InnerIntersectionBuilder<'db> {
|
||||
@@ -1325,146 +1325,19 @@ impl<'db> InnerIntersectionBuilder<'db> {
|
||||
(1, 0) => self.positive[0],
|
||||
_ => {
|
||||
self.positive.shrink_to_fit();
|
||||
self.negative.shrink_to_fit();
|
||||
if order_elements {
|
||||
self.positive
|
||||
.sort_unstable_by(|l, r| union_or_intersection_elements_ordering(db, l, r));
|
||||
self.negative
|
||||
.sort_unstable_by(|l, r| union_or_intersection_elements_ordering(db, l, r));
|
||||
}
|
||||
Type::Intersection(IntersectionType::new(
|
||||
db,
|
||||
self.positive,
|
||||
self.negative
|
||||
.iter()
|
||||
.copied()
|
||||
.collect::<smallvec::SmallVec<_>>(),
|
||||
))
|
||||
Type::Intersection(IntersectionType::new(db, self.positive, self.negative))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// To avoid unnecessary allocations for the common case of 0-1 negative elements,
|
||||
/// we use this enum to represent the negative elements of an intersection type.
|
||||
///
|
||||
/// It should otherwise have identical behavior to `FxOrderSet<Type<'db>>`.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, get_size2::GetSize, salsa::Update, Default)]
|
||||
pub enum NegativeIntersectionElementsBuilder<'db> {
|
||||
#[default]
|
||||
Empty,
|
||||
Single(Type<'db>),
|
||||
Multiple(FxOrderSet<Type<'db>>),
|
||||
}
|
||||
|
||||
impl<'db> NegativeIntersectionElementsBuilder<'db> {
|
||||
pub(crate) fn iter(&self) -> NegativeIntersectionElementsIterator<'_, 'db> {
|
||||
match self {
|
||||
Self::Empty => NegativeIntersectionElementsIterator::EmptyOrOne(None),
|
||||
Self::Single(ty) => NegativeIntersectionElementsIterator::EmptyOrOne(Some(ty)),
|
||||
Self::Multiple(set) => NegativeIntersectionElementsIterator::Multiple(set.iter()),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn len(&self) -> usize {
|
||||
match self {
|
||||
Self::Empty => 0,
|
||||
Self::Single(_) => 1,
|
||||
Self::Multiple(set) => set.len(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn insert(&mut self, ty: Type<'db>) {
|
||||
match self {
|
||||
Self::Empty => *self = Self::Single(ty),
|
||||
Self::Single(existing) => {
|
||||
if ty != *existing {
|
||||
*self = Self::Multiple(FxOrderSet::from_iter([*existing, ty]));
|
||||
}
|
||||
}
|
||||
Self::Multiple(set) => {
|
||||
set.insert(ty);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn sort_unstable_by(
|
||||
&mut self,
|
||||
compare: impl FnMut(&Type<'db>, &Type<'db>) -> std::cmp::Ordering,
|
||||
) {
|
||||
match self {
|
||||
Self::Empty | Self::Single(_) => {}
|
||||
Self::Multiple(set) => {
|
||||
set.sort_unstable_by(compare);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn swap_remove(&mut self, ty: &Type<'db>) -> bool {
|
||||
match self {
|
||||
Self::Empty => false,
|
||||
Self::Single(existing) => {
|
||||
if existing == ty {
|
||||
*self = Self::Empty;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
// We don't try to maintain the invariant here that length-0 collections
|
||||
// are *always* `Self::Empty` and length-1 collections are *always*
|
||||
// `Self::Single`. It's unnecessary to do so, and would probably add overhead.
|
||||
Self::Multiple(set) => set.swap_remove(ty),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn swap_remove_index(&mut self, index: usize) -> Option<Type<'db>> {
|
||||
match self {
|
||||
Self::Empty => None,
|
||||
Self::Single(existing) => {
|
||||
if index == 0 {
|
||||
let ty = *existing;
|
||||
*self = Self::Empty;
|
||||
Some(ty)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
// We don't try to maintain the invariant here that length-0 collections
|
||||
// are *always* `Self::Empty` and length-1 collections are *always*
|
||||
// `Self::Single`. It's unnecessary to do so, and would probably add overhead.
|
||||
Self::Multiple(set) => set.swap_remove_index(index),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'db> IntoIterator for &'a NegativeIntersectionElementsBuilder<'db> {
|
||||
type Item = &'a Type<'db>;
|
||||
type IntoIter = NegativeIntersectionElementsIterator<'a, 'db>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.iter()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum NegativeIntersectionElementsIterator<'a, 'db> {
|
||||
EmptyOrOne(Option<&'a Type<'db>>),
|
||||
Multiple(ordermap::set::Iter<'a, Type<'db>>),
|
||||
}
|
||||
|
||||
impl<'a, 'db> Iterator for NegativeIntersectionElementsIterator<'a, 'db> {
|
||||
type Item = &'a Type<'db>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
match self {
|
||||
NegativeIntersectionElementsIterator::EmptyOrOne(opt) => opt.take(),
|
||||
NegativeIntersectionElementsIterator::Multiple(iter) => iter.next(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::iter::FusedIterator for NegativeIntersectionElementsIterator<'_, '_> {}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{IntersectionBuilder, Type, UnionBuilder, UnionType};
|
||||
|
||||
@@ -202,7 +202,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 +689,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>(
|
||||
|
||||
@@ -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
|
||||
@@ -12341,6 +12368,13 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
Some(todo_type!("Subscript expressions on intersections"))
|
||||
}
|
||||
|
||||
(Type::TypeAlias(alias), _) => Some(self.infer_subscript_expression_types(
|
||||
subscript,
|
||||
alias.value_type(db),
|
||||
slice_ty,
|
||||
expr_context,
|
||||
)),
|
||||
|
||||
// Ex) Given `("a", "b", "c", "d")[1]`, return `"b"`
|
||||
(Type::NominalInstance(nominal), Type::IntLiteral(i64_int)) => nominal
|
||||
.tuple_spec(db)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ use crate::types::enums::{enum_member_literals, enum_metadata};
|
||||
use crate::types::function::KnownFunction;
|
||||
use crate::types::infer::{ExpressionInference, infer_same_file_expression_type};
|
||||
use crate::types::typed_dict::{
|
||||
SynthesizedTypedDictType, TypedDictFieldBuilder, TypedDictSchema, TypedDictType,
|
||||
SynthesizedTypedDictType, TypedDictField, TypedDictFieldBuilder, TypedDictSchema, TypedDictType,
|
||||
};
|
||||
use crate::types::{
|
||||
CallableType, ClassLiteral, ClassType, IntersectionBuilder, IntersectionType, KnownClass,
|
||||
@@ -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
|
||||
@@ -1099,6 +1096,75 @@ impl<'db, 'ast> NarrowingConstraintsBuilder<'db, 'ast> {
|
||||
}
|
||||
}
|
||||
|
||||
// Narrow unions and intersections of `TypedDict` in cases where required keys are
|
||||
// excluded:
|
||||
//
|
||||
// class Foo(TypedDict):
|
||||
// foo: int
|
||||
// class Bar(TypedDict):
|
||||
// bar: int
|
||||
//
|
||||
// def _(u: Foo | Bar):
|
||||
// if "foo" not in u:
|
||||
// reveal_type(u) # revealed: Bar
|
||||
if matches!(&**ops, [ast::CmpOp::In | ast::CmpOp::NotIn])
|
||||
&& let Type::StringLiteral(key) = inference.expression_type(&**left)
|
||||
&& let Some(rhs_place_expr) = place_expr(&comparators[0])
|
||||
&& let rhs_type = inference.expression_type(&comparators[0])
|
||||
&& is_typeddict_or_union_with_typeddicts(self.db, rhs_type)
|
||||
{
|
||||
let is_negative_check = is_positive == (ops[0] == ast::CmpOp::NotIn);
|
||||
if is_negative_check {
|
||||
let requires_key = |td: TypedDictType<'db>| -> bool {
|
||||
td.items(self.db)
|
||||
.get(key.value(self.db))
|
||||
.is_some_and(TypedDictField::is_required)
|
||||
};
|
||||
|
||||
let narrowed = match rhs_type {
|
||||
Type::TypedDict(td) => {
|
||||
if requires_key(td) {
|
||||
Type::Never
|
||||
} else {
|
||||
rhs_type
|
||||
}
|
||||
}
|
||||
Type::Intersection(intersection) => {
|
||||
if intersection
|
||||
.positive(self.db)
|
||||
.iter()
|
||||
.copied()
|
||||
.filter_map(Type::as_typed_dict)
|
||||
.any(requires_key)
|
||||
{
|
||||
Type::Never
|
||||
} else {
|
||||
rhs_type
|
||||
}
|
||||
}
|
||||
Type::Union(union) => {
|
||||
// remove all members of the union that would require the key
|
||||
union.filter(self.db, |ty| match ty {
|
||||
Type::TypedDict(td) => !requires_key(*td),
|
||||
Type::Intersection(intersection) => !intersection
|
||||
.positive(self.db)
|
||||
.iter()
|
||||
.copied()
|
||||
.filter_map(Type::as_typed_dict)
|
||||
.any(requires_key),
|
||||
_ => true,
|
||||
})
|
||||
}
|
||||
_ => rhs_type,
|
||||
};
|
||||
|
||||
if narrowed != rhs_type {
|
||||
let place = self.expect_place(&rhs_place_expr);
|
||||
constraints.insert(place, NarrowingConstraint::typeguard(narrowed));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut last_rhs_ty: Option<Type> = None;
|
||||
|
||||
for (op, (left, right)) in std::iter::zip(&**ops, comparator_tuples) {
|
||||
@@ -1677,18 +1743,13 @@ impl<'db, 'ast> NarrowingConstraintsBuilder<'db, 'ast> {
|
||||
fn is_typeddict_or_union_with_typeddicts<'db>(db: &'db dyn Db, ty: Type<'db>) -> bool {
|
||||
match ty {
|
||||
Type::TypedDict(_) => true,
|
||||
Type::Union(union) => {
|
||||
union
|
||||
.elements(db)
|
||||
.iter()
|
||||
.any(|union_member_ty| match union_member_ty {
|
||||
Type::TypedDict(_) => true,
|
||||
Type::Intersection(intersection) => {
|
||||
intersection.positive(db).iter().any(Type::is_typed_dict)
|
||||
}
|
||||
_ => false,
|
||||
})
|
||||
Type::Intersection(intersection) => {
|
||||
intersection.positive(db).iter().any(Type::is_typed_dict)
|
||||
}
|
||||
Type::Union(union) => union
|
||||
.elements(db)
|
||||
.iter()
|
||||
.any(|union_member_ty| is_typeddict_or_union_with_typeddicts(db, *union_member_ty)),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -879,7 +879,7 @@ pub(super) fn validate_typed_dict_dict_literal<'db>(
|
||||
}
|
||||
}
|
||||
|
||||
#[salsa::interned(debug)]
|
||||
#[salsa::interned(debug, heap_size=ruff_memory_usage::heap_size)]
|
||||
pub struct SynthesizedTypedDictType<'db> {
|
||||
#[returns(ref)]
|
||||
pub(crate) items: TypedDictSchema<'db>,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -40,15 +40,15 @@ thiserror = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
tracing-subscriber = { workspace = true, features = ["chrono"] }
|
||||
|
||||
[target.'cfg(target_vendor = "apple")'.dependencies]
|
||||
libc = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
dunce = { workspace = true }
|
||||
insta = { workspace = true, features = ["filters", "json"] }
|
||||
regex = { workspace = true }
|
||||
tempfile = { workspace = true }
|
||||
smallvec = { workspace = true }
|
||||
|
||||
[target.'cfg(target_vendor = "apple")'.dependencies]
|
||||
libc = { workspace = true }
|
||||
tempfile = { workspace = true }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
@@ -12,8 +12,8 @@ license = { workspace = true }
|
||||
[lib]
|
||||
doctest = false
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
ruff_macros = { workspace = true }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
@@ -15,10 +15,10 @@ ruff_db = { workspace = true, features = ["os", "testing"] }
|
||||
ruff_diagnostics = { workspace = true }
|
||||
ruff_index = { workspace = true }
|
||||
ruff_notebook = { workspace = true }
|
||||
ruff_python_ast = { workspace = true }
|
||||
ruff_python_trivia = { workspace = true }
|
||||
ruff_source_file = { workspace = true }
|
||||
ruff_text_size = { workspace = true }
|
||||
ruff_python_ast = { workspace = true }
|
||||
ty_module_resolver = { workspace = true }
|
||||
ty_python_semantic = { workspace = true, features = ["serde", "testing"] }
|
||||
ty_static = { workspace = true }
|
||||
@@ -26,8 +26,8 @@ ty_vendored = { workspace = true }
|
||||
|
||||
anyhow = { workspace = true }
|
||||
camino = { workspace = true }
|
||||
dunce = { workspace = true }
|
||||
colored = { workspace = true }
|
||||
dunce = { workspace = true }
|
||||
insta = { workspace = true, features = ["filters"] }
|
||||
memchr = { workspace = true }
|
||||
path-slash = { workspace = true }
|
||||
@@ -35,12 +35,12 @@ regex = { workspace = true }
|
||||
rustc-hash = { workspace = true }
|
||||
rustc-stable-hash = { workspace = true }
|
||||
salsa = { workspace = true }
|
||||
smallvec = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
smallvec = { workspace = true }
|
||||
tempfile = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
toml = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
@@ -11,13 +11,14 @@ repository = { workspace = true }
|
||||
license = { workspace = true }
|
||||
description = "WebAssembly bindings for ty"
|
||||
|
||||
[package.metadata.cargo-shear]
|
||||
# Depended on only to enable `log` feature as of 2025-10-03.
|
||||
ignored = ["tracing"]
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
doctest = false
|
||||
|
||||
[features]
|
||||
default = ["console_error_panic_hook"]
|
||||
|
||||
[dependencies]
|
||||
ty_ide = { workspace = true }
|
||||
ty_project = { workspace = true, default-features = false, features = [
|
||||
@@ -35,11 +36,11 @@ ruff_text_size = { workspace = true }
|
||||
|
||||
console_error_panic_hook = { workspace = true, optional = true }
|
||||
console_log = { workspace = true }
|
||||
js-sys = { workspace = true }
|
||||
log = { workspace = true }
|
||||
# Not a direct dependency but required to enable the `wasm_js` feature.
|
||||
# See https://docs.rs/getrandom/latest/getrandom/#webassembly-support
|
||||
getrandom = { workspace = true, features = ["wasm_js"] }
|
||||
js-sys = { workspace = true }
|
||||
log = { workspace = true }
|
||||
serde-wasm-bindgen = { workspace = true }
|
||||
tracing = { workspace = true, features = ["log"] }
|
||||
# Not a direct dependency but required to compile for Wasm.
|
||||
@@ -50,9 +51,8 @@ wasm-bindgen = { workspace = true }
|
||||
[dev-dependencies]
|
||||
wasm-bindgen-test = { workspace = true }
|
||||
|
||||
[features]
|
||||
default = ["console_error_panic_hook"]
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[package.metadata.cargo-shear]
|
||||
# Depended on only to enable `log` feature as of 2025-10-03.
|
||||
ignored = ["tracing"]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -17,3 +17,4 @@ exclude = ["./ty_benchmark"]
|
||||
[tool.ty.rules]
|
||||
possibly-unresolved-reference = "error"
|
||||
division-by-zero = "error"
|
||||
unused-ignore-comment = "error"
|
||||
|
||||
@@ -34,3 +34,4 @@ ignore = [
|
||||
[tool.ty.rules]
|
||||
possibly-unresolved-reference = "error"
|
||||
division-by-zero = "error"
|
||||
unused-ignore-comment = "error"
|
||||
|
||||
Reference in New Issue
Block a user