diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 7730a7e517aad..378ea0fa6dd54 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -619,7 +619,7 @@ jobs: run: cargo codspeed build -p ruff_benchmark - name: "Run benchmarks" - uses: CodSpeedHQ/action@v2 + uses: CodSpeedHQ/action@v3 with: run: cargo codspeed run token: ${{ secrets.CODSPEED_TOKEN }} diff --git a/.github/workflows/pr-comment.yaml b/.github/workflows/pr-comment.yaml index 429f1f2c749c2..8d67b35082432 100644 --- a/.github/workflows/pr-comment.yaml +++ b/.github/workflows/pr-comment.yaml @@ -23,6 +23,7 @@ jobs: name: pr-number run_id: ${{ github.event.workflow_run.id || github.event.inputs.workflow_run_id }} if_no_artifact_found: ignore + allow_forks: true - name: Parse pull request number id: pr-number @@ -43,6 +44,7 @@ jobs: path: pr/ecosystem workflow_conclusion: completed if_no_artifact_found: ignore + allow_forks: true - name: Generate comment content id: generate-comment diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 116df8fd546f0..b8881ec08dbe3 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -10,7 +10,7 @@ exclude: | crates/ruff_python_formatter/tests/snapshots/.*| crates/ruff_python_resolver/resources/.*| crates/ruff_python_resolver/tests/snapshots/.*| - crates/red_knot/resources/.* + crates/red_knot_workspace/resources/.* )$ repos: @@ -43,7 +43,7 @@ repos: )$ - repo: https://github.com/crate-ci/typos - rev: v1.23.2 + rev: v1.23.5 hooks: - id: typos @@ -57,7 +57,7 @@ repos: pass_filenames: false # This makes it a lot faster - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.5.4 + rev: v0.5.5 hooks: - id: ruff-format - id: ruff diff --git a/Cargo.lock b/Cargo.lock index 6cb2de709a825..ab8ead5b80cb5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -141,9 +141,9 @@ checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" [[package]] name = "argfile" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7c5c8e418080ef8aa932039d12eda7b6f5043baf48f1523c166fbc32d004534" +checksum = "0a1cc0ba69de57db40674c66f7cf2caee3981ddef084388482c95c0e2133e5e8" dependencies = [ "fs-err", "os_str_bytes", @@ -188,11 +188,23 @@ version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +[[package]] +name = "boomphf" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "617e2d952880a00583ddb9237ac3965732e8df6a92a8e7bcc054100ec467ec3b" +dependencies = [ + "crossbeam-utils", + "log", + "rayon", + "wyhash", +] + [[package]] name = "bstr" -version = "1.9.1" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05efc5cfd9110c8416e471df0e96702d58690178e206e61b7173706673c93706" +checksum = "40723b8fb387abc38f4f4a37c09073622e41dd12327033091ef8950659e6dc0c" dependencies = [ "memchr", "regex-automata 0.4.6", @@ -314,9 +326,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.9" +version = "4.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64acc1846d54c1fe936a78dc189c34e28d3f5afc348403f28ecf53660b9b8462" +checksum = "35723e6a11662c2afb578bcf0b88bf6ea8e21282a953428f240574fcc3a2b5b3" dependencies = [ "clap_builder", "clap_derive", @@ -324,9 +336,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.9" +version = "4.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fb8393d67ba2e7bfaf28a23458e4e2b543cc73a99595511eb207fdb8aede942" +checksum = "49eb96cbfa7cfa35017b7cd548c75b14c3118c98b423041d70562665e07fb0fa" dependencies = [ "anstream", "anstyle", @@ -367,9 +379,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.8" +version = "4.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bac35c6dafb060fd4d275d9a4ffae97917c13a6327903a8be2153cd964f7085" +checksum = "5d029b67f89d30bbb547c89fd5161293c0aec155fc691d7924b64550662db93e" dependencies = [ "heck", "proc-macro2", @@ -759,9 +771,9 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.11.3" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38b35839ba51819680ba087cd351788c9a3c476841207e0b8cee0b04722343b9" +checksum = "e13fa619b91fb2381732789fc5de83b45675e882f66623b7d8cb4f643017018d" dependencies = [ "anstream", "anstyle", @@ -930,9 +942,9 @@ dependencies = [ [[package]] name = "hashlink" -version = "0.8.4" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" +checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af" dependencies = [ "hashbrown", ] @@ -1021,9 +1033,9 @@ dependencies = [ [[package]] name = "imara-diff" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af13c8ceb376860ff0c6a66d83a8cdd4ecd9e464da24621bbffcd02b49619434" +checksum = "fc9da1a252bd44cd341657203722352efc9bc0c847d06ea6d2dc1cd1135e0a01" dependencies = [ "ahash", "hashbrown", @@ -1031,9 +1043,9 @@ dependencies = [ [[package]] name = "imperative" -version = "1.0.5" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b70798296d538cdaa6d652941fcc795963f8b9878b9e300c9fab7a522bd2fc0" +checksum = "29a1f6526af721f9aec9ceed7ab8ebfca47f3399d08b80056c2acca3fcb694a9" dependencies = [ "phf", "rust-stemmers", @@ -1525,11 +1537,85 @@ dependencies = [ "indexmap", ] +[[package]] +name = "orx-concurrent-ordered-bag" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aa866e2be4aa03927eddb481e7c479d5109fe3121324fb7db6d97f91adf9876" +dependencies = [ + "orx-fixed-vec", + "orx-pinned-concurrent-col", + "orx-pinned-vec", + "orx-pseudo-default", + "orx-split-vec", +] + +[[package]] +name = "orx-concurrent-vec" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5912426ffb660f8b61e8f0812a1d07400803cd5513969d2c7af4d69602ba8a1" +dependencies = [ + "orx-concurrent-ordered-bag", + "orx-fixed-vec", + "orx-pinned-concurrent-col", + "orx-pinned-vec", + "orx-pseudo-default", + "orx-split-vec", +] + +[[package]] +name = "orx-fixed-vec" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f69466c7c1fc2e1f00b58e39059b78c438b9fad144d1937ef177ecfc413e997" +dependencies = [ + "orx-pinned-vec", + "orx-pseudo-default", +] + +[[package]] +name = "orx-pinned-concurrent-col" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdbcb1fa05dc1676f1c9cf19f443b3d2d2ca5835911477d22fa77cad8b79208d" +dependencies = [ + "orx-fixed-vec", + "orx-pinned-vec", + "orx-pseudo-default", + "orx-split-vec", +] + +[[package]] +name = "orx-pinned-vec" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1071baf586de45722668234bddf56c52c1ece6a6153d16541bbb0505f0ac055" +dependencies = [ + "orx-pseudo-default", +] + +[[package]] +name = "orx-pseudo-default" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2f627c439e723fa78e410a0faba89047a8a47d0dc013da5c0e05806e8a6cddb" + +[[package]] +name = "orx-split-vec" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52b9dbfa8c7069ae73a890870d3aa9097a897d616751d3d0278f2b42d5214730" +dependencies = [ + "orx-pinned-vec", + "orx-pseudo-default", +] + [[package]] name = "os_str_bytes" -version = "6.6.1" +version = "7.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1" +checksum = "7ac44c994af577c799b1b4bd80dc214701e349873ad894d6cdf96f4f7526e0b9" dependencies = [ "memchr", ] @@ -1858,13 +1944,11 @@ dependencies = [ "countme", "crossbeam", "ctrlc", - "notify", + "filetime", "rayon", "red_knot_module_resolver", - "red_knot_python_semantic", + "red_knot_workspace", "ruff_db", - "ruff_python_ast", - "rustc-hash 2.0.0", "salsa", "tempfile", "tracing", @@ -1912,6 +1996,22 @@ dependencies = [ "tracing", ] +[[package]] +name = "red_knot_workspace" +version = "0.0.0" +dependencies = [ + "anyhow", + "crossbeam", + "notify", + "red_knot_module_resolver", + "red_knot_python_semantic", + "ruff_db", + "ruff_python_ast", + "rustc-hash 2.0.0", + "salsa", + "tracing", +] + [[package]] name = "redox_syscall" version = "0.4.1" @@ -2050,7 +2150,7 @@ dependencies = [ "codspeed-criterion-compat", "mimalloc", "once_cell", - "red_knot", + "red_knot_workspace", "ruff_db", "ruff_linter", "ruff_python_ast", @@ -2087,6 +2187,8 @@ dependencies = [ "filetime", "ignore", "insta", + "matchit", + "path-slash", "ruff_cache", "ruff_notebook", "ruff_python_ast", @@ -2231,6 +2333,7 @@ dependencies = [ "thiserror", "toml", "typed-arena", + "unicode-normalization", "unicode-width", "unicode_names2", "url", @@ -2636,25 +2739,34 @@ checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" [[package]] name = "salsa" version = "0.18.0" -source = "git+https://github.com/salsa-rs/salsa.git?rev=a1bf3a613f451af7fc0a59411c56abc47fe8e8e1#a1bf3a613f451af7fc0a59411c56abc47fe8e8e1" +source = "git+https://github.com/MichaReiser/salsa.git?rev=0cae5c52a3240172ef0be5c9d19e63448c53397c#0cae5c52a3240172ef0be5c9d19e63448c53397c" dependencies = [ "arc-swap", + "boomphf", "crossbeam", - "dashmap 5.5.3", + "dashmap 6.0.1", "hashlink", "indexmap", - "log", + "orx-concurrent-vec", "parking_lot", - "rustc-hash 1.1.0", + "rustc-hash 2.0.0", + "salsa-macro-rules", "salsa-macros", "smallvec", + "tracing", ] +[[package]] +name = "salsa-macro-rules" +version = "0.1.0" +source = "git+https://github.com/MichaReiser/salsa.git?rev=0cae5c52a3240172ef0be5c9d19e63448c53397c#0cae5c52a3240172ef0be5c9d19e63448c53397c" + [[package]] name = "salsa-macros" version = "0.18.0" -source = "git+https://github.com/salsa-rs/salsa.git?rev=a1bf3a613f451af7fc0a59411c56abc47fe8e8e1#a1bf3a613f451af7fc0a59411c56abc47fe8e8e1" +source = "git+https://github.com/MichaReiser/salsa.git?rev=0cae5c52a3240172ef0be5c9d19e63448c53397c#0cae5c52a3240172ef0be5c9d19e63448c53397c" dependencies = [ + "heck", "proc-macro2", "quote", "syn", @@ -2756,11 +2868,12 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.120" +version = "1.0.121" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e0d21c9a8cae1235ad58a00c11cb40d4b1e5c784f1ef2c537876ed6ffd8b7c5" +checksum = "4ab380d7d9f22ef3f21ad3e6c1ebe8e4fc7a2000ccba2e4d71fc96f15b2cb609" dependencies = [ "itoa", + "memchr", "ryu", "serde", ] @@ -2778,9 +2891,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "0.6.6" +version = "0.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79e674e01f999af37c49f70a6ede167a8a60b2503e56c5599532a65baa5969a0" +checksum = "eb5b1b31579f3811bf615c144393417496f152e12ac8b7663bf664f4a815306d" dependencies = [ "serde", ] @@ -3081,9 +3194,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "toml" -version = "0.8.15" +version = "0.8.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac2caab0bf757388c6c0ae23b3293fdb463fee59434529014f85e3263b995c28" +checksum = "81967dd0dd2c1ab0bc3468bd7caecc32b8a4aa47d0c8c695d8c2b2108168d62c" dependencies = [ "serde", "serde_spanned", @@ -3093,18 +3206,18 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.6" +version = "0.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf" +checksum = "f8fb9f64314842840f1d940ac544da178732128f1c78c21772e876579e0da1db" dependencies = [ "serde", ] [[package]] name = "toml_edit" -version = "0.22.16" +version = "0.22.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "278f3d518e152219c994ce877758516bca5e118eaed6996192a774fb9fbf0788" +checksum = "8d9f8729f5aea9562aac1cc0441f5d6de3cff1ee0c5d67293eeca5eb36ee7c16" dependencies = [ "indexmap", "serde", @@ -3751,6 +3864,15 @@ version = "0.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904" +[[package]] +name = "wyhash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf6e163c25e3fac820b4b453185ea2dea3b6a3e0a721d4d23d75bd33734c295" +dependencies = [ + "rand_core", +] + [[package]] name = "yansi" version = "0.5.1" diff --git a/Cargo.toml b/Cargo.toml index 8a3b382a6f41e..a6bfb5d5e79d4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ resolver = "2" [workspace.package] edition = "2021" -rust-version = "1.75" +rust-version = "1.76" homepage = "https://docs.astral.sh/ruff" documentation = "https://docs.astral.sh/ruff" repository = "https://github.com/astral-sh/ruff" @@ -35,9 +35,9 @@ ruff_source_file = { path = "crates/ruff_source_file" } ruff_text_size = { path = "crates/ruff_text_size" } ruff_workspace = { path = "crates/ruff_workspace" } -red_knot = { path = "crates/red_knot" } red_knot_module_resolver = { path = "crates/red_knot_module_resolver" } red_knot_python_semantic = { path = "crates/red_knot_python_semantic" } +red_knot_workspace = { path = "crates/red_knot_workspace" } aho-corasick = { version = "1.1.3" } annotate-snippets = { version = "0.9.2", features = ["color"] } @@ -107,7 +107,7 @@ rand = { version = "0.8.5" } rayon = { version = "1.10.0" } regex = { version = "1.10.2" } rustc-hash = { version = "2.0.0" } -salsa = { git = "https://github.com/salsa-rs/salsa.git", rev = "a1bf3a613f451af7fc0a59411c56abc47fe8e8e1" } +salsa = { git = "https://github.com/MichaReiser/salsa.git", rev = "0cae5c52a3240172ef0be5c9d19e63448c53397c" } schemars = { version = "0.8.16" } seahash = { version = "4.1.0" } serde = { version = "1.0.197", features = ["derive"] } @@ -156,6 +156,7 @@ zip = { version = "0.6.6", default-features = false, features = ["zstd"] } [workspace.lints.rust] unsafe_code = "warn" unreachable_pub = "warn" +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(fuzzing)'] } [workspace.lints.clippy] pedantic = { level = "warn", priority = -2 } diff --git a/README.md b/README.md index 18f4465e9c879..45ceffd3a6acc 100644 --- a/README.md +++ b/README.md @@ -424,6 +424,7 @@ Ruff is used by a number of major open-source projects and companies, including: - [Dagger](https://github.com/dagger/dagger) - [Dagster](https://github.com/dagster-io/dagster) - Databricks ([MLflow](https://github.com/mlflow/mlflow)) +- [Dify](https://github.com/langgenius/dify) - [FastAPI](https://github.com/tiangolo/fastapi) - [Godot](https://github.com/godotengine/godot) - [Gradio](https://github.com/gradio-app/gradio) diff --git a/clippy.toml b/clippy.toml index 6cacb00103ac1..12052f24fd26e 100644 --- a/clippy.toml +++ b/clippy.toml @@ -10,4 +10,12 @@ doc-valid-idents = [ "SCREAMING_SNAKE_CASE", "SQLAlchemy", "StackOverflow", + "PyCharm", +] + +ignore-interior-mutability = [ + # Interned is read-only. The wrapped `Rc` never gets updated. + "ruff_formatter::format_element::Interned", + # The expression is read-only. + "ruff_python_ast::hashable::HashableExpr", ] diff --git a/crates/red_knot/Cargo.toml b/crates/red_knot/Cargo.toml index a4c6166d604f4..622cf7fc00324 100644 --- a/crates/red_knot/Cargo.toml +++ b/crates/red_knot/Cargo.toml @@ -13,25 +13,23 @@ license.workspace = true [dependencies] red_knot_module_resolver = { workspace = true } -red_knot_python_semantic = { workspace = true } +red_knot_workspace = { workspace = true } ruff_db = { workspace = true, features = ["os", "cache"] } -ruff_python_ast = { workspace = true } anyhow = { workspace = true } clap = { workspace = true, features = ["wrap_help"] } countme = { workspace = true, features = ["enable"] } crossbeam = { workspace = true } ctrlc = { version = "3.4.4" } -notify = { workspace = true } rayon = { workspace = true } -rustc-hash = { workspace = true } salsa = { workspace = true } tracing = { workspace = true } tracing-subscriber = { workspace = true } tracing-tree = { workspace = true } [dev-dependencies] +filetime = { workspace = true } tempfile = { workspace = true } diff --git a/crates/red_knot/src/main.rs b/crates/red_knot/src/main.rs index b8ad8022f8911..812d994d3a152 100644 --- a/crates/red_knot/src/main.rs +++ b/crates/red_knot/src/main.rs @@ -2,7 +2,6 @@ use std::sync::Mutex; use clap::Parser; use crossbeam::channel as crossbeam_channel; -use salsa::ParallelDatabase; use tracing::subscriber::Interest; use tracing::{Level, Metadata}; use tracing_subscriber::filter::LevelFilter; @@ -10,10 +9,10 @@ use tracing_subscriber::layer::{Context, Filter, SubscriberExt}; use tracing_subscriber::{Layer, Registry}; use tracing_tree::time::Uptime; -use red_knot::db::RootDatabase; -use red_knot::watch; -use red_knot::watch::WorkspaceWatcher; -use red_knot::workspace::WorkspaceMetadata; +use red_knot_workspace::db::RootDatabase; +use red_knot_workspace::watch; +use red_knot_workspace::watch::WorkspaceWatcher; +use red_knot_workspace::workspace::WorkspaceMetadata; use ruff_db::program::{ProgramSettings, SearchPathSettings}; use ruff_db::system::{OsSystem, System, SystemPathBuf}; @@ -111,7 +110,7 @@ pub fn main() -> anyhow::Result<()> { // TODO: Use the `program_settings` to compute the key for the database's persistent // cache and load the cache if it exists. - let mut db = RootDatabase::new(workspace_metadata, program_settings, system); + let db = RootDatabase::new(workspace_metadata, program_settings, system); let (main_loop, main_loop_cancellation_token) = MainLoop::new(verbosity); @@ -125,11 +124,14 @@ pub fn main() -> anyhow::Result<()> { } })?; + let mut db = salsa::Handle::new(db); if watch { main_loop.watch(&mut db)?; } else { main_loop.run(&mut db); - } + }; + + std::mem::forget(db); Ok(()) } @@ -162,7 +164,7 @@ impl MainLoop { ) } - fn watch(mut self, db: &mut RootDatabase) -> anyhow::Result<()> { + fn watch(mut self, db: &mut salsa::Handle) -> anyhow::Result<()> { let sender = self.sender.clone(); let watcher = watch::directory_watcher(move |event| { sender.send(MainLoopMessage::ApplyChanges(event)).unwrap(); @@ -170,12 +172,11 @@ impl MainLoop { self.watcher = Some(WorkspaceWatcher::new(watcher, db)); self.run(db); - Ok(()) } #[allow(clippy::print_stderr)] - fn run(mut self, db: &mut RootDatabase) { + fn run(mut self, db: &mut salsa::Handle) { // Schedule the first check. self.sender.send(MainLoopMessage::CheckWorkspace).unwrap(); let mut revision = 0usize; @@ -185,7 +186,7 @@ impl MainLoop { match message { MainLoopMessage::CheckWorkspace => { - let db = db.snapshot(); + let db = db.clone(); let sender = self.sender.clone(); // Spawn a new task that checks the workspace. This needs to be done in a separate thread @@ -220,7 +221,7 @@ impl MainLoop { MainLoopMessage::ApplyChanges(changes) => { revision += 1; // Automatically cancels any pending queries and waits for them to complete. - db.apply_changes(changes); + db.get_mut().apply_changes(changes); if let Some(watcher) = self.watcher.as_mut() { watcher.update(db); } @@ -231,6 +232,8 @@ impl MainLoop { } } } + + self.exit(); } #[allow(clippy::print_stderr, clippy::unused_self)] @@ -296,6 +299,9 @@ impl LoggingFilter { fn is_enabled(&self, meta: &Metadata<'_>) -> bool { let filter = if meta.target().starts_with("red_knot") || meta.target().starts_with("ruff") { self.trace_level + } else if meta.target().starts_with("salsa") && self.trace_level <= Level::INFO { + // Salsa emits very verbose query traces with level info. Let's not show these to the user. + Level::WARN } else { Level::INFO }; diff --git a/crates/red_knot/tests/file_watching.rs b/crates/red_knot/tests/file_watching.rs index d5bb50901dd39..c74ab4efdb287 100644 --- a/crates/red_knot/tests/file_watching.rs +++ b/crates/red_knot/tests/file_watching.rs @@ -1,15 +1,17 @@ #![allow(clippy::disallowed_names)] +use std::io::Write; use std::time::Duration; use anyhow::{anyhow, Context}; +use salsa::Setter; -use red_knot::db::RootDatabase; -use red_knot::watch; -use red_knot::watch::{directory_watcher, WorkspaceWatcher}; -use red_knot::workspace::WorkspaceMetadata; use red_knot_module_resolver::{resolve_module, ModuleName}; -use ruff_db::files::{system_path_to_file, File}; +use red_knot_workspace::db::RootDatabase; +use red_knot_workspace::watch; +use red_knot_workspace::watch::{directory_watcher, WorkspaceWatcher}; +use red_knot_workspace::workspace::WorkspaceMetadata; +use ruff_db::files::{system_path_to_file, File, FileError}; use ruff_db::program::{Program, ProgramSettings, SearchPathSettings, TargetVersion}; use ruff_db::source::source_text; use ruff_db::system::{OsSystem, SystemPath, SystemPathBuf}; @@ -19,7 +21,10 @@ struct TestCase { db: RootDatabase, watcher: Option, changes_receiver: crossbeam::channel::Receiver>, - temp_dir: tempfile::TempDir, + /// The temporary directory that contains the test files. + /// We need to hold on to it in the test case or the temp files get deleted. + _temp_dir: tempfile::TempDir, + root_dir: SystemPathBuf, } impl TestCase { @@ -28,7 +33,7 @@ impl TestCase { } fn root_path(&self) -> &SystemPath { - SystemPath::from_std_path(self.temp_dir.path()).unwrap() + &self.root_dir } fn db(&self) -> &RootDatabase { @@ -40,19 +45,63 @@ impl TestCase { } fn stop_watch(&mut self) -> Vec { - if let Some(watcher) = self.watcher.take() { - // Give the watcher some time to catch up. - std::thread::sleep(Duration::from_millis(10)); - watcher.flush(); - watcher.stop(); + self.try_stop_watch(Duration::from_secs(10)) + .expect("Expected watch changes but observed none.") + } + + fn try_stop_watch(&mut self, timeout: Duration) -> Option> { + let watcher = self + .watcher + .take() + .expect("Cannot call `stop_watch` more than once."); + + let mut all_events = self + .changes_receiver + .recv_timeout(timeout) + .unwrap_or_default(); + watcher.flush(); + watcher.stop(); + + for event in &self.changes_receiver { + all_events.extend(event); + } + + if all_events.is_empty() { + return None; } - let mut all_events = Vec::new(); - for events in &self.changes_receiver { - all_events.extend(events); + Some(all_events) + } + + #[cfg(unix)] + fn take_watch_changes(&self) -> Vec { + self.try_take_watch_changes(Duration::from_secs(10)) + .expect("Expected watch changes but observed none.") + } + + fn try_take_watch_changes(&self, timeout: Duration) -> Option> { + let Some(watcher) = &self.watcher else { + return None; + }; + + let mut all_events = self + .changes_receiver + .recv_timeout(timeout) + .unwrap_or_default(); + watcher.flush(); + + while let Ok(event) = self + .changes_receiver + .recv_timeout(Duration::from_millis(10)) + { + all_events.extend(event); + watcher.flush(); } - all_events + if all_events.is_empty() { + return None; + } + Some(all_events) } fn update_search_path_settings( @@ -81,33 +130,67 @@ impl TestCase { collected } - fn system_file(&self, path: impl AsRef) -> Option { + fn system_file(&self, path: impl AsRef) -> Result { system_path_to_file(self.db(), path.as_ref()) } } -fn setup(workspace_files: I) -> anyhow::Result +trait SetupFiles { + fn setup(self, root_path: &SystemPath, workspace_path: &SystemPath) -> anyhow::Result<()>; +} + +impl SetupFiles for [(P, &'static str); N] where - I: IntoIterator, P: AsRef, { - setup_with_search_paths(workspace_files, |_root, workspace_path| { - SearchPathSettings { - extra_paths: vec![], - workspace_root: workspace_path.to_path_buf(), - custom_typeshed: None, - site_packages: None, + fn setup(self, _root_path: &SystemPath, workspace_path: &SystemPath) -> anyhow::Result<()> { + for (relative_path, content) in self { + let relative_path = relative_path.as_ref(); + let absolute_path = workspace_path.join(relative_path); + if let Some(parent) = absolute_path.parent() { + std::fs::create_dir_all(parent).with_context(|| { + format!("Failed to create parent directory for file '{relative_path}'.",) + })?; + } + + let mut file = std::fs::File::create(absolute_path.as_std_path()) + .with_context(|| format!("Failed to open file '{relative_path}'"))?; + file.write_all(content.as_bytes()) + .with_context(|| format!("Failed to write to file '{relative_path}'"))?; + file.sync_data()?; } + + Ok(()) + } +} + +impl SetupFiles for F +where + F: FnOnce(&SystemPath, &SystemPath) -> anyhow::Result<()>, +{ + fn setup(self, root_path: &SystemPath, workspace_path: &SystemPath) -> anyhow::Result<()> { + self(root_path, workspace_path) + } +} + +fn setup(setup_files: F) -> anyhow::Result +where + F: SetupFiles, +{ + setup_with_search_paths(setup_files, |_root, workspace_path| SearchPathSettings { + extra_paths: vec![], + workspace_root: workspace_path.to_path_buf(), + custom_typeshed: None, + site_packages: None, }) } -fn setup_with_search_paths( - workspace_files: I, +fn setup_with_search_paths( + setup_files: F, create_search_paths: impl FnOnce(&SystemPath, &SystemPath) -> SearchPathSettings, ) -> anyhow::Result where - I: IntoIterator, - P: AsRef, + F: SetupFiles, { let temp_dir = tempfile::tempdir()?; @@ -130,18 +213,9 @@ where std::fs::create_dir_all(workspace_path.as_std_path()) .with_context(|| format!("Failed to create workspace directory '{workspace_path}'",))?; - for (relative_path, content) in workspace_files { - let relative_path = relative_path.as_ref(); - let absolute_path = workspace_path.join(relative_path); - if let Some(parent) = absolute_path.parent() { - std::fs::create_dir_all(parent).with_context(|| { - format!("Failed to create parent directory for file '{relative_path}'.",) - })?; - } - - std::fs::write(absolute_path.as_std_path(), content) - .with_context(|| format!("Failed to write file '{relative_path}'"))?; - } + setup_files + .setup(&root_path, &workspace_path) + .context("Failed to setup test files")?; let system = OsSystem::new(&workspace_path); @@ -176,12 +250,46 @@ where db, changes_receiver: receiver, watcher: Some(watcher), - temp_dir, + _temp_dir: temp_dir, + root_dir: root_path, }; + // Sometimes the file watcher reports changes for events that happened before the watcher was started. + // Do a best effort at dropping these events. + test_case.try_take_watch_changes(Duration::from_millis(100)); + Ok(test_case) } +/// Updates the content of a file and ensures that the last modified file time is updated. +fn update_file(path: impl AsRef, content: &str) -> anyhow::Result<()> { + let path = path.as_ref().as_std_path(); + + let metadata = path.metadata()?; + let last_modified_time = filetime::FileTime::from_last_modification_time(&metadata); + + let mut file = std::fs::OpenOptions::new() + .create(false) + .write(true) + .truncate(true) + .open(path)?; + file.write_all(content.as_bytes())?; + + loop { + file.sync_all()?; + + let modified_time = filetime::FileTime::from_last_modification_time(&path.metadata()?); + + if modified_time != last_modified_time { + break Ok(()); + } + + std::thread::sleep(Duration::from_nanos(10)); + + filetime::set_file_handle_times(&file, None, Some(filetime::FileTime::now()))?; + } +} + #[test] fn new_file() -> anyhow::Result<()> { let mut case = setup([("bar.py", "")])?; @@ -189,7 +297,7 @@ fn new_file() -> anyhow::Result<()> { let bar_file = case.system_file(&bar_path).unwrap(); let foo_path = case.workspace_path("foo.py"); - assert_eq!(case.system_file(&foo_path), None); + assert_eq!(case.system_file(&foo_path), Err(FileError::NotFound)); assert_eq!(&case.collect_package_files(&bar_path), &[bar_file]); std::fs::write(foo_path.as_std_path(), "print('Hello')")?; @@ -212,7 +320,7 @@ fn new_ignored_file() -> anyhow::Result<()> { let bar_file = case.system_file(&bar_path).unwrap(); let foo_path = case.workspace_path("foo.py"); - assert_eq!(case.system_file(&foo_path), None); + assert_eq!(case.system_file(&foo_path), Err(FileError::NotFound)); assert_eq!(&case.collect_package_files(&bar_path), &[bar_file]); std::fs::write(foo_path.as_std_path(), "print('Hello')")?; @@ -221,7 +329,7 @@ fn new_ignored_file() -> anyhow::Result<()> { case.db_mut().apply_changes(changes); - assert!(case.system_file(&foo_path).is_some()); + assert!(case.system_file(&foo_path).is_ok()); assert_eq!(&case.collect_package_files(&bar_path), &[bar_file]); Ok(()) @@ -233,16 +341,16 @@ fn changed_file() -> anyhow::Result<()> { let mut case = setup([("foo.py", foo_source)])?; let foo_path = case.workspace_path("foo.py"); - let foo = case - .system_file(&foo_path) - .ok_or_else(|| anyhow!("Foo not found"))?; + let foo = case.system_file(&foo_path)?; assert_eq!(source_text(case.db(), foo).as_str(), foo_source); assert_eq!(&case.collect_package_files(&foo_path), &[foo]); - std::fs::write(foo_path.as_std_path(), "print('Version 2')")?; + update_file(&foo_path, "print('Version 2')")?; let changes = case.stop_watch(); + assert!(!changes.is_empty()); + case.db_mut().apply_changes(changes); assert_eq!(source_text(case.db(), foo).as_str(), "print('Version 2')"); @@ -251,59 +359,13 @@ fn changed_file() -> anyhow::Result<()> { Ok(()) } -#[cfg(unix)] -#[test] -fn changed_metadata() -> anyhow::Result<()> { - use std::os::unix::fs::PermissionsExt; - - let mut case = setup([("foo.py", "")])?; - let foo_path = case.workspace_path("foo.py"); - - let foo = case - .system_file(&foo_path) - .ok_or_else(|| anyhow!("Foo not found"))?; - assert_eq!( - foo.permissions(case.db()), - Some( - std::fs::metadata(foo_path.as_std_path()) - .unwrap() - .permissions() - .mode() - ) - ); - - std::fs::set_permissions( - foo_path.as_std_path(), - std::fs::Permissions::from_mode(0o777), - ) - .with_context(|| "Failed to set file permissions.")?; - - let changes = case.stop_watch(); - - case.db_mut().apply_changes(changes); - - assert_eq!( - foo.permissions(case.db()), - Some( - std::fs::metadata(foo_path.as_std_path()) - .unwrap() - .permissions() - .mode() - ) - ); - - Ok(()) -} - #[test] fn deleted_file() -> anyhow::Result<()> { let foo_source = "print('Hello, world!')"; let mut case = setup([("foo.py", foo_source)])?; let foo_path = case.workspace_path("foo.py"); - let foo = case - .system_file(&foo_path) - .ok_or_else(|| anyhow!("Foo not found"))?; + let foo = case.system_file(&foo_path)?; assert!(foo.exists(case.db())); assert_eq!(&case.collect_package_files(&foo_path), &[foo]); @@ -332,9 +394,7 @@ fn move_file_to_trash() -> anyhow::Result<()> { let trash_path = case.root_path().join(".trash"); std::fs::create_dir_all(trash_path.as_std_path())?; - let foo = case - .system_file(&foo_path) - .ok_or_else(|| anyhow!("Foo not found"))?; + let foo = case.system_file(&foo_path)?; assert!(foo.exists(case.db())); assert_eq!(&case.collect_package_files(&foo_path), &[foo]); @@ -366,7 +426,7 @@ fn move_file_to_workspace() -> anyhow::Result<()> { let foo_in_workspace_path = case.workspace_path("foo.py"); - assert!(case.system_file(&foo_path).is_some()); + assert!(case.system_file(&foo_path).is_ok()); assert_eq!(&case.collect_package_files(&bar_path), &[bar]); assert!(case .db() @@ -380,9 +440,7 @@ fn move_file_to_workspace() -> anyhow::Result<()> { case.db_mut().apply_changes(changes); - let foo_in_workspace = case - .system_file(&foo_in_workspace_path) - .ok_or_else(|| anyhow!("Foo not found"))?; + let foo_in_workspace = case.system_file(&foo_in_workspace_path)?; assert!(foo_in_workspace.exists(case.db())); assert_eq!( @@ -400,9 +458,7 @@ fn rename_file() -> anyhow::Result<()> { let foo_path = case.workspace_path("foo.py"); let bar_path = case.workspace_path("bar.py"); - let foo = case - .system_file(&foo_path) - .ok_or_else(|| anyhow!("Foo not found"))?; + let foo = case.system_file(&foo_path)?; assert_eq!(case.collect_package_files(&foo_path), [foo]); @@ -414,9 +470,7 @@ fn rename_file() -> anyhow::Result<()> { assert!(!foo.exists(case.db())); - let bar = case - .system_file(&bar_path) - .ok_or_else(|| anyhow!("Bar not found"))?; + let bar = case.system_file(&bar_path)?; assert!(bar.exists(case.db())); assert_eq!(case.collect_package_files(&foo_path), [bar]); @@ -482,7 +536,7 @@ fn directory_moved_to_trash() -> anyhow::Result<()> { ])?; let bar = case.system_file(case.workspace_path("bar.py")).unwrap(); - assert!(resolve_module(case.db().upcast(), ModuleName::new_static("sub.a").unwrap()).is_some(),); + assert!(resolve_module(case.db().upcast(), ModuleName::new_static("sub.a").unwrap()).is_some()); let sub_path = case.workspace_path("sub"); let init_file = case @@ -716,9 +770,464 @@ fn remove_search_path() -> anyhow::Result<()> { std::fs::write(site_packages.join("a.py").as_std_path(), "class A: ...")?; + let changes = case.try_stop_watch(Duration::from_millis(100)); + + assert_eq!(changes, None); + + Ok(()) +} + +/// Watch a workspace that contains two files where one file is a hardlink to another. +/// +/// Setup: +/// ```text +/// - workspace +/// |- foo.py +/// |- bar.py (hard link to foo.py) +/// ``` +/// +/// # Linux +/// `inotify` only emits a single change event for the file that was changed. +/// Other files that point to the same inode (hardlinks) won't get updated. +/// +/// For reference: VS Code and PyCharm have the same behavior where the results for one of the +/// files are stale. +/// +/// # Windows +/// I haven't found any documentation that states the notification behavior on Windows but what +/// we're seeing is that Windows only emits a single event, similar to Linux. +#[test] +fn hard_links_in_workspace() -> anyhow::Result<()> { + let mut case = setup(|_root: &SystemPath, workspace: &SystemPath| { + let foo_path = workspace.join("foo.py"); + std::fs::write(foo_path.as_std_path(), "print('Version 1')")?; + + // Create a hardlink to `foo` + let bar_path = workspace.join("bar.py"); + std::fs::hard_link(foo_path.as_std_path(), bar_path.as_std_path()) + .context("Failed to create hard link from foo.py -> bar.py")?; + + Ok(()) + })?; + + let foo_path = case.workspace_path("foo.py"); + let foo = case.system_file(&foo_path).unwrap(); + let bar_path = case.workspace_path("bar.py"); + let bar = case.system_file(&bar_path).unwrap(); + + assert_eq!(source_text(case.db(), foo).as_str(), "print('Version 1')"); + assert_eq!(source_text(case.db(), bar).as_str(), "print('Version 1')"); + + // Write to the hard link target. + update_file(foo_path, "print('Version 2')").context("Failed to update foo.py")?; + let changes = case.stop_watch(); - assert_eq!(changes, &[]); + case.db_mut().apply_changes(changes); + + assert_eq!(source_text(case.db(), foo).as_str(), "print('Version 2')"); + + // macOS is the only platform that emits events for every hardlink. + if cfg!(target_os = "macos") { + assert_eq!(source_text(case.db(), bar).as_str(), "print('Version 2')"); + } Ok(()) } + +/// Watch a workspace that contains one file that is a hardlink to a file outside the workspace. +/// +/// Setup: +/// ```text +/// - foo.py +/// - workspace +/// |- bar.py (hard link to /foo.py) +/// ``` +/// +/// # Linux +/// inotiyf doesn't support observing changes to hard linked files. +/// +/// > Note: when monitoring a directory, events are not generated for +/// > the files inside the directory when the events are performed via +/// > a pathname (i.e., a link) that lies outside the monitored +/// > directory. [source](https://man7.org/linux/man-pages/man7/inotify.7.html) +/// +/// # Windows +/// > Retrieves information that describes the changes within the specified directory. +/// +/// [source](https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-readdirectorychangesw) +/// +/// My interpretation of this is that Windows doesn't support observing changes made to +/// hard linked files outside the workspace. +#[test] +#[cfg_attr( + target_os = "linux", + ignore = "inotify doesn't support observing changes to hard linked files." +)] +#[cfg_attr( + target_os = "windows", + ignore = "windows doesn't support observing changes to hard linked files." +)] +fn hard_links_to_target_outside_workspace() -> anyhow::Result<()> { + let mut case = setup(|root: &SystemPath, workspace: &SystemPath| { + let foo_path = root.join("foo.py"); + std::fs::write(foo_path.as_std_path(), "print('Version 1')")?; + + // Create a hardlink to `foo` + let bar_path = workspace.join("bar.py"); + std::fs::hard_link(foo_path.as_std_path(), bar_path.as_std_path()) + .context("Failed to create hard link from foo.py -> bar.py")?; + + Ok(()) + })?; + + let foo_path = case.root_path().join("foo.py"); + let foo = case.system_file(&foo_path).unwrap(); + let bar_path = case.workspace_path("bar.py"); + let bar = case.system_file(&bar_path).unwrap(); + + assert_eq!(source_text(case.db(), foo).as_str(), "print('Version 1')"); + assert_eq!(source_text(case.db(), bar).as_str(), "print('Version 1')"); + + // Write to the hard link target. + update_file(foo_path, "print('Version 2')").context("Failed to update foo.py")?; + + let changes = case.stop_watch(); + + case.db_mut().apply_changes(changes); + + assert_eq!(source_text(case.db(), bar).as_str(), "print('Version 2')"); + + Ok(()) +} + +#[cfg(unix)] +mod unix { + //! Tests that make use of unix specific file-system features. + use super::*; + + /// Changes the metadata of the only file in the workspace. + #[test] + fn changed_metadata() -> anyhow::Result<()> { + use std::os::unix::fs::PermissionsExt; + + let mut case = setup([("foo.py", "")])?; + let foo_path = case.workspace_path("foo.py"); + + let foo = case.system_file(&foo_path)?; + assert_eq!( + foo.permissions(case.db()), + Some( + std::fs::metadata(foo_path.as_std_path()) + .unwrap() + .permissions() + .mode() + ) + ); + + std::fs::set_permissions( + foo_path.as_std_path(), + std::fs::Permissions::from_mode(0o777), + ) + .with_context(|| "Failed to set file permissions.")?; + + let changes = case.stop_watch(); + + case.db_mut().apply_changes(changes); + + assert_eq!( + foo.permissions(case.db()), + Some( + std::fs::metadata(foo_path.as_std_path()) + .unwrap() + .permissions() + .mode() + ) + ); + + Ok(()) + } + + /// A workspace path is a symlink to a file outside the workspace. + /// + /// Setup: + /// ```text + /// - bar + /// |- baz.py + /// + /// - workspace + /// |- bar -> /bar + /// ``` + /// + /// # macOS + /// This test case isn't supported on macOS. + /// macOS uses `FSEvents` and `FSEvents` doesn't emit an event if a file in a symlinked directory is changed. + /// + /// > Generally speaking, when working with file system event notifications, you will probably want to use lstat, + /// > because changes to the underlying file will not result in a change notification for the directory containing + /// > the symbolic link to that file. However, if you are working with a controlled file structure in + /// > which symbolic links always point within your watched tree, you might have reason to use stat. + /// + /// [source](https://developer.apple.com/library/archive/documentation/Darwin/Conceptual/FSEvents_ProgGuide/UsingtheFSEventsFramework/UsingtheFSEventsFramework.html#//apple_ref/doc/uid/TP40005289-CH4-SW4) + /// + /// Pyright also does not support this case. + #[test] + #[cfg_attr( + target_os = "macos", + ignore = "FSEvents doesn't emit change events for symlinked directories outside of the watched paths." + )] + fn symlink_target_outside_watched_paths() -> anyhow::Result<()> { + let mut case = setup(|root: &SystemPath, workspace: &SystemPath| { + // Set up the symlink target. + let link_target = root.join("bar"); + std::fs::create_dir_all(link_target.as_std_path()) + .context("Failed to create link target directory")?; + let baz_original = link_target.join("baz.py"); + std::fs::write(baz_original.as_std_path(), "def baz(): ...") + .context("Failed to write link target file")?; + + // Create a symlink inside the workspace + let bar = workspace.join("bar"); + std::os::unix::fs::symlink(link_target.as_std_path(), bar.as_std_path()) + .context("Failed to create symlink to bar package")?; + + Ok(()) + })?; + + let baz = resolve_module( + case.db().upcast(), + ModuleName::new_static("bar.baz").unwrap(), + ) + .expect("Expected bar.baz to exist in site-packages."); + let baz_workspace = case.workspace_path("bar/baz.py"); + + assert_eq!( + source_text(case.db(), baz.file()).as_str(), + "def baz(): ..." + ); + assert_eq!( + baz.file().path(case.db()).as_system_path(), + Some(&*baz_workspace) + ); + + let baz_original = case.root_path().join("bar/baz.py"); + + // Write to the symlink target. + update_file(baz_original, "def baz(): print('Version 2')") + .context("Failed to update bar/baz.py")?; + + let changes = case.take_watch_changes(); + + case.db_mut().apply_changes(changes); + + assert_eq!( + source_text(case.db(), baz.file()).as_str(), + "def baz(): print('Version 2')" + ); + + // Write to the symlink source. + update_file(baz_workspace, "def baz(): print('Version 3')") + .context("Failed to update bar/baz.py")?; + + let changes = case.stop_watch(); + + case.db_mut().apply_changes(changes); + + assert_eq!( + source_text(case.db(), baz.file()).as_str(), + "def baz(): print('Version 3')" + ); + + Ok(()) + } + + /// Workspace contains a symlink to another directory inside the workspace. + /// Changes to files in the symlinked directory should be reflected + /// to all files. + /// + /// Setup: + /// ```text + /// - workspace + /// | - bar -> /workspace/patched/bar + /// | + /// | - patched + /// | |-- bar + /// | | |- baz.py + /// | + /// |-- foo.py + /// ``` + #[test] + fn symlink_inside_workspace() -> anyhow::Result<()> { + let mut case = setup(|_root: &SystemPath, workspace: &SystemPath| { + // Set up the symlink target. + let link_target = workspace.join("patched/bar"); + std::fs::create_dir_all(link_target.as_std_path()) + .context("Failed to create link target directory")?; + let baz_original = link_target.join("baz.py"); + std::fs::write(baz_original.as_std_path(), "def baz(): ...") + .context("Failed to write link target file")?; + + // Create a symlink inside site-packages + let bar_in_workspace = workspace.join("bar"); + std::os::unix::fs::symlink(link_target.as_std_path(), bar_in_workspace.as_std_path()) + .context("Failed to create symlink to bar package")?; + + Ok(()) + })?; + + let baz = resolve_module( + case.db().upcast(), + ModuleName::new_static("bar.baz").unwrap(), + ) + .expect("Expected bar.baz to exist in site-packages."); + let bar_baz = case.workspace_path("bar/baz.py"); + + let patched_bar_baz = case.workspace_path("patched/bar/baz.py"); + let patched_bar_baz_file = case.system_file(&patched_bar_baz).unwrap(); + + assert_eq!( + source_text(case.db(), patched_bar_baz_file).as_str(), + "def baz(): ..." + ); + + assert_eq!( + source_text(case.db(), baz.file()).as_str(), + "def baz(): ..." + ); + assert_eq!(baz.file().path(case.db()).as_system_path(), Some(&*bar_baz)); + + // Write to the symlink target. + update_file(&patched_bar_baz, "def baz(): print('Version 2')") + .context("Failed to update bar/baz.py")?; + + let changes = case.stop_watch(); + + case.db_mut().apply_changes(changes); + + // The file watcher is guaranteed to emit one event for the changed file, but it isn't specified + // if the event is emitted for the "original" or linked path because both paths are watched. + // The best we can assert here is that one of the files should have been updated. + // + // In a perfect world, the file watcher would emit two events, one for the original file and + // one for the symlink. I tried parcel/watcher, node's `fs.watch` and `chokidar` and + // only `chokidar seems to support it (used by Pyright). + // + // I further tested how good editor support is for symlinked files and it is not good ;) + // * VS Code doesn't update the file content if a file gets changed through a symlink + // * PyCharm doesn't update diagnostics if a symlinked module is changed (same as red knot). + // + // That's why I think it's fine to not support this case for now. + + let patched_baz_text = source_text(case.db(), patched_bar_baz_file); + let did_update_patched_baz = patched_baz_text.as_str() == "def baz(): print('Version 2')"; + + let bar_baz_text = source_text(case.db(), baz.file()); + let did_update_bar_baz = bar_baz_text.as_str() == "def baz(): print('Version 2')"; + + assert!( + did_update_patched_baz || did_update_bar_baz, + "Expected one of the files to be updated but neither file was updated.\nOriginal: {patched_baz_text}\nSymlinked: {bar_baz_text}", + patched_baz_text = patched_baz_text.as_str(), + bar_baz_text = bar_baz_text.as_str() + ); + + Ok(()) + } + + /// A module search path is a symlink. + /// + /// Setup: + /// ```text + /// - site-packages + /// | - bar/baz.py + /// + /// - workspace + /// |-- .venv/lib/python3.12/site-packages -> /site-packages + /// | + /// |-- foo.py + /// ``` + #[test] + fn symlinked_module_search_path() -> anyhow::Result<()> { + let mut case = setup_with_search_paths( + |root: &SystemPath, workspace: &SystemPath| { + // Set up the symlink target. + let site_packages = root.join("site-packages"); + let bar = site_packages.join("bar"); + std::fs::create_dir_all(bar.as_std_path()) + .context("Failed to create bar directory")?; + let baz_original = bar.join("baz.py"); + std::fs::write(baz_original.as_std_path(), "def baz(): ...") + .context("Failed to write baz.py")?; + + // Symlink the site packages in the venv to the global site packages + let venv_site_packages = workspace.join(".venv/lib/python3.12/site-packages"); + std::fs::create_dir_all(venv_site_packages.parent().unwrap()) + .context("Failed to create .venv directory")?; + std::os::unix::fs::symlink( + site_packages.as_std_path(), + venv_site_packages.as_std_path(), + ) + .context("Failed to create symlink to site-packages")?; + + Ok(()) + }, + |_root, workspace| SearchPathSettings { + extra_paths: vec![], + workspace_root: workspace.to_path_buf(), + custom_typeshed: None, + site_packages: Some(workspace.join(".venv/lib/python3.12/site-packages")), + }, + )?; + + let baz = resolve_module( + case.db().upcast(), + ModuleName::new_static("bar.baz").unwrap(), + ) + .expect("Expected bar.baz to exist in site-packages."); + let baz_site_packages = + case.workspace_path(".venv/lib/python3.12/site-packages/bar/baz.py"); + let baz_original = case.root_path().join("site-packages/bar/baz.py"); + let baz_original_file = case.system_file(&baz_original).unwrap(); + + assert_eq!( + source_text(case.db(), baz_original_file).as_str(), + "def baz(): ..." + ); + + assert_eq!( + source_text(case.db(), baz.file()).as_str(), + "def baz(): ..." + ); + assert_eq!( + baz.file().path(case.db()).as_system_path(), + Some(&*baz_site_packages) + ); + + // Write to the symlink target. + update_file(&baz_original, "def baz(): print('Version 2')") + .context("Failed to update bar/baz.py")?; + + let changes = case.stop_watch(); + + case.db_mut().apply_changes(changes); + + assert_eq!( + source_text(case.db(), baz.file()).as_str(), + "def baz(): print('Version 2')" + ); + + // It would be nice if this is supported but the underlying file system watchers + // only emit a single event. For reference + // * VS Code doesn't update the file content if a file gets changed through a symlink + // * PyCharm doesn't update diagnostics if a symlinked module is changed (same as red knot). + // We could add support for it by keeping a reverse map from `real_path` to symlinked path but + // it doesn't seem worth doing considering that as prominent tools like PyCharm don't support it. + // Pyright does support it, thanks to chokidar. + assert_ne!( + source_text(case.db(), baz_original_file).as_str(), + "def baz(): print('Version 2')" + ); + + Ok(()) + } +} diff --git a/crates/red_knot_module_resolver/src/db.rs b/crates/red_knot_module_resolver/src/db.rs index 3ea9247df9b02..69d20a3ce07d7 100644 --- a/crates/red_knot_module_resolver/src/db.rs +++ b/crates/red_knot_module_resolver/src/db.rs @@ -1,29 +1,12 @@ use ruff_db::Upcast; -use crate::resolver::{ - editable_install_resolution_paths, file_to_module, internal::ModuleNameIngredient, - module_resolution_settings, resolve_module_query, -}; -use crate::typeshed::parse_typeshed_versions; - -#[salsa::jar(db=Db)] -pub struct Jar( - ModuleNameIngredient<'_>, - module_resolution_settings, - editable_install_resolution_paths, - resolve_module_query, - file_to_module, - parse_typeshed_versions, -); - -pub trait Db: salsa::DbWithJar + ruff_db::Db + Upcast {} +#[salsa::db] +pub trait Db: ruff_db::Db + Upcast {} #[cfg(test)] pub(crate) mod tests { use std::sync; - use salsa::DebugWithDb; - use ruff_db::files::Files; use ruff_db::system::{DbWithTestSystem, TestSystem}; use ruff_db::vendored::VendoredFileSystem; @@ -32,7 +15,7 @@ pub(crate) mod tests { use super::*; - #[salsa::db(Jar, ruff_db::Jar)] + #[salsa::db] pub(crate) struct TestDb { storage: salsa::Storage, system: TestSystem, @@ -46,7 +29,7 @@ pub(crate) mod tests { Self { storage: salsa::Storage::default(), system: TestSystem::default(), - vendored: vendored_typeshed_stubs().snapshot(), + vendored: vendored_typeshed_stubs().clone(), events: sync::Arc::default(), files: Files::default(), } @@ -81,6 +64,7 @@ pub(crate) mod tests { } } + #[salsa::db] impl ruff_db::Db for TestDb { fn vendored(&self) -> &VendoredFileSystem { &self.vendored @@ -95,6 +79,7 @@ pub(crate) mod tests { } } + #[salsa::db] impl Db for TestDb {} impl DbWithTestSystem for TestDb { @@ -107,23 +92,14 @@ pub(crate) mod tests { } } + #[salsa::db] impl salsa::Database for TestDb { fn salsa_event(&self, event: salsa::Event) { - tracing::trace!("event: {:?}", event.debug(self)); - let mut events = self.events.lock().unwrap(); - events.push(event); - } - } - - impl salsa::ParallelDatabase for TestDb { - fn snapshot(&self) -> salsa::Snapshot { - salsa::Snapshot::new(Self { - storage: self.storage.snapshot(), - system: self.system.snapshot(), - vendored: self.vendored.snapshot(), - files: self.files.snapshot(), - events: self.events.clone(), - }) + self.attach(|_| { + tracing::trace!("event: {event:?}"); + let mut events = self.events.lock().unwrap(); + events.push(event); + }); } } } diff --git a/crates/red_knot_module_resolver/src/lib.rs b/crates/red_knot_module_resolver/src/lib.rs index efc9cd2c6195a..f0eac6e276d9b 100644 --- a/crates/red_knot_module_resolver/src/lib.rs +++ b/crates/red_knot_module_resolver/src/lib.rs @@ -1,6 +1,6 @@ use std::iter::FusedIterator; -pub use db::{Db, Jar}; +pub use db::Db; pub use module::{Module, ModuleKind}; pub use module_name::ModuleName; pub use resolver::resolve_module; diff --git a/crates/red_knot_module_resolver/src/module.rs b/crates/red_knot_module_resolver/src/module.rs index 037bdd6376f69..e1a783459272d 100644 --- a/crates/red_knot_module_resolver/src/module.rs +++ b/crates/red_knot_module_resolver/src/module.rs @@ -3,7 +3,6 @@ use std::sync::Arc; use ruff_db::files::File; -use crate::db::Db; use crate::module_name::ModuleName; use crate::path::SearchPath; @@ -62,17 +61,6 @@ impl std::fmt::Debug for Module { } } -impl salsa::DebugWithDb for Module { - fn fmt(&self, f: &mut Formatter<'_>, db: &dyn Db) -> std::fmt::Result { - f.debug_struct("Module") - .field("name", &self.name()) - .field("kind", &self.kind()) - .field("file", &self.file().debug(db.upcast())) - .field("search_path", &self.search_path()) - .finish() - } -} - #[derive(PartialEq, Eq)] struct ModuleInner { name: ModuleName, diff --git a/crates/red_knot_module_resolver/src/path.rs b/crates/red_knot_module_resolver/src/path.rs index e9fdcd493d176..a649dd078be45 100644 --- a/crates/red_knot_module_resolver/src/path.rs +++ b/crates/red_knot_module_resolver/src/path.rs @@ -5,7 +5,7 @@ use std::sync::Arc; use camino::{Utf8Path, Utf8PathBuf}; -use ruff_db::files::{system_path_to_file, vendored_path_to_file, File}; +use ruff_db::files::{system_path_to_file, vendored_path_to_file, File, FileError}; use ruff_db::system::{System, SystemPath, SystemPathBuf}; use ruff_db::vendored::{VendoredPath, VendoredPathBuf}; @@ -68,16 +68,18 @@ impl ModulePath { SearchPathInner::Extra(search_path) | SearchPathInner::FirstParty(search_path) | SearchPathInner::SitePackages(search_path) - | SearchPathInner::Editable(search_path) => resolver - .system() - .is_directory(&search_path.join(relative_path)), + | SearchPathInner::Editable(search_path) => { + system_path_to_file(resolver.db.upcast(), search_path.join(relative_path)) + == Err(FileError::IsADirectory) + } SearchPathInner::StandardLibraryCustom(stdlib_root) => { match query_stdlib_version(Some(stdlib_root), relative_path, resolver) { TypeshedVersionsQueryResult::DoesNotExist => false, TypeshedVersionsQueryResult::Exists - | TypeshedVersionsQueryResult::MaybeExists => resolver - .system() - .is_directory(&stdlib_root.join(relative_path)), + | TypeshedVersionsQueryResult::MaybeExists => { + system_path_to_file(resolver.db.upcast(), stdlib_root.join(relative_path)) + == Err(FileError::IsADirectory) + } } } SearchPathInner::StandardLibraryVendored(stdlib_root) => { @@ -105,10 +107,9 @@ impl ModulePath { | SearchPathInner::SitePackages(search_path) | SearchPathInner::Editable(search_path) => { let absolute_path = search_path.join(relative_path); - system_path_to_file(resolver.db.upcast(), absolute_path.join("__init__.py")) - .is_some() + system_path_to_file(resolver.db.upcast(), absolute_path.join("__init__.py")).is_ok() || system_path_to_file(resolver.db.upcast(), absolute_path.join("__init__.py")) - .is_some() + .is_ok() } SearchPathInner::StandardLibraryCustom(search_path) => { match query_stdlib_version(Some(search_path), relative_path, resolver) { @@ -118,7 +119,7 @@ impl ModulePath { resolver.db.upcast(), search_path.join(relative_path).join("__init__.pyi"), ) - .is_some(), + .is_ok(), } } SearchPathInner::StandardLibraryVendored(search_path) => { @@ -145,14 +146,14 @@ impl ModulePath { | SearchPathInner::FirstParty(search_path) | SearchPathInner::SitePackages(search_path) | SearchPathInner::Editable(search_path) => { - system_path_to_file(db, search_path.join(relative_path)) + system_path_to_file(db, search_path.join(relative_path)).ok() } SearchPathInner::StandardLibraryCustom(stdlib_root) => { match query_stdlib_version(Some(stdlib_root), relative_path, resolver) { TypeshedVersionsQueryResult::DoesNotExist => None, TypeshedVersionsQueryResult::Exists | TypeshedVersionsQueryResult::MaybeExists => { - system_path_to_file(db, stdlib_root.join(relative_path)) + system_path_to_file(db, stdlib_root.join(relative_path)).ok() } } } @@ -161,7 +162,7 @@ impl ModulePath { TypeshedVersionsQueryResult::DoesNotExist => None, TypeshedVersionsQueryResult::Exists | TypeshedVersionsQueryResult::MaybeExists => { - vendored_path_to_file(db, stdlib_root.join(relative_path)) + vendored_path_to_file(db, stdlib_root.join(relative_path)).ok() } } } @@ -301,11 +302,15 @@ pub(crate) enum SearchPathValidationError { /// (This is only relevant for stdlib search paths.) NoStdlibSubdirectory(SystemPathBuf), - /// The path provided by the user is a directory, + /// The typeshed path provided by the user is a directory, /// but no `stdlib/VERSIONS` file exists. /// (This is only relevant for stdlib search paths.) NoVersionsFile(SystemPathBuf), + /// `stdlib/VERSIONS` is a directory. + /// (This is only relevant for stdlib search paths.) + VersionsIsADirectory(SystemPathBuf), + /// The path provided by the user is a directory, /// and a `stdlib/VERSIONS` file exists, but it fails to parse. /// (This is only relevant for stdlib search paths.) @@ -319,7 +324,8 @@ impl fmt::Display for SearchPathValidationError { Self::NoStdlibSubdirectory(path) => { write!(f, "The directory at {path} has no `stdlib/` subdirectory") } - Self::NoVersionsFile(path) => write!(f, "Expected a file at {path}/stldib/VERSIONS"), + Self::NoVersionsFile(path) => write!(f, "Expected a file at {path}/stdlib/VERSIONS"), + Self::VersionsIsADirectory(path) => write!(f, "{path}/stdlib/VERSIONS is a directory."), Self::VersionsParseError(underlying_error) => underlying_error.fmt(f), } } @@ -408,10 +414,13 @@ impl SearchPath { typeshed.to_path_buf(), )); } - let Some(typeshed_versions) = system_path_to_file(db.upcast(), stdlib.join("VERSIONS")) - else { - return Err(SearchPathValidationError::NoVersionsFile(typeshed)); - }; + let typeshed_versions = + system_path_to_file(db.upcast(), stdlib.join("VERSIONS")).map_err(|err| match err { + FileError::NotFound => SearchPathValidationError::NoVersionsFile(typeshed), + FileError::IsADirectory => { + SearchPathValidationError::VersionsIsADirectory(typeshed) + } + })?; crate::typeshed::parse_typeshed_versions(db, typeshed_versions) .as_ref() .map_err(|validation_error| { diff --git a/crates/red_knot_module_resolver/src/resolver.rs b/crates/red_knot_module_resolver/src/resolver.rs index 5b76a87df3f22..a1c5f46a6bc8d 100644 --- a/crates/red_knot_module_resolver/src/resolver.rs +++ b/crates/red_knot_module_resolver/src/resolver.rs @@ -2,12 +2,11 @@ use std::borrow::Cow; use std::iter::FusedIterator; use once_cell::sync::Lazy; -use rustc_hash::{FxBuildHasher, FxHashSet}; - -use ruff_db::files::{File, FilePath}; +use ruff_db::files::{File, FilePath, FileRootKind}; use ruff_db::program::{Program, SearchPathSettings, TargetVersion}; use ruff_db::system::{DirectoryEntry, System, SystemPath, SystemPathBuf}; use ruff_db::vendored::VendoredPath; +use rustc_hash::{FxBuildHasher, FxHashSet}; use crate::db::Db; use crate::module::{Module, ModuleKind}; @@ -17,7 +16,7 @@ use crate::state::ResolverState; /// Resolves a module name to a module. pub fn resolve_module(db: &dyn Db, module_name: ModuleName) -> Option { - let interned_name = internal::ModuleNameIngredient::new(db, module_name); + let interned_name = ModuleNameIngredient::new(db, module_name); resolve_module_query(db, interned_name) } @@ -29,7 +28,7 @@ pub fn resolve_module(db: &dyn Db, module_name: ModuleName) -> Option { #[salsa::tracked] pub(crate) fn resolve_module_query<'db>( db: &'db dyn Db, - module_name: internal::ModuleNameIngredient<'db>, + module_name: ModuleNameIngredient<'db>, ) -> Option { let name = module_name.name(db); let _span = tracing::trace_span!("resolve_module", %name).entered(); @@ -139,24 +138,33 @@ fn try_resolve_module_resolution_settings( } let system = db.system(); + let files = db.files(); let mut static_search_paths = vec![]; - for path in extra_paths.iter().cloned() { - static_search_paths.push(SearchPath::extra(system, path)?); + for path in extra_paths { + files.try_add_root(db.upcast(), path, FileRootKind::LibrarySearchPath); + static_search_paths.push(SearchPath::extra(system, path.clone())?); } static_search_paths.push(SearchPath::first_party(system, workspace_root.clone())?); static_search_paths.push(if let Some(custom_typeshed) = custom_typeshed.as_ref() { + files.try_add_root( + db.upcast(), + custom_typeshed, + FileRootKind::LibrarySearchPath, + ); SearchPath::custom_stdlib(db, custom_typeshed.clone())? } else { SearchPath::vendored_stdlib() }); if let Some(site_packages) = site_packages { + files.try_add_root(db.upcast(), site_packages, FileRootKind::LibrarySearchPath); + static_search_paths.push(SearchPath::site_packages(system, site_packages.clone())?); - } + }; // TODO vendor typeshed's third-party stubs as well as the stdlib and fallback to them as a final step @@ -197,62 +205,62 @@ pub(crate) fn module_resolution_settings(db: &dyn Db) -> ModuleResolutionSetting /// due to editable installations of third-party packages. #[salsa::tracked(return_ref)] pub(crate) fn editable_install_resolution_paths(db: &dyn Db) -> Vec { - // This query needs to be re-executed each time a `.pth` file - // is added, modified or removed from the `site-packages` directory. - // However, we don't use Salsa queries to read the source text of `.pth` files; - // we use the APIs on the `System` trait directly. As such, for now we simply ask - // Salsa to recompute this query on each new revision. - // - // TODO: add some kind of watcher for the `site-packages` directory that looks - // for `site-packages/*.pth` files being added/modified/removed; get rid of this. - // When doing so, also make the test - // `deleting_pth_file_on_which_module_resolution_depends_invalidates_cache()` - // more principled! - db.report_untracked_read(); - - let static_search_paths = &module_resolution_settings(db).static_search_paths; + let settings = module_resolution_settings(db); + let static_search_paths = &settings.static_search_paths; + let site_packages = static_search_paths .iter() .find(|path| path.is_site_packages()); + let Some(site_packages) = site_packages else { + return Vec::new(); + }; + + let site_packages = site_packages + .as_system_path() + .expect("Expected site-packages never to be a VendoredPath!"); + let mut dynamic_paths = Vec::default(); - if let Some(site_packages) = site_packages { - let site_packages = site_packages - .as_system_path() - .expect("Expected site-packages never to be a VendoredPath!"); - - // As well as modules installed directly into `site-packages`, - // the directory may also contain `.pth` files. - // Each `.pth` file in `site-packages` may contain one or more lines - // containing a (relative or absolute) path. - // Each of these paths may point to an editable install of a package, - // so should be considered an additional search path. - let Ok(pth_file_iterator) = PthFileIterator::new(db, site_packages) else { - return dynamic_paths; - }; + // This query needs to be re-executed each time a `.pth` file + // is added, modified or removed from the `site-packages` directory. + // However, we don't use Salsa queries to read the source text of `.pth` files; + // we use the APIs on the `System` trait directly. As such, add a dependency on the + // site-package directory's revision. + if let Some(site_packages_root) = db.files().root(db.upcast(), site_packages) { + let _ = site_packages_root.revision(db.upcast()); + } - // The Python documentation specifies that `.pth` files in `site-packages` - // are processed in alphabetical order, so collecting and then sorting is necessary. - // https://docs.python.org/3/library/site.html#module-site - let mut all_pth_files: Vec = pth_file_iterator.collect(); - all_pth_files.sort_by(|a, b| a.path.cmp(&b.path)); + // As well as modules installed directly into `site-packages`, + // the directory may also contain `.pth` files. + // Each `.pth` file in `site-packages` may contain one or more lines + // containing a (relative or absolute) path. + // Each of these paths may point to an editable install of a package, + // so should be considered an additional search path. + let Ok(pth_file_iterator) = PthFileIterator::new(db, site_packages) else { + return dynamic_paths; + }; - let mut existing_paths: FxHashSet<_> = static_search_paths - .iter() - .filter_map(|path| path.as_system_path()) - .map(Cow::Borrowed) - .collect(); - - dynamic_paths.reserve(all_pth_files.len()); - - for pth_file in &all_pth_files { - for installation in pth_file.editable_installations() { - if existing_paths.insert(Cow::Owned( - installation.as_system_path().unwrap().to_path_buf(), - )) { - dynamic_paths.push(installation); - } + // The Python documentation specifies that `.pth` files in `site-packages` + // are processed in alphabetical order, so collecting and then sorting is necessary. + // https://docs.python.org/3/library/site.html#module-site + let mut all_pth_files: Vec = pth_file_iterator.collect(); + all_pth_files.sort_by(|a, b| a.path.cmp(&b.path)); + + let mut existing_paths: FxHashSet<_> = static_search_paths + .iter() + .filter_map(|path| path.as_system_path()) + .map(Cow::Borrowed) + .collect(); + + dynamic_paths.reserve(all_pth_files.len()); + + for pth_file in &all_pth_files { + for installation in pth_file.editable_installations() { + if existing_paths.insert(Cow::Owned( + installation.as_system_path().unwrap().to_path_buf(), + )) { + dynamic_paths.push(installation); } } } @@ -397,9 +405,6 @@ pub(crate) struct ModuleResolutionSettings { target_version: TargetVersion, /// Search paths that have been statically determined purely from reading Ruff's configuration settings. /// These shouldn't ever change unless the config settings themselves change. - /// - /// Note that `site-packages` *is included* as a search path in this sequence, - /// but it is also stored separately so that we're able to find editable installs later. static_search_paths: Vec, } @@ -417,22 +422,13 @@ impl ModuleResolutionSettings { } } -// The singleton methods generated by salsa are all `pub` instead of `pub(crate)` which triggers -// `unreachable_pub`. Work around this by creating a module and allow `unreachable_pub` for it. -// Salsa also generates uses to `_db` variables for `interned` which triggers `clippy::used_underscore_binding`. Suppress that too -// TODO(micha): Contribute a fix for this upstream where the singleton methods have the same visibility as the struct. -#[allow(unreachable_pub, clippy::used_underscore_binding)] -pub(crate) mod internal { - use crate::module_name::ModuleName; - - /// A thin wrapper around `ModuleName` to make it a Salsa ingredient. - /// - /// This is needed because Salsa requires that all query arguments are salsa ingredients. - #[salsa::interned] - pub(crate) struct ModuleNameIngredient<'db> { - #[return_ref] - pub(super) name: ModuleName, - } +/// A thin wrapper around `ModuleName` to make it a Salsa ingredient. +/// +/// This is needed because Salsa requires that all query arguments are salsa ingredients. +#[salsa::interned] +struct ModuleNameIngredient<'db> { + #[return_ref] + pub(super) name: ModuleName, } /// Modules that are builtin to the Python interpreter itself. @@ -492,6 +488,7 @@ fn resolve_name(db: &dyn Db, name: &ModuleName) -> Option<(SearchPath, File, Mod if is_builtin_module && !search_path.is_standard_library() { continue; } + let mut components = name.components(); let module_name = components.next_back()?; @@ -626,13 +623,13 @@ impl PackageKind { #[cfg(test)] mod tests { - use internal::ModuleNameIngredient; use ruff_db::files::{system_path_to_file, File, FilePath}; - use ruff_db::system::{DbWithTestSystem, OsSystem, SystemPath}; - use ruff_db::testing::assert_function_query_was_not_run; + use ruff_db::system::DbWithTestSystem; + use ruff_db::testing::{ + assert_const_function_query_was_not_run, assert_function_query_was_not_run, + }; use ruff_db::Db; - use crate::db::tests::TestDb; use crate::module::ModuleKind; use crate::module_name::ModuleName; use crate::testing::{FileSpec, MockedTypeshed, TestCase, TestCaseBuilder}; @@ -1154,7 +1151,9 @@ mod tests { #[test] #[cfg(target_family = "unix")] fn symlink() -> anyhow::Result<()> { + use crate::db::tests::TestDb; use ruff_db::program::Program; + use ruff_db::system::{OsSystem, SystemPath}; let mut db = TestDb::new(); @@ -1284,6 +1283,7 @@ mod tests { db.memory_file_system() .remove_directory(foo_init_path.parent().unwrap())?; File::sync_path(&mut db, &foo_init_path); + File::sync_path(&mut db, foo_init_path.parent().unwrap()); let foo_module = resolve_module(&db, foo_module_name).expect("Foo module to resolve"); assert_eq!(&src.join("foo.py"), foo_module.file().path(&db)); @@ -1314,7 +1314,7 @@ mod tests { let functools_module = resolve_module(&db, functools_module_name.clone()).unwrap(); assert_eq!(functools_module.search_path(), &stdlib); assert_eq!( - Some(functools_module.file()), + Ok(functools_module.file()), system_path_to_file(&db, &stdlib_functools_path) ); @@ -1326,15 +1326,15 @@ mod tests { .unwrap(); let functools_module = resolve_module(&db, functools_module_name.clone()).unwrap(); let events = db.take_salsa_events(); - assert_function_query_was_not_run::( + assert_function_query_was_not_run( &db, - |res| &res.function, - &ModuleNameIngredient::new(&db, functools_module_name.clone()), + resolve_module_query, + ModuleNameIngredient::new(&db, functools_module_name.clone()), &events, ); assert_eq!(functools_module.search_path(), &stdlib); assert_eq!( - Some(functools_module.file()), + Ok(functools_module.file()), system_path_to_file(&db, &stdlib_functools_path) ); } @@ -1360,7 +1360,7 @@ mod tests { let functools_module = resolve_module(&db, functools_module_name.clone()).unwrap(); assert_eq!(functools_module.search_path(), &stdlib); assert_eq!( - Some(functools_module.file()), + Ok(functools_module.file()), system_path_to_file(&db, stdlib.join("functools.pyi")) ); @@ -1371,7 +1371,7 @@ mod tests { let functools_module = resolve_module(&db, functools_module_name.clone()).unwrap(); assert_eq!(functools_module.search_path(), &src); assert_eq!( - Some(functools_module.file()), + Ok(functools_module.file()), system_path_to_file(&db, &src_functools_path) ); } @@ -1402,7 +1402,7 @@ mod tests { let functools_module = resolve_module(&db, functools_module_name.clone()).unwrap(); assert_eq!(functools_module.search_path(), &src); assert_eq!( - Some(functools_module.file()), + Ok(functools_module.file()), system_path_to_file(&db, &src_functools_path) ); @@ -1415,7 +1415,7 @@ mod tests { let functools_module = resolve_module(&db, functools_module_name.clone()).unwrap(); assert_eq!(functools_module.search_path(), &stdlib); assert_eq!( - Some(functools_module.file()), + Ok(functools_module.file()), system_path_to_file(&db, stdlib.join("functools.pyi")) ); } @@ -1578,12 +1578,7 @@ not_a_directory &FilePath::system("/y/src/bar.py") ); let events = db.take_salsa_events(); - assert_function_query_was_not_run::( - &db, - |res| &res.function, - &(), - &events, - ); + assert_const_function_query_was_not_run(&db, editable_install_resolution_paths, &events); } #[test] @@ -1612,18 +1607,7 @@ not_a_directory .remove_file(site_packages.join("_foo.pth")) .unwrap(); - // Why are we touching a random file in the path that's been editably installed, - // rather than the `.pth` file, when the `.pth` file is the one that has been deleted? - // It's because the `.pth` file isn't directly tracked as a dependency by Salsa - // currently (we don't use `system_path_to_file()` to get the file, and we don't use - // `source_text()` to read the source of the file). Instead of using these APIs which - // would automatically add the existence and contents of the file as a Salsa-tracked - // dependency, we use `.report_untracked_read()` to force Salsa to re-parse all - // `.pth` files on each new "revision". Making a random modification to a tracked - // Salsa file forces a new revision. - // - // TODO: get rid of the `.report_untracked_read()` call... - File::sync_path(&mut db, SystemPath::new("/x/src/foo.py")); + File::sync_path(&mut db, &site_packages.join("_foo.pth")); assert_eq!(resolve_module(&db, foo_module_name.clone()), None); } diff --git a/crates/red_knot_module_resolver/src/state.rs b/crates/red_knot_module_resolver/src/state.rs index 048504f60ceeb..ec32c3e791db2 100644 --- a/crates/red_knot_module_resolver/src/state.rs +++ b/crates/red_knot_module_resolver/src/state.rs @@ -1,5 +1,4 @@ use ruff_db::program::TargetVersion; -use ruff_db::system::System; use ruff_db::vendored::VendoredFileSystem; use crate::db::Db; @@ -20,10 +19,6 @@ impl<'db> ResolverState<'db> { } } - pub(crate) fn system(&self) -> &dyn System { - self.db.system() - } - pub(crate) fn vendored(&self) -> &VendoredFileSystem { self.db.vendored() } diff --git a/crates/red_knot_module_resolver/src/typeshed/versions.rs b/crates/red_knot_module_resolver/src/typeshed/versions.rs index d0aef6e0bd79f..e5aae22c5ffd9 100644 --- a/crates/red_knot_module_resolver/src/typeshed/versions.rs +++ b/crates/red_knot_module_resolver/src/typeshed/versions.rs @@ -52,7 +52,7 @@ impl<'db> LazyTypeshedVersions<'db> { } else { return &VENDORED_VERSIONS; }; - let Some(versions_file) = system_path_to_file(db.upcast(), &versions_path) else { + let Ok(versions_file) = system_path_to_file(db.upcast(), &versions_path) else { todo!( "Still need to figure out how to handle VERSIONS files being deleted \ from custom typeshed directories! Expected a file to exist at {versions_path}" diff --git a/crates/red_knot_module_resolver/vendor/typeshed/source_commit.txt b/crates/red_knot_module_resolver/vendor/typeshed/source_commit.txt index 3eadcae4686e0..78d760bf12b4d 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/source_commit.txt +++ b/crates/red_knot_module_resolver/vendor/typeshed/source_commit.txt @@ -1 +1 @@ -f863db6bc5242348ceaa6a3bca4e59aa9e62faaa +4ef2d66663fc080fefa379e6ae5fc45d4f8b54eb diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/_csv.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/_csv.pyi index 19f2dc9664b18..9bb5d27f6e352 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/_csv.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/_csv.pyi @@ -1,18 +1,18 @@ import sys from _typeshed import SupportsWrite from collections.abc import Iterable, Iterator -from typing import Any, Final, Literal +from typing import Any, Final from typing_extensions import TypeAlias __version__: Final[str] -QUOTE_ALL: Literal[1] -QUOTE_MINIMAL: Literal[0] -QUOTE_NONE: Literal[3] -QUOTE_NONNUMERIC: Literal[2] +QUOTE_ALL: Final = 1 +QUOTE_MINIMAL: Final = 0 +QUOTE_NONE: Final = 3 +QUOTE_NONNUMERIC: Final = 2 if sys.version_info >= (3, 12): - QUOTE_STRINGS: Literal[4] - QUOTE_NOTNULL: Literal[5] + QUOTE_STRINGS: Final = 4 + QUOTE_NOTNULL: Final = 5 # Ideally this would be `QUOTE_ALL | QUOTE_MINIMAL | QUOTE_NONE | QUOTE_NONNUMERIC` # However, using literals in situations like these can cause false-positives (see #7258) diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/_ctypes.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/_ctypes.pyi index 5be81fa53823e..c1fb86193b648 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/_ctypes.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/_ctypes.pyi @@ -71,7 +71,7 @@ class _CData(metaclass=_CDataMeta): @classmethod def from_address(cls, address: int) -> Self: ... @classmethod - def from_param(cls, obj: Any) -> Self | _CArgObject: ... + def from_param(cls, value: Any, /) -> Self | _CArgObject: ... @classmethod def in_dll(cls, library: CDLL, name: str) -> Self: ... def __buffer__(self, flags: int, /) -> memoryview: ... diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/_curses.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/_curses.pyi index eb1d7b9bde9f3..505637574af12 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/_curses.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/_curses.pyi @@ -368,11 +368,7 @@ def tparm( ) -> bytes: ... def typeahead(fd: int, /) -> None: ... def unctrl(ch: _ChType, /) -> bytes: ... - -if sys.version_info < (3, 12) or sys.platform != "darwin": - # The support for macos was dropped in 3.12 - def unget_wch(ch: int | str, /) -> None: ... - +def unget_wch(ch: int | str, /) -> None: ... def ungetch(ch: _ChType, /) -> None: ... def ungetmouse(id: int, x: int, y: int, z: int, bstate: int, /) -> None: ... def update_lines_cols() -> None: ... @@ -447,13 +443,10 @@ class _CursesWindow: def getch(self) -> int: ... @overload def getch(self, y: int, x: int) -> int: ... - if sys.version_info < (3, 12) or sys.platform != "darwin": - # The support for macos was dropped in 3.12 - @overload - def get_wch(self) -> int | str: ... - @overload - def get_wch(self, y: int, x: int) -> int | str: ... - + @overload + def get_wch(self) -> int | str: ... + @overload + def get_wch(self, y: int, x: int) -> int | str: ... @overload def getkey(self) -> str: ... @overload diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/_decimal.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/_decimal.pyi index 90d16215c280d..937a04ac37998 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/_decimal.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/_decimal.pyi @@ -17,20 +17,20 @@ class DecimalTuple(NamedTuple): digits: tuple[int, ...] exponent: int | Literal["n", "N", "F"] -ROUND_DOWN: str -ROUND_HALF_UP: str -ROUND_HALF_EVEN: str -ROUND_CEILING: str -ROUND_FLOOR: str -ROUND_UP: str -ROUND_HALF_DOWN: str -ROUND_05UP: str -HAVE_CONTEXTVAR: bool -HAVE_THREADS: bool -MAX_EMAX: int -MAX_PREC: int -MIN_EMIN: int -MIN_ETINY: int +ROUND_DOWN: Final[str] +ROUND_HALF_UP: Final[str] +ROUND_HALF_EVEN: Final[str] +ROUND_CEILING: Final[str] +ROUND_FLOOR: Final[str] +ROUND_UP: Final[str] +ROUND_HALF_DOWN: Final[str] +ROUND_05UP: Final[str] +HAVE_CONTEXTVAR: Final[bool] +HAVE_THREADS: Final[bool] +MAX_EMAX: Final[int] +MAX_PREC: Final[int] +MIN_EMIN: Final[int] +MIN_ETINY: Final[int] class DecimalException(ArithmeticError): ... class Clamped(DecimalException): ... diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/_interpchannels.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/_interpchannels.pyi index b77fe321a0716..c03496044df06 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/_interpchannels.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/_interpchannels.pyi @@ -1,5 +1,5 @@ from _typeshed import structseq -from typing import Final, Literal, SupportsIndex, final +from typing import Any, Final, Literal, SupportsIndex, final from typing_extensions import Buffer, Self class ChannelError(RuntimeError): ... @@ -72,13 +72,15 @@ class ChannelInfo(structseq[int], tuple[bool, bool, bool, int, int, int, int, in @property def send_released(self) -> bool: ... -def create() -> ChannelID: ... +def create(unboundop: Literal[1, 2, 3]) -> ChannelID: ... def destroy(cid: SupportsIndex) -> None: ... def list_all() -> list[ChannelID]: ... def list_interpreters(cid: SupportsIndex, *, send: bool) -> list[int]: ... def send(cid: SupportsIndex, obj: object, *, blocking: bool = True, timeout: float | None = None) -> None: ... def send_buffer(cid: SupportsIndex, obj: Buffer, *, blocking: bool = True, timeout: float | None = None) -> None: ... -def recv(cid: SupportsIndex, default: object = ...) -> object: ... +def recv(cid: SupportsIndex, default: object = ...) -> tuple[Any, Literal[1, 2, 3]]: ... def close(cid: SupportsIndex, *, send: bool = False, recv: bool = False) -> None: ... +def get_count(cid: SupportsIndex) -> int: ... def get_info(cid: SupportsIndex) -> ChannelInfo: ... +def get_channel_defaults(cid: SupportsIndex) -> Literal[1, 2, 3]: ... def release(cid: SupportsIndex, *, send: bool = False, recv: bool = False, force: bool = False) -> None: ... diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/_osx_support.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/_osx_support.pyi index 64dbdd24fd401..fb00e6986dd06 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/_osx_support.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/_osx_support.pyi @@ -1,5 +1,5 @@ from collections.abc import Iterable, Sequence -from typing import TypeVar +from typing import Final, TypeVar _T = TypeVar("_T") _K = TypeVar("_K") @@ -7,15 +7,15 @@ _V = TypeVar("_V") __all__ = ["compiler_fixup", "customize_config_vars", "customize_compiler", "get_platform_osx"] -_UNIVERSAL_CONFIG_VARS: tuple[str, ...] # undocumented -_COMPILER_CONFIG_VARS: tuple[str, ...] # undocumented -_INITPRE: str # undocumented +_UNIVERSAL_CONFIG_VARS: Final[tuple[str, ...]] # undocumented +_COMPILER_CONFIG_VARS: Final[tuple[str, ...]] # undocumented +_INITPRE: Final[str] # undocumented def _find_executable(executable: str, path: str | None = None) -> str | None: ... # undocumented def _read_output(commandstring: str, capture_stderr: bool = False) -> str | None: ... # undocumented def _find_build_tool(toolname: str) -> str: ... # undocumented -_SYSTEM_VERSION: str | None # undocumented +_SYSTEM_VERSION: Final[str | None] # undocumented def _get_system_version() -> str: ... # undocumented def _remove_original_values(_config_vars: dict[str, str]) -> None: ... # undocumented diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/_stat.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/_stat.pyi index c4e918d8b57f6..903571a64bca0 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/_stat.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/_stat.pyi @@ -1,30 +1,30 @@ import sys -from typing import Literal - -SF_APPEND: Literal[0x00040000] -SF_ARCHIVED: Literal[0x00010000] -SF_IMMUTABLE: Literal[0x00020000] -SF_NOUNLINK: Literal[0x00100000] -SF_SNAPSHOT: Literal[0x00200000] - -ST_MODE: Literal[0] -ST_INO: Literal[1] -ST_DEV: Literal[2] -ST_NLINK: Literal[3] -ST_UID: Literal[4] -ST_GID: Literal[5] -ST_SIZE: Literal[6] -ST_ATIME: Literal[7] -ST_MTIME: Literal[8] -ST_CTIME: Literal[9] - -S_IFIFO: Literal[0o010000] -S_IFLNK: Literal[0o120000] -S_IFREG: Literal[0o100000] -S_IFSOCK: Literal[0o140000] -S_IFBLK: Literal[0o060000] -S_IFCHR: Literal[0o020000] -S_IFDIR: Literal[0o040000] +from typing import Final + +SF_APPEND: Final = 0x00040000 +SF_ARCHIVED: Final = 0x00010000 +SF_IMMUTABLE: Final = 0x00020000 +SF_NOUNLINK: Final = 0x00100000 +SF_SNAPSHOT: Final = 0x00200000 + +ST_MODE: Final = 0 +ST_INO: Final = 1 +ST_DEV: Final = 2 +ST_NLINK: Final = 3 +ST_UID: Final = 4 +ST_GID: Final = 5 +ST_SIZE: Final = 6 +ST_ATIME: Final = 7 +ST_MTIME: Final = 8 +ST_CTIME: Final = 9 + +S_IFIFO: Final = 0o010000 +S_IFLNK: Final = 0o120000 +S_IFREG: Final = 0o100000 +S_IFSOCK: Final = 0o140000 +S_IFBLK: Final = 0o060000 +S_IFCHR: Final = 0o020000 +S_IFDIR: Final = 0o040000 # These are 0 on systems that don't support the specific kind of file. # Example: Linux doesn't support door files, so S_IFDOOR is 0 on linux. @@ -32,37 +32,37 @@ S_IFDOOR: int S_IFPORT: int S_IFWHT: int -S_ISUID: Literal[0o4000] -S_ISGID: Literal[0o2000] -S_ISVTX: Literal[0o1000] - -S_IRWXU: Literal[0o0700] -S_IRUSR: Literal[0o0400] -S_IWUSR: Literal[0o0200] -S_IXUSR: Literal[0o0100] - -S_IRWXG: Literal[0o0070] -S_IRGRP: Literal[0o0040] -S_IWGRP: Literal[0o0020] -S_IXGRP: Literal[0o0010] - -S_IRWXO: Literal[0o0007] -S_IROTH: Literal[0o0004] -S_IWOTH: Literal[0o0002] -S_IXOTH: Literal[0o0001] - -S_ENFMT: Literal[0o2000] -S_IREAD: Literal[0o0400] -S_IWRITE: Literal[0o0200] -S_IEXEC: Literal[0o0100] - -UF_APPEND: Literal[0x00000004] -UF_COMPRESSED: Literal[0x00000020] # OS X 10.6+ only -UF_HIDDEN: Literal[0x00008000] # OX X 10.5+ only -UF_IMMUTABLE: Literal[0x00000002] -UF_NODUMP: Literal[0x00000001] -UF_NOUNLINK: Literal[0x00000010] -UF_OPAQUE: Literal[0x00000008] +S_ISUID: Final = 0o4000 +S_ISGID: Final = 0o2000 +S_ISVTX: Final = 0o1000 + +S_IRWXU: Final = 0o0700 +S_IRUSR: Final = 0o0400 +S_IWUSR: Final = 0o0200 +S_IXUSR: Final = 0o0100 + +S_IRWXG: Final = 0o0070 +S_IRGRP: Final = 0o0040 +S_IWGRP: Final = 0o0020 +S_IXGRP: Final = 0o0010 + +S_IRWXO: Final = 0o0007 +S_IROTH: Final = 0o0004 +S_IWOTH: Final = 0o0002 +S_IXOTH: Final = 0o0001 + +S_ENFMT: Final = 0o2000 +S_IREAD: Final = 0o0400 +S_IWRITE: Final = 0o0200 +S_IEXEC: Final = 0o0100 + +UF_APPEND: Final = 0x00000004 +UF_COMPRESSED: Final = 0x00000020 # OS X 10.6+ only +UF_HIDDEN: Final = 0x00008000 # OX X 10.5+ only +UF_IMMUTABLE: Final = 0x00000002 +UF_NODUMP: Final = 0x00000001 +UF_NOUNLINK: Final = 0x00000010 +UF_OPAQUE: Final = 0x00000008 def S_IMODE(mode: int, /) -> int: ... def S_IFMT(mode: int, /) -> int: ... @@ -84,34 +84,36 @@ if sys.platform == "win32": IO_REPARSE_TAG_APPEXECLINK: int if sys.platform == "win32": - FILE_ATTRIBUTE_ARCHIVE: Literal[32] - FILE_ATTRIBUTE_COMPRESSED: Literal[2048] - FILE_ATTRIBUTE_DEVICE: Literal[64] - FILE_ATTRIBUTE_DIRECTORY: Literal[16] - FILE_ATTRIBUTE_ENCRYPTED: Literal[16384] - FILE_ATTRIBUTE_HIDDEN: Literal[2] - FILE_ATTRIBUTE_INTEGRITY_STREAM: Literal[32768] - FILE_ATTRIBUTE_NORMAL: Literal[128] - FILE_ATTRIBUTE_NOT_CONTENT_INDEXED: Literal[8192] - FILE_ATTRIBUTE_NO_SCRUB_DATA: Literal[131072] - FILE_ATTRIBUTE_OFFLINE: Literal[4096] - FILE_ATTRIBUTE_READONLY: Literal[1] - FILE_ATTRIBUTE_REPARSE_POINT: Literal[1024] - FILE_ATTRIBUTE_SPARSE_FILE: Literal[512] - FILE_ATTRIBUTE_SYSTEM: Literal[4] - FILE_ATTRIBUTE_TEMPORARY: Literal[256] - FILE_ATTRIBUTE_VIRTUAL: Literal[65536] + FILE_ATTRIBUTE_ARCHIVE: Final = 32 + FILE_ATTRIBUTE_COMPRESSED: Final = 2048 + FILE_ATTRIBUTE_DEVICE: Final = 64 + FILE_ATTRIBUTE_DIRECTORY: Final = 16 + FILE_ATTRIBUTE_ENCRYPTED: Final = 16384 + FILE_ATTRIBUTE_HIDDEN: Final = 2 + FILE_ATTRIBUTE_INTEGRITY_STREAM: Final = 32768 + FILE_ATTRIBUTE_NORMAL: Final = 128 + FILE_ATTRIBUTE_NOT_CONTENT_INDEXED: Final = 8192 + FILE_ATTRIBUTE_NO_SCRUB_DATA: Final = 131072 + FILE_ATTRIBUTE_OFFLINE: Final = 4096 + FILE_ATTRIBUTE_READONLY: Final = 1 + FILE_ATTRIBUTE_REPARSE_POINT: Final = 1024 + FILE_ATTRIBUTE_SPARSE_FILE: Final = 512 + FILE_ATTRIBUTE_SYSTEM: Final = 4 + FILE_ATTRIBUTE_TEMPORARY: Final = 256 + FILE_ATTRIBUTE_VIRTUAL: Final = 65536 if sys.version_info >= (3, 13): - SF_SETTABLE: Literal[0x3FFF0000] + # Varies by platform. + SF_SETTABLE: Final[int] # https://github.com/python/cpython/issues/114081#issuecomment-2119017790 # SF_RESTRICTED: Literal[0x00080000] - SF_FIRMLINK: Literal[0x00800000] - SF_DATALESS: Literal[0x40000000] + SF_FIRMLINK: Final = 0x00800000 + SF_DATALESS: Final = 0x40000000 - SF_SUPPORTED: Literal[0x9F0000] - SF_SYNTHETIC: Literal[0xC0000000] + if sys.platform == "darwin": + SF_SUPPORTED: Final = 0x9F0000 + SF_SYNTHETIC: Final = 0xC0000000 - UF_TRACKED: Literal[0x00000040] - UF_DATAVAULT: Literal[0x00000080] - UF_SETTABLE: Literal[0x0000FFFF] + UF_TRACKED: Final = 0x00000040 + UF_DATAVAULT: Final = 0x00000080 + UF_SETTABLE: Final = 0x0000FFFF diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/_tkinter.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/_tkinter.pyi index aea74c8be279e..a7293054d2935 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/_tkinter.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/_tkinter.pyi @@ -1,6 +1,6 @@ import sys from collections.abc import Callable -from typing import Any, ClassVar, Literal, final +from typing import Any, ClassVar, Final, final from typing_extensions import TypeAlias # _tkinter is meant to be only used internally by tkinter, but some tkinter @@ -95,16 +95,16 @@ class TkappType: def settrace(self, func: _TkinterTraceFunc | None, /) -> None: ... # These should be kept in sync with tkinter.tix constants, except ALL_EVENTS which doesn't match TCL_ALL_EVENTS -ALL_EVENTS: Literal[-3] -FILE_EVENTS: Literal[8] -IDLE_EVENTS: Literal[32] -TIMER_EVENTS: Literal[16] -WINDOW_EVENTS: Literal[4] +ALL_EVENTS: Final = -3 +FILE_EVENTS: Final = 8 +IDLE_EVENTS: Final = 32 +TIMER_EVENTS: Final = 16 +WINDOW_EVENTS: Final = 4 -DONT_WAIT: Literal[2] -EXCEPTION: Literal[8] -READABLE: Literal[2] -WRITABLE: Literal[4] +DONT_WAIT: Final = 2 +EXCEPTION: Final = 8 +READABLE: Final = 2 +WRITABLE: Final = 4 TCL_VERSION: str TK_VERSION: str diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/_winapi.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/_winapi.pyi index c6fb0484df8e2..62ea124045cc3 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/_winapi.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/_winapi.pyi @@ -1,117 +1,117 @@ import sys from _typeshed import ReadableBuffer from collections.abc import Sequence -from typing import Any, Literal, NoReturn, final, overload +from typing import Any, Final, Literal, NoReturn, final, overload if sys.platform == "win32": - ABOVE_NORMAL_PRIORITY_CLASS: Literal[0x8000] - BELOW_NORMAL_PRIORITY_CLASS: Literal[0x4000] - - CREATE_BREAKAWAY_FROM_JOB: Literal[0x1000000] - CREATE_DEFAULT_ERROR_MODE: Literal[0x4000000] - CREATE_NO_WINDOW: Literal[0x8000000] - CREATE_NEW_CONSOLE: Literal[0x10] - CREATE_NEW_PROCESS_GROUP: Literal[0x200] - - DETACHED_PROCESS: Literal[8] - DUPLICATE_CLOSE_SOURCE: Literal[1] - DUPLICATE_SAME_ACCESS: Literal[2] - - ERROR_ALREADY_EXISTS: Literal[183] - ERROR_BROKEN_PIPE: Literal[109] - ERROR_IO_PENDING: Literal[997] - ERROR_MORE_DATA: Literal[234] - ERROR_NETNAME_DELETED: Literal[64] - ERROR_NO_DATA: Literal[232] - ERROR_NO_SYSTEM_RESOURCES: Literal[1450] - ERROR_OPERATION_ABORTED: Literal[995] - ERROR_PIPE_BUSY: Literal[231] - ERROR_PIPE_CONNECTED: Literal[535] - ERROR_SEM_TIMEOUT: Literal[121] - - FILE_FLAG_FIRST_PIPE_INSTANCE: Literal[0x80000] - FILE_FLAG_OVERLAPPED: Literal[0x40000000] - - FILE_GENERIC_READ: Literal[1179785] - FILE_GENERIC_WRITE: Literal[1179926] - - FILE_MAP_ALL_ACCESS: Literal[983071] - FILE_MAP_COPY: Literal[1] - FILE_MAP_EXECUTE: Literal[32] - FILE_MAP_READ: Literal[4] - FILE_MAP_WRITE: Literal[2] - - FILE_TYPE_CHAR: Literal[2] - FILE_TYPE_DISK: Literal[1] - FILE_TYPE_PIPE: Literal[3] - FILE_TYPE_REMOTE: Literal[32768] - FILE_TYPE_UNKNOWN: Literal[0] - - GENERIC_READ: Literal[0x80000000] - GENERIC_WRITE: Literal[0x40000000] - HIGH_PRIORITY_CLASS: Literal[0x80] - INFINITE: Literal[0xFFFFFFFF] + ABOVE_NORMAL_PRIORITY_CLASS: Final = 0x8000 + BELOW_NORMAL_PRIORITY_CLASS: Final = 0x4000 + + CREATE_BREAKAWAY_FROM_JOB: Final = 0x1000000 + CREATE_DEFAULT_ERROR_MODE: Final = 0x4000000 + CREATE_NO_WINDOW: Final = 0x8000000 + CREATE_NEW_CONSOLE: Final = 0x10 + CREATE_NEW_PROCESS_GROUP: Final = 0x200 + + DETACHED_PROCESS: Final = 8 + DUPLICATE_CLOSE_SOURCE: Final = 1 + DUPLICATE_SAME_ACCESS: Final = 2 + + ERROR_ALREADY_EXISTS: Final = 183 + ERROR_BROKEN_PIPE: Final = 109 + ERROR_IO_PENDING: Final = 997 + ERROR_MORE_DATA: Final = 234 + ERROR_NETNAME_DELETED: Final = 64 + ERROR_NO_DATA: Final = 232 + ERROR_NO_SYSTEM_RESOURCES: Final = 1450 + ERROR_OPERATION_ABORTED: Final = 995 + ERROR_PIPE_BUSY: Final = 231 + ERROR_PIPE_CONNECTED: Final = 535 + ERROR_SEM_TIMEOUT: Final = 121 + + FILE_FLAG_FIRST_PIPE_INSTANCE: Final = 0x80000 + FILE_FLAG_OVERLAPPED: Final = 0x40000000 + + FILE_GENERIC_READ: Final = 1179785 + FILE_GENERIC_WRITE: Final = 1179926 + + FILE_MAP_ALL_ACCESS: Final = 983071 + FILE_MAP_COPY: Final = 1 + FILE_MAP_EXECUTE: Final = 32 + FILE_MAP_READ: Final = 4 + FILE_MAP_WRITE: Final = 2 + + FILE_TYPE_CHAR: Final = 2 + FILE_TYPE_DISK: Final = 1 + FILE_TYPE_PIPE: Final = 3 + FILE_TYPE_REMOTE: Final = 32768 + FILE_TYPE_UNKNOWN: Final = 0 + + GENERIC_READ: Final = 0x80000000 + GENERIC_WRITE: Final = 0x40000000 + HIGH_PRIORITY_CLASS: Final = 0x80 + INFINITE: Final = 0xFFFFFFFF # Ignore the Flake8 error -- flake8-pyi assumes # most numbers this long will be implementation details, # but here we can see that it's a power of 2 - INVALID_HANDLE_VALUE: Literal[0xFFFFFFFFFFFFFFFF] # noqa: Y054 - IDLE_PRIORITY_CLASS: Literal[0x40] - NORMAL_PRIORITY_CLASS: Literal[0x20] - REALTIME_PRIORITY_CLASS: Literal[0x100] - NMPWAIT_WAIT_FOREVER: Literal[0xFFFFFFFF] - - MEM_COMMIT: Literal[0x1000] - MEM_FREE: Literal[0x10000] - MEM_IMAGE: Literal[0x1000000] - MEM_MAPPED: Literal[0x40000] - MEM_PRIVATE: Literal[0x20000] - MEM_RESERVE: Literal[0x2000] - - NULL: Literal[0] - OPEN_EXISTING: Literal[3] - - PIPE_ACCESS_DUPLEX: Literal[3] - PIPE_ACCESS_INBOUND: Literal[1] - PIPE_READMODE_MESSAGE: Literal[2] - PIPE_TYPE_MESSAGE: Literal[4] - PIPE_UNLIMITED_INSTANCES: Literal[255] - PIPE_WAIT: Literal[0] - - PAGE_EXECUTE: Literal[0x10] - PAGE_EXECUTE_READ: Literal[0x20] - PAGE_EXECUTE_READWRITE: Literal[0x40] - PAGE_EXECUTE_WRITECOPY: Literal[0x80] - PAGE_GUARD: Literal[0x100] - PAGE_NOACCESS: Literal[0x1] - PAGE_NOCACHE: Literal[0x200] - PAGE_READONLY: Literal[0x2] - PAGE_READWRITE: Literal[0x4] - PAGE_WRITECOMBINE: Literal[0x400] - PAGE_WRITECOPY: Literal[0x8] - - PROCESS_ALL_ACCESS: Literal[0x1FFFFF] - PROCESS_DUP_HANDLE: Literal[0x40] - - SEC_COMMIT: Literal[0x8000000] - SEC_IMAGE: Literal[0x1000000] - SEC_LARGE_PAGES: Literal[0x80000000] - SEC_NOCACHE: Literal[0x10000000] - SEC_RESERVE: Literal[0x4000000] - SEC_WRITECOMBINE: Literal[0x40000000] - - STARTF_USESHOWWINDOW: Literal[0x1] - STARTF_USESTDHANDLES: Literal[0x100] - - STD_ERROR_HANDLE: Literal[0xFFFFFFF4] - STD_OUTPUT_HANDLE: Literal[0xFFFFFFF5] - STD_INPUT_HANDLE: Literal[0xFFFFFFF6] - - STILL_ACTIVE: Literal[259] - SW_HIDE: Literal[0] - SYNCHRONIZE: Literal[0x100000] - WAIT_ABANDONED_0: Literal[128] - WAIT_OBJECT_0: Literal[0] - WAIT_TIMEOUT: Literal[258] + INVALID_HANDLE_VALUE: Final = 0xFFFFFFFFFFFFFFFF # noqa: Y054 + IDLE_PRIORITY_CLASS: Final = 0x40 + NORMAL_PRIORITY_CLASS: Final = 0x20 + REALTIME_PRIORITY_CLASS: Final = 0x100 + NMPWAIT_WAIT_FOREVER: Final = 0xFFFFFFFF + + MEM_COMMIT: Final = 0x1000 + MEM_FREE: Final = 0x10000 + MEM_IMAGE: Final = 0x1000000 + MEM_MAPPED: Final = 0x40000 + MEM_PRIVATE: Final = 0x20000 + MEM_RESERVE: Final = 0x2000 + + NULL: Final = 0 + OPEN_EXISTING: Final = 3 + + PIPE_ACCESS_DUPLEX: Final = 3 + PIPE_ACCESS_INBOUND: Final = 1 + PIPE_READMODE_MESSAGE: Final = 2 + PIPE_TYPE_MESSAGE: Final = 4 + PIPE_UNLIMITED_INSTANCES: Final = 255 + PIPE_WAIT: Final = 0 + + PAGE_EXECUTE: Final = 0x10 + PAGE_EXECUTE_READ: Final = 0x20 + PAGE_EXECUTE_READWRITE: Final = 0x40 + PAGE_EXECUTE_WRITECOPY: Final = 0x80 + PAGE_GUARD: Final = 0x100 + PAGE_NOACCESS: Final = 0x1 + PAGE_NOCACHE: Final = 0x200 + PAGE_READONLY: Final = 0x2 + PAGE_READWRITE: Final = 0x4 + PAGE_WRITECOMBINE: Final = 0x400 + PAGE_WRITECOPY: Final = 0x8 + + PROCESS_ALL_ACCESS: Final = 0x1FFFFF + PROCESS_DUP_HANDLE: Final = 0x40 + + SEC_COMMIT: Final = 0x8000000 + SEC_IMAGE: Final = 0x1000000 + SEC_LARGE_PAGES: Final = 0x80000000 + SEC_NOCACHE: Final = 0x10000000 + SEC_RESERVE: Final = 0x4000000 + SEC_WRITECOMBINE: Final = 0x40000000 + + STARTF_USESHOWWINDOW: Final = 0x1 + STARTF_USESTDHANDLES: Final = 0x100 + + STD_ERROR_HANDLE: Final = 0xFFFFFFF4 + STD_OUTPUT_HANDLE: Final = 0xFFFFFFF5 + STD_INPUT_HANDLE: Final = 0xFFFFFFF6 + + STILL_ACTIVE: Final = 259 + SW_HIDE: Final = 0 + SYNCHRONIZE: Final = 0x100000 + WAIT_ABANDONED_0: Final = 128 + WAIT_OBJECT_0: Final = 0 + WAIT_TIMEOUT: Final = 258 if sys.version_info >= (3, 10): LOCALE_NAME_INVARIANT: str @@ -131,32 +131,32 @@ if sys.platform == "win32": LCMAP_UPPERCASE: int if sys.version_info >= (3, 12): - COPYFILE2_CALLBACK_CHUNK_STARTED: Literal[1] - COPYFILE2_CALLBACK_CHUNK_FINISHED: Literal[2] - COPYFILE2_CALLBACK_STREAM_STARTED: Literal[3] - COPYFILE2_CALLBACK_STREAM_FINISHED: Literal[4] - COPYFILE2_CALLBACK_POLL_CONTINUE: Literal[5] - COPYFILE2_CALLBACK_ERROR: Literal[6] - - COPYFILE2_PROGRESS_CONTINUE: Literal[0] - COPYFILE2_PROGRESS_CANCEL: Literal[1] - COPYFILE2_PROGRESS_STOP: Literal[2] - COPYFILE2_PROGRESS_QUIET: Literal[3] - COPYFILE2_PROGRESS_PAUSE: Literal[4] - - COPY_FILE_FAIL_IF_EXISTS: Literal[0x1] - COPY_FILE_RESTARTABLE: Literal[0x2] - COPY_FILE_OPEN_SOURCE_FOR_WRITE: Literal[0x4] - COPY_FILE_ALLOW_DECRYPTED_DESTINATION: Literal[0x8] - COPY_FILE_COPY_SYMLINK: Literal[0x800] - COPY_FILE_NO_BUFFERING: Literal[0x1000] - COPY_FILE_REQUEST_SECURITY_PRIVILEGES: Literal[0x2000] - COPY_FILE_RESUME_FROM_PAUSE: Literal[0x4000] - COPY_FILE_NO_OFFLOAD: Literal[0x40000] - COPY_FILE_REQUEST_COMPRESSED_TRAFFIC: Literal[0x10000000] - - ERROR_ACCESS_DENIED: Literal[5] - ERROR_PRIVILEGE_NOT_HELD: Literal[1314] + COPYFILE2_CALLBACK_CHUNK_STARTED: Final = 1 + COPYFILE2_CALLBACK_CHUNK_FINISHED: Final = 2 + COPYFILE2_CALLBACK_STREAM_STARTED: Final = 3 + COPYFILE2_CALLBACK_STREAM_FINISHED: Final = 4 + COPYFILE2_CALLBACK_POLL_CONTINUE: Final = 5 + COPYFILE2_CALLBACK_ERROR: Final = 6 + + COPYFILE2_PROGRESS_CONTINUE: Final = 0 + COPYFILE2_PROGRESS_CANCEL: Final = 1 + COPYFILE2_PROGRESS_STOP: Final = 2 + COPYFILE2_PROGRESS_QUIET: Final = 3 + COPYFILE2_PROGRESS_PAUSE: Final = 4 + + COPY_FILE_FAIL_IF_EXISTS: Final = 0x1 + COPY_FILE_RESTARTABLE: Final = 0x2 + COPY_FILE_OPEN_SOURCE_FOR_WRITE: Final = 0x4 + COPY_FILE_ALLOW_DECRYPTED_DESTINATION: Final = 0x8 + COPY_FILE_COPY_SYMLINK: Final = 0x800 + COPY_FILE_NO_BUFFERING: Final = 0x1000 + COPY_FILE_REQUEST_SECURITY_PRIVILEGES: Final = 0x2000 + COPY_FILE_RESUME_FROM_PAUSE: Final = 0x4000 + COPY_FILE_NO_OFFLOAD: Final = 0x40000 + COPY_FILE_REQUEST_COMPRESSED_TRAFFIC: Final = 0x10000000 + + ERROR_ACCESS_DENIED: Final = 5 + ERROR_PRIVILEGE_NOT_HELD: Final = 1314 def CloseHandle(handle: int, /) -> None: ... @overload diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/argparse.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/argparse.pyi index bc781ec8e61df..66fa4e15291fa 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/argparse.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/argparse.pyi @@ -2,7 +2,7 @@ import sys from _typeshed import sentinel from collections.abc import Callable, Generator, Iterable, Sequence from re import Pattern -from typing import IO, Any, Generic, Literal, NewType, NoReturn, Protocol, TypeVar, overload +from typing import IO, Any, Final, Generic, NewType, NoReturn, Protocol, TypeVar, overload from typing_extensions import Self, TypeAlias, deprecated __all__ = [ @@ -43,15 +43,15 @@ _ActionStr: TypeAlias = str # callers that don't use a literal argument _NArgsStr: TypeAlias = str -ONE_OR_MORE: Literal["+"] -OPTIONAL: Literal["?"] -PARSER: Literal["A..."] -REMAINDER: Literal["..."] +ONE_OR_MORE: Final = "+" +OPTIONAL: Final = "?" +PARSER: Final = "A..." +REMAINDER: Final = "..." _SUPPRESS_T = NewType("_SUPPRESS_T", str) SUPPRESS: _SUPPRESS_T | str # not using Literal because argparse sometimes compares SUPPRESS with is # the | str is there so that foo = argparse.SUPPRESS; foo = "test" checks out in mypy -ZERO_OR_MORE: Literal["*"] -_UNRECOGNIZED_ARGS_ATTR: str # undocumented +ZERO_OR_MORE: Final = "*" +_UNRECOGNIZED_ARGS_ATTR: Final[str] # undocumented class ArgumentError(Exception): argument_name: str | None diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/asyncio/base_futures.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/asyncio/base_futures.pyi index 2317662009349..55d2fbdbdb627 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/asyncio/base_futures.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/asyncio/base_futures.pyi @@ -1,6 +1,6 @@ from collections.abc import Callable, Sequence from contextvars import Context -from typing import Any, Literal +from typing import Any, Final from . import futures @@ -11,9 +11,9 @@ __all__ = () # That's why the import order is reversed. from .futures import isfuture as isfuture -_PENDING: Literal["PENDING"] # undocumented -_CANCELLED: Literal["CANCELLED"] # undocumented -_FINISHED: Literal["FINISHED"] # undocumented +_PENDING: Final = "PENDING" # undocumented +_CANCELLED: Final = "CANCELLED" # undocumented +_FINISHED: Final = "FINISHED" # undocumented def _format_callbacks(cb: Sequence[tuple[Callable[[futures.Future[Any]], None], Context]]) -> str: ... # undocumented def _future_repr_info(future: futures.Future[Any]) -> list[str]: ... # undocumented diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/asyncio/constants.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/asyncio/constants.pyi index 7759a28449530..5c6456b0e9c04 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/asyncio/constants.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/asyncio/constants.pyi @@ -1,18 +1,18 @@ import enum import sys -from typing import Literal +from typing import Final -LOG_THRESHOLD_FOR_CONNLOST_WRITES: Literal[5] -ACCEPT_RETRY_DELAY: Literal[1] -DEBUG_STACK_DEPTH: Literal[10] +LOG_THRESHOLD_FOR_CONNLOST_WRITES: Final = 5 +ACCEPT_RETRY_DELAY: Final = 1 +DEBUG_STACK_DEPTH: Final = 10 SSL_HANDSHAKE_TIMEOUT: float -SENDFILE_FALLBACK_READBUFFER_SIZE: Literal[262144] +SENDFILE_FALLBACK_READBUFFER_SIZE: Final = 262144 if sys.version_info >= (3, 11): SSL_SHUTDOWN_TIMEOUT: float - FLOW_CONTROL_HIGH_WATER_SSL_READ: Literal[256] - FLOW_CONTROL_HIGH_WATER_SSL_WRITE: Literal[512] + FLOW_CONTROL_HIGH_WATER_SSL_READ: Final = 256 + FLOW_CONTROL_HIGH_WATER_SSL_WRITE: Final = 512 if sys.version_info >= (3, 12): - THREAD_JOIN_TIMEOUT: Literal[300] + THREAD_JOIN_TIMEOUT: Final = 300 class _SendfileMode(enum.Enum): UNSUPPORTED = 1 diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/asyncio/sslproto.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/asyncio/sslproto.pyi index e904d7395cdc8..ded1933dd6597 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/asyncio/sslproto.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/asyncio/sslproto.pyi @@ -3,7 +3,7 @@ import sys from collections import deque from collections.abc import Callable from enum import Enum -from typing import Any, ClassVar, Literal +from typing import Any, ClassVar, Final, Literal from typing_extensions import TypeAlias from . import constants, events, futures, protocols, transports @@ -29,10 +29,10 @@ if sys.version_info >= (3, 11): def add_flowcontrol_defaults(high: int | None, low: int | None, kb: int) -> tuple[int, int]: ... else: - _UNWRAPPED: Literal["UNWRAPPED"] - _DO_HANDSHAKE: Literal["DO_HANDSHAKE"] - _WRAPPED: Literal["WRAPPED"] - _SHUTDOWN: Literal["SHUTDOWN"] + _UNWRAPPED: Final = "UNWRAPPED" + _DO_HANDSHAKE: Final = "DO_HANDSHAKE" + _WRAPPED: Final = "WRAPPED" + _SHUTDOWN: Final = "SHUTDOWN" if sys.version_info < (3, 11): class _SSLPipe: diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/asyncio/tasks.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/asyncio/tasks.pyi index 4613bca70c1a5..f23ecef126d6f 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/asyncio/tasks.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/asyncio/tasks.pyi @@ -429,7 +429,11 @@ class Task(Future[_T_co]): # type: ignore[type-var] # pyright: ignore[reportIn self, coro: _TaskCompatibleCoro[_T_co], *, loop: AbstractEventLoop = ..., name: str | None = ... ) -> None: ... - def get_coro(self) -> _TaskCompatibleCoro[_T_co]: ... + if sys.version_info >= (3, 12): + def get_coro(self) -> _TaskCompatibleCoro[_T_co] | None: ... + else: + def get_coro(self) -> _TaskCompatibleCoro[_T_co]: ... + def get_name(self) -> str: ... def set_name(self, value: object, /) -> None: ... if sys.version_info >= (3, 12): diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/asyncio/windows_events.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/asyncio/windows_events.pyi index 5c4e3067ad1c0..e5205ba4dcb07 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/asyncio/windows_events.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/asyncio/windows_events.pyi @@ -2,7 +2,7 @@ import socket import sys from _typeshed import Incomplete, ReadableBuffer, WriteableBuffer from collections.abc import Callable -from typing import IO, Any, ClassVar, Literal, NoReturn +from typing import IO, Any, ClassVar, Final, NoReturn from . import events, futures, proactor_events, selector_events, streams, windows_utils @@ -28,10 +28,10 @@ if sys.platform == "win32": "WindowsProactorEventLoopPolicy", ) - NULL: Literal[0] - INFINITE: Literal[0xFFFFFFFF] - ERROR_CONNECTION_REFUSED: Literal[1225] - ERROR_CONNECTION_ABORTED: Literal[1236] + NULL: Final = 0 + INFINITE: Final = 0xFFFFFFFF + ERROR_CONNECTION_REFUSED: Final = 1225 + ERROR_CONNECTION_ABORTED: Final = 1236 CONNECT_PIPE_INIT_DELAY: float CONNECT_PIPE_MAX_DELAY: float diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/asyncio/windows_utils.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/asyncio/windows_utils.pyi index 6b3589adc3cb9..4fa0145323762 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/asyncio/windows_utils.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/asyncio/windows_utils.pyi @@ -2,13 +2,13 @@ import subprocess import sys from collections.abc import Callable from types import TracebackType -from typing import Any, AnyStr, Literal +from typing import Any, AnyStr, Final from typing_extensions import Self if sys.platform == "win32": __all__ = ("pipe", "Popen", "PIPE", "PipeHandle") - BUFSIZE: Literal[8192] + BUFSIZE: Final = 8192 PIPE = subprocess.PIPE STDOUT = subprocess.STDOUT def pipe(*, duplex: bool = False, overlapped: tuple[bool, bool] = (True, True), bufsize: int = 8192) -> tuple[int, int]: ... diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/bdb.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/bdb.pyi index b73d8dcf4e367..75bfa91cc3798 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/bdb.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/bdb.pyi @@ -2,7 +2,7 @@ import sys from _typeshed import ExcInfo, TraceFunction, Unused from collections.abc import Callable, Iterable, Mapping from types import CodeType, FrameType, TracebackType -from typing import IO, Any, Literal, SupportsInt, TypeVar +from typing import IO, Any, Final, SupportsInt, TypeVar from typing_extensions import ParamSpec __all__ = ["BdbQuit", "Bdb", "Breakpoint"] @@ -10,7 +10,10 @@ __all__ = ["BdbQuit", "Bdb", "Breakpoint"] _T = TypeVar("_T") _P = ParamSpec("_P") -GENERATOR_AND_COROUTINE_FLAGS: Literal[672] +# A union of code-object flags at runtime. +# The exact values of code-object flags are implementation details, +# so we don't include the value of this constant in the stubs. +GENERATOR_AND_COROUTINE_FLAGS: Final[int] class BdbQuit(Exception): ... diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/binhex.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/binhex.pyi index d514be3b9b26a..bdead928468f4 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/binhex.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/binhex.pyi @@ -1,14 +1,14 @@ from _typeshed import SizedBuffer -from typing import IO, Any, Literal +from typing import IO, Any, Final from typing_extensions import TypeAlias __all__ = ["binhex", "hexbin", "Error"] class Error(Exception): ... -REASONABLY_LARGE: Literal[32768] -LINELEN: Literal[64] -RUNCHAR: Literal[b"\x90"] +REASONABLY_LARGE: Final = 32768 +LINELEN: Final = 64 +RUNCHAR: Final = b"\x90" class FInfo: Type: str diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/builtins.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/builtins.pyi index 6e0232f200ec0..bd9e759e90fb5 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/builtins.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/builtins.pyi @@ -1868,6 +1868,7 @@ class BaseException: __suppress_context__: bool __traceback__: TracebackType | None def __init__(self, *args: object) -> None: ... + def __new__(cls, *args: Any, **kwds: Any) -> Self: ... def __setstate__(self, state: dict[str, Any] | None, /) -> None: ... def with_traceback(self, tb: TracebackType | None, /) -> Self: ... if sys.version_info >= (3, 11): diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/cmd.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/cmd.pyi index 9499847fb1534..0733857433bee 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/cmd.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/cmd.pyi @@ -1,9 +1,9 @@ from collections.abc import Callable -from typing import IO, Any, Literal +from typing import IO, Any, Final __all__ = ["Cmd"] -PROMPT: Literal["(Cmd) "] +PROMPT: Final = "(Cmd) " IDENTCHARS: str # Too big to be `Literal` class Cmd: diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/codecs.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/codecs.pyi index 6e53b780c4736..9bc098dbc6d74 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/codecs.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/codecs.pyi @@ -3,7 +3,7 @@ from _codecs import * from _typeshed import ReadableBuffer from abc import abstractmethod from collections.abc import Callable, Generator, Iterable -from typing import Any, BinaryIO, Literal, Protocol, TextIO +from typing import Any, BinaryIO, Final, Literal, Protocol, TextIO from typing_extensions import Self __all__ = [ @@ -53,10 +53,10 @@ __all__ = [ "lookup_error", ] -BOM32_BE: Literal[b"\xfe\xff"] -BOM32_LE: Literal[b"\xff\xfe"] -BOM64_BE: Literal[b"\x00\x00\xfe\xff"] -BOM64_LE: Literal[b"\xff\xfe\x00\x00"] +BOM32_BE: Final = b"\xfe\xff" +BOM32_LE: Final = b"\xff\xfe" +BOM64_BE: Final = b"\x00\x00\xfe\xff" +BOM64_LE: Final = b"\xff\xfe\x00\x00" class _WritableStream(Protocol): def write(self, data: bytes, /) -> object: ... @@ -135,23 +135,23 @@ def EncodedFile(file: _Stream, data_encoding: str, file_encoding: str | None = N def iterencode(iterator: Iterable[str], encoding: str, errors: str = "strict") -> Generator[bytes, None, None]: ... def iterdecode(iterator: Iterable[bytes], encoding: str, errors: str = "strict") -> Generator[str, None, None]: ... -BOM: Literal[b"\xff\xfe", b"\xfe\xff"] # depends on `sys.byteorder` -BOM_BE: Literal[b"\xfe\xff"] -BOM_LE: Literal[b"\xff\xfe"] -BOM_UTF8: Literal[b"\xef\xbb\xbf"] -BOM_UTF16: Literal[b"\xff\xfe", b"\xfe\xff"] # depends on `sys.byteorder` -BOM_UTF16_BE: Literal[b"\xfe\xff"] -BOM_UTF16_LE: Literal[b"\xff\xfe"] -BOM_UTF32: Literal[b"\xff\xfe\x00\x00", b"\x00\x00\xfe\xff"] # depends on `sys.byteorder` -BOM_UTF32_BE: Literal[b"\x00\x00\xfe\xff"] -BOM_UTF32_LE: Literal[b"\xff\xfe\x00\x00"] +BOM: Final[Literal[b"\xff\xfe", b"\xfe\xff"]] # depends on `sys.byteorder` +BOM_BE: Final = b"\xfe\xff" +BOM_LE: Final = b"\xff\xfe" +BOM_UTF8: Final = b"\xef\xbb\xbf" +BOM_UTF16: Final[Literal[b"\xff\xfe", b"\xfe\xff"]] # depends on `sys.byteorder` +BOM_UTF16_BE: Final = b"\xfe\xff" +BOM_UTF16_LE: Final = b"\xff\xfe" +BOM_UTF32: Final[Literal[b"\xff\xfe\x00\x00", b"\x00\x00\xfe\xff"]] # depends on `sys.byteorder` +BOM_UTF32_BE: Final = b"\x00\x00\xfe\xff" +BOM_UTF32_LE: Final = b"\xff\xfe\x00\x00" -def strict_errors(exception: UnicodeError) -> tuple[str | bytes, int]: ... -def replace_errors(exception: UnicodeError) -> tuple[str | bytes, int]: ... -def ignore_errors(exception: UnicodeError) -> tuple[str | bytes, int]: ... -def xmlcharrefreplace_errors(exception: UnicodeError) -> tuple[str | bytes, int]: ... -def backslashreplace_errors(exception: UnicodeError) -> tuple[str | bytes, int]: ... -def namereplace_errors(exception: UnicodeError) -> tuple[str | bytes, int]: ... +def strict_errors(exception: UnicodeError, /) -> tuple[str | bytes, int]: ... +def replace_errors(exception: UnicodeError, /) -> tuple[str | bytes, int]: ... +def ignore_errors(exception: UnicodeError, /) -> tuple[str | bytes, int]: ... +def xmlcharrefreplace_errors(exception: UnicodeError, /) -> tuple[str | bytes, int]: ... +def backslashreplace_errors(exception: UnicodeError, /) -> tuple[str | bytes, int]: ... +def namereplace_errors(exception: UnicodeError, /) -> tuple[str | bytes, int]: ... class Codec: # These are sort of @abstractmethod but sort of not. diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/concurrent/futures/_base.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/concurrent/futures/_base.pyi index 3d5eccfc048dc..0c019457902b0 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/concurrent/futures/_base.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/concurrent/futures/_base.pyi @@ -4,20 +4,20 @@ from _typeshed import Unused from collections.abc import Callable, Collection, Iterable, Iterator from logging import Logger from types import TracebackType -from typing import Any, Generic, Literal, NamedTuple, Protocol, TypeVar +from typing import Any, Final, Generic, NamedTuple, Protocol, TypeVar from typing_extensions import ParamSpec, Self if sys.version_info >= (3, 9): from types import GenericAlias -FIRST_COMPLETED: Literal["FIRST_COMPLETED"] -FIRST_EXCEPTION: Literal["FIRST_EXCEPTION"] -ALL_COMPLETED: Literal["ALL_COMPLETED"] -PENDING: Literal["PENDING"] -RUNNING: Literal["RUNNING"] -CANCELLED: Literal["CANCELLED"] -CANCELLED_AND_NOTIFIED: Literal["CANCELLED_AND_NOTIFIED"] -FINISHED: Literal["FINISHED"] +FIRST_COMPLETED: Final = "FIRST_COMPLETED" +FIRST_EXCEPTION: Final = "FIRST_EXCEPTION" +ALL_COMPLETED: Final = "ALL_COMPLETED" +PENDING: Final = "PENDING" +RUNNING: Final = "RUNNING" +CANCELLED: Final = "CANCELLED" +CANCELLED_AND_NOTIFIED: Final = "CANCELLED_AND_NOTIFIED" +FINISHED: Final = "FINISHED" _FUTURE_STATES: list[str] _STATE_TO_DESCRIPTION_MAP: dict[str, str] LOGGER: Logger diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/configparser.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/configparser.pyi index f38bb1de674de..ee5000196e0e7 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/configparser.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/configparser.pyi @@ -2,7 +2,7 @@ import sys from _typeshed import StrOrBytesPath, SupportsWrite from collections.abc import Callable, ItemsView, Iterable, Iterator, Mapping, MutableMapping, Sequence from re import Pattern -from typing import Any, ClassVar, Literal, TypeVar, overload +from typing import Any, ClassVar, Final, Literal, TypeVar, overload from typing_extensions import TypeAlias if sys.version_info >= (3, 13): @@ -83,8 +83,8 @@ _ConverterCallback: TypeAlias = Callable[[str], Any] _ConvertersMap: TypeAlias = dict[str, _ConverterCallback] _T = TypeVar("_T") -DEFAULTSECT: Literal["DEFAULT"] -MAX_INTERPOLATION_DEPTH: Literal[10] +DEFAULTSECT: Final = "DEFAULT" +MAX_INTERPOLATION_DEPTH: Final = 10 class Interpolation: def before_get(self, parser: _Parser, section: str, option: str, value: str, defaults: _Section) -> str: ... diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/copy.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/copy.pyi index 8a2dcc508e5d1..020ce6c31b580 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/copy.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/copy.pyi @@ -1,8 +1,16 @@ -from typing import Any, TypeVar +import sys +from typing import Any, Protocol, TypeVar +from typing_extensions import ParamSpec, Self __all__ = ["Error", "copy", "deepcopy"] _T = TypeVar("_T") +_SR = TypeVar("_SR", bound=_SupportsReplace[Any]) +_P = ParamSpec("_P") + +class _SupportsReplace(Protocol[_P]): + # In reality doesn't support args, but there's no other great way to express this. + def __replace__(self, *args: _P.args, **kwargs: _P.kwargs) -> Self: ... # None in CPython but non-None in Jython PyStringMap: Any @@ -11,6 +19,10 @@ PyStringMap: Any def deepcopy(x: _T, memo: dict[int, Any] | None = None, _nil: Any = []) -> _T: ... def copy(x: _T) -> _T: ... +if sys.version_info >= (3, 13): + __all__ += ["replace"] + def replace(obj: _SR, /, **changes: Any) -> _SR: ... + class Error(Exception): ... error = Error diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/datetime.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/datetime.pyi index 71522a59d4df8..38d5ac4c08198 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/datetime.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/datetime.pyi @@ -1,7 +1,7 @@ import sys from abc import abstractmethod from time import struct_time -from typing import ClassVar, Literal, NamedTuple, NoReturn, SupportsIndex, final, overload +from typing import ClassVar, Final, NamedTuple, NoReturn, SupportsIndex, final, overload from typing_extensions import Self, TypeAlias, deprecated if sys.version_info >= (3, 11): @@ -9,8 +9,8 @@ if sys.version_info >= (3, 11): elif sys.version_info >= (3, 9): __all__ = ("date", "datetime", "time", "timedelta", "timezone", "tzinfo", "MINYEAR", "MAXYEAR") -MINYEAR: Literal[1] -MAXYEAR: Literal[9999] +MINYEAR: Final = 1 +MAXYEAR: Final = 9999 class tzinfo: @abstractmethod diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/distutils/ccompiler.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/distutils/ccompiler.pyi index cd6efee0a2103..e0f33f430e5a0 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/distutils/ccompiler.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/distutils/ccompiler.pyi @@ -1,10 +1,11 @@ -from _typeshed import BytesPath, StrPath +from _typeshed import BytesPath, StrPath, Unused from collections.abc import Callable, Iterable from distutils.file_util import _BytesPathT, _StrPathT -from typing import Any, Literal, overload -from typing_extensions import TypeAlias +from typing import Literal, overload +from typing_extensions import TypeAlias, TypeVarTuple, Unpack _Macro: TypeAlias = tuple[str] | tuple[str, str | None] +_Ts = TypeVarTuple("_Ts") def gen_lib_options( compiler: CCompiler, library_dirs: list[str], runtime_library_dirs: list[str], libraries: list[str] @@ -161,7 +162,9 @@ class CCompiler: def shared_object_filename(self, basename: str, strip_dir: Literal[0, False] = 0, output_dir: StrPath = "") -> str: ... @overload def shared_object_filename(self, basename: StrPath, strip_dir: Literal[1, True], output_dir: StrPath = "") -> str: ... - def execute(self, func: Callable[..., object], args: tuple[Any, ...], msg: str | None = None, level: int = 1) -> None: ... + def execute( + self, func: Callable[[Unpack[_Ts]], Unused], args: tuple[Unpack[_Ts]], msg: str | None = None, level: int = 1 + ) -> None: ... def spawn(self, cmd: list[str]) -> None: ... def mkpath(self, name: str, mode: int = 0o777) -> None: ... @overload diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/distutils/cmd.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/distutils/cmd.pyi index defea50e78dc2..ca4fb3265324f 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/distutils/cmd.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/distutils/cmd.pyi @@ -3,7 +3,11 @@ from abc import abstractmethod from collections.abc import Callable, Iterable from distutils.dist import Distribution from distutils.file_util import _BytesPathT, _StrPathT -from typing import Any, ClassVar, Literal, overload +from typing import Any, ClassVar, Literal, TypeVar, overload +from typing_extensions import TypeVarTuple, Unpack + +_CommandT = TypeVar("_CommandT", bound=Command) +_Ts = TypeVarTuple("_Ts") class Command: distribution: Distribution @@ -19,17 +23,22 @@ class Command: def announce(self, msg: str, level: int = 1) -> None: ... def debug_print(self, msg: str) -> None: ... def ensure_string(self, option: str, default: str | None = None) -> None: ... - def ensure_string_list(self, option: str | list[str]) -> None: ... + def ensure_string_list(self, option: str) -> None: ... def ensure_filename(self, option: str) -> None: ... def ensure_dirname(self, option: str) -> None: ... def get_command_name(self) -> str: ... def set_undefined_options(self, src_cmd: str, *option_pairs: tuple[str, str]) -> None: ... def get_finalized_command(self, command: str, create: bool | Literal[0, 1] = 1) -> Command: ... - def reinitialize_command(self, command: Command | str, reinit_subcommands: bool | Literal[0, 1] = 0) -> Command: ... + @overload + def reinitialize_command(self, command: str, reinit_subcommands: bool | Literal[0, 1] = 0) -> Command: ... + @overload + def reinitialize_command(self, command: _CommandT, reinit_subcommands: bool | Literal[0, 1] = 0) -> _CommandT: ... def run_command(self, command: str) -> None: ... def get_sub_commands(self) -> list[str]: ... def warn(self, msg: str) -> None: ... - def execute(self, func: Callable[..., object], args: Iterable[Any], msg: str | None = None, level: int = 1) -> None: ... + def execute( + self, func: Callable[[Unpack[_Ts]], Unused], args: tuple[Unpack[_Ts]], msg: str | None = None, level: int = 1 + ) -> None: ... def mkpath(self, name: str, mode: int = 0o777) -> None: ... @overload def copy_file( @@ -89,8 +98,8 @@ class Command: self, infiles: str | list[str] | tuple[str, ...], outfile: StrOrBytesPath, - func: Callable[..., object], - args: list[Any], + func: Callable[[Unpack[_Ts]], Unused], + args: tuple[Unpack[_Ts]], exec_msg: str | None = None, skip_msg: str | None = None, level: Unused = 1, diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/distutils/command/bdist.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/distutils/command/bdist.pyi index e1f141d3a40fa..43d77087f7d8a 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/distutils/command/bdist.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/distutils/command/bdist.pyi @@ -1,4 +1,6 @@ -from typing import Any +from _typeshed import Unused +from collections.abc import Callable +from typing import Any, ClassVar from ..cmd import Command @@ -6,13 +8,13 @@ def show_formats() -> None: ... class bdist(Command): description: str - user_options: Any - boolean_options: Any - help_options: Any - no_format_option: Any - default_format: Any - format_commands: Any - format_command: Any + user_options: ClassVar[list[tuple[str, str | None, str]]] + boolean_options: ClassVar[list[str]] + help_options: ClassVar[list[tuple[str, str | None, str, Callable[[], Unused]]]] + no_format_option: ClassVar[tuple[str, ...]] + default_format: ClassVar[dict[str, str]] + format_commands: ClassVar[list[str]] + format_command: ClassVar[dict[str, tuple[str, str]]] bdist_base: Any plat_name: Any formats: Any diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/distutils/command/bdist_dumb.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/distutils/command/bdist_dumb.pyi index 74cca4d13cd0b..19997882dd537 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/distutils/command/bdist_dumb.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/distutils/command/bdist_dumb.pyi @@ -1,12 +1,12 @@ -from typing import Any +from typing import Any, ClassVar from ..cmd import Command class bdist_dumb(Command): description: str - user_options: Any - boolean_options: Any - default_format: Any + user_options: ClassVar[list[tuple[str, str | None, str]]] + boolean_options: ClassVar[list[str]] + default_format: ClassVar[dict[str, str]] bdist_dir: Any plat_name: Any format: Any diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/distutils/command/bdist_msi.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/distutils/command/bdist_msi.pyi index d1eb374ff52bd..d0eac1a3be5bb 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/distutils/command/bdist_msi.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/distutils/command/bdist_msi.pyi @@ -1,5 +1,5 @@ import sys -from typing import Any, Literal +from typing import Any, ClassVar, Literal from ..cmd import Command @@ -16,8 +16,8 @@ if sys.platform == "win32": class bdist_msi(Command): description: str - user_options: Any - boolean_options: Any + user_options: ClassVar[list[tuple[str, str | None, str]]] + boolean_options: ClassVar[list[str]] all_versions: Any other_version: str if sys.version_info >= (3, 9): diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/distutils/command/bdist_rpm.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/distutils/command/bdist_rpm.pyi index 76691310b5999..89c43e1b974cd 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/distutils/command/bdist_rpm.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/distutils/command/bdist_rpm.pyi @@ -1,12 +1,12 @@ -from typing import Any +from typing import Any, ClassVar from ..cmd import Command class bdist_rpm(Command): description: str - user_options: Any - boolean_options: Any - negative_opt: Any + user_options: ClassVar[list[tuple[str, str | None, str]]] + boolean_options: ClassVar[list[str]] + negative_opt: ClassVar[dict[str, str]] bdist_base: Any rpm_base: Any dist_dir: Any diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/distutils/command/bdist_wininst.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/distutils/command/bdist_wininst.pyi index 8491d31262007..cf333bc5400dd 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/distutils/command/bdist_wininst.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/distutils/command/bdist_wininst.pyi @@ -1,10 +1,10 @@ from _typeshed import StrOrBytesPath from distutils.cmd import Command -from typing import Any, ClassVar +from typing import ClassVar class bdist_wininst(Command): description: ClassVar[str] - user_options: ClassVar[list[tuple[Any, ...]]] + user_options: ClassVar[list[tuple[str, str | None, str]]] boolean_options: ClassVar[list[str]] def initialize_options(self) -> None: ... diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/distutils/command/build.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/distutils/command/build.pyi index 31fc036d4f97e..78ba6b7042dc1 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/distutils/command/build.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/distutils/command/build.pyi @@ -1,3 +1,4 @@ +from _typeshed import Unused from collections.abc import Callable from typing import Any, ClassVar @@ -7,9 +8,9 @@ def show_compilers() -> None: ... class build(Command): description: str - user_options: Any - boolean_options: Any - help_options: Any + user_options: ClassVar[list[tuple[str, str | None, str]]] + boolean_options: ClassVar[list[str]] + help_options: ClassVar[list[tuple[str, str | None, str, Callable[[], Unused]]]] build_base: str build_purelib: Any build_platlib: Any diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/distutils/command/build_clib.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/distutils/command/build_clib.pyi index 32ab182b30d04..1f66e2efc20c7 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/distutils/command/build_clib.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/distutils/command/build_clib.pyi @@ -1,4 +1,6 @@ -from typing import Any +from _typeshed import Unused +from collections.abc import Callable +from typing import Any, ClassVar from ..cmd import Command @@ -6,9 +8,9 @@ def show_compilers() -> None: ... class build_clib(Command): description: str - user_options: Any - boolean_options: Any - help_options: Any + user_options: ClassVar[list[tuple[str, str, str]]] + boolean_options: ClassVar[list[str]] + help_options: ClassVar[list[tuple[str, str | None, str, Callable[[], Unused]]]] build_clib: Any build_temp: Any libraries: Any diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/distutils/command/build_ext.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/distutils/command/build_ext.pyi index 5eb541fb91019..a0813c314021d 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/distutils/command/build_ext.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/distutils/command/build_ext.pyi @@ -1,4 +1,6 @@ -from typing import Any +from _typeshed import Unused +from collections.abc import Callable +from typing import Any, ClassVar from ..cmd import Command @@ -9,9 +11,9 @@ def show_compilers() -> None: ... class build_ext(Command): description: str sep_by: Any - user_options: Any - boolean_options: Any - help_options: Any + user_options: ClassVar[list[tuple[str, str | None, str]]] + boolean_options: ClassVar[list[str]] + help_options: ClassVar[list[tuple[str, str | None, str, Callable[[], Unused]]]] extensions: Any build_lib: Any plat_name: Any diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/distutils/command/build_py.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/distutils/command/build_py.pyi index 4c607c6dabe90..90f06751416a6 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/distutils/command/build_py.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/distutils/command/build_py.pyi @@ -1,13 +1,13 @@ -from typing import Any, Literal +from typing import Any, ClassVar, Literal from ..cmd import Command from ..util import Mixin2to3 as Mixin2to3 class build_py(Command): description: str - user_options: Any - boolean_options: Any - negative_opt: Any + user_options: ClassVar[list[tuple[str, str | None, str]]] + boolean_options: ClassVar[list[str]] + negative_opt: ClassVar[dict[str, str]] build_lib: Any py_modules: Any package: Any diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/distutils/command/build_scripts.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/distutils/command/build_scripts.pyi index 42135eceafefd..7871bb8a57197 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/distutils/command/build_scripts.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/distutils/command/build_scripts.pyi @@ -1,4 +1,4 @@ -from typing import Any +from typing import Any, ClassVar from ..cmd import Command from ..util import Mixin2to3 as Mixin2to3 @@ -7,8 +7,8 @@ first_line_re: Any class build_scripts(Command): description: str - user_options: Any - boolean_options: Any + user_options: ClassVar[list[tuple[str, str, str]]] + boolean_options: ClassVar[list[str]] build_dir: Any scripts: Any force: Any diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/distutils/command/check.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/distutils/command/check.pyi index da041d82587de..c67e4cbfdfe01 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/distutils/command/check.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/distutils/command/check.pyi @@ -1,4 +1,4 @@ -from typing import Any, Literal +from typing import Any, ClassVar, Literal from typing_extensions import TypeAlias from ..cmd import Command @@ -26,8 +26,8 @@ HAS_DOCUTILS: bool class check(Command): description: str - user_options: Any - boolean_options: Any + user_options: ClassVar[list[tuple[str, str, str]]] + boolean_options: ClassVar[list[str]] restructuredtext: int metadata: int strict: int diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/distutils/command/clean.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/distutils/command/clean.pyi index 99560aa8a716c..55f0a0eeaf10b 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/distutils/command/clean.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/distutils/command/clean.pyi @@ -1,11 +1,11 @@ -from typing import Any +from typing import Any, ClassVar from ..cmd import Command class clean(Command): description: str - user_options: Any - boolean_options: Any + user_options: ClassVar[list[tuple[str, str | None, str]]] + boolean_options: ClassVar[list[str]] build_base: Any build_lib: Any build_temp: Any diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/distutils/command/config.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/distutils/command/config.pyi index 391f5a8620383..2f528c2c290b0 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/distutils/command/config.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/distutils/command/config.pyi @@ -1,7 +1,7 @@ from _typeshed import StrOrBytesPath from collections.abc import Sequence from re import Pattern -from typing import Any, Literal +from typing import Any, ClassVar, Literal from ..ccompiler import CCompiler from ..cmd import Command @@ -11,7 +11,7 @@ LANG_EXT: dict[str, str] class config(Command): description: str # Tuple is full name, short name, description - user_options: Sequence[tuple[str, str | None, str]] + user_options: ClassVar[list[tuple[str, str | None, str]]] compiler: str | CCompiler cc: str | None include_dirs: Sequence[str] | None diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/distutils/command/install.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/distutils/command/install.pyi index 8b2295d7a3c7e..b0a5a82fc3f6e 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/distutils/command/install.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/distutils/command/install.pyi @@ -9,9 +9,9 @@ INSTALL_SCHEMES: dict[str, dict[Any, Any]] class install(Command): description: str - user_options: Any - boolean_options: Any - negative_opt: Any + user_options: ClassVar[list[tuple[str, str | None, str]]] + boolean_options: ClassVar[list[str]] + negative_opt: ClassVar[dict[str, str]] prefix: str | None exec_prefix: Any home: str | None diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/distutils/command/install_data.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/distutils/command/install_data.pyi index 6cc9b528ac9da..342c7a7ccca4a 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/distutils/command/install_data.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/distutils/command/install_data.pyi @@ -1,11 +1,11 @@ -from typing import Any +from typing import Any, ClassVar from ..cmd import Command class install_data(Command): description: str - user_options: Any - boolean_options: Any + user_options: ClassVar[list[tuple[str, str | None, str]]] + boolean_options: ClassVar[list[str]] install_dir: Any outfiles: Any root: Any diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/distutils/command/install_egg_info.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/distutils/command/install_egg_info.pyi index 776eafc1de09c..3fd54989d14f3 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/distutils/command/install_egg_info.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/distutils/command/install_egg_info.pyi @@ -4,7 +4,7 @@ from ..cmd import Command class install_egg_info(Command): description: ClassVar[str] - user_options: ClassVar[list[tuple[str, str | None, str]]] + user_options: ClassVar[list[tuple[str, str, str]]] install_dir: Any def initialize_options(self) -> None: ... target: Any diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/distutils/command/install_headers.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/distutils/command/install_headers.pyi index 795bd1cf8356b..7854d2393a987 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/distutils/command/install_headers.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/distutils/command/install_headers.pyi @@ -1,11 +1,11 @@ -from typing import Any +from typing import Any, ClassVar from ..cmd import Command class install_headers(Command): description: str - user_options: Any - boolean_options: Any + user_options: ClassVar[list[tuple[str, str, str]]] + boolean_options: ClassVar[list[str]] install_dir: Any force: int outfiles: Any diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/distutils/command/install_lib.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/distutils/command/install_lib.pyi index a6a5e4e73f4c0..718d082b7b076 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/distutils/command/install_lib.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/distutils/command/install_lib.pyi @@ -1,4 +1,4 @@ -from typing import Any +from typing import Any, ClassVar from ..cmd import Command @@ -6,9 +6,9 @@ PYTHON_SOURCE_EXTENSION: str class install_lib(Command): description: str - user_options: Any - boolean_options: Any - negative_opt: Any + user_options: ClassVar[list[tuple[str, str | None, str]]] + boolean_options: ClassVar[list[str]] + negative_opt: ClassVar[dict[str, str]] install_dir: Any build_dir: Any force: int diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/distutils/command/install_scripts.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/distutils/command/install_scripts.pyi index 92728a16a7478..5ee5589ad33d0 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/distutils/command/install_scripts.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/distutils/command/install_scripts.pyi @@ -1,11 +1,11 @@ -from typing import Any +from typing import Any, ClassVar from ..cmd import Command class install_scripts(Command): description: str - user_options: Any - boolean_options: Any + user_options: ClassVar[list[tuple[str, str | None, str]]] + boolean_options: ClassVar[list[str]] install_dir: Any force: int build_dir: Any diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/distutils/command/sdist.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/distutils/command/sdist.pyi index db303f77a4634..5b7fe24195519 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/distutils/command/sdist.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/distutils/command/sdist.pyi @@ -1,3 +1,4 @@ +from _typeshed import Unused from collections.abc import Callable from typing import Any, ClassVar @@ -8,13 +9,13 @@ def show_formats() -> None: ... class sdist(Command): description: str def checking_metadata(self): ... - user_options: Any - boolean_options: Any - help_options: Any - negative_opt: Any + user_options: ClassVar[list[tuple[str, str | None, str]]] + boolean_options: ClassVar[list[str]] + help_options: ClassVar[list[tuple[str, str | None, str, Callable[[], Unused]]]] + negative_opt: ClassVar[dict[str, str]] # Any to work around variance issues sub_commands: ClassVar[list[tuple[str, Callable[[Any], bool] | None]]] - READMES: Any + READMES: ClassVar[tuple[str, ...]] template: Any manifest: Any use_defaults: int diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/distutils/dist.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/distutils/dist.pyi index 4094df9033250..21ddbc4259183 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/distutils/dist.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/distutils/dist.pyi @@ -1,8 +1,8 @@ from _typeshed import Incomplete, StrOrBytesPath, StrPath, SupportsWrite -from collections.abc import Iterable, Mapping +from collections.abc import Iterable, MutableMapping from distutils.cmd import Command from re import Pattern -from typing import IO, Any, ClassVar, Literal, TypeVar, overload +from typing import IO, ClassVar, Literal, TypeVar, overload from typing_extensions import TypeAlias command_re: Pattern[str] @@ -60,7 +60,7 @@ class DistributionMetadata: class Distribution: cmdclass: dict[str, type[Command]] metadata: DistributionMetadata - def __init__(self, attrs: Mapping[str, Any] | None = None) -> None: ... + def __init__(self, attrs: MutableMapping[str, Incomplete] | None = None) -> None: ... def get_option_dict(self, command: str) -> dict[str, tuple[str, str]]: ... def parse_config_files(self, filenames: Iterable[str] | None = None) -> None: ... @overload diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/distutils/util.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/distutils/util.pyi index 515b5b2b86d9f..0e1bb4165d99d 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/distutils/util.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/distutils/util.pyi @@ -1,6 +1,9 @@ from _typeshed import StrPath, Unused from collections.abc import Callable, Container, Iterable, Mapping from typing import Any, Literal +from typing_extensions import TypeVarTuple, Unpack + +_Ts = TypeVarTuple("_Ts") def get_host_platform() -> str: ... def get_platform() -> str: ... @@ -10,8 +13,8 @@ def check_environ() -> None: ... def subst_vars(s: str, local_vars: Mapping[str, str]) -> None: ... def split_quoted(s: str) -> list[str]: ... def execute( - func: Callable[..., object], - args: tuple[Any, ...], + func: Callable[[Unpack[_Ts]], Unused], + args: tuple[Unpack[_Ts]], msg: str | None = None, verbose: bool | Literal[0, 1] = 0, dry_run: bool | Literal[0, 1] = 0, diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/email/charset.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/email/charset.pyi index 2d12df3372079..2939192c95264 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/email/charset.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/email/charset.pyi @@ -1,12 +1,12 @@ from collections.abc import Callable, Iterator from email.message import Message -from typing import overload +from typing import Final, overload __all__ = ["Charset", "add_alias", "add_charset", "add_codec"] -QP: int # undocumented -BASE64: int # undocumented -SHORTEST: int # undocumented +QP: Final[int] # undocumented +BASE64: Final[int] # undocumented +SHORTEST: Final[int] # undocumented class Charset: input_charset: str diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/fcntl.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/fcntl.pyi index ccf638205bbe2..376611f166b85 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/fcntl.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/fcntl.pyi @@ -1,6 +1,6 @@ import sys from _typeshed import FileDescriptorLike, ReadOnlyBuffer, WriteableBuffer -from typing import Any, Literal, overload +from typing import Any, Final, Literal, overload from typing_extensions import Buffer if sys.platform != "win32": @@ -44,9 +44,10 @@ if sys.platform != "win32": F_SEAL_SHRINK: int F_SEAL_WRITE: int if sys.version_info >= (3, 9): - F_OFD_GETLK: int - F_OFD_SETLK: int - F_OFD_SETLKW: int + F_OFD_GETLK: Final[int] + F_OFD_SETLK: Final[int] + F_OFD_SETLKW: Final[int] + if sys.version_info >= (3, 10): F_GETPIPE_SZ: int F_SETPIPE_SZ: int @@ -105,6 +106,36 @@ if sys.platform != "win32": FICLONE: int FICLONERANGE: int + if sys.version_info >= (3, 13) and sys.platform == "linux": + F_OWNER_TID: Final = 0 + F_OWNER_PID: Final = 1 + F_OWNER_PGRP: Final = 2 + F_SETOWN_EX: Final = 15 + F_GETOWN_EX: Final = 16 + F_SEAL_FUTURE_WRITE: Final = 16 + F_GET_RW_HINT: Final = 1035 + F_SET_RW_HINT: Final = 1036 + F_GET_FILE_RW_HINT: Final = 1037 + F_SET_FILE_RW_HINT: Final = 1038 + RWH_WRITE_LIFE_NOT_SET: Final = 0 + RWH_WRITE_LIFE_NONE: Final = 1 + RWH_WRITE_LIFE_SHORT: Final = 2 + RWH_WRITE_LIFE_MEDIUM: Final = 3 + RWH_WRITE_LIFE_LONG: Final = 4 + RWH_WRITE_LIFE_EXTREME: Final = 5 + + if sys.version_info >= (3, 11) and sys.platform == "darwin": + F_OFD_SETLK: Final = 90 + F_OFD_SETLKW: Final = 91 + F_OFD_GETLK: Final = 92 + + if sys.version_info >= (3, 13) and sys.platform != "linux": + # OSx and NetBSD + F_GETNOSIGPIPE: Final[int] + F_SETNOSIGPIPE: Final[int] + # OSx and FreeBSD + F_RDAHEAD: Final[int] + @overload def fcntl(fd: FileDescriptorLike, cmd: int, arg: int = 0, /) -> int: ... @overload diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/filecmp.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/filecmp.pyi index 5c8232d800d5f..dfec2da723440 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/filecmp.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/filecmp.pyi @@ -1,7 +1,7 @@ import sys from _typeshed import GenericPath, StrOrBytesPath from collections.abc import Callable, Iterable, Sequence -from typing import Any, AnyStr, Generic, Literal +from typing import Any, AnyStr, Final, Generic, Literal if sys.version_info >= (3, 9): from types import GenericAlias @@ -9,7 +9,7 @@ if sys.version_info >= (3, 9): __all__ = ["clear_cache", "cmp", "dircmp", "cmpfiles", "DEFAULT_IGNORES"] DEFAULT_IGNORES: list[str] -BUFSIZE: Literal[8192] +BUFSIZE: Final = 8192 def cmp(f1: StrOrBytesPath, f2: StrOrBytesPath, shallow: bool | Literal[0, 1] = True) -> bool: ... def cmpfiles( diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/ftplib.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/ftplib.pyi index 9e7097ddc56e8..1b96e0d504b71 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/ftplib.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/ftplib.pyi @@ -4,16 +4,16 @@ from collections.abc import Callable, Iterable, Iterator from socket import socket from ssl import SSLContext from types import TracebackType -from typing import Any, Literal, TextIO +from typing import Any, Final, Literal, TextIO from typing_extensions import Self __all__ = ["FTP", "error_reply", "error_temp", "error_perm", "error_proto", "all_errors", "FTP_TLS"] -MSG_OOB: Literal[1] -FTP_PORT: Literal[21] -MAXLINE: Literal[8192] -CRLF: Literal["\r\n"] -B_CRLF: Literal[b"\r\n"] +MSG_OOB: Final = 1 +FTP_PORT: Final = 21 +MAXLINE: Final = 8192 +CRLF: Final = "\r\n" +B_CRLF: Final = b"\r\n" class Error(Exception): ... class error_reply(Error): ... diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/gc.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/gc.pyi index 31179add314cf..9d34e0d6213a3 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/gc.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/gc.pyi @@ -1,13 +1,13 @@ import sys from collections.abc import Callable -from typing import Any, Literal +from typing import Any, Final, Literal from typing_extensions import TypeAlias -DEBUG_COLLECTABLE: Literal[2] -DEBUG_LEAK: Literal[38] -DEBUG_SAVEALL: Literal[32] -DEBUG_STATS: Literal[1] -DEBUG_UNCOLLECTABLE: Literal[4] +DEBUG_COLLECTABLE: Final = 2 +DEBUG_LEAK: Final = 38 +DEBUG_SAVEALL: Final = 32 +DEBUG_STATS: Final = 1 +DEBUG_UNCOLLECTABLE: Final = 4 _CallbackType: TypeAlias = Callable[[Literal["start", "stop"], dict[str, int]], object] @@ -34,4 +34,4 @@ if sys.version_info >= (3, 9): def isenabled() -> bool: ... def set_debug(flags: int, /) -> None: ... -def set_threshold(threshold0: int, threshold1: int = ..., threshold2: int = ...) -> None: ... +def set_threshold(threshold0: int, threshold1: int = ..., threshold2: int = ..., /) -> None: ... diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/gzip.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/gzip.pyi index 542945698bba0..9b32008dcbf65 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/gzip.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/gzip.pyi @@ -3,7 +3,7 @@ import sys import zlib from _typeshed import ReadableBuffer, SizedBuffer, StrOrBytesPath from io import FileIO -from typing import Literal, Protocol, TextIO, overload +from typing import Final, Literal, Protocol, TextIO, overload from typing_extensions import TypeAlias __all__ = ["BadGzipFile", "GzipFile", "open", "compress", "decompress"] @@ -12,14 +12,14 @@ _ReadBinaryMode: TypeAlias = Literal["r", "rb"] _WriteBinaryMode: TypeAlias = Literal["a", "ab", "w", "wb", "x", "xb"] _OpenTextMode: TypeAlias = Literal["rt", "at", "wt", "xt"] -READ: object # undocumented -WRITE: object # undocumented +READ: Final[object] # undocumented +WRITE: Final[object] # undocumented -FTEXT: int # actually Literal[1] # undocumented -FHCRC: int # actually Literal[2] # undocumented -FEXTRA: int # actually Literal[4] # undocumented -FNAME: int # actually Literal[8] # undocumented -FCOMMENT: int # actually Literal[16] # undocumented +FTEXT: Final[int] # actually Literal[1] # undocumented +FHCRC: Final[int] # actually Literal[2] # undocumented +FEXTRA: Final[int] # actually Literal[4] # undocumented +FNAME: Final[int] # actually Literal[8] # undocumented +FCOMMENT: Final[int] # actually Literal[16] # undocumented class _ReadableFileobj(Protocol): def read(self, n: int, /) -> bytes: ... diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/http/cookiejar.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/http/cookiejar.pyi index faac20d13125a..56097f163afd3 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/http/cookiejar.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/http/cookiejar.pyi @@ -42,7 +42,7 @@ class CookieJar(Iterable[Cookie]): def __len__(self) -> int: ... class FileCookieJar(CookieJar): - filename: str + filename: str | None delayload: bool def __init__(self, filename: StrPath | None = None, delayload: bool = False, policy: CookiePolicy | None = None) -> None: ... def save(self, filename: str | None = None, ignore_discard: bool = False, ignore_expires: bool = False) -> None: ... diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/inspect.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/inspect.pyi index 3f3e701206361..1eb9fc502e125 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/inspect.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/inspect.pyi @@ -25,7 +25,7 @@ from types import ( TracebackType, WrapperDescriptorType, ) -from typing import Any, ClassVar, Literal, NamedTuple, Protocol, TypeVar, overload +from typing import Any, ClassVar, Final, Literal, NamedTuple, Protocol, TypeVar, overload from typing_extensions import ParamSpec, Self, TypeAlias, TypeGuard, TypeIs if sys.version_info >= (3, 11): @@ -161,17 +161,17 @@ class BlockFinder: last: int def tokeneater(self, type: int, token: str, srowcol: tuple[int, int], erowcol: tuple[int, int], line: str) -> None: ... -CO_OPTIMIZED: Literal[1] -CO_NEWLOCALS: Literal[2] -CO_VARARGS: Literal[4] -CO_VARKEYWORDS: Literal[8] -CO_NESTED: Literal[16] -CO_GENERATOR: Literal[32] -CO_NOFREE: Literal[64] -CO_COROUTINE: Literal[128] -CO_ITERABLE_COROUTINE: Literal[256] -CO_ASYNC_GENERATOR: Literal[512] -TPFLAGS_IS_ABSTRACT: Literal[1048576] +CO_OPTIMIZED: Final = 1 +CO_NEWLOCALS: Final = 2 +CO_VARARGS: Final = 4 +CO_VARKEYWORDS: Final = 8 +CO_NESTED: Final = 16 +CO_GENERATOR: Final = 32 +CO_NOFREE: Final = 64 +CO_COROUTINE: Final = 128 +CO_ITERABLE_COROUTINE: Final = 256 +CO_ASYNC_GENERATOR: Final = 512 +TPFLAGS_IS_ABSTRACT: Final = 1048576 modulesbyfile: dict[str, Any] @@ -364,10 +364,10 @@ class _ParameterKind(enum.IntEnum): def description(self) -> str: ... if sys.version_info >= (3, 12): - AGEN_CREATED: Literal["AGEN_CREATED"] - AGEN_RUNNING: Literal["AGEN_RUNNING"] - AGEN_SUSPENDED: Literal["AGEN_SUSPENDED"] - AGEN_CLOSED: Literal["AGEN_CLOSED"] + AGEN_CREATED: Final = "AGEN_CREATED" + AGEN_RUNNING: Final = "AGEN_RUNNING" + AGEN_SUSPENDED: Final = "AGEN_SUSPENDED" + AGEN_CLOSED: Final = "AGEN_CLOSED" def getasyncgenstate( agen: AsyncGenerator[Any, Any] @@ -584,19 +584,19 @@ def getattr_static(obj: object, attr: str, default: Any | None = ...) -> Any: .. # Current State of Generators and Coroutines # -GEN_CREATED: Literal["GEN_CREATED"] -GEN_RUNNING: Literal["GEN_RUNNING"] -GEN_SUSPENDED: Literal["GEN_SUSPENDED"] -GEN_CLOSED: Literal["GEN_CLOSED"] +GEN_CREATED: Final = "GEN_CREATED" +GEN_RUNNING: Final = "GEN_RUNNING" +GEN_SUSPENDED: Final = "GEN_SUSPENDED" +GEN_CLOSED: Final = "GEN_CLOSED" def getgeneratorstate( generator: Generator[Any, Any, Any] ) -> Literal["GEN_CREATED", "GEN_RUNNING", "GEN_SUSPENDED", "GEN_CLOSED"]: ... -CORO_CREATED: Literal["CORO_CREATED"] -CORO_RUNNING: Literal["CORO_RUNNING"] -CORO_SUSPENDED: Literal["CORO_SUSPENDED"] -CORO_CLOSED: Literal["CORO_CLOSED"] +CORO_CREATED: Final = "CORO_CREATED" +CORO_RUNNING: Final = "CORO_RUNNING" +CORO_SUSPENDED: Final = "CORO_SUSPENDED" +CORO_CLOSED: Final = "CORO_CLOSED" def getcoroutinestate( coroutine: Coroutine[Any, Any, Any] diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/io.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/io.pyi index 66b9a0f5642a2..2d64d261951d1 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/io.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/io.pyi @@ -6,7 +6,7 @@ from _typeshed import FileDescriptorOrPath, ReadableBuffer, WriteableBuffer from collections.abc import Callable, Iterable, Iterator from os import _Opener from types import TracebackType -from typing import IO, Any, BinaryIO, Generic, Literal, Protocol, TextIO, TypeVar, overload, type_check_only +from typing import IO, Any, BinaryIO, Final, Generic, Literal, Protocol, TextIO, TypeVar, overload, type_check_only from typing_extensions import Self __all__ = [ @@ -36,11 +36,11 @@ if sys.version_info >= (3, 11): _T = TypeVar("_T") -DEFAULT_BUFFER_SIZE: Literal[8192] +DEFAULT_BUFFER_SIZE: Final = 8192 -SEEK_SET: Literal[0] -SEEK_CUR: Literal[1] -SEEK_END: Literal[2] +SEEK_SET: Final = 0 +SEEK_CUR: Final = 1 +SEEK_END: Final = 2 open = builtins.open @@ -168,7 +168,7 @@ class _WrappedBuffer(Protocol): def writable(self) -> bool: ... def truncate(self, size: int, /) -> int: ... def fileno(self) -> int: ... - def isatty(self) -> int: ... + def isatty(self) -> bool: ... # Optional: Only needs to be present if seekable() returns True. # def seek(self, offset: Literal[0], whence: Literal[2]) -> int: ... # def tell(self) -> int: ... diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/ipaddress.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/ipaddress.pyi index 03decc74e65e7..f51ea87dcfcfe 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/ipaddress.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/ipaddress.pyi @@ -1,11 +1,11 @@ import sys from collections.abc import Iterable, Iterator -from typing import Any, Generic, Literal, SupportsInt, TypeVar, overload +from typing import Any, Final, Generic, Literal, SupportsInt, TypeVar, overload from typing_extensions import Self, TypeAlias # Undocumented length constants -IPV4LENGTH: Literal[32] -IPV6LENGTH: Literal[128] +IPV4LENGTH: Final = 32 +IPV6LENGTH: Final = 128 _A = TypeVar("_A", IPv4Address, IPv6Address) _N = TypeVar("_N", IPv4Network, IPv6Network) diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/lib2to3/fixes/fix_asserts.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/lib2to3/fixes/fix_asserts.pyi index fb0b472aa12ac..1bf7db2f76e98 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/lib2to3/fixes/fix_asserts.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/lib2to3/fixes/fix_asserts.pyi @@ -1,8 +1,8 @@ -from typing import ClassVar, Literal +from typing import ClassVar, Final, Literal from ..fixer_base import BaseFix -NAMES: dict[str, str] +NAMES: Final[dict[str, str]] class FixAsserts(BaseFix): BM_compatible: ClassVar[Literal[False]] diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/lib2to3/fixes/fix_idioms.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/lib2to3/fixes/fix_idioms.pyi index 4595c57c7eb92..6b2723d09d436 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/lib2to3/fixes/fix_idioms.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/lib2to3/fixes/fix_idioms.pyi @@ -1,9 +1,9 @@ -from typing import ClassVar, Literal +from typing import ClassVar, Final, Literal from .. import fixer_base -CMP: str -TYPE: str +CMP: Final[str] +TYPE: Final[str] class FixIdioms(fixer_base.BaseFix): BM_compatible: ClassVar[Literal[False]] diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/lib2to3/fixes/fix_imports.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/lib2to3/fixes/fix_imports.pyi index dd6f72dd88ac2..c747af529f440 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/lib2to3/fixes/fix_imports.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/lib2to3/fixes/fix_imports.pyi @@ -1,11 +1,11 @@ from _typeshed import StrPath from collections.abc import Generator -from typing import ClassVar, Literal +from typing import ClassVar, Final, Literal from .. import fixer_base from ..pytree import Node -MAPPING: dict[str, str] +MAPPING: Final[dict[str, str]] def alternates(members): ... def build_pattern(mapping=...) -> Generator[str, None, None]: ... diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/lib2to3/fixes/fix_imports2.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/lib2to3/fixes/fix_imports2.pyi index 8d55433085dd4..618ecd0424d86 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/lib2to3/fixes/fix_imports2.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/lib2to3/fixes/fix_imports2.pyi @@ -1,6 +1,8 @@ +from typing import Final + from . import fix_imports -MAPPING: dict[str, str] +MAPPING: Final[dict[str, str]] class FixImports2(fix_imports.FixImports): mapping = MAPPING diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/lib2to3/fixes/fix_methodattrs.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/lib2to3/fixes/fix_methodattrs.pyi index 594b5e2c95c95..ca9b71e43f856 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/lib2to3/fixes/fix_methodattrs.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/lib2to3/fixes/fix_methodattrs.pyi @@ -1,8 +1,8 @@ -from typing import ClassVar, Literal +from typing import ClassVar, Final, Literal from .. import fixer_base -MAP: dict[str, str] +MAP: Final[dict[str, str]] class FixMethodattrs(fixer_base.BaseFix): BM_compatible: ClassVar[Literal[True]] diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/lib2to3/fixes/fix_renames.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/lib2to3/fixes/fix_renames.pyi index 6283f1ab7ce21..652d8f15ea1a9 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/lib2to3/fixes/fix_renames.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/lib2to3/fixes/fix_renames.pyi @@ -1,10 +1,10 @@ from collections.abc import Generator -from typing import ClassVar, Literal +from typing import ClassVar, Final, Literal from .. import fixer_base -MAPPING: dict[str, dict[str, str]] -LOOKUP: dict[tuple[str, str], str] +MAPPING: Final[dict[str, dict[str, str]]] +LOOKUP: Final[dict[tuple[str, str], str]] def alternates(members): ... def build_pattern() -> Generator[str, None, None]: ... diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/lib2to3/fixes/fix_urllib.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/lib2to3/fixes/fix_urllib.pyi index 625472f609ab4..abdcc0f62970f 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/lib2to3/fixes/fix_urllib.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/lib2to3/fixes/fix_urllib.pyi @@ -1,9 +1,9 @@ from collections.abc import Generator -from typing import Literal +from typing import Final, Literal from .fix_imports import FixImports -MAPPING: dict[str, list[tuple[Literal["urllib.request", "urllib.parse", "urllib.error"], list[str]]]] +MAPPING: Final[dict[str, list[tuple[Literal["urllib.request", "urllib.parse", "urllib.error"], list[str]]]]] def build_pattern() -> Generator[str, None, None]: ... diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/lib2to3/pgen2/token.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/lib2to3/pgen2/token.pyi index debcb2193987d..6898517acee64 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/lib2to3/pgen2/token.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/lib2to3/pgen2/token.pyi @@ -1,65 +1,67 @@ -ENDMARKER: int -NAME: int -NUMBER: int -STRING: int -NEWLINE: int -INDENT: int -DEDENT: int -LPAR: int -RPAR: int -LSQB: int -RSQB: int -COLON: int -COMMA: int -SEMI: int -PLUS: int -MINUS: int -STAR: int -SLASH: int -VBAR: int -AMPER: int -LESS: int -GREATER: int -EQUAL: int -DOT: int -PERCENT: int -BACKQUOTE: int -LBRACE: int -RBRACE: int -EQEQUAL: int -NOTEQUAL: int -LESSEQUAL: int -GREATEREQUAL: int -TILDE: int -CIRCUMFLEX: int -LEFTSHIFT: int -RIGHTSHIFT: int -DOUBLESTAR: int -PLUSEQUAL: int -MINEQUAL: int -STAREQUAL: int -SLASHEQUAL: int -PERCENTEQUAL: int -AMPEREQUAL: int -VBAREQUAL: int -CIRCUMFLEXEQUAL: int -LEFTSHIFTEQUAL: int -RIGHTSHIFTEQUAL: int -DOUBLESTAREQUAL: int -DOUBLESLASH: int -DOUBLESLASHEQUAL: int -OP: int -COMMENT: int -NL: int -RARROW: int -AT: int -ATEQUAL: int -AWAIT: int -ASYNC: int -ERRORTOKEN: int -COLONEQUAL: int -N_TOKENS: int -NT_OFFSET: int +from typing import Final + +ENDMARKER: Final[int] +NAME: Final[int] +NUMBER: Final[int] +STRING: Final[int] +NEWLINE: Final[int] +INDENT: Final[int] +DEDENT: Final[int] +LPAR: Final[int] +RPAR: Final[int] +LSQB: Final[int] +RSQB: Final[int] +COLON: Final[int] +COMMA: Final[int] +SEMI: Final[int] +PLUS: Final[int] +MINUS: Final[int] +STAR: Final[int] +SLASH: Final[int] +VBAR: Final[int] +AMPER: Final[int] +LESS: Final[int] +GREATER: Final[int] +EQUAL: Final[int] +DOT: Final[int] +PERCENT: Final[int] +BACKQUOTE: Final[int] +LBRACE: Final[int] +RBRACE: Final[int] +EQEQUAL: Final[int] +NOTEQUAL: Final[int] +LESSEQUAL: Final[int] +GREATEREQUAL: Final[int] +TILDE: Final[int] +CIRCUMFLEX: Final[int] +LEFTSHIFT: Final[int] +RIGHTSHIFT: Final[int] +DOUBLESTAR: Final[int] +PLUSEQUAL: Final[int] +MINEQUAL: Final[int] +STAREQUAL: Final[int] +SLASHEQUAL: Final[int] +PERCENTEQUAL: Final[int] +AMPEREQUAL: Final[int] +VBAREQUAL: Final[int] +CIRCUMFLEXEQUAL: Final[int] +LEFTSHIFTEQUAL: Final[int] +RIGHTSHIFTEQUAL: Final[int] +DOUBLESTAREQUAL: Final[int] +DOUBLESLASH: Final[int] +DOUBLESLASHEQUAL: Final[int] +OP: Final[int] +COMMENT: Final[int] +NL: Final[int] +RARROW: Final[int] +AT: Final[int] +ATEQUAL: Final[int] +AWAIT: Final[int] +ASYNC: Final[int] +ERRORTOKEN: Final[int] +COLONEQUAL: Final[int] +N_TOKENS: Final[int] +NT_OFFSET: Final[int] tok_name: dict[int, str] def ISTERMINAL(x: int) -> bool: ... diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/logging/__init__.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/logging/__init__.pyi index 4c6163257236f..e6e6e8f645a09 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/logging/__init__.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/logging/__init__.pyi @@ -7,7 +7,7 @@ from re import Pattern from string import Template from time import struct_time from types import FrameType, TracebackType -from typing import Any, ClassVar, Generic, Literal, Protocol, TextIO, TypeVar, overload +from typing import Any, ClassVar, Final, Generic, Literal, Protocol, TextIO, TypeVar, overload from typing_extensions import Self, TypeAlias, deprecated if sys.version_info >= (3, 11): @@ -236,14 +236,14 @@ class Logger(Filterer): def hasHandlers(self) -> bool: ... def callHandlers(self, record: LogRecord) -> None: ... # undocumented -CRITICAL: int -FATAL: int -ERROR: int -WARNING: int -WARN: int -INFO: int -DEBUG: int -NOTSET: int +CRITICAL: Final = 50 +FATAL: Final = CRITICAL +ERROR: Final = 40 +WARNING: Final = 30 +WARN: Final = WARNING +INFO: Final = 20 +DEBUG: Final = 10 +NOTSET: Final = 0 class Handler(Filterer): level: int # undocumented @@ -684,6 +684,6 @@ class StrFormatStyle(PercentStyle): # undocumented class StringTemplateStyle(PercentStyle): # undocumented _tpl: Template -_STYLES: dict[str, tuple[PercentStyle, str]] +_STYLES: Final[dict[str, tuple[PercentStyle, str]]] -BASIC_FORMAT: str +BASIC_FORMAT: Final[str] diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/logging/config.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/logging/config.pyi index 7a26846addbbc..83fe7461cb5c5 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/logging/config.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/logging/config.pyi @@ -4,14 +4,14 @@ from collections.abc import Callable, Hashable, Iterable, Sequence from configparser import RawConfigParser from re import Pattern from threading import Thread -from typing import IO, Any, Literal, SupportsIndex, TypedDict, overload +from typing import IO, Any, Final, Literal, SupportsIndex, TypedDict, overload from typing_extensions import Required, TypeAlias from . import Filter, Filterer, Formatter, Handler, Logger, _FilterType, _FormatStyle, _Level DEFAULT_LOGGING_CONFIG_PORT: int -RESET_ERROR: int # undocumented -IDENTIFIER: Pattern[str] # undocumented +RESET_ERROR: Final[int] # undocumented +IDENTIFIER: Final[Pattern[str]] # undocumented if sys.version_info >= (3, 11): class _RootLoggerConfiguration(TypedDict, total=False): diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/logging/handlers.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/logging/handlers.pyi index 4e97012abba11..91f9fe57e46f3 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/logging/handlers.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/logging/handlers.pyi @@ -8,16 +8,16 @@ from logging import FileHandler, Handler, LogRecord from re import Pattern from socket import SocketKind, socket from threading import Thread -from typing import Any, ClassVar, Protocol, TypeVar +from typing import Any, ClassVar, Final, Protocol, TypeVar _T = TypeVar("_T") -DEFAULT_TCP_LOGGING_PORT: int -DEFAULT_UDP_LOGGING_PORT: int -DEFAULT_HTTP_LOGGING_PORT: int -DEFAULT_SOAP_LOGGING_PORT: int -SYSLOG_UDP_PORT: int -SYSLOG_TCP_PORT: int +DEFAULT_TCP_LOGGING_PORT: Final[int] +DEFAULT_UDP_LOGGING_PORT: Final[int] +DEFAULT_HTTP_LOGGING_PORT: Final[int] +DEFAULT_SOAP_LOGGING_PORT: Final[int] +SYSLOG_UDP_PORT: Final[int] +SYSLOG_TCP_PORT: Final[int] class WatchedFileHandler(FileHandler): dev: int # undocumented diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/lzma.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/lzma.pyi index c05e46a02aeb2..2df2b9a8bd6a4 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/lzma.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/lzma.pyi @@ -1,7 +1,7 @@ from _compression import BaseStream from _typeshed import ReadableBuffer, StrOrBytesPath from collections.abc import Mapping, Sequence -from typing import IO, Any, Literal, TextIO, final, overload +from typing import IO, Any, Final, Literal, TextIO, final, overload from typing_extensions import Self, TypeAlias __all__ = [ @@ -50,33 +50,33 @@ _PathOrFile: TypeAlias = StrOrBytesPath | IO[bytes] _FilterChain: TypeAlias = Sequence[Mapping[str, Any]] -FORMAT_AUTO: Literal[0] -FORMAT_XZ: Literal[1] -FORMAT_ALONE: Literal[2] -FORMAT_RAW: Literal[3] -CHECK_NONE: Literal[0] -CHECK_CRC32: Literal[1] -CHECK_CRC64: Literal[4] -CHECK_SHA256: Literal[10] -CHECK_ID_MAX: Literal[15] -CHECK_UNKNOWN: Literal[16] +FORMAT_AUTO: Final = 0 +FORMAT_XZ: Final = 1 +FORMAT_ALONE: Final = 2 +FORMAT_RAW: Final = 3 +CHECK_NONE: Final = 0 +CHECK_CRC32: Final = 1 +CHECK_CRC64: Final = 4 +CHECK_SHA256: Final = 10 +CHECK_ID_MAX: Final = 15 +CHECK_UNKNOWN: Final = 16 FILTER_LZMA1: int # v big number -FILTER_LZMA2: Literal[33] -FILTER_DELTA: Literal[3] -FILTER_X86: Literal[4] -FILTER_IA64: Literal[6] -FILTER_ARM: Literal[7] -FILTER_ARMTHUMB: Literal[8] -FILTER_SPARC: Literal[9] -FILTER_POWERPC: Literal[5] -MF_HC3: Literal[3] -MF_HC4: Literal[4] -MF_BT2: Literal[18] -MF_BT3: Literal[19] -MF_BT4: Literal[20] -MODE_FAST: Literal[1] -MODE_NORMAL: Literal[2] -PRESET_DEFAULT: Literal[6] +FILTER_LZMA2: Final = 33 +FILTER_DELTA: Final = 3 +FILTER_X86: Final = 4 +FILTER_IA64: Final = 6 +FILTER_ARM: Final = 7 +FILTER_ARMTHUMB: Final = 8 +FILTER_SPARC: Final = 9 +FILTER_POWERPC: Final = 5 +MF_HC3: Final = 3 +MF_HC4: Final = 4 +MF_BT2: Final = 18 +MF_BT3: Final = 19 +MF_BT4: Final = 20 +MODE_FAST: Final = 1 +MODE_NORMAL: Final = 2 +PRESET_DEFAULT: Final = 6 PRESET_EXTREME: int # v big number # from _lzma.c diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/mmap.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/mmap.pyi index 60629e1836140..a0c150d6e7e84 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/mmap.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/mmap.pyi @@ -118,4 +118,16 @@ if sys.version_info >= (3, 13) and sys.platform != "win32": MAP_32BIT: Final = 32768 if sys.version_info >= (3, 13) and sys.platform == "darwin": + MAP_NORESERVE: Final = 64 + MAP_NOEXTEND: Final = 256 + MAP_HASSEMAPHORE: Final = 512 + MAP_NOCACHE: Final = 1024 + MAP_JIT: Final = 2048 + MAP_RESILIENT_CODESIGN: Final = 8192 + MAP_RESILIENT_MEDIA: Final = 16384 + MAP_TRANSLATED_ALLOW_EXECUTE: Final = 131072 + MAP_UNIX03: Final = 262144 MAP_TPRO: Final = 524288 + +if sys.version_info >= (3, 13) and sys.platform == "linux": + MAP_NORESERVE: Final = 16384 diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/modulefinder.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/modulefinder.pyi index 132cac5f18785..2cf948ba898a8 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/modulefinder.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/modulefinder.pyi @@ -1,15 +1,15 @@ import sys from collections.abc import Container, Iterable, Iterator, Sequence from types import CodeType -from typing import IO, Any +from typing import IO, Any, Final if sys.version_info < (3, 11): - LOAD_CONST: int # undocumented - IMPORT_NAME: int # undocumented - STORE_NAME: int # undocumented - STORE_GLOBAL: int # undocumented - STORE_OPS: tuple[int, int] # undocumented - EXTENDED_ARG: int # undocumented + LOAD_CONST: Final[int] # undocumented + IMPORT_NAME: Final[int] # undocumented + STORE_NAME: Final[int] # undocumented + STORE_GLOBAL: Final[int] # undocumented + STORE_OPS: Final[tuple[int, int]] # undocumented + EXTENDED_ARG: Final[int] # undocumented packagePathMap: dict[str, list[str]] # undocumented diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/msvcrt.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/msvcrt.pyi index 54b3674a3a460..403a5d9335227 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/msvcrt.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/msvcrt.pyi @@ -1,14 +1,14 @@ import sys -from typing import Final, Literal +from typing import Final # This module is only available on Windows if sys.platform == "win32": CRT_ASSEMBLY_VERSION: Final[str] - LK_UNLCK: Literal[0] - LK_LOCK: Literal[1] - LK_NBLCK: Literal[2] - LK_RLCK: Literal[3] - LK_NBRLCK: Literal[4] + LK_UNLCK: Final = 0 + LK_LOCK: Final = 1 + LK_NBLCK: Final = 2 + LK_RLCK: Final = 3 + LK_NBRLCK: Final = 4 SEM_FAILCRITICALERRORS: int SEM_NOALIGNMENTFAULTEXCEPT: int SEM_NOGPFAULTERRORBOX: int diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/multiprocessing/forkserver.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/multiprocessing/forkserver.pyi index 9a15f2683b7d4..31b9828563554 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/multiprocessing/forkserver.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/multiprocessing/forkserver.pyi @@ -1,12 +1,12 @@ from _typeshed import FileDescriptorLike, Unused from collections.abc import Sequence from struct import Struct -from typing import Any +from typing import Any, Final __all__ = ["ensure_running", "get_inherited_fds", "connect_to_new_process", "set_forkserver_preload"] -MAXFDS_TO_SEND: int -SIGNED_STRUCT: Struct +MAXFDS_TO_SEND: Final = 256 +SIGNED_STRUCT: Final[Struct] class ForkServer: def set_forkserver_preload(self, modules_names: list[str]) -> None: ... diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/multiprocessing/pool.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/multiprocessing/pool.pyi index d2d611e3ca622..950ed1d8c56b6 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/multiprocessing/pool.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/multiprocessing/pool.pyi @@ -1,7 +1,7 @@ import sys from collections.abc import Callable, Iterable, Iterator, Mapping from types import TracebackType -from typing import Any, Generic, Literal, TypeVar +from typing import Any, Final, Generic, TypeVar from typing_extensions import Self if sys.version_info >= (3, 9): @@ -97,7 +97,7 @@ class ThreadPool(Pool): ) -> None: ... # undocumented -INIT: Literal["INIT"] -RUN: Literal["RUN"] -CLOSE: Literal["CLOSE"] -TERMINATE: Literal["TERMINATE"] +INIT: Final = "INIT" +RUN: Final = "RUN" +CLOSE: Final = "CLOSE" +TERMINATE: Final = "TERMINATE" diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/multiprocessing/popen_spawn_win32.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/multiprocessing/popen_spawn_win32.pyi index 3dc9d5bd7332a..481b9eec5a37c 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/multiprocessing/popen_spawn_win32.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/multiprocessing/popen_spawn_win32.pyi @@ -1,16 +1,16 @@ import sys from multiprocessing.process import BaseProcess -from typing import ClassVar +from typing import ClassVar, Final from .util import Finalize if sys.platform == "win32": __all__ = ["Popen"] - TERMINATE: int - WINEXE: bool - WINSERVICE: bool - WINENV: bool + TERMINATE: Final[int] + WINEXE: Final[bool] + WINSERVICE: Final[bool] + WINENV: Final[bool] class Popen: finalizer: Finalize diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/multiprocessing/reduction.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/multiprocessing/reduction.pyi index 91532633e1b9f..a31987bcc3cbe 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/multiprocessing/reduction.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/multiprocessing/reduction.pyi @@ -8,14 +8,14 @@ from copyreg import _DispatchTableType from multiprocessing import connection from pickle import _ReducedType from socket import socket -from typing import Any, Literal +from typing import Any, Final if sys.platform == "win32": __all__ = ["send_handle", "recv_handle", "ForkingPickler", "register", "dump", "DupHandle", "duplicate", "steal_handle"] else: __all__ = ["send_handle", "recv_handle", "ForkingPickler", "register", "dump", "DupFd", "sendfds", "recvfds"] -HAVE_SEND_HANDLE: bool +HAVE_SEND_HANDLE: Final[bool] class ForkingPickler(pickle.Pickler): dispatch_table: _DispatchTableType @@ -43,10 +43,7 @@ if sys.platform == "win32": def detach(self) -> int: ... else: - if sys.platform == "darwin": - ACKNOWLEDGE: Literal[True] - else: - ACKNOWLEDGE: Literal[False] + ACKNOWLEDGE: Final[bool] def recvfds(sock: socket, size: int) -> list[int]: ... def send_handle(conn: HasFileno, handle: int, destination_pid: Unused) -> None: ... diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/multiprocessing/spawn.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/multiprocessing/spawn.pyi index 26ff165756bfa..43ce2f07d9962 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/multiprocessing/spawn.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/multiprocessing/spawn.pyi @@ -1,6 +1,6 @@ from collections.abc import Mapping, Sequence from types import ModuleType -from typing import Any +from typing import Any, Final __all__ = [ "_main", @@ -12,8 +12,8 @@ __all__ = [ "import_main_path", ] -WINEXE: bool -WINSERVICE: bool +WINEXE: Final[bool] +WINSERVICE: Final[bool] def set_executable(exe: str) -> None: ... def get_executable() -> str: ... diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/multiprocessing/util.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/multiprocessing/util.pyi index 790d6c7467f05..d5b6384afd5ed 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/multiprocessing/util.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/multiprocessing/util.pyi @@ -2,7 +2,7 @@ import threading from _typeshed import ConvertibleToInt, Incomplete, Unused from collections.abc import Callable, Iterable, Mapping, MutableMapping, Sequence from logging import Logger, _Level as _LoggingLevel -from typing import Any, Generic, TypeVar, overload +from typing import Any, Final, Generic, TypeVar, overload __all__ = [ "sub_debug", @@ -25,14 +25,14 @@ __all__ = [ _T = TypeVar("_T") _R_co = TypeVar("_R_co", default=Any, covariant=True) -NOTSET: int -SUBDEBUG: int -DEBUG: int -INFO: int -SUBWARNING: int +NOTSET: Final[int] +SUBDEBUG: Final[int] +DEBUG: Final[int] +INFO: Final[int] +SUBWARNING: Final[int] -LOGGER_NAME: str -DEFAULT_LOGGING_FORMAT: str +LOGGER_NAME: Final[str] +DEFAULT_LOGGING_FORMAT: Final[str] def sub_debug(msg: object, *args: object) -> None: ... def debug(msg: object, *args: object) -> None: ... @@ -92,7 +92,7 @@ class ForkAwareThreadLock: class ForkAwareLocal(threading.local): ... -MAXFD: int +MAXFD: Final[int] def close_all_fds_except(fds: Iterable[int]) -> None: ... def spawnv_passfds(path: bytes, args: Sequence[ConvertibleToInt], passfds: Sequence[int]) -> int: ... diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/nntplib.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/nntplib.pyi index 969c657e9aab8..85dfbff1cb50e 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/nntplib.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/nntplib.pyi @@ -5,7 +5,7 @@ import sys from _typeshed import Unused from builtins import list as _list # conflicts with a method named "list" from collections.abc import Iterable -from typing import IO, Any, Literal, NamedTuple +from typing import IO, Any, Final, NamedTuple from typing_extensions import Self, TypeAlias __all__ = [ @@ -31,8 +31,8 @@ class NNTPPermanentError(NNTPError): ... class NNTPProtocolError(NNTPError): ... class NNTPDataError(NNTPError): ... -NNTP_PORT: Literal[119] -NNTP_SSL_PORT: Literal[563] +NNTP_PORT: Final = 119 +NNTP_SSL_PORT: Final = 563 class GroupInfo(NamedTuple): group: str diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/optparse.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/optparse.pyi index a179c2d1bb3ce..b513bb6470605 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/optparse.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/optparse.pyi @@ -1,7 +1,7 @@ from _typeshed import Incomplete from abc import abstractmethod from collections.abc import Callable, Iterable, Mapping, Sequence -from typing import IO, Any, AnyStr, Literal, overload +from typing import IO, Any, AnyStr, Literal, NoReturn, overload __all__ = [ "Option", @@ -231,8 +231,8 @@ class OptionParser(OptionContainer): def check_values(self, values: Values, args: list[str]) -> tuple[Values, list[str]]: ... def disable_interspersed_args(self) -> None: ... def enable_interspersed_args(self) -> None: ... - def error(self, msg: str) -> None: ... - def exit(self, status: int = 0, msg: str | None = None) -> None: ... + def error(self, msg: str) -> NoReturn: ... + def exit(self, status: int = 0, msg: str | None = None) -> NoReturn: ... def expand_prog_name(self, s: str) -> str: ... def format_epilog(self, formatter: HelpFormatter) -> str: ... def format_help(self, formatter: HelpFormatter | None = None) -> str: ... diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/os/__init__.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/os/__init__.pyi index 9b00117a55999..e2d272cb41127 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/os/__init__.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/os/__init__.pyi @@ -971,7 +971,8 @@ else: def spawnvp(mode: int, file: StrOrBytesPath, args: _ExecVArgs) -> int: ... def spawnvpe(mode: int, file: StrOrBytesPath, args: _ExecVArgs, env: _ExecEnv) -> int: ... def wait() -> tuple[int, int]: ... # Unix only - if sys.platform != "darwin": + # Added to MacOS in 3.13 + if sys.platform != "darwin" or sys.version_info >= (3, 13): @final class waitid_result(structseq[int], tuple[int, int, int, int, int]): if sys.version_info >= (3, 10): @@ -1155,3 +1156,24 @@ if sys.version_info >= (3, 12) and sys.platform == "linux": CLONE_VM: int def unshare(flags: int) -> None: ... def setns(fd: FileDescriptorLike, nstype: int = 0) -> None: ... + +if sys.version_info >= (3, 13) and sys.platform != "win32": + def posix_openpt(oflag: int, /) -> int: ... + def grantpt(fd: FileDescriptorLike, /) -> None: ... + def unlockpt(fd: FileDescriptorLike, /) -> None: ... + def ptsname(fd: FileDescriptorLike, /) -> str: ... + +if sys.version_info >= (3, 13) and sys.platform == "linux": + TFD_TIMER_ABSTIME: Final = 1 + TFD_TIMER_CANCEL_ON_SET: Final = 2 + TFD_NONBLOCK: Final[int] + TFD_CLOEXEC: Final[int] + POSIX_SPAWN_CLOSEFROM: Final[int] + + def timerfd_create(clockid: int, /, *, flags: int = 0) -> int: ... + def timerfd_settime( + fd: FileDescriptor, /, *, flags: int = 0, initial: float = 0.0, interval: float = 0.0 + ) -> tuple[float, float]: ... + def timerfd_settime_ns(fd: FileDescriptor, /, *, flags: int = 0, initial: int = 0, interval: int = 0) -> tuple[int, int]: ... + def timerfd_gettime(fd: FileDescriptor, /) -> tuple[float, float]: ... + def timerfd_gettime_ns(fd: FileDescriptor, /) -> tuple[int, int]: ... diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/pathlib.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/pathlib.pyi index dfa6648e71ba7..116bf6431831c 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/pathlib.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/pathlib.pyi @@ -49,7 +49,7 @@ class PurePath(PathLike[str]): def stem(self) -> str: ... if sys.version_info >= (3, 12): def __new__(cls, *args: StrPath, **kwargs: Unused) -> Self: ... - def __init__(self, *args: StrPath) -> None: ... + def __init__(self, *args: StrPath) -> None: ... # pyright: ignore[reportInconsistentConstructor] else: def __new__(cls, *args: StrPath) -> Self: ... @@ -101,7 +101,11 @@ class PurePosixPath(PurePath): ... class PureWindowsPath(PurePath): ... class Path(PurePath): - def __new__(cls, *args: StrPath, **kwargs: Any) -> Self: ... + if sys.version_info >= (3, 12): + def __new__(cls, *args: StrPath, **kwargs: Unused) -> Self: ... # pyright: ignore[reportInconsistentConstructor] + else: + def __new__(cls, *args: StrPath, **kwargs: Unused) -> Self: ... + @classmethod def cwd(cls) -> Self: ... if sys.version_info >= (3, 10): diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/poplib.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/poplib.pyi index 12f1d16a0d6fb..7476f2991978c 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/poplib.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/poplib.pyi @@ -3,7 +3,7 @@ import ssl import sys from builtins import list as _list # conflicts with a method named "list" from re import Pattern -from typing import Any, BinaryIO, Literal, NoReturn, overload +from typing import Any, BinaryIO, Final, NoReturn, overload from typing_extensions import TypeAlias __all__ = ["POP3", "error_proto", "POP3_SSL"] @@ -12,11 +12,11 @@ _LongResp: TypeAlias = tuple[bytes, list[bytes], int] class error_proto(Exception): ... -POP3_PORT: Literal[110] -POP3_SSL_PORT: Literal[995] -CR: Literal[b"\r"] -LF: Literal[b"\n"] -CRLF: Literal[b"\r\n"] +POP3_PORT: Final = 110 +POP3_SSL_PORT: Final = 995 +CR: Final = b"\r" +LF: Final = b"\n" +CRLF: Final = b"\r\n" HAVE_SSL: bool class POP3: diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/posix.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/posix.pyi index b31b8f3d35245..1a4f22af82cf4 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/posix.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/posix.pyi @@ -236,6 +236,23 @@ if sys.platform != "win32": if sys.version_info >= (3, 11): from os import login_tty as login_tty + if sys.version_info >= (3, 13): + from os import grantpt as grantpt, posix_openpt as posix_openpt, ptsname as ptsname, unlockpt as unlockpt + + if sys.version_info >= (3, 13) and sys.platform == "linux": + from os import ( + POSIX_SPAWN_CLOSEFROM as POSIX_SPAWN_CLOSEFROM, + TFD_CLOEXEC as TFD_CLOEXEC, + TFD_NONBLOCK as TFD_NONBLOCK, + TFD_TIMER_ABSTIME as TFD_TIMER_ABSTIME, + TFD_TIMER_CANCEL_ON_SET as TFD_TIMER_CANCEL_ON_SET, + timerfd_create as timerfd_create, + timerfd_gettime as timerfd_gettime, + timerfd_gettime_ns as timerfd_gettime_ns, + timerfd_settime as timerfd_settime, + timerfd_settime_ns as timerfd_settime_ns, + ) + if sys.platform != "linux": from os import chflags as chflags, lchflags as lchflags, lchmod as lchmod @@ -269,13 +286,14 @@ if sys.platform != "win32": sched_setscheduler as sched_setscheduler, setresgid as setresgid, setresuid as setresuid, - waitid as waitid, - waitid_result as waitid_result, ) if sys.version_info >= (3, 10): from os import RWF_APPEND as RWF_APPEND + if sys.platform != "darwin" or sys.version_info >= (3, 13): + from os import waitid as waitid, waitid_result as waitid_result + if sys.platform == "linux": from os import ( GRND_NONBLOCK as GRND_NONBLOCK, diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/pty.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/pty.pyi index 022b08046c542..4c9e42b4ec5e9 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/pty.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/pty.pyi @@ -1,17 +1,17 @@ import sys from collections.abc import Callable, Iterable -from typing import Literal +from typing import Final from typing_extensions import TypeAlias if sys.platform != "win32": __all__ = ["openpty", "fork", "spawn"] _Reader: TypeAlias = Callable[[int], bytes] - STDIN_FILENO: Literal[0] - STDOUT_FILENO: Literal[1] - STDERR_FILENO: Literal[2] + STDIN_FILENO: Final = 0 + STDOUT_FILENO: Final = 1 + STDERR_FILENO: Final = 2 - CHILD: Literal[0] + CHILD: Final = 0 def openpty() -> tuple[int, int]: ... def master_open() -> tuple[int, str]: ... # deprecated, use openpty() def slave_open(tty_name: str) -> int: ... # deprecated, use openpty() diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/pyexpat/__init__.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/pyexpat/__init__.pyi index 88bf9464d130c..64decd56bee68 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/pyexpat/__init__.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/pyexpat/__init__.pyi @@ -1,10 +1,10 @@ from _typeshed import ReadableBuffer, SupportsRead from collections.abc import Callable from pyexpat import errors as errors, model as model -from typing import Any, final +from typing import Any, Final, final from typing_extensions import TypeAlias -EXPAT_VERSION: str # undocumented +EXPAT_VERSION: Final[str] # undocumented version_info: tuple[int, int, int] # undocumented native_encoding: str # undocumented features: list[tuple[str, int]] # undocumented @@ -15,7 +15,6 @@ class ExpatError(Exception): offset: int error = ExpatError - XML_PARAM_ENTITY_PARSING_NEVER: int XML_PARAM_ENTITY_PARSING_UNLESS_STANDALONE: int XML_PARAM_ENTITY_PARSING_ALWAYS: int diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/readline.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/readline.pyi index 688ae48d9f924..7325c267b32c2 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/readline.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/readline.pyi @@ -1,6 +1,7 @@ import sys from _typeshed import StrOrBytesPath from collections.abc import Callable, Sequence +from typing import Literal from typing_extensions import TypeAlias if sys.platform != "win32": @@ -34,3 +35,6 @@ if sys.platform != "win32": def set_completer_delims(string: str, /) -> None: ... def get_completer_delims() -> str: ... def set_completion_display_matches_hook(function: _CompDisp | None = None, /) -> None: ... + + if sys.version_info >= (3, 13): + backend: Literal["readline", "editline"] diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/sqlite3/dbapi2.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/sqlite3/dbapi2.pyi index 3cb4b93e88fe1..9e46012ee7776 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/sqlite3/dbapi2.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/sqlite3/dbapi2.pyi @@ -4,7 +4,7 @@ from _typeshed import ReadableBuffer, StrOrBytesPath, SupportsLenAndGetItem, Unu from collections.abc import Callable, Generator, Iterable, Iterator, Mapping from datetime import date, datetime, time from types import TracebackType -from typing import Any, Literal, Protocol, SupportsIndex, TypeVar, final, overload +from typing import Any, Final, Literal, Protocol, SupportsIndex, TypeVar, final, overload from typing_extensions import Self, TypeAlias _T = TypeVar("_T") @@ -35,186 +35,186 @@ Binary = memoryview # The remaining definitions are imported from _sqlite3. -PARSE_COLNAMES: int -PARSE_DECLTYPES: int -SQLITE_ALTER_TABLE: int -SQLITE_ANALYZE: int -SQLITE_ATTACH: int -SQLITE_CREATE_INDEX: int -SQLITE_CREATE_TABLE: int -SQLITE_CREATE_TEMP_INDEX: int -SQLITE_CREATE_TEMP_TABLE: int -SQLITE_CREATE_TEMP_TRIGGER: int -SQLITE_CREATE_TEMP_VIEW: int -SQLITE_CREATE_TRIGGER: int -SQLITE_CREATE_VIEW: int -SQLITE_CREATE_VTABLE: int -SQLITE_DELETE: int -SQLITE_DENY: int -SQLITE_DETACH: int -SQLITE_DONE: int -SQLITE_DROP_INDEX: int -SQLITE_DROP_TABLE: int -SQLITE_DROP_TEMP_INDEX: int -SQLITE_DROP_TEMP_TABLE: int -SQLITE_DROP_TEMP_TRIGGER: int -SQLITE_DROP_TEMP_VIEW: int -SQLITE_DROP_TRIGGER: int -SQLITE_DROP_VIEW: int -SQLITE_DROP_VTABLE: int -SQLITE_FUNCTION: int -SQLITE_IGNORE: int -SQLITE_INSERT: int -SQLITE_OK: int +PARSE_COLNAMES: Final[int] +PARSE_DECLTYPES: Final[int] +SQLITE_ALTER_TABLE: Final[int] +SQLITE_ANALYZE: Final[int] +SQLITE_ATTACH: Final[int] +SQLITE_CREATE_INDEX: Final[int] +SQLITE_CREATE_TABLE: Final[int] +SQLITE_CREATE_TEMP_INDEX: Final[int] +SQLITE_CREATE_TEMP_TABLE: Final[int] +SQLITE_CREATE_TEMP_TRIGGER: Final[int] +SQLITE_CREATE_TEMP_VIEW: Final[int] +SQLITE_CREATE_TRIGGER: Final[int] +SQLITE_CREATE_VIEW: Final[int] +SQLITE_CREATE_VTABLE: Final[int] +SQLITE_DELETE: Final[int] +SQLITE_DENY: Final[int] +SQLITE_DETACH: Final[int] +SQLITE_DONE: Final[int] +SQLITE_DROP_INDEX: Final[int] +SQLITE_DROP_TABLE: Final[int] +SQLITE_DROP_TEMP_INDEX: Final[int] +SQLITE_DROP_TEMP_TABLE: Final[int] +SQLITE_DROP_TEMP_TRIGGER: Final[int] +SQLITE_DROP_TEMP_VIEW: Final[int] +SQLITE_DROP_TRIGGER: Final[int] +SQLITE_DROP_VIEW: Final[int] +SQLITE_DROP_VTABLE: Final[int] +SQLITE_FUNCTION: Final[int] +SQLITE_IGNORE: Final[int] +SQLITE_INSERT: Final[int] +SQLITE_OK: Final[int] if sys.version_info >= (3, 11): - SQLITE_LIMIT_LENGTH: int - SQLITE_LIMIT_SQL_LENGTH: int - SQLITE_LIMIT_COLUMN: int - SQLITE_LIMIT_EXPR_DEPTH: int - SQLITE_LIMIT_COMPOUND_SELECT: int - SQLITE_LIMIT_VDBE_OP: int - SQLITE_LIMIT_FUNCTION_ARG: int - SQLITE_LIMIT_ATTACHED: int - SQLITE_LIMIT_LIKE_PATTERN_LENGTH: int - SQLITE_LIMIT_VARIABLE_NUMBER: int - SQLITE_LIMIT_TRIGGER_DEPTH: int - SQLITE_LIMIT_WORKER_THREADS: int -SQLITE_PRAGMA: int -SQLITE_READ: int -SQLITE_REINDEX: int -SQLITE_RECURSIVE: int -SQLITE_SAVEPOINT: int -SQLITE_SELECT: int -SQLITE_TRANSACTION: int -SQLITE_UPDATE: int + SQLITE_LIMIT_LENGTH: Final[int] + SQLITE_LIMIT_SQL_LENGTH: Final[int] + SQLITE_LIMIT_COLUMN: Final[int] + SQLITE_LIMIT_EXPR_DEPTH: Final[int] + SQLITE_LIMIT_COMPOUND_SELECT: Final[int] + SQLITE_LIMIT_VDBE_OP: Final[int] + SQLITE_LIMIT_FUNCTION_ARG: Final[int] + SQLITE_LIMIT_ATTACHED: Final[int] + SQLITE_LIMIT_LIKE_PATTERN_LENGTH: Final[int] + SQLITE_LIMIT_VARIABLE_NUMBER: Final[int] + SQLITE_LIMIT_TRIGGER_DEPTH: Final[int] + SQLITE_LIMIT_WORKER_THREADS: Final[int] +SQLITE_PRAGMA: Final[int] +SQLITE_READ: Final[int] +SQLITE_REINDEX: Final[int] +SQLITE_RECURSIVE: Final[int] +SQLITE_SAVEPOINT: Final[int] +SQLITE_SELECT: Final[int] +SQLITE_TRANSACTION: Final[int] +SQLITE_UPDATE: Final[int] adapters: dict[tuple[type[Any], type[Any]], _Adapter[Any]] converters: dict[str, _Converter] sqlite_version: str version: str if sys.version_info >= (3, 11): - SQLITE_ABORT: int - SQLITE_ABORT_ROLLBACK: int - SQLITE_AUTH: int - SQLITE_AUTH_USER: int - SQLITE_BUSY: int - SQLITE_BUSY_RECOVERY: int - SQLITE_BUSY_SNAPSHOT: int - SQLITE_BUSY_TIMEOUT: int - SQLITE_CANTOPEN: int - SQLITE_CANTOPEN_CONVPATH: int - SQLITE_CANTOPEN_DIRTYWAL: int - SQLITE_CANTOPEN_FULLPATH: int - SQLITE_CANTOPEN_ISDIR: int - SQLITE_CANTOPEN_NOTEMPDIR: int - SQLITE_CANTOPEN_SYMLINK: int - SQLITE_CONSTRAINT: int - SQLITE_CONSTRAINT_CHECK: int - SQLITE_CONSTRAINT_COMMITHOOK: int - SQLITE_CONSTRAINT_FOREIGNKEY: int - SQLITE_CONSTRAINT_FUNCTION: int - SQLITE_CONSTRAINT_NOTNULL: int - SQLITE_CONSTRAINT_PINNED: int - SQLITE_CONSTRAINT_PRIMARYKEY: int - SQLITE_CONSTRAINT_ROWID: int - SQLITE_CONSTRAINT_TRIGGER: int - SQLITE_CONSTRAINT_UNIQUE: int - SQLITE_CONSTRAINT_VTAB: int - SQLITE_CORRUPT: int - SQLITE_CORRUPT_INDEX: int - SQLITE_CORRUPT_SEQUENCE: int - SQLITE_CORRUPT_VTAB: int - SQLITE_EMPTY: int - SQLITE_ERROR: int - SQLITE_ERROR_MISSING_COLLSEQ: int - SQLITE_ERROR_RETRY: int - SQLITE_ERROR_SNAPSHOT: int - SQLITE_FORMAT: int - SQLITE_FULL: int - SQLITE_INTERNAL: int - SQLITE_INTERRUPT: int - SQLITE_IOERR: int - SQLITE_IOERR_ACCESS: int - SQLITE_IOERR_AUTH: int - SQLITE_IOERR_BEGIN_ATOMIC: int - SQLITE_IOERR_BLOCKED: int - SQLITE_IOERR_CHECKRESERVEDLOCK: int - SQLITE_IOERR_CLOSE: int - SQLITE_IOERR_COMMIT_ATOMIC: int - SQLITE_IOERR_CONVPATH: int - SQLITE_IOERR_CORRUPTFS: int - SQLITE_IOERR_DATA: int - SQLITE_IOERR_DELETE: int - SQLITE_IOERR_DELETE_NOENT: int - SQLITE_IOERR_DIR_CLOSE: int - SQLITE_IOERR_DIR_FSYNC: int - SQLITE_IOERR_FSTAT: int - SQLITE_IOERR_FSYNC: int - SQLITE_IOERR_GETTEMPPATH: int - SQLITE_IOERR_LOCK: int - SQLITE_IOERR_MMAP: int - SQLITE_IOERR_NOMEM: int - SQLITE_IOERR_RDLOCK: int - SQLITE_IOERR_READ: int - SQLITE_IOERR_ROLLBACK_ATOMIC: int - SQLITE_IOERR_SEEK: int - SQLITE_IOERR_SHMLOCK: int - SQLITE_IOERR_SHMMAP: int - SQLITE_IOERR_SHMOPEN: int - SQLITE_IOERR_SHMSIZE: int - SQLITE_IOERR_SHORT_READ: int - SQLITE_IOERR_TRUNCATE: int - SQLITE_IOERR_UNLOCK: int - SQLITE_IOERR_VNODE: int - SQLITE_IOERR_WRITE: int - SQLITE_LOCKED: int - SQLITE_LOCKED_SHAREDCACHE: int - SQLITE_LOCKED_VTAB: int - SQLITE_MISMATCH: int - SQLITE_MISUSE: int - SQLITE_NOLFS: int - SQLITE_NOMEM: int - SQLITE_NOTADB: int - SQLITE_NOTFOUND: int - SQLITE_NOTICE: int - SQLITE_NOTICE_RECOVER_ROLLBACK: int - SQLITE_NOTICE_RECOVER_WAL: int - SQLITE_OK_LOAD_PERMANENTLY: int - SQLITE_OK_SYMLINK: int - SQLITE_PERM: int - SQLITE_PROTOCOL: int - SQLITE_RANGE: int - SQLITE_READONLY: int - SQLITE_READONLY_CANTINIT: int - SQLITE_READONLY_CANTLOCK: int - SQLITE_READONLY_DBMOVED: int - SQLITE_READONLY_DIRECTORY: int - SQLITE_READONLY_RECOVERY: int - SQLITE_READONLY_ROLLBACK: int - SQLITE_ROW: int - SQLITE_SCHEMA: int - SQLITE_TOOBIG: int - SQLITE_WARNING: int - SQLITE_WARNING_AUTOINDEX: int + SQLITE_ABORT: Final[int] + SQLITE_ABORT_ROLLBACK: Final[int] + SQLITE_AUTH: Final[int] + SQLITE_AUTH_USER: Final[int] + SQLITE_BUSY: Final[int] + SQLITE_BUSY_RECOVERY: Final[int] + SQLITE_BUSY_SNAPSHOT: Final[int] + SQLITE_BUSY_TIMEOUT: Final[int] + SQLITE_CANTOPEN: Final[int] + SQLITE_CANTOPEN_CONVPATH: Final[int] + SQLITE_CANTOPEN_DIRTYWAL: Final[int] + SQLITE_CANTOPEN_FULLPATH: Final[int] + SQLITE_CANTOPEN_ISDIR: Final[int] + SQLITE_CANTOPEN_NOTEMPDIR: Final[int] + SQLITE_CANTOPEN_SYMLINK: Final[int] + SQLITE_CONSTRAINT: Final[int] + SQLITE_CONSTRAINT_CHECK: Final[int] + SQLITE_CONSTRAINT_COMMITHOOK: Final[int] + SQLITE_CONSTRAINT_FOREIGNKEY: Final[int] + SQLITE_CONSTRAINT_FUNCTION: Final[int] + SQLITE_CONSTRAINT_NOTNULL: Final[int] + SQLITE_CONSTRAINT_PINNED: Final[int] + SQLITE_CONSTRAINT_PRIMARYKEY: Final[int] + SQLITE_CONSTRAINT_ROWID: Final[int] + SQLITE_CONSTRAINT_TRIGGER: Final[int] + SQLITE_CONSTRAINT_UNIQUE: Final[int] + SQLITE_CONSTRAINT_VTAB: Final[int] + SQLITE_CORRUPT: Final[int] + SQLITE_CORRUPT_INDEX: Final[int] + SQLITE_CORRUPT_SEQUENCE: Final[int] + SQLITE_CORRUPT_VTAB: Final[int] + SQLITE_EMPTY: Final[int] + SQLITE_ERROR: Final[int] + SQLITE_ERROR_MISSING_COLLSEQ: Final[int] + SQLITE_ERROR_RETRY: Final[int] + SQLITE_ERROR_SNAPSHOT: Final[int] + SQLITE_FORMAT: Final[int] + SQLITE_FULL: Final[int] + SQLITE_INTERNAL: Final[int] + SQLITE_INTERRUPT: Final[int] + SQLITE_IOERR: Final[int] + SQLITE_IOERR_ACCESS: Final[int] + SQLITE_IOERR_AUTH: Final[int] + SQLITE_IOERR_BEGIN_ATOMIC: Final[int] + SQLITE_IOERR_BLOCKED: Final[int] + SQLITE_IOERR_CHECKRESERVEDLOCK: Final[int] + SQLITE_IOERR_CLOSE: Final[int] + SQLITE_IOERR_COMMIT_ATOMIC: Final[int] + SQLITE_IOERR_CONVPATH: Final[int] + SQLITE_IOERR_CORRUPTFS: Final[int] + SQLITE_IOERR_DATA: Final[int] + SQLITE_IOERR_DELETE: Final[int] + SQLITE_IOERR_DELETE_NOENT: Final[int] + SQLITE_IOERR_DIR_CLOSE: Final[int] + SQLITE_IOERR_DIR_FSYNC: Final[int] + SQLITE_IOERR_FSTAT: Final[int] + SQLITE_IOERR_FSYNC: Final[int] + SQLITE_IOERR_GETTEMPPATH: Final[int] + SQLITE_IOERR_LOCK: Final[int] + SQLITE_IOERR_MMAP: Final[int] + SQLITE_IOERR_NOMEM: Final[int] + SQLITE_IOERR_RDLOCK: Final[int] + SQLITE_IOERR_READ: Final[int] + SQLITE_IOERR_ROLLBACK_ATOMIC: Final[int] + SQLITE_IOERR_SEEK: Final[int] + SQLITE_IOERR_SHMLOCK: Final[int] + SQLITE_IOERR_SHMMAP: Final[int] + SQLITE_IOERR_SHMOPEN: Final[int] + SQLITE_IOERR_SHMSIZE: Final[int] + SQLITE_IOERR_SHORT_READ: Final[int] + SQLITE_IOERR_TRUNCATE: Final[int] + SQLITE_IOERR_UNLOCK: Final[int] + SQLITE_IOERR_VNODE: Final[int] + SQLITE_IOERR_WRITE: Final[int] + SQLITE_LOCKED: Final[int] + SQLITE_LOCKED_SHAREDCACHE: Final[int] + SQLITE_LOCKED_VTAB: Final[int] + SQLITE_MISMATCH: Final[int] + SQLITE_MISUSE: Final[int] + SQLITE_NOLFS: Final[int] + SQLITE_NOMEM: Final[int] + SQLITE_NOTADB: Final[int] + SQLITE_NOTFOUND: Final[int] + SQLITE_NOTICE: Final[int] + SQLITE_NOTICE_RECOVER_ROLLBACK: Final[int] + SQLITE_NOTICE_RECOVER_WAL: Final[int] + SQLITE_OK_LOAD_PERMANENTLY: Final[int] + SQLITE_OK_SYMLINK: Final[int] + SQLITE_PERM: Final[int] + SQLITE_PROTOCOL: Final[int] + SQLITE_RANGE: Final[int] + SQLITE_READONLY: Final[int] + SQLITE_READONLY_CANTINIT: Final[int] + SQLITE_READONLY_CANTLOCK: Final[int] + SQLITE_READONLY_DBMOVED: Final[int] + SQLITE_READONLY_DIRECTORY: Final[int] + SQLITE_READONLY_RECOVERY: Final[int] + SQLITE_READONLY_ROLLBACK: Final[int] + SQLITE_ROW: Final[int] + SQLITE_SCHEMA: Final[int] + SQLITE_TOOBIG: Final[int] + SQLITE_WARNING: Final[int] + SQLITE_WARNING_AUTOINDEX: Final[int] if sys.version_info >= (3, 12): - LEGACY_TRANSACTION_CONTROL: int - SQLITE_DBCONFIG_DEFENSIVE: int - SQLITE_DBCONFIG_DQS_DDL: int - SQLITE_DBCONFIG_DQS_DML: int - SQLITE_DBCONFIG_ENABLE_FKEY: int - SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER: int - SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION: int - SQLITE_DBCONFIG_ENABLE_QPSG: int - SQLITE_DBCONFIG_ENABLE_TRIGGER: int - SQLITE_DBCONFIG_ENABLE_VIEW: int - SQLITE_DBCONFIG_LEGACY_ALTER_TABLE: int - SQLITE_DBCONFIG_LEGACY_FILE_FORMAT: int - SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE: int - SQLITE_DBCONFIG_RESET_DATABASE: int - SQLITE_DBCONFIG_TRIGGER_EQP: int - SQLITE_DBCONFIG_TRUSTED_SCHEMA: int - SQLITE_DBCONFIG_WRITABLE_SCHEMA: int + LEGACY_TRANSACTION_CONTROL: Final[int] + SQLITE_DBCONFIG_DEFENSIVE: Final[int] + SQLITE_DBCONFIG_DQS_DDL: Final[int] + SQLITE_DBCONFIG_DQS_DML: Final[int] + SQLITE_DBCONFIG_ENABLE_FKEY: Final[int] + SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER: Final[int] + SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION: Final[int] + SQLITE_DBCONFIG_ENABLE_QPSG: Final[int] + SQLITE_DBCONFIG_ENABLE_TRIGGER: Final[int] + SQLITE_DBCONFIG_ENABLE_VIEW: Final[int] + SQLITE_DBCONFIG_LEGACY_ALTER_TABLE: Final[int] + SQLITE_DBCONFIG_LEGACY_FILE_FORMAT: Final[int] + SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE: Final[int] + SQLITE_DBCONFIG_RESET_DATABASE: Final[int] + SQLITE_DBCONFIG_TRIGGER_EQP: Final[int] + SQLITE_DBCONFIG_TRUSTED_SCHEMA: Final[int] + SQLITE_DBCONFIG_WRITABLE_SCHEMA: Final[int] # Can take or return anything depending on what's in the registry. @overload diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/stat.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/stat.pyi index f3bdd92c1068a..face28ab0cbb6 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/stat.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/stat.pyi @@ -1,7 +1,7 @@ import sys from _stat import * -from typing import Literal +from typing import Final if sys.version_info >= (3, 13): # https://github.com/python/cpython/issues/114081#issuecomment-2119017790 - SF_RESTRICTED: Literal[0x00080000] + SF_RESTRICTED: Final = 0x00080000 diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/subprocess.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/subprocess.pyi index b01bac2455cef..2a5859807b511 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/subprocess.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/subprocess.pyi @@ -2,7 +2,7 @@ import sys from _typeshed import ReadableBuffer, StrOrBytesPath from collections.abc import Callable, Collection, Iterable, Mapping, Sequence from types import TracebackType -from typing import IO, Any, AnyStr, Generic, Literal, TypeVar, overload +from typing import IO, Any, AnyStr, Final, Generic, Literal, TypeVar, overload from typing_extensions import Self, TypeAlias if sys.version_info >= (3, 9): @@ -74,8 +74,8 @@ _T = TypeVar("_T") # These two are private but documented if sys.version_info >= (3, 11): - _USE_VFORK: bool -_USE_POSIX_SPAWN: bool + _USE_VFORK: Final[bool] +_USE_POSIX_SPAWN: Final[bool] class CompletedProcess(Generic[_T]): # morally: _CMD @@ -1810,9 +1810,9 @@ else: text: bool | None = None, ) -> Any: ... # morally: -> str | bytes -PIPE: int -STDOUT: int -DEVNULL: int +PIPE: Final[int] +STDOUT: Final[int] +DEVNULL: Final[int] class SubprocessError(Exception): ... diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/syslog.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/syslog.pyi index d539dd5e4579f..1e0d0d3839022 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/syslog.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/syslog.pyi @@ -1,48 +1,50 @@ import sys -from typing import Literal, overload +from typing import Final, overload if sys.platform != "win32": - LOG_ALERT: Literal[1] - LOG_AUTH: Literal[32] - LOG_AUTHPRIV: Literal[80] - LOG_CONS: Literal[2] - LOG_CRIT: Literal[2] - LOG_CRON: Literal[72] - LOG_DAEMON: Literal[24] - LOG_DEBUG: Literal[7] - LOG_EMERG: Literal[0] - LOG_ERR: Literal[3] - LOG_INFO: Literal[6] - LOG_KERN: Literal[0] - LOG_LOCAL0: Literal[128] - LOG_LOCAL1: Literal[136] - LOG_LOCAL2: Literal[144] - LOG_LOCAL3: Literal[152] - LOG_LOCAL4: Literal[160] - LOG_LOCAL5: Literal[168] - LOG_LOCAL6: Literal[176] - LOG_LOCAL7: Literal[184] - LOG_LPR: Literal[48] - LOG_MAIL: Literal[16] - LOG_NDELAY: Literal[8] - LOG_NEWS: Literal[56] - LOG_NOTICE: Literal[5] - LOG_NOWAIT: Literal[16] - LOG_ODELAY: Literal[4] - LOG_PERROR: Literal[32] - LOG_PID: Literal[1] - LOG_SYSLOG: Literal[40] - LOG_USER: Literal[8] - LOG_UUCP: Literal[64] - LOG_WARNING: Literal[4] + LOG_ALERT: Final = 1 + LOG_AUTH: Final = 32 + LOG_AUTHPRIV: Final = 80 + LOG_CONS: Final = 2 + LOG_CRIT: Final = 2 + LOG_CRON: Final = 72 + LOG_DAEMON: Final = 24 + LOG_DEBUG: Final = 7 + LOG_EMERG: Final = 0 + LOG_ERR: Final = 3 + LOG_INFO: Final = 6 + LOG_KERN: Final = 0 + LOG_LOCAL0: Final = 128 + LOG_LOCAL1: Final = 136 + LOG_LOCAL2: Final = 144 + LOG_LOCAL3: Final = 152 + LOG_LOCAL4: Final = 160 + LOG_LOCAL5: Final = 168 + LOG_LOCAL6: Final = 176 + LOG_LOCAL7: Final = 184 + LOG_LPR: Final = 48 + LOG_MAIL: Final = 16 + LOG_NDELAY: Final = 8 + LOG_NEWS: Final = 56 + LOG_NOTICE: Final = 5 + LOG_NOWAIT: Final = 16 + LOG_ODELAY: Final = 4 + LOG_PERROR: Final = 32 + LOG_PID: Final = 1 + LOG_SYSLOG: Final = 40 + LOG_USER: Final = 8 + LOG_UUCP: Final = 64 + LOG_WARNING: Final = 4 if sys.version_info >= (3, 13): - LOG_FTP: Literal[88] - LOG_INSTALL: Literal[112] - LOG_LAUNCHD: Literal[192] - LOG_NETINFO: Literal[96] - LOG_RAS: Literal[120] - LOG_REMOTEAUTH: Literal[104] + LOG_FTP: Final = 88 + + if sys.platform == "darwin": + LOG_INSTALL: Final = 112 + LOG_LAUNCHD: Final = 192 + LOG_NETINFO: Final = 96 + LOG_RAS: Final = 120 + LOG_REMOTEAUTH: Final = 104 def LOG_MASK(pri: int, /) -> int: ... def LOG_UPTO(pri: int, /) -> int: ... diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/tempfile.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/tempfile.pyi index 3ae8cca39f77e..d31fd74d34827 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/tempfile.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/tempfile.pyi @@ -264,6 +264,8 @@ class _TemporaryFileWrapper(IO[AnyStr]): def writelines(self: _TemporaryFileWrapper[bytes], lines: Iterable[ReadableBuffer]) -> None: ... @overload def writelines(self, lines: Iterable[AnyStr]) -> None: ... + @property + def closed(self) -> bool: ... if sys.version_info >= (3, 11): _SpooledTemporaryFileBase = io.IOBase diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/tkinter/constants.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/tkinter/constants.pyi index 74fa72acb0bfb..0b497f3a42e47 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/tkinter/constants.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/tkinter/constants.pyi @@ -1,4 +1,4 @@ -from typing import Literal +from typing import Final # These are not actually bools. See #4669 NO: bool @@ -7,74 +7,74 @@ TRUE: bool FALSE: bool ON: bool OFF: bool -N: Literal["n"] -S: Literal["s"] -W: Literal["w"] -E: Literal["e"] -NW: Literal["nw"] -SW: Literal["sw"] -NE: Literal["ne"] -SE: Literal["se"] -NS: Literal["ns"] -EW: Literal["ew"] -NSEW: Literal["nsew"] -CENTER: Literal["center"] -NONE: Literal["none"] -X: Literal["x"] -Y: Literal["y"] -BOTH: Literal["both"] -LEFT: Literal["left"] -TOP: Literal["top"] -RIGHT: Literal["right"] -BOTTOM: Literal["bottom"] -RAISED: Literal["raised"] -SUNKEN: Literal["sunken"] -FLAT: Literal["flat"] -RIDGE: Literal["ridge"] -GROOVE: Literal["groove"] -SOLID: Literal["solid"] -HORIZONTAL: Literal["horizontal"] -VERTICAL: Literal["vertical"] -NUMERIC: Literal["numeric"] -CHAR: Literal["char"] -WORD: Literal["word"] -BASELINE: Literal["baseline"] -INSIDE: Literal["inside"] -OUTSIDE: Literal["outside"] -SEL: Literal["sel"] -SEL_FIRST: Literal["sel.first"] -SEL_LAST: Literal["sel.last"] -END: Literal["end"] -INSERT: Literal["insert"] -CURRENT: Literal["current"] -ANCHOR: Literal["anchor"] -ALL: Literal["all"] -NORMAL: Literal["normal"] -DISABLED: Literal["disabled"] -ACTIVE: Literal["active"] -HIDDEN: Literal["hidden"] -CASCADE: Literal["cascade"] -CHECKBUTTON: Literal["checkbutton"] -COMMAND: Literal["command"] -RADIOBUTTON: Literal["radiobutton"] -SEPARATOR: Literal["separator"] -SINGLE: Literal["single"] -BROWSE: Literal["browse"] -MULTIPLE: Literal["multiple"] -EXTENDED: Literal["extended"] -DOTBOX: Literal["dotbox"] -UNDERLINE: Literal["underline"] -PIESLICE: Literal["pieslice"] -CHORD: Literal["chord"] -ARC: Literal["arc"] -FIRST: Literal["first"] -LAST: Literal["last"] -BUTT: Literal["butt"] -PROJECTING: Literal["projecting"] -ROUND: Literal["round"] -BEVEL: Literal["bevel"] -MITER: Literal["miter"] -MOVETO: Literal["moveto"] -SCROLL: Literal["scroll"] -UNITS: Literal["units"] -PAGES: Literal["pages"] +N: Final = "n" +S: Final = "s" +W: Final = "w" +E: Final = "e" +NW: Final = "nw" +SW: Final = "sw" +NE: Final = "ne" +SE: Final = "se" +NS: Final = "ns" +EW: Final = "ew" +NSEW: Final = "nsew" +CENTER: Final = "center" +NONE: Final = "none" +X: Final = "x" +Y: Final = "y" +BOTH: Final = "both" +LEFT: Final = "left" +TOP: Final = "top" +RIGHT: Final = "right" +BOTTOM: Final = "bottom" +RAISED: Final = "raised" +SUNKEN: Final = "sunken" +FLAT: Final = "flat" +RIDGE: Final = "ridge" +GROOVE: Final = "groove" +SOLID: Final = "solid" +HORIZONTAL: Final = "horizontal" +VERTICAL: Final = "vertical" +NUMERIC: Final = "numeric" +CHAR: Final = "char" +WORD: Final = "word" +BASELINE: Final = "baseline" +INSIDE: Final = "inside" +OUTSIDE: Final = "outside" +SEL: Final = "sel" +SEL_FIRST: Final = "sel.first" +SEL_LAST: Final = "sel.last" +END: Final = "end" +INSERT: Final = "insert" +CURRENT: Final = "current" +ANCHOR: Final = "anchor" +ALL: Final = "all" +NORMAL: Final = "normal" +DISABLED: Final = "disabled" +ACTIVE: Final = "active" +HIDDEN: Final = "hidden" +CASCADE: Final = "cascade" +CHECKBUTTON: Final = "checkbutton" +COMMAND: Final = "command" +RADIOBUTTON: Final = "radiobutton" +SEPARATOR: Final = "separator" +SINGLE: Final = "single" +BROWSE: Final = "browse" +MULTIPLE: Final = "multiple" +EXTENDED: Final = "extended" +DOTBOX: Final = "dotbox" +UNDERLINE: Final = "underline" +PIESLICE: Final = "pieslice" +CHORD: Final = "chord" +ARC: Final = "arc" +FIRST: Final = "first" +LAST: Final = "last" +BUTT: Final = "butt" +PROJECTING: Final = "projecting" +ROUND: Final = "round" +BEVEL: Final = "bevel" +MITER: Final = "miter" +MOVETO: Final = "moveto" +SCROLL: Final = "scroll" +UNITS: Final = "units" +PAGES: Final = "pages" diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/tkinter/font.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/tkinter/font.pyi index 46625014d4ac4..317f3068be63a 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/tkinter/font.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/tkinter/font.pyi @@ -1,16 +1,16 @@ import _tkinter import sys import tkinter -from typing import Any, Literal, TypedDict, overload +from typing import Any, Final, Literal, TypedDict, overload from typing_extensions import TypeAlias if sys.version_info >= (3, 9): __all__ = ["NORMAL", "ROMAN", "BOLD", "ITALIC", "nametofont", "Font", "families", "names"] -NORMAL: Literal["normal"] -ROMAN: Literal["roman"] -BOLD: Literal["bold"] -ITALIC: Literal["italic"] +NORMAL: Final = "normal" +ROMAN: Final = "roman" +BOLD: Final = "bold" +ITALIC: Final = "italic" _FontDescription: TypeAlias = ( str # "Helvetica 12" diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/tkinter/tix.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/tkinter/tix.pyi index 73649de427e85..7891364fa02c6 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/tkinter/tix.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/tkinter/tix.pyi @@ -1,38 +1,38 @@ import tkinter from _typeshed import Incomplete -from typing import Any, Literal - -WINDOW: Literal["window"] -TEXT: Literal["text"] -STATUS: Literal["status"] -IMMEDIATE: Literal["immediate"] -IMAGE: Literal["image"] -IMAGETEXT: Literal["imagetext"] -BALLOON: Literal["balloon"] -AUTO: Literal["auto"] -ACROSSTOP: Literal["acrosstop"] - -ASCII: Literal["ascii"] -CELL: Literal["cell"] -COLUMN: Literal["column"] -DECREASING: Literal["decreasing"] -INCREASING: Literal["increasing"] -INTEGER: Literal["integer"] -MAIN: Literal["main"] -MAX: Literal["max"] -REAL: Literal["real"] -ROW: Literal["row"] -S_REGION: Literal["s-region"] -X_REGION: Literal["x-region"] -Y_REGION: Literal["y-region"] +from typing import Any, Final + +WINDOW: Final = "window" +TEXT: Final = "text" +STATUS: Final = "status" +IMMEDIATE: Final = "immediate" +IMAGE: Final = "image" +IMAGETEXT: Final = "imagetext" +BALLOON: Final = "balloon" +AUTO: Final = "auto" +ACROSSTOP: Final = "acrosstop" + +ASCII: Final = "ascii" +CELL: Final = "cell" +COLUMN: Final = "column" +DECREASING: Final = "decreasing" +INCREASING: Final = "increasing" +INTEGER: Final = "integer" +MAIN: Final = "main" +MAX: Final = "max" +REAL: Final = "real" +ROW: Final = "row" +S_REGION: Final = "s-region" +X_REGION: Final = "x-region" +Y_REGION: Final = "y-region" # These should be kept in sync with _tkinter constants, except TCL_ALL_EVENTS which doesn't match ALL_EVENTS -TCL_DONT_WAIT: Literal[2] -TCL_WINDOW_EVENTS: Literal[4] -TCL_FILE_EVENTS: Literal[8] -TCL_TIMER_EVENTS: Literal[16] -TCL_IDLE_EVENTS: Literal[32] -TCL_ALL_EVENTS: Literal[0] +TCL_DONT_WAIT: Final = 2 +TCL_WINDOW_EVENTS: Final = 4 +TCL_FILE_EVENTS: Final = 8 +TCL_TIMER_EVENTS: Final = 16 +TCL_IDLE_EVENTS: Final = 32 +TCL_ALL_EVENTS: Final = 0 class tixCommand: def tix_addbitmapdir(self, directory: str) -> None: ... diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/tty.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/tty.pyi index add0d57a8d4b1..0611879cf1b29 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/tty.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/tty.pyi @@ -1,6 +1,6 @@ import sys import termios -from typing import IO +from typing import IO, Final from typing_extensions import TypeAlias if sys.platform != "win32": @@ -15,13 +15,13 @@ if sys.platform != "win32": _FD: TypeAlias = int | IO[str] # XXX: Undocumented integer constants - IFLAG: int - OFLAG: int - CFLAG: int - LFLAG: int - ISPEED: int - OSPEED: int - CC: int + IFLAG: Final[int] + OFLAG: Final[int] + CFLAG: Final[int] + LFLAG: Final[int] + ISPEED: Final[int] + OSPEED: Final[int] + CC: Final[int] def setraw(fd: _FD, when: int = 2) -> _ModeSetterReturn: ... def setcbreak(fd: _FD, when: int = 2) -> _ModeSetterReturn: ... diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/types.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/types.pyi index a569b55efa23b..1e3eacd9f1fa6 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/types.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/types.pyi @@ -304,6 +304,10 @@ class MappingProxyType(Mapping[_KT, _VT_co]): def keys(self) -> KeysView[_KT]: ... def values(self) -> ValuesView[_VT_co]: ... def items(self) -> ItemsView[_KT, _VT_co]: ... + @overload + def get(self, key: _KT, /) -> _VT_co | None: ... # type: ignore[override] + @overload + def get(self, key: _KT, default: _VT_co | _T2, /) -> _VT_co | _T2: ... # type: ignore[override] if sys.version_info >= (3, 9): def __class_getitem__(cls, item: Any, /) -> GenericAlias: ... def __reversed__(self) -> Iterator[_KT]: ... diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/typing.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/typing.pyi index c64baf6ba8f3c..f4de1fa86de55 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/typing.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/typing.pyi @@ -1056,7 +1056,7 @@ if sys.version_info >= (3, 12): # It's writable on types, but not on instances of TypeAliasType. @property def __module__(self) -> str | None: ... # type: ignore[override] - def __getitem__(self, parameters: Any) -> Any: ... + def __getitem__(self, parameters: Any) -> GenericAlias: ... def __or__(self, right: Any) -> _SpecialForm: ... def __ror__(self, left: Any) -> _SpecialForm: ... diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/typing_extensions.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/typing_extensions.pyi index a7d2b2c2e0835..1e4f90a0a7226 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/typing_extensions.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/typing_extensions.pyi @@ -403,6 +403,7 @@ else: # It's writable on types, but not on instances of TypeAliasType. @property def __module__(self) -> str | None: ... # type: ignore[override] + # Returns typing._GenericAlias, which isn't stubbed. def __getitem__(self, parameters: Any) -> Any: ... if sys.version_info >= (3, 10): def __or__(self, right: Any) -> _SpecialForm: ... diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/unittest/case.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/unittest/case.pyi index b63292604ecc5..a92f03f9745fe 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/unittest/case.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/unittest/case.pyi @@ -6,7 +6,20 @@ from collections.abc import Callable, Container, Iterable, Mapping, Sequence, Se from contextlib import AbstractContextManager from re import Pattern from types import TracebackType -from typing import Any, AnyStr, ClassVar, Generic, NamedTuple, NoReturn, Protocol, SupportsAbs, SupportsRound, TypeVar, overload +from typing import ( + Any, + AnyStr, + ClassVar, + Final, + Generic, + NamedTuple, + NoReturn, + Protocol, + SupportsAbs, + SupportsRound, + TypeVar, + overload, +) from typing_extensions import ParamSpec, Self, TypeAlias from warnings import WarningMessage @@ -22,7 +35,7 @@ _E = TypeVar("_E", bound=BaseException) _FT = TypeVar("_FT", bound=Callable[..., Any]) _P = ParamSpec("_P") -DIFF_OMITTED: str +DIFF_OMITTED: Final[str] class _BaseTestCaseContext: test_case: TestCase diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/unittest/loader.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/unittest/loader.pyi index 657f3d6dca719..598e3cd84a5e8 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/unittest/loader.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/unittest/loader.pyi @@ -4,13 +4,13 @@ import unittest.suite from collections.abc import Callable, Sequence from re import Pattern from types import ModuleType -from typing import Any +from typing import Any, Final from typing_extensions import TypeAlias, deprecated _SortComparisonMethod: TypeAlias = Callable[[str, str], int] _SuiteClass: TypeAlias = Callable[[list[unittest.case.TestCase]], unittest.suite.TestSuite] -VALID_MODULE_NAME: Pattern[str] +VALID_MODULE_NAME: Final[Pattern[str]] class TestLoader: errors: list[type[BaseException]] diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/unittest/main.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/unittest/main.pyi index 3eb3d1612a3c3..22f2ec10634d6 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/unittest/main.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/unittest/main.pyi @@ -5,11 +5,11 @@ import unittest.result import unittest.suite from collections.abc import Iterable from types import ModuleType -from typing import Any, Protocol +from typing import Any, Final, Protocol from typing_extensions import deprecated -MAIN_EXAMPLES: str -MODULE_EXAMPLES: str +MAIN_EXAMPLES: Final[str] +MODULE_EXAMPLES: Final[str] class _TestRunner(Protocol): def run(self, test: unittest.suite.TestSuite | unittest.case.TestCase, /) -> unittest.result.TestResult: ... diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/unittest/result.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/unittest/result.pyi index 436fabf20c658..0761baaa2830b 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/unittest/result.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/unittest/result.pyi @@ -2,14 +2,14 @@ import sys import unittest.case from _typeshed import OptExcInfo from collections.abc import Callable -from typing import Any, TextIO, TypeVar +from typing import Any, Final, TextIO, TypeVar from typing_extensions import TypeAlias _F = TypeVar("_F", bound=Callable[..., Any]) _DurationsType: TypeAlias = list[tuple[str, float]] -STDOUT_LINE: str -STDERR_LINE: str +STDOUT_LINE: Final[str] +STDERR_LINE: Final[str] # undocumented def failfast(method: _F) -> _F: ... diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/unittest/util.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/unittest/util.pyi index c42d1346e4b76..945b0cecfed09 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/unittest/util.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/unittest/util.pyi @@ -1,16 +1,16 @@ from collections.abc import MutableSequence, Sequence -from typing import Any, TypeVar +from typing import Any, Final, TypeVar from typing_extensions import TypeAlias _T = TypeVar("_T") _Mismatch: TypeAlias = tuple[_T, _T, int] -_MAX_LENGTH: int -_PLACEHOLDER_LEN: int -_MIN_BEGIN_LEN: int -_MIN_END_LEN: int -_MIN_COMMON_LEN: int -_MIN_DIFF_LEN: int +_MAX_LENGTH: Final[int] +_PLACEHOLDER_LEN: Final[int] +_MIN_BEGIN_LEN: Final[int] +_MIN_END_LEN: Final[int] +_MIN_COMMON_LEN: Final[int] +_MIN_DIFF_LEN: Final[int] def _shorten(s: str, prefixlen: int, suffixlen: int) -> str: ... def _common_shorten_repr(*args: str) -> tuple[str, ...]: ... diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/wave.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/wave.pyi index 9137f1e476438..9319d5347c791 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/wave.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/wave.pyi @@ -1,6 +1,6 @@ import sys from _typeshed import ReadableBuffer, Unused -from typing import IO, Any, BinaryIO, Literal, NamedTuple, NoReturn, overload +from typing import IO, Any, BinaryIO, Final, Literal, NamedTuple, NoReturn, overload from typing_extensions import Self, TypeAlias, deprecated if sys.version_info >= (3, 9): @@ -12,7 +12,7 @@ _File: TypeAlias = str | IO[bytes] class Error(Exception): ... -WAVE_FORMAT_PCM: Literal[1] +WAVE_FORMAT_PCM: Final = 1 class _wave_params(NamedTuple): nchannels: int diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/webbrowser.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/webbrowser.pyi index 2b3f978c814bb..d7bf033172f65 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/webbrowser.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/webbrowser.pyi @@ -2,6 +2,7 @@ import sys from abc import abstractmethod from collections.abc import Callable, Sequence from typing import Literal +from typing_extensions import deprecated __all__ = ["Error", "open", "open_new", "open_new_tab", "get", "register"] @@ -62,8 +63,10 @@ if sys.platform == "win32": def open(self, url: str, new: int = 0, autoraise: bool = True) -> bool: ... if sys.platform == "darwin": - class MacOSX(BaseBrowser): - def open(self, url: str, new: int = 0, autoraise: bool = True) -> bool: ... + if sys.version_info < (3, 13): + @deprecated("Deprecated in 3.11, to be removed in 3.13.") + class MacOSX(BaseBrowser): + def open(self, url: str, new: int = 0, autoraise: bool = True) -> bool: ... class MacOSXOSAScript(BaseBrowser): # In runtime this class does not have `name` and `basename` if sys.version_info >= (3, 11): diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/winsound.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/winsound.pyi index bacc5302826f0..a20e81f94f98f 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/winsound.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/winsound.pyi @@ -1,24 +1,24 @@ import sys from _typeshed import ReadableBuffer -from typing import Literal, overload +from typing import Final, Literal, overload if sys.platform == "win32": - SND_APPLICATION: Literal[128] - SND_FILENAME: Literal[131072] - SND_ALIAS: Literal[65536] - SND_LOOP: Literal[8] - SND_MEMORY: Literal[4] - SND_PURGE: Literal[64] - SND_ASYNC: Literal[1] - SND_NODEFAULT: Literal[2] - SND_NOSTOP: Literal[16] - SND_NOWAIT: Literal[8192] + SND_APPLICATION: Final = 128 + SND_FILENAME: Final = 131072 + SND_ALIAS: Final = 65536 + SND_LOOP: Final = 8 + SND_MEMORY: Final = 4 + SND_PURGE: Final = 64 + SND_ASYNC: Final = 1 + SND_NODEFAULT: Final = 2 + SND_NOSTOP: Final = 16 + SND_NOWAIT: Final = 8192 - MB_ICONASTERISK: Literal[64] - MB_ICONEXCLAMATION: Literal[48] - MB_ICONHAND: Literal[16] - MB_ICONQUESTION: Literal[32] - MB_OK: Literal[0] + MB_ICONASTERISK: Final = 64 + MB_ICONEXCLAMATION: Final = 48 + MB_ICONHAND: Final = 16 + MB_ICONQUESTION: Final = 32 + MB_OK: Final = 0 def Beep(frequency: int, duration: int) -> None: ... # Can actually accept anything ORed with 4, and if not it's definitely str, but that's inexpressible @overload diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/xml/dom/pulldom.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/xml/dom/pulldom.pyi index 95436ab5dd381..50250de5cb2f6 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/xml/dom/pulldom.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/xml/dom/pulldom.pyi @@ -1,20 +1,20 @@ import sys from _typeshed import Incomplete, SupportsRead from collections.abc import Sequence -from typing import Literal +from typing import Final, Literal from typing_extensions import TypeAlias from xml.dom.minidom import Document, DOMImplementation, Element, Text from xml.sax.handler import ContentHandler from xml.sax.xmlreader import XMLReader -START_ELEMENT: Literal["START_ELEMENT"] -END_ELEMENT: Literal["END_ELEMENT"] -COMMENT: Literal["COMMENT"] -START_DOCUMENT: Literal["START_DOCUMENT"] -END_DOCUMENT: Literal["END_DOCUMENT"] -PROCESSING_INSTRUCTION: Literal["PROCESSING_INSTRUCTION"] -IGNORABLE_WHITESPACE: Literal["IGNORABLE_WHITESPACE"] -CHARACTERS: Literal["CHARACTERS"] +START_ELEMENT: Final = "START_ELEMENT" +END_ELEMENT: Final = "END_ELEMENT" +COMMENT: Final = "COMMENT" +START_DOCUMENT: Final = "START_DOCUMENT" +END_DOCUMENT: Final = "END_DOCUMENT" +PROCESSING_INSTRUCTION: Final = "PROCESSING_INSTRUCTION" +IGNORABLE_WHITESPACE: Final = "IGNORABLE_WHITESPACE" +CHARACTERS: Final = "CHARACTERS" _DocumentFactory: TypeAlias = DOMImplementation | None _Node: TypeAlias = Document | Element | Text diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/xmlrpc/client.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/xmlrpc/client.pyi index 2be5f7df2d7de..d254102acc553 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/xmlrpc/client.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/xmlrpc/client.pyi @@ -6,7 +6,7 @@ from collections.abc import Callable, Iterable, Mapping from datetime import datetime from io import BytesIO from types import TracebackType -from typing import Any, Literal, Protocol, overload +from typing import Any, Final, Literal, Protocol, overload from typing_extensions import Self, TypeAlias class _SupportsTimeTuple(Protocol): @@ -34,22 +34,22 @@ _HostType: TypeAlias = tuple[str, dict[str, str]] | str def escape(s: str) -> str: ... # undocumented -MAXINT: int # undocumented -MININT: int # undocumented +MAXINT: Final[int] # undocumented +MININT: Final[int] # undocumented -PARSE_ERROR: int # undocumented -SERVER_ERROR: int # undocumented -APPLICATION_ERROR: int # undocumented -SYSTEM_ERROR: int # undocumented -TRANSPORT_ERROR: int # undocumented +PARSE_ERROR: Final[int] # undocumented +SERVER_ERROR: Final[int] # undocumented +APPLICATION_ERROR: Final[int] # undocumented +SYSTEM_ERROR: Final[int] # undocumented +TRANSPORT_ERROR: Final[int] # undocumented -NOT_WELLFORMED_ERROR: int # undocumented -UNSUPPORTED_ENCODING: int # undocumented -INVALID_ENCODING_CHAR: int # undocumented -INVALID_XMLRPC: int # undocumented -METHOD_NOT_FOUND: int # undocumented -INVALID_METHOD_PARAMS: int # undocumented -INTERNAL_ERROR: int # undocumented +NOT_WELLFORMED_ERROR: Final[int] # undocumented +UNSUPPORTED_ENCODING: Final[int] # undocumented +INVALID_ENCODING_CHAR: Final[int] # undocumented +INVALID_XMLRPC: Final[int] # undocumented +METHOD_NOT_FOUND: Final[int] # undocumented +INVALID_METHOD_PARAMS: Final[int] # undocumented +INTERNAL_ERROR: Final[int] # undocumented class Error(Exception): ... @@ -98,7 +98,7 @@ class Binary: def _binary(data: ReadableBuffer) -> Binary: ... # undocumented -WRAPPERS: tuple[type[DateTime], type[Binary]] # undocumented +WRAPPERS: Final[tuple[type[DateTime], type[Binary]]] # undocumented class ExpatParser: # undocumented def __init__(self, target: Unmarshaller) -> None: ... diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/zipfile/__init__.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/zipfile/__init__.pyi index aa52a0b56e41f..57a8a6aaa40af 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/zipfile/__init__.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/zipfile/__init__.pyi @@ -5,7 +5,7 @@ from collections.abc import Callable, Iterable, Iterator from io import TextIOWrapper from os import PathLike from types import TracebackType -from typing import IO, Literal, Protocol, overload +from typing import IO, Final, Literal, Protocol, overload from typing_extensions import Self, TypeAlias __all__ = [ @@ -300,10 +300,10 @@ else: def is_zipfile(filename: StrOrBytesPath | _SupportsReadSeekTell) -> bool: ... -ZIP_STORED: int -ZIP_DEFLATED: int -ZIP64_LIMIT: int -ZIP_FILECOUNT_LIMIT: int -ZIP_MAX_COMMENT: int -ZIP_BZIP2: int -ZIP_LZMA: int +ZIP_STORED: Final[int] +ZIP_DEFLATED: Final[int] +ZIP64_LIMIT: Final[int] +ZIP_FILECOUNT_LIMIT: Final[int] +ZIP_MAX_COMMENT: Final[int] +ZIP_BZIP2: Final[int] +ZIP_LZMA: Final[int] diff --git a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/zlib.pyi b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/zlib.pyi index 234770172d40e..2f6c406560384 100644 --- a/crates/red_knot_module_resolver/vendor/typeshed/stdlib/zlib.pyi +++ b/crates/red_knot_module_resolver/vendor/typeshed/stdlib/zlib.pyi @@ -1,29 +1,29 @@ import sys from _typeshed import ReadableBuffer -from typing import Literal +from typing import Final -DEFLATED: Literal[8] +DEFLATED: Final = 8 DEF_MEM_LEVEL: int # can change -DEF_BUF_SIZE: Literal[16384] +DEF_BUF_SIZE: Final = 16384 MAX_WBITS: int ZLIB_VERSION: str # can change ZLIB_RUNTIME_VERSION: str # can change -Z_NO_COMPRESSION: Literal[0] -Z_PARTIAL_FLUSH: Literal[1] -Z_BEST_COMPRESSION: Literal[9] -Z_BEST_SPEED: Literal[1] -Z_BLOCK: Literal[5] -Z_DEFAULT_COMPRESSION: Literal[-1] -Z_DEFAULT_STRATEGY: Literal[0] -Z_FILTERED: Literal[1] -Z_FINISH: Literal[4] -Z_FIXED: Literal[4] -Z_FULL_FLUSH: Literal[3] -Z_HUFFMAN_ONLY: Literal[2] -Z_NO_FLUSH: Literal[0] -Z_RLE: Literal[3] -Z_SYNC_FLUSH: Literal[2] -Z_TREES: Literal[6] +Z_NO_COMPRESSION: Final = 0 +Z_PARTIAL_FLUSH: Final = 1 +Z_BEST_COMPRESSION: Final = 9 +Z_BEST_SPEED: Final = 1 +Z_BLOCK: Final = 5 +Z_DEFAULT_COMPRESSION: Final = -1 +Z_DEFAULT_STRATEGY: Final = 0 +Z_FILTERED: Final = 1 +Z_FINISH: Final = 4 +Z_FIXED: Final = 4 +Z_FULL_FLUSH: Final = 3 +Z_HUFFMAN_ONLY: Final = 2 +Z_NO_FLUSH: Final = 0 +Z_RLE: Final = 3 +Z_SYNC_FLUSH: Final = 2 +Z_TREES: Final = 6 class error(Exception): ... diff --git a/crates/red_knot_python_semantic/src/db.rs b/crates/red_knot_python_semantic/src/db.rs index 1ba9208b32266..19f1f23a2f770 100644 --- a/crates/red_knot_python_semantic/src/db.rs +++ b/crates/red_knot_python_semantic/src/db.rs @@ -1,55 +1,23 @@ -use salsa::DbWithJar; - use red_knot_module_resolver::Db as ResolverDb; -use ruff_db::{Db as SourceDb, Upcast}; - -use crate::builtins::builtins_scope; -use crate::semantic_index::definition::Definition; -use crate::semantic_index::expression::Expression; -use crate::semantic_index::symbol::ScopeId; -use crate::semantic_index::{global_scope, semantic_index, symbol_table, use_def_map}; -use crate::types::{ - infer_definition_types, infer_expression_types, infer_scope_types, ClassType, FunctionType, - IntersectionType, UnionType, -}; - -#[salsa::jar(db=Db)] -pub struct Jar( - ScopeId<'_>, - Definition<'_>, - Expression<'_>, - FunctionType<'_>, - ClassType<'_>, - UnionType<'_>, - IntersectionType<'_>, - symbol_table, - use_def_map, - global_scope, - semantic_index, - infer_definition_types, - infer_expression_types, - infer_scope_types, - builtins_scope, -); +use ruff_db::Upcast; /// Database giving access to semantic information about a Python program. -pub trait Db: SourceDb + ResolverDb + DbWithJar + Upcast {} +#[salsa::db] +pub trait Db: ResolverDb + Upcast {} #[cfg(test)] pub(crate) mod tests { use std::sync::Arc; - use salsa::DebugWithDb; - - use red_knot_module_resolver::{vendored_typeshed_stubs, Db as ResolverDb, Jar as ResolverJar}; + use red_knot_module_resolver::{vendored_typeshed_stubs, Db as ResolverDb}; use ruff_db::files::Files; use ruff_db::system::{DbWithTestSystem, System, TestSystem}; use ruff_db::vendored::VendoredFileSystem; - use ruff_db::{Db as SourceDb, Jar as SourceJar, Upcast}; + use ruff_db::{Db as SourceDb, Upcast}; - use super::{Db, Jar}; + use super::Db; - #[salsa::db(Jar, ResolverJar, SourceJar)] + #[salsa::db] pub(crate) struct TestDb { storage: salsa::Storage, files: Files, @@ -63,7 +31,7 @@ pub(crate) mod tests { Self { storage: salsa::Storage::default(), system: TestSystem::default(), - vendored: vendored_typeshed_stubs().snapshot(), + vendored: vendored_typeshed_stubs().clone(), events: std::sync::Arc::default(), files: Files::default(), } @@ -99,6 +67,7 @@ pub(crate) mod tests { } } + #[salsa::db] impl SourceDb for TestDb { fn vendored(&self) -> &VendoredFileSystem { &self.vendored @@ -131,26 +100,20 @@ pub(crate) mod tests { } } + #[salsa::db] impl red_knot_module_resolver::Db for TestDb {} + + #[salsa::db] impl Db for TestDb {} + #[salsa::db] impl salsa::Database for TestDb { fn salsa_event(&self, event: salsa::Event) { - tracing::trace!("event: {:?}", event.debug(self)); - let mut events = self.events.lock().unwrap(); - events.push(event); - } - } - - impl salsa::ParallelDatabase for TestDb { - fn snapshot(&self) -> salsa::Snapshot { - salsa::Snapshot::new(Self { - storage: self.storage.snapshot(), - files: self.files.snapshot(), - system: self.system.snapshot(), - vendored: self.vendored.snapshot(), - events: self.events.clone(), - }) + self.attach(|_| { + tracing::trace!("event: {event:?}"); + let mut events = self.events.lock().unwrap(); + events.push(event); + }); } } } diff --git a/crates/red_knot_python_semantic/src/lib.rs b/crates/red_knot_python_semantic/src/lib.rs index 236b0aa534030..7d3166c2bfc7e 100644 --- a/crates/red_knot_python_semantic/src/lib.rs +++ b/crates/red_knot_python_semantic/src/lib.rs @@ -2,7 +2,7 @@ use std::hash::BuildHasherDefault; use rustc_hash::FxHasher; -pub use db::{Db, Jar}; +pub use db::Db; pub use semantic_model::{HasTy, SemanticModel}; pub mod ast_node_ref; diff --git a/crates/red_knot_python_semantic/src/semantic_index.rs b/crates/red_knot_python_semantic/src/semantic_index.rs index ef8f6f0aa15be..45d24a599db35 100644 --- a/crates/red_knot_python_semantic/src/semantic_index.rs +++ b/crates/red_knot_python_semantic/src/semantic_index.rs @@ -2,6 +2,7 @@ use std::iter::FusedIterator; use std::sync::Arc; use rustc_hash::FxHashMap; +use salsa::plumbing::AsId; use ruff_db::files::File; use ruff_db::parsed::parsed_module; @@ -17,6 +18,8 @@ use crate::semantic_index::symbol::{ }; use crate::Db; +pub(crate) use self::use_def::UseDefMap; + pub mod ast_ids; mod builder; pub mod definition; @@ -24,8 +27,6 @@ pub mod expression; pub mod symbol; mod use_def; -pub(crate) use self::use_def::UseDefMap; - type SymbolMap = hashbrown::HashMap; /// Returns the semantic index for `file`. @@ -33,7 +34,7 @@ type SymbolMap = hashbrown::HashMap; /// Prefer using [`symbol_table`] when working with symbols from a single scope. #[salsa::tracked(return_ref, no_eq)] pub(crate) fn semantic_index(db: &dyn Db, file: File) -> SemanticIndex<'_> { - let _span = tracing::trace_span!("semantic_index", ?file).entered(); + let _span = tracing::trace_span!("semantic_index", file=?file.path(db)).entered(); let parsed = parsed_module(db.upcast(), file); @@ -47,8 +48,10 @@ pub(crate) fn semantic_index(db: &dyn Db, file: File) -> SemanticIndex<'_> { /// is unchanged. #[salsa::tracked] pub(crate) fn symbol_table<'db>(db: &'db dyn Db, scope: ScopeId<'db>) -> Arc { - let _span = tracing::trace_span!("symbol_table", ?scope).entered(); - let index = semantic_index(db, scope.file(db)); + let file = scope.file(db); + let _span = + tracing::trace_span!("symbol_table", scope=?scope.as_id(), file=?file.path(db)).entered(); + let index = semantic_index(db, file); index.symbol_table(scope.file_scope_id(db)) } @@ -60,8 +63,10 @@ pub(crate) fn symbol_table<'db>(db: &'db dyn Db, scope: ScopeId<'db>) -> Arc(db: &'db dyn Db, scope: ScopeId<'db>) -> Arc> { - let _span = tracing::trace_span!("use_def_map", ?scope).entered(); - let index = semantic_index(db, scope.file(db)); + let file = scope.file(db); + let _span = + tracing::trace_span!("use_def_map", scope=?scope.as_id(), file=?file.path(db)).entered(); + let index = semantic_index(db, file); index.use_def_map(scope.file_scope_id(db)) } @@ -69,7 +74,7 @@ pub(crate) fn use_def_map<'db>(db: &'db dyn Db, scope: ScopeId<'db>) -> Arc ScopeId<'_> { - let _span = tracing::trace_span!("global_scope", ?file).entered(); + let _span = tracing::trace_span!("global_scope", file=?file.path(db)).entered(); FileScopeId::global().to_scope_id(db, file) } diff --git a/crates/red_knot_python_semantic/src/semantic_index/builder.rs b/crates/red_knot_python_semantic/src/semantic_index/builder.rs index 0214d6c899f12..f442e98815fdf 100644 --- a/crates/red_knot_python_semantic/src/semantic_index/builder.rs +++ b/crates/red_knot_python_semantic/src/semantic_index/builder.rs @@ -330,7 +330,7 @@ where function_def.type_params.as_deref(), |builder| { builder.visit_parameters(&function_def.parameters); - for expr in &function_def.returns { + if let Some(expr) = &function_def.returns { builder.visit_annotation(expr); } diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index bcd294255b209..ea0a931f851b2 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -10,7 +10,7 @@ use crate::{Db, FxOrderSet}; mod display; mod infer; -pub(crate) use self::infer::{infer_definition_types, infer_expression_types, infer_scope_types}; +pub(crate) use self::infer::{infer_definition_types, infer_scope_types}; /// Infer the public type of a symbol (its type as seen from outside its scope). pub(crate) fn symbol_ty<'db>( @@ -157,12 +157,15 @@ impl<'db> Type<'db> { // TODO MRO? get_own_instance_member, get_instance_member todo!("attribute lookup on Instance type") } - Type::Union(_) => { - // TODO perform the get_member on each type in the union - // TODO return the union of those results - // TODO if any of those results is `None` then include Unknown in the result union - todo!("attribute lookup on Union type") - } + Type::Union(union) => Type::Union( + union + .elements(db) + .iter() + .fold(UnionTypeBuilder::new(db), |builder, element_ty| { + builder.add(element_ty.member(db, name)) + }) + .build(), + ), Type::Intersection(_) => { // TODO perform the get_member on each type in the intersection // TODO return the intersection of those results diff --git a/crates/red_knot_python_semantic/src/types/display.rs b/crates/red_knot_python_semantic/src/types/display.rs index 42850e9e4c82e..d2ff7eae0fef2 100644 --- a/crates/red_knot_python_semantic/src/types/display.rs +++ b/crates/red_knot_python_semantic/src/types/display.rs @@ -26,7 +26,7 @@ impl Display for DisplayType<'_> { Type::Unbound => f.write_str("Unbound"), Type::None => f.write_str("None"), Type::Module(file) => { - write!(f, "", file.path(self.db.upcast())) + write!(f, "", file.path(self.db)) } // TODO functions and classes should display using a fully qualified name Type::Class(class) => write!(f, "Literal[{}]", class.name(self.db)), diff --git a/crates/red_knot_python_semantic/src/types/infer.rs b/crates/red_knot_python_semantic/src/types/infer.rs index da19ab9ebb352..f8cd746401e3e 100644 --- a/crates/red_knot_python_semantic/src/types/infer.rs +++ b/crates/red_knot_python_semantic/src/types/infer.rs @@ -22,6 +22,7 @@ //! holds types for every [`Definition`] and expression within the inferred region. use rustc_hash::FxHashMap; use salsa; +use salsa::plumbing::AsId; use red_knot_module_resolver::{resolve_module, ModuleName}; use ruff_db::files::File; @@ -48,7 +49,9 @@ use crate::Db; #[salsa::tracked(return_ref)] pub(crate) fn infer_scope_types<'db>(db: &'db dyn Db, scope: ScopeId<'db>) -> TypeInference<'db> { let file = scope.file(db); - let _span = tracing::trace_span!("infer_scope_types", ?scope, ?file).entered(); + let _span = + tracing::trace_span!("infer_scope_types", scope=?scope.as_id(), file=?file.path(db)) + .entered(); // Using the index here is fine because the code below depends on the AST anyway. // The isolation of the query is by the return inferred types. @@ -77,7 +80,12 @@ pub(crate) fn infer_definition_types<'db>( definition: Definition<'db>, ) -> TypeInference<'db> { let file = definition.file(db); - let _span = tracing::trace_span!("infer_definition_types", ?definition, ?file,).entered(); + let _span = tracing::trace_span!( + "infer_definition_types", + definition = ?definition.as_id(), + file = ?file.path(db) + ) + .entered(); let index = semantic_index(db, file); @@ -95,7 +103,9 @@ pub(crate) fn infer_expression_types<'db>( expression: Expression<'db>, ) -> TypeInference<'db> { let file = expression.file(db); - let _span = tracing::trace_span!("infer_expression_types", ?expression, ?file).entered(); + let _span = + tracing::trace_span!("infer_expression_types", expression=?expression.as_id(), file=?file.path(db)) + .entered(); let index = semantic_index(db, file); @@ -1491,12 +1501,9 @@ mod tests { use crate::builtins::builtins_scope; use crate::db::tests::TestDb; use crate::semantic_index::definition::Definition; - use crate::semantic_index::semantic_index; use crate::semantic_index::symbol::FileScopeId; - use crate::types::{ - global_scope, global_symbol_ty_by_name, infer_definition_types, symbol_table, - symbol_ty_by_name, use_def_map, Type, - }; + use crate::semantic_index::{global_scope, semantic_index, symbol_table, use_def_map}; + use crate::types::{global_symbol_ty_by_name, infer_definition_types, symbol_ty_by_name, Type}; use crate::{HasTy, SemanticModel}; fn setup_db() -> TestDb { @@ -2231,6 +2238,36 @@ mod tests { Ok(()) } + #[test] + fn attribute_of_union() -> anyhow::Result<()> { + let mut db = setup_db(); + + db.write_dedented( + "/src/a.py", + " + if flag: + class C: + x = 1 + else: + class C: + x = 2 + y = C.x + ", + )?; + + assert_public_ty(&db, "/src/a.py", "y", "Literal[1, 2]"); + + Ok(()) + } + + fn first_public_def<'db>(db: &'db TestDb, file: File, name: &str) -> Definition<'db> { + let scope = global_scope(db, file); + *use_def_map(db, scope) + .public_definitions(symbol_table(db, scope).symbol_id_by_name(name).unwrap()) + .first() + .unwrap() + } + #[test] fn big_int() -> anyhow::Result<()> { let mut db = setup_db(); @@ -2315,14 +2352,6 @@ mod tests { Ok(()) } - fn first_public_def<'db>(db: &'db TestDb, file: File, name: &str) -> Definition<'db> { - let scope = global_scope(db, file); - *use_def_map(db, scope) - .public_definitions(symbol_table(db, scope).symbol_id_by_name(name).unwrap()) - .first() - .unwrap() - } - #[test] fn dependency_public_symbol_type_change() -> anyhow::Result<()> { let mut db = setup_db(); @@ -2375,10 +2404,10 @@ mod tests { let events = db.take_salsa_events(); - assert_function_query_was_not_run::( + assert_function_query_was_not_run( &db, - |ty| &ty.function, - &first_public_def(&db, a, "x"), + infer_definition_types, + first_public_def(&db, a, "x"), &events, ); @@ -2411,10 +2440,10 @@ mod tests { let events = db.take_salsa_events(); - assert_function_query_was_not_run::( + assert_function_query_was_not_run( &db, - |ty| &ty.function, - &first_public_def(&db, a, "x"), + infer_definition_types, + first_public_def(&db, a, "x"), &events, ); Ok(()) diff --git a/crates/red_knot_workspace/Cargo.toml b/crates/red_knot_workspace/Cargo.toml new file mode 100644 index 0000000000000..3bcb9688a5c05 --- /dev/null +++ b/crates/red_knot_workspace/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "red_knot_workspace" +version = "0.0.0" +edition.workspace = true +rust-version.workspace = true +homepage.workspace = true +documentation.workspace = true +repository.workspace = true +authors.workspace = true +license.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +red_knot_module_resolver = { workspace = true } +red_knot_python_semantic = { workspace = true } + +ruff_db = { workspace = true, features = ["os", "cache"] } +ruff_python_ast = { workspace = true } + +anyhow = { workspace = true } +crossbeam = { workspace = true } +notify = { workspace = true } +rustc-hash = { workspace = true } +salsa = { workspace = true } +tracing = { workspace = true } + +[dev-dependencies] + +[lints] +workspace = true diff --git a/crates/red_knot/resources/test/corpus/00_const.py b/crates/red_knot_workspace/resources/test/corpus/00_const.py similarity index 100% rename from crates/red_knot/resources/test/corpus/00_const.py rename to crates/red_knot_workspace/resources/test/corpus/00_const.py diff --git a/crates/red_knot/resources/test/corpus/00_empty.py b/crates/red_knot_workspace/resources/test/corpus/00_empty.py similarity index 100% rename from crates/red_knot/resources/test/corpus/00_empty.py rename to crates/red_knot_workspace/resources/test/corpus/00_empty.py diff --git a/crates/red_knot/resources/test/corpus/00_expr_discard.py b/crates/red_knot_workspace/resources/test/corpus/00_expr_discard.py similarity index 100% rename from crates/red_knot/resources/test/corpus/00_expr_discard.py rename to crates/red_knot_workspace/resources/test/corpus/00_expr_discard.py diff --git a/crates/red_knot/resources/test/corpus/00_expr_var1.py b/crates/red_knot_workspace/resources/test/corpus/00_expr_var1.py similarity index 100% rename from crates/red_knot/resources/test/corpus/00_expr_var1.py rename to crates/red_knot_workspace/resources/test/corpus/00_expr_var1.py diff --git a/crates/red_knot/resources/test/corpus/01_expr_unary.py b/crates/red_knot_workspace/resources/test/corpus/01_expr_unary.py similarity index 100% rename from crates/red_knot/resources/test/corpus/01_expr_unary.py rename to crates/red_knot_workspace/resources/test/corpus/01_expr_unary.py diff --git a/crates/red_knot/resources/test/corpus/02_expr_attr.py b/crates/red_knot_workspace/resources/test/corpus/02_expr_attr.py similarity index 100% rename from crates/red_knot/resources/test/corpus/02_expr_attr.py rename to crates/red_knot_workspace/resources/test/corpus/02_expr_attr.py diff --git a/crates/red_knot/resources/test/corpus/02_expr_attr_multiline.py b/crates/red_knot_workspace/resources/test/corpus/02_expr_attr_multiline.py similarity index 100% rename from crates/red_knot/resources/test/corpus/02_expr_attr_multiline.py rename to crates/red_knot_workspace/resources/test/corpus/02_expr_attr_multiline.py diff --git a/crates/red_knot/resources/test/corpus/02_expr_attr_multiline_assign.py b/crates/red_knot_workspace/resources/test/corpus/02_expr_attr_multiline_assign.py similarity index 100% rename from crates/red_knot/resources/test/corpus/02_expr_attr_multiline_assign.py rename to crates/red_knot_workspace/resources/test/corpus/02_expr_attr_multiline_assign.py diff --git a/crates/red_knot/resources/test/corpus/02_expr_bin_bool.py b/crates/red_knot_workspace/resources/test/corpus/02_expr_bin_bool.py similarity index 100% rename from crates/red_knot/resources/test/corpus/02_expr_bin_bool.py rename to crates/red_knot_workspace/resources/test/corpus/02_expr_bin_bool.py diff --git a/crates/red_knot/resources/test/corpus/02_expr_binary.py b/crates/red_knot_workspace/resources/test/corpus/02_expr_binary.py similarity index 100% rename from crates/red_knot/resources/test/corpus/02_expr_binary.py rename to crates/red_knot_workspace/resources/test/corpus/02_expr_binary.py diff --git a/crates/red_knot/resources/test/corpus/02_expr_bool_op_multiline.py b/crates/red_knot_workspace/resources/test/corpus/02_expr_bool_op_multiline.py similarity index 100% rename from crates/red_knot/resources/test/corpus/02_expr_bool_op_multiline.py rename to crates/red_knot_workspace/resources/test/corpus/02_expr_bool_op_multiline.py diff --git a/crates/red_knot/resources/test/corpus/02_expr_bool_op_multiline2.py b/crates/red_knot_workspace/resources/test/corpus/02_expr_bool_op_multiline2.py similarity index 100% rename from crates/red_knot/resources/test/corpus/02_expr_bool_op_multiline2.py rename to crates/red_knot_workspace/resources/test/corpus/02_expr_bool_op_multiline2.py diff --git a/crates/red_knot/resources/test/corpus/02_expr_rel.py b/crates/red_knot_workspace/resources/test/corpus/02_expr_rel.py similarity index 100% rename from crates/red_knot/resources/test/corpus/02_expr_rel.py rename to crates/red_knot_workspace/resources/test/corpus/02_expr_rel.py diff --git a/crates/red_knot/resources/test/corpus/02_expr_rel_multiple.py b/crates/red_knot_workspace/resources/test/corpus/02_expr_rel_multiple.py similarity index 100% rename from crates/red_knot/resources/test/corpus/02_expr_rel_multiple.py rename to crates/red_knot_workspace/resources/test/corpus/02_expr_rel_multiple.py diff --git a/crates/red_knot/resources/test/corpus/02_expr_subscr.py b/crates/red_knot_workspace/resources/test/corpus/02_expr_subscr.py similarity index 100% rename from crates/red_knot/resources/test/corpus/02_expr_subscr.py rename to crates/red_knot_workspace/resources/test/corpus/02_expr_subscr.py diff --git a/crates/red_knot/resources/test/corpus/03_dict.py b/crates/red_knot_workspace/resources/test/corpus/03_dict.py similarity index 100% rename from crates/red_knot/resources/test/corpus/03_dict.py rename to crates/red_knot_workspace/resources/test/corpus/03_dict.py diff --git a/crates/red_knot/resources/test/corpus/03_dict_ex.py b/crates/red_knot_workspace/resources/test/corpus/03_dict_ex.py similarity index 100% rename from crates/red_knot/resources/test/corpus/03_dict_ex.py rename to crates/red_knot_workspace/resources/test/corpus/03_dict_ex.py diff --git a/crates/red_knot/resources/test/corpus/03_dict_literal_large.py b/crates/red_knot_workspace/resources/test/corpus/03_dict_literal_large.py similarity index 100% rename from crates/red_knot/resources/test/corpus/03_dict_literal_large.py rename to crates/red_knot_workspace/resources/test/corpus/03_dict_literal_large.py diff --git a/crates/red_knot/resources/test/corpus/03_dict_unpack_huge.py b/crates/red_knot_workspace/resources/test/corpus/03_dict_unpack_huge.py similarity index 100% rename from crates/red_knot/resources/test/corpus/03_dict_unpack_huge.py rename to crates/red_knot_workspace/resources/test/corpus/03_dict_unpack_huge.py diff --git a/crates/red_knot/resources/test/corpus/03_list.py b/crates/red_knot_workspace/resources/test/corpus/03_list.py similarity index 100% rename from crates/red_knot/resources/test/corpus/03_list.py rename to crates/red_knot_workspace/resources/test/corpus/03_list.py diff --git a/crates/red_knot/resources/test/corpus/03_list_ex.py b/crates/red_knot_workspace/resources/test/corpus/03_list_ex.py similarity index 100% rename from crates/red_knot/resources/test/corpus/03_list_ex.py rename to crates/red_knot_workspace/resources/test/corpus/03_list_ex.py diff --git a/crates/red_knot/resources/test/corpus/03_list_large.py b/crates/red_knot_workspace/resources/test/corpus/03_list_large.py similarity index 100% rename from crates/red_knot/resources/test/corpus/03_list_large.py rename to crates/red_knot_workspace/resources/test/corpus/03_list_large.py diff --git a/crates/red_knot/resources/test/corpus/03_set.py b/crates/red_knot_workspace/resources/test/corpus/03_set.py similarity index 100% rename from crates/red_knot/resources/test/corpus/03_set.py rename to crates/red_knot_workspace/resources/test/corpus/03_set.py diff --git a/crates/red_knot/resources/test/corpus/03_set_multi.py b/crates/red_knot_workspace/resources/test/corpus/03_set_multi.py similarity index 100% rename from crates/red_knot/resources/test/corpus/03_set_multi.py rename to crates/red_knot_workspace/resources/test/corpus/03_set_multi.py diff --git a/crates/red_knot/resources/test/corpus/03_slice.py b/crates/red_knot_workspace/resources/test/corpus/03_slice.py similarity index 100% rename from crates/red_knot/resources/test/corpus/03_slice.py rename to crates/red_knot_workspace/resources/test/corpus/03_slice.py diff --git a/crates/red_knot/resources/test/corpus/03_slice_ext.py b/crates/red_knot_workspace/resources/test/corpus/03_slice_ext.py similarity index 100% rename from crates/red_knot/resources/test/corpus/03_slice_ext.py rename to crates/red_knot_workspace/resources/test/corpus/03_slice_ext.py diff --git a/crates/red_knot/resources/test/corpus/03_tuple.py b/crates/red_knot_workspace/resources/test/corpus/03_tuple.py similarity index 100% rename from crates/red_knot/resources/test/corpus/03_tuple.py rename to crates/red_knot_workspace/resources/test/corpus/03_tuple.py diff --git a/crates/red_knot/resources/test/corpus/03_tuple_ex.py b/crates/red_knot_workspace/resources/test/corpus/03_tuple_ex.py similarity index 100% rename from crates/red_knot/resources/test/corpus/03_tuple_ex.py rename to crates/red_knot_workspace/resources/test/corpus/03_tuple_ex.py diff --git a/crates/red_knot/resources/test/corpus/04_assign.py b/crates/red_knot_workspace/resources/test/corpus/04_assign.py similarity index 100% rename from crates/red_knot/resources/test/corpus/04_assign.py rename to crates/red_knot_workspace/resources/test/corpus/04_assign.py diff --git a/crates/red_knot/resources/test/corpus/04_assign_attr.py b/crates/red_knot_workspace/resources/test/corpus/04_assign_attr.py similarity index 100% rename from crates/red_knot/resources/test/corpus/04_assign_attr.py rename to crates/red_knot_workspace/resources/test/corpus/04_assign_attr.py diff --git a/crates/red_knot/resources/test/corpus/04_assign_attr_func.py b/crates/red_knot_workspace/resources/test/corpus/04_assign_attr_func.py similarity index 100% rename from crates/red_knot/resources/test/corpus/04_assign_attr_func.py rename to crates/red_knot_workspace/resources/test/corpus/04_assign_attr_func.py diff --git a/crates/red_knot/resources/test/corpus/04_assign_subscr.py b/crates/red_knot_workspace/resources/test/corpus/04_assign_subscr.py similarity index 100% rename from crates/red_knot/resources/test/corpus/04_assign_subscr.py rename to crates/red_knot_workspace/resources/test/corpus/04_assign_subscr.py diff --git a/crates/red_knot/resources/test/corpus/04_assign_unpack.py b/crates/red_knot_workspace/resources/test/corpus/04_assign_unpack.py similarity index 100% rename from crates/red_knot/resources/test/corpus/04_assign_unpack.py rename to crates/red_knot_workspace/resources/test/corpus/04_assign_unpack.py diff --git a/crates/red_knot/resources/test/corpus/04_assign_unpack_ex.py b/crates/red_knot_workspace/resources/test/corpus/04_assign_unpack_ex.py similarity index 100% rename from crates/red_knot/resources/test/corpus/04_assign_unpack_ex.py rename to crates/red_knot_workspace/resources/test/corpus/04_assign_unpack_ex.py diff --git a/crates/red_knot/resources/test/corpus/04_assign_unpack_tuple.py b/crates/red_knot_workspace/resources/test/corpus/04_assign_unpack_tuple.py similarity index 100% rename from crates/red_knot/resources/test/corpus/04_assign_unpack_tuple.py rename to crates/red_knot_workspace/resources/test/corpus/04_assign_unpack_tuple.py diff --git a/crates/red_knot/resources/test/corpus/04_aug_assign.py b/crates/red_knot_workspace/resources/test/corpus/04_aug_assign.py similarity index 100% rename from crates/red_knot/resources/test/corpus/04_aug_assign.py rename to crates/red_knot_workspace/resources/test/corpus/04_aug_assign.py diff --git a/crates/red_knot/resources/test/corpus/04_aug_assign_attr_multiline.py b/crates/red_knot_workspace/resources/test/corpus/04_aug_assign_attr_multiline.py similarity index 100% rename from crates/red_knot/resources/test/corpus/04_aug_assign_attr_multiline.py rename to crates/red_knot_workspace/resources/test/corpus/04_aug_assign_attr_multiline.py diff --git a/crates/red_knot/resources/test/corpus/04_aug_assign_attr_sub.py b/crates/red_knot_workspace/resources/test/corpus/04_aug_assign_attr_sub.py similarity index 100% rename from crates/red_knot/resources/test/corpus/04_aug_assign_attr_sub.py rename to crates/red_knot_workspace/resources/test/corpus/04_aug_assign_attr_sub.py diff --git a/crates/red_knot/resources/test/corpus/05_funcall.py b/crates/red_knot_workspace/resources/test/corpus/05_funcall.py similarity index 100% rename from crates/red_knot/resources/test/corpus/05_funcall.py rename to crates/red_knot_workspace/resources/test/corpus/05_funcall.py diff --git a/crates/red_knot/resources/test/corpus/05_funcall_1.py b/crates/red_knot_workspace/resources/test/corpus/05_funcall_1.py similarity index 100% rename from crates/red_knot/resources/test/corpus/05_funcall_1.py rename to crates/red_knot_workspace/resources/test/corpus/05_funcall_1.py diff --git a/crates/red_knot/resources/test/corpus/05_funcall_2.py b/crates/red_knot_workspace/resources/test/corpus/05_funcall_2.py similarity index 100% rename from crates/red_knot/resources/test/corpus/05_funcall_2.py rename to crates/red_knot_workspace/resources/test/corpus/05_funcall_2.py diff --git a/crates/red_knot/resources/test/corpus/05_funcall_in_multiline_tuple.py b/crates/red_knot_workspace/resources/test/corpus/05_funcall_in_multiline_tuple.py similarity index 100% rename from crates/red_knot/resources/test/corpus/05_funcall_in_multiline_tuple.py rename to crates/red_knot_workspace/resources/test/corpus/05_funcall_in_multiline_tuple.py diff --git a/crates/red_knot/resources/test/corpus/05_funcall_kw.py b/crates/red_knot_workspace/resources/test/corpus/05_funcall_kw.py similarity index 100% rename from crates/red_knot/resources/test/corpus/05_funcall_kw.py rename to crates/red_knot_workspace/resources/test/corpus/05_funcall_kw.py diff --git a/crates/red_knot/resources/test/corpus/05_funcall_kw_many.py b/crates/red_knot_workspace/resources/test/corpus/05_funcall_kw_many.py similarity index 100% rename from crates/red_knot/resources/test/corpus/05_funcall_kw_many.py rename to crates/red_knot_workspace/resources/test/corpus/05_funcall_kw_many.py diff --git a/crates/red_knot/resources/test/corpus/05_funcall_kw_pos.py b/crates/red_knot_workspace/resources/test/corpus/05_funcall_kw_pos.py similarity index 100% rename from crates/red_knot/resources/test/corpus/05_funcall_kw_pos.py rename to crates/red_knot_workspace/resources/test/corpus/05_funcall_kw_pos.py diff --git a/crates/red_knot/resources/test/corpus/05_funcall_method_multiline.py b/crates/red_knot_workspace/resources/test/corpus/05_funcall_method_multiline.py similarity index 100% rename from crates/red_knot/resources/test/corpus/05_funcall_method_multiline.py rename to crates/red_knot_workspace/resources/test/corpus/05_funcall_method_multiline.py diff --git a/crates/red_knot/resources/test/corpus/06_funcall_kwargs.py b/crates/red_knot_workspace/resources/test/corpus/06_funcall_kwargs.py similarity index 100% rename from crates/red_knot/resources/test/corpus/06_funcall_kwargs.py rename to crates/red_knot_workspace/resources/test/corpus/06_funcall_kwargs.py diff --git a/crates/red_knot/resources/test/corpus/06_funcall_many_args.py b/crates/red_knot_workspace/resources/test/corpus/06_funcall_many_args.py similarity index 100% rename from crates/red_knot/resources/test/corpus/06_funcall_many_args.py rename to crates/red_knot_workspace/resources/test/corpus/06_funcall_many_args.py diff --git a/crates/red_knot/resources/test/corpus/06_funcall_starargs_ex.py b/crates/red_knot_workspace/resources/test/corpus/06_funcall_starargs_ex.py similarity index 100% rename from crates/red_knot/resources/test/corpus/06_funcall_starargs_ex.py rename to crates/red_knot_workspace/resources/test/corpus/06_funcall_starargs_ex.py diff --git a/crates/red_knot/resources/test/corpus/06_funcall_varargs.py b/crates/red_knot_workspace/resources/test/corpus/06_funcall_varargs.py similarity index 100% rename from crates/red_knot/resources/test/corpus/06_funcall_varargs.py rename to crates/red_knot_workspace/resources/test/corpus/06_funcall_varargs.py diff --git a/crates/red_knot/resources/test/corpus/06_funcall_varargs_kwargs.py b/crates/red_knot_workspace/resources/test/corpus/06_funcall_varargs_kwargs.py similarity index 100% rename from crates/red_knot/resources/test/corpus/06_funcall_varargs_kwargs.py rename to crates/red_knot_workspace/resources/test/corpus/06_funcall_varargs_kwargs.py diff --git a/crates/red_knot/resources/test/corpus/06_funcall_varargs_kwargs_mixed.py b/crates/red_knot_workspace/resources/test/corpus/06_funcall_varargs_kwargs_mixed.py similarity index 100% rename from crates/red_knot/resources/test/corpus/06_funcall_varargs_kwargs_mixed.py rename to crates/red_knot_workspace/resources/test/corpus/06_funcall_varargs_kwargs_mixed.py diff --git a/crates/red_knot/resources/test/corpus/07_ifexpr.py b/crates/red_knot_workspace/resources/test/corpus/07_ifexpr.py similarity index 100% rename from crates/red_knot/resources/test/corpus/07_ifexpr.py rename to crates/red_knot_workspace/resources/test/corpus/07_ifexpr.py diff --git a/crates/red_knot/resources/test/corpus/07_ifexpr_multiline.py b/crates/red_knot_workspace/resources/test/corpus/07_ifexpr_multiline.py similarity index 100% rename from crates/red_knot/resources/test/corpus/07_ifexpr_multiline.py rename to crates/red_knot_workspace/resources/test/corpus/07_ifexpr_multiline.py diff --git a/crates/red_knot/resources/test/corpus/07_ifexpr_multiline2.py b/crates/red_knot_workspace/resources/test/corpus/07_ifexpr_multiline2.py similarity index 100% rename from crates/red_knot/resources/test/corpus/07_ifexpr_multiline2.py rename to crates/red_knot_workspace/resources/test/corpus/07_ifexpr_multiline2.py diff --git a/crates/red_knot/resources/test/corpus/08_del.py b/crates/red_knot_workspace/resources/test/corpus/08_del.py similarity index 100% rename from crates/red_knot/resources/test/corpus/08_del.py rename to crates/red_knot_workspace/resources/test/corpus/08_del.py diff --git a/crates/red_knot/resources/test/corpus/08_del_multi.py b/crates/red_knot_workspace/resources/test/corpus/08_del_multi.py similarity index 100% rename from crates/red_knot/resources/test/corpus/08_del_multi.py rename to crates/red_knot_workspace/resources/test/corpus/08_del_multi.py diff --git a/crates/red_knot/resources/test/corpus/09_pass.py b/crates/red_knot_workspace/resources/test/corpus/09_pass.py similarity index 100% rename from crates/red_knot/resources/test/corpus/09_pass.py rename to crates/red_knot_workspace/resources/test/corpus/09_pass.py diff --git a/crates/red_knot/resources/test/corpus/10_if.py b/crates/red_knot_workspace/resources/test/corpus/10_if.py similarity index 100% rename from crates/red_knot/resources/test/corpus/10_if.py rename to crates/red_knot_workspace/resources/test/corpus/10_if.py diff --git a/crates/red_knot/resources/test/corpus/10_if_chained_compare.py b/crates/red_knot_workspace/resources/test/corpus/10_if_chained_compare.py similarity index 100% rename from crates/red_knot/resources/test/corpus/10_if_chained_compare.py rename to crates/red_knot_workspace/resources/test/corpus/10_if_chained_compare.py diff --git a/crates/red_knot/resources/test/corpus/10_if_false.py b/crates/red_knot_workspace/resources/test/corpus/10_if_false.py similarity index 100% rename from crates/red_knot/resources/test/corpus/10_if_false.py rename to crates/red_knot_workspace/resources/test/corpus/10_if_false.py diff --git a/crates/red_knot/resources/test/corpus/10_if_true.py b/crates/red_knot_workspace/resources/test/corpus/10_if_true.py similarity index 100% rename from crates/red_knot/resources/test/corpus/10_if_true.py rename to crates/red_knot_workspace/resources/test/corpus/10_if_true.py diff --git a/crates/red_knot/resources/test/corpus/11_if_else.py b/crates/red_knot_workspace/resources/test/corpus/11_if_else.py similarity index 100% rename from crates/red_knot/resources/test/corpus/11_if_else.py rename to crates/red_knot_workspace/resources/test/corpus/11_if_else.py diff --git a/crates/red_knot/resources/test/corpus/11_if_else_deeply_nested_for.py b/crates/red_knot_workspace/resources/test/corpus/11_if_else_deeply_nested_for.py similarity index 100% rename from crates/red_knot/resources/test/corpus/11_if_else_deeply_nested_for.py rename to crates/red_knot_workspace/resources/test/corpus/11_if_else_deeply_nested_for.py diff --git a/crates/red_knot/resources/test/corpus/11_if_else_false.py b/crates/red_knot_workspace/resources/test/corpus/11_if_else_false.py similarity index 100% rename from crates/red_knot/resources/test/corpus/11_if_else_false.py rename to crates/red_knot_workspace/resources/test/corpus/11_if_else_false.py diff --git a/crates/red_knot/resources/test/corpus/11_if_else_true.py b/crates/red_knot_workspace/resources/test/corpus/11_if_else_true.py similarity index 100% rename from crates/red_knot/resources/test/corpus/11_if_else_true.py rename to crates/red_knot_workspace/resources/test/corpus/11_if_else_true.py diff --git a/crates/red_knot/resources/test/corpus/12_if_elif.py b/crates/red_knot_workspace/resources/test/corpus/12_if_elif.py similarity index 100% rename from crates/red_knot/resources/test/corpus/12_if_elif.py rename to crates/red_knot_workspace/resources/test/corpus/12_if_elif.py diff --git a/crates/red_knot/resources/test/corpus/12_if_elif_else.py b/crates/red_knot_workspace/resources/test/corpus/12_if_elif_else.py similarity index 100% rename from crates/red_knot/resources/test/corpus/12_if_elif_else.py rename to crates/red_knot_workspace/resources/test/corpus/12_if_elif_else.py diff --git a/crates/red_knot/resources/test/corpus/13_ifelse_complex1.py b/crates/red_knot_workspace/resources/test/corpus/13_ifelse_complex1.py similarity index 100% rename from crates/red_knot/resources/test/corpus/13_ifelse_complex1.py rename to crates/red_knot_workspace/resources/test/corpus/13_ifelse_complex1.py diff --git a/crates/red_knot/resources/test/corpus/13_ifelse_many.py b/crates/red_knot_workspace/resources/test/corpus/13_ifelse_many.py similarity index 100% rename from crates/red_knot/resources/test/corpus/13_ifelse_many.py rename to crates/red_knot_workspace/resources/test/corpus/13_ifelse_many.py diff --git a/crates/red_knot/resources/test/corpus/15_while.py b/crates/red_knot_workspace/resources/test/corpus/15_while.py similarity index 100% rename from crates/red_knot/resources/test/corpus/15_while.py rename to crates/red_knot_workspace/resources/test/corpus/15_while.py diff --git a/crates/red_knot/resources/test/corpus/15_while_break.py b/crates/red_knot_workspace/resources/test/corpus/15_while_break.py similarity index 100% rename from crates/red_knot/resources/test/corpus/15_while_break.py rename to crates/red_knot_workspace/resources/test/corpus/15_while_break.py diff --git a/crates/red_knot/resources/test/corpus/15_while_break_in_finally.py b/crates/red_knot_workspace/resources/test/corpus/15_while_break_in_finally.py similarity index 100% rename from crates/red_knot/resources/test/corpus/15_while_break_in_finally.py rename to crates/red_knot_workspace/resources/test/corpus/15_while_break_in_finally.py diff --git a/crates/red_knot/resources/test/corpus/15_while_break_non_empty.py b/crates/red_knot_workspace/resources/test/corpus/15_while_break_non_empty.py similarity index 100% rename from crates/red_knot/resources/test/corpus/15_while_break_non_empty.py rename to crates/red_knot_workspace/resources/test/corpus/15_while_break_non_empty.py diff --git a/crates/red_knot/resources/test/corpus/15_while_break_non_exit.py b/crates/red_knot_workspace/resources/test/corpus/15_while_break_non_exit.py similarity index 100% rename from crates/red_knot/resources/test/corpus/15_while_break_non_exit.py rename to crates/red_knot_workspace/resources/test/corpus/15_while_break_non_exit.py diff --git a/crates/red_knot/resources/test/corpus/15_while_continue.py b/crates/red_knot_workspace/resources/test/corpus/15_while_continue.py similarity index 100% rename from crates/red_knot/resources/test/corpus/15_while_continue.py rename to crates/red_knot_workspace/resources/test/corpus/15_while_continue.py diff --git a/crates/red_knot/resources/test/corpus/15_while_false.py b/crates/red_knot_workspace/resources/test/corpus/15_while_false.py similarity index 100% rename from crates/red_knot/resources/test/corpus/15_while_false.py rename to crates/red_knot_workspace/resources/test/corpus/15_while_false.py diff --git a/crates/red_knot/resources/test/corpus/15_while_infinite.py b/crates/red_knot_workspace/resources/test/corpus/15_while_infinite.py similarity index 100% rename from crates/red_knot/resources/test/corpus/15_while_infinite.py rename to crates/red_knot_workspace/resources/test/corpus/15_while_infinite.py diff --git a/crates/red_knot/resources/test/corpus/15_while_true.py b/crates/red_knot_workspace/resources/test/corpus/15_while_true.py similarity index 100% rename from crates/red_knot/resources/test/corpus/15_while_true.py rename to crates/red_knot_workspace/resources/test/corpus/15_while_true.py diff --git a/crates/red_knot/resources/test/corpus/16_for.py b/crates/red_knot_workspace/resources/test/corpus/16_for.py similarity index 100% rename from crates/red_knot/resources/test/corpus/16_for.py rename to crates/red_knot_workspace/resources/test/corpus/16_for.py diff --git a/crates/red_knot/resources/test/corpus/16_for_break.py b/crates/red_knot_workspace/resources/test/corpus/16_for_break.py similarity index 100% rename from crates/red_knot/resources/test/corpus/16_for_break.py rename to crates/red_knot_workspace/resources/test/corpus/16_for_break.py diff --git a/crates/red_knot/resources/test/corpus/16_for_continue.py b/crates/red_knot_workspace/resources/test/corpus/16_for_continue.py similarity index 100% rename from crates/red_knot/resources/test/corpus/16_for_continue.py rename to crates/red_knot_workspace/resources/test/corpus/16_for_continue.py diff --git a/crates/red_knot/resources/test/corpus/16_for_else.py b/crates/red_knot_workspace/resources/test/corpus/16_for_else.py similarity index 100% rename from crates/red_knot/resources/test/corpus/16_for_else.py rename to crates/red_knot_workspace/resources/test/corpus/16_for_else.py diff --git a/crates/red_knot/resources/test/corpus/16_for_list_literal.py b/crates/red_knot_workspace/resources/test/corpus/16_for_list_literal.py similarity index 100% rename from crates/red_knot/resources/test/corpus/16_for_list_literal.py rename to crates/red_knot_workspace/resources/test/corpus/16_for_list_literal.py diff --git a/crates/red_knot/resources/test/corpus/16_for_nested_ifs.py b/crates/red_knot_workspace/resources/test/corpus/16_for_nested_ifs.py similarity index 100% rename from crates/red_knot/resources/test/corpus/16_for_nested_ifs.py rename to crates/red_knot_workspace/resources/test/corpus/16_for_nested_ifs.py diff --git a/crates/red_knot/resources/test/corpus/20_lambda.py b/crates/red_knot_workspace/resources/test/corpus/20_lambda.py similarity index 100% rename from crates/red_knot/resources/test/corpus/20_lambda.py rename to crates/red_knot_workspace/resources/test/corpus/20_lambda.py diff --git a/crates/red_knot/resources/test/corpus/20_lambda_const.py b/crates/red_knot_workspace/resources/test/corpus/20_lambda_const.py similarity index 100% rename from crates/red_knot/resources/test/corpus/20_lambda_const.py rename to crates/red_knot_workspace/resources/test/corpus/20_lambda_const.py diff --git a/crates/red_knot/resources/test/corpus/20_lambda_default_arg.py b/crates/red_knot_workspace/resources/test/corpus/20_lambda_default_arg.py similarity index 100% rename from crates/red_knot/resources/test/corpus/20_lambda_default_arg.py rename to crates/red_knot_workspace/resources/test/corpus/20_lambda_default_arg.py diff --git a/crates/red_knot/resources/test/corpus/20_lambda_ifelse.py b/crates/red_knot_workspace/resources/test/corpus/20_lambda_ifelse.py similarity index 100% rename from crates/red_knot/resources/test/corpus/20_lambda_ifelse.py rename to crates/red_knot_workspace/resources/test/corpus/20_lambda_ifelse.py diff --git a/crates/red_knot/resources/test/corpus/21_func1.py b/crates/red_knot_workspace/resources/test/corpus/21_func1.py similarity index 100% rename from crates/red_knot/resources/test/corpus/21_func1.py rename to crates/red_knot_workspace/resources/test/corpus/21_func1.py diff --git a/crates/red_knot/resources/test/corpus/21_func1_ret.py b/crates/red_knot_workspace/resources/test/corpus/21_func1_ret.py similarity index 100% rename from crates/red_knot/resources/test/corpus/21_func1_ret.py rename to crates/red_knot_workspace/resources/test/corpus/21_func1_ret.py diff --git a/crates/red_knot/resources/test/corpus/21_func_assign.py b/crates/red_knot_workspace/resources/test/corpus/21_func_assign.py similarity index 100% rename from crates/red_knot/resources/test/corpus/21_func_assign.py rename to crates/red_knot_workspace/resources/test/corpus/21_func_assign.py diff --git a/crates/red_knot/resources/test/corpus/21_func_assign2.py b/crates/red_knot_workspace/resources/test/corpus/21_func_assign2.py similarity index 100% rename from crates/red_knot/resources/test/corpus/21_func_assign2.py rename to crates/red_knot_workspace/resources/test/corpus/21_func_assign2.py diff --git a/crates/red_knot/resources/test/corpus/22_func_arg.py b/crates/red_knot_workspace/resources/test/corpus/22_func_arg.py similarity index 100% rename from crates/red_knot/resources/test/corpus/22_func_arg.py rename to crates/red_knot_workspace/resources/test/corpus/22_func_arg.py diff --git a/crates/red_knot/resources/test/corpus/22_func_vararg.py b/crates/red_knot_workspace/resources/test/corpus/22_func_vararg.py similarity index 100% rename from crates/red_knot/resources/test/corpus/22_func_vararg.py rename to crates/red_knot_workspace/resources/test/corpus/22_func_vararg.py diff --git a/crates/red_knot/resources/test/corpus/23_func_ret.py b/crates/red_knot_workspace/resources/test/corpus/23_func_ret.py similarity index 100% rename from crates/red_knot/resources/test/corpus/23_func_ret.py rename to crates/red_knot_workspace/resources/test/corpus/23_func_ret.py diff --git a/crates/red_knot/resources/test/corpus/23_func_ret_val.py b/crates/red_knot_workspace/resources/test/corpus/23_func_ret_val.py similarity index 100% rename from crates/red_knot/resources/test/corpus/23_func_ret_val.py rename to crates/red_knot_workspace/resources/test/corpus/23_func_ret_val.py diff --git a/crates/red_knot/resources/test/corpus/24_func_if_ret.py b/crates/red_knot_workspace/resources/test/corpus/24_func_if_ret.py similarity index 100% rename from crates/red_knot/resources/test/corpus/24_func_if_ret.py rename to crates/red_knot_workspace/resources/test/corpus/24_func_if_ret.py diff --git a/crates/red_knot/resources/test/corpus/24_func_ifelse_ret.py b/crates/red_knot_workspace/resources/test/corpus/24_func_ifelse_ret.py similarity index 100% rename from crates/red_knot/resources/test/corpus/24_func_ifelse_ret.py rename to crates/red_knot_workspace/resources/test/corpus/24_func_ifelse_ret.py diff --git a/crates/red_knot/resources/test/corpus/24_func_ifnot_ret.py b/crates/red_knot_workspace/resources/test/corpus/24_func_ifnot_ret.py similarity index 100% rename from crates/red_knot/resources/test/corpus/24_func_ifnot_ret.py rename to crates/red_knot_workspace/resources/test/corpus/24_func_ifnot_ret.py diff --git a/crates/red_knot/resources/test/corpus/25_func_annotations.py b/crates/red_knot_workspace/resources/test/corpus/25_func_annotations.py similarity index 100% rename from crates/red_knot/resources/test/corpus/25_func_annotations.py rename to crates/red_knot_workspace/resources/test/corpus/25_func_annotations.py diff --git a/crates/red_knot/resources/test/corpus/25_func_annotations_nested.py b/crates/red_knot_workspace/resources/test/corpus/25_func_annotations_nested.py similarity index 100% rename from crates/red_knot/resources/test/corpus/25_func_annotations_nested.py rename to crates/red_knot_workspace/resources/test/corpus/25_func_annotations_nested.py diff --git a/crates/red_knot/resources/test/corpus/25_func_annotations_scope.py b/crates/red_knot_workspace/resources/test/corpus/25_func_annotations_scope.py similarity index 100% rename from crates/red_knot/resources/test/corpus/25_func_annotations_scope.py rename to crates/red_knot_workspace/resources/test/corpus/25_func_annotations_scope.py diff --git a/crates/red_knot/resources/test/corpus/26_func_const_defaults.py b/crates/red_knot_workspace/resources/test/corpus/26_func_const_defaults.py similarity index 100% rename from crates/red_knot/resources/test/corpus/26_func_const_defaults.py rename to crates/red_knot_workspace/resources/test/corpus/26_func_const_defaults.py diff --git a/crates/red_knot/resources/test/corpus/27_func_generic.py b/crates/red_knot_workspace/resources/test/corpus/27_func_generic.py similarity index 100% rename from crates/red_knot/resources/test/corpus/27_func_generic.py rename to crates/red_knot_workspace/resources/test/corpus/27_func_generic.py diff --git a/crates/red_knot/resources/test/corpus/27_func_generic_bound.py b/crates/red_knot_workspace/resources/test/corpus/27_func_generic_bound.py similarity index 100% rename from crates/red_knot/resources/test/corpus/27_func_generic_bound.py rename to crates/red_knot_workspace/resources/test/corpus/27_func_generic_bound.py diff --git a/crates/red_knot/resources/test/corpus/27_func_generic_constraint.py b/crates/red_knot_workspace/resources/test/corpus/27_func_generic_constraint.py similarity index 100% rename from crates/red_knot/resources/test/corpus/27_func_generic_constraint.py rename to crates/red_knot_workspace/resources/test/corpus/27_func_generic_constraint.py diff --git a/crates/red_knot/resources/test/corpus/27_func_generic_default.py b/crates/red_knot_workspace/resources/test/corpus/27_func_generic_default.py similarity index 100% rename from crates/red_knot/resources/test/corpus/27_func_generic_default.py rename to crates/red_knot_workspace/resources/test/corpus/27_func_generic_default.py diff --git a/crates/red_knot/resources/test/corpus/27_func_generic_paramspec.py b/crates/red_knot_workspace/resources/test/corpus/27_func_generic_paramspec.py similarity index 100% rename from crates/red_knot/resources/test/corpus/27_func_generic_paramspec.py rename to crates/red_knot_workspace/resources/test/corpus/27_func_generic_paramspec.py diff --git a/crates/red_knot/resources/test/corpus/27_func_generic_paramspec_default.py b/crates/red_knot_workspace/resources/test/corpus/27_func_generic_paramspec_default.py similarity index 100% rename from crates/red_knot/resources/test/corpus/27_func_generic_paramspec_default.py rename to crates/red_knot_workspace/resources/test/corpus/27_func_generic_paramspec_default.py diff --git a/crates/red_knot/resources/test/corpus/27_func_generic_tuple.py b/crates/red_knot_workspace/resources/test/corpus/27_func_generic_tuple.py similarity index 100% rename from crates/red_knot/resources/test/corpus/27_func_generic_tuple.py rename to crates/red_knot_workspace/resources/test/corpus/27_func_generic_tuple.py diff --git a/crates/red_knot/resources/test/corpus/27_func_generic_tuple_default.py b/crates/red_knot_workspace/resources/test/corpus/27_func_generic_tuple_default.py similarity index 100% rename from crates/red_knot/resources/test/corpus/27_func_generic_tuple_default.py rename to crates/red_knot_workspace/resources/test/corpus/27_func_generic_tuple_default.py diff --git a/crates/red_knot/resources/test/corpus/30_func_enclosed.py b/crates/red_knot_workspace/resources/test/corpus/30_func_enclosed.py similarity index 100% rename from crates/red_knot/resources/test/corpus/30_func_enclosed.py rename to crates/red_knot_workspace/resources/test/corpus/30_func_enclosed.py diff --git a/crates/red_knot/resources/test/corpus/30_func_enclosed_many.py b/crates/red_knot_workspace/resources/test/corpus/30_func_enclosed_many.py similarity index 100% rename from crates/red_knot/resources/test/corpus/30_func_enclosed_many.py rename to crates/red_knot_workspace/resources/test/corpus/30_func_enclosed_many.py diff --git a/crates/red_knot/resources/test/corpus/31_func_global.py b/crates/red_knot_workspace/resources/test/corpus/31_func_global.py similarity index 100% rename from crates/red_knot/resources/test/corpus/31_func_global.py rename to crates/red_knot_workspace/resources/test/corpus/31_func_global.py diff --git a/crates/red_knot/resources/test/corpus/31_func_global_annotated_later.py b/crates/red_knot_workspace/resources/test/corpus/31_func_global_annotated_later.py similarity index 100% rename from crates/red_knot/resources/test/corpus/31_func_global_annotated_later.py rename to crates/red_knot_workspace/resources/test/corpus/31_func_global_annotated_later.py diff --git a/crates/red_knot/resources/test/corpus/31_func_nonlocal.py b/crates/red_knot_workspace/resources/test/corpus/31_func_nonlocal.py similarity index 100% rename from crates/red_knot/resources/test/corpus/31_func_nonlocal.py rename to crates/red_knot_workspace/resources/test/corpus/31_func_nonlocal.py diff --git a/crates/red_knot/resources/test/corpus/32_func_global_nested.py b/crates/red_knot_workspace/resources/test/corpus/32_func_global_nested.py similarity index 100% rename from crates/red_knot/resources/test/corpus/32_func_global_nested.py rename to crates/red_knot_workspace/resources/test/corpus/32_func_global_nested.py diff --git a/crates/red_knot/resources/test/corpus/33_func_with_docstring_optimizable_tuple_and_return.py b/crates/red_knot_workspace/resources/test/corpus/33_func_with_docstring_optimizable_tuple_and_return.py similarity index 100% rename from crates/red_knot/resources/test/corpus/33_func_with_docstring_optimizable_tuple_and_return.py rename to crates/red_knot_workspace/resources/test/corpus/33_func_with_docstring_optimizable_tuple_and_return.py diff --git a/crates/red_knot/resources/test/corpus/40_import.py b/crates/red_knot_workspace/resources/test/corpus/40_import.py similarity index 100% rename from crates/red_knot/resources/test/corpus/40_import.py rename to crates/red_knot_workspace/resources/test/corpus/40_import.py diff --git a/crates/red_knot/resources/test/corpus/41_from_import.py b/crates/red_knot_workspace/resources/test/corpus/41_from_import.py similarity index 100% rename from crates/red_knot/resources/test/corpus/41_from_import.py rename to crates/red_knot_workspace/resources/test/corpus/41_from_import.py diff --git a/crates/red_knot/resources/test/corpus/42_import_from_dot.py b/crates/red_knot_workspace/resources/test/corpus/42_import_from_dot.py similarity index 100% rename from crates/red_knot/resources/test/corpus/42_import_from_dot.py rename to crates/red_knot_workspace/resources/test/corpus/42_import_from_dot.py diff --git a/crates/red_knot/resources/test/corpus/50_yield.py b/crates/red_knot_workspace/resources/test/corpus/50_yield.py similarity index 100% rename from crates/red_knot/resources/test/corpus/50_yield.py rename to crates/red_knot_workspace/resources/test/corpus/50_yield.py diff --git a/crates/red_knot/resources/test/corpus/51_gen_comp.py b/crates/red_knot_workspace/resources/test/corpus/51_gen_comp.py similarity index 100% rename from crates/red_knot/resources/test/corpus/51_gen_comp.py rename to crates/red_knot_workspace/resources/test/corpus/51_gen_comp.py diff --git a/crates/red_knot/resources/test/corpus/51_gen_comp2.py b/crates/red_knot_workspace/resources/test/corpus/51_gen_comp2.py similarity index 100% rename from crates/red_knot/resources/test/corpus/51_gen_comp2.py rename to crates/red_knot_workspace/resources/test/corpus/51_gen_comp2.py diff --git a/crates/red_knot/resources/test/corpus/52_gen_comp_if.py b/crates/red_knot_workspace/resources/test/corpus/52_gen_comp_if.py similarity index 100% rename from crates/red_knot/resources/test/corpus/52_gen_comp_if.py rename to crates/red_knot_workspace/resources/test/corpus/52_gen_comp_if.py diff --git a/crates/red_knot/resources/test/corpus/53_dict_comp.py b/crates/red_knot_workspace/resources/test/corpus/53_dict_comp.py similarity index 100% rename from crates/red_knot/resources/test/corpus/53_dict_comp.py rename to crates/red_knot_workspace/resources/test/corpus/53_dict_comp.py diff --git a/crates/red_knot/resources/test/corpus/53_list_comp.py b/crates/red_knot_workspace/resources/test/corpus/53_list_comp.py similarity index 100% rename from crates/red_knot/resources/test/corpus/53_list_comp.py rename to crates/red_knot_workspace/resources/test/corpus/53_list_comp.py diff --git a/crates/red_knot/resources/test/corpus/53_list_comp_method.py b/crates/red_knot_workspace/resources/test/corpus/53_list_comp_method.py similarity index 100% rename from crates/red_knot/resources/test/corpus/53_list_comp_method.py rename to crates/red_knot_workspace/resources/test/corpus/53_list_comp_method.py diff --git a/crates/red_knot/resources/test/corpus/53_set_comp.py b/crates/red_knot_workspace/resources/test/corpus/53_set_comp.py similarity index 100% rename from crates/red_knot/resources/test/corpus/53_set_comp.py rename to crates/red_knot_workspace/resources/test/corpus/53_set_comp.py diff --git a/crates/red_knot/resources/test/corpus/54_list_comp_func.py b/crates/red_knot_workspace/resources/test/corpus/54_list_comp_func.py similarity index 100% rename from crates/red_knot/resources/test/corpus/54_list_comp_func.py rename to crates/red_knot_workspace/resources/test/corpus/54_list_comp_func.py diff --git a/crates/red_knot/resources/test/corpus/54_list_comp_lambda.py b/crates/red_knot_workspace/resources/test/corpus/54_list_comp_lambda.py similarity index 100% rename from crates/red_knot/resources/test/corpus/54_list_comp_lambda.py rename to crates/red_knot_workspace/resources/test/corpus/54_list_comp_lambda.py diff --git a/crates/red_knot/resources/test/corpus/54_list_comp_lambda_listcomp.py b/crates/red_knot_workspace/resources/test/corpus/54_list_comp_lambda_listcomp.py similarity index 100% rename from crates/red_knot/resources/test/corpus/54_list_comp_lambda_listcomp.py rename to crates/red_knot_workspace/resources/test/corpus/54_list_comp_lambda_listcomp.py diff --git a/crates/red_knot/resources/test/corpus/54_list_comp_recur_func.py b/crates/red_knot_workspace/resources/test/corpus/54_list_comp_recur_func.py similarity index 100% rename from crates/red_knot/resources/test/corpus/54_list_comp_recur_func.py rename to crates/red_knot_workspace/resources/test/corpus/54_list_comp_recur_func.py diff --git a/crates/red_knot/resources/test/corpus/55_list_comp_nested.py b/crates/red_knot_workspace/resources/test/corpus/55_list_comp_nested.py similarity index 100% rename from crates/red_knot/resources/test/corpus/55_list_comp_nested.py rename to crates/red_knot_workspace/resources/test/corpus/55_list_comp_nested.py diff --git a/crates/red_knot/resources/test/corpus/56_yield_from.py b/crates/red_knot_workspace/resources/test/corpus/56_yield_from.py similarity index 100% rename from crates/red_knot/resources/test/corpus/56_yield_from.py rename to crates/red_knot_workspace/resources/test/corpus/56_yield_from.py diff --git a/crates/red_knot/resources/test/corpus/57_await.py b/crates/red_knot_workspace/resources/test/corpus/57_await.py similarity index 100% rename from crates/red_knot/resources/test/corpus/57_await.py rename to crates/red_knot_workspace/resources/test/corpus/57_await.py diff --git a/crates/red_knot/resources/test/corpus/58_async_for.py b/crates/red_knot_workspace/resources/test/corpus/58_async_for.py similarity index 100% rename from crates/red_knot/resources/test/corpus/58_async_for.py rename to crates/red_knot_workspace/resources/test/corpus/58_async_for.py diff --git a/crates/red_knot/resources/test/corpus/58_async_for_break.py b/crates/red_knot_workspace/resources/test/corpus/58_async_for_break.py similarity index 100% rename from crates/red_knot/resources/test/corpus/58_async_for_break.py rename to crates/red_knot_workspace/resources/test/corpus/58_async_for_break.py diff --git a/crates/red_knot/resources/test/corpus/58_async_for_continue.py b/crates/red_knot_workspace/resources/test/corpus/58_async_for_continue.py similarity index 100% rename from crates/red_knot/resources/test/corpus/58_async_for_continue.py rename to crates/red_knot_workspace/resources/test/corpus/58_async_for_continue.py diff --git a/crates/red_knot/resources/test/corpus/58_async_for_dict_comp.py b/crates/red_knot_workspace/resources/test/corpus/58_async_for_dict_comp.py similarity index 100% rename from crates/red_knot/resources/test/corpus/58_async_for_dict_comp.py rename to crates/red_knot_workspace/resources/test/corpus/58_async_for_dict_comp.py diff --git a/crates/red_knot/resources/test/corpus/58_async_for_else.py b/crates/red_knot_workspace/resources/test/corpus/58_async_for_else.py similarity index 100% rename from crates/red_knot/resources/test/corpus/58_async_for_else.py rename to crates/red_knot_workspace/resources/test/corpus/58_async_for_else.py diff --git a/crates/red_knot/resources/test/corpus/58_async_for_gen_comp.py b/crates/red_knot_workspace/resources/test/corpus/58_async_for_gen_comp.py similarity index 100% rename from crates/red_knot/resources/test/corpus/58_async_for_gen_comp.py rename to crates/red_knot_workspace/resources/test/corpus/58_async_for_gen_comp.py diff --git a/crates/red_knot/resources/test/corpus/58_async_for_list_comp.py b/crates/red_knot_workspace/resources/test/corpus/58_async_for_list_comp.py similarity index 100% rename from crates/red_knot/resources/test/corpus/58_async_for_list_comp.py rename to crates/red_knot_workspace/resources/test/corpus/58_async_for_list_comp.py diff --git a/crates/red_knot/resources/test/corpus/58_async_for_set_comp.py b/crates/red_knot_workspace/resources/test/corpus/58_async_for_set_comp.py similarity index 100% rename from crates/red_knot/resources/test/corpus/58_async_for_set_comp.py rename to crates/red_knot_workspace/resources/test/corpus/58_async_for_set_comp.py diff --git a/crates/red_knot/resources/test/corpus/59_async_with.py b/crates/red_knot_workspace/resources/test/corpus/59_async_with.py similarity index 100% rename from crates/red_knot/resources/test/corpus/59_async_with.py rename to crates/red_knot_workspace/resources/test/corpus/59_async_with.py diff --git a/crates/red_knot/resources/test/corpus/59_async_with_nested_with.py b/crates/red_knot_workspace/resources/test/corpus/59_async_with_nested_with.py similarity index 100% rename from crates/red_knot/resources/test/corpus/59_async_with_nested_with.py rename to crates/red_knot_workspace/resources/test/corpus/59_async_with_nested_with.py diff --git a/crates/red_knot/resources/test/corpus/60_try_except.py b/crates/red_knot_workspace/resources/test/corpus/60_try_except.py similarity index 100% rename from crates/red_knot/resources/test/corpus/60_try_except.py rename to crates/red_knot_workspace/resources/test/corpus/60_try_except.py diff --git a/crates/red_knot/resources/test/corpus/60_try_except2.py b/crates/red_knot_workspace/resources/test/corpus/60_try_except2.py similarity index 100% rename from crates/red_knot/resources/test/corpus/60_try_except2.py rename to crates/red_knot_workspace/resources/test/corpus/60_try_except2.py diff --git a/crates/red_knot/resources/test/corpus/60_try_except_bare.py b/crates/red_knot_workspace/resources/test/corpus/60_try_except_bare.py similarity index 100% rename from crates/red_knot/resources/test/corpus/60_try_except_bare.py rename to crates/red_knot_workspace/resources/test/corpus/60_try_except_bare.py diff --git a/crates/red_knot/resources/test/corpus/60_try_finally.py b/crates/red_knot_workspace/resources/test/corpus/60_try_finally.py similarity index 100% rename from crates/red_knot/resources/test/corpus/60_try_finally.py rename to crates/red_knot_workspace/resources/test/corpus/60_try_finally.py diff --git a/crates/red_knot/resources/test/corpus/60_try_finally_codeobj.py b/crates/red_knot_workspace/resources/test/corpus/60_try_finally_codeobj.py similarity index 100% rename from crates/red_knot/resources/test/corpus/60_try_finally_codeobj.py rename to crates/red_knot_workspace/resources/test/corpus/60_try_finally_codeobj.py diff --git a/crates/red_knot/resources/test/corpus/60_try_finally_cond.py b/crates/red_knot_workspace/resources/test/corpus/60_try_finally_cond.py similarity index 100% rename from crates/red_knot/resources/test/corpus/60_try_finally_cond.py rename to crates/red_knot_workspace/resources/test/corpus/60_try_finally_cond.py diff --git a/crates/red_knot/resources/test/corpus/60_try_finally_for.py b/crates/red_knot_workspace/resources/test/corpus/60_try_finally_for.py similarity index 100% rename from crates/red_knot/resources/test/corpus/60_try_finally_for.py rename to crates/red_knot_workspace/resources/test/corpus/60_try_finally_for.py diff --git a/crates/red_knot/resources/test/corpus/60_try_finally_ret.py b/crates/red_knot_workspace/resources/test/corpus/60_try_finally_ret.py similarity index 100% rename from crates/red_knot/resources/test/corpus/60_try_finally_ret.py rename to crates/red_knot_workspace/resources/test/corpus/60_try_finally_ret.py diff --git a/crates/red_knot/resources/test/corpus/61_try_except_finally.py b/crates/red_knot_workspace/resources/test/corpus/61_try_except_finally.py similarity index 100% rename from crates/red_knot/resources/test/corpus/61_try_except_finally.py rename to crates/red_knot_workspace/resources/test/corpus/61_try_except_finally.py diff --git a/crates/red_knot/resources/test/corpus/62_try_except_as.py b/crates/red_knot_workspace/resources/test/corpus/62_try_except_as.py similarity index 100% rename from crates/red_knot/resources/test/corpus/62_try_except_as.py rename to crates/red_knot_workspace/resources/test/corpus/62_try_except_as.py diff --git a/crates/red_knot/resources/test/corpus/62_try_except_break.py b/crates/red_knot_workspace/resources/test/corpus/62_try_except_break.py similarity index 100% rename from crates/red_knot/resources/test/corpus/62_try_except_break.py rename to crates/red_knot_workspace/resources/test/corpus/62_try_except_break.py diff --git a/crates/red_knot/resources/test/corpus/62_try_except_cond.py b/crates/red_knot_workspace/resources/test/corpus/62_try_except_cond.py similarity index 100% rename from crates/red_knot/resources/test/corpus/62_try_except_cond.py rename to crates/red_knot_workspace/resources/test/corpus/62_try_except_cond.py diff --git a/crates/red_knot/resources/test/corpus/62_try_except_double_nested_inside_if_else.py b/crates/red_knot_workspace/resources/test/corpus/62_try_except_double_nested_inside_if_else.py similarity index 100% rename from crates/red_knot/resources/test/corpus/62_try_except_double_nested_inside_if_else.py rename to crates/red_knot_workspace/resources/test/corpus/62_try_except_double_nested_inside_if_else.py diff --git a/crates/red_knot/resources/test/corpus/62_try_except_return.py b/crates/red_knot_workspace/resources/test/corpus/62_try_except_return.py similarity index 100% rename from crates/red_knot/resources/test/corpus/62_try_except_return.py rename to crates/red_knot_workspace/resources/test/corpus/62_try_except_return.py diff --git a/crates/red_knot/resources/test/corpus/63_raise.py b/crates/red_knot_workspace/resources/test/corpus/63_raise.py similarity index 100% rename from crates/red_knot/resources/test/corpus/63_raise.py rename to crates/red_knot_workspace/resources/test/corpus/63_raise.py diff --git a/crates/red_knot/resources/test/corpus/63_raise_func.py b/crates/red_knot_workspace/resources/test/corpus/63_raise_func.py similarity index 100% rename from crates/red_knot/resources/test/corpus/63_raise_func.py rename to crates/red_knot_workspace/resources/test/corpus/63_raise_func.py diff --git a/crates/red_knot/resources/test/corpus/63_raise_x.py b/crates/red_knot_workspace/resources/test/corpus/63_raise_x.py similarity index 100% rename from crates/red_knot/resources/test/corpus/63_raise_x.py rename to crates/red_knot_workspace/resources/test/corpus/63_raise_x.py diff --git a/crates/red_knot/resources/test/corpus/63_raise_x_from_y.py b/crates/red_knot_workspace/resources/test/corpus/63_raise_x_from_y.py similarity index 100% rename from crates/red_knot/resources/test/corpus/63_raise_x_from_y.py rename to crates/red_knot_workspace/resources/test/corpus/63_raise_x_from_y.py diff --git a/crates/red_knot/resources/test/corpus/64_assert.py b/crates/red_knot_workspace/resources/test/corpus/64_assert.py similarity index 100% rename from crates/red_knot/resources/test/corpus/64_assert.py rename to crates/red_knot_workspace/resources/test/corpus/64_assert.py diff --git a/crates/red_knot/resources/test/corpus/67_with.py b/crates/red_knot_workspace/resources/test/corpus/67_with.py similarity index 100% rename from crates/red_knot/resources/test/corpus/67_with.py rename to crates/red_knot_workspace/resources/test/corpus/67_with.py diff --git a/crates/red_knot/resources/test/corpus/67_with_as.py b/crates/red_knot_workspace/resources/test/corpus/67_with_as.py similarity index 100% rename from crates/red_knot/resources/test/corpus/67_with_as.py rename to crates/red_knot_workspace/resources/test/corpus/67_with_as.py diff --git a/crates/red_knot/resources/test/corpus/67_with_as_func.py b/crates/red_knot_workspace/resources/test/corpus/67_with_as_func.py similarity index 100% rename from crates/red_knot/resources/test/corpus/67_with_as_func.py rename to crates/red_knot_workspace/resources/test/corpus/67_with_as_func.py diff --git a/crates/red_knot/resources/test/corpus/67_with_cond_return.py b/crates/red_knot_workspace/resources/test/corpus/67_with_cond_return.py similarity index 100% rename from crates/red_knot/resources/test/corpus/67_with_cond_return.py rename to crates/red_knot_workspace/resources/test/corpus/67_with_cond_return.py diff --git a/crates/red_knot/resources/test/corpus/67_with_inside_try_finally_multiple_terminal_elif.py b/crates/red_knot_workspace/resources/test/corpus/67_with_inside_try_finally_multiple_terminal_elif.py similarity index 100% rename from crates/red_knot/resources/test/corpus/67_with_inside_try_finally_multiple_terminal_elif.py rename to crates/red_knot_workspace/resources/test/corpus/67_with_inside_try_finally_multiple_terminal_elif.py diff --git a/crates/red_knot/resources/test/corpus/67_with_inside_try_finally_preceding_terminal_except.py b/crates/red_knot_workspace/resources/test/corpus/67_with_inside_try_finally_preceding_terminal_except.py similarity index 100% rename from crates/red_knot/resources/test/corpus/67_with_inside_try_finally_preceding_terminal_except.py rename to crates/red_knot_workspace/resources/test/corpus/67_with_inside_try_finally_preceding_terminal_except.py diff --git a/crates/red_knot/resources/test/corpus/67_with_multi_exit.py b/crates/red_knot_workspace/resources/test/corpus/67_with_multi_exit.py similarity index 100% rename from crates/red_knot/resources/test/corpus/67_with_multi_exit.py rename to crates/red_knot_workspace/resources/test/corpus/67_with_multi_exit.py diff --git a/crates/red_knot/resources/test/corpus/67_with_return.py b/crates/red_knot_workspace/resources/test/corpus/67_with_return.py similarity index 100% rename from crates/red_knot/resources/test/corpus/67_with_return.py rename to crates/red_knot_workspace/resources/test/corpus/67_with_return.py diff --git a/crates/red_knot/resources/test/corpus/68_with2.py b/crates/red_knot_workspace/resources/test/corpus/68_with2.py similarity index 100% rename from crates/red_knot/resources/test/corpus/68_with2.py rename to crates/red_knot_workspace/resources/test/corpus/68_with2.py diff --git a/crates/red_knot/resources/test/corpus/69_for_try_except_continue1.py b/crates/red_knot_workspace/resources/test/corpus/69_for_try_except_continue1.py similarity index 100% rename from crates/red_knot/resources/test/corpus/69_for_try_except_continue1.py rename to crates/red_knot_workspace/resources/test/corpus/69_for_try_except_continue1.py diff --git a/crates/red_knot/resources/test/corpus/69_for_try_except_continue2.py b/crates/red_knot_workspace/resources/test/corpus/69_for_try_except_continue2.py similarity index 100% rename from crates/red_knot/resources/test/corpus/69_for_try_except_continue2.py rename to crates/red_knot_workspace/resources/test/corpus/69_for_try_except_continue2.py diff --git a/crates/red_knot/resources/test/corpus/69_for_try_except_continue3.py b/crates/red_knot_workspace/resources/test/corpus/69_for_try_except_continue3.py similarity index 100% rename from crates/red_knot/resources/test/corpus/69_for_try_except_continue3.py rename to crates/red_knot_workspace/resources/test/corpus/69_for_try_except_continue3.py diff --git a/crates/red_knot/resources/test/corpus/70_class.py b/crates/red_knot_workspace/resources/test/corpus/70_class.py similarity index 100% rename from crates/red_knot/resources/test/corpus/70_class.py rename to crates/red_knot_workspace/resources/test/corpus/70_class.py diff --git a/crates/red_knot/resources/test/corpus/70_class_base.py b/crates/red_knot_workspace/resources/test/corpus/70_class_base.py similarity index 100% rename from crates/red_knot/resources/test/corpus/70_class_base.py rename to crates/red_knot_workspace/resources/test/corpus/70_class_base.py diff --git a/crates/red_knot/resources/test/corpus/70_class_doc_str.py b/crates/red_knot_workspace/resources/test/corpus/70_class_doc_str.py similarity index 100% rename from crates/red_knot/resources/test/corpus/70_class_doc_str.py rename to crates/red_knot_workspace/resources/test/corpus/70_class_doc_str.py diff --git a/crates/red_knot/resources/test/corpus/71_class_meth.py b/crates/red_knot_workspace/resources/test/corpus/71_class_meth.py similarity index 100% rename from crates/red_knot/resources/test/corpus/71_class_meth.py rename to crates/red_knot_workspace/resources/test/corpus/71_class_meth.py diff --git a/crates/red_knot/resources/test/corpus/71_class_var.py b/crates/red_knot_workspace/resources/test/corpus/71_class_var.py similarity index 100% rename from crates/red_knot/resources/test/corpus/71_class_var.py rename to crates/red_knot_workspace/resources/test/corpus/71_class_var.py diff --git a/crates/red_knot/resources/test/corpus/72_class_mix.py b/crates/red_knot_workspace/resources/test/corpus/72_class_mix.py similarity index 100% rename from crates/red_knot/resources/test/corpus/72_class_mix.py rename to crates/red_knot_workspace/resources/test/corpus/72_class_mix.py diff --git a/crates/red_knot/resources/test/corpus/73_class_generic.py b/crates/red_knot_workspace/resources/test/corpus/73_class_generic.py similarity index 100% rename from crates/red_knot/resources/test/corpus/73_class_generic.py rename to crates/red_knot_workspace/resources/test/corpus/73_class_generic.py diff --git a/crates/red_knot/resources/test/corpus/73_class_generic_bounds.py b/crates/red_knot_workspace/resources/test/corpus/73_class_generic_bounds.py similarity index 100% rename from crates/red_knot/resources/test/corpus/73_class_generic_bounds.py rename to crates/red_knot_workspace/resources/test/corpus/73_class_generic_bounds.py diff --git a/crates/red_knot/resources/test/corpus/73_class_generic_constraints.py b/crates/red_knot_workspace/resources/test/corpus/73_class_generic_constraints.py similarity index 100% rename from crates/red_knot/resources/test/corpus/73_class_generic_constraints.py rename to crates/red_knot_workspace/resources/test/corpus/73_class_generic_constraints.py diff --git a/crates/red_knot/resources/test/corpus/73_class_generic_defaults.py b/crates/red_knot_workspace/resources/test/corpus/73_class_generic_defaults.py similarity index 100% rename from crates/red_knot/resources/test/corpus/73_class_generic_defaults.py rename to crates/red_knot_workspace/resources/test/corpus/73_class_generic_defaults.py diff --git a/crates/red_knot/resources/test/corpus/73_class_generic_paramspec.py b/crates/red_knot_workspace/resources/test/corpus/73_class_generic_paramspec.py similarity index 100% rename from crates/red_knot/resources/test/corpus/73_class_generic_paramspec.py rename to crates/red_knot_workspace/resources/test/corpus/73_class_generic_paramspec.py diff --git a/crates/red_knot/resources/test/corpus/73_class_generic_paramspec_default.py b/crates/red_knot_workspace/resources/test/corpus/73_class_generic_paramspec_default.py similarity index 100% rename from crates/red_knot/resources/test/corpus/73_class_generic_paramspec_default.py rename to crates/red_knot_workspace/resources/test/corpus/73_class_generic_paramspec_default.py diff --git a/crates/red_knot/resources/test/corpus/73_class_generic_tuple.py b/crates/red_knot_workspace/resources/test/corpus/73_class_generic_tuple.py similarity index 100% rename from crates/red_knot/resources/test/corpus/73_class_generic_tuple.py rename to crates/red_knot_workspace/resources/test/corpus/73_class_generic_tuple.py diff --git a/crates/red_knot/resources/test/corpus/73_class_generic_tuple_default.py b/crates/red_knot_workspace/resources/test/corpus/73_class_generic_tuple_default.py similarity index 100% rename from crates/red_knot/resources/test/corpus/73_class_generic_tuple_default.py rename to crates/red_knot_workspace/resources/test/corpus/73_class_generic_tuple_default.py diff --git a/crates/red_knot/resources/test/corpus/74_class_kwargs.py b/crates/red_knot_workspace/resources/test/corpus/74_class_kwargs.py similarity index 100% rename from crates/red_knot/resources/test/corpus/74_class_kwargs.py rename to crates/red_knot_workspace/resources/test/corpus/74_class_kwargs.py diff --git a/crates/red_knot/resources/test/corpus/74_class_kwargs_2.py b/crates/red_knot_workspace/resources/test/corpus/74_class_kwargs_2.py similarity index 100% rename from crates/red_knot/resources/test/corpus/74_class_kwargs_2.py rename to crates/red_knot_workspace/resources/test/corpus/74_class_kwargs_2.py diff --git a/crates/red_knot/resources/test/corpus/74_class_super.py b/crates/red_knot_workspace/resources/test/corpus/74_class_super.py similarity index 100% rename from crates/red_knot/resources/test/corpus/74_class_super.py rename to crates/red_knot_workspace/resources/test/corpus/74_class_super.py diff --git a/crates/red_knot/resources/test/corpus/74_class_super_nested.py b/crates/red_knot_workspace/resources/test/corpus/74_class_super_nested.py similarity index 100% rename from crates/red_knot/resources/test/corpus/74_class_super_nested.py rename to crates/red_knot_workspace/resources/test/corpus/74_class_super_nested.py diff --git a/crates/red_knot/resources/test/corpus/74_just_super.py b/crates/red_knot_workspace/resources/test/corpus/74_just_super.py similarity index 100% rename from crates/red_knot/resources/test/corpus/74_just_super.py rename to crates/red_knot_workspace/resources/test/corpus/74_just_super.py diff --git a/crates/red_knot/resources/test/corpus/75_classderef.py b/crates/red_knot_workspace/resources/test/corpus/75_classderef.py similarity index 100% rename from crates/red_knot/resources/test/corpus/75_classderef.py rename to crates/red_knot_workspace/resources/test/corpus/75_classderef.py diff --git a/crates/red_knot/resources/test/corpus/75_classderef_no.py b/crates/red_knot_workspace/resources/test/corpus/75_classderef_no.py similarity index 100% rename from crates/red_knot/resources/test/corpus/75_classderef_no.py rename to crates/red_knot_workspace/resources/test/corpus/75_classderef_no.py diff --git a/crates/red_knot/resources/test/corpus/76_class_nonlocal1.py b/crates/red_knot_workspace/resources/test/corpus/76_class_nonlocal1.py similarity index 100% rename from crates/red_knot/resources/test/corpus/76_class_nonlocal1.py rename to crates/red_knot_workspace/resources/test/corpus/76_class_nonlocal1.py diff --git a/crates/red_knot/resources/test/corpus/76_class_nonlocal2.py b/crates/red_knot_workspace/resources/test/corpus/76_class_nonlocal2.py similarity index 100% rename from crates/red_knot/resources/test/corpus/76_class_nonlocal2.py rename to crates/red_knot_workspace/resources/test/corpus/76_class_nonlocal2.py diff --git a/crates/red_knot/resources/test/corpus/76_class_nonlocal3.py b/crates/red_knot_workspace/resources/test/corpus/76_class_nonlocal3.py similarity index 100% rename from crates/red_knot/resources/test/corpus/76_class_nonlocal3.py rename to crates/red_knot_workspace/resources/test/corpus/76_class_nonlocal3.py diff --git a/crates/red_knot/resources/test/corpus/76_class_nonlocal4.py b/crates/red_knot_workspace/resources/test/corpus/76_class_nonlocal4.py similarity index 100% rename from crates/red_knot/resources/test/corpus/76_class_nonlocal4.py rename to crates/red_knot_workspace/resources/test/corpus/76_class_nonlocal4.py diff --git a/crates/red_knot/resources/test/corpus/76_class_nonlocal5.py b/crates/red_knot_workspace/resources/test/corpus/76_class_nonlocal5.py similarity index 100% rename from crates/red_knot/resources/test/corpus/76_class_nonlocal5.py rename to crates/red_knot_workspace/resources/test/corpus/76_class_nonlocal5.py diff --git a/crates/red_knot/resources/test/corpus/77_class__class__.py b/crates/red_knot_workspace/resources/test/corpus/77_class__class__.py similarity index 100% rename from crates/red_knot/resources/test/corpus/77_class__class__.py rename to crates/red_knot_workspace/resources/test/corpus/77_class__class__.py diff --git a/crates/red_knot/resources/test/corpus/77_class__class__nested.py b/crates/red_knot_workspace/resources/test/corpus/77_class__class__nested.py similarity index 100% rename from crates/red_knot/resources/test/corpus/77_class__class__nested.py rename to crates/red_knot_workspace/resources/test/corpus/77_class__class__nested.py diff --git a/crates/red_knot/resources/test/corpus/77_class__class__no_class.py b/crates/red_knot_workspace/resources/test/corpus/77_class__class__no_class.py similarity index 100% rename from crates/red_knot/resources/test/corpus/77_class__class__no_class.py rename to crates/red_knot_workspace/resources/test/corpus/77_class__class__no_class.py diff --git a/crates/red_knot/resources/test/corpus/77_class__class__nonlocals.py b/crates/red_knot_workspace/resources/test/corpus/77_class__class__nonlocals.py similarity index 100% rename from crates/red_knot/resources/test/corpus/77_class__class__nonlocals.py rename to crates/red_knot_workspace/resources/test/corpus/77_class__class__nonlocals.py diff --git a/crates/red_knot/resources/test/corpus/77_class__class__nonlocals_2.py b/crates/red_knot_workspace/resources/test/corpus/77_class__class__nonlocals_2.py similarity index 100% rename from crates/red_knot/resources/test/corpus/77_class__class__nonlocals_2.py rename to crates/red_knot_workspace/resources/test/corpus/77_class__class__nonlocals_2.py diff --git a/crates/red_knot/resources/test/corpus/77_class__class__param.py b/crates/red_knot_workspace/resources/test/corpus/77_class__class__param.py similarity index 100% rename from crates/red_knot/resources/test/corpus/77_class__class__param.py rename to crates/red_knot_workspace/resources/test/corpus/77_class__class__param.py diff --git a/crates/red_knot/resources/test/corpus/77_class__class__param_lambda.py b/crates/red_knot_workspace/resources/test/corpus/77_class__class__param_lambda.py similarity index 100% rename from crates/red_knot/resources/test/corpus/77_class__class__param_lambda.py rename to crates/red_knot_workspace/resources/test/corpus/77_class__class__param_lambda.py diff --git a/crates/red_knot/resources/test/corpus/78_class_body_cond.py b/crates/red_knot_workspace/resources/test/corpus/78_class_body_cond.py similarity index 100% rename from crates/red_knot/resources/test/corpus/78_class_body_cond.py rename to crates/red_knot_workspace/resources/test/corpus/78_class_body_cond.py diff --git a/crates/red_knot/resources/test/corpus/78_class_dec.py b/crates/red_knot_workspace/resources/test/corpus/78_class_dec.py similarity index 100% rename from crates/red_knot/resources/test/corpus/78_class_dec.py rename to crates/red_knot_workspace/resources/test/corpus/78_class_dec.py diff --git a/crates/red_knot/resources/test/corpus/78_class_dec_member.py b/crates/red_knot_workspace/resources/test/corpus/78_class_dec_member.py similarity index 100% rename from crates/red_knot/resources/test/corpus/78_class_dec_member.py rename to crates/red_knot_workspace/resources/test/corpus/78_class_dec_member.py diff --git a/crates/red_knot/resources/test/corpus/78_class_dec_member_func.py b/crates/red_knot_workspace/resources/test/corpus/78_class_dec_member_func.py similarity index 100% rename from crates/red_knot/resources/test/corpus/78_class_dec_member_func.py rename to crates/red_knot_workspace/resources/test/corpus/78_class_dec_member_func.py diff --git a/crates/red_knot/resources/test/corpus/79_metaclass.py b/crates/red_knot_workspace/resources/test/corpus/79_metaclass.py similarity index 100% rename from crates/red_knot/resources/test/corpus/79_metaclass.py rename to crates/red_knot_workspace/resources/test/corpus/79_metaclass.py diff --git a/crates/red_knot/resources/test/corpus/80_func_kwonlyargs1.py b/crates/red_knot_workspace/resources/test/corpus/80_func_kwonlyargs1.py similarity index 100% rename from crates/red_knot/resources/test/corpus/80_func_kwonlyargs1.py rename to crates/red_knot_workspace/resources/test/corpus/80_func_kwonlyargs1.py diff --git a/crates/red_knot/resources/test/corpus/80_func_kwonlyargs2.py b/crates/red_knot_workspace/resources/test/corpus/80_func_kwonlyargs2.py similarity index 100% rename from crates/red_knot/resources/test/corpus/80_func_kwonlyargs2.py rename to crates/red_knot_workspace/resources/test/corpus/80_func_kwonlyargs2.py diff --git a/crates/red_knot/resources/test/corpus/80_func_kwonlyargs3.py b/crates/red_knot_workspace/resources/test/corpus/80_func_kwonlyargs3.py similarity index 100% rename from crates/red_knot/resources/test/corpus/80_func_kwonlyargs3.py rename to crates/red_knot_workspace/resources/test/corpus/80_func_kwonlyargs3.py diff --git a/crates/red_knot/resources/test/corpus/81_func_kwonlyargs_defaults.py b/crates/red_knot_workspace/resources/test/corpus/81_func_kwonlyargs_defaults.py similarity index 100% rename from crates/red_knot/resources/test/corpus/81_func_kwonlyargs_defaults.py rename to crates/red_knot_workspace/resources/test/corpus/81_func_kwonlyargs_defaults.py diff --git a/crates/red_knot/resources/test/corpus/85_match.py b/crates/red_knot_workspace/resources/test/corpus/85_match.py similarity index 100% rename from crates/red_knot/resources/test/corpus/85_match.py rename to crates/red_knot_workspace/resources/test/corpus/85_match.py diff --git a/crates/red_knot/resources/test/corpus/85_match_as.py b/crates/red_knot_workspace/resources/test/corpus/85_match_as.py similarity index 100% rename from crates/red_knot/resources/test/corpus/85_match_as.py rename to crates/red_knot_workspace/resources/test/corpus/85_match_as.py diff --git a/crates/red_knot/resources/test/corpus/85_match_attr.py b/crates/red_knot_workspace/resources/test/corpus/85_match_attr.py similarity index 100% rename from crates/red_knot/resources/test/corpus/85_match_attr.py rename to crates/red_knot_workspace/resources/test/corpus/85_match_attr.py diff --git a/crates/red_knot/resources/test/corpus/85_match_class.py b/crates/red_knot_workspace/resources/test/corpus/85_match_class.py similarity index 100% rename from crates/red_knot/resources/test/corpus/85_match_class.py rename to crates/red_knot_workspace/resources/test/corpus/85_match_class.py diff --git a/crates/red_knot/resources/test/corpus/85_match_default.py b/crates/red_knot_workspace/resources/test/corpus/85_match_default.py similarity index 100% rename from crates/red_knot/resources/test/corpus/85_match_default.py rename to crates/red_knot_workspace/resources/test/corpus/85_match_default.py diff --git a/crates/red_knot/resources/test/corpus/85_match_guard.py b/crates/red_knot_workspace/resources/test/corpus/85_match_guard.py similarity index 100% rename from crates/red_knot/resources/test/corpus/85_match_guard.py rename to crates/red_knot_workspace/resources/test/corpus/85_match_guard.py diff --git a/crates/red_knot/resources/test/corpus/85_match_in_func.py b/crates/red_knot_workspace/resources/test/corpus/85_match_in_func.py similarity index 100% rename from crates/red_knot/resources/test/corpus/85_match_in_func.py rename to crates/red_knot_workspace/resources/test/corpus/85_match_in_func.py diff --git a/crates/red_knot/resources/test/corpus/85_match_in_func_with_rest.py b/crates/red_knot_workspace/resources/test/corpus/85_match_in_func_with_rest.py similarity index 100% rename from crates/red_knot/resources/test/corpus/85_match_in_func_with_rest.py rename to crates/red_knot_workspace/resources/test/corpus/85_match_in_func_with_rest.py diff --git a/crates/red_knot/resources/test/corpus/85_match_in_func_with_star.py b/crates/red_knot_workspace/resources/test/corpus/85_match_in_func_with_star.py similarity index 100% rename from crates/red_knot/resources/test/corpus/85_match_in_func_with_star.py rename to crates/red_knot_workspace/resources/test/corpus/85_match_in_func_with_star.py diff --git a/crates/red_knot/resources/test/corpus/85_match_mapping.py b/crates/red_knot_workspace/resources/test/corpus/85_match_mapping.py similarity index 100% rename from crates/red_knot/resources/test/corpus/85_match_mapping.py rename to crates/red_knot_workspace/resources/test/corpus/85_match_mapping.py diff --git a/crates/red_knot/resources/test/corpus/85_match_mapping_subpattern.py b/crates/red_knot_workspace/resources/test/corpus/85_match_mapping_subpattern.py similarity index 100% rename from crates/red_knot/resources/test/corpus/85_match_mapping_subpattern.py rename to crates/red_knot_workspace/resources/test/corpus/85_match_mapping_subpattern.py diff --git a/crates/red_knot/resources/test/corpus/85_match_or.py b/crates/red_knot_workspace/resources/test/corpus/85_match_or.py similarity index 100% rename from crates/red_knot/resources/test/corpus/85_match_or.py rename to crates/red_knot_workspace/resources/test/corpus/85_match_or.py diff --git a/crates/red_knot/resources/test/corpus/85_match_sequence.py b/crates/red_knot_workspace/resources/test/corpus/85_match_sequence.py similarity index 100% rename from crates/red_knot/resources/test/corpus/85_match_sequence.py rename to crates/red_knot_workspace/resources/test/corpus/85_match_sequence.py diff --git a/crates/red_knot/resources/test/corpus/85_match_sequence_wildcard.py b/crates/red_knot_workspace/resources/test/corpus/85_match_sequence_wildcard.py similarity index 100% rename from crates/red_knot/resources/test/corpus/85_match_sequence_wildcard.py rename to crates/red_knot_workspace/resources/test/corpus/85_match_sequence_wildcard.py diff --git a/crates/red_knot/resources/test/corpus/85_match_singleton.py b/crates/red_knot_workspace/resources/test/corpus/85_match_singleton.py similarity index 100% rename from crates/red_knot/resources/test/corpus/85_match_singleton.py rename to crates/red_knot_workspace/resources/test/corpus/85_match_singleton.py diff --git a/crates/red_knot/resources/test/corpus/89_type_alias.py b/crates/red_knot_workspace/resources/test/corpus/89_type_alias.py similarity index 100% rename from crates/red_knot/resources/test/corpus/89_type_alias.py rename to crates/red_knot_workspace/resources/test/corpus/89_type_alias.py diff --git a/crates/red_knot/resources/test/corpus/90_docstring_class.py b/crates/red_knot_workspace/resources/test/corpus/90_docstring_class.py similarity index 100% rename from crates/red_knot/resources/test/corpus/90_docstring_class.py rename to crates/red_knot_workspace/resources/test/corpus/90_docstring_class.py diff --git a/crates/red_knot/resources/test/corpus/90_docstring_func.py b/crates/red_knot_workspace/resources/test/corpus/90_docstring_func.py similarity index 100% rename from crates/red_knot/resources/test/corpus/90_docstring_func.py rename to crates/red_knot_workspace/resources/test/corpus/90_docstring_func.py diff --git a/crates/red_knot/resources/test/corpus/90_docstring_mod.py b/crates/red_knot_workspace/resources/test/corpus/90_docstring_mod.py similarity index 100% rename from crates/red_knot/resources/test/corpus/90_docstring_mod.py rename to crates/red_knot_workspace/resources/test/corpus/90_docstring_mod.py diff --git a/crates/red_knot/resources/test/corpus/91_line_numbers1.py b/crates/red_knot_workspace/resources/test/corpus/91_line_numbers1.py similarity index 100% rename from crates/red_knot/resources/test/corpus/91_line_numbers1.py rename to crates/red_knot_workspace/resources/test/corpus/91_line_numbers1.py diff --git a/crates/red_knot/resources/test/corpus/91_line_numbers2.py b/crates/red_knot_workspace/resources/test/corpus/91_line_numbers2.py similarity index 100% rename from crates/red_knot/resources/test/corpus/91_line_numbers2.py rename to crates/red_knot_workspace/resources/test/corpus/91_line_numbers2.py diff --git a/crates/red_knot/resources/test/corpus/91_line_numbers2_comp.py b/crates/red_knot_workspace/resources/test/corpus/91_line_numbers2_comp.py similarity index 100% rename from crates/red_knot/resources/test/corpus/91_line_numbers2_comp.py rename to crates/red_knot_workspace/resources/test/corpus/91_line_numbers2_comp.py diff --git a/crates/red_knot/resources/test/corpus/91_line_numbers3.py b/crates/red_knot_workspace/resources/test/corpus/91_line_numbers3.py similarity index 100% rename from crates/red_knot/resources/test/corpus/91_line_numbers3.py rename to crates/red_knot_workspace/resources/test/corpus/91_line_numbers3.py diff --git a/crates/red_knot/resources/test/corpus/91_line_numbers4.py b/crates/red_knot_workspace/resources/test/corpus/91_line_numbers4.py similarity index 100% rename from crates/red_knot/resources/test/corpus/91_line_numbers4.py rename to crates/red_knot_workspace/resources/test/corpus/91_line_numbers4.py diff --git a/crates/red_knot/resources/test/corpus/91_line_numbers_dict.py b/crates/red_knot_workspace/resources/test/corpus/91_line_numbers_dict.py similarity index 100% rename from crates/red_knot/resources/test/corpus/91_line_numbers_dict.py rename to crates/red_knot_workspace/resources/test/corpus/91_line_numbers_dict.py diff --git a/crates/red_knot/resources/test/corpus/91_line_numbers_dict_comp.py b/crates/red_knot_workspace/resources/test/corpus/91_line_numbers_dict_comp.py similarity index 100% rename from crates/red_knot/resources/test/corpus/91_line_numbers_dict_comp.py rename to crates/red_knot_workspace/resources/test/corpus/91_line_numbers_dict_comp.py diff --git a/crates/red_knot/resources/test/corpus/92_qual_class_in_class.py b/crates/red_knot_workspace/resources/test/corpus/92_qual_class_in_class.py similarity index 100% rename from crates/red_knot/resources/test/corpus/92_qual_class_in_class.py rename to crates/red_knot_workspace/resources/test/corpus/92_qual_class_in_class.py diff --git a/crates/red_knot/resources/test/corpus/92_qual_class_in_func.py b/crates/red_knot_workspace/resources/test/corpus/92_qual_class_in_func.py similarity index 100% rename from crates/red_knot/resources/test/corpus/92_qual_class_in_func.py rename to crates/red_knot_workspace/resources/test/corpus/92_qual_class_in_func.py diff --git a/crates/red_knot/resources/test/corpus/93_deadcode.py b/crates/red_knot_workspace/resources/test/corpus/93_deadcode.py similarity index 100% rename from crates/red_knot/resources/test/corpus/93_deadcode.py rename to crates/red_knot_workspace/resources/test/corpus/93_deadcode.py diff --git a/crates/red_knot/resources/test/corpus/94_strformat.py b/crates/red_knot_workspace/resources/test/corpus/94_strformat.py similarity index 100% rename from crates/red_knot/resources/test/corpus/94_strformat.py rename to crates/red_knot_workspace/resources/test/corpus/94_strformat.py diff --git a/crates/red_knot/resources/test/corpus/94_strformat_complex.py b/crates/red_knot_workspace/resources/test/corpus/94_strformat_complex.py similarity index 100% rename from crates/red_knot/resources/test/corpus/94_strformat_complex.py rename to crates/red_knot_workspace/resources/test/corpus/94_strformat_complex.py diff --git a/crates/red_knot/resources/test/corpus/94_strformat_conv.py b/crates/red_knot_workspace/resources/test/corpus/94_strformat_conv.py similarity index 100% rename from crates/red_knot/resources/test/corpus/94_strformat_conv.py rename to crates/red_knot_workspace/resources/test/corpus/94_strformat_conv.py diff --git a/crates/red_knot/resources/test/corpus/94_strformat_spec.py b/crates/red_knot_workspace/resources/test/corpus/94_strformat_spec.py similarity index 100% rename from crates/red_knot/resources/test/corpus/94_strformat_spec.py rename to crates/red_knot_workspace/resources/test/corpus/94_strformat_spec.py diff --git a/crates/red_knot/resources/test/corpus/95_annotation_assign_subscript_no_rhs.py b/crates/red_knot_workspace/resources/test/corpus/95_annotation_assign_subscript_no_rhs.py similarity index 100% rename from crates/red_knot/resources/test/corpus/95_annotation_assign_subscript_no_rhs.py rename to crates/red_knot_workspace/resources/test/corpus/95_annotation_assign_subscript_no_rhs.py diff --git a/crates/red_knot/resources/test/corpus/95_annotation_assign_tuple.py b/crates/red_knot_workspace/resources/test/corpus/95_annotation_assign_tuple.py similarity index 100% rename from crates/red_knot/resources/test/corpus/95_annotation_assign_tuple.py rename to crates/red_knot_workspace/resources/test/corpus/95_annotation_assign_tuple.py diff --git a/crates/red_knot/resources/test/corpus/95_annotation_class.py b/crates/red_knot_workspace/resources/test/corpus/95_annotation_class.py similarity index 100% rename from crates/red_knot/resources/test/corpus/95_annotation_class.py rename to crates/red_knot_workspace/resources/test/corpus/95_annotation_class.py diff --git a/crates/red_knot/resources/test/corpus/95_annotation_class_multiline.py b/crates/red_knot_workspace/resources/test/corpus/95_annotation_class_multiline.py similarity index 100% rename from crates/red_knot/resources/test/corpus/95_annotation_class_multiline.py rename to crates/red_knot_workspace/resources/test/corpus/95_annotation_class_multiline.py diff --git a/crates/red_knot/resources/test/corpus/95_annotation_class_no_value.py b/crates/red_knot_workspace/resources/test/corpus/95_annotation_class_no_value.py similarity index 100% rename from crates/red_knot/resources/test/corpus/95_annotation_class_no_value.py rename to crates/red_knot_workspace/resources/test/corpus/95_annotation_class_no_value.py diff --git a/crates/red_knot/resources/test/corpus/95_annotation_func.py b/crates/red_knot_workspace/resources/test/corpus/95_annotation_func.py similarity index 100% rename from crates/red_knot/resources/test/corpus/95_annotation_func.py rename to crates/red_knot_workspace/resources/test/corpus/95_annotation_func.py diff --git a/crates/red_knot/resources/test/corpus/95_annotation_func_future.py b/crates/red_knot_workspace/resources/test/corpus/95_annotation_func_future.py similarity index 100% rename from crates/red_knot/resources/test/corpus/95_annotation_func_future.py rename to crates/red_knot_workspace/resources/test/corpus/95_annotation_func_future.py diff --git a/crates/red_knot/resources/test/corpus/95_annotation_global.py b/crates/red_knot_workspace/resources/test/corpus/95_annotation_global.py similarity index 100% rename from crates/red_knot/resources/test/corpus/95_annotation_global.py rename to crates/red_knot_workspace/resources/test/corpus/95_annotation_global.py diff --git a/crates/red_knot/resources/test/corpus/95_annotation_global_simple.py b/crates/red_knot_workspace/resources/test/corpus/95_annotation_global_simple.py similarity index 100% rename from crates/red_knot/resources/test/corpus/95_annotation_global_simple.py rename to crates/red_knot_workspace/resources/test/corpus/95_annotation_global_simple.py diff --git a/crates/red_knot/resources/test/corpus/95_annotation_local_attr.py b/crates/red_knot_workspace/resources/test/corpus/95_annotation_local_attr.py similarity index 100% rename from crates/red_knot/resources/test/corpus/95_annotation_local_attr.py rename to crates/red_knot_workspace/resources/test/corpus/95_annotation_local_attr.py diff --git a/crates/red_knot/resources/test/corpus/95_annotation_module.py b/crates/red_knot_workspace/resources/test/corpus/95_annotation_module.py similarity index 100% rename from crates/red_knot/resources/test/corpus/95_annotation_module.py rename to crates/red_knot_workspace/resources/test/corpus/95_annotation_module.py diff --git a/crates/red_knot/resources/test/corpus/96_debug.py b/crates/red_knot_workspace/resources/test/corpus/96_debug.py similarity index 100% rename from crates/red_knot/resources/test/corpus/96_debug.py rename to crates/red_knot_workspace/resources/test/corpus/96_debug.py diff --git a/crates/red_knot/resources/test/corpus/97_global_nonlocal_store.py b/crates/red_knot_workspace/resources/test/corpus/97_global_nonlocal_store.py similarity index 100% rename from crates/red_knot/resources/test/corpus/97_global_nonlocal_store.py rename to crates/red_knot_workspace/resources/test/corpus/97_global_nonlocal_store.py diff --git a/crates/red_knot/resources/test/corpus/98_ann_assign_annotation_future_annotations.py b/crates/red_knot_workspace/resources/test/corpus/98_ann_assign_annotation_future_annotations.py similarity index 100% rename from crates/red_knot/resources/test/corpus/98_ann_assign_annotation_future_annotations.py rename to crates/red_knot_workspace/resources/test/corpus/98_ann_assign_annotation_future_annotations.py diff --git a/crates/red_knot/resources/test/corpus/98_ann_assign_annotation_wrong_future.py b/crates/red_knot_workspace/resources/test/corpus/98_ann_assign_annotation_wrong_future.py similarity index 100% rename from crates/red_knot/resources/test/corpus/98_ann_assign_annotation_wrong_future.py rename to crates/red_knot_workspace/resources/test/corpus/98_ann_assign_annotation_wrong_future.py diff --git a/crates/red_knot/resources/test/corpus/98_ann_assign_simple_annotation.py b/crates/red_knot_workspace/resources/test/corpus/98_ann_assign_simple_annotation.py similarity index 100% rename from crates/red_knot/resources/test/corpus/98_ann_assign_simple_annotation.py rename to crates/red_knot_workspace/resources/test/corpus/98_ann_assign_simple_annotation.py diff --git a/crates/red_knot/resources/test/corpus/99_empty_jump_target_insts.py b/crates/red_knot_workspace/resources/test/corpus/99_empty_jump_target_insts.py similarity index 100% rename from crates/red_knot/resources/test/corpus/99_empty_jump_target_insts.py rename to crates/red_knot_workspace/resources/test/corpus/99_empty_jump_target_insts.py diff --git a/crates/red_knot/src/db.rs b/crates/red_knot_workspace/src/db.rs similarity index 77% rename from crates/red_knot/src/db.rs rename to crates/red_knot_workspace/src/db.rs index b875d1bfefe2f..f2bbe5087eed3 100644 --- a/crates/red_knot/src/db.rs +++ b/crates/red_knot_workspace/src/db.rs @@ -1,39 +1,28 @@ use std::panic::{AssertUnwindSafe, RefUnwindSafe}; -use std::sync::Arc; -use salsa::{Cancelled, Database, DbWithJar}; - -use red_knot_module_resolver::{vendored_typeshed_stubs, Db as ResolverDb, Jar as ResolverJar}; -use red_knot_python_semantic::{Db as SemanticDb, Jar as SemanticJar}; +use red_knot_module_resolver::{vendored_typeshed_stubs, Db as ResolverDb}; +use red_knot_python_semantic::Db as SemanticDb; use ruff_db::files::{File, Files}; use ruff_db::program::{Program, ProgramSettings}; use ruff_db::system::System; use ruff_db::vendored::VendoredFileSystem; -use ruff_db::{Db as SourceDb, Jar as SourceJar, Upcast}; +use ruff_db::{Db as SourceDb, Upcast}; +use salsa::Cancelled; -use crate::lint::{lint_semantic, lint_syntax, unwind_if_cancelled, Diagnostics}; -use crate::workspace::{check_file, Package, Package_files, Workspace, WorkspaceMetadata}; +use crate::lint::Diagnostics; +use crate::workspace::{check_file, Workspace, WorkspaceMetadata}; mod changes; -pub trait Db: DbWithJar + SemanticDb + Upcast {} - -#[salsa::jar(db=Db)] -pub struct Jar( - Workspace, - Package, - Package_files, - lint_syntax, - lint_semantic, - unwind_if_cancelled, -); +#[salsa::db] +pub trait Db: SemanticDb + Upcast {} -#[salsa::db(SourceJar, ResolverJar, SemanticJar, Jar)] +#[salsa::db] pub struct RootDatabase { workspace: Option, storage: salsa::Storage, files: Files, - system: Arc, + system: Box, } impl RootDatabase { @@ -45,7 +34,7 @@ impl RootDatabase { workspace: None, storage: salsa::Storage::default(), files: Files::default(), - system: Arc::new(system), + system: Box::new(system), }; let workspace = Workspace::from_metadata(&db, workspace); @@ -127,10 +116,13 @@ impl Upcast for RootDatabase { } } +#[salsa::db] impl ResolverDb for RootDatabase {} +#[salsa::db] impl SemanticDb for RootDatabase {} +#[salsa::db] impl SourceDb for RootDatabase { fn vendored(&self) -> &VendoredFileSystem { vendored_typeshed_stubs() @@ -145,33 +137,23 @@ impl SourceDb for RootDatabase { } } -impl Database for RootDatabase {} +#[salsa::db] +impl salsa::Database for RootDatabase {} +#[salsa::db] impl Db for RootDatabase {} -impl salsa::ParallelDatabase for RootDatabase { - fn snapshot(&self) -> salsa::Snapshot { - salsa::Snapshot::new(Self { - workspace: self.workspace, - storage: self.storage.snapshot(), - files: self.files.snapshot(), - system: self.system.clone(), - }) - } -} - #[cfg(test)] pub(crate) mod tests { - use red_knot_module_resolver::{vendored_typeshed_stubs, Db as ResolverDb, Jar as ResolverJar}; - use red_knot_python_semantic::{Db as SemanticDb, Jar as SemanticJar}; + use crate::db::Db; + use red_knot_module_resolver::{vendored_typeshed_stubs, Db as ResolverDb}; + use red_knot_python_semantic::Db as SemanticDb; use ruff_db::files::Files; use ruff_db::system::{DbWithTestSystem, System, TestSystem}; use ruff_db::vendored::VendoredFileSystem; - use ruff_db::{Db as SourceDb, Jar as SourceJar, Upcast}; + use ruff_db::{Db as SourceDb, Upcast}; - use super::{Db, Jar}; - - #[salsa::db(Jar, SemanticJar, ResolverJar, SourceJar)] + #[salsa::db] pub(crate) struct TestDb { storage: salsa::Storage, files: Files, @@ -184,7 +166,7 @@ pub(crate) mod tests { Self { storage: salsa::Storage::default(), system: TestSystem::default(), - vendored: vendored_typeshed_stubs().snapshot(), + vendored: vendored_typeshed_stubs().clone(), files: Files::default(), } } @@ -200,6 +182,7 @@ pub(crate) mod tests { } } + #[salsa::db] impl SourceDb for TestDb { fn vendored(&self) -> &VendoredFileSystem { &self.vendored @@ -241,20 +224,13 @@ pub(crate) mod tests { } } + #[salsa::db] impl red_knot_module_resolver::Db for TestDb {} + #[salsa::db] impl red_knot_python_semantic::Db for TestDb {} + #[salsa::db] impl Db for TestDb {} + #[salsa::db] impl salsa::Database for TestDb {} - - impl salsa::ParallelDatabase for TestDb { - fn snapshot(&self) -> salsa::Snapshot { - salsa::Snapshot::new(Self { - storage: self.storage.snapshot(), - files: self.files.snapshot(), - system: self.system.snapshot(), - vendored: self.vendored.snapshot(), - }) - } - } } diff --git a/crates/red_knot/src/db/changes.rs b/crates/red_knot_workspace/src/db/changes.rs similarity index 98% rename from crates/red_knot/src/db/changes.rs rename to crates/red_knot_workspace/src/db/changes.rs index df527f68fd3ff..d97cc4f034dcd 100644 --- a/crates/red_knot/src/db/changes.rs +++ b/crates/red_knot_workspace/src/db/changes.rs @@ -173,7 +173,7 @@ impl RootDatabase { let package = workspace.package(self, &path); let file = system_path_to_file(self, &path); - if let (Some(package), Some(file)) = (package, file) { + if let (Some(package), Ok(file)) = (package, file) { package.add_file(self, file); } } diff --git a/crates/red_knot/src/lib.rs b/crates/red_knot_workspace/src/lib.rs similarity index 74% rename from crates/red_knot/src/lib.rs rename to crates/red_knot_workspace/src/lib.rs index e59be4290e08b..f0b3f62a9802f 100644 --- a/crates/red_knot/src/lib.rs +++ b/crates/red_knot_workspace/src/lib.rs @@ -1,5 +1,3 @@ -use crate::db::Jar; - pub mod db; pub mod lint; pub mod watch; diff --git a/crates/red_knot/src/lint.rs b/crates/red_knot_workspace/src/lint.rs similarity index 99% rename from crates/red_knot/src/lint.rs rename to crates/red_knot_workspace/src/lint.rs index c7e996353c71e..27114bf251427 100644 --- a/crates/red_knot/src/lint.rs +++ b/crates/red_knot_workspace/src/lint.rs @@ -76,7 +76,7 @@ fn lint_lines(source: &str, diagnostics: &mut Vec) { #[allow(unreachable_pub)] #[salsa::tracked(return_ref)] pub fn lint_semantic(db: &dyn Db, file_id: File) -> Diagnostics { - let _span = trace_span!("lint_semantic", ?file_id).entered(); + let _span = trace_span!("lint_semantic", file=?file_id.path(db)).entered(); let source = source_text(db.upcast(), file_id); let parsed = parsed_module(db.upcast(), file_id); diff --git a/crates/red_knot/src/watch.rs b/crates/red_knot_workspace/src/watch.rs similarity index 100% rename from crates/red_knot/src/watch.rs rename to crates/red_knot_workspace/src/watch.rs diff --git a/crates/red_knot/src/watch/watcher.rs b/crates/red_knot_workspace/src/watch/watcher.rs similarity index 99% rename from crates/red_knot/src/watch/watcher.rs rename to crates/red_knot_workspace/src/watch/watcher.rs index 6e9f7123020ff..58a88f39a06a4 100644 --- a/crates/red_knot/src/watch/watcher.rs +++ b/crates/red_knot_workspace/src/watch/watcher.rs @@ -240,7 +240,7 @@ impl Debouncer { } ModifyKind::Data(_) => ChangeEvent::Changed { - kind: ChangedKind::FileMetadata, + kind: ChangedKind::FileContent, path, }, diff --git a/crates/red_knot/src/watch/workspace_watcher.rs b/crates/red_knot_workspace/src/watch/workspace_watcher.rs similarity index 100% rename from crates/red_knot/src/watch/workspace_watcher.rs rename to crates/red_knot_workspace/src/watch/workspace_watcher.rs diff --git a/crates/red_knot/src/workspace.rs b/crates/red_knot_workspace/src/workspace.rs similarity index 93% rename from crates/red_knot/src/workspace.rs rename to crates/red_knot_workspace/src/workspace.rs index 0f93d46ea35c3..d262ef39b0dbb 100644 --- a/crates/red_knot/src/workspace.rs +++ b/crates/red_knot_workspace/src/workspace.rs @@ -1,6 +1,4 @@ -// TODO: Fix clippy warnings created by salsa macros -#![allow(clippy::used_underscore_binding, unreachable_pub)] - +use salsa::{Durability, Setter as _}; use std::{collections::BTreeMap, sync::Arc}; use rustc_hash::{FxBuildHasher, FxHashSet}; @@ -67,7 +65,6 @@ mod metadata; /// holding on to the most fundamental settings required for checking. #[salsa::input] pub struct Workspace { - #[id] #[return_ref] root_buf: SystemPathBuf, @@ -90,7 +87,6 @@ pub struct Package { pub name: Name, /// The path to the root directory of the package. - #[id] #[return_ref] root_buf: SystemPathBuf, @@ -109,7 +105,9 @@ impl Workspace { packages.insert(package.root.clone(), Package::from_metadata(db, package)); } - Workspace::new(db, metadata.root, None, packages) + Workspace::builder(metadata.root, None, packages) + .durability(Durability::MEDIUM) + .new(db) } pub fn root(self, db: &dyn Db) -> &SystemPath { @@ -140,7 +138,9 @@ impl Workspace { new_packages.insert(path, package); } - self.set_package_tree(db).to(new_packages); + self.set_package_tree(db) + .with_durability(Durability::MEDIUM) + .to(new_packages); } #[tracing::instrument(level = "debug", skip_all)] @@ -309,20 +309,28 @@ impl Package { } fn from_metadata(db: &dyn Db, metadata: PackageMetadata) -> Self { - Self::new(db, metadata.name, metadata.root, PackageFiles::default()) + Self::builder(metadata.name, metadata.root, PackageFiles::default()) + .durability(Durability::MEDIUM) + .new(db) } fn update(self, db: &mut dyn Db, metadata: PackageMetadata) { let root = self.root(db); assert_eq!(root, metadata.root()); - self.set_name(db).to(metadata.name); + if self.name(db) != metadata.name() { + self.set_name(db) + .with_durability(Durability::MEDIUM) + .to(metadata.name); + } } #[tracing::instrument(level = "debug", skip(db))] pub fn reload_files(self, db: &mut dyn Db) { - // Force a re-index of the files in the next revision. - self.set_file_set(db).to(PackageFiles::lazy()); + if !self.file_set(db).is_lazy() { + // Force a re-index of the files in the next revision. + self.set_file_set(db).to(PackageFiles::lazy()); + } } } @@ -368,7 +376,7 @@ fn discover_package_files(db: &dyn Db, path: &SystemPath) -> FxHashSet { for path in paths { // If this returns `None`, then the file was deleted between the `walk_directory` call and now. // We can ignore this. - if let Some(file) = system_path_to_file(db.upcast(), &path) { + if let Ok(file) = system_path_to_file(db.upcast(), &path) { files.insert(file); } } diff --git a/crates/red_knot/src/workspace/files.rs b/crates/red_knot_workspace/src/workspace/files.rs similarity index 98% rename from crates/red_knot/src/workspace/files.rs rename to crates/red_knot_workspace/src/workspace/files.rs index 4a52c8930015f..ae391fdcd26a2 100644 --- a/crates/red_knot/src/workspace/files.rs +++ b/crates/red_knot_workspace/src/workspace/files.rs @@ -3,10 +3,12 @@ use std::ops::Deref; use std::sync::Arc; use rustc_hash::FxHashSet; +use salsa::Setter; + +use ruff_db::files::File; use crate::db::Db; use crate::workspace::Package; -use ruff_db::files::File; /// The indexed files of a package. /// @@ -45,6 +47,10 @@ impl PackageFiles { } } + pub fn is_lazy(&self) -> bool { + matches!(*self.state.lock().unwrap(), State::Lazy) + } + /// Returns a mutable view on the index that allows cheap in-place mutations. /// /// The changes are automatically written back to the database once the view is dropped. diff --git a/crates/red_knot/src/workspace/metadata.rs b/crates/red_knot_workspace/src/workspace/metadata.rs similarity index 100% rename from crates/red_knot/src/workspace/metadata.rs rename to crates/red_knot_workspace/src/workspace/metadata.rs diff --git a/crates/red_knot/tests/check.rs b/crates/red_knot_workspace/tests/check.rs similarity index 92% rename from crates/red_knot/tests/check.rs rename to crates/red_knot_workspace/tests/check.rs index c91c0515478bf..a2d0f99207f02 100644 --- a/crates/red_knot/tests/check.rs +++ b/crates/red_knot_workspace/tests/check.rs @@ -1,6 +1,6 @@ -use red_knot::db::RootDatabase; -use red_knot::lint::lint_semantic; -use red_knot::workspace::WorkspaceMetadata; +use red_knot_workspace::db::RootDatabase; +use red_knot_workspace::lint::lint_semantic; +use red_knot_workspace::workspace::WorkspaceMetadata; use ruff_db::files::system_path_to_file; use ruff_db::program::{ProgramSettings, SearchPathSettings, TargetVersion}; use ruff_db::system::{OsSystem, SystemPathBuf}; diff --git a/crates/ruff/src/args.rs b/crates/ruff/src/args.rs index 74448b72a745a..3862c2a0d9cd5 100644 --- a/crates/ruff/src/args.rs +++ b/crates/ruff/src/args.rs @@ -400,9 +400,6 @@ pub struct CheckCommand { conflicts_with = "watch", )] pub show_settings: bool, - /// Dev-only argument to show fixes - #[arg(long, hide = true)] - pub ecosystem_ci: bool, } #[derive(Clone, Debug, clap::Parser)] @@ -662,7 +659,6 @@ impl CheckCommand { let check_arguments = CheckArguments { add_noqa: self.add_noqa, diff: self.diff, - ecosystem_ci: self.ecosystem_ci, exit_non_zero_on_fix: self.exit_non_zero_on_fix, exit_zero: self.exit_zero, files: self.files, @@ -946,7 +942,6 @@ fn resolve_output_format( pub struct CheckArguments { pub add_noqa: bool, pub diff: bool, - pub ecosystem_ci: bool, pub exit_non_zero_on_fix: bool, pub exit_zero: bool, pub files: Vec, diff --git a/crates/ruff/src/lib.rs b/crates/ruff/src/lib.rs index 60823478af974..8ba057cefc2bd 100644 --- a/crates/ruff/src/lib.rs +++ b/crates/ruff/src/lib.rs @@ -287,13 +287,6 @@ pub fn check(args: CheckCommand, global_options: GlobalConfigArgs) -> Result ExitCode { Err(err) => { #[allow(clippy::print_stderr)] { + use std::io::Write; + + // Use `writeln` instead of `eprintln` to avoid panicking when the stderr pipe is broken. + let mut stderr = std::io::stderr().lock(); + // This communicates that this isn't a linter error but ruff itself hard-errored for // some reason (e.g. failed to resolve the configuration) - eprintln!("{}", "ruff failed".red().bold()); + writeln!(stderr, "{}", "ruff failed".red().bold()).ok(); // Currently we generally only see one error, but e.g. with io errors when resolving // the configuration it is help to chain errors ("resolving configuration failed" -> // "failed to read file: subdir/pyproject.toml") for cause in err.chain() { - eprintln!(" {} {cause}", "Cause:".bold()); + writeln!(stderr, " {} {cause}", "Cause:".bold()).ok(); } } ExitStatus::Error.into() diff --git a/crates/ruff/tests/snapshots/show_settings__display_default_settings.snap b/crates/ruff/tests/snapshots/show_settings__display_default_settings.snap index 1f67300804724..cbe6a7bc4fd11 100644 --- a/crates/ruff/tests/snapshots/show_settings__display_default_settings.snap +++ b/crates/ruff/tests/snapshots/show_settings__display_default_settings.snap @@ -232,6 +232,7 @@ linter.flake8_bandit.hardcoded_tmp_directory = [ ] linter.flake8_bandit.check_typed_exception = false linter.flake8_bugbear.extend_immutable_calls = [] +linter.flake8_builtins.builtins_allowed_modules = [] linter.flake8_builtins.builtins_ignorelist = [] linter.flake8_comprehensions.allow_dict_calls_with_keyword_arguments = false linter.flake8_copyright.notice_rgx = (?i)Copyright\s+((?:\(C\)|©)\s+)?\d{4}((-|,\s)\d{4})* diff --git a/crates/ruff_benchmark/Cargo.toml b/crates/ruff_benchmark/Cargo.toml index 763a622988fe7..98cac7185b5ec 100644 --- a/crates/ruff_benchmark/Cargo.toml +++ b/crates/ruff_benchmark/Cargo.toml @@ -50,7 +50,7 @@ ruff_python_ast = { workspace = true } ruff_python_formatter = { workspace = true } ruff_python_parser = { workspace = true } ruff_python_trivia = { workspace = true } -red_knot = { workspace = true } +red_knot_workspace = { workspace = true } [lints] workspace = true diff --git a/crates/ruff_benchmark/benches/red_knot.rs b/crates/ruff_benchmark/benches/red_knot.rs index 07abdafa6b4fa..079bd17200814 100644 --- a/crates/ruff_benchmark/benches/red_knot.rs +++ b/crates/ruff_benchmark/benches/red_knot.rs @@ -2,8 +2,8 @@ use codspeed_criterion_compat::{criterion_group, criterion_main, BatchSize, Criterion}; -use red_knot::db::RootDatabase; -use red_knot::workspace::WorkspaceMetadata; +use red_knot_workspace::db::RootDatabase; +use red_knot_workspace::workspace::WorkspaceMetadata; use ruff_db::files::{system_path_to_file, vendored_path_to_file, File}; use ruff_db::parsed::parsed_module; use ruff_db::program::{ProgramSettings, SearchPathSettings, TargetVersion}; diff --git a/crates/ruff_db/Cargo.toml b/crates/ruff_db/Cargo.toml index f2e3c532ac8bd..6d4ee3ff95c38 100644 --- a/crates/ruff_db/Cargo.toml +++ b/crates/ruff_db/Cargo.toml @@ -24,7 +24,9 @@ countme = { workspace = true } dashmap = { workspace = true } filetime = { workspace = true } ignore = { workspace = true, optional = true } +matchit = { workspace = true } salsa = { workspace = true } +path-slash = { workspace = true } tracing = { workspace = true } rustc-hash = { workspace = true } zip = { workspace = true } diff --git a/crates/ruff_db/src/file_revision.rs b/crates/ruff_db/src/file_revision.rs index 20a25b5f05c53..a12d91a5b3b2f 100644 --- a/crates/ruff_db/src/file_revision.rs +++ b/crates/ruff_db/src/file_revision.rs @@ -15,6 +15,10 @@ impl FileRevision { Self(value) } + pub fn now() -> Self { + Self::from(filetime::FileTime::now()) + } + pub const fn zero() -> Self { Self(0) } diff --git a/crates/ruff_db/src/files.rs b/crates/ruff_db/src/files.rs index 424c876d023a3..2ad371542bd51 100644 --- a/crates/ruff_db/src/files.rs +++ b/crates/ruff_db/src/files.rs @@ -1,35 +1,48 @@ +use std::fmt::Formatter; use std::sync::Arc; use countme::Count; use dashmap::mapref::entry::Entry; +use salsa::{Durability, Setter}; + +pub use file_root::{FileRoot, FileRootKind}; +pub use path::FilePath; +use ruff_notebook::{Notebook, NotebookError}; use crate::file_revision::FileRevision; +use crate::files::file_root::FileRoots; use crate::files::private::FileStatus; use crate::system::{Metadata, SystemPath, SystemPathBuf, SystemVirtualPath, SystemVirtualPathBuf}; use crate::vendored::{VendoredPath, VendoredPathBuf}; -use crate::{Db, FxDashMap}; -pub use path::FilePath; -use ruff_notebook::{Notebook, NotebookError}; +use crate::{vendored, Db, FxDashMap}; +mod file_root; mod path; /// Interns a file system path and returns a salsa `File` ingredient. /// -/// Returns `None` if the path doesn't exist, isn't accessible, or if the path points to a directory. +/// Returns `Err` if the path doesn't exist, isn't accessible, or if the path points to a directory. #[inline] -pub fn system_path_to_file(db: &dyn Db, path: impl AsRef) -> Option { +pub fn system_path_to_file(db: &dyn Db, path: impl AsRef) -> Result { let file = db.files().system(db, path.as_ref()); // It's important that `vfs.file_system` creates a `VfsFile` even for files that don't exist or don't // exist anymore so that Salsa can track that the caller of this function depends on the existence of // that file. This function filters out files that don't exist, but Salsa will know that it must // re-run the calling query whenever the `file`'s status changes (because of the `.status` call here). - file.exists(db).then_some(file) + match file.status(db) { + FileStatus::Exists => Ok(file), + FileStatus::IsADirectory => Err(FileError::IsADirectory), + FileStatus::NotFound => Err(FileError::NotFound), + } } /// Interns a vendored file path. Returns `Some` if the vendored file for `path` exists and `None` otherwise. #[inline] -pub fn vendored_path_to_file(db: &dyn Db, path: impl AsRef) -> Option { +pub fn vendored_path_to_file( + db: &dyn Db, + path: impl AsRef, +) -> Result { db.files().vendored(db, path.as_ref()) } @@ -52,6 +65,9 @@ struct FilesInner { /// Lookup table that maps vendored files to the salsa [`File`] ingredients. vendored_by_path: FxDashMap, + + /// Lookup table that maps file paths to their [`FileRoot`]. + roots: std::sync::RwLock, } impl Files { @@ -60,8 +76,8 @@ impl Files { /// For a non-existing file, creates a new salsa [`File`] ingredient and stores it for future lookups. /// /// The operation always succeeds even if the path doesn't exist on disk, isn't accessible or if the path points to a directory. - /// In these cases, a file with status [`FileStatus::Deleted`] is returned. - #[tracing::instrument(level = "trace", skip(self, db), ret)] + /// In these cases, a file with status [`FileStatus::NotFound`] is returned. + #[tracing::instrument(level = "trace", skip(self, db))] fn system(&self, db: &dyn Db, path: &SystemPath) -> File { let absolute = SystemPath::absolute(path, db.system().current_directory()); @@ -71,25 +87,31 @@ impl Files { .entry(absolute.clone()) .or_insert_with(|| { let metadata = db.system().path_metadata(path); + let durability = self + .root(db, path) + .map_or(Durability::default(), |root| root.durability(db)); - match metadata { - Ok(metadata) if metadata.file_type().is_file() => File::new( - db, - FilePath::System(absolute), + let (permissions, revision, status) = match metadata { + Ok(metadata) if metadata.file_type().is_file() => ( metadata.permissions(), metadata.revision(), FileStatus::Exists, - Count::default(), - ), - _ => File::new( - db, - FilePath::System(absolute), - None, - FileRevision::zero(), - FileStatus::Deleted, - Count::default(), ), - } + Ok(metadata) if metadata.file_type().is_directory() => { + (None, FileRevision::zero(), FileStatus::IsADirectory) + } + _ => (None, FileRevision::zero(), FileStatus::NotFound), + }; + + File::builder( + FilePath::System(absolute), + permissions, + revision, + status, + Count::default(), + ) + .durability(durability) + .new(db) }) } @@ -104,21 +126,28 @@ impl Files { /// Looks up a vendored file by its path. Returns `Some` if a vendored file for the given path /// exists and `None` otherwise. - #[tracing::instrument(level = "trace", skip(self, db), ret)] - fn vendored(&self, db: &dyn Db, path: &VendoredPath) -> Option { + #[tracing::instrument(level = "trace", skip(self, db))] + fn vendored(&self, db: &dyn Db, path: &VendoredPath) -> Result { let file = match self.inner.vendored_by_path.entry(path.to_path_buf()) { Entry::Occupied(entry) => *entry.get(), Entry::Vacant(entry) => { - let metadata = db.vendored().metadata(path).ok()?; - - let file = File::new( - db, + let metadata = match db.vendored().metadata(path) { + Ok(metadata) => match metadata.kind() { + vendored::FileType::File => metadata, + vendored::FileType::Directory => return Err(FileError::IsADirectory), + }, + Err(_) => return Err(FileError::NotFound), + }; + + let file = File::builder( FilePath::Vendored(path.to_path_buf()), Some(0o444), metadata.revision(), FileStatus::Exists, Count::default(), - ); + ) + .durability(Durability::HIGH) + .new(db); entry.insert(file); @@ -126,7 +155,7 @@ impl Files { } }; - Some(file) + Ok(file) } /// Looks up a virtual file by its `path`. @@ -159,6 +188,33 @@ impl Files { Some(file) } + /// Looks up the closest root for `path`. Returns `None` if `path` isn't enclosed by any source root. + /// + /// Roots can be nested, in which case the closest root is returned. + pub fn root(&self, db: &dyn Db, path: &SystemPath) -> Option { + let roots = self.inner.roots.read().unwrap(); + + let absolute = SystemPath::absolute(path, db.system().current_directory()); + roots.at(&absolute) + } + + /// Adds a new root for `path` and returns the root. + /// + /// The root isn't added nor is the file root's kind updated if a root for `path` already exists. + pub fn try_add_root(&self, db: &dyn Db, path: &SystemPath, kind: FileRootKind) -> FileRoot { + let mut roots = self.inner.roots.write().unwrap(); + + let absolute = SystemPath::absolute(path, db.system().current_directory()); + roots.try_add(db, absolute, kind) + } + + /// Updates the revision of the root for `path`. + pub fn touch_root(db: &mut dyn Db, path: &SystemPath) { + if let Some(root) = db.files().root(db, path) { + root.set_revision(db).to(FileRevision::now()); + } + } + /// Refreshes the state of all known files under `path` recursively. /// /// The most common use case is to update the [`Files`] state after removing or moving a directory. @@ -174,8 +230,17 @@ impl Files { let inner = Arc::clone(&db.files().inner); for entry in inner.system_by_path.iter_mut() { if entry.key().starts_with(&path) { - let file = entry.value(); - file.sync(db); + File::sync_system_path(db, entry.key(), Some(*entry.value())); + } + } + + let roots = inner.roots.read().unwrap(); + + for root in roots.all() { + if root.path(db).starts_with(&path) { + root.set_revision(db) + .with_durability(Durability::HIGH) + .to(FileRevision::now()); } } } @@ -192,16 +257,15 @@ impl Files { pub fn sync_all(db: &mut dyn Db) { let inner = Arc::clone(&db.files().inner); for entry in inner.system_by_path.iter_mut() { - let file = entry.value(); - file.sync(db); + File::sync_system_path(db, entry.key(), Some(*entry.value())); } - } - /// Creates a salsa like snapshot. The instances share - /// the same path-to-file mapping. - pub fn snapshot(&self) -> Self { - Self { - inner: self.inner.clone(), + let roots = inner.roots.read().unwrap(); + + for root in roots.all() { + root.set_revision(db) + .with_durability(Durability::HIGH) + .to(FileRevision::now()); } } } @@ -221,7 +285,6 @@ impl std::fmt::Debug for Files { #[salsa::input] pub struct File { /// The path of the file. - #[id] #[return_ref] pub path: FilePath, @@ -294,6 +357,7 @@ impl File { #[tracing::instrument(level = "debug", skip(db))] pub fn sync_path(db: &mut dyn Db, path: &SystemPath) { let absolute = SystemPath::absolute(path, db.system().current_directory()); + Files::touch_root(db, &absolute); Self::sync_system_path(db, &absolute, None); } @@ -304,6 +368,7 @@ impl File { match path { FilePath::System(system) => { + Files::touch_root(db, &system); Self::sync_system_path(db, &system, Some(self)); } FilePath::Vendored(_) => { @@ -320,29 +385,52 @@ impl File { return; }; let metadata = db.system().path_metadata(path); - Self::sync_impl(db, metadata, file); + let durability = db.files().root(db, path).map(|root| root.durability(db)); + Self::sync_impl(db, metadata, file, durability); } fn sync_system_virtual_path(db: &mut dyn Db, path: &SystemVirtualPath, file: File) { let metadata = db.system().virtual_path_metadata(path); - Self::sync_impl(db, metadata, file); + Self::sync_impl(db, metadata, file, None); } /// Private method providing the implementation for [`Self::sync_system_path`] and /// [`Self::sync_system_virtual_path`]. - fn sync_impl(db: &mut dyn Db, metadata: crate::system::Result, file: File) { + fn sync_impl( + db: &mut dyn Db, + metadata: crate::system::Result, + file: File, + durability: Option, + ) { let (status, revision, permission) = match metadata { Ok(metadata) if metadata.file_type().is_file() => ( FileStatus::Exists, metadata.revision(), metadata.permissions(), ), - _ => (FileStatus::Deleted, FileRevision::zero(), None), + Ok(metadata) if metadata.file_type().is_directory() => { + (FileStatus::IsADirectory, FileRevision::zero(), None) + } + _ => (FileStatus::NotFound, FileRevision::zero(), None), }; - file.set_status(db).to(status); - file.set_revision(db).to(revision); - file.set_permissions(db).to(permission); + let durability = durability.unwrap_or_default(); + + if file.status(db) != status { + file.set_status(db).with_durability(durability).to(status); + } + + if file.revision(db) != revision { + file.set_revision(db) + .with_durability(durability) + .to(revision); + } + + if file.permissions(db) != permission { + file.set_permissions(db) + .with_durability(durability) + .to(permission); + } } /// Returns `true` if the file exists. @@ -359,15 +447,35 @@ mod private { /// The file exists. Exists, - /// The file was deleted, didn't exist to begin with or the path isn't a file. - Deleted, + /// The path isn't a file and instead points to a directory. + IsADirectory, + + /// The path doesn't exist, isn't accessible, or no longer exists. + NotFound, } } +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum FileError { + IsADirectory, + NotFound, +} + +impl std::fmt::Display for FileError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + FileError::IsADirectory => f.write_str("Is a directory"), + FileError::NotFound => f.write_str("Not found"), + } + } +} + +impl std::error::Error for FileError {} + #[cfg(test)] mod tests { use crate::file_revision::FileRevision; - use crate::files::{system_path_to_file, vendored_path_to_file}; + use crate::files::{system_path_to_file, vendored_path_to_file, FileError}; use crate::system::DbWithTestSystem; use crate::tests::TestDb; use crate::vendored::tests::VendoredFileSystemBuilder; @@ -393,7 +501,7 @@ mod tests { let test = system_path_to_file(&db, "test.py"); - assert_eq!(test, None); + assert_eq!(test, Err(FileError::NotFound)); } #[test] @@ -435,6 +543,9 @@ mod tests { fn stubbed_vendored_file_non_existing() { let db = TestDb::new(); - assert_eq!(vendored_path_to_file(&db, "test.py"), None); + assert_eq!( + vendored_path_to_file(&db, "test.py"), + Err(FileError::NotFound) + ); } } diff --git a/crates/ruff_db/src/files/file_root.rs b/crates/ruff_db/src/files/file_root.rs new file mode 100644 index 0000000000000..6375655edd820 --- /dev/null +++ b/crates/ruff_db/src/files/file_root.rs @@ -0,0 +1,126 @@ +use std::fmt::Formatter; + +use path_slash::PathExt; +use salsa::Durability; + +use crate::file_revision::FileRevision; +use crate::system::{SystemPath, SystemPathBuf}; +use crate::Db; + +/// A root path for files tracked by the database. +/// +/// We currently create roots for: +/// * static module resolution paths +/// * the workspace root +/// +/// The main usage of file roots is to determine a file's durability. But it can also be used +/// to make a salsa query dependent on whether a file in a root has changed without writing any +/// manual invalidation logic. +#[salsa::input] +pub struct FileRoot { + /// The path of a root is guaranteed to never change. + #[return_ref] + path_buf: SystemPathBuf, + + /// The kind of the root at the time of its creation. + kind_at_time_of_creation: FileRootKind, + + /// A revision that changes when the contents of the source root change. + /// + /// The revision changes when a new file was added, removed, or changed inside this source root. + pub revision: FileRevision, +} + +impl FileRoot { + pub fn path(self, db: &dyn Db) -> &SystemPath { + self.path_buf(db) + } + + pub fn durability(self, db: &dyn Db) -> salsa::Durability { + match self.kind_at_time_of_creation(db) { + FileRootKind::Workspace => salsa::Durability::LOW, + FileRootKind::LibrarySearchPath => salsa::Durability::HIGH, + } + } +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum FileRootKind { + /// The root of a workspace. + Workspace, + + /// A non-workspace module resolution search path. + LibrarySearchPath, +} + +#[derive(Default)] +pub(super) struct FileRoots { + by_path: matchit::Router, + roots: Vec, +} + +impl FileRoots { + /// Tries to add a new root for `path` and returns the root. + /// + /// The root isn't added nor is the file root's kind updated if a root for `path` already exists. + pub(super) fn try_add( + &mut self, + db: &dyn Db, + path: SystemPathBuf, + kind: FileRootKind, + ) -> FileRoot { + // SAFETY: Guaranteed to succeed because `path` is a UTF-8 that only contains Unicode characters. + let normalized_path = path.as_std_path().to_slash().unwrap(); + + if let Ok(existing) = self.by_path.at(&normalized_path) { + // Only if it is an exact match + if existing.value.path(db) == &*path { + return *existing.value; + } + } + + // normalize the path to use `/` separators and escape the '{' and '}' characters, + // which matchit uses for routing parameters + let mut route = normalized_path.replace('{', "{{").replace('}', "}}"); + + // Insert a new source root + let root = FileRoot::builder(path, kind, FileRevision::now()) + .durability(Durability::HIGH) + .new(db); + + // Insert a path that matches the root itself + self.by_path.insert(route.clone(), root).unwrap(); + + // Insert a path that matches all subdirectories and files + route.push_str("/{*filepath}"); + + self.by_path.insert(route, root).unwrap(); + self.roots.push(root); + + root + } + + /// Returns the closest root for `path` or `None` if no root contains `path`. + pub(super) fn at(&self, path: &SystemPath) -> Option { + // SAFETY: Guaranteed to succeed because `path` is a UTF-8 that only contains Unicode characters. + let normalized_path = path.as_std_path().to_slash().unwrap(); + let entry = self.by_path.at(&normalized_path).ok()?; + Some(*entry.value) + } + + pub(super) fn all(&self) -> impl Iterator + '_ { + self.roots.iter().copied() + } +} + +impl std::fmt::Debug for FileRoots { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("FileRoots").field(&self.roots).finish() + } +} + +impl PartialEq for FileRoots { + fn eq(&self, other: &Self) -> bool { + self.roots.eq(&other.roots) + } +} diff --git a/crates/ruff_db/src/files/path.rs b/crates/ruff_db/src/files/path.rs index a1c3530ab0b45..816eaf461a3db 100644 --- a/crates/ruff_db/src/files/path.rs +++ b/crates/ruff_db/src/files/path.rs @@ -95,8 +95,8 @@ impl FilePath { #[inline] pub fn to_file(&self, db: &dyn Db) -> Option { match self { - FilePath::System(path) => system_path_to_file(db, path), - FilePath::Vendored(path) => vendored_path_to_file(db, path), + FilePath::System(path) => system_path_to_file(db, path).ok(), + FilePath::Vendored(path) => vendored_path_to_file(db, path).ok(), FilePath::SystemVirtual(_) => None, } } diff --git a/crates/ruff_db/src/lib.rs b/crates/ruff_db/src/lib.rs index a7fe6051f1c96..62494dd24352f 100644 --- a/crates/ruff_db/src/lib.rs +++ b/crates/ruff_db/src/lib.rs @@ -1,12 +1,8 @@ use std::hash::BuildHasherDefault; -use program::Program; use rustc_hash::FxHasher; -use salsa::DbWithJar; -use crate::files::{File, Files}; -use crate::parsed::parsed_module; -use crate::source::{line_index, source_text}; +use crate::files::Files; use crate::system::System; use crate::vendored::VendoredFileSystem; @@ -22,11 +18,9 @@ pub mod vendored; pub type FxDashMap = dashmap::DashMap>; pub type FxDashSet = dashmap::DashSet>; -#[salsa::jar(db=Db)] -pub struct Jar(File, Program, source_text, line_index, parsed_module); - /// Most basic database that gives access to files, the host system, source code, and parsed AST. -pub trait Db: DbWithJar { +#[salsa::db] +pub trait Db: salsa::Database { fn vendored(&self) -> &VendoredFileSystem; fn system(&self) -> &dyn System; fn files(&self) -> &Files; @@ -42,19 +36,17 @@ pub trait Upcast { mod tests { use std::sync::Arc; - use salsa::DebugWithDb; - use crate::files::Files; use crate::system::TestSystem; use crate::system::{DbWithTestSystem, System}; use crate::vendored::VendoredFileSystem; - use crate::{Db, Jar}; + use crate::Db; /// Database that can be used for testing. /// /// Uses an in memory filesystem and it stubs out the vendored files by default. + #[salsa::db] #[derive(Default)] - #[salsa::db(Jar)] pub(crate) struct TestDb { storage: salsa::Storage, files: Files, @@ -101,6 +93,7 @@ mod tests { } } + #[salsa::db] impl Db for TestDb { fn vendored(&self) -> &VendoredFileSystem { &self.vendored @@ -125,23 +118,14 @@ mod tests { } } + #[salsa::db] impl salsa::Database for TestDb { fn salsa_event(&self, event: salsa::Event) { - tracing::trace!("event: {:?}", event.debug(self)); - let mut events = self.events.lock().unwrap(); - events.push(event); - } - } - - impl salsa::ParallelDatabase for TestDb { - fn snapshot(&self) -> salsa::Snapshot { - salsa::Snapshot::new(Self { - storage: self.storage.snapshot(), - system: self.system.snapshot(), - files: self.files.snapshot(), - events: self.events.clone(), - vendored: self.vendored.snapshot(), - }) + salsa::Database::attach(self, |_| { + tracing::trace!("event: {:?}", event); + let mut events = self.events.lock().unwrap(); + events.push(event); + }); } } } diff --git a/crates/ruff_db/src/parsed.rs b/crates/ruff_db/src/parsed.rs index 3f621cd36b088..90afb1fa7ba36 100644 --- a/crates/ruff_db/src/parsed.rs +++ b/crates/ruff_db/src/parsed.rs @@ -22,7 +22,7 @@ use crate::Db; /// for determining if a query result is unchanged. #[salsa::tracked(return_ref, no_eq)] pub fn parsed_module(db: &dyn Db, file: File) -> ParsedModule { - let _span = tracing::trace_span!("parse_module", file = ?file).entered(); + let _span = tracing::trace_span!("parse_module", file = ?file.path(db)).entered(); let source = source_text(db, file); let path = file.path(db); @@ -41,7 +41,7 @@ pub fn parsed_module(db: &dyn Db, file: File) -> ParsedModule { } /// Cheap cloneable wrapper around the parsed module. -#[derive(Clone, PartialEq)] +#[derive(Clone)] pub struct ParsedModule { inner: Arc>, } diff --git a/crates/ruff_db/src/program.rs b/crates/ruff_db/src/program.rs index 01ac910d4e0af..c5cdc30de64fd 100644 --- a/crates/ruff_db/src/program.rs +++ b/crates/ruff_db/src/program.rs @@ -1,7 +1,5 @@ -// TODO: Fix clippy warnings in Salsa macros -#![allow(clippy::needless_lifetimes, clippy::clone_on_copy)] - use crate::{system::SystemPathBuf, Db}; +use salsa::Durability; #[salsa::input(singleton)] pub struct Program { @@ -13,7 +11,9 @@ pub struct Program { impl Program { pub fn from_settings(db: &dyn Db, settings: ProgramSettings) -> Self { - Program::new(db, settings.target_version, settings.search_paths) + Program::builder(settings.target_version, settings.search_paths) + .durability(Durability::HIGH) + .new(db) } } diff --git a/crates/ruff_db/src/source.rs b/crates/ruff_db/src/source.rs index 9f147dc15d560..3bebac8e5778d 100644 --- a/crates/ruff_db/src/source.rs +++ b/crates/ruff_db/src/source.rs @@ -2,7 +2,6 @@ use std::ops::Deref; use std::sync::Arc; use countme::Count; -use salsa::DebugWithDb; use ruff_notebook::Notebook; use ruff_python_ast::PySourceType; @@ -14,9 +13,10 @@ use crate::Db; /// Reads the source text of a python text file (must be valid UTF8) or notebook. #[salsa::tracked] pub fn source_text(db: &dyn Db, file: File) -> SourceText { - let _span = tracing::trace_span!("source_text", ?file).entered(); + let path = file.path(db); + let _span = tracing::trace_span!("source_text", file=?path).entered(); - let is_notebook = match file.path(db) { + let is_notebook = match path { FilePath::System(system) => system.extension().is_some_and(|extension| { PySourceType::try_from_extension(extension) == Some(PySourceType::Ipynb) }), @@ -129,7 +129,7 @@ enum SourceTextKind { /// Computes the [`LineIndex`] for `file`. #[salsa::tracked] pub fn line_index(db: &dyn Db, file: File) -> LineIndex { - let _span = tracing::trace_span!("line_index", file = ?file.debug(db)).entered(); + let _span = tracing::trace_span!("line_index", file = ?file).entered(); let source = source_text(db, file); @@ -139,6 +139,7 @@ pub fn line_index(db: &dyn Db, file: File) -> LineIndex { #[cfg(test)] mod tests { use salsa::EventKind; + use salsa::Setter as _; use ruff_source_file::OneIndexed; use ruff_text_size::TextSize; diff --git a/crates/ruff_db/src/system.rs b/crates/ruff_db/src/system.rs index ca7d4cb74805a..ed1cea552d7ab 100644 --- a/crates/ruff_db/src/system.rs +++ b/crates/ruff_db/src/system.rs @@ -39,6 +39,8 @@ pub type Result = std::io::Result; /// Abstracting the system also enables tests to use a more efficient in-memory file system. pub trait System: Debug { /// Reads the metadata of the file or directory at `path`. + /// + /// This function will traverse symbolic links to query information about the destination file. fn path_metadata(&self, path: &SystemPath) -> Result; /// Reads the content of the file at `path` into a [`String`]. diff --git a/crates/ruff_db/src/system/memory_fs.rs b/crates/ruff_db/src/system/memory_fs.rs index 300ac2daee3eb..3754a5b9c26a6 100644 --- a/crates/ruff_db/src/system/memory_fs.rs +++ b/crates/ruff_db/src/system/memory_fs.rs @@ -68,13 +68,6 @@ impl MemoryFileSystem { &self.inner.cwd } - #[must_use] - pub fn snapshot(&self) -> Self { - Self { - inner: self.inner.clone(), - } - } - pub fn metadata(&self, path: impl AsRef) -> Result { fn metadata(fs: &MemoryFileSystem, path: &SystemPath) -> Result { let by_path = fs.inner.by_path.read().unwrap(); @@ -214,7 +207,9 @@ impl MemoryFileSystem { let normalized = self.normalize_path(path.as_ref()); - get_or_create_file(&mut by_path, &normalized)?.content = content.to_string(); + let file = get_or_create_file(&mut by_path, &normalized)?; + file.content = content.to_string(); + file.last_modified = FileTime::now(); Ok(()) } diff --git a/crates/ruff_db/src/system/os.rs b/crates/ruff_db/src/system/os.rs index 30ea7840892b1..0a0102d6c3f2c 100644 --- a/crates/ruff_db/src/system/os.rs +++ b/crates/ruff_db/src/system/os.rs @@ -49,12 +49,6 @@ impl OsSystem { fn permissions(_metadata: &std::fs::Metadata) -> Option { None } - - pub fn snapshot(&self) -> Self { - Self { - inner: self.inner.clone(), - } - } } impl System for OsSystem { diff --git a/crates/ruff_db/src/system/test.rs b/crates/ruff_db/src/system/test.rs index 85842886a4dc2..c5f6dd4060952 100644 --- a/crates/ruff_db/src/system/test.rs +++ b/crates/ruff_db/src/system/test.rs @@ -1,3 +1,7 @@ +use std::any::Any; +use std::panic::RefUnwindSafe; +use std::sync::Arc; + use ruff_notebook::{Notebook, NotebookError}; use ruff_python_trivia::textwrap; @@ -6,9 +10,6 @@ use crate::system::{ DirectoryEntry, MemoryFileSystem, Metadata, Result, System, SystemPath, SystemVirtualPath, }; use crate::Db; -use std::any::Any; -use std::panic::RefUnwindSafe; -use std::sync::Arc; use super::walk_directory::WalkDirectoryBuilder; @@ -25,12 +26,6 @@ pub struct TestSystem { } impl TestSystem { - pub fn snapshot(&self) -> Self { - Self { - inner: self.inner.snapshot(), - } - } - /// Returns the memory file system. /// /// ## Panics @@ -235,15 +230,6 @@ enum TestSystemInner { System(Arc), } -impl TestSystemInner { - fn snapshot(&self) -> Self { - match self { - Self::Stub(system) => Self::Stub(system.snapshot()), - Self::System(system) => Self::System(Arc::clone(system)), - } - } -} - impl Default for TestSystemInner { fn default() -> Self { Self::Stub(MemoryFileSystem::default()) diff --git a/crates/ruff_db/src/testing.rs b/crates/ruff_db/src/testing.rs index 06f4f96713463..2624390ee0ec7 100644 --- a/crates/ruff_db/src/testing.rs +++ b/crates/ruff_db/src/testing.rs @@ -1,116 +1,228 @@ //! Test helpers for working with Salsa databases -use std::fmt; -use std::marker::PhantomData; - -use salsa::id::AsId; -use salsa::ingredient::Ingredient; -use salsa::storage::HasIngredientsFor; - -/// Assert that the Salsa query described by the generic parameter `C` -/// was executed at least once with the input `input` -/// in the history span represented by `events`. -pub fn assert_function_query_was_run<'db, C, Db, Jar>( - db: &'db Db, - to_function: impl FnOnce(&C) -> &salsa::function::FunctionIngredient, - input: &C::Input<'db>, +pub fn assert_function_query_was_not_run( + db: &Db, + query: Q, + input: I, events: &[salsa::Event], ) where - C: salsa::function::Configuration - + salsa::storage::IngredientsFor, - Jar: HasIngredientsFor, - Db: salsa::DbWithJar, - C::Input<'db>: AsId, + Db: salsa::Database, + Q: Fn(QDb, I) -> R, + I: salsa::plumbing::AsId + std::fmt::Debug + Copy, { - function_query_was_run(db, to_function, input, events, true); + let id = input.as_id().as_u32(); + let (query_name, will_execute_event) = find_will_execute_event(db, query, input, events); + + db.attach(|_| { + if let Some(will_execute_event) = will_execute_event { + panic!("Expected query {query_name}({id}) not to have run but it did: {will_execute_event:?}"); + } + }); } -/// Assert that there were no executions with the input `input` -/// of the Salsa query described by the generic parameter `C` -/// in the history span represented by `events`. -pub fn assert_function_query_was_not_run<'db, C, Db, Jar>( - db: &'db Db, - to_function: impl FnOnce(&C) -> &salsa::function::FunctionIngredient, - input: &C::Input<'db>, +pub fn assert_const_function_query_was_not_run( + db: &Db, + query: Q, events: &[salsa::Event], ) where - C: salsa::function::Configuration - + salsa::storage::IngredientsFor, - Jar: HasIngredientsFor, - Db: salsa::DbWithJar, - C::Input<'db>: AsId, + Db: salsa::Database, + Q: Fn(QDb) -> R, { - function_query_was_run(db, to_function, input, events, false); + let (query_name, will_execute_event) = find_will_execute_event(db, query, (), events); + + db.attach(|_| { + if let Some(will_execute_event) = will_execute_event { + panic!( + "Expected query {query_name}() not to have run but it did: {will_execute_event:?}" + ); + } + }); } -fn function_query_was_run<'db, C, Db, Jar>( - db: &'db Db, - to_function: impl FnOnce(&C) -> &salsa::function::FunctionIngredient, - input: &C::Input<'db>, +/// Assert that the Salsa query described by the generic parameter `C` +/// was executed at least once with the input `input` +/// in the history span represented by `events`. +pub fn assert_function_query_was_run( + db: &Db, + query: Q, + input: I, events: &[salsa::Event], - should_have_run: bool, ) where - C: salsa::function::Configuration - + salsa::storage::IngredientsFor, - Jar: HasIngredientsFor, - Db: salsa::DbWithJar, - C::Input<'db>: AsId, + Db: salsa::Database, + Q: Fn(QDb, I) -> R, + I: salsa::plumbing::AsId + std::fmt::Debug + Copy, { - let (jar, _) = - <_ as salsa::storage::HasJar<::Jar>>::jar(db); - let ingredient = jar.ingredient(); - - let function_ingredient = to_function(ingredient); + let id = input.as_id().as_u32(); + let (query_name, will_execute_event) = find_will_execute_event(db, query, input, events); - let ingredient_index = - as Ingredient>::ingredient_index( - function_ingredient, + db.attach(|_| { + assert!( + will_execute_event.is_some(), + "Expected query {query_name}({id:?}) to have run but it did not:\n{events:#?}" ); + }); +} - let did_run = events.iter().any(|event| { +pub fn find_will_execute_event<'a, Q, I>( + db: &dyn salsa::Database, + query: Q, + input: I, + events: &'a [salsa::Event], +) -> (&'static str, Option<&'a salsa::Event>) +where + I: salsa::plumbing::AsId, +{ + let query_name = query_name(&query); + + let event = events.iter().find(|event| { if let salsa::EventKind::WillExecute { database_key } = event.kind { - database_key.ingredient_index() == ingredient_index + db.lookup_ingredient(database_key.ingredient_index()) + .debug_name() + == query_name && database_key.key_index() == input.as_id() } else { false } }); - if should_have_run && !did_run { - panic!( - "Expected query {:?} to have run but it didn't", - DebugIdx { - db: PhantomData::, - value_id: input.as_id(), - ingredient: function_ingredient, - } - ); - } else if !should_have_run && did_run { - panic!( - "Expected query {:?} not to have run but it did", - DebugIdx { - db: PhantomData::, - value_id: input.as_id(), - ingredient: function_ingredient, - } - ); + (query_name, event) +} + +fn query_name(_query: &Q) -> &'static str { + let full_qualified_query_name = std::any::type_name::(); + full_qualified_query_name + .rsplit_once("::") + .map(|(_, name)| name) + .unwrap_or(full_qualified_query_name) +} + +#[test] +fn query_was_not_run() { + use crate::tests::TestDb; + use salsa::prelude::*; + + #[salsa::input] + struct Input { + text: String, + } + + #[salsa::tracked] + fn len(db: &dyn salsa::Database, input: Input) -> usize { + input.text(db).len() } + + let mut db = TestDb::new(); + + let hello = Input::new(&db, "Hello, world!".to_string()); + let goodbye = Input::new(&db, "Goodbye!".to_string()); + + assert_eq!(len(&db, hello), 13); + assert_eq!(len(&db, goodbye), 8); + + // Change the input of one query + goodbye.set_text(&mut db).to("Bye".to_string()); + db.clear_salsa_events(); + + assert_eq!(len(&db, goodbye), 3); + let events = db.take_salsa_events(); + + assert_function_query_was_run(&db, len, goodbye, &events); + assert_function_query_was_not_run(&db, len, hello, &events); } -struct DebugIdx<'a, I, Db> -where - I: Ingredient, -{ - value_id: salsa::Id, - ingredient: &'a I, - db: PhantomData, +#[test] +#[should_panic(expected = "Expected query len(0) not to have run but it did:")] +fn query_was_not_run_fails_if_query_was_run() { + use crate::tests::TestDb; + use salsa::prelude::*; + + #[salsa::input] + struct Input { + text: String, + } + + #[salsa::tracked] + fn len(db: &dyn salsa::Database, input: Input) -> usize { + input.text(db).len() + } + + let mut db = TestDb::new(); + + let hello = Input::new(&db, "Hello, world!".to_string()); + + assert_eq!(len(&db, hello), 13); + + // Change the input + hello.set_text(&mut db).to("Hy".to_string()); + db.clear_salsa_events(); + + assert_eq!(len(&db, hello), 2); + let events = db.take_salsa_events(); + + assert_function_query_was_not_run(&db, len, hello, &events); } -impl<'a, I, Db> fmt::Debug for DebugIdx<'a, I, Db> -where - I: Ingredient, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result { - self.ingredient.fmt_index(Some(self.value_id), f) +#[test] +#[should_panic(expected = "Expected query len() not to have run but it did:")] +fn const_query_was_not_run_fails_if_query_was_run() { + use crate::tests::TestDb; + use salsa::prelude::*; + + #[salsa::input] + struct Input { + text: String, + } + + #[salsa::tracked] + fn len(db: &dyn salsa::Database) -> usize { + db.report_untracked_read(); + 5 + } + + let mut db = TestDb::new(); + let hello = Input::new(&db, "Hello, world!".to_string()); + assert_eq!(len(&db), 5); + + // Create a new revision + db.clear_salsa_events(); + hello.set_text(&mut db).to("Hy".to_string()); + + assert_eq!(len(&db), 5); + let events = db.take_salsa_events(); + + assert_const_function_query_was_not_run(&db, len, &events); +} + +#[test] +#[should_panic(expected = "Expected query len(0) to have run but it did not:")] +fn query_was_run_fails_if_query_was_not_run() { + use crate::tests::TestDb; + use salsa::prelude::*; + + #[salsa::input] + struct Input { + text: String, } + + #[salsa::tracked] + fn len(db: &dyn salsa::Database, input: Input) -> usize { + input.text(db).len() + } + + let mut db = TestDb::new(); + + let hello = Input::new(&db, "Hello, world!".to_string()); + let goodbye = Input::new(&db, "Goodbye!".to_string()); + + assert_eq!(len(&db, hello), 13); + assert_eq!(len(&db, goodbye), 8); + + // Change the input of one query + goodbye.set_text(&mut db).to("Bye".to_string()); + db.clear_salsa_events(); + + assert_eq!(len(&db, goodbye), 3); + let events = db.take_salsa_events(); + + assert_function_query_was_run(&db, len, hello, &events); } diff --git a/crates/ruff_db/src/vendored.rs b/crates/ruff_db/src/vendored.rs index 27f03163ef91c..5cd462d55a873 100644 --- a/crates/ruff_db/src/vendored.rs +++ b/crates/ruff_db/src/vendored.rs @@ -20,6 +20,7 @@ type LockedZipArchive<'a> = MutexGuard<'a, VendoredZipArchive>; /// /// "Files" in the `VendoredFileSystem` are read-only and immutable. /// Directories are supported, but symlinks and hardlinks cannot exist. +#[derive(Clone)] pub struct VendoredFileSystem { inner: Arc>, } @@ -39,12 +40,6 @@ impl VendoredFileSystem { }) } - pub fn snapshot(&self) -> Self { - Self { - inner: Arc::clone(&self.inner), - } - } - pub fn exists(&self, path: impl AsRef) -> bool { fn exists(fs: &VendoredFileSystem, path: &VendoredPath) -> bool { let normalized = NormalizedVendoredPath::from(path); diff --git a/crates/ruff_formatter/src/lib.rs b/crates/ruff_formatter/src/lib.rs index 3e26166adec25..0a81cb121902e 100644 --- a/crates/ruff_formatter/src/lib.rs +++ b/crates/ruff_formatter/src/lib.rs @@ -7,10 +7,10 @@ //! //! * [`Format`]: Implemented by objects that can be formatted. //! * [`FormatRule`]: Rule that knows how to format an object of another type. Useful in the situation where -//! it's necessary to implement [Format] on an object from another crate. This module defines the -//! [`FormatRefWithRule`] and [`FormatOwnedWithRule`] structs to pass an item with its corresponding rule. +//! it's necessary to implement [Format] on an object from another crate. This module defines the +//! [`FormatRefWithRule`] and [`FormatOwnedWithRule`] structs to pass an item with its corresponding rule. //! * [`FormatWithRule`] implemented by objects that know how to format another type. Useful for implementing -//! some reusable formatting logic inside of this module if the type itself doesn't implement [Format] +//! some reusable formatting logic inside of this module if the type itself doesn't implement [Format] //! //! ## Formatting Macros //! diff --git a/crates/ruff_formatter/src/printer/mod.rs b/crates/ruff_formatter/src/printer/mod.rs index 853d301d34317..cb896168e0df4 100644 --- a/crates/ruff_formatter/src/printer/mod.rs +++ b/crates/ruff_formatter/src/printer/mod.rs @@ -982,7 +982,7 @@ impl Indentation { /// The behaviour depends on the [`indent_style`][IndentStyle] if this is an [`Indent::Align`]: /// - **Tabs**: `align` is converted into an indent. This results in `level` increasing by two: once for the align, once for the level increment /// - **Spaces**: Increments the `level` by one and keeps the `align` unchanged. - /// Keeps any the current value is [`Indent::Align`] and increments the level by one. + /// Keeps any the current value is [`Indent::Align`] and increments the level by one. fn increment_level(self, indent_style: IndentStyle) -> Self { match self { Indentation::Level(count) => Indentation::Level(count + 1), diff --git a/crates/ruff_linter/Cargo.toml b/crates/ruff_linter/Cargo.toml index c11ba0b9eee79..b98ee74d72f57 100644 --- a/crates/ruff_linter/Cargo.toml +++ b/crates/ruff_linter/Cargo.toml @@ -69,6 +69,7 @@ toml = { workspace = true } typed-arena = { workspace = true } unicode-width = { workspace = true } unicode_names2 = { workspace = true } +unicode-normalization = { workspace = true } url = { workspace = true } [dev-dependencies] diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_async/ASYNC100.py b/crates/ruff_linter/resources/test/fixtures/flake8_async/ASYNC100.py index 24d89f49225cd..8371d2e2a5a28 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_async/ASYNC100.py +++ b/crates/ruff_linter/resources/test/fixtures/flake8_async/ASYNC100.py @@ -4,56 +4,61 @@ async def func(): - with trio.fail_after(): + async with trio.fail_after(): ... async def func(): - with trio.fail_at(): + async with trio.fail_at(): await ... async def func(): - with trio.move_on_after(): + async with trio.move_on_after(): ... async def func(): - with trio.move_at(): + async with trio.move_at(): await ... async def func(): - with trio.move_at(): - async with trio.open_nursery() as nursery: + async with trio.move_at(): + async with trio.open_nursery(): ... async def func(): - with anyio.move_on_after(): + async with anyio.move_on_after(delay=0.2): ... async def func(): - with anyio.fail_after(): + async with anyio.fail_after(): ... async def func(): - with anyio.CancelScope(): + async with anyio.CancelScope(): ... async def func(): - with anyio.CancelScope(): + async with anyio.CancelScope(): ... async def func(): - with asyncio.timeout(): + async with asyncio.timeout(delay=0.2): ... async def func(): - with asyncio.timeout_at(): + async with asyncio.timeout_at(when=0.2): + ... + + +async def func(): + async with asyncio.timeout(delay=0.2), asyncio.TaskGroup(): ... diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_bugbear/B019.py b/crates/ruff_linter/resources/test/fixtures/flake8_bugbear/B019.py index bbca28a563da2..e8a5a50f3305d 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_bugbear/B019.py +++ b/crates/ruff_linter/resources/test/fixtures/flake8_bugbear/B019.py @@ -118,3 +118,9 @@ class Foo(enum.Enum): @functools.cache def bar(self, arg: str) -> str: return f"{self} - {arg}" + + +class Metaclass(type): + @functools.lru_cache + def lru_cached_instance_method_on_metaclass(cls, x: int): + ... diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_builtins/A004.py b/crates/ruff_linter/resources/test/fixtures/flake8_builtins/A004.py new file mode 100644 index 0000000000000..ed07e5502294d --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/flake8_builtins/A004.py @@ -0,0 +1,5 @@ +import some as sum +import float +from some import other as int +from some import input, exec +from directory import new as dir diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_builtins/A005/modules/logging/__init__.py b/crates/ruff_linter/resources/test/fixtures/flake8_builtins/A005/modules/logging/__init__.py new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_builtins/A005/modules/non_builtin/__init__.py b/crates/ruff_linter/resources/test/fixtures/flake8_builtins/A005/modules/non_builtin/__init__.py new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_builtins/A005/modules/package/__init__.py b/crates/ruff_linter/resources/test/fixtures/flake8_builtins/A005/modules/package/__init__.py new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_builtins/A005/modules/package/bisect.py b/crates/ruff_linter/resources/test/fixtures/flake8_builtins/A005/modules/package/bisect.py new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_builtins/A005/modules/package/xml.py b/crates/ruff_linter/resources/test/fixtures/flake8_builtins/A005/modules/package/xml.py new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_builtins/A005/modules/string/__init__.py b/crates/ruff_linter/resources/test/fixtures/flake8_builtins/A005/modules/string/__init__.py new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_builtins/A006.py b/crates/ruff_linter/resources/test/fixtures/flake8_builtins/A006.py new file mode 100644 index 0000000000000..629ce4165ccc2 --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/flake8_builtins/A006.py @@ -0,0 +1,5 @@ +lambda print, copyright: print +lambda x, float, y: x + y +lambda min, max: min +lambda id: id +lambda dir: dir diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_return/RET501.py b/crates/ruff_linter/resources/test/fixtures/flake8_return/RET501.py index 57e814d70d6bd..70346bef98686 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_return/RET501.py +++ b/crates/ruff_linter/resources/test/fixtures/flake8_return/RET501.py @@ -17,3 +17,35 @@ def get(self, key: str) -> None: def prop(self) -> None: print("Property not found") return None + + +from functools import cached_property + + +class BaseCache2: + @cached_property + def prop(self) -> None: + print("Property not found") + return None + + +import abc +import enum +import types + + +class Baz: + @abc.abstractproperty + def prop2(self) -> None: + print("Override me") + return None + + @types.DynamicClassAttribute + def prop3(self) -> None: + print("Gotta make this a multiline function for it to be a meaningful test") + return None + + @enum.property + def prop4(self) -> None: + print("I've run out of things to say") + return None diff --git a/crates/ruff_linter/resources/test/fixtures/pycodestyle/E30.py b/crates/ruff_linter/resources/test/fixtures/pycodestyle/E30.py index 2fdc72c2e3835..e134c3e001538 100644 --- a/crates/ruff_linter/resources/test/fixtures/pycodestyle/E30.py +++ b/crates/ruff_linter/resources/test/fixtures/pycodestyle/E30.py @@ -935,3 +935,42 @@ def arrow_strip_whitespace(obj: Array, /, *cols: str) -> Array: ... # type: ign def arrow_strip_whitespace(obj, /, *cols): ... # end + + +# E302 +def test_update(): + pass + # comment +def test_clientmodel(): + pass +# end + + +# E302 +def test_update(): + pass + # comment +def test_clientmodel(): + pass +# end + + +# E302 +def test_update(): + pass +# comment +def test_clientmodel(): + pass +# end + + +# E305 + +class A: + pass + +# ====== Cool constants ======== +BANANA = 100 +APPLE = 200 + +# end diff --git a/crates/ruff_linter/resources/test/fixtures/pydoclint/DOC201_google.py b/crates/ruff_linter/resources/test/fixtures/pydoclint/DOC201_google.py index 800ed3ed9c503..ccb9a76560088 100644 --- a/crates/ruff_linter/resources/test/fixtures/pydoclint/DOC201_google.py +++ b/crates/ruff_linter/resources/test/fixtures/pydoclint/DOC201_google.py @@ -71,3 +71,18 @@ def nested(): return 5 print("I never return") + + +from functools import cached_property + +class Baz: + # OK + @cached_property + def baz(self) -> str: + """ + Do something + + Args: + num (int): A number + """ + return 'test' diff --git a/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_30.py b/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_30.py new file mode 100644 index 0000000000000..5d2e4bd119adc --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/pyflakes/F401_30.py @@ -0,0 +1,6 @@ +""" +Test: ensure we're able to correctly remove unused imports +even if they have characters in them that undergo NFKC normalization +""" + +from .main import MaµToMan diff --git a/crates/ruff_linter/resources/test/fixtures/pylint/property_with_parameters.py b/crates/ruff_linter/resources/test/fixtures/pylint/property_with_parameters.py index dba24c4a1e388..4b6a076e6e81b 100644 --- a/crates/ruff_linter/resources/test/fixtures/pylint/property_with_parameters.py +++ b/crates/ruff_linter/resources/test/fixtures/pylint/property_with_parameters.py @@ -38,3 +38,31 @@ def attribute_var_args(self, *args): # [property-with-parameters] @property def attribute_var_kwargs(self, **kwargs): #[property-with-parameters] return {key: value * 2 for key, value in kwargs.items()} + + +from functools import cached_property + + +class Cached: + @cached_property + def cached_prop(self, value): # [property-with-parameters] + ... + + +import abc +import enum +import types + + +class Baz: + @abc.abstractproperty + def prop2(self, param) -> None: # [property-with-parameters] + return None + + @types.DynamicClassAttribute + def prop3(self, param) -> None: # [property-with-parameters] + return None + + @enum.property + def prop4(self, param) -> None: # [property-with-parameters] + return None diff --git a/crates/ruff_linter/resources/test/fixtures/pylint/unnecessary_list_index_lookup.py b/crates/ruff_linter/resources/test/fixtures/pylint/unnecessary_list_index_lookup.py index 8911c8bd26c96..43fe6964475ec 100644 --- a/crates/ruff_linter/resources/test/fixtures/pylint/unnecessary_list_index_lookup.py +++ b/crates/ruff_linter/resources/test/fixtures/pylint/unnecessary_list_index_lookup.py @@ -62,3 +62,17 @@ def value_intentionally_unused(): print(letters[index]) # OK blah = letters[index] # OK letters[index] = "d" # OK + + +def start(): + # OK + for index, list_item in enumerate(some_list, start=1): + print(some_list[index]) + + # PLR1736 + for index, list_item in enumerate(some_list, start=0): + print(some_list[index]) + + # PLR1736 + for index, list_item in enumerate(some_list): + print(some_list[index]) diff --git a/crates/ruff_linter/src/checkers/ast/analyze/deferred_lambdas.rs b/crates/ruff_linter/src/checkers/ast/analyze/deferred_lambdas.rs index 421a972e760bb..83af7589a2c77 100644 --- a/crates/ruff_linter/src/checkers/ast/analyze/deferred_lambdas.rs +++ b/crates/ruff_linter/src/checkers/ast/analyze/deferred_lambdas.rs @@ -2,7 +2,7 @@ use ruff_python_ast::Expr; use crate::checkers::ast::Checker; use crate::codes::Rule; -use crate::rules::{flake8_pie, pylint, refurb}; +use crate::rules::{flake8_builtins, flake8_pie, pylint, refurb}; /// Run lint rules over all deferred lambdas in the [`SemanticModel`]. pub(crate) fn deferred_lambdas(checker: &mut Checker) { @@ -24,6 +24,9 @@ pub(crate) fn deferred_lambdas(checker: &mut Checker) { if checker.enabled(Rule::ReimplementedOperator) { refurb::rules::reimplemented_operator(checker, &lambda.into()); } + if checker.enabled(Rule::BuiltinLambdaArgumentShadowing) { + flake8_builtins::rules::builtin_lambda_argument_shadowing(checker, lambda); + } } } } diff --git a/crates/ruff_linter/src/checkers/ast/analyze/definitions.rs b/crates/ruff_linter/src/checkers/ast/analyze/definitions.rs index 1d5ad3892e4a9..2f1dcda09e953 100644 --- a/crates/ruff_linter/src/checkers/ast/analyze/definitions.rs +++ b/crates/ruff_linter/src/checkers/ast/analyze/definitions.rs @@ -176,7 +176,7 @@ pub(crate) fn definitions(checker: &mut Checker) { if enforce_docstrings || enforce_pydoclint { if pydocstyle::helpers::should_ignore_definition( definition, - &checker.settings.pydocstyle.ignore_decorators, + &checker.settings.pydocstyle, &checker.semantic, ) { continue; @@ -273,7 +273,7 @@ pub(crate) fn definitions(checker: &mut Checker) { pydocstyle::rules::non_imperative_mood( checker, &docstring, - &checker.settings.pydocstyle.property_decorators, + &checker.settings.pydocstyle, ); } if checker.enabled(Rule::NoSignature) { @@ -312,7 +312,7 @@ pub(crate) fn definitions(checker: &mut Checker) { if enforce_sections || enforce_pydoclint { let section_contexts = pydocstyle::helpers::get_section_contexts( &docstring, - checker.settings.pydocstyle.convention.as_ref(), + checker.settings.pydocstyle.convention(), ); if enforce_sections { @@ -320,7 +320,7 @@ pub(crate) fn definitions(checker: &mut Checker) { checker, &docstring, §ion_contexts, - checker.settings.pydocstyle.convention.as_ref(), + checker.settings.pydocstyle.convention(), ); } @@ -329,7 +329,7 @@ pub(crate) fn definitions(checker: &mut Checker) { checker, definition, §ion_contexts, - checker.settings.pydocstyle.convention.as_ref(), + checker.settings.pydocstyle.convention(), ); } } diff --git a/crates/ruff_linter/src/checkers/ast/analyze/statement.rs b/crates/ruff_linter/src/checkers/ast/analyze/statement.rs index 67f28b84ba94b..69dff843c6512 100644 --- a/crates/ruff_linter/src/checkers/ast/analyze/statement.rs +++ b/crates/ruff_linter/src/checkers/ast/analyze/statement.rs @@ -597,8 +597,11 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) { if checker.enabled(Rule::NonAsciiImportName) { pylint::rules::non_ascii_module_import(checker, alias); } + // TODO(charlie): Remove when stabilizing A004. if let Some(asname) = &alias.asname { - if checker.enabled(Rule::BuiltinVariableShadowing) { + if checker.settings.preview.is_disabled() + && checker.enabled(Rule::BuiltinVariableShadowing) + { flake8_builtins::rules::builtin_variable_shadowing( checker, asname, @@ -739,6 +742,9 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) { checker.diagnostics.push(diagnostic); } } + if checker.enabled(Rule::BuiltinImportShadowing) { + flake8_builtins::rules::builtin_import_shadowing(checker, alias); + } } } Stmt::ImportFrom( @@ -917,8 +923,11 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) { )); } } else { + // TODO(charlie): Remove when stabilizing A004. if let Some(asname) = &alias.asname { - if checker.enabled(Rule::BuiltinVariableShadowing) { + if checker.settings.preview.is_disabled() + && checker.enabled(Rule::BuiltinVariableShadowing) + { flake8_builtins::rules::builtin_variable_shadowing( checker, asname, @@ -1030,6 +1039,9 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) { } } } + if checker.enabled(Rule::BuiltinImportShadowing) { + flake8_builtins::rules::builtin_import_shadowing(checker, alias); + } } if checker.enabled(Rule::ImportSelf) { if let Some(diagnostic) = pylint::rules::import_from_self( diff --git a/crates/ruff_linter/src/checkers/ast/mod.rs b/crates/ruff_linter/src/checkers/ast/mod.rs index fa2a4f2cfcae2..61cdfb3d8758b 100644 --- a/crates/ruff_linter/src/checkers/ast/mod.rs +++ b/crates/ruff_linter/src/checkers/ast/mod.rs @@ -719,7 +719,7 @@ impl<'a> Visitor<'a> for Checker<'a> { self.visit_expr(expr); } } - for expr in returns { + if let Some(expr) = returns { match annotation { AnnotationContext::RuntimeRequired => { self.visit_runtime_required_annotation(expr); @@ -1240,7 +1240,7 @@ impl<'a> Visitor<'a> for Checker<'a> { for arg in args { self.visit_type_definition(arg); } - for keyword in arguments.keywords.iter() { + for keyword in &*arguments.keywords { let Keyword { arg, value, @@ -1286,7 +1286,7 @@ impl<'a> Visitor<'a> for Checker<'a> { } } - for keyword in arguments.keywords.iter() { + for keyword in &*arguments.keywords { let Keyword { arg, value, .. } = keyword; match (arg.as_ref(), value) { // Ex) NamedTuple("a", **{"a": int}) @@ -1331,7 +1331,7 @@ impl<'a> Visitor<'a> for Checker<'a> { } // Ex) TypedDict("a", a=int) - for keyword in arguments.keywords.iter() { + for keyword in &*arguments.keywords { let Keyword { value, .. } = keyword; self.visit_type_definition(value); } @@ -1345,13 +1345,13 @@ impl<'a> Visitor<'a> for Checker<'a> { for arg in args { self.visit_non_type_definition(arg); } - for keyword in arguments.keywords.iter() { + for keyword in &*arguments.keywords { let Keyword { value, .. } = keyword; self.visit_non_type_definition(value); } } else { // Ex) DefaultNamedArg(type="bool", name="some_prop_name") - for keyword in arguments.keywords.iter() { + for keyword in &*arguments.keywords { let Keyword { value, arg, @@ -1369,10 +1369,10 @@ impl<'a> Visitor<'a> for Checker<'a> { // If we're in a type definition, we need to treat the arguments to any // other callables as non-type definitions (i.e., we don't want to treat // any strings as deferred type definitions). - for arg in arguments.args.iter() { + for arg in &*arguments.args { self.visit_non_type_definition(arg); } - for keyword in arguments.keywords.iter() { + for keyword in &*arguments.keywords { let Keyword { value, .. } = keyword; self.visit_non_type_definition(value); } diff --git a/crates/ruff_linter/src/checkers/filesystem.rs b/crates/ruff_linter/src/checkers/filesystem.rs index c71db50cb3563..2427409b254de 100644 --- a/crates/ruff_linter/src/checkers/filesystem.rs +++ b/crates/ruff_linter/src/checkers/filesystem.rs @@ -5,6 +5,7 @@ use ruff_python_trivia::CommentRanges; use ruff_source_file::Locator; use crate::registry::Rule; +use crate::rules::flake8_builtins::rules::builtin_module_shadowing; use crate::rules::flake8_no_pep420::rules::implicit_namespace_package; use crate::rules::pep8_naming::rules::invalid_module_name; use crate::settings::LinterSettings; @@ -41,5 +42,17 @@ pub(crate) fn check_file_path( } } + // flake8-builtins + if settings.rules.enabled(Rule::BuiltinModuleShadowing) { + if let Some(diagnostic) = builtin_module_shadowing( + path, + package, + &settings.flake8_builtins.builtins_allowed_modules, + settings.target_version, + ) { + diagnostics.push(diagnostic); + } + } + diagnostics } diff --git a/crates/ruff_linter/src/codes.rs b/crates/ruff_linter/src/codes.rs index 64e4ea831cdff..969c5dc4b7066 100644 --- a/crates/ruff_linter/src/codes.rs +++ b/crates/ruff_linter/src/codes.rs @@ -310,6 +310,10 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> { (Flake8Builtins, "001") => (RuleGroup::Stable, rules::flake8_builtins::rules::BuiltinVariableShadowing), (Flake8Builtins, "002") => (RuleGroup::Stable, rules::flake8_builtins::rules::BuiltinArgumentShadowing), (Flake8Builtins, "003") => (RuleGroup::Stable, rules::flake8_builtins::rules::BuiltinAttributeShadowing), + // TODO(charlie): When stabilizing, remove preview gating for A001's treatment of imports. + (Flake8Builtins, "004") => (RuleGroup::Preview, rules::flake8_builtins::rules::BuiltinImportShadowing), + (Flake8Builtins, "005") => (RuleGroup::Preview, rules::flake8_builtins::rules::BuiltinModuleShadowing), + (Flake8Builtins, "006") => (RuleGroup::Preview, rules::flake8_builtins::rules::BuiltinLambdaArgumentShadowing), // flake8-bugbear (Flake8Bugbear, "002") => (RuleGroup::Stable, rules::flake8_bugbear::rules::UnaryPrefixIncrementDecrement), diff --git a/crates/ruff_linter/src/fix/codemods.rs b/crates/ruff_linter/src/fix/codemods.rs index c3c7691726967..70a928856c3b3 100644 --- a/crates/ruff_linter/src/fix/codemods.rs +++ b/crates/ruff_linter/src/fix/codemods.rs @@ -1,13 +1,16 @@ //! Interface for editing code snippets. These functions take statements or expressions as input, //! and return the modified code snippet as output. +use std::borrow::Cow; + use anyhow::{bail, Result}; use libcst_native::{ Codegen, CodegenState, Expression, ImportNames, NameOrAttribute, ParenthesizableWhitespace, SmallStatement, Statement, }; -use ruff_python_ast::name::UnqualifiedName; use smallvec::{smallvec, SmallVec}; +use unicode_normalization::UnicodeNormalization; +use ruff_python_ast::name::UnqualifiedName; use ruff_python_ast::Stmt; use ruff_python_codegen::Stylist; use ruff_source_file::Locator; @@ -167,39 +170,55 @@ pub(crate) fn retain_imports( Ok(tree.codegen_stylist(stylist)) } -fn collect_segments<'a>(expr: &'a Expression, parts: &mut SmallVec<[&'a str; 8]>) { - match expr { - Expression::Call(expr) => { - collect_segments(&expr.func, parts); - } - Expression::Attribute(expr) => { - collect_segments(&expr.value, parts); - parts.push(expr.attr.value); - } - Expression::Name(expr) => { - parts.push(expr.value); +/// Create an NFKC-normalized qualified name from a libCST node. +fn qualified_name_from_name_or_attribute(module: &NameOrAttribute) -> String { + fn collect_segments<'a>(expr: &'a Expression, parts: &mut SmallVec<[&'a str; 8]>) { + match expr { + Expression::Call(expr) => { + collect_segments(&expr.func, parts); + } + Expression::Attribute(expr) => { + collect_segments(&expr.value, parts); + parts.push(expr.attr.value); + } + Expression::Name(expr) => { + parts.push(expr.value); + } + _ => {} } - _ => {} } -} -fn unqualified_name_from_expression<'a>(expr: &'a Expression<'a>) -> Option> { - let mut segments = smallvec![]; - collect_segments(expr, &mut segments); - if segments.is_empty() { - None - } else { - Some(segments.into_iter().collect()) + /// Attempt to create an [`UnqualifiedName`] from a libCST expression. + /// + /// Strictly speaking, the `UnqualifiedName` returned by this function may be invalid, + /// since it hasn't been NFKC-normalized. In order for an `UnqualifiedName` to be + /// comparable to one constructed from a `ruff_python_ast` node, it has to undergo + /// NFKC normalization. As a local function, however, this is fine; + /// the outer function always performs NFKC normalization before returning the + /// qualified name to the caller. + fn unqualified_name_from_expression<'a>( + expr: &'a Expression<'a>, + ) -> Option> { + let mut segments = smallvec![]; + collect_segments(expr, &mut segments); + if segments.is_empty() { + None + } else { + Some(segments.into_iter().collect()) + } } -} -fn qualified_name_from_name_or_attribute(module: &NameOrAttribute) -> String { - match module { - NameOrAttribute::N(name) => name.value.to_string(), + let unnormalized = match module { + NameOrAttribute::N(name) => Cow::Borrowed(name.value), NameOrAttribute::A(attr) => { let name = attr.attr.value; let prefix = unqualified_name_from_expression(&attr.value); - prefix.map_or_else(|| name.to_string(), |prefix| format!("{prefix}.{name}")) + prefix.map_or_else( + || Cow::Borrowed(name), + |prefix| Cow::Owned(format!("{prefix}.{name}")), + ) } - } + }; + + unnormalized.nfkc().collect() } diff --git a/crates/ruff_linter/src/fix/edits.rs b/crates/ruff_linter/src/fix/edits.rs index 6b0eaeda0b6be..161425b4a746e 100644 --- a/crates/ruff_linter/src/fix/edits.rs +++ b/crates/ruff_linter/src/fix/edits.rs @@ -1,7 +1,5 @@ //! Interface for generating fix edits from higher-level actions (e.g., "remove an argument"). -use std::borrow::Cow; - use anyhow::{Context, Result}; use ruff_diagnostics::Edit; @@ -126,7 +124,7 @@ pub(crate) fn remove_unused_imports<'a>( /// Edits to make the specified imports explicit, e.g. change `import x` to `import x as x`. pub(crate) fn make_redundant_alias<'a>( - member_names: impl Iterator>, + member_names: impl Iterator, stmt: &Stmt, ) -> Vec { let aliases = match stmt { @@ -527,7 +525,6 @@ fn all_lines_fit( #[cfg(test)] mod tests { use anyhow::{anyhow, Result}; - use std::borrow::Cow; use test_case::test_case; use ruff_diagnostics::{Diagnostic, Edit, Fix}; @@ -619,7 +616,7 @@ x = 1 \ let contents = "import x, y as y, z as bees"; let stmt = parse_first_stmt(contents)?; assert_eq!( - make_redundant_alias(["x"].into_iter().map(Cow::from), &stmt), + make_redundant_alias(["x"].into_iter(), &stmt), vec![Edit::range_replacement( String::from("x as x"), TextRange::new(TextSize::new(7), TextSize::new(8)), @@ -627,7 +624,7 @@ x = 1 \ "make just one item redundant" ); assert_eq!( - make_redundant_alias(vec!["x", "y"].into_iter().map(Cow::from), &stmt), + make_redundant_alias(vec!["x", "y"].into_iter(), &stmt), vec![Edit::range_replacement( String::from("x as x"), TextRange::new(TextSize::new(7), TextSize::new(8)), @@ -635,7 +632,7 @@ x = 1 \ "the second item is already a redundant alias" ); assert_eq!( - make_redundant_alias(vec!["x", "z"].into_iter().map(Cow::from), &stmt), + make_redundant_alias(vec!["x", "z"].into_iter(), &stmt), vec![Edit::range_replacement( String::from("x as x"), TextRange::new(TextSize::new(7), TextSize::new(8)), diff --git a/crates/ruff_linter/src/message/json_lines.rs b/crates/ruff_linter/src/message/json_lines.rs index f939f921dc0f0..24cf8821703c7 100644 --- a/crates/ruff_linter/src/message/json_lines.rs +++ b/crates/ruff_linter/src/message/json_lines.rs @@ -13,10 +13,9 @@ impl Emitter for JsonLinesEmitter { messages: &[Message], context: &EmitterContext, ) -> anyhow::Result<()> { - let mut w = writer; for message in messages { - serde_json::to_writer(&mut w, &message_to_json_value(message, context))?; - w.write_all(b"\n")?; + serde_json::to_writer(&mut *writer, &message_to_json_value(message, context))?; + writer.write_all(b"\n")?; } Ok(()) } diff --git a/crates/ruff_linter/src/registry.rs b/crates/ruff_linter/src/registry.rs index 4901c2e47f33d..1ee0cc5102add 100644 --- a/crates/ruff_linter/src/registry.rs +++ b/crates/ruff_linter/src/registry.rs @@ -304,7 +304,9 @@ impl Rule { | Rule::UTF8EncodingDeclaration => LintSource::Tokens, Rule::IOError => LintSource::Io, Rule::UnsortedImports | Rule::MissingRequiredImport => LintSource::Imports, - Rule::ImplicitNamespacePackage | Rule::InvalidModuleName => LintSource::Filesystem, + Rule::ImplicitNamespacePackage + | Rule::InvalidModuleName + | Rule::BuiltinModuleShadowing => LintSource::Filesystem, Rule::IndentationWithInvalidMultiple | Rule::IndentationWithInvalidMultipleComment | Rule::MissingWhitespace diff --git a/crates/ruff_linter/src/rules/flake8_async/rules/cancel_scope_no_checkpoint.rs b/crates/ruff_linter/src/rules/flake8_async/rules/cancel_scope_no_checkpoint.rs index 2037710446081..7064318e7f099 100644 --- a/crates/ruff_linter/src/rules/flake8_async/rules/cancel_scope_no_checkpoint.rs +++ b/crates/ruff_linter/src/rules/flake8_async/rules/cancel_scope_no_checkpoint.rs @@ -69,12 +69,31 @@ pub(crate) fn cancel_scope_no_checkpoint( return; } + // If the body contains an `await` statement, the context manager is used correctly. let mut visitor = AwaitVisitor::default(); visitor.visit_body(&with_stmt.body); if visitor.seen_await { return; } + // If there's an `asyncio.TaskGroup()` context manager alongside the timeout, it's fine, as in: + // ```python + // async with asyncio.timeout(2.0), asyncio.TaskGroup(): + // ... + // ``` + if with_items.iter().any(|item| { + item.context_expr.as_call_expr().is_some_and(|call| { + checker + .semantic() + .resolve_qualified_name(call.func.as_ref()) + .is_some_and(|qualified_name| { + matches!(qualified_name.segments(), ["asyncio", "TaskGroup"]) + }) + }) + }) { + return; + } + if matches!(checker.settings.preview, PreviewMode::Disabled) { if matches!(method_name.module(), AsyncModule::Trio) { checker.diagnostics.push(Diagnostic::new( diff --git a/crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__ASYNC100_ASYNC100.py.snap b/crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__ASYNC100_ASYNC100.py.snap index f4bddeb95c02c..22f7c8a1ebe0d 100644 --- a/crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__ASYNC100_ASYNC100.py.snap +++ b/crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__ASYNC100_ASYNC100.py.snap @@ -4,7 +4,7 @@ source: crates/ruff_linter/src/rules/flake8_async/mod.rs ASYNC100.py:7:5: ASYNC100 A `with trio.fail_after(...):` context does not contain any `await` statements. This makes it pointless, as the timeout can only be triggered by a checkpoint. | 6 | async def func(): -7 | with trio.fail_after(): +7 | async with trio.fail_after(): | _____^ 8 | | ... | |___________^ ASYNC100 @@ -13,7 +13,7 @@ ASYNC100.py:7:5: ASYNC100 A `with trio.fail_after(...):` context does not contai ASYNC100.py:17:5: ASYNC100 A `with trio.move_on_after(...):` context does not contain any `await` statements. This makes it pointless, as the timeout can only be triggered by a checkpoint. | 16 | async def func(): -17 | with trio.move_on_after(): +17 | async with trio.move_on_after(): | _____^ 18 | | ... | |___________^ ASYNC100 diff --git a/crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__preview__ASYNC100_ASYNC100.py.snap b/crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__preview__ASYNC100_ASYNC100.py.snap index a805c2c3e3b18..bf704040e6d46 100644 --- a/crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__preview__ASYNC100_ASYNC100.py.snap +++ b/crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__preview__ASYNC100_ASYNC100.py.snap @@ -4,7 +4,7 @@ source: crates/ruff_linter/src/rules/flake8_async/mod.rs ASYNC100.py:7:5: ASYNC100 A `with trio.fail_after(...):` context does not contain any `await` statements. This makes it pointless, as the timeout can only be triggered by a checkpoint. | 6 | async def func(): -7 | with trio.fail_after(): +7 | async with trio.fail_after(): | _____^ 8 | | ... | |___________^ ASYNC100 @@ -13,7 +13,7 @@ ASYNC100.py:7:5: ASYNC100 A `with trio.fail_after(...):` context does not contai ASYNC100.py:17:5: ASYNC100 A `with trio.move_on_after(...):` context does not contain any `await` statements. This makes it pointless, as the timeout can only be triggered by a checkpoint. | 16 | async def func(): -17 | with trio.move_on_after(): +17 | async with trio.move_on_after(): | _____^ 18 | | ... | |___________^ ASYNC100 @@ -22,7 +22,7 @@ ASYNC100.py:17:5: ASYNC100 A `with trio.move_on_after(...):` context does not co ASYNC100.py:33:5: ASYNC100 A `with anyio.move_on_after(...):` context does not contain any `await` statements. This makes it pointless, as the timeout can only be triggered by a checkpoint. | 32 | async def func(): -33 | with anyio.move_on_after(): +33 | async with anyio.move_on_after(delay=0.2): | _____^ 34 | | ... | |___________^ ASYNC100 @@ -31,7 +31,7 @@ ASYNC100.py:33:5: ASYNC100 A `with anyio.move_on_after(...):` context does not c ASYNC100.py:38:5: ASYNC100 A `with anyio.fail_after(...):` context does not contain any `await` statements. This makes it pointless, as the timeout can only be triggered by a checkpoint. | 37 | async def func(): -38 | with anyio.fail_after(): +38 | async with anyio.fail_after(): | _____^ 39 | | ... | |___________^ ASYNC100 @@ -40,7 +40,7 @@ ASYNC100.py:38:5: ASYNC100 A `with anyio.fail_after(...):` context does not cont ASYNC100.py:43:5: ASYNC100 A `with anyio.CancelScope(...):` context does not contain any `await` statements. This makes it pointless, as the timeout can only be triggered by a checkpoint. | 42 | async def func(): -43 | with anyio.CancelScope(): +43 | async with anyio.CancelScope(): | _____^ 44 | | ... | |___________^ ASYNC100 @@ -49,7 +49,7 @@ ASYNC100.py:43:5: ASYNC100 A `with anyio.CancelScope(...):` context does not con ASYNC100.py:48:5: ASYNC100 A `with anyio.CancelScope(...):` context does not contain any `await` statements. This makes it pointless, as the timeout can only be triggered by a checkpoint. | 47 | async def func(): -48 | with anyio.CancelScope(): +48 | async with anyio.CancelScope(): | _____^ 49 | | ... | |___________^ ASYNC100 @@ -58,7 +58,7 @@ ASYNC100.py:48:5: ASYNC100 A `with anyio.CancelScope(...):` context does not con ASYNC100.py:53:5: ASYNC100 A `with asyncio.timeout(...):` context does not contain any `await` statements. This makes it pointless, as the timeout can only be triggered by a checkpoint. | 52 | async def func(): -53 | with asyncio.timeout(): +53 | async with asyncio.timeout(delay=0.2): | _____^ 54 | | ... | |___________^ ASYNC100 @@ -67,7 +67,7 @@ ASYNC100.py:53:5: ASYNC100 A `with asyncio.timeout(...):` context does not conta ASYNC100.py:58:5: ASYNC100 A `with asyncio.timeout_at(...):` context does not contain any `await` statements. This makes it pointless, as the timeout can only be triggered by a checkpoint. | 57 | async def func(): -58 | with asyncio.timeout_at(): +58 | async with asyncio.timeout_at(when=0.2): | _____^ 59 | | ... | |___________^ ASYNC100 diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/function_uses_loop_variable.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/function_uses_loop_variable.rs index 852a4d5098441..58822804a6054 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/function_uses_loop_variable.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/function_uses_loop_variable.rs @@ -132,7 +132,7 @@ impl<'a> Visitor<'a> for SuspiciousVariablesVisitor<'a> { match func.as_ref() { Expr::Name(ast::ExprName { id, .. }) => { if matches!(id.as_str(), "filter" | "reduce" | "map") { - for arg in arguments.args.iter() { + for arg in &*arguments.args { if arg.is_lambda_expr() { self.safe_functions.push(arg); } @@ -143,7 +143,7 @@ impl<'a> Visitor<'a> for SuspiciousVariablesVisitor<'a> { if attr == "reduce" { if let Expr::Name(ast::ExprName { id, .. }) = value.as_ref() { if id == "functools" { - for arg in arguments.args.iter() { + for arg in &*arguments.args { if arg.is_lambda_expr() { self.safe_functions.push(arg); } @@ -155,7 +155,7 @@ impl<'a> Visitor<'a> for SuspiciousVariablesVisitor<'a> { _ => {} } - for keyword in arguments.keywords.iter() { + for keyword in &*arguments.keywords { if keyword.arg.as_ref().is_some_and(|arg| arg == "key") && keyword.value.is_lambda_expr() { diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/zip_without_explicit_strict.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/zip_without_explicit_strict.rs index 967b950c01e05..513c0c7e52f40 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/zip_without_explicit_strict.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/zip_without_explicit_strict.rs @@ -32,7 +32,7 @@ use crate::fix::edits::add_argument; /// /// ## Fix safety /// This rule's fix is marked as unsafe for `zip` calls that contain -/// `**kwargs`, as adding a `check` keyword argument to such a call may lead +/// `**kwargs`, as adding a `strict` keyword argument to such a call may lead /// to a duplicate keyword argument error. /// /// ## References @@ -116,7 +116,7 @@ fn is_infinite_iterator(arg: &Expr, semantic: &SemanticModel) -> bool { } // Ex) `iterools.repeat(1, times=None)` - for keyword in keywords.iter() { + for keyword in &**keywords { if keyword.arg.as_ref().is_some_and(|name| name == "times") { if keyword.value.is_none_literal_expr() { return true; diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__B019_B019.py.snap b/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__B019_B019.py.snap index 76541860c05f5..907178352a1ad 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__B019_B019.py.snap +++ b/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__B019_B019.py.snap @@ -80,4 +80,11 @@ B019.py:106:5: B019 Use of `functools.lru_cache` or `functools.cache` on methods 108 | ... | - +B019.py:124:5: B019 Use of `functools.lru_cache` or `functools.cache` on methods can lead to memory leaks + | +123 | class Metaclass(type): +124 | @functools.lru_cache + | ^^^^^^^^^^^^^^^^^^^^ B019 +125 | def lru_cached_instance_method_on_metaclass(cls, x: int): +126 | ... + | diff --git a/crates/ruff_linter/src/rules/flake8_builtins/mod.rs b/crates/ruff_linter/src/rules/flake8_builtins/mod.rs index 3ce0725066ca2..8a35abb23d5be 100644 --- a/crates/ruff_linter/src/rules/flake8_builtins/mod.rs +++ b/crates/ruff_linter/src/rules/flake8_builtins/mod.rs @@ -18,6 +18,25 @@ mod tests { #[test_case(Rule::BuiltinVariableShadowing, Path::new("A001.py"))] #[test_case(Rule::BuiltinArgumentShadowing, Path::new("A002.py"))] #[test_case(Rule::BuiltinAttributeShadowing, Path::new("A003.py"))] + #[test_case(Rule::BuiltinImportShadowing, Path::new("A004.py"))] + #[test_case( + Rule::BuiltinModuleShadowing, + Path::new("A005/modules/non_builtin/__init__.py") + )] + #[test_case( + Rule::BuiltinModuleShadowing, + Path::new("A005/modules/logging/__init__.py") + )] + #[test_case( + Rule::BuiltinModuleShadowing, + Path::new("A005/modules/string/__init__.py") + )] + #[test_case( + Rule::BuiltinModuleShadowing, + Path::new("A005/modules/package/bisect.py") + )] + #[test_case(Rule::BuiltinModuleShadowing, Path::new("A005/modules/package/xml.py"))] + #[test_case(Rule::BuiltinLambdaArgumentShadowing, Path::new("A006.py"))] fn rules(rule_code: Rule, path: &Path) -> Result<()> { let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy()); let diagnostics = test_path( @@ -31,6 +50,8 @@ mod tests { #[test_case(Rule::BuiltinVariableShadowing, Path::new("A001.py"))] #[test_case(Rule::BuiltinArgumentShadowing, Path::new("A002.py"))] #[test_case(Rule::BuiltinAttributeShadowing, Path::new("A003.py"))] + #[test_case(Rule::BuiltinImportShadowing, Path::new("A004.py"))] + #[test_case(Rule::BuiltinLambdaArgumentShadowing, Path::new("A006.py"))] fn builtins_ignorelist(rule_code: Rule, path: &Path) -> Result<()> { let snapshot = format!( "{}_{}_builtins_ignorelist", @@ -43,6 +64,46 @@ mod tests { &LinterSettings { flake8_builtins: super::settings::Settings { builtins_ignorelist: vec!["id".to_string(), "dir".to_string()], + ..Default::default() + }, + ..LinterSettings::for_rules(vec![rule_code]) + }, + )?; + + assert_messages!(snapshot, diagnostics); + Ok(()) + } + + #[test_case( + Rule::BuiltinModuleShadowing, + Path::new("A005/modules/non_builtin/__init__.py") + )] + #[test_case( + Rule::BuiltinModuleShadowing, + Path::new("A005/modules/logging/__init__.py") + )] + #[test_case( + Rule::BuiltinModuleShadowing, + Path::new("A005/modules/string/__init__.py") + )] + #[test_case( + Rule::BuiltinModuleShadowing, + Path::new("A005/modules/package/bisect.py") + )] + #[test_case(Rule::BuiltinModuleShadowing, Path::new("A005/modules/package/xml.py"))] + fn builtins_allowed_modules(rule_code: Rule, path: &Path) -> Result<()> { + let snapshot = format!( + "{}_{}_builtins_allowed_modules", + rule_code.noqa_code(), + path.to_string_lossy() + ); + + let diagnostics = test_path( + Path::new("flake8_builtins").join(path).as_path(), + &LinterSettings { + flake8_builtins: super::settings::Settings { + builtins_allowed_modules: vec!["xml".to_string(), "logging".to_string()], + ..Default::default() }, ..LinterSettings::for_rules(vec![rule_code]) }, diff --git a/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_argument_shadowing.rs b/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_argument_shadowing.rs index 74088538aa51f..e8337dadbccb0 100644 --- a/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_argument_shadowing.rs +++ b/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_argument_shadowing.rs @@ -1,8 +1,7 @@ -use ruff_python_ast::Parameter; - use ruff_diagnostics::Diagnostic; use ruff_diagnostics::Violation; use ruff_macros::{derive_message_formats, violation}; +use ruff_python_ast::Parameter; use ruff_python_semantic::analyze::visibility::{is_overload, is_override}; use ruff_text_size::Ranged; @@ -11,7 +10,7 @@ use crate::checkers::ast::Checker; use super::super::helpers::shadows_builtin; /// ## What it does -/// Checks for any function arguments that use the same name as a builtin. +/// Checks for function arguments that use the same names as builtins. /// /// ## Why is this bad? /// Reusing a builtin name for the name of an argument increases the diff --git a/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_attribute_shadowing.rs b/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_attribute_shadowing.rs index de4a625c3dac9..124a1bd574aac 100644 --- a/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_attribute_shadowing.rs +++ b/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_attribute_shadowing.rs @@ -10,8 +10,8 @@ use crate::checkers::ast::Checker; use crate::rules::flake8_builtins::helpers::shadows_builtin; /// ## What it does -/// Checks for any class attributes or methods that use the same name as a -/// builtin. +/// Checks for class attributes and methods that use the same names as +/// Python builtins. /// /// ## Why is this bad? /// Reusing a builtin name for the name of an attribute increases the diff --git a/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_import_shadowing.rs b/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_import_shadowing.rs new file mode 100644 index 0000000000000..2c601a48af97a --- /dev/null +++ b/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_import_shadowing.rs @@ -0,0 +1,49 @@ +use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_macros::{derive_message_formats, violation}; +use ruff_python_ast::Alias; + +use crate::checkers::ast::Checker; +use crate::rules::flake8_builtins::helpers::shadows_builtin; + +/// ## What it does +/// Checks for imports that use the same names as builtins. +/// +/// ## Why is this bad? +/// Reusing a builtin for the name of an import increases the difficulty +/// of reading and maintaining the code, and can cause non-obvious errors, +/// as readers may mistake the variable for the builtin and vice versa. +/// +/// Builtins can be marked as exceptions to this rule via the +/// [`lint.flake8-builtins.builtins-ignorelist`] configuration option. +/// +/// ## Options +/// - `lint.flake8-builtins.builtins-ignorelist` +#[violation] +pub struct BuiltinImportShadowing { + name: String, +} + +impl Violation for BuiltinImportShadowing { + #[derive_message_formats] + fn message(&self) -> String { + let BuiltinImportShadowing { name } = self; + format!("Import `{name}` is shadowing a Python builtin") + } +} + +/// A004 +pub(crate) fn builtin_import_shadowing(checker: &mut Checker, alias: &Alias) { + let name = alias.asname.as_ref().unwrap_or(&alias.name); + if shadows_builtin( + name.as_str(), + &checker.settings.flake8_builtins.builtins_ignorelist, + checker.source_type, + ) { + checker.diagnostics.push(Diagnostic::new( + BuiltinImportShadowing { + name: name.to_string(), + }, + name.range, + )); + } +} diff --git a/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_lambda_argument_shadowing.rs b/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_lambda_argument_shadowing.rs new file mode 100644 index 0000000000000..446e3f2ddec82 --- /dev/null +++ b/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_lambda_argument_shadowing.rs @@ -0,0 +1,56 @@ +use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_macros::{derive_message_formats, violation}; +use ruff_python_ast::ExprLambda; +use ruff_text_size::Ranged; + +use crate::checkers::ast::Checker; +use crate::rules::flake8_builtins::helpers::shadows_builtin; + +/// ## What it does +/// Checks for lambda arguments that use the same names as Python builtins. +/// +/// ## Why is this bad? +/// Reusing a builtin name for the name of a lambda argument increases the +/// difficulty of reading and maintaining the code, and can cause +/// non-obvious errors, as readers may mistake the variable for the +/// builtin and vice versa. +/// +/// Builtins can be marked as exceptions to this rule via the +/// [`lint.flake8-builtins.builtins-ignorelist`] configuration option. +/// +/// ## Options +/// - `lint.flake8-builtins.builtins-ignorelist` +#[violation] +pub struct BuiltinLambdaArgumentShadowing { + name: String, +} + +impl Violation for BuiltinLambdaArgumentShadowing { + #[derive_message_formats] + fn message(&self) -> String { + let BuiltinLambdaArgumentShadowing { name } = self; + format!("Lambda argument `{name}` is shadowing a Python builtin") + } +} + +/// A006 +pub(crate) fn builtin_lambda_argument_shadowing(checker: &mut Checker, lambda: &ExprLambda) { + let Some(parameters) = lambda.parameters.as_ref() else { + return; + }; + for param in parameters.iter_non_variadic_params() { + let name = ¶m.parameter.name; + if shadows_builtin( + name.as_ref(), + &checker.settings.flake8_builtins.builtins_ignorelist, + checker.source_type, + ) { + checker.diagnostics.push(Diagnostic::new( + BuiltinLambdaArgumentShadowing { + name: name.to_string(), + }, + name.range(), + )); + } + } +} diff --git a/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_module_shadowing.rs b/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_module_shadowing.rs new file mode 100644 index 0000000000000..d38665274d328 --- /dev/null +++ b/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_module_shadowing.rs @@ -0,0 +1,73 @@ +use std::path::Path; + +use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_macros::{derive_message_formats, violation}; +use ruff_python_stdlib::path::is_module_file; +use ruff_python_stdlib::sys::is_known_standard_library; +use ruff_text_size::TextRange; + +use crate::settings::types::PythonVersion; + +/// ## What it does +/// Checks for modules that use the same names as Python builtin modules. +/// +/// ## Why is this bad? +/// Reusing a builtin module name for the name of a module increases the +/// difficulty of reading and maintaining the code, and can cause +/// non-obvious errors, as readers may mistake the variable for the +/// builtin and vice versa. +/// +/// Builtin modules can be marked as exceptions to this rule via the +/// [`lint.flake8-builtins.builtins-allowed-modules`] configuration option. +/// +/// ## Options +/// - `lint.flake8-builtins.builtins-allowed-modules` +#[violation] +pub struct BuiltinModuleShadowing { + name: String, +} + +impl Violation for BuiltinModuleShadowing { + #[derive_message_formats] + fn message(&self) -> String { + let BuiltinModuleShadowing { name } = self; + format!("Module `{name}` is shadowing a Python builtin module") + } +} + +/// A005 +pub(crate) fn builtin_module_shadowing( + path: &Path, + package: Option<&Path>, + allowed_modules: &[String], + target_version: PythonVersion, +) -> Option { + if !path + .extension() + .is_some_and(|ext| ext == "py" || ext == "pyi") + { + return None; + } + + if let Some(package) = package { + let module_name = if is_module_file(path) { + package.file_name().unwrap().to_string_lossy() + } else { + path.file_stem().unwrap().to_string_lossy() + }; + + if is_known_standard_library(target_version.minor(), &module_name) + && allowed_modules + .iter() + .all(|allowed_module| allowed_module != &module_name) + { + return Some(Diagnostic::new( + BuiltinModuleShadowing { + name: module_name.to_string(), + }, + TextRange::default(), + )); + } + } + None +} diff --git a/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_variable_shadowing.rs b/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_variable_shadowing.rs index 0b5a0080d063b..1654b59422760 100644 --- a/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_variable_shadowing.rs +++ b/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_variable_shadowing.rs @@ -1,15 +1,14 @@ -use ruff_text_size::TextRange; - use ruff_diagnostics::Diagnostic; use ruff_diagnostics::Violation; use ruff_macros::{derive_message_formats, violation}; +use ruff_text_size::TextRange; use crate::checkers::ast::Checker; use crate::rules::flake8_builtins::helpers::shadows_builtin; /// ## What it does -/// Checks for variable (and function) assignments that use the same name -/// as a builtin. +/// Checks for variable (and function) assignments that use the same names +/// as builtins. /// /// ## Why is this bad? /// Reusing a builtin name for the name of a variable increases the diff --git a/crates/ruff_linter/src/rules/flake8_builtins/rules/mod.rs b/crates/ruff_linter/src/rules/flake8_builtins/rules/mod.rs index d81afec0d6e5b..46478f7c236a1 100644 --- a/crates/ruff_linter/src/rules/flake8_builtins/rules/mod.rs +++ b/crates/ruff_linter/src/rules/flake8_builtins/rules/mod.rs @@ -1,7 +1,13 @@ pub(crate) use builtin_argument_shadowing::*; pub(crate) use builtin_attribute_shadowing::*; +pub(crate) use builtin_import_shadowing::*; +pub(crate) use builtin_lambda_argument_shadowing::*; +pub(crate) use builtin_module_shadowing::*; pub(crate) use builtin_variable_shadowing::*; mod builtin_argument_shadowing; mod builtin_attribute_shadowing; +mod builtin_import_shadowing; +mod builtin_lambda_argument_shadowing; +mod builtin_module_shadowing; mod builtin_variable_shadowing; diff --git a/crates/ruff_linter/src/rules/flake8_builtins/settings.rs b/crates/ruff_linter/src/rules/flake8_builtins/settings.rs index e11537efb7ff4..cfb5573ee05a9 100644 --- a/crates/ruff_linter/src/rules/flake8_builtins/settings.rs +++ b/crates/ruff_linter/src/rules/flake8_builtins/settings.rs @@ -7,6 +7,7 @@ use std::fmt::{Display, Formatter}; #[derive(Debug, Clone, Default, CacheKey)] pub struct Settings { pub builtins_ignorelist: Vec, + pub builtins_allowed_modules: Vec, } impl Display for Settings { @@ -15,7 +16,8 @@ impl Display for Settings { formatter = f, namespace = "linter.flake8_builtins", fields = [ - self.builtins_ignorelist | array + self.builtins_allowed_modules | array, + self.builtins_ignorelist | array, ] } Ok(()) diff --git a/crates/ruff_linter/src/rules/flake8_builtins/snapshots/ruff_linter__rules__flake8_builtins__tests__A004_A004.py.snap b/crates/ruff_linter/src/rules/flake8_builtins/snapshots/ruff_linter__rules__flake8_builtins__tests__A004_A004.py.snap new file mode 100644 index 0000000000000..a645772146433 --- /dev/null +++ b/crates/ruff_linter/src/rules/flake8_builtins/snapshots/ruff_linter__rules__flake8_builtins__tests__A004_A004.py.snap @@ -0,0 +1,55 @@ +--- +source: crates/ruff_linter/src/rules/flake8_builtins/mod.rs +--- +A004.py:1:16: A004 Import `sum` is shadowing a Python builtin + | +1 | import some as sum + | ^^^ A004 +2 | import float +3 | from some import other as int + | + +A004.py:2:8: A004 Import `float` is shadowing a Python builtin + | +1 | import some as sum +2 | import float + | ^^^^^ A004 +3 | from some import other as int +4 | from some import input, exec + | + +A004.py:3:27: A004 Import `int` is shadowing a Python builtin + | +1 | import some as sum +2 | import float +3 | from some import other as int + | ^^^ A004 +4 | from some import input, exec +5 | from directory import new as dir + | + +A004.py:4:18: A004 Import `input` is shadowing a Python builtin + | +2 | import float +3 | from some import other as int +4 | from some import input, exec + | ^^^^^ A004 +5 | from directory import new as dir + | + +A004.py:4:25: A004 Import `exec` is shadowing a Python builtin + | +2 | import float +3 | from some import other as int +4 | from some import input, exec + | ^^^^ A004 +5 | from directory import new as dir + | + +A004.py:5:30: A004 Import `dir` is shadowing a Python builtin + | +3 | from some import other as int +4 | from some import input, exec +5 | from directory import new as dir + | ^^^ A004 + | diff --git a/crates/ruff_linter/src/rules/flake8_builtins/snapshots/ruff_linter__rules__flake8_builtins__tests__A004_A004.py_builtins_ignorelist.snap b/crates/ruff_linter/src/rules/flake8_builtins/snapshots/ruff_linter__rules__flake8_builtins__tests__A004_A004.py_builtins_ignorelist.snap new file mode 100644 index 0000000000000..e9d130125bc02 --- /dev/null +++ b/crates/ruff_linter/src/rules/flake8_builtins/snapshots/ruff_linter__rules__flake8_builtins__tests__A004_A004.py_builtins_ignorelist.snap @@ -0,0 +1,47 @@ +--- +source: crates/ruff_linter/src/rules/flake8_builtins/mod.rs +--- +A004.py:1:16: A004 Import `sum` is shadowing a Python builtin + | +1 | import some as sum + | ^^^ A004 +2 | import float +3 | from some import other as int + | + +A004.py:2:8: A004 Import `float` is shadowing a Python builtin + | +1 | import some as sum +2 | import float + | ^^^^^ A004 +3 | from some import other as int +4 | from some import input, exec + | + +A004.py:3:27: A004 Import `int` is shadowing a Python builtin + | +1 | import some as sum +2 | import float +3 | from some import other as int + | ^^^ A004 +4 | from some import input, exec +5 | from directory import new as dir + | + +A004.py:4:18: A004 Import `input` is shadowing a Python builtin + | +2 | import float +3 | from some import other as int +4 | from some import input, exec + | ^^^^^ A004 +5 | from directory import new as dir + | + +A004.py:4:25: A004 Import `exec` is shadowing a Python builtin + | +2 | import float +3 | from some import other as int +4 | from some import input, exec + | ^^^^ A004 +5 | from directory import new as dir + | diff --git a/crates/ruff_linter/src/rules/flake8_builtins/snapshots/ruff_linter__rules__flake8_builtins__tests__A005_A005__modules__logging____init__.py.snap b/crates/ruff_linter/src/rules/flake8_builtins/snapshots/ruff_linter__rules__flake8_builtins__tests__A005_A005__modules__logging____init__.py.snap new file mode 100644 index 0000000000000..9f05f0aa763d5 --- /dev/null +++ b/crates/ruff_linter/src/rules/flake8_builtins/snapshots/ruff_linter__rules__flake8_builtins__tests__A005_A005__modules__logging____init__.py.snap @@ -0,0 +1,4 @@ +--- +source: crates/ruff_linter/src/rules/flake8_builtins/mod.rs +--- +__init__.py:1:1: A005 Module `logging` is shadowing a Python builtin module diff --git a/crates/ruff_linter/src/rules/flake8_builtins/snapshots/ruff_linter__rules__flake8_builtins__tests__A005_A005__modules__logging____init__.py_builtins_allowed_modules.snap b/crates/ruff_linter/src/rules/flake8_builtins/snapshots/ruff_linter__rules__flake8_builtins__tests__A005_A005__modules__logging____init__.py_builtins_allowed_modules.snap new file mode 100644 index 0000000000000..df35fcb66a979 --- /dev/null +++ b/crates/ruff_linter/src/rules/flake8_builtins/snapshots/ruff_linter__rules__flake8_builtins__tests__A005_A005__modules__logging____init__.py_builtins_allowed_modules.snap @@ -0,0 +1,4 @@ +--- +source: crates/ruff_linter/src/rules/flake8_builtins/mod.rs +--- + diff --git a/crates/ruff_linter/src/rules/flake8_builtins/snapshots/ruff_linter__rules__flake8_builtins__tests__A005_A005__modules__non_builtin____init__.py.snap b/crates/ruff_linter/src/rules/flake8_builtins/snapshots/ruff_linter__rules__flake8_builtins__tests__A005_A005__modules__non_builtin____init__.py.snap new file mode 100644 index 0000000000000..df35fcb66a979 --- /dev/null +++ b/crates/ruff_linter/src/rules/flake8_builtins/snapshots/ruff_linter__rules__flake8_builtins__tests__A005_A005__modules__non_builtin____init__.py.snap @@ -0,0 +1,4 @@ +--- +source: crates/ruff_linter/src/rules/flake8_builtins/mod.rs +--- + diff --git a/crates/ruff_linter/src/rules/flake8_builtins/snapshots/ruff_linter__rules__flake8_builtins__tests__A005_A005__modules__non_builtin____init__.py_builtins_allowed_modules.snap b/crates/ruff_linter/src/rules/flake8_builtins/snapshots/ruff_linter__rules__flake8_builtins__tests__A005_A005__modules__non_builtin____init__.py_builtins_allowed_modules.snap new file mode 100644 index 0000000000000..df35fcb66a979 --- /dev/null +++ b/crates/ruff_linter/src/rules/flake8_builtins/snapshots/ruff_linter__rules__flake8_builtins__tests__A005_A005__modules__non_builtin____init__.py_builtins_allowed_modules.snap @@ -0,0 +1,4 @@ +--- +source: crates/ruff_linter/src/rules/flake8_builtins/mod.rs +--- + diff --git a/crates/ruff_linter/src/rules/flake8_builtins/snapshots/ruff_linter__rules__flake8_builtins__tests__A005_A005__modules__package__bisect.py.snap b/crates/ruff_linter/src/rules/flake8_builtins/snapshots/ruff_linter__rules__flake8_builtins__tests__A005_A005__modules__package__bisect.py.snap new file mode 100644 index 0000000000000..3615ea42974ab --- /dev/null +++ b/crates/ruff_linter/src/rules/flake8_builtins/snapshots/ruff_linter__rules__flake8_builtins__tests__A005_A005__modules__package__bisect.py.snap @@ -0,0 +1,4 @@ +--- +source: crates/ruff_linter/src/rules/flake8_builtins/mod.rs +--- +bisect.py:1:1: A005 Module `bisect` is shadowing a Python builtin module diff --git a/crates/ruff_linter/src/rules/flake8_builtins/snapshots/ruff_linter__rules__flake8_builtins__tests__A005_A005__modules__package__bisect.py_builtins_allowed_modules.snap b/crates/ruff_linter/src/rules/flake8_builtins/snapshots/ruff_linter__rules__flake8_builtins__tests__A005_A005__modules__package__bisect.py_builtins_allowed_modules.snap new file mode 100644 index 0000000000000..3615ea42974ab --- /dev/null +++ b/crates/ruff_linter/src/rules/flake8_builtins/snapshots/ruff_linter__rules__flake8_builtins__tests__A005_A005__modules__package__bisect.py_builtins_allowed_modules.snap @@ -0,0 +1,4 @@ +--- +source: crates/ruff_linter/src/rules/flake8_builtins/mod.rs +--- +bisect.py:1:1: A005 Module `bisect` is shadowing a Python builtin module diff --git a/crates/ruff_linter/src/rules/flake8_builtins/snapshots/ruff_linter__rules__flake8_builtins__tests__A005_A005__modules__package__xml.py.snap b/crates/ruff_linter/src/rules/flake8_builtins/snapshots/ruff_linter__rules__flake8_builtins__tests__A005_A005__modules__package__xml.py.snap new file mode 100644 index 0000000000000..3fade6e0a3757 --- /dev/null +++ b/crates/ruff_linter/src/rules/flake8_builtins/snapshots/ruff_linter__rules__flake8_builtins__tests__A005_A005__modules__package__xml.py.snap @@ -0,0 +1,4 @@ +--- +source: crates/ruff_linter/src/rules/flake8_builtins/mod.rs +--- +xml.py:1:1: A005 Module `xml` is shadowing a Python builtin module diff --git a/crates/ruff_linter/src/rules/flake8_builtins/snapshots/ruff_linter__rules__flake8_builtins__tests__A005_A005__modules__package__xml.py_builtins_allowed_modules.snap b/crates/ruff_linter/src/rules/flake8_builtins/snapshots/ruff_linter__rules__flake8_builtins__tests__A005_A005__modules__package__xml.py_builtins_allowed_modules.snap new file mode 100644 index 0000000000000..df35fcb66a979 --- /dev/null +++ b/crates/ruff_linter/src/rules/flake8_builtins/snapshots/ruff_linter__rules__flake8_builtins__tests__A005_A005__modules__package__xml.py_builtins_allowed_modules.snap @@ -0,0 +1,4 @@ +--- +source: crates/ruff_linter/src/rules/flake8_builtins/mod.rs +--- + diff --git a/crates/ruff_linter/src/rules/flake8_builtins/snapshots/ruff_linter__rules__flake8_builtins__tests__A005_A005__modules__string____init__.py.snap b/crates/ruff_linter/src/rules/flake8_builtins/snapshots/ruff_linter__rules__flake8_builtins__tests__A005_A005__modules__string____init__.py.snap new file mode 100644 index 0000000000000..69dc571b6ffc5 --- /dev/null +++ b/crates/ruff_linter/src/rules/flake8_builtins/snapshots/ruff_linter__rules__flake8_builtins__tests__A005_A005__modules__string____init__.py.snap @@ -0,0 +1,4 @@ +--- +source: crates/ruff_linter/src/rules/flake8_builtins/mod.rs +--- +__init__.py:1:1: A005 Module `string` is shadowing a Python builtin module diff --git a/crates/ruff_linter/src/rules/flake8_builtins/snapshots/ruff_linter__rules__flake8_builtins__tests__A005_A005__modules__string____init__.py_builtins_allowed_modules.snap b/crates/ruff_linter/src/rules/flake8_builtins/snapshots/ruff_linter__rules__flake8_builtins__tests__A005_A005__modules__string____init__.py_builtins_allowed_modules.snap new file mode 100644 index 0000000000000..69dc571b6ffc5 --- /dev/null +++ b/crates/ruff_linter/src/rules/flake8_builtins/snapshots/ruff_linter__rules__flake8_builtins__tests__A005_A005__modules__string____init__.py_builtins_allowed_modules.snap @@ -0,0 +1,4 @@ +--- +source: crates/ruff_linter/src/rules/flake8_builtins/mod.rs +--- +__init__.py:1:1: A005 Module `string` is shadowing a Python builtin module diff --git a/crates/ruff_linter/src/rules/flake8_builtins/snapshots/ruff_linter__rules__flake8_builtins__tests__A006_A006.py.snap b/crates/ruff_linter/src/rules/flake8_builtins/snapshots/ruff_linter__rules__flake8_builtins__tests__A006_A006.py.snap new file mode 100644 index 0000000000000..14544a4f3da76 --- /dev/null +++ b/crates/ruff_linter/src/rules/flake8_builtins/snapshots/ruff_linter__rules__flake8_builtins__tests__A006_A006.py.snap @@ -0,0 +1,64 @@ +--- +source: crates/ruff_linter/src/rules/flake8_builtins/mod.rs +--- +A006.py:1:8: A006 Lambda argument `print` is shadowing a Python builtin + | +1 | lambda print, copyright: print + | ^^^^^ A006 +2 | lambda x, float, y: x + y +3 | lambda min, max: min + | + +A006.py:1:15: A006 Lambda argument `copyright` is shadowing a Python builtin + | +1 | lambda print, copyright: print + | ^^^^^^^^^ A006 +2 | lambda x, float, y: x + y +3 | lambda min, max: min + | + +A006.py:2:11: A006 Lambda argument `float` is shadowing a Python builtin + | +1 | lambda print, copyright: print +2 | lambda x, float, y: x + y + | ^^^^^ A006 +3 | lambda min, max: min +4 | lambda id: id + | + +A006.py:3:8: A006 Lambda argument `min` is shadowing a Python builtin + | +1 | lambda print, copyright: print +2 | lambda x, float, y: x + y +3 | lambda min, max: min + | ^^^ A006 +4 | lambda id: id +5 | lambda dir: dir + | + +A006.py:3:13: A006 Lambda argument `max` is shadowing a Python builtin + | +1 | lambda print, copyright: print +2 | lambda x, float, y: x + y +3 | lambda min, max: min + | ^^^ A006 +4 | lambda id: id +5 | lambda dir: dir + | + +A006.py:4:8: A006 Lambda argument `id` is shadowing a Python builtin + | +2 | lambda x, float, y: x + y +3 | lambda min, max: min +4 | lambda id: id + | ^^ A006 +5 | lambda dir: dir + | + +A006.py:5:8: A006 Lambda argument `dir` is shadowing a Python builtin + | +3 | lambda min, max: min +4 | lambda id: id +5 | lambda dir: dir + | ^^^ A006 + | diff --git a/crates/ruff_linter/src/rules/flake8_builtins/snapshots/ruff_linter__rules__flake8_builtins__tests__A006_A006.py_builtins_ignorelist.snap b/crates/ruff_linter/src/rules/flake8_builtins/snapshots/ruff_linter__rules__flake8_builtins__tests__A006_A006.py_builtins_ignorelist.snap new file mode 100644 index 0000000000000..2ac1bb621e447 --- /dev/null +++ b/crates/ruff_linter/src/rules/flake8_builtins/snapshots/ruff_linter__rules__flake8_builtins__tests__A006_A006.py_builtins_ignorelist.snap @@ -0,0 +1,47 @@ +--- +source: crates/ruff_linter/src/rules/flake8_builtins/mod.rs +--- +A006.py:1:8: A006 Lambda argument `print` is shadowing a Python builtin + | +1 | lambda print, copyright: print + | ^^^^^ A006 +2 | lambda x, float, y: x + y +3 | lambda min, max: min + | + +A006.py:1:15: A006 Lambda argument `copyright` is shadowing a Python builtin + | +1 | lambda print, copyright: print + | ^^^^^^^^^ A006 +2 | lambda x, float, y: x + y +3 | lambda min, max: min + | + +A006.py:2:11: A006 Lambda argument `float` is shadowing a Python builtin + | +1 | lambda print, copyright: print +2 | lambda x, float, y: x + y + | ^^^^^ A006 +3 | lambda min, max: min +4 | lambda id: id + | + +A006.py:3:8: A006 Lambda argument `min` is shadowing a Python builtin + | +1 | lambda print, copyright: print +2 | lambda x, float, y: x + y +3 | lambda min, max: min + | ^^^ A006 +4 | lambda id: id +5 | lambda dir: dir + | + +A006.py:3:13: A006 Lambda argument `max` is shadowing a Python builtin + | +1 | lambda print, copyright: print +2 | lambda x, float, y: x + y +3 | lambda min, max: min + | ^^^ A006 +4 | lambda id: id +5 | lambda dir: dir + | diff --git a/crates/ruff_linter/src/rules/flake8_django/rules/nullable_model_string_field.rs b/crates/ruff_linter/src/rules/flake8_django/rules/nullable_model_string_field.rs index 741e8e831f6ac..2fe2b6aaba9d2 100644 --- a/crates/ruff_linter/src/rules/flake8_django/rules/nullable_model_string_field.rs +++ b/crates/ruff_linter/src/rules/flake8_django/rules/nullable_model_string_field.rs @@ -88,7 +88,7 @@ fn is_nullable_field<'a>(value: &'a Expr, semantic: &'a SemanticModel) -> Option let mut null_key = false; let mut blank_key = false; let mut unique_key = false; - for keyword in call.arguments.keywords.iter() { + for keyword in &*call.arguments.keywords { let Some(argument) = &keyword.arg else { continue; }; diff --git a/crates/ruff_linter/src/rules/flake8_errmsg/rules/string_in_exception.rs b/crates/ruff_linter/src/rules/flake8_errmsg/rules/string_in_exception.rs index 60da28d45a9e7..2aa46a6e2d1a9 100644 --- a/crates/ruff_linter/src/rules/flake8_errmsg/rules/string_in_exception.rs +++ b/crates/ruff_linter/src/rules/flake8_errmsg/rules/string_in_exception.rs @@ -260,7 +260,7 @@ pub(crate) fn string_in_exception(checker: &mut Checker, stmt: &Stmt, exc: &Expr /// /// The fix includes two edits: /// 1. Insert the exception argument into a variable assignment before the -/// `raise` statement. The variable name is `msg`. +/// `raise` statement. The variable name is `msg`. /// 2. Replace the exception argument with the variable name. fn generate_fix( stmt: &Stmt, diff --git a/crates/ruff_linter/src/rules/flake8_executable/rules/shebang_missing_executable_file.rs b/crates/ruff_linter/src/rules/flake8_executable/rules/shebang_missing_executable_file.rs index ab3ff053013dc..7e78cd269e163 100644 --- a/crates/ruff_linter/src/rules/flake8_executable/rules/shebang_missing_executable_file.rs +++ b/crates/ruff_linter/src/rules/flake8_executable/rules/shebang_missing_executable_file.rs @@ -22,10 +22,16 @@ use crate::rules::flake8_executable::helpers::is_executable; /// If a `.py` file is executable, but does not have a shebang, it may be run /// with the wrong interpreter, or fail to run at all. /// -/// If the file is meant to be executable, add a shebang; otherwise, remove the -/// executable bit from the file. +/// If the file is meant to be executable, add a shebang, as in: +/// ```python +/// #!/usr/bin/env python +/// ``` /// -/// _This rule is only available on Unix-like systems._ +/// Otherwise, remove the executable bit from the file (e.g., `chmod -x __main__.py`). +/// +/// A file is considered executable if it has the executable bit set (i.e., its +/// permissions mode intersects with `0o111`). As such, _this rule is only +/// available on Unix-like systems_, and is not enforced on Windows or WSL. /// /// ## References /// - [Python documentation: Executable Python Scripts](https://docs.python.org/3/tutorial/appendix.html#executable-python-scripts) diff --git a/crates/ruff_linter/src/rules/flake8_executable/rules/shebang_not_executable.rs b/crates/ruff_linter/src/rules/flake8_executable/rules/shebang_not_executable.rs index a6ae32d1183c7..872cc7e64f1a4 100644 --- a/crates/ruff_linter/src/rules/flake8_executable/rules/shebang_not_executable.rs +++ b/crates/ruff_linter/src/rules/flake8_executable/rules/shebang_not_executable.rs @@ -22,10 +22,21 @@ use crate::rules::flake8_executable::helpers::is_executable; /// executable. If a file contains a shebang but is not executable, then the /// shebang is misleading, or the file is missing the executable bit. /// -/// If the file is meant to be executable, add a shebang; otherwise, remove the -/// executable bit from the file. +/// If the file is meant to be executable, add a shebang, as in: +/// ```python +/// #!/usr/bin/env python +/// ``` /// -/// _This rule is only available on Unix-like systems._ +/// Otherwise, remove the executable bit from the file (e.g., `chmod -x __main__.py`). +/// +/// A file is considered executable if it has the executable bit set (i.e., its +/// permissions mode intersects with `0o111`). As such, _this rule is only +/// available on Unix-like systems_, and is not enforced on Windows or WSL. +/// +/// ## Example +/// ```python +/// #!/usr/bin/env python +/// ``` /// /// ## References /// - [Python documentation: Executable Python Scripts](https://docs.python.org/3/tutorial/appendix.html#executable-python-scripts) diff --git a/crates/ruff_linter/src/rules/flake8_logging_format/rules/logging_call.rs b/crates/ruff_linter/src/rules/flake8_logging_format/rules/logging_call.rs index 9b7c4ac0e8fd5..03e25673bd3c3 100644 --- a/crates/ruff_linter/src/rules/flake8_logging_format/rules/logging_call.rs +++ b/crates/ruff_linter/src/rules/flake8_logging_format/rules/logging_call.rs @@ -110,7 +110,7 @@ fn check_log_record_attr_clash(checker: &mut Checker, extra: &Keyword) { .. }) => { if checker.semantic().match_builtin_expr(func, "dict") { - for keyword in keywords.iter() { + for keyword in &**keywords { if let Some(attr) = &keyword.arg { if is_reserved_attr(attr) { checker.diagnostics.push(Diagnostic::new( diff --git a/crates/ruff_linter/src/rules/flake8_pie/rules/unnecessary_dict_kwargs.rs b/crates/ruff_linter/src/rules/flake8_pie/rules/unnecessary_dict_kwargs.rs index 2800470b04431..236d339b80709 100644 --- a/crates/ruff_linter/src/rules/flake8_pie/rules/unnecessary_dict_kwargs.rs +++ b/crates/ruff_linter/src/rules/flake8_pie/rules/unnecessary_dict_kwargs.rs @@ -58,7 +58,7 @@ impl Violation for UnnecessaryDictKwargs { /// PIE804 pub(crate) fn unnecessary_dict_kwargs(checker: &mut Checker, call: &ast::ExprCall) { let mut duplicate_keywords = None; - for keyword in call.arguments.keywords.iter() { + for keyword in &*call.arguments.keywords { // keyword is a spread operator (indicated by None). if keyword.arg.is_some() { continue; @@ -152,7 +152,7 @@ fn duplicates(call: &ast::ExprCall) -> FxHashSet<&str> { FxHashSet::with_capacity_and_hasher(call.arguments.keywords.len(), FxBuildHasher); let mut duplicates = FxHashSet::with_capacity_and_hasher(call.arguments.keywords.len(), FxBuildHasher); - for keyword in call.arguments.keywords.iter() { + for keyword in &*call.arguments.keywords { if let Some(name) = &keyword.arg { if !seen.insert(name.as_str()) { duplicates.insert(name.as_str()); diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/non_self_return_type.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/non_self_return_type.rs index d2a28ae2e8d1c..b02cb555b5cc7 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/non_self_return_type.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/non_self_return_type.rs @@ -135,7 +135,7 @@ pub(crate) fn non_self_return_type( }; // PEP 673 forbids the use of `typing(_extensions).Self` in metaclasses. - if is_metaclass(class_def, semantic) { + if analyze::class::is_metaclass(class_def, semantic) { return; } @@ -219,16 +219,6 @@ pub(crate) fn non_self_return_type( } } -/// Returns `true` if the given class is a metaclass. -fn is_metaclass(class_def: &ast::StmtClassDef, semantic: &SemanticModel) -> bool { - analyze::class::any_qualified_name(class_def, semantic, &|qualified_name| { - matches!( - qualified_name.segments(), - ["" | "builtins", "type"] | ["abc", "ABCMeta"] | ["enum", "EnumMeta" | "EnumType"] - ) - }) -} - /// Returns `true` if the method is an in-place binary operator. fn is_inplace_bin_op(name: &str) -> bool { matches!( diff --git a/crates/ruff_linter/src/rules/flake8_return/rules/function.rs b/crates/ruff_linter/src/rules/flake8_return/rules/function.rs index 0085f6bfe4b2b..17e041bed5c8e 100644 --- a/crates/ruff_linter/src/rules/flake8_return/rules/function.rs +++ b/crates/ruff_linter/src/rules/flake8_return/rules/function.rs @@ -12,6 +12,7 @@ use ruff_python_ast::whitespace::indentation; use ruff_python_ast::{self as ast, Decorator, ElifElseClause, Expr, Stmt}; use ruff_python_codegen::Stylist; use ruff_python_index::Indexer; +use ruff_python_semantic::analyze::visibility::is_property; use ruff_python_semantic::SemanticModel; use ruff_python_trivia::{is_python_whitespace, SimpleTokenKind, SimpleTokenizer}; use ruff_source_file::Locator; @@ -373,12 +374,12 @@ fn unnecessary_return_none(checker: &mut Checker, decorator_list: &[Decorator], continue; } - // Skip properties. - if decorator_list.iter().any(|decorator| { - checker - .semantic() - .match_builtin_expr(&decorator.expression, "property") - }) { + // Skip property functions + if is_property( + decorator_list, + checker.settings.pydocstyle.property_decorators(), + checker.semantic(), + ) { return; } diff --git a/crates/ruff_linter/src/rules/pep8_naming/rules/invalid_first_argument_name.rs b/crates/ruff_linter/src/rules/pep8_naming/rules/invalid_first_argument_name.rs index 999bb151a172b..c43f2fafe59f3 100644 --- a/crates/ruff_linter/src/rules/pep8_naming/rules/invalid_first_argument_name.rs +++ b/crates/ruff_linter/src/rules/pep8_naming/rules/invalid_first_argument_name.rs @@ -5,6 +5,7 @@ use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast as ast; use ruff_python_ast::ParameterWithDefault; use ruff_python_codegen::Stylist; +use ruff_python_semantic::analyze::class::is_metaclass; use ruff_python_semantic::analyze::function_type; use ruff_python_semantic::{Scope, ScopeKind, SemanticModel}; use ruff_text_size::Ranged; @@ -190,22 +191,34 @@ pub(crate) fn invalid_first_argument_name( panic!("Expected ScopeKind::Function") }; - let Some(parent) = checker.semantic().first_non_type_parent_scope(scope) else { + let semantic = checker.semantic(); + + let Some(parent_scope) = semantic.first_non_type_parent_scope(scope) else { + return; + }; + + let ScopeKind::Class(parent) = parent_scope.kind else { return; }; let function_type = match function_type::classify( name, decorator_list, - parent, - checker.semantic(), + parent_scope, + semantic, &checker.settings.pep8_naming.classmethod_decorators, &checker.settings.pep8_naming.staticmethod_decorators, ) { function_type::FunctionType::Function | function_type::FunctionType::StaticMethod => { return; } - function_type::FunctionType::Method => FunctionType::Method, + function_type::FunctionType::Method => { + if is_metaclass(parent, semantic) { + FunctionType::ClassMethod + } else { + FunctionType::Method + } + } function_type::FunctionType::ClassMethod => FunctionType::ClassMethod, }; if !checker.enabled(function_type.rule()) { diff --git a/crates/ruff_linter/src/rules/pep8_naming/rules/invalid_module_name.rs b/crates/ruff_linter/src/rules/pep8_naming/rules/invalid_module_name.rs index 7c8a178b4ab80..6be2eb83aa7b4 100644 --- a/crates/ruff_linter/src/rules/pep8_naming/rules/invalid_module_name.rs +++ b/crates/ruff_linter/src/rules/pep8_naming/rules/invalid_module_name.rs @@ -4,6 +4,7 @@ use std::path::Path; use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_stdlib::identifiers::{is_migration_name, is_module_name}; +use ruff_python_stdlib::path::is_module_file; use ruff_text_size::TextRange; use crate::rules::pep8_naming::settings::IgnoreNames; @@ -92,16 +93,6 @@ pub(crate) fn invalid_module_name( None } -/// Return `true` if a [`Path`] should use the name of its parent directory as its module name. -fn is_module_file(path: &Path) -> bool { - path.file_name().is_some_and(|file_name| { - file_name == "__init__.py" - || file_name == "__init__.pyi" - || file_name == "__main__.py" - || file_name == "__main__.pyi" - }) -} - /// Return `true` if a [`Path`] refers to a migration file. fn is_migration_file(path: &Path) -> bool { path.parent() diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/blank_lines.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/blank_lines.rs index 98bcbbb36ef75..30492e2fcca8b 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/blank_lines.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/blank_lines.rs @@ -352,13 +352,13 @@ struct LogicalLineInfo { kind: LogicalLineKind, first_token_range: TextRange, - // The kind of the last non-trivia token before the newline ending the logical line. + /// The kind of the last non-trivia token before the newline ending the logical line. last_token: TokenKind, - // The end of the logical line including the newline. + /// The end of the logical line including the newline. logical_line_end: TextSize, - // `true` if this is not a blank but only consists of a comment. + /// `true` if this is not a blank but only consists of a comment. is_comment_only: bool, /// If running on a notebook, whether the line is the first logical line (or a comment preceding it) of its cell. @@ -721,6 +721,7 @@ impl<'a> BlankLinesChecker<'a> { /// E301, E302, E303, E304, E305, E306 pub(crate) fn check_lines(&self, tokens: &Tokens, diagnostics: &mut Vec) { let mut prev_indent_length: Option = None; + let mut prev_logical_line: Option = None; let mut state = BlankLinesState::default(); let line_preprocessor = LinePreprocessor::new(tokens, self.locator, self.indent_width, self.cell_offsets); @@ -739,6 +740,23 @@ impl<'a> BlankLinesChecker<'a> { } } + // Reset the previous line end after an indent or dedent: + // ```python + // if True: + // import test + // # comment + // a = 10 + // ``` + // The `# comment` should be attached to the `import` statement, rather than the + // assignment. + if let Some(prev_logical_line) = prev_logical_line { + if prev_logical_line.is_comment_only { + if prev_logical_line.indent_length != logical_line.indent_length { + state.last_non_comment_line_end = prev_logical_line.logical_line_end; + } + } + } + state.class_status.update(&logical_line); state.fn_status.update(&logical_line); @@ -793,6 +811,8 @@ impl<'a> BlankLinesChecker<'a> { if !logical_line.is_comment_only { prev_indent_length = Some(logical_line.indent_length); } + + prev_logical_line = Some(logical_line); } } @@ -891,9 +911,10 @@ impl<'a> BlankLinesChecker<'a> { ))); } else { diagnostic.set_fix(Fix::safe_edit(Edit::insertion( - self.stylist - .line_ending() - .repeat(expected_blank_lines_before_definition as usize), + self.stylist.line_ending().repeat( + (expected_blank_lines_before_definition + - line.preceding_blank_lines.count()) as usize, + ), self.locator.line_start(state.last_non_comment_line_end), ))); } @@ -1006,10 +1027,10 @@ impl<'a> BlankLinesChecker<'a> { ))); } else { diagnostic.set_fix(Fix::safe_edit(Edit::insertion( - self.stylist - .line_ending() - .repeat(BLANK_LINES_TOP_LEVEL as usize), - self.locator.line_start(line.first_token_range.start()), + self.stylist.line_ending().repeat( + (BLANK_LINES_TOP_LEVEL - line.preceding_blank_lines.count()) as usize, + ), + self.locator.line_start(state.last_non_comment_line_end), ))); } diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/type_comparison.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/type_comparison.rs index fb415eb7234e4..ed9430b963938 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/type_comparison.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/type_comparison.rs @@ -180,8 +180,8 @@ fn is_type(expr: &Expr, semantic: &SemanticModel) -> bool { /// Returns `true` if the [`Expr`] appears to be a reference to a NumPy dtype, since: /// > `dtype` are a bit of a strange beast, but definitely best thought of as instances, not /// > classes, and they are meant to be comparable not just to their own class, but also to the -/// corresponding scalar types (e.g., `x.dtype == np.float32`) and strings (e.g., -/// `x.dtype == ['i1,i4']`; basically, __eq__ always tries to do `dtype(other)`). +/// > corresponding scalar types (e.g., `x.dtype == np.float32`) and strings (e.g., +/// > `x.dtype == ['i1,i4']`; basically, __eq__ always tries to do `dtype(other)`). fn is_dtype(expr: &Expr, semantic: &SemanticModel) -> bool { match expr { // Ex) `np.dtype(obj)` diff --git a/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E302_E30.py.snap b/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E302_E30.py.snap index 3ae60bb0da739..e3fe5c1f337a7 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E302_E30.py.snap +++ b/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E302_E30.py.snap @@ -179,10 +179,9 @@ E30.py:602:1: E302 [*] Expected 2 blank lines, found 1 599 599 | pass 600 600 | 601 |+ - 602 |+ -601 603 | # comment -602 604 | @decorator -603 605 | def g(): +601 602 | # comment +602 603 | @decorator +603 604 | def g(): E30.py:624:1: E302 [*] Expected 2 blank lines, found 0 | @@ -223,3 +222,66 @@ E30.py:634:1: E302 [*] Expected 2 blank lines, found 1 634 635 | def fn(a: int | str) -> int | str: 635 636 | ... 636 637 | # end + +E30.py:944:1: E302 [*] Expected 2 blank lines, found 0 + | +942 | pass +943 | # comment +944 | def test_clientmodel(): + | ^^^ E302 +945 | pass +946 | # end + | + = help: Add missing blank line(s) + +ℹ Safe fix +941 941 | def test_update(): +942 942 | pass +943 943 | # comment + 944 |+ + 945 |+ +944 946 | def test_clientmodel(): +945 947 | pass +946 948 | # end + +E30.py:953:1: E302 [*] Expected 2 blank lines, found 0 + | +951 | pass +952 | # comment +953 | def test_clientmodel(): + | ^^^ E302 +954 | pass +955 | # end + | + = help: Add missing blank line(s) + +ℹ Safe fix +950 950 | def test_update(): +951 951 | pass +952 952 | # comment + 953 |+ + 954 |+ +953 955 | def test_clientmodel(): +954 956 | pass +955 957 | # end + +E30.py:962:1: E302 [*] Expected 2 blank lines, found 0 + | +960 | pass +961 | # comment +962 | def test_clientmodel(): + | ^^^ E302 +963 | pass +964 | # end + | + = help: Add missing blank line(s) + +ℹ Safe fix +958 958 | # E302 +959 959 | def test_update(): +960 960 | pass + 961 |+ + 962 |+ +961 963 | # comment +962 964 | def test_clientmodel(): +963 965 | pass diff --git a/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E305_E30.py.snap b/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E305_E30.py.snap index 6ebd457fc2509..275e9a19ef86c 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E305_E30.py.snap +++ b/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E305_E30.py.snap @@ -15,10 +15,9 @@ E30.py:798:1: E305 [*] Expected 2 blank lines after class or function definition 796 796 | 797 797 | # another comment 798 |+ - 799 |+ -798 800 | fn() -799 801 | # end -800 802 | +798 799 | fn() +799 800 | # end +800 801 | E30.py:809:1: E305 [*] Expected 2 blank lines after class or function definition, found (1) | @@ -34,10 +33,9 @@ E30.py:809:1: E305 [*] Expected 2 blank lines after class or function definition 807 807 | 808 808 | # another comment 809 |+ - 810 |+ -809 811 | a = 1 -810 812 | # end -811 813 | +809 810 | a = 1 +810 811 | # end +811 812 | E30.py:821:1: E305 [*] Expected 2 blank lines after class or function definition, found (1) | @@ -70,14 +68,13 @@ E30.py:833:1: E305 [*] Expected 2 blank lines after class or function definition = help: Add missing blank line(s) ℹ Safe fix +829 829 | def a(): 830 830 | print() 831 831 | -832 832 | # Two spaces before comments, too. - 833 |+ - 834 |+ -833 835 | if a(): -834 836 | a() -835 837 | # end + 832 |+ +832 833 | # Two spaces before comments, too. +833 834 | if a(): +834 835 | a() E30.py:846:1: E305 [*] Expected 2 blank lines after class or function definition, found (1) | @@ -98,3 +95,21 @@ E30.py:846:1: E305 [*] Expected 2 blank lines after class or function definition 846 847 | if __name__ == '__main__': 847 848 | main() 848 849 | # end + +E30.py:973:1: E305 [*] Expected 2 blank lines after class or function definition, found (1) + | +972 | # ====== Cool constants ======== +973 | BANANA = 100 + | ^^^^^^ E305 +974 | APPLE = 200 + | + = help: Add missing blank line(s) + +ℹ Safe fix +969 969 | class A: +970 970 | pass +971 971 | + 972 |+ +972 973 | # ====== Cool constants ======== +973 974 | BANANA = 100 +974 975 | APPLE = 200 diff --git a/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__blank_lines_E305_notebook.snap b/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__blank_lines_E305_notebook.snap index 9672329251a92..15dde40958e8c 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__blank_lines_E305_notebook.snap +++ b/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__blank_lines_E305_notebook.snap @@ -16,7 +16,6 @@ E30.ipynb:55:1: E305 [*] Expected 2 blank lines after class or function definiti 53 53 | 54 54 | # another comment 55 |+ - 56 |+ -55 57 | fn() -56 58 | # end -57 59 | # E306:3:5 +55 56 | fn() +56 57 | # end +57 58 | # E306:3:5 diff --git a/crates/ruff_linter/src/rules/pydoclint/mod.rs b/crates/ruff_linter/src/rules/pydoclint/mod.rs index db2c79662f685..68565de689e19 100644 --- a/crates/ruff_linter/src/rules/pydoclint/mod.rs +++ b/crates/ruff_linter/src/rules/pydoclint/mod.rs @@ -3,7 +3,6 @@ pub(crate) mod rules; #[cfg(test)] mod tests { - use std::collections::BTreeSet; use std::convert::AsRef; use std::path::Path; @@ -11,7 +10,8 @@ mod tests { use test_case::test_case; use crate::registry::Rule; - use crate::rules::pydocstyle::settings::{Convention, Settings}; + use crate::rules::pydocstyle; + use crate::rules::pydocstyle::settings::Convention; use crate::test::test_path; use crate::{assert_messages, settings}; @@ -37,11 +37,7 @@ mod tests { let diagnostics = test_path( Path::new("pydoclint").join(path).as_path(), &settings::LinterSettings { - pydocstyle: Settings { - convention: Some(Convention::Google), - ignore_decorators: BTreeSet::new(), - property_decorators: BTreeSet::new(), - }, + pydocstyle: pydocstyle::settings::Settings::new(Some(Convention::Google), [], []), ..settings::LinterSettings::for_rule(rule_code) }, )?; @@ -60,11 +56,7 @@ mod tests { let diagnostics = test_path( Path::new("pydoclint").join(path).as_path(), &settings::LinterSettings { - pydocstyle: Settings { - convention: Some(Convention::Numpy), - ignore_decorators: BTreeSet::new(), - property_decorators: BTreeSet::new(), - }, + pydocstyle: pydocstyle::settings::Settings::new(Some(Convention::Numpy), [], []), ..settings::LinterSettings::for_rule(rule_code) }, )?; diff --git a/crates/ruff_linter/src/rules/pydoclint/rules/check_docstring.rs b/crates/ruff_linter/src/rules/pydoclint/rules/check_docstring.rs index e71b1501fcd57..91054bb29f8cb 100644 --- a/crates/ruff_linter/src/rules/pydoclint/rules/check_docstring.rs +++ b/crates/ruff_linter/src/rules/pydoclint/rules/check_docstring.rs @@ -15,7 +15,7 @@ use crate::registry::Rule; use crate::rules::pydocstyle::settings::Convention; /// ## What it does -/// Checks for functions with explicit returns missing a returns section in +/// Checks for functions with explicit returns missing a "returns" section in /// their docstring. /// /// ## Why is this bad? @@ -59,7 +59,7 @@ impl Violation for DocstringMissingReturns { } /// ## What it does -/// Checks for function docstrings that have a returns section without +/// Checks for function docstrings that have a "returns" section without /// needing one. /// /// ## Why is this bad? @@ -103,7 +103,7 @@ impl Violation for DocstringExtraneousReturns { } /// ## What it does -/// Checks for functions with yield statements missing a yields section in +/// Checks for functions with yield statements missing a "yields" section in /// their docstring. /// /// ## Why is this bad? @@ -113,27 +113,27 @@ impl Violation for DocstringExtraneousReturns { /// ## Example /// ```python /// def count_to_n(n: int) -> int: -/// """Generate integers up to n. +/// """Generate integers up to *n*. /// /// Args: -/// n: Max integer. +/// n: The number at which to stop counting. /// """ -/// for i in range(n): +/// for i in range(1, n + 1): /// yield i /// ``` /// /// Use instead: /// ```python /// def count_to_n(n: int) -> int: -/// """Generate integers up to n. +/// """Generate integers up to *n*. /// /// Args: -/// n: Max integer. +/// n: The number at which to stop counting. /// /// Yields: -/// int: The ith integer. +/// int: The number we're at in the count. /// """ -/// for i in range(n): +/// for i in range(1, n + 1): /// yield i /// ``` #[violation] @@ -144,10 +144,14 @@ impl Violation for DocstringMissingYields { fn message(&self) -> String { format!("`yield` is not documented in docstring") } + + fn fix_title(&self) -> Option { + Some(format!("Add a \"Yields\" section to the docstring")) + } } /// ## What it does -/// Checks for function docstrings that have a yields section without +/// Checks for function docstrings that have a "yields" section without /// needing one. /// /// ## Why is this bad? @@ -186,13 +190,17 @@ pub struct DocstringExtraneousYields; impl Violation for DocstringExtraneousYields { #[derive_message_formats] fn message(&self) -> String { - format!("Docstring should not have a yields section because the function doesn't yield anything") + format!("Docstring has a \"Yields\" section but the function doesn't yield anything") + } + + fn fix_title(&self) -> Option { + Some(format!("Remove the \"Yields\" section")) } } /// ## What it does /// Checks for function docstrings that do not include documentation for all -/// explicitly-raised exceptions. +/// explicitly raised exceptions. /// /// ## Why is this bad? /// If a function raises an exception without documenting it in its docstring, @@ -365,7 +373,7 @@ struct DocstringSections<'a> { impl<'a> DocstringSections<'a> { fn from_sections(sections: &'a SectionContexts, style: SectionStyle) -> Self { let mut docstring_sections = Self::default(); - for section in sections.iter() { + for section in sections { match section.kind() { SectionKind::Raises => { docstring_sections.raises = Some(RaisesSection::from_section(§ion, style)) @@ -555,33 +563,12 @@ fn extract_raised_exception<'a>( None } -// Checks if a function has a `@property` decorator -fn is_property(definition: &Definition, checker: &Checker) -> bool { - let Some(function) = definition.as_function_def() else { - return false; - }; - - let Some(last_decorator) = function.decorator_list.last() else { - return false; - }; - - checker - .semantic() - .resolve_qualified_name(&last_decorator.expression) - .is_some_and(|qualified_name| { - matches!( - qualified_name.segments(), - ["", "property"] | ["functools", "cached_property"] - ) - }) -} - /// DOC201, DOC202, DOC402, DOC403, DOC501, DOC502 pub(crate) fn check_docstring( checker: &mut Checker, definition: &Definition, section_contexts: &SectionContexts, - convention: Option<&Convention>, + convention: Option, ) { let mut diagnostics = Vec::new(); let Definition::Member(member) = definition else { @@ -614,8 +601,9 @@ pub(crate) fn check_docstring( }; // DOC201 - if checker.enabled(Rule::DocstringMissingReturns) { - if !is_property(definition, checker) && docstring_sections.returns.is_none() { + if checker.enabled(Rule::DocstringMissingReturns) && docstring_sections.returns.is_none() { + let extra_property_decorators = checker.settings.pydocstyle.property_decorators(); + if !definition.is_property(extra_property_decorators, checker.semantic()) { if let Some(body_return) = body_entries.returns.first() { let diagnostic = Diagnostic::new(DocstringMissingReturns, body_return.range()); diagnostics.push(diagnostic); diff --git a/crates/ruff_linter/src/rules/pydoclint/snapshots/ruff_linter__rules__pydoclint__tests__docstring-extraneous-yields_DOC403_google.py.snap b/crates/ruff_linter/src/rules/pydoclint/snapshots/ruff_linter__rules__pydoclint__tests__docstring-extraneous-yields_DOC403_google.py.snap index 3ee75598a189e..2c3c651b226ff 100644 --- a/crates/ruff_linter/src/rules/pydoclint/snapshots/ruff_linter__rules__pydoclint__tests__docstring-extraneous-yields_DOC403_google.py.snap +++ b/crates/ruff_linter/src/rules/pydoclint/snapshots/ruff_linter__rules__pydoclint__tests__docstring-extraneous-yields_DOC403_google.py.snap @@ -1,7 +1,7 @@ --- source: crates/ruff_linter/src/rules/pydoclint/mod.rs --- -DOC403_google.py:20:1: DOC403 Docstring should not have a yields section because the function doesn't yield anything +DOC403_google.py:20:1: DOC403 Docstring has a "Yields" section but the function doesn't yield anything | 18 | num (int): A number 19 | @@ -11,8 +11,9 @@ DOC403_google.py:20:1: DOC403 Docstring should not have a yields section because | |____^ DOC403 23 | print('test') | + = help: Remove the "Yields" section -DOC403_google.py:36:1: DOC403 Docstring should not have a yields section because the function doesn't yield anything +DOC403_google.py:36:1: DOC403 Docstring has a "Yields" section but the function doesn't yield anything | 34 | num (int): A number 35 | @@ -22,3 +23,4 @@ DOC403_google.py:36:1: DOC403 Docstring should not have a yields section because | |________^ DOC403 39 | print('test') | + = help: Remove the "Yields" section diff --git a/crates/ruff_linter/src/rules/pydoclint/snapshots/ruff_linter__rules__pydoclint__tests__docstring-extraneous-yields_DOC403_numpy.py.snap b/crates/ruff_linter/src/rules/pydoclint/snapshots/ruff_linter__rules__pydoclint__tests__docstring-extraneous-yields_DOC403_numpy.py.snap index 394f7c7ca38df..aff49d2869d95 100644 --- a/crates/ruff_linter/src/rules/pydoclint/snapshots/ruff_linter__rules__pydoclint__tests__docstring-extraneous-yields_DOC403_numpy.py.snap +++ b/crates/ruff_linter/src/rules/pydoclint/snapshots/ruff_linter__rules__pydoclint__tests__docstring-extraneous-yields_DOC403_numpy.py.snap @@ -1,7 +1,7 @@ --- source: crates/ruff_linter/src/rules/pydoclint/mod.rs --- -DOC403_numpy.py:24:1: DOC403 Docstring should not have a yields section because the function doesn't yield anything +DOC403_numpy.py:24:1: DOC403 Docstring has a "Yields" section but the function doesn't yield anything | 22 | A number 23 | @@ -13,8 +13,9 @@ DOC403_numpy.py:24:1: DOC403 Docstring should not have a yields section because | |____^ DOC403 29 | print('test') | + = help: Remove the "Yields" section -DOC403_numpy.py:44:1: DOC403 Docstring should not have a yields section because the function doesn't yield anything +DOC403_numpy.py:44:1: DOC403 Docstring has a "Yields" section but the function doesn't yield anything | 42 | A number 43 | @@ -26,3 +27,4 @@ DOC403_numpy.py:44:1: DOC403 Docstring should not have a yields section because | |________^ DOC403 49 | print('test') | + = help: Remove the "Yields" section diff --git a/crates/ruff_linter/src/rules/pydoclint/snapshots/ruff_linter__rules__pydoclint__tests__docstring-missing-yields_DOC402_google.py.snap b/crates/ruff_linter/src/rules/pydoclint/snapshots/ruff_linter__rules__pydoclint__tests__docstring-missing-yields_DOC402_google.py.snap index 8226ed16ff95e..c9ebc3f280864 100644 --- a/crates/ruff_linter/src/rules/pydoclint/snapshots/ruff_linter__rules__pydoclint__tests__docstring-missing-yields_DOC402_google.py.snap +++ b/crates/ruff_linter/src/rules/pydoclint/snapshots/ruff_linter__rules__pydoclint__tests__docstring-missing-yields_DOC402_google.py.snap @@ -8,6 +8,7 @@ DOC402_google.py:9:5: DOC402 `yield` is not documented in docstring 9 | yield 'test' | ^^^^^^^^^^^^ DOC402 | + = help: Add a "Yields" section to the docstring DOC402_google.py:50:9: DOC402 `yield` is not documented in docstring | @@ -16,6 +17,7 @@ DOC402_google.py:50:9: DOC402 `yield` is not documented in docstring 50 | yield 'test' | ^^^^^^^^^^^^ DOC402 | + = help: Add a "Yields" section to the docstring DOC402_google.py:59:9: DOC402 `yield` is not documented in docstring | @@ -26,6 +28,7 @@ DOC402_google.py:59:9: DOC402 `yield` is not documented in docstring 60 | 61 | print("I never yield") | + = help: Add a "Yields" section to the docstring DOC402_google.py:67:5: DOC402 `yield` is not documented in docstring | @@ -34,3 +37,4 @@ DOC402_google.py:67:5: DOC402 `yield` is not documented in docstring 67 | yield from range(10) | ^^^^^^^^^^^^^^^^^^^^ DOC402 | + = help: Add a "Yields" section to the docstring diff --git a/crates/ruff_linter/src/rules/pydoclint/snapshots/ruff_linter__rules__pydoclint__tests__docstring-missing-yields_DOC402_numpy.py.snap b/crates/ruff_linter/src/rules/pydoclint/snapshots/ruff_linter__rules__pydoclint__tests__docstring-missing-yields_DOC402_numpy.py.snap index 55233c7db09c0..4737fe16037ce 100644 --- a/crates/ruff_linter/src/rules/pydoclint/snapshots/ruff_linter__rules__pydoclint__tests__docstring-missing-yields_DOC402_numpy.py.snap +++ b/crates/ruff_linter/src/rules/pydoclint/snapshots/ruff_linter__rules__pydoclint__tests__docstring-missing-yields_DOC402_numpy.py.snap @@ -8,6 +8,7 @@ DOC402_numpy.py:11:5: DOC402 `yield` is not documented in docstring 11 | yield 'test' | ^^^^^^^^^^^^ DOC402 | + = help: Add a "Yields" section to the docstring DOC402_numpy.py:62:9: DOC402 `yield` is not documented in docstring | @@ -16,3 +17,4 @@ DOC402_numpy.py:62:9: DOC402 `yield` is not documented in docstring 62 | yield 'test' | ^^^^^^^^^^^^ DOC402 | + = help: Add a "Yields" section to the docstring diff --git a/crates/ruff_linter/src/rules/pydocstyle/helpers.rs b/crates/ruff_linter/src/rules/pydocstyle/helpers.rs index 3b78c003c9b8d..e943ac915ce8a 100644 --- a/crates/ruff_linter/src/rules/pydocstyle/helpers.rs +++ b/crates/ruff_linter/src/rules/pydocstyle/helpers.rs @@ -1,14 +1,11 @@ -use std::collections::BTreeSet; - use ruff_python_ast::helpers::map_callable; -use ruff_python_ast::name::QualifiedName; use ruff_python_semantic::{Definition, SemanticModel}; use ruff_source_file::UniversalNewlines; use crate::docstrings::sections::{SectionContexts, SectionKind}; use crate::docstrings::styles::SectionStyle; use crate::docstrings::Docstring; -use crate::rules::pydocstyle::settings::Convention; +use crate::rules::pydocstyle::settings::{Convention, Settings}; /// Return the index of the first logical line in a string. pub(super) fn logical_line(content: &str) -> Option { @@ -45,10 +42,12 @@ pub(super) fn ends_with_backslash(line: &str) -> bool { /// Check decorator list to see if function should be ignored. pub(crate) fn should_ignore_definition( definition: &Definition, - ignore_decorators: &BTreeSet, + settings: &Settings, semantic: &SemanticModel, ) -> bool { - if ignore_decorators.is_empty() { + let ignore_decorators = settings.ignore_decorators(); + + if ignore_decorators.len() == 0 { return false; } @@ -61,15 +60,15 @@ pub(crate) fn should_ignore_definition( .resolve_qualified_name(map_callable(&decorator.expression)) .is_some_and(|qualified_name| { ignore_decorators - .iter() - .any(|decorator| QualifiedName::from_dotted_name(decorator) == qualified_name) + .clone() + .any(|decorator| decorator == qualified_name) }) }) } pub(crate) fn get_section_contexts<'a>( docstring: &'a Docstring<'a>, - convention: Option<&'a Convention>, + convention: Option, ) -> SectionContexts<'a> { match convention { Some(Convention::Google) => { diff --git a/crates/ruff_linter/src/rules/pydocstyle/mod.rs b/crates/ruff_linter/src/rules/pydocstyle/mod.rs index 5b2d237766c46..1ea3ff9ffd3ac 100644 --- a/crates/ruff_linter/src/rules/pydocstyle/mod.rs +++ b/crates/ruff_linter/src/rules/pydocstyle/mod.rs @@ -5,7 +5,6 @@ pub mod settings; #[cfg(test)] mod tests { - use std::collections::BTreeSet; use std::path::Path; use anyhow::Result; @@ -98,13 +97,11 @@ mod tests { let diagnostics = test_path( Path::new("pydocstyle").join(path).as_path(), &settings::LinterSettings { - pydocstyle: Settings { - convention: None, - ignore_decorators: BTreeSet::from_iter(["functools.wraps".to_string()]), - property_decorators: BTreeSet::from_iter([ - "gi.repository.GObject.Property".to_string() - ]), - }, + pydocstyle: Settings::new( + None, + ["functools.wraps".to_string()], + ["gi.repository.GObject.Property".to_string()], + ), ..settings::LinterSettings::for_rule(rule_code) }, )?; @@ -129,11 +126,7 @@ mod tests { &settings::LinterSettings { // When inferring the convention, we'll see a few false negatives. // See: https://github.com/PyCQA/pydocstyle/issues/459. - pydocstyle: Settings { - convention: None, - ignore_decorators: BTreeSet::new(), - property_decorators: BTreeSet::new(), - }, + pydocstyle: Settings::default(), ..settings::LinterSettings::for_rule(Rule::UndocumentedParam) }, )?; @@ -147,11 +140,7 @@ mod tests { Path::new("pydocstyle/D417.py"), &settings::LinterSettings { // With explicit Google convention, we should flag every function. - pydocstyle: Settings { - convention: Some(Convention::Google), - ignore_decorators: BTreeSet::new(), - property_decorators: BTreeSet::new(), - }, + pydocstyle: Settings::new(Some(Convention::Google), [], []), ..settings::LinterSettings::for_rule(Rule::UndocumentedParam) }, )?; @@ -164,12 +153,8 @@ mod tests { let diagnostics = test_path( Path::new("pydocstyle/D417.py"), &settings::LinterSettings { - // With explicit Google convention, we shouldn't flag anything. - pydocstyle: Settings { - convention: Some(Convention::Numpy), - ignore_decorators: BTreeSet::new(), - property_decorators: BTreeSet::new(), - }, + // With explicit numpy convention, we shouldn't flag anything. + pydocstyle: Settings::new(Some(Convention::Numpy), [], []), ..settings::LinterSettings::for_rule(Rule::UndocumentedParam) }, )?; diff --git a/crates/ruff_linter/src/rules/pydocstyle/rules/non_imperative_mood.rs b/crates/ruff_linter/src/rules/pydocstyle/rules/non_imperative_mood.rs index 3a2576775b386..2391aeb11a131 100644 --- a/crates/ruff_linter/src/rules/pydocstyle/rules/non_imperative_mood.rs +++ b/crates/ruff_linter/src/rules/pydocstyle/rules/non_imperative_mood.rs @@ -1,11 +1,8 @@ -use std::collections::BTreeSet; - use imperative::Mood; use once_cell::sync::Lazy; use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; -use ruff_python_ast::name::QualifiedName; use ruff_python_semantic::analyze::visibility::{is_property, is_test}; use ruff_source_file::UniversalNewlines; use ruff_text_size::Ranged; @@ -13,6 +10,7 @@ use ruff_text_size::Ranged; use crate::checkers::ast::Checker; use crate::docstrings::Docstring; use crate::rules::pydocstyle::helpers::normalize_word; +use crate::rules::pydocstyle::settings::Settings; static MOOD: Lazy = Lazy::new(Mood::new); @@ -66,24 +64,21 @@ impl Violation for NonImperativeMood { pub(crate) fn non_imperative_mood( checker: &mut Checker, docstring: &Docstring, - property_decorators: &BTreeSet, + settings: &Settings, ) { let Some(function) = docstring.definition.as_function_def() else { return; }; - let property_decorators = property_decorators - .iter() - .map(|decorator| QualifiedName::from_dotted_name(decorator)) - .collect::>(); + if is_test(&function.name) { + return; + } - if is_test(&function.name) - || is_property( - &function.decorator_list, - &property_decorators, - checker.semantic(), - ) - { + if is_property( + &function.decorator_list, + settings.property_decorators(), + checker.semantic(), + ) { return; } diff --git a/crates/ruff_linter/src/rules/pydocstyle/rules/sections.rs b/crates/ruff_linter/src/rules/pydocstyle/rules/sections.rs index 7385226e0904b..95e0c46af6163 100644 --- a/crates/ruff_linter/src/rules/pydocstyle/rules/sections.rs +++ b/crates/ruff_linter/src/rules/pydocstyle/rules/sections.rs @@ -1325,7 +1325,7 @@ pub(crate) fn sections( checker: &mut Checker, docstring: &Docstring, section_contexts: &SectionContexts, - convention: Option<&Convention>, + convention: Option, ) { match convention { Some(Convention::Google) => parse_google_sections(checker, docstring, section_contexts), diff --git a/crates/ruff_linter/src/rules/pydocstyle/settings.rs b/crates/ruff_linter/src/rules/pydocstyle/settings.rs index 974c8742f9ec0..c8b05ba3c5012 100644 --- a/crates/ruff_linter/src/rules/pydocstyle/settings.rs +++ b/crates/ruff_linter/src/rules/pydocstyle/settings.rs @@ -2,12 +2,14 @@ use std::collections::BTreeSet; use std::fmt; +use std::iter::FusedIterator; use serde::{Deserialize, Serialize}; -use crate::display_settings; use ruff_macros::CacheKey; +use ruff_python_ast::name::QualifiedName; +use crate::display_settings; use crate::registry::Rule; #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, CacheKey)] @@ -85,9 +87,36 @@ impl fmt::Display for Convention { #[derive(Debug, Clone, Default, CacheKey)] pub struct Settings { - pub convention: Option, - pub ignore_decorators: BTreeSet, - pub property_decorators: BTreeSet, + convention: Option, + ignore_decorators: BTreeSet, + property_decorators: BTreeSet, +} + +impl Settings { + #[must_use] + pub fn new( + convention: Option, + ignore_decorators: impl IntoIterator, + property_decorators: impl IntoIterator, + ) -> Self { + Self { + convention, + ignore_decorators: ignore_decorators.into_iter().collect(), + property_decorators: property_decorators.into_iter().collect(), + } + } + + pub fn convention(&self) -> Option { + self.convention + } + + pub fn ignore_decorators(&self) -> DecoratorIterator { + DecoratorIterator::new(&self.ignore_decorators) + } + + pub fn property_decorators(&self) -> DecoratorIterator { + DecoratorIterator::new(&self.property_decorators) + } } impl fmt::Display for Settings { @@ -104,3 +133,34 @@ impl fmt::Display for Settings { Ok(()) } } + +#[derive(Debug, Clone)] +pub struct DecoratorIterator<'a> { + decorators: std::collections::btree_set::Iter<'a, String>, +} + +impl<'a> DecoratorIterator<'a> { + fn new(decorators: &'a BTreeSet) -> Self { + Self { + decorators: decorators.iter(), + } + } +} + +impl<'a> Iterator for DecoratorIterator<'a> { + type Item = QualifiedName<'a>; + + fn next(&mut self) -> Option> { + self.decorators + .next() + .map(|deco| QualifiedName::from_dotted_name(deco)) + } +} + +impl FusedIterator for DecoratorIterator<'_> {} + +impl ExactSizeIterator for DecoratorIterator<'_> { + fn len(&self) -> usize { + self.decorators.len() + } +} diff --git a/crates/ruff_linter/src/rules/pyflakes/mod.rs b/crates/ruff_linter/src/rules/pyflakes/mod.rs index c4c0f25d1e47f..f1d9117d42609 100644 --- a/crates/ruff_linter/src/rules/pyflakes/mod.rs +++ b/crates/ruff_linter/src/rules/pyflakes/mod.rs @@ -11,6 +11,7 @@ mod tests { use anyhow::Result; use regex::Regex; + use rustc_hash::FxHashMap; use test_case::test_case; @@ -24,11 +25,12 @@ mod tests { use crate::linter::check_path; use crate::registry::{AsRule, Linter, Rule}; + use crate::rules::isort; use crate::rules::pyflakes; use crate::settings::types::PreviewMode; use crate::settings::{flags, LinterSettings}; use crate::source_kind::SourceKind; - use crate::test::{test_path, test_snippet}; + use crate::test::{test_contents, test_path, test_snippet}; use crate::{assert_messages, directives}; #[test_case(Rule::UnusedImport, Path::new("F401_0.py"))] @@ -232,6 +234,44 @@ mod tests { Ok(()) } + #[test_case( + r"import submodule.a", + "f401_preview_first_party_submodule_no_dunder_all" + )] + #[test_case( + r" + import submodule.a + __all__ = ['FOO'] + FOO = 42", + "f401_preview_first_party_submodule_dunder_all" + )] + fn f401_preview_first_party_submodule(contents: &str, snapshot: &str) { + let diagnostics = test_contents( + &SourceKind::Python(dedent(contents).to_string()), + Path::new("f401_preview_first_party_submodule/__init__.py"), + &LinterSettings { + preview: PreviewMode::Enabled, + isort: isort::settings::Settings { + // This case specifically tests the scenario where + // the unused import is a first-party submodule import; + // use the isort settings to ensure that the `submodule.a` import + // is recognised as first-party in the test: + known_modules: isort::categorize::KnownModules::new( + vec!["submodule".parse().unwrap()], + vec![], + vec![], + vec![], + FxHashMap::default(), + ), + ..isort::settings::Settings::default() + }, + ..LinterSettings::for_rule(Rule::UnusedImport) + }, + ) + .0; + assert_messages!(snapshot, diagnostics); + } + #[test_case(Rule::UnusedImport, Path::new("F401_24/__init__.py"))] #[test_case(Rule::UnusedImport, Path::new("F401_25__all_nonempty/__init__.py"))] #[test_case(Rule::UnusedImport, Path::new("F401_26__all_empty/__init__.py"))] @@ -258,6 +298,7 @@ mod tests { #[test_case(Rule::UnusedImport, Path::new("F401_27__all_mistyped/__init__.py"))] #[test_case(Rule::UnusedImport, Path::new("F401_28__all_multiple/__init__.py"))] #[test_case(Rule::UnusedImport, Path::new("F401_29__all_conditional/__init__.py"))] + #[test_case(Rule::UnusedImport, Path::new("F401_30.py"))] fn f401_deprecated_option(rule_code: Rule, path: &Path) -> Result<()> { let snapshot = format!( "{}_deprecated_option_{}", diff --git a/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs b/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs index bfa884801ccf2..ef134f2c42dfd 100644 --- a/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs +++ b/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs @@ -9,7 +9,7 @@ use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast as ast; use ruff_python_ast::{Stmt, StmtImportFrom}; use ruff_python_semantic::{ - AnyImport, BindingKind, Exceptions, Imported, NodeId, Scope, SemanticModel, + AnyImport, BindingKind, Exceptions, Imported, NodeId, Scope, SemanticModel, SubmoduleImport, }; use ruff_text_size::{Ranged, TextRange}; @@ -18,16 +18,6 @@ use crate::fix; use crate::registry::Rule; use crate::rules::{isort, isort::ImportSection, isort::ImportType}; -#[derive(Debug, Copy, Clone, Eq, PartialEq)] -enum UnusedImportContext { - ExceptHandler, - Init { - first_party: bool, - dunder_all_count: usize, - ignore_init_module_imports: bool, - }, -} - /// ## What it does /// Checks for unused imports. /// @@ -111,8 +101,9 @@ pub struct UnusedImport { module: String, /// Name of the import binding binding: String, - context: Option, + context: UnusedImportContext, multiple: bool, + ignore_init_module_imports: bool, } impl Violation for UnusedImport { @@ -122,17 +113,17 @@ impl Violation for UnusedImport { fn message(&self) -> String { let UnusedImport { name, context, .. } = self; match context { - Some(UnusedImportContext::ExceptHandler) => { + UnusedImportContext::ExceptHandler => { format!( "`{name}` imported but unused; consider using `importlib.util.find_spec` to test for availability" ) } - Some(UnusedImportContext::Init { .. }) => { + UnusedImportContext::DunderInitFirstParty { .. } => { format!( "`{name}` imported but unused; consider removing, adding to `__all__`, or using a redundant alias" ) } - None => format!("`{name}` imported but unused"), + UnusedImportContext::Other => format!("`{name}` imported but unused"), } } @@ -142,30 +133,91 @@ impl Violation for UnusedImport { module, binding, multiple, - .. + ignore_init_module_imports, + context, } = self; - match self.context { - Some(UnusedImportContext::Init { - first_party: true, - dunder_all_count: 1, - ignore_init_module_imports: true, - }) => Some(format!("Add unused import `{binding}` to __all__")), - - Some(UnusedImportContext::Init { - first_party: true, - dunder_all_count: 0, - ignore_init_module_imports: true, - }) => Some(format!("Use an explicit re-export: `{module} as {module}`")), - - _ => Some(if *multiple { - "Remove unused import".to_string() - } else { - format!("Remove unused import: `{name}`") - }), + if *ignore_init_module_imports { + match context { + UnusedImportContext::DunderInitFirstParty { + dunder_all_count: DunderAllCount::Zero, + submodule_import: false, + } => return Some(format!("Use an explicit re-export: `{module} as {module}`")), + UnusedImportContext::DunderInitFirstParty { + dunder_all_count: DunderAllCount::Zero, + submodule_import: true, + } => { + return Some(format!( + "Use an explicit re-export: `import {parent} as {parent}; import {binding}`", + parent = binding + .split('.') + .next() + .expect("Expected all submodule imports to contain a '.'") + )) + } + UnusedImportContext::DunderInitFirstParty { + dunder_all_count: DunderAllCount::One, + submodule_import: false, + } => return Some(format!("Add unused import `{binding}` to __all__")), + UnusedImportContext::DunderInitFirstParty { + dunder_all_count: DunderAllCount::One, + submodule_import: true, + } => { + return Some(format!( + "Add `{}` to __all__", + binding + .split('.') + .next() + .expect("Expected all submodule imports to contain a '.'") + )) + } + UnusedImportContext::DunderInitFirstParty { + dunder_all_count: DunderAllCount::Many, + submodule_import: _, + } + | UnusedImportContext::ExceptHandler + | UnusedImportContext::Other => {} + } + } + Some(if *multiple { + "Remove unused import".to_string() + } else { + format!("Remove unused import: `{name}`") + }) + } +} + +/// Enumeration providing three possible answers to the question: +/// "How many `__all__` definitions are there in this file?" +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum DunderAllCount { + Zero, + One, + Many, +} + +impl From for DunderAllCount { + fn from(value: usize) -> Self { + match value { + 0 => Self::Zero, + 1 => Self::One, + _ => Self::Many, } } } +#[derive(Debug, Copy, Clone, Eq, PartialEq, is_macro::Is)] +enum UnusedImportContext { + /// The unused import occurs inside an except handler + ExceptHandler, + /// The unused import is a first-party import in an `__init__.py` file + DunderInitFirstParty { + dunder_all_count: DunderAllCount, + submodule_import: bool, + }, + /// The unused import is something else + Other, +} + fn is_first_party(qualified_name: &str, level: u32, checker: &Checker) -> bool { let category = isort::categorize( qualified_name, @@ -304,31 +356,20 @@ pub(crate) fn unused_import(checker: &Checker, scope: &Scope, diagnostics: &mut .into_iter() .map(|binding| { let context = if in_except_handler { - Some(UnusedImportContext::ExceptHandler) - } else if in_init { - Some(UnusedImportContext::Init { - first_party: is_first_party( - &binding.import.qualified_name().to_string(), - level, - checker, - ), - dunder_all_count: dunder_all_exprs.len(), - ignore_init_module_imports: !fix_init, - }) + UnusedImportContext::ExceptHandler + } else if in_init + && is_first_party(&binding.import.qualified_name().to_string(), level, checker) + { + UnusedImportContext::DunderInitFirstParty { + dunder_all_count: DunderAllCount::from(dunder_all_exprs.len()), + submodule_import: binding.import.is_submodule_import(), + } } else { - None + UnusedImportContext::Other }; (binding, context) }) - .partition(|(_, context)| { - matches!( - context, - Some(UnusedImportContext::Init { - first_party: true, - .. - }) - ) && preview_mode - }); + .partition(|(_, context)| context.is_dunder_init_first_party() && preview_mode); // generate fixes that are shared across bindings in the statement let (fix_remove, fix_reexport) = @@ -344,7 +385,7 @@ pub(crate) fn unused_import(checker: &Checker, scope: &Scope, diagnostics: &mut fix_by_reexporting( checker, import_statement, - to_reexport.iter().map(|(b, _)| b).collect::>(), + to_reexport.iter().map(|(b, _)| b), &dunder_all_exprs, ) .ok(), @@ -364,6 +405,7 @@ pub(crate) fn unused_import(checker: &Checker, scope: &Scope, diagnostics: &mut binding: binding.name.to_string(), context, multiple, + ignore_init_module_imports: !fix_init, }, binding.range, ); @@ -387,8 +429,9 @@ pub(crate) fn unused_import(checker: &Checker, scope: &Scope, diagnostics: &mut name: binding.import.qualified_name().to_string(), module: binding.import.member_name().to_string(), binding: binding.name.to_string(), - context: None, + context: UnusedImportContext::Other, multiple: false, + ignore_init_module_imports: !fix_init, }, binding.range, ); @@ -412,6 +455,31 @@ struct ImportBinding<'a> { parent_range: Option, } +impl<'a> ImportBinding<'a> { + /// The symbol that is stored in the outer scope as a result of this import. + /// + /// For example: + /// - `import foo` => `foo` symbol stored in outer scope + /// - `import foo as bar` => `bar` symbol stored in outer scope + /// - `from foo import bar` => `bar` symbol stored in outer scope + /// - `from foo import bar as baz` => `baz` symbol stored in outer scope + /// - `import foo.bar` => `foo` symbol stored in outer scope + fn symbol_stored_in_outer_scope(&self) -> &str { + match &self.import { + AnyImport::FromImport(_) => self.name, + AnyImport::Import(_) => self.name, + AnyImport::SubmoduleImport(SubmoduleImport { qualified_name }) => { + qualified_name.segments().first().unwrap_or_else(|| { + panic!( + "Expected an import binding to have a non-empty qualified name; + got {qualified_name}" + ) + }) + } + } + } +} + impl Ranged for ImportBinding<'_> { fn range(&self) -> TextRange { self.range @@ -461,29 +529,31 @@ fn fix_by_removing_imports<'a>( /// Generate a [`Fix`] to make bindings in a statement explicit, either by adding them to `__all__` /// or changing them from `import a` to `import a as a`. -fn fix_by_reexporting( +fn fix_by_reexporting<'a>( checker: &Checker, node_id: NodeId, - mut imports: Vec<&ImportBinding>, + imports: impl IntoIterator>, dunder_all_exprs: &[&ast::Expr], ) -> Result { let statement = checker.semantic().statement(node_id); - if imports.is_empty() { - bail!("Expected import bindings"); - } - imports.sort_by_key(|b| b.name); + let imports = { + let mut imports: Vec<&str> = imports + .into_iter() + .map(ImportBinding::symbol_stored_in_outer_scope) + .collect(); + if imports.is_empty() { + bail!("Expected import bindings"); + } + imports.sort_unstable(); + imports + }; let edits = match dunder_all_exprs { - [] => fix::edits::make_redundant_alias( - imports.iter().map(|b| b.import.member_name()), - statement, - ), - [dunder_all] => fix::edits::add_to_dunder_all( - imports.iter().map(|b| b.name), - dunder_all, - checker.stylist(), - ), + [] => fix::edits::make_redundant_alias(imports.into_iter(), statement), + [dunder_all] => { + fix::edits::add_to_dunder_all(imports.into_iter(), dunder_all, checker.stylist()) + } _ => bail!("Cannot offer a fix when there are multiple __all__ definitions"), }; diff --git a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_deprecated_option_F401_24____init__.py.snap b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_deprecated_option_F401_24____init__.py.snap index 3f44409c80a34..019ddc0195896 100644 --- a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_deprecated_option_F401_24____init__.py.snap +++ b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_deprecated_option_F401_24____init__.py.snap @@ -1,7 +1,7 @@ --- source: crates/ruff_linter/src/rules/pyflakes/mod.rs --- -__init__.py:19:8: F401 [*] `sys` imported but unused; consider removing, adding to `__all__`, or using a redundant alias +__init__.py:19:8: F401 [*] `sys` imported but unused | 19 | import sys # F401: remove unused | ^^^ F401 diff --git a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_deprecated_option_F401_25__all_nonempty____init__.py.snap b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_deprecated_option_F401_25__all_nonempty____init__.py.snap index f75cb2f1dc9e6..2d1d54e3488e5 100644 --- a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_deprecated_option_F401_25__all_nonempty____init__.py.snap +++ b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_deprecated_option_F401_25__all_nonempty____init__.py.snap @@ -1,7 +1,7 @@ --- source: crates/ruff_linter/src/rules/pyflakes/mod.rs --- -__init__.py:19:8: F401 [*] `sys` imported but unused; consider removing, adding to `__all__`, or using a redundant alias +__init__.py:19:8: F401 [*] `sys` imported but unused | 19 | import sys # F401: remove unused | ^^^ F401 diff --git a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_deprecated_option_F401_30.py.snap b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_deprecated_option_F401_30.py.snap new file mode 100644 index 0000000000000..7b42b5f341833 --- /dev/null +++ b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_deprecated_option_F401_30.py.snap @@ -0,0 +1,17 @@ +--- +source: crates/ruff_linter/src/rules/pyflakes/mod.rs +--- +F401_30.py:6:19: F401 [*] `.main.MaμToMan` imported but unused + | +4 | """ +5 | +6 | from .main import MaµToMan + | ^^^^^^^^ F401 + | + = help: Remove unused import: `.main.MaμToMan` + +ℹ Safe fix +3 3 | even if they have characters in them that undergo NFKC normalization +4 4 | """ +5 5 | +6 |-from .main import MaµToMan diff --git a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_stable_F401_24____init__.py.snap b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_stable_F401_24____init__.py.snap index e1e8ca664f400..02a82d9ec05d2 100644 --- a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_stable_F401_24____init__.py.snap +++ b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_stable_F401_24____init__.py.snap @@ -1,7 +1,7 @@ --- source: crates/ruff_linter/src/rules/pyflakes/mod.rs --- -__init__.py:19:8: F401 `sys` imported but unused; consider removing, adding to `__all__`, or using a redundant alias +__init__.py:19:8: F401 `sys` imported but unused | 19 | import sys # F401: remove unused | ^^^ F401 diff --git a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_stable_F401_25__all_nonempty____init__.py.snap b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_stable_F401_25__all_nonempty____init__.py.snap index faeb9037ef744..16f8364dd65aa 100644 --- a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_stable_F401_25__all_nonempty____init__.py.snap +++ b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F401_stable_F401_25__all_nonempty____init__.py.snap @@ -1,7 +1,7 @@ --- source: crates/ruff_linter/src/rules/pyflakes/mod.rs --- -__init__.py:19:8: F401 `sys` imported but unused; consider removing, adding to `__all__`, or using a redundant alias +__init__.py:19:8: F401 `sys` imported but unused | 19 | import sys # F401: remove unused | ^^^ F401 diff --git a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__f401_preview_first_party_submodule_dunder_all.snap b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__f401_preview_first_party_submodule_dunder_all.snap new file mode 100644 index 0000000000000..0d7b4c45059e9 --- /dev/null +++ b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__f401_preview_first_party_submodule_dunder_all.snap @@ -0,0 +1,18 @@ +--- +source: crates/ruff_linter/src/rules/pyflakes/mod.rs +--- +__init__.py:2:8: F401 [*] `submodule.a` imported but unused; consider removing, adding to `__all__`, or using a redundant alias + | +2 | import submodule.a + | ^^^^^^^^^^^ F401 +3 | __all__ = ['FOO'] +4 | FOO = 42 + | + = help: Add `submodule` to __all__ + +ℹ Safe fix +1 1 | +2 2 | import submodule.a +3 |-__all__ = ['FOO'] + 3 |+__all__ = ['FOO', 'submodule'] +4 4 | FOO = 42 diff --git a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__f401_preview_first_party_submodule_no_dunder_all.snap b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__f401_preview_first_party_submodule_no_dunder_all.snap new file mode 100644 index 0000000000000..07dbda1e721dc --- /dev/null +++ b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__f401_preview_first_party_submodule_no_dunder_all.snap @@ -0,0 +1,9 @@ +--- +source: crates/ruff_linter/src/rules/pyflakes/mod.rs +--- +__init__.py:1:8: F401 `submodule.a` imported but unused; consider removing, adding to `__all__`, or using a redundant alias + | +1 | import submodule.a + | ^^^^^^^^^^^ F401 + | + = help: Use an explicit re-export: `import submodule as submodule; import submodule.a` diff --git a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__init.snap b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__init.snap index 3792cb39ddeaa..cd22f8384785c 100644 --- a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__init.snap +++ b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__init.snap @@ -1,7 +1,7 @@ --- source: crates/ruff_linter/src/rules/pyflakes/mod.rs --- -__init__.py:1:8: F401 `os` imported but unused; consider removing, adding to `__all__`, or using a redundant alias +__init__.py:1:8: F401 `os` imported but unused | 1 | import os | ^^ F401 diff --git a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_24____init__.py.snap b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_24____init__.py.snap index f7db8b02e72f1..1c7ce2e8a7a8b 100644 --- a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_24____init__.py.snap +++ b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_24____init__.py.snap @@ -1,7 +1,7 @@ --- source: crates/ruff_linter/src/rules/pyflakes/mod.rs --- -__init__.py:19:8: F401 [*] `sys` imported but unused; consider removing, adding to `__all__`, or using a redundant alias +__init__.py:19:8: F401 [*] `sys` imported but unused | 19 | import sys # F401: remove unused | ^^^ F401 diff --git a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_25__all_nonempty____init__.py.snap b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_25__all_nonempty____init__.py.snap index 9d04194da7494..cb3e3848d5a93 100644 --- a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_25__all_nonempty____init__.py.snap +++ b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401_F401_25__all_nonempty____init__.py.snap @@ -1,7 +1,7 @@ --- source: crates/ruff_linter/src/rules/pyflakes/mod.rs --- -__init__.py:19:8: F401 [*] `sys` imported but unused; consider removing, adding to `__all__`, or using a redundant alias +__init__.py:19:8: F401 [*] `sys` imported but unused | 19 | import sys # F401: remove unused | ^^^ F401 diff --git a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401___init__.py.snap b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401___init__.py.snap index f141588829c77..3f4855817c4b1 100644 --- a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401___init__.py.snap +++ b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F401___init__.py.snap @@ -1,7 +1,7 @@ --- source: crates/ruff_linter/src/rules/pyflakes/mod.rs --- -__init__.py:1:8: F401 [*] `os` imported but unused; consider removing, adding to `__all__`, or using a redundant alias +__init__.py:1:8: F401 [*] `os` imported but unused | 1 | import os | ^^ F401 diff --git a/crates/ruff_linter/src/rules/pylint/rules/duplicate_bases.rs b/crates/ruff_linter/src/rules/pylint/rules/duplicate_bases.rs index 890ca16cdc53d..5ec9859cf2358 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/duplicate_bases.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/duplicate_bases.rs @@ -64,7 +64,7 @@ pub(crate) fn duplicate_bases(checker: &mut Checker, name: &str, arguments: Opti let bases = &arguments.args; let mut seen: FxHashSet<&str> = FxHashSet::with_capacity_and_hasher(bases.len(), FxBuildHasher); - for base in bases.iter() { + for base in &**bases { if let Expr::Name(ast::ExprName { id, .. }) = base { if !seen.insert(id) { let mut diagnostic = Diagnostic::new( diff --git a/crates/ruff_linter/src/rules/pylint/rules/no_self_use.rs b/crates/ruff_linter/src/rules/pylint/rules/no_self_use.rs index 7dfa73cece80f..e2f14d09bd329 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/no_self_use.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/no_self_use.rs @@ -2,7 +2,6 @@ use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast as ast; use ruff_python_ast::identifier::Identifier; -use ruff_python_ast::name::QualifiedName; use ruff_python_semantic::{ analyze::{function_type, visibility}, Scope, ScopeId, ScopeKind, @@ -49,7 +48,9 @@ pub(crate) fn no_self_use( scope: &Scope, diagnostics: &mut Vec, ) { - let Some(parent) = checker.semantic().first_non_type_parent_scope(scope) else { + let semantic = checker.semantic(); + + let Some(parent) = semantic.first_non_type_parent_scope(scope) else { return; }; @@ -69,7 +70,7 @@ pub(crate) fn no_self_use( name, decorator_list, parent, - checker.semantic(), + semantic, &checker.settings.pep8_naming.classmethod_decorators, &checker.settings.pep8_naming.staticmethod_decorators, ), @@ -78,20 +79,14 @@ pub(crate) fn no_self_use( return; } - let property_decorators = checker - .settings - .pydocstyle - .property_decorators - .iter() - .map(|decorator| QualifiedName::from_dotted_name(decorator)) - .collect::>(); + let extra_property_decorators = checker.settings.pydocstyle.property_decorators(); - if function_type::is_stub(func, checker.semantic()) + if function_type::is_stub(func, semantic) || visibility::is_magic(name) - || visibility::is_abstract(decorator_list, checker.semantic()) - || visibility::is_override(decorator_list, checker.semantic()) - || visibility::is_overload(decorator_list, checker.semantic()) - || visibility::is_property(decorator_list, &property_decorators, checker.semantic()) + || visibility::is_abstract(decorator_list, semantic) + || visibility::is_override(decorator_list, semantic) + || visibility::is_overload(decorator_list, semantic) + || visibility::is_property(decorator_list, extra_property_decorators, semantic) { return; } @@ -113,12 +108,12 @@ pub(crate) fn no_self_use( // If the method contains a `super` reference, then it should be considered to use self // implicitly. - if let Some(binding_id) = checker.semantic().global_scope().get("super") { - let binding = checker.semantic().binding(binding_id); + if let Some(binding_id) = semantic.global_scope().get("super") { + let binding = semantic.binding(binding_id); if binding.kind.is_builtin() { if binding .references() - .any(|id| checker.semantic().reference(id).scope_id() == scope_id) + .any(|id| semantic.reference(id).scope_id() == scope_id) { return; } @@ -127,7 +122,7 @@ pub(crate) fn no_self_use( if scope .get("self") - .map(|binding_id| checker.semantic().binding(binding_id)) + .map(|binding_id| semantic.binding(binding_id)) .is_some_and(|binding| binding.kind.is_argument() && !binding.is_used()) { diagnostics.push(Diagnostic::new( diff --git a/crates/ruff_linter/src/rules/pylint/rules/property_with_parameters.rs b/crates/ruff_linter/src/rules/pylint/rules/property_with_parameters.rs index 835f19c1a38b4..16764ff3f715f 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/property_with_parameters.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/property_with_parameters.rs @@ -1,6 +1,7 @@ use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast::{identifier::Identifier, Decorator, Parameters, Stmt}; +use ruff_python_semantic::analyze::visibility::is_property; use crate::checkers::ast::Checker; @@ -55,10 +56,8 @@ pub(crate) fn property_with_parameters( return; } let semantic = checker.semantic(); - if decorator_list - .iter() - .any(|decorator| semantic.match_builtin_expr(&decorator.expression, "property")) - { + let extra_property_decorators = checker.settings.pydocstyle.property_decorators(); + if is_property(decorator_list, extra_property_decorators, semantic) { checker .diagnostics .push(Diagnostic::new(PropertyWithParameters, stmt.identifier())); diff --git a/crates/ruff_linter/src/rules/pylint/rules/repeated_keyword_argument.rs b/crates/ruff_linter/src/rules/pylint/rules/repeated_keyword_argument.rs index 9ff941fc86e89..3c2b54f74c05d 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/repeated_keyword_argument.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/repeated_keyword_argument.rs @@ -40,7 +40,7 @@ pub(crate) fn repeated_keyword_argument(checker: &mut Checker, call: &ExprCall) let mut seen = FxHashSet::with_capacity_and_hasher(arguments.keywords.len(), FxBuildHasher); - for keyword in arguments.keywords.iter() { + for keyword in &*arguments.keywords { if let Some(id) = &keyword.arg { // Ex) `func(a=1, a=2)` if !seen.insert(id.as_str()) { diff --git a/crates/ruff_linter/src/rules/pylint/rules/unnecessary_list_index_lookup.rs b/crates/ruff_linter/src/rules/pylint/rules/unnecessary_list_index_lookup.rs index 1203378999ffe..21540b1655d81 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/unnecessary_list_index_lookup.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/unnecessary_list_index_lookup.rs @@ -1,7 +1,7 @@ use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast::visitor::Visitor; -use ruff_python_ast::{self as ast, Expr, StmtFor}; +use ruff_python_ast::{self as ast, Expr, Int, Number, StmtFor}; use ruff_python_semantic::SemanticModel; use ruff_text_size::Ranged; @@ -151,6 +151,19 @@ fn enumerate_items<'a>( return None; }; + // If the `enumerate` call has a non-zero `start`, don't omit. + if !arguments.find_argument("start", 1).map_or(true, |expr| { + matches!( + expr, + Expr::NumberLiteral(ast::ExprNumberLiteral { + value: Number::Int(Int::ZERO), + .. + }) + ) + }) { + return None; + } + // Check that the function is the `enumerate` builtin. if !semantic.match_builtin_expr(func, "enumerate") { return None; diff --git a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLR0206_property_with_parameters.py.snap b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLR0206_property_with_parameters.py.snap index 50ffeaf9636b8..ada975adc5a78 100644 --- a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLR0206_property_with_parameters.py.snap +++ b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLR0206_property_with_parameters.py.snap @@ -42,3 +42,37 @@ property_with_parameters.py:39:9: PLR0206 Cannot have defined parameters for pro | ^^^^^^^^^^^^^^^^^^^^ PLR0206 40 | return {key: value * 2 for key, value in kwargs.items()} | + +property_with_parameters.py:48:9: PLR0206 Cannot have defined parameters for properties + | +46 | class Cached: +47 | @cached_property +48 | def cached_prop(self, value): # [property-with-parameters] + | ^^^^^^^^^^^ PLR0206 +49 | ... + | + +property_with_parameters.py:59:9: PLR0206 Cannot have defined parameters for properties + | +57 | class Baz: +58 | @abc.abstractproperty +59 | def prop2(self, param) -> None: # [property-with-parameters] + | ^^^^^ PLR0206 +60 | return None + | + +property_with_parameters.py:63:9: PLR0206 Cannot have defined parameters for properties + | +62 | @types.DynamicClassAttribute +63 | def prop3(self, param) -> None: # [property-with-parameters] + | ^^^^^ PLR0206 +64 | return None + | + +property_with_parameters.py:67:9: PLR0206 Cannot have defined parameters for properties + | +66 | @enum.property +67 | def prop4(self, param) -> None: # [property-with-parameters] + | ^^^^^ PLR0206 +68 | return None + | diff --git a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLR1736_unnecessary_list_index_lookup.py.snap b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLR1736_unnecessary_list_index_lookup.py.snap index a212e600b2cf7..afdb950361e2e 100644 --- a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLR1736_unnecessary_list_index_lookup.py.snap +++ b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLR1736_unnecessary_list_index_lookup.py.snap @@ -181,3 +181,40 @@ unnecessary_list_index_lookup.py:19:16: PLR1736 [*] List index lookup in `enumer 20 20 | 21 21 | 22 22 | def dont_fix_these(): + +unnecessary_list_index_lookup.py:74:15: PLR1736 [*] List index lookup in `enumerate()` loop + | +72 | # PLR1736 +73 | for index, list_item in enumerate(some_list, start=0): +74 | print(some_list[index]) + | ^^^^^^^^^^^^^^^^ PLR1736 +75 | +76 | # PLR1736 + | + = help: Use the loop variable directly + +ℹ Safe fix +71 71 | +72 72 | # PLR1736 +73 73 | for index, list_item in enumerate(some_list, start=0): +74 |- print(some_list[index]) + 74 |+ print(list_item) +75 75 | +76 76 | # PLR1736 +77 77 | for index, list_item in enumerate(some_list): + +unnecessary_list_index_lookup.py:78:15: PLR1736 [*] List index lookup in `enumerate()` loop + | +76 | # PLR1736 +77 | for index, list_item in enumerate(some_list): +78 | print(some_list[index]) + | ^^^^^^^^^^^^^^^^ PLR1736 + | + = help: Use the loop variable directly + +ℹ Safe fix +75 75 | +76 76 | # PLR1736 +77 77 | for index, list_item in enumerate(some_list): +78 |- print(some_list[index]) + 78 |+ print(list_item) diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/f_strings.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/f_strings.rs index f17af71c8f671..c73806d7de677 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/f_strings.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/f_strings.rs @@ -71,7 +71,7 @@ impl<'a> FormatSummaryValues<'a> { let mut extracted_args: Vec<&Expr> = Vec::new(); let mut extracted_kwargs: FxHashMap<&str, &Expr> = FxHashMap::default(); - for arg in call.arguments.args.iter() { + for arg in &*call.arguments.args { if matches!(arg, Expr::Starred(..)) || contains_quotes(locator.slice(arg)) || locator.contains_line_break(arg.range()) @@ -80,7 +80,7 @@ impl<'a> FormatSummaryValues<'a> { } extracted_args.push(arg); } - for keyword in call.arguments.keywords.iter() { + for keyword in &*call.arguments.keywords { let Keyword { arg, value, diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/replace_str_enum.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/replace_str_enum.rs index 5fc112369102a..d7d7724987b37 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/replace_str_enum.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/replace_str_enum.rs @@ -108,7 +108,7 @@ pub(crate) fn replace_str_enum(checker: &mut Checker, class_def: &ast::StmtClass // Determine whether the class inherits from both `str` and `enum.Enum`. let mut inherits_str = false; let mut inherits_enum = false; - for base in arguments.args.iter() { + for base in &*arguments.args { if let Some(qualified_name) = checker.semantic().resolve_qualified_name(base) { match qualified_name.segments() { ["" | "builtins", "str"] => inherits_str = true, diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/useless_object_inheritance.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/useless_object_inheritance.rs index 755e8c468adb7..cbe88ab380d61 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/useless_object_inheritance.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/useless_object_inheritance.rs @@ -50,7 +50,7 @@ pub(crate) fn useless_object_inheritance(checker: &mut Checker, class_def: &ast: return; }; - for base in arguments.args.iter() { + for base in &*arguments.args { if !checker.semantic().match_builtin_expr(base, "object") { continue; } diff --git a/crates/ruff_linter/src/rules/refurb/rules/sorted_min_max.rs b/crates/ruff_linter/src/rules/refurb/rules/sorted_min_max.rs index 6c71d50b7a5c8..34bdc914c1ffd 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/sorted_min_max.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/sorted_min_max.rs @@ -138,7 +138,7 @@ pub(crate) fn sorted_min_max(checker: &mut Checker, subscript: &ast::ExprSubscri let mut key_keyword_expr = None; // Check if the call to `sorted()` has the `reverse` and `key` keywords. - for keyword in arguments.keywords.iter() { + for keyword in &*arguments.keywords { // If the call contains `**kwargs`, return. let Some(arg) = keyword.arg.as_ref() else { return; diff --git a/crates/ruff_linter/src/rules/ruff/rules/missing_fstring_syntax.rs b/crates/ruff_linter/src/rules/ruff/rules/missing_fstring_syntax.rs index 87f0efcf98bfb..388e74cb56cf9 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/missing_fstring_syntax.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/missing_fstring_syntax.rs @@ -174,12 +174,12 @@ fn should_be_fstring( _ => {} } } - for keyword in keywords.iter() { + for keyword in &**keywords { if let Some(ident) = keyword.arg.as_ref() { arg_names.insert(ident.as_str()); } } - for arg in args.iter() { + for arg in &**args { if let ast::Expr::Name(ast::ExprName { id, .. }) = arg { arg_names.insert(id.as_str()); } diff --git a/crates/ruff_linter/src/rules/ruff/rules/unused_async.rs b/crates/ruff_linter/src/rules/ruff/rules/unused_async.rs index dc5ff793100d0..f0bc106b98ebc 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/unused_async.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/unused_async.rs @@ -119,7 +119,7 @@ fn function_def_visit_preorder_except_body<'a, V>( visitor.visit_parameters(parameters); - for expr in returns { + if let Some(expr) = returns { visitor.visit_annotation(expr); } } diff --git a/crates/ruff_python_ast/src/node.rs b/crates/ruff_python_ast/src/node.rs index 34b8406a5ea57..d1ba8de8e0934 100644 --- a/crates/ruff_python_ast/src/node.rs +++ b/crates/ruff_python_ast/src/node.rs @@ -785,7 +785,7 @@ impl AstNode for ast::StmtFunctionDef { visitor.visit_parameters(parameters); - for expr in returns { + if let Some(expr) = returns { visitor.visit_annotation(expr); } diff --git a/crates/ruff_python_ast/src/visitor.rs b/crates/ruff_python_ast/src/visitor.rs index b462125daf0a4..687a4bcf14019 100644 --- a/crates/ruff_python_ast/src/visitor.rs +++ b/crates/ruff_python_ast/src/visitor.rs @@ -143,7 +143,7 @@ pub fn walk_stmt<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, stmt: &'a Stmt) { visitor.visit_type_params(type_params); } visitor.visit_parameters(parameters); - for expr in returns { + if let Some(expr) = returns { visitor.visit_annotation(expr); } visitor.visit_body(body); @@ -593,10 +593,10 @@ pub fn walk_arguments<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, arguments: & // Note that the there might be keywords before the last arg, e.g. in // f(*args, a=2, *args2, **kwargs)`, but we follow Python in evaluating first `args` and then // `keywords`. See also [Arguments::arguments_source_order`]. - for arg in arguments.args.iter() { + for arg in &*arguments.args { visitor.visit_expr(arg); } - for keyword in arguments.keywords.iter() { + for keyword in &*arguments.keywords { visitor.visit_keyword(keyword); } } diff --git a/crates/ruff_python_ast/src/visitor/transformer.rs b/crates/ruff_python_ast/src/visitor/transformer.rs index 9589617ee06c2..bc48c1bd95f70 100644 --- a/crates/ruff_python_ast/src/visitor/transformer.rs +++ b/crates/ruff_python_ast/src/visitor/transformer.rs @@ -130,7 +130,7 @@ pub fn walk_stmt(visitor: &V, stmt: &mut Stmt) { visitor.visit_type_params(type_params); } visitor.visit_parameters(parameters); - for expr in returns { + if let Some(expr) = returns { visitor.visit_annotation(expr); } visitor.visit_body(body); @@ -579,10 +579,10 @@ pub fn walk_arguments(visitor: &V, arguments: &mut Argu // Note that the there might be keywords before the last arg, e.g. in // f(*args, a=2, *args2, **kwargs)`, but we follow Python in evaluating first `args` and then // `keywords`. See also [Arguments::arguments_source_order`]. - for arg in arguments.args.iter_mut() { + for arg in &mut *arguments.args { visitor.visit_expr(arg); } - for keyword in arguments.keywords.iter_mut() { + for keyword in &mut *arguments.keywords { visitor.visit_keyword(keyword); } } diff --git a/crates/ruff_python_formatter/src/expression/binary_like.rs b/crates/ruff_python_formatter/src/expression/binary_like.rs index 1c466701a9f7c..d0113682b4285 100644 --- a/crates/ruff_python_formatter/src/expression/binary_like.rs +++ b/crates/ruff_python_formatter/src/expression/binary_like.rs @@ -573,7 +573,8 @@ impl<'a> FlatBinaryExpressionSlice<'a> { #[allow(unsafe_code)] unsafe { // SAFETY: `BinaryChainSlice` has the same layout as a slice because it uses `repr(transparent)` - &*(slice as *const [OperandOrOperator<'a>] as *const FlatBinaryExpressionSlice<'a>) + &*(std::ptr::from_ref::<[OperandOrOperator<'a>]>(slice) + as *const FlatBinaryExpressionSlice<'a>) } } diff --git a/crates/ruff_python_formatter/src/statement/clause.rs b/crates/ruff_python_formatter/src/statement/clause.rs index f00729fcc8418..6e7e2adf5da2e 100644 --- a/crates/ruff_python_formatter/src/statement/clause.rs +++ b/crates/ruff_python_formatter/src/statement/clause.rs @@ -17,6 +17,7 @@ use crate::{has_skip_comment, prelude::*}; /// > A compound statement consists of one or more ‘clauses.’ A clause consists of a header and a ‘suite.’ /// > The clause headers of a particular compound statement are all at the same indentation level. /// > Each clause header begins with a uniquely identifying keyword and ends with a colon. +/// /// [source](https://docs.python.org/3/reference/compound_stmts.html#compound-statements) #[derive(Copy, Clone)] pub(crate) enum ClauseHeader<'a> { diff --git a/crates/ruff_python_formatter/src/string/docstring.rs b/crates/ruff_python_formatter/src/string/docstring.rs index f6098f31278c4..7c56fe8c4c1ab 100644 --- a/crates/ruff_python_formatter/src/string/docstring.rs +++ b/crates/ruff_python_formatter/src/string/docstring.rs @@ -928,9 +928,9 @@ impl<'src> CodeExampleDoctest<'src> { /// the same with two main differences: /// /// 1. Literal blocks are began with a line that ends with `::`. Code block -/// directives are began with a line like `.. code-block:: python`. +/// directives are began with a line like `.. code-block:: python`. /// 2. Code block directives permit a list of options as a "field list" -/// immediately after the opening line. Literal blocks have no options. +/// immediately after the opening line. Literal blocks have no options. /// /// Otherwise, everything else, including the indentation structure, is the /// same. diff --git a/crates/ruff_python_parser/src/parser/expression.rs b/crates/ruff_python_parser/src/parser/expression.rs index 2b16c2d4c825e..62b42b6c5eefc 100644 --- a/crates/ruff_python_parser/src/parser/expression.rs +++ b/crates/ruff_python_parser/src/parser/expression.rs @@ -2295,7 +2295,7 @@ impl<'src> Parser<'src> { } if arguments.len() > 1 { - for arg in arguments.args.iter() { + for arg in &*arguments.args { if let Some(ast::ExprGenerator { range, parenthesized: false, diff --git a/crates/ruff_python_semantic/src/analyze/class.rs b/crates/ruff_python_semantic/src/analyze/class.rs index 44aa216d07da8..4ea0d3cb08094 100644 --- a/crates/ruff_python_semantic/src/analyze/class.rs +++ b/crates/ruff_python_semantic/src/analyze/class.rs @@ -110,3 +110,13 @@ pub fn is_enumeration(class_def: &ast::StmtClassDef, semantic: &SemanticModel) - ) }) } + +/// Returns `true` if the given class is a metaclass. +pub fn is_metaclass(class_def: &ast::StmtClassDef, semantic: &SemanticModel) -> bool { + any_qualified_name(class_def, semantic, &|qualified_name| { + matches!( + qualified_name.segments(), + ["" | "builtins", "type"] | ["abc", "ABCMeta"] | ["enum", "EnumMeta" | "EnumType"] + ) + }) +} diff --git a/crates/ruff_python_semantic/src/analyze/function_type.rs b/crates/ruff_python_semantic/src/analyze/function_type.rs index a13881df15108..a9ba29c0e5128 100644 --- a/crates/ruff_python_semantic/src/analyze/function_type.rs +++ b/crates/ruff_python_semantic/src/analyze/function_type.rs @@ -3,7 +3,7 @@ use ruff_python_ast::name::{QualifiedName, UnqualifiedName}; use ruff_python_ast::{Decorator, Expr, Stmt, StmtExpr, StmtFunctionDef, StmtRaise}; use crate::model::SemanticModel; -use crate::scope::{Scope, ScopeKind}; +use crate::scope::Scope; #[derive(Debug, Copy, Clone)] pub enum FunctionType { @@ -17,12 +17,12 @@ pub enum FunctionType { pub fn classify( name: &str, decorator_list: &[Decorator], - scope: &Scope, + parent_scope: &Scope, semantic: &SemanticModel, classmethod_decorators: &[String], staticmethod_decorators: &[String], ) -> FunctionType { - let ScopeKind::Class(class_def) = &scope.kind else { + if !parent_scope.kind.is_class() { return FunctionType::Function; }; if decorator_list @@ -30,16 +30,7 @@ pub fn classify( .any(|decorator| is_static_method(decorator, semantic, staticmethod_decorators)) { FunctionType::StaticMethod - } else if matches!(name, "__new__" | "__init_subclass__" | "__class_getitem__") - // Special-case class method, like `__new__`. - || class_def.bases().iter().any(|expr| { - // The class itself extends a known metaclass, so all methods are class methods. - semantic - .resolve_qualified_name(map_callable(expr)) - .is_some_and( |qualified_name| { - matches!(qualified_name.segments(), ["" | "builtins", "type"] | ["abc", "ABCMeta"]) - }) - }) + } else if matches!(name, "__new__" | "__init_subclass__" | "__class_getitem__") // Special-case class method, like `__new__`. || decorator_list.iter().any(|decorator| is_class_method(decorator, semantic, classmethod_decorators)) { FunctionType::ClassMethod diff --git a/crates/ruff_python_semantic/src/analyze/visibility.rs b/crates/ruff_python_semantic/src/analyze/visibility.rs index 3e107e3af3822..e28e13d338f7e 100644 --- a/crates/ruff_python_semantic/src/analyze/visibility.rs +++ b/crates/ruff_python_semantic/src/analyze/visibility.rs @@ -28,23 +28,23 @@ pub fn is_classmethod(decorator_list: &[Decorator], semantic: &SemanticModel) -> /// Returns `true` if a function definition is an `@overload`. pub fn is_overload(decorator_list: &[Decorator], semantic: &SemanticModel) -> bool { - decorator_list.iter().any(|decorator| { - semantic.match_typing_expr(map_callable(&decorator.expression), "overload") - }) + decorator_list + .iter() + .any(|decorator| semantic.match_typing_expr(&decorator.expression, "overload")) } /// Returns `true` if a function definition is an `@override` (PEP 698). pub fn is_override(decorator_list: &[Decorator], semantic: &SemanticModel) -> bool { - decorator_list.iter().any(|decorator| { - semantic.match_typing_expr(map_callable(&decorator.expression), "override") - }) + decorator_list + .iter() + .any(|decorator| semantic.match_typing_expr(&decorator.expression, "override")) } /// Returns `true` if a function definition is an abstract method based on its decorators. pub fn is_abstract(decorator_list: &[Decorator], semantic: &SemanticModel) -> bool { decorator_list.iter().any(|decorator| { semantic - .resolve_qualified_name(map_callable(&decorator.expression)) + .resolve_qualified_name(&decorator.expression) .is_some_and(|qualified_name| { matches!( qualified_name.segments(), @@ -63,21 +63,29 @@ pub fn is_abstract(decorator_list: &[Decorator], semantic: &SemanticModel) -> bo /// Returns `true` if a function definition is a `@property`. /// `extra_properties` can be used to check additional non-standard /// `@property`-like decorators. -pub fn is_property( +pub fn is_property<'a, P, I>( decorator_list: &[Decorator], - extra_properties: &[QualifiedName], + extra_properties: P, semantic: &SemanticModel, -) -> bool { +) -> bool +where + P: IntoIterator, + I: Iterator> + Clone, +{ + let extra_properties = extra_properties.into_iter(); decorator_list.iter().any(|decorator| { semantic .resolve_qualified_name(map_callable(&decorator.expression)) .is_some_and(|qualified_name| { matches!( qualified_name.segments(), - ["" | "builtins", "property"] | ["functools", "cached_property"] + ["" | "builtins" | "enum", "property"] + | ["functools", "cached_property"] + | ["abc", "abstractproperty"] + | ["types", "DynamicClassAttribute"] ) || extra_properties - .iter() - .any(|extra_property| extra_property.segments() == qualified_name.segments()) + .clone() + .any(|extra_property| extra_property == qualified_name) }) }) } @@ -86,7 +94,7 @@ pub fn is_property( pub fn is_final(decorator_list: &[Decorator], semantic: &SemanticModel) -> bool { decorator_list .iter() - .any(|decorator| semantic.match_typing_expr(map_callable(&decorator.expression), "final")) + .any(|decorator| semantic.match_typing_expr(&decorator.expression, "final")) } /// Returns `true` if a function is a "magic method". diff --git a/crates/ruff_python_semantic/src/definition.rs b/crates/ruff_python_semantic/src/definition.rs index 069e3c36ebfd6..3a14a0be4aaf2 100644 --- a/crates/ruff_python_semantic/src/definition.rs +++ b/crates/ruff_python_semantic/src/definition.rs @@ -6,13 +6,16 @@ use std::ops::Deref; use std::path::Path; use ruff_index::{newtype_index, IndexSlice, IndexVec}; -use ruff_python_ast::{self as ast, Stmt}; +use ruff_python_ast::name::QualifiedName; +use ruff_python_ast::{self as ast, Stmt, StmtFunctionDef}; use ruff_text_size::{Ranged, TextRange}; use crate::analyze::visibility::{ - class_visibility, function_visibility, method_visibility, module_visibility, Visibility, + class_visibility, function_visibility, is_property, method_visibility, module_visibility, + Visibility, }; use crate::model::all::DunderAllName; +use crate::SemanticModel; /// Id uniquely identifying a definition in a program. #[newtype_index] @@ -148,6 +151,17 @@ impl<'a> Definition<'a> { ) } + pub fn is_property(&self, extra_properties: P, semantic: &SemanticModel) -> bool + where + P: IntoIterator, + I: Iterator> + Clone, + { + self.as_function_def() + .is_some_and(|StmtFunctionDef { decorator_list, .. }| { + is_property(decorator_list, extra_properties, semantic) + }) + } + /// Return the name of the definition. pub fn name(&self) -> Option<&'a str> { match self { diff --git a/crates/ruff_python_stdlib/src/path.rs b/crates/ruff_python_stdlib/src/path.rs index 5f7d6253d496f..f9998efec3d89 100644 --- a/crates/ruff_python_stdlib/src/path.rs +++ b/crates/ruff_python_stdlib/src/path.rs @@ -16,6 +16,16 @@ pub fn is_jupyter_notebook(path: &Path) -> bool { path.extension().is_some_and(|ext| ext == "ipynb") } +/// Return `true` if a [`Path`] should use the name of its parent directory as its module name. +pub fn is_module_file(path: &Path) -> bool { + path.file_name().is_some_and(|file_name| { + file_name == "__init__.py" + || file_name == "__init__.pyi" + || file_name == "__main__.py" + || file_name == "__main__.pyi" + }) +} + #[cfg(test)] mod tests { use std::path::Path; diff --git a/crates/ruff_server/src/edit/notebook.rs b/crates/ruff_server/src/edit/notebook.rs index 686449d6b54f4..bc14dc785c551 100644 --- a/crates/ruff_server/src/edit/notebook.rs +++ b/crates/ruff_server/src/edit/notebook.rs @@ -114,10 +114,20 @@ impl NotebookDocument { let start = structure.array.start as usize; let delete = structure.array.delete_count as usize; + // This is required because of the way the `NotebookCell` is modelled. We include + // the `TextDocument` within the `NotebookCell` so when it's deleted, the + // corresponding `TextDocument` is removed as well. But, when cells are + // re-oredered, the change request doesn't provide the actual contents of the cell. + // Instead, it only provides that (a) these cell URIs were removed, and (b) these + // cell URIs were added. + // https://github.com/astral-sh/ruff/issues/12573 + let mut deleted_cells = FxHashMap::default(); + // First, delete the cells and remove them from the index. if delete > 0 { for cell in self.cells.drain(start..start + delete) { self.cell_index.remove(&cell.url); + deleted_cells.insert(cell.url, cell.document); } } @@ -125,8 +135,17 @@ impl NotebookDocument { // provide the actual contents of the cells, so we'll initialize them with empty // contents. for cell in structure.array.cells.into_iter().flatten().rev() { - self.cells - .insert(start, NotebookCell::new(cell, String::new(), 0)); + if let Some(text_document) = deleted_cells.remove(&cell.document) { + let version = text_document.version(); + self.cells.push(NotebookCell::new( + cell, + text_document.into_contents(), + version, + )); + } else { + self.cells + .insert(start, NotebookCell::new(cell, String::new(), 0)); + } } // Third, register the new cells in the index and update existing ones that came diff --git a/crates/ruff_server/src/edit/text_document.rs b/crates/ruff_server/src/edit/text_document.rs index 7e1f5b22aae0f..1d5d496b5bb48 100644 --- a/crates/ruff_server/src/edit/text_document.rs +++ b/crates/ruff_server/src/edit/text_document.rs @@ -32,6 +32,10 @@ impl TextDocument { } } + pub fn into_contents(self) -> String { + self.contents + } + pub fn contents(&self) -> &str { &self.contents } diff --git a/crates/ruff_server/src/message.rs b/crates/ruff_server/src/message.rs index 66ad75542ccbb..79d7c63ec347a 100644 --- a/crates/ruff_server/src/message.rs +++ b/crates/ruff_server/src/message.rs @@ -1,6 +1,6 @@ -use std::sync::OnceLock; - +use anyhow::Context; use lsp_types::notification::Notification; +use std::sync::OnceLock; use crate::server::ClientSender; @@ -10,53 +10,31 @@ pub(crate) fn init_messenger(client_sender: ClientSender) { MESSENGER .set(client_sender) .expect("messenger should only be initialized once"); - - // unregister any previously registered panic hook - let _ = std::panic::take_hook(); - - // When we panic, try to notify the client. - std::panic::set_hook(Box::new(move |panic_info| { - if let Some(messenger) = MESSENGER.get() { - let _ = messenger.send(lsp_server::Message::Notification( - lsp_server::Notification { - method: lsp_types::notification::ShowMessage::METHOD.into(), - params: serde_json::to_value(lsp_types::ShowMessageParams { - typ: lsp_types::MessageType::ERROR, - message: String::from( - "The Ruff language server exited with a panic. See the logs for more details." - ), - }) - .unwrap_or_default(), - }, - )); - } - - let backtrace = std::backtrace::Backtrace::force_capture(); - tracing::error!("{panic_info}\n{backtrace}"); - #[allow(clippy::print_stderr)] - { - // we also need to print to stderr directly in case tracing hasn't - // been initialized. - eprintln!("{panic_info}\n{backtrace}"); - } - })); } pub(crate) fn show_message(message: String, message_type: lsp_types::MessageType) { + try_show_message(message, message_type).unwrap(); +} + +pub(super) fn try_show_message( + message: String, + message_type: lsp_types::MessageType, +) -> crate::Result<()> { MESSENGER .get() - .expect("messenger should be initialized") + .ok_or_else(|| anyhow::anyhow!("messenger not initialized"))? .send(lsp_server::Message::Notification( lsp_server::Notification { method: lsp_types::notification::ShowMessage::METHOD.into(), params: serde_json::to_value(lsp_types::ShowMessageParams { typ: message_type, message, - }) - .unwrap(), + })?, }, )) - .expect("message should send"); + .context("Failed to send message")?; + + Ok(()) } /// Sends an error to the client with a formatted message. The error is sent in a diff --git a/crates/ruff_server/src/server.rs b/crates/ruff_server/src/server.rs index 03f52175a7332..015ba9de3eddc 100644 --- a/crates/ruff_server/src/server.rs +++ b/crates/ruff_server/src/server.rs @@ -1,10 +1,10 @@ //! Scheduling, I/O, and API endpoints. -use std::num::NonZeroUsize; -use std::str::FromStr; - use lsp_server as lsp; use lsp_types as types; +use std::num::NonZeroUsize; +use std::panic::PanicInfo; +use std::str::FromStr; use types::ClientCapabilities; use types::CodeActionKind; use types::CodeActionOptions; @@ -36,6 +36,7 @@ mod client; mod connection; mod schedule; +use crate::message::try_show_message; pub(crate) use connection::ClientSender; pub(crate) type Result = std::result::Result; @@ -90,6 +91,7 @@ impl Server { .log_level .unwrap_or(crate::trace::LogLevel::Info), global_settings.tracing.log_file.as_deref(), + init_params.client_info.as_ref(), ); let mut workspace_for_url = |url: lsp_types::Url| { @@ -132,6 +134,46 @@ impl Server { } pub fn run(self) -> crate::Result<()> { + type PanicHook = Box) + 'static + Sync + Send>; + struct RestorePanicHook { + hook: Option, + } + + impl Drop for RestorePanicHook { + fn drop(&mut self) { + if let Some(hook) = self.hook.take() { + std::panic::set_hook(hook); + } + } + } + + // unregister any previously registered panic hook + // The hook will be restored when this function exits. + let _ = RestorePanicHook { + hook: Some(std::panic::take_hook()), + }; + + // When we panic, try to notify the client. + std::panic::set_hook(Box::new(move |panic_info| { + use std::io::Write; + + let backtrace = std::backtrace::Backtrace::force_capture(); + tracing::error!("{panic_info}\n{backtrace}"); + + // we also need to print to stderr directly for when using `$logTrace` because + // the message won't be sent to the client. + // But don't use `eprintln` because `eprintln` itself may panic if the pipe is broken. + let mut stderr = std::io::stderr().lock(); + writeln!(stderr, "{panic_info}\n{backtrace}").ok(); + + try_show_message( + "The Ruff language server exited with a panic. See the logs for more details." + .to_string(), + lsp_types::MessageType::ERROR, + ) + .ok(); + })); + event_loop_thread(move || { Self::event_loop( &self.connection, diff --git a/crates/ruff_server/src/trace.rs b/crates/ruff_server/src/trace.rs index eeac188377838..7bd27747ef3fb 100644 --- a/crates/ruff_server/src/trace.rs +++ b/crates/ruff_server/src/trace.rs @@ -14,16 +14,22 @@ //! //! Tracing will write to `stderr` by default, which should appear in the logs for most LSP clients. //! A `logFile` path can also be specified in the settings, and output will be directed there instead. -use lsp_types::TraceValue; +use core::str; +use lsp_server::{Message, Notification}; +use lsp_types::{ + notification::{LogTrace, Notification as _}, + ClientInfo, TraceValue, +}; use serde::Deserialize; use std::{ + io::{Error as IoError, ErrorKind, Write}, path::PathBuf, str::FromStr, sync::{Arc, Mutex, OnceLock}, }; use tracing::level_filters::LevelFilter; use tracing_subscriber::{ - fmt::{time::Uptime, writer::BoxMakeWriter}, + fmt::{time::Uptime, writer::BoxMakeWriter, MakeWriter}, layer::SubscriberExt, Layer, }; @@ -43,10 +49,43 @@ pub(crate) fn set_trace_value(trace_value: TraceValue) { *global_trace_value = trace_value; } +// A tracing writer that uses LSPs logTrace method. +struct TraceLogWriter; + +impl Write for TraceLogWriter { + fn write(&mut self, buf: &[u8]) -> std::io::Result { + let message = str::from_utf8(buf).map_err(|e| IoError::new(ErrorKind::InvalidData, e))?; + LOGGING_SENDER + .get() + .expect("logging sender should be initialized at this point") + .send(Message::Notification(Notification { + method: LogTrace::METHOD.to_owned(), + params: serde_json::json!({ + "message": message + }), + })) + .map_err(|e| IoError::new(ErrorKind::Other, e))?; + Ok(buf.len()) + } + + fn flush(&mut self) -> std::io::Result<()> { + Ok(()) + } +} + +impl<'a> MakeWriter<'a> for TraceLogWriter { + type Writer = Self; + + fn make_writer(&'a self) -> Self::Writer { + Self + } +} + pub(crate) fn init_tracing( sender: ClientSender, log_level: LogLevel, log_file: Option<&std::path::Path>, + client: Option<&ClientInfo>, ) { LOGGING_SENDER .set(sender) @@ -82,15 +121,24 @@ pub(crate) fn init_tracing( .ok() }); + let logger = match log_file { + Some(file) => BoxMakeWriter::new(Arc::new(file)), + None => { + if client.is_some_and(|client| { + client.name.starts_with("Zed") || client.name.starts_with("Visual Studio Code") + }) { + BoxMakeWriter::new(TraceLogWriter) + } else { + BoxMakeWriter::new(std::io::stderr) + } + } + }; let subscriber = tracing_subscriber::Registry::default().with( tracing_subscriber::fmt::layer() .with_timer(Uptime::default()) .with_thread_names(true) .with_ansi(false) - .with_writer(match log_file { - Some(file) => BoxMakeWriter::new(Arc::new(file)), - None => BoxMakeWriter::new(std::io::stderr), - }) + .with_writer(logger) .with_filter(TraceLevelFilter) .with_filter(LogLevelFilter { filter: log_level }), ); diff --git a/crates/ruff_workspace/src/options.rs b/crates/ruff_workspace/src/options.rs index 740abf63e9c15..30ba34612b545 100644 --- a/crates/ruff_workspace/src/options.rs +++ b/crates/ruff_workspace/src/options.rs @@ -1,5 +1,3 @@ -use std::collections::BTreeSet; - use regex::Regex; use rustc_hash::{FxBuildHasher, FxHashMap, FxHashSet}; use serde::{Deserialize, Serialize}; @@ -1104,12 +1102,20 @@ pub struct Flake8BuiltinsOptions { )] /// Ignore list of builtins. pub builtins_ignorelist: Option>, + #[option( + default = r#"[]"#, + value_type = "list[str]", + example = "builtins-allowed-modules = [\"id\"]" + )] + /// List of builtin module names to allow. + pub builtins_allowed_modules: Option>, } impl Flake8BuiltinsOptions { pub fn into_settings(self) -> ruff_linter::rules::flake8_builtins::settings::Settings { ruff_linter::rules::flake8_builtins::settings::Settings { builtins_ignorelist: self.builtins_ignorelist.unwrap_or_default(), + builtins_allowed_modules: self.builtins_allowed_modules.unwrap_or_default(), } } } @@ -2754,11 +2760,16 @@ pub struct PydocstyleOptions { impl PydocstyleOptions { pub fn into_settings(self) -> pydocstyle::settings::Settings { - pydocstyle::settings::Settings { - convention: self.convention, - ignore_decorators: BTreeSet::from_iter(self.ignore_decorators.unwrap_or_default()), - property_decorators: BTreeSet::from_iter(self.property_decorators.unwrap_or_default()), - } + let PydocstyleOptions { + convention, + ignore_decorators, + property_decorators, + } = self; + pydocstyle::settings::Settings::new( + convention, + ignore_decorators.unwrap_or_default(), + property_decorators.unwrap_or_default(), + ) } } diff --git a/docs/editors/settings.md b/docs/editors/settings.md index bf3543c57dd97..9d200c9c9450e 100644 --- a/docs/editors/settings.md +++ b/docs/editors/settings.md @@ -713,7 +713,7 @@ automatically decide between the two based on the Ruff version and extension set 1. If the Ruff version is \< `0.5.3`, use [`ruff-lsp`](https://github.com/astral-sh/ruff-lsp). A warning will be displayed if settings specific to the native server are detected. - `true`: Same as `on` -- `false: Same as`off\` +- `false`: Same as `off` **Default value**: `"auto"` diff --git a/docs/editors/setup.md b/docs/editors/setup.md index 6048b688441be..f554dcb1e05f3 100644 --- a/docs/editors/setup.md +++ b/docs/editors/setup.md @@ -90,15 +90,15 @@ require('lspconfig').pyright.setup { ``` By default, Ruff will not show any logs. To enable logging in Neovim, you'll need to set the -`RUFF_TRACE` environment variable to either `messages` or `verbose`, and use the +[`trace`](https://neovim.io/doc/user/lsp.html#vim.lsp.ClientConfig) setting to either `messages` or `verbose`, and use the [`logLevel`](./settings.md#loglevel) setting to change the log level: ```lua require('lspconfig').ruff.setup { - cmd_env = { RUFF_TRACE = "messages" } + trace = 'messages', init_options = { settings = { - logLevel = "debug", + logLevel = 'debug', } } } diff --git a/playground/api/package-lock.json b/playground/api/package-lock.json index 71bf6a69e77be..6f842054b73b4 100644 --- a/playground/api/package-lock.json +++ b/playground/api/package-lock.json @@ -16,7 +16,7 @@ "@cloudflare/workers-types": "^4.20230801.0", "miniflare": "^3.20230801.1", "typescript": "^5.1.6", - "wrangler": "3.65.1" + "wrangler": "3.67.1" } }, "node_modules/@cloudflare/kv-asset-handler": { @@ -118,9 +118,9 @@ } }, "node_modules/@cloudflare/workers-types": { - "version": "4.20240718.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workers-types/-/workers-types-4.20240718.0.tgz", - "integrity": "sha512-7RqxXIM9HyhjfZ9ztXjITuc7mL0w4s+zXgypqKmMuvuObC3DgXutJ3bOYbQ+Ss5QbywrzWSNMlmGdL/ldg/yZg==", + "version": "4.20240725.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workers-types/-/workers-types-4.20240725.0.tgz", + "integrity": "sha512-L6T/Bg50zm9IIACQVQ0CdVcQL+2nLkRXdPz6BsXF3SlzgjyWR5ndVctAbfr/HLV7aKYxWnnEZsIORsTWb+FssA==", "dev": true, "license": "MIT OR Apache-2.0" }, @@ -1105,9 +1105,9 @@ } }, "node_modules/miniflare": { - "version": "3.20240718.0", - "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-3.20240718.0.tgz", - "integrity": "sha512-TKgSeyqPBeT8TBLxbDJOKPWlq/wydoJRHjAyDdgxbw59N6wbP8JucK6AU1vXCfu21eKhrEin77ssXOpbfekzPA==", + "version": "3.20240718.1", + "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-3.20240718.1.tgz", + "integrity": "sha512-mn3MjGnpgYvarCRTfz4TQyVyY8yW0zz7f8LOAPVai78IGC/lcVcyskZcuIr7Zovb2i+IERmmsJAiEPeZHIIKbA==", "dev": true, "license": "MIT", "dependencies": { @@ -1484,9 +1484,9 @@ "dev": true }, "node_modules/typescript": { - "version": "5.5.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.3.tgz", - "integrity": "sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ==", + "version": "5.5.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz", + "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==", "dev": true, "license": "Apache-2.0", "bin": { @@ -1593,9 +1593,9 @@ } }, "node_modules/wrangler": { - "version": "3.65.1", - "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-3.65.1.tgz", - "integrity": "sha512-Z5NyrbpGMQCpim/6VnI1im0/Weh5+CU1sdep1JbfFxHjn/Jt9K+MeUq+kCns5ubkkdRx2EYsusB/JKyX2JdJ4w==", + "version": "3.67.1", + "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-3.67.1.tgz", + "integrity": "sha512-lLVJxq/OZMfntvZ79WQJNC1OKfxOCs6PLfogqDBuPFEQ3L/Mwqvd9IZ0bB8ahrwUN/K3lSdDPXynk9HfcGZxVw==", "dev": true, "license": "MIT OR Apache-2.0", "dependencies": { @@ -1606,7 +1606,7 @@ "chokidar": "^3.5.3", "date-fns": "^3.6.0", "esbuild": "0.17.19", - "miniflare": "3.20240718.0", + "miniflare": "3.20240718.1", "nanoid": "^3.3.3", "path-to-regexp": "^6.2.0", "resolve": "^1.22.8", @@ -1614,6 +1614,7 @@ "selfsigned": "^2.0.1", "source-map": "^0.6.1", "unenv": "npm:unenv-nightly@1.10.0-1717606461.a117952", + "workerd": "1.20240718.0", "xxhash-wasm": "^1.0.1" }, "bin": { diff --git a/playground/api/package.json b/playground/api/package.json index 9f1bd64ff4921..7f00c47b87dbf 100644 --- a/playground/api/package.json +++ b/playground/api/package.json @@ -5,7 +5,7 @@ "@cloudflare/workers-types": "^4.20230801.0", "miniflare": "^3.20230801.1", "typescript": "^5.1.6", - "wrangler": "3.65.1" + "wrangler": "3.67.1" }, "private": true, "scripts": { diff --git a/playground/package-lock.json b/playground/package-lock.json index 3777624608f3d..da53c0f3c93bd 100644 --- a/playground/package-lock.json +++ b/playground/package-lock.json @@ -1096,17 +1096,17 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "7.16.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.16.1.tgz", - "integrity": "sha512-SxdPak/5bO0EnGktV05+Hq8oatjAYVY3Zh2bye9pGZy6+jwyR3LG3YKkV4YatlsgqXP28BTeVm9pqwJM96vf2A==", + "version": "7.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.17.0.tgz", + "integrity": "sha512-pyiDhEuLM3PuANxH7uNYan1AaFs5XE0zw1hq69JBvGvE7gSuEoQl1ydtEe/XQeoC3GQxLXyOVa5kNOATgM638A==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "7.16.1", - "@typescript-eslint/type-utils": "7.16.1", - "@typescript-eslint/utils": "7.16.1", - "@typescript-eslint/visitor-keys": "7.16.1", + "@typescript-eslint/scope-manager": "7.17.0", + "@typescript-eslint/type-utils": "7.17.0", + "@typescript-eslint/utils": "7.17.0", + "@typescript-eslint/visitor-keys": "7.17.0", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", @@ -1130,16 +1130,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "7.16.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.16.1.tgz", - "integrity": "sha512-u+1Qx86jfGQ5i4JjK33/FnawZRpsLxRnKzGE6EABZ40KxVT/vWsiZFEBBHjFOljmmV3MBYOHEKi0Jm9hbAOClA==", + "version": "7.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.17.0.tgz", + "integrity": "sha512-puiYfGeg5Ydop8eusb/Hy1k7QmOU6X3nvsqCgzrB2K4qMavK//21+PzNE8qeECgNOIoertJPUC1SpegHDI515A==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/scope-manager": "7.16.1", - "@typescript-eslint/types": "7.16.1", - "@typescript-eslint/typescript-estree": "7.16.1", - "@typescript-eslint/visitor-keys": "7.16.1", + "@typescript-eslint/scope-manager": "7.17.0", + "@typescript-eslint/types": "7.17.0", + "@typescript-eslint/typescript-estree": "7.17.0", + "@typescript-eslint/visitor-keys": "7.17.0", "debug": "^4.3.4" }, "engines": { @@ -1159,14 +1159,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "7.16.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.16.1.tgz", - "integrity": "sha512-nYpyv6ALte18gbMz323RM+vpFpTjfNdyakbf3nsLvF43uF9KeNC289SUEW3QLZ1xPtyINJ1dIsZOuWuSRIWygw==", + "version": "7.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.17.0.tgz", + "integrity": "sha512-0P2jTTqyxWp9HiKLu/Vemr2Rg1Xb5B7uHItdVZ6iAenXmPo4SZ86yOPCJwMqpCyaMiEHTNqizHfsbmCFT1x9SA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "7.16.1", - "@typescript-eslint/visitor-keys": "7.16.1" + "@typescript-eslint/types": "7.17.0", + "@typescript-eslint/visitor-keys": "7.17.0" }, "engines": { "node": "^18.18.0 || >=20.0.0" @@ -1177,14 +1177,14 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "7.16.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.16.1.tgz", - "integrity": "sha512-rbu/H2MWXN4SkjIIyWcmYBjlp55VT+1G3duFOIukTNFxr9PI35pLc2ydwAfejCEitCv4uztA07q0QWanOHC7dA==", + "version": "7.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.17.0.tgz", + "integrity": "sha512-XD3aaBt+orgkM/7Cei0XNEm1vwUxQ958AOLALzPlbPqb8C1G8PZK85tND7Jpe69Wualri81PLU+Zc48GVKIMMA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "7.16.1", - "@typescript-eslint/utils": "7.16.1", + "@typescript-eslint/typescript-estree": "7.17.0", + "@typescript-eslint/utils": "7.17.0", "debug": "^4.3.4", "ts-api-utils": "^1.3.0" }, @@ -1205,9 +1205,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "7.16.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.16.1.tgz", - "integrity": "sha512-AQn9XqCzUXd4bAVEsAXM/Izk11Wx2u4H3BAfQVhSfzfDOm/wAON9nP7J5rpkCxts7E5TELmN845xTUCQrD1xIQ==", + "version": "7.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.17.0.tgz", + "integrity": "sha512-a29Ir0EbyKTKHnZWbNsrc/gqfIBqYPwj3F2M+jWE/9bqfEHg0AMtXzkbUkOG6QgEScxh2+Pz9OXe11jHDnHR7A==", "dev": true, "license": "MIT", "engines": { @@ -1219,14 +1219,14 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "7.16.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.16.1.tgz", - "integrity": "sha512-0vFPk8tMjj6apaAZ1HlwM8w7jbghC8jc1aRNJG5vN8Ym5miyhTQGMqU++kuBFDNKe9NcPeZ6x0zfSzV8xC1UlQ==", + "version": "7.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.17.0.tgz", + "integrity": "sha512-72I3TGq93t2GoSBWI093wmKo0n6/b7O4j9o8U+f65TVD0FS6bI2180X5eGEr8MA8PhKMvYe9myZJquUT2JkCZw==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/types": "7.16.1", - "@typescript-eslint/visitor-keys": "7.16.1", + "@typescript-eslint/types": "7.17.0", + "@typescript-eslint/visitor-keys": "7.17.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -1274,16 +1274,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "7.16.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.16.1.tgz", - "integrity": "sha512-WrFM8nzCowV0he0RlkotGDujx78xudsxnGMBHI88l5J8wEhED6yBwaSLP99ygfrzAjsQvcYQ94quDwI0d7E1fA==", + "version": "7.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.17.0.tgz", + "integrity": "sha512-r+JFlm5NdB+JXc7aWWZ3fKSm1gn0pkswEwIYsrGPdsT2GjsRATAKXiNtp3vgAAO1xZhX8alIOEQnNMl3kbTgJw==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "7.16.1", - "@typescript-eslint/types": "7.16.1", - "@typescript-eslint/typescript-estree": "7.16.1" + "@typescript-eslint/scope-manager": "7.17.0", + "@typescript-eslint/types": "7.17.0", + "@typescript-eslint/typescript-estree": "7.17.0" }, "engines": { "node": "^18.18.0 || >=20.0.0" @@ -1297,13 +1297,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "7.16.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.16.1.tgz", - "integrity": "sha512-Qlzzx4sE4u3FsHTPQAAQFJFNOuqtuY0LFrZHwQ8IHK705XxBiWOFkfKRWu6niB7hwfgnwIpO4jTC75ozW1PHWg==", + "version": "7.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.17.0.tgz", + "integrity": "sha512-RVGC9UhPOCsfCdI9pU++K4nD7to+jTcMIbXTSOcrLqUEW6gF2pU1UUbYJKc9cvcRSK1UDeMJ7pdMxf4bhMpV/A==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "7.16.1", + "@typescript-eslint/types": "7.17.0", "eslint-visitor-keys": "^3.4.3" }, "engines": { @@ -4056,9 +4056,9 @@ } }, "node_modules/postcss": { - "version": "8.4.39", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.39.tgz", - "integrity": "sha512-0vzE+lAiG7hZl1/9I8yzKLx3aR9Xbof3fBHKunvMfOCYAtMhrsnccJY2iTURb9EZd5+pLuiNV9/c/GZJOHsgIw==", + "version": "8.4.40", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.40.tgz", + "integrity": "sha512-YF2kKIUzAofPMpfH6hOi2cGnv/HrUlfucspc7pDyvv7kGdqXrfj8SCl/t8owkEgKEuu8ZcRjSOxFxVLqwChZ2Q==", "dev": true, "funding": [ { @@ -4823,9 +4823,9 @@ } }, "node_modules/tailwindcss": { - "version": "3.4.6", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.6.tgz", - "integrity": "sha512-1uRHzPB+Vzu57ocybfZ4jh5Q3SdlH7XW23J5sQoM9LhE9eIOlzxer/3XPSsycvih3rboRsvt0QCmzSrqyOYUIA==", + "version": "3.4.7", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.7.tgz", + "integrity": "sha512-rxWZbe87YJb4OcSopb7up2Ba4U82BoiSGUdoDr3Ydrg9ckxFS/YWsvhN323GMcddgU65QRy7JndC7ahhInhvlQ==", "dev": true, "license": "MIT", "dependencies": { @@ -5033,9 +5033,9 @@ } }, "node_modules/typescript": { - "version": "5.5.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.3.tgz", - "integrity": "sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ==", + "version": "5.5.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz", + "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==", "dev": true, "license": "Apache-2.0", "bin": { @@ -5107,9 +5107,9 @@ "dev": true }, "node_modules/vite": { - "version": "5.3.4", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.3.4.tgz", - "integrity": "sha512-Cw+7zL3ZG9/NZBB8C+8QbQZmR54GwqIz+WMI4b3JgdYJvX+ny9AjJXqkGQlDXSXRP9rP0B4tbciRMOVEKulVOA==", + "version": "5.3.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.3.5.tgz", + "integrity": "sha512-MdjglKR6AQXQb9JGiS7Rc2wC6uMjcm7Go/NHNO63EwiJXfuk9PgqiP/n5IDJCziMkfw9n4Ubp7lttNwz+8ZVKA==", "dev": true, "license": "MIT", "dependencies": { diff --git a/python/ruff-ecosystem/ruff_ecosystem/projects.py b/python/ruff-ecosystem/ruff_ecosystem/projects.py index c477588ef5970..996a6adb9f527 100644 --- a/python/ruff-ecosystem/ruff_ecosystem/projects.py +++ b/python/ruff-ecosystem/ruff_ecosystem/projects.py @@ -221,7 +221,7 @@ def to_ruff_args(self) -> list[str]: if self.exclude: args.extend(["--exclude", self.exclude]) if self.show_fixes: - args.extend(["--show-fixes", "--ecosystem-ci"]) + args.extend(["--show-fixes"]) return args diff --git a/ruff.schema.json b/ruff.schema.json index b00f5c6fbe9fd..35f109c649708 100644 --- a/ruff.schema.json +++ b/ruff.schema.json @@ -933,6 +933,16 @@ "Flake8BuiltinsOptions": { "type": "object", "properties": { + "builtins-allowed-modules": { + "description": "List of builtin module names to allow.", + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, "builtins-ignorelist": { "description": "Ignore list of builtins.", "type": [ @@ -2669,6 +2679,9 @@ "A001", "A002", "A003", + "A004", + "A005", + "A006", "AIR", "AIR0", "AIR00", diff --git a/rust-toolchain.toml b/rust-toolchain.toml index c6e4d7d5031fa..8cca5be0594d4 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,2 +1,2 @@ [toolchain] -channel = "1.79" +channel = "1.80" diff --git a/scripts/check_ecosystem.py b/scripts/check_ecosystem.py index 7e9502b0e956a..e9b38d3d4e136 100755 --- a/scripts/check_ecosystem.py +++ b/scripts/check_ecosystem.py @@ -191,7 +191,7 @@ async def check( if exclude: ruff_args.extend(["--exclude", exclude]) if show_fixes: - ruff_args.extend(["--show-fixes", "--ecosystem-ci"]) + ruff_args.extend(["--show-fixes"]) start = time.time() proc = await create_subprocess_exec(