mirror of
https://github.com/astral-sh/ruff.git
synced 2026-10-28 10:45:37 +08:00
Merge main and reconcile type relation imports
This commit is contained in:
commit
8f94a11b76
117 files changed
+1099
-427
No files matched your search
@@ -26,6 +26,6 @@ body:
|
||||
attributes:
|
||||
label: Version
|
||||
description: What version of ruff are you using? (see `ruff version`)
|
||||
placeholder: e.g., ruff 0.17.2
|
||||
placeholder: e.g., ruff 0.17.3
|
||||
validations:
|
||||
required: false
|
||||
@@ -13,6 +13,6 @@ body:
|
||||
attributes:
|
||||
label: Version
|
||||
description: What version of ruff are you using? (see `ruff version`)
|
||||
placeholder: e.g., ruff 0.17.2
|
||||
placeholder: e.g., ruff 0.17.3
|
||||
validations:
|
||||
required: false
|
||||
@@ -1,5 +1,33 @@
|
||||
# Changelog
|
||||
|
||||
## 0.17.3
|
||||
|
||||
Released on 2026-10-26.
|
||||
|
||||
### Preview features
|
||||
|
||||
- \[`suspicious`\] Add `shared-clock-mock` ([#29074](https://github.com/astral-sh/ruff/pull/29074))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- \[`pyupgrade`\] Avoid incorrect conversions to f-strings (`UP032`) ([#25485](https://github.com/astral-sh/ruff/pull/25485))
|
||||
|
||||
### Performance
|
||||
|
||||
- Buffer stdout in `ruff format` ([#29631](https://github.com/astral-sh/ruff/pull/29631))
|
||||
- Specialize ASCII identifier lexing ([#29503](https://github.com/astral-sh/ruff/pull/29503))
|
||||
- Speed up identifier and keyword lexing ([#29463](https://github.com/astral-sh/ruff/pull/29463))
|
||||
|
||||
### Documentation
|
||||
|
||||
- \[`flake8-bandit`\] Describe what the rule actually checks for `httpx` (`S113`) ([#27936](https://github.com/astral-sh/ruff/pull/27936))
|
||||
|
||||
### Contributors
|
||||
|
||||
- [@itsvedantkumar](https://github.com/itsvedantkumar)
|
||||
- [@charliermarsh](https://github.com/charliermarsh)
|
||||
- [@anishgirianish](https://github.com/anishgirianish)
|
||||
|
||||
## 0.17.2
|
||||
|
||||
Released on 2026-10-22.
|
||||
|
||||
Generated
+41
-41
@@ -3098,7 +3098,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.17.2"
|
||||
version = "0.17.3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"assert_fs",
|
||||
@@ -3162,7 +3162,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_annotate_snippets"
|
||||
version = "0.0.19"
|
||||
version = "0.0.20"
|
||||
dependencies = [
|
||||
"anstream 1.0.0",
|
||||
"anstyle",
|
||||
@@ -3206,7 +3206,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_cache"
|
||||
version = "0.0.19"
|
||||
version = "0.0.20"
|
||||
dependencies = [
|
||||
"char_str",
|
||||
"filetime",
|
||||
@@ -3220,14 +3220,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_command_line"
|
||||
version = "0.0.19"
|
||||
version = "0.0.20"
|
||||
dependencies = [
|
||||
"argfile",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ruff_db"
|
||||
version = "0.0.19"
|
||||
version = "0.0.20"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"arc-swap",
|
||||
@@ -3319,7 +3319,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_diagnostics"
|
||||
version = "0.0.19"
|
||||
version = "0.0.20"
|
||||
dependencies = [
|
||||
"get-size2",
|
||||
"is-macro",
|
||||
@@ -3329,7 +3329,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_formatter"
|
||||
version = "0.0.19"
|
||||
version = "0.0.20"
|
||||
dependencies = [
|
||||
"drop_bomb",
|
||||
"ruff_cache",
|
||||
@@ -3345,7 +3345,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_graph"
|
||||
version = "0.0.19"
|
||||
version = "0.0.20"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
@@ -3366,7 +3366,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_index"
|
||||
version = "0.0.19"
|
||||
version = "0.0.20"
|
||||
dependencies = [
|
||||
"get-size2",
|
||||
"ruff_macros",
|
||||
@@ -3376,7 +3376,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_linter"
|
||||
version = "0.17.2"
|
||||
version = "0.17.3"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"anyhow",
|
||||
@@ -3438,7 +3438,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_macros"
|
||||
version = "0.0.19"
|
||||
version = "0.0.20"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"itertools 0.15.0",
|
||||
@@ -3451,7 +3451,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_markdown"
|
||||
version = "0.0.19"
|
||||
version = "0.0.20"
|
||||
dependencies = [
|
||||
"insta",
|
||||
"regex",
|
||||
@@ -3482,14 +3482,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_memory_usage"
|
||||
version = "0.0.19"
|
||||
version = "0.0.20"
|
||||
dependencies = [
|
||||
"get-size2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ruff_notebook"
|
||||
version = "0.0.19"
|
||||
version = "0.0.20"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"rand 0.10.3",
|
||||
@@ -3505,14 +3505,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_options_metadata"
|
||||
version = "0.0.19"
|
||||
version = "0.0.20"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ruff_python_ast"
|
||||
version = "0.0.19"
|
||||
version = "0.0.20"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"arrayvec",
|
||||
@@ -3549,7 +3549,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_python_codegen"
|
||||
version = "0.0.19"
|
||||
version = "0.0.20"
|
||||
dependencies = [
|
||||
"ruff_python_ast",
|
||||
"ruff_python_literal",
|
||||
@@ -3561,7 +3561,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_python_edits"
|
||||
version = "0.0.19"
|
||||
version = "0.0.20"
|
||||
dependencies = [
|
||||
"ruff_python_ast",
|
||||
"ruff_python_parser",
|
||||
@@ -3571,7 +3571,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_python_formatter"
|
||||
version = "0.0.19"
|
||||
version = "0.0.20"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
@@ -3604,7 +3604,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_python_importer"
|
||||
version = "0.0.19"
|
||||
version = "0.0.20"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"insta",
|
||||
@@ -3619,7 +3619,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_python_index"
|
||||
version = "0.0.19"
|
||||
version = "0.0.20"
|
||||
dependencies = [
|
||||
"ruff_python_ast",
|
||||
"ruff_python_parser",
|
||||
@@ -3630,7 +3630,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_python_literal"
|
||||
version = "0.0.19"
|
||||
version = "0.0.20"
|
||||
dependencies = [
|
||||
"bitflags 2.13.2",
|
||||
"icu_normalizer",
|
||||
@@ -3641,7 +3641,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_python_parser"
|
||||
version = "0.0.19"
|
||||
version = "0.0.20"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bitflags 2.13.2",
|
||||
@@ -3672,7 +3672,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_python_semantic"
|
||||
version = "0.0.19"
|
||||
version = "0.0.20"
|
||||
dependencies = [
|
||||
"bitflags 2.13.2",
|
||||
"insta",
|
||||
@@ -3693,7 +3693,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_python_stdlib"
|
||||
version = "0.0.19"
|
||||
version = "0.0.20"
|
||||
dependencies = [
|
||||
"bitflags 2.13.2",
|
||||
"unicode-ident",
|
||||
@@ -3701,7 +3701,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_python_trivia"
|
||||
version = "0.0.19"
|
||||
version = "0.0.20"
|
||||
dependencies = [
|
||||
"itertools 0.15.0",
|
||||
"memchr",
|
||||
@@ -3724,7 +3724,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_ranged_value"
|
||||
version = "0.0.19"
|
||||
version = "0.0.20"
|
||||
dependencies = [
|
||||
"get-size2",
|
||||
"ruff_db",
|
||||
@@ -3737,7 +3737,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_server"
|
||||
version = "0.0.19"
|
||||
version = "0.0.20"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"crossbeam",
|
||||
@@ -3781,7 +3781,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_source_file"
|
||||
version = "0.0.19"
|
||||
version = "0.0.20"
|
||||
dependencies = [
|
||||
"get-size2",
|
||||
"memchr",
|
||||
@@ -3791,7 +3791,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_text_size"
|
||||
version = "0.0.19"
|
||||
version = "0.0.20"
|
||||
dependencies = [
|
||||
"get-size2",
|
||||
"schemars",
|
||||
@@ -3802,7 +3802,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_unicode_names2"
|
||||
version = "0.0.19"
|
||||
version = "0.0.20"
|
||||
dependencies = [
|
||||
"phf 0.14.0",
|
||||
"rand 0.10.3",
|
||||
@@ -3811,7 +3811,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_unicode_names2_generator"
|
||||
version = "0.0.19"
|
||||
version = "0.0.20"
|
||||
dependencies = [
|
||||
"phf_codegen 0.14.0",
|
||||
"rand 0.10.3",
|
||||
@@ -3819,7 +3819,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_wasm"
|
||||
version = "0.17.2"
|
||||
version = "0.17.3"
|
||||
dependencies = [
|
||||
"console_error_panic_hook",
|
||||
"console_log",
|
||||
@@ -3846,7 +3846,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_workspace"
|
||||
version = "0.0.19"
|
||||
version = "0.0.20"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"colored",
|
||||
@@ -4729,7 +4729,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ty_combine"
|
||||
version = "0.0.19"
|
||||
version = "0.0.20"
|
||||
dependencies = [
|
||||
"ordermap",
|
||||
"ruff_db",
|
||||
@@ -4810,7 +4810,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ty_module_resolver"
|
||||
version = "0.0.19"
|
||||
version = "0.0.20"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"camino",
|
||||
@@ -4891,7 +4891,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ty_python_core"
|
||||
version = "0.0.19"
|
||||
version = "0.0.20"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bitflags 2.13.2",
|
||||
@@ -4926,7 +4926,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ty_python_semantic"
|
||||
version = "0.0.19"
|
||||
version = "0.0.20"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bitflags 2.13.2",
|
||||
@@ -5027,7 +5027,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ty_site_packages"
|
||||
version = "0.0.19"
|
||||
version = "0.0.20"
|
||||
dependencies = [
|
||||
"camino",
|
||||
"colored",
|
||||
@@ -5048,7 +5048,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ty_static"
|
||||
version = "0.0.19"
|
||||
version = "0.0.20"
|
||||
dependencies = [
|
||||
"ruff_macros",
|
||||
]
|
||||
@@ -5081,7 +5081,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ty_vendored"
|
||||
version = "0.0.19"
|
||||
version = "0.0.20"
|
||||
dependencies = [
|
||||
"path-slash",
|
||||
"ruff_db",
|
||||
|
||||
+40
-40
@@ -14,55 +14,55 @@ license = "MIT"
|
||||
|
||||
[workspace.dependencies]
|
||||
char_str = { version = "0.0.4", default-features = false, features = ["std"] }
|
||||
ruff = { version = "0.17.2", path = "crates/ruff" }
|
||||
ruff_annotate_snippets = { version = "0.0.19", path = "crates/ruff_annotate_snippets" }
|
||||
ruff_cache = { version = "0.0.19", path = "crates/ruff_cache" }
|
||||
ruff_command_line = { version = "0.0.19", path = "crates/ruff_command_line" }
|
||||
ruff_db = { version = "0.0.19", path = "crates/ruff_db", default-features = false }
|
||||
ruff_diagnostics = { version = "0.0.19", path = "crates/ruff_diagnostics" }
|
||||
ruff_formatter = { version = "0.0.19", path = "crates/ruff_formatter" }
|
||||
ruff_graph = { version = "0.0.19", path = "crates/ruff_graph" }
|
||||
ruff_index = { version = "0.0.19", path = "crates/ruff_index" }
|
||||
ruff_linter = { version = "0.17.2", path = "crates/ruff_linter" }
|
||||
ruff_macros = { version = "0.0.19", path = "crates/ruff_macros" }
|
||||
ruff_markdown = { version = "0.0.19", path = "crates/ruff_markdown" }
|
||||
ruff_memory_usage = { version = "0.0.19", path = "crates/ruff_memory_usage" }
|
||||
ruff_notebook = { version = "0.0.19", path = "crates/ruff_notebook" }
|
||||
ruff_options_metadata = { version = "0.0.19", path = "crates/ruff_options_metadata" }
|
||||
ruff_python_ast = { version = "0.0.19", path = "crates/ruff_python_ast" }
|
||||
ruff_python_codegen = { version = "0.0.19", path = "crates/ruff_python_codegen" }
|
||||
ruff_python_edits = { version = "0.0.19", path = "crates/ruff_python_edits" }
|
||||
ruff_python_formatter = { version = "0.0.19", path = "crates/ruff_python_formatter" }
|
||||
ruff_python_importer = { version = "0.0.19", path = "crates/ruff_python_importer" }
|
||||
ruff_python_index = { version = "0.0.19", path = "crates/ruff_python_index" }
|
||||
ruff_python_literal = { version = "0.0.19", path = "crates/ruff_python_literal" }
|
||||
ruff_python_parser = { version = "0.0.19", path = "crates/ruff_python_parser" }
|
||||
ruff_python_semantic = { version = "0.0.19", path = "crates/ruff_python_semantic" }
|
||||
ruff_python_stdlib = { version = "0.0.19", path = "crates/ruff_python_stdlib" }
|
||||
ruff_python_trivia = { version = "0.0.19", path = "crates/ruff_python_trivia" }
|
||||
ruff_server = { version = "0.0.19", path = "crates/ruff_server" }
|
||||
ruff_source_file = { version = "0.0.19", path = "crates/ruff_source_file" }
|
||||
ruff = { version = "0.17.3", path = "crates/ruff" }
|
||||
ruff_annotate_snippets = { version = "0.0.20", path = "crates/ruff_annotate_snippets" }
|
||||
ruff_cache = { version = "0.0.20", path = "crates/ruff_cache" }
|
||||
ruff_command_line = { version = "0.0.20", path = "crates/ruff_command_line" }
|
||||
ruff_db = { version = "0.0.20", path = "crates/ruff_db", default-features = false }
|
||||
ruff_diagnostics = { version = "0.0.20", path = "crates/ruff_diagnostics" }
|
||||
ruff_formatter = { version = "0.0.20", path = "crates/ruff_formatter" }
|
||||
ruff_graph = { version = "0.0.20", path = "crates/ruff_graph" }
|
||||
ruff_index = { version = "0.0.20", path = "crates/ruff_index" }
|
||||
ruff_linter = { version = "0.17.3", path = "crates/ruff_linter" }
|
||||
ruff_macros = { version = "0.0.20", path = "crates/ruff_macros" }
|
||||
ruff_markdown = { version = "0.0.20", path = "crates/ruff_markdown" }
|
||||
ruff_memory_usage = { version = "0.0.20", path = "crates/ruff_memory_usage" }
|
||||
ruff_notebook = { version = "0.0.20", path = "crates/ruff_notebook" }
|
||||
ruff_options_metadata = { version = "0.0.20", path = "crates/ruff_options_metadata" }
|
||||
ruff_python_ast = { version = "0.0.20", path = "crates/ruff_python_ast" }
|
||||
ruff_python_codegen = { version = "0.0.20", path = "crates/ruff_python_codegen" }
|
||||
ruff_python_edits = { version = "0.0.20", path = "crates/ruff_python_edits" }
|
||||
ruff_python_formatter = { version = "0.0.20", path = "crates/ruff_python_formatter" }
|
||||
ruff_python_importer = { version = "0.0.20", path = "crates/ruff_python_importer" }
|
||||
ruff_python_index = { version = "0.0.20", path = "crates/ruff_python_index" }
|
||||
ruff_python_literal = { version = "0.0.20", path = "crates/ruff_python_literal" }
|
||||
ruff_python_parser = { version = "0.0.20", path = "crates/ruff_python_parser" }
|
||||
ruff_python_semantic = { version = "0.0.20", path = "crates/ruff_python_semantic" }
|
||||
ruff_python_stdlib = { version = "0.0.20", path = "crates/ruff_python_stdlib" }
|
||||
ruff_python_trivia = { version = "0.0.20", path = "crates/ruff_python_trivia" }
|
||||
ruff_server = { version = "0.0.20", path = "crates/ruff_server" }
|
||||
ruff_source_file = { version = "0.0.20", path = "crates/ruff_source_file" }
|
||||
ruff_mdtest = { path = "crates/ruff_mdtest" }
|
||||
ruff_ranged_value = { version = "0.0.19", path = "crates/ruff_ranged_value" }
|
||||
ruff_text_size = { version = "0.0.19", path = "crates/ruff_text_size" }
|
||||
ruff_unicode_names2 = { version = "0.0.19", path = "crates/ruff_unicode_names2" }
|
||||
ruff_unicode_names2_generator = { version = "0.0.19", path = "crates/ruff_unicode_names2_generator" }
|
||||
ruff_workspace = { version = "0.0.19", path = "crates/ruff_workspace" }
|
||||
ruff_ranged_value = { version = "0.0.20", path = "crates/ruff_ranged_value" }
|
||||
ruff_text_size = { version = "0.0.20", path = "crates/ruff_text_size" }
|
||||
ruff_unicode_names2 = { version = "0.0.20", path = "crates/ruff_unicode_names2" }
|
||||
ruff_unicode_names2_generator = { version = "0.0.20", path = "crates/ruff_unicode_names2_generator" }
|
||||
ruff_workspace = { version = "0.0.20", path = "crates/ruff_workspace" }
|
||||
|
||||
ty = { path = "crates/ty" }
|
||||
ty_combine = { version = "0.0.19", path = "crates/ty_combine" }
|
||||
ty_combine = { version = "0.0.20", path = "crates/ty_combine" }
|
||||
ty_completion_bench = { path = "crates/ty_completion_bench" }
|
||||
ty_completion_eval = { path = "crates/ty_completion_eval" }
|
||||
ty_ide = { path = "crates/ty_ide" }
|
||||
ty_module_resolver = { version = "0.0.19", path = "crates/ty_module_resolver" }
|
||||
ty_module_resolver = { version = "0.0.20", path = "crates/ty_module_resolver" }
|
||||
ty_project = { path = "crates/ty_project", default-features = false }
|
||||
ty_python_semantic = { version = "0.0.19", path = "crates/ty_python_semantic" }
|
||||
ty_python_core = { version = "0.0.19", path = "crates/ty_python_core" }
|
||||
ty_python_semantic = { version = "0.0.20", path = "crates/ty_python_semantic" }
|
||||
ty_python_core = { version = "0.0.20", path = "crates/ty_python_core" }
|
||||
ty_server = { path = "crates/ty_server" }
|
||||
ty_site_packages = { version = "0.0.19", path = "crates/ty_site_packages" }
|
||||
ty_static = { version = "0.0.19", path = "crates/ty_static" }
|
||||
ty_site_packages = { version = "0.0.20", path = "crates/ty_site_packages" }
|
||||
ty_static = { version = "0.0.20", path = "crates/ty_static" }
|
||||
ty_test = { path = "crates/ty_test" }
|
||||
ty_vendored = { version = "0.0.19", path = "crates/ty_vendored" }
|
||||
ty_vendored = { version = "0.0.20", path = "crates/ty_vendored" }
|
||||
|
||||
mdtest = { path = "crates/mdtest" }
|
||||
|
||||
|
||||
@@ -122,8 +122,8 @@ Ruff is available as [`ruff`](https://pypi.org/project/ruff/) on PyPI.
|
||||
Invoke Ruff directly with [`uvx`](https://docs.astral.sh/uv/):
|
||||
|
||||
```shell
|
||||
uvx ruff@0.17.2 check # Lint all files in the current directory.
|
||||
uvx ruff@0.17.2 format # Format all files in the current directory.
|
||||
uvx ruff@0.17.3 check # Lint all files in the current directory.
|
||||
uvx ruff@0.17.3 format # Format all files in the current directory.
|
||||
```
|
||||
|
||||
Or install Ruff with `uv` (recommended), `pip`, or `pipx`:
|
||||
@@ -150,8 +150,8 @@ curl -LsSf https://astral.sh/ruff/install.sh | sh
|
||||
powershell -c "irm https://astral.sh/ruff/install.ps1 | iex"
|
||||
|
||||
# For a specific version.
|
||||
curl -LsSf https://astral.sh/ruff/0.17.2/install.sh | sh
|
||||
powershell -c "irm https://astral.sh/ruff/0.17.2/install.ps1 | iex"
|
||||
curl -LsSf https://astral.sh/ruff/0.17.3/install.sh | sh
|
||||
powershell -c "irm https://astral.sh/ruff/0.17.3/install.ps1 | iex"
|
||||
```
|
||||
|
||||
You can also install Ruff via [Homebrew](https://formulae.brew.sh/formula/ruff), [Conda](https://anaconda.org/conda-forge/ruff),
|
||||
@@ -184,7 +184,7 @@ Ruff can also be used as a [pre-commit](https://pre-commit.com/) hook via [`ruff
|
||||
```yaml
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: v0.17.2
|
||||
rev: v0.17.3
|
||||
hooks:
|
||||
# Run the linter.
|
||||
- id: ruff-check
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff"
|
||||
version = "0.17.2"
|
||||
version = "0.17.3"
|
||||
description = "An extremely fast Python linter and code formatter"
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
@@ -10,7 +10,7 @@ See the [documentation](https://docs.astral.sh/ruff/) or
|
||||
This crate is the entry point to the Ruff command-line interface. The Rust API exposed here is not
|
||||
considered public interface.
|
||||
|
||||
This is version 0.17.2. The source can be found [here](https://github.com/astral-sh/ruff/blob/0.17.2/crates/ruff).
|
||||
This is version 0.17.3. The source can be found [here](https://github.com/astral-sh/ruff/blob/0.17.3/crates/ruff).
|
||||
|
||||
The following Ruff workspace members are also available:
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_annotate_snippets"
|
||||
version = "0.0.19"
|
||||
version = "0.0.20"
|
||||
description = "This is an internal component crate of Ruff"
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_cache"
|
||||
version = "0.0.19"
|
||||
version = "0.0.20"
|
||||
description = "This is an internal component crate of Ruff"
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
This crate is an internal component of [Ruff](https://crates.io/crates/ruff). The Rust API exposed
|
||||
here is unstable and will have frequent breaking changes.
|
||||
|
||||
This version (0.0.19) is a component of [Ruff 0.17.2](https://crates.io/crates/ruff/0.17.2). The
|
||||
source can be found [here](https://github.com/astral-sh/ruff/blob/0.17.2/crates/ruff_cache).
|
||||
This version (0.0.20) is a component of [Ruff 0.17.3](https://crates.io/crates/ruff/0.17.3). The
|
||||
source can be found [here](https://github.com/astral-sh/ruff/blob/0.17.3/crates/ruff_cache).
|
||||
|
||||
See Ruff's [crate versioning policy](https://docs.astral.sh/ruff/versioning/#crate-versioning) for
|
||||
details on versioning.
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_command_line"
|
||||
version = "0.0.19"
|
||||
version = "0.0.20"
|
||||
description = "This is an internal component crate of Ruff"
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
This crate is an internal component of [Ruff](https://crates.io/crates/ruff). The Rust API exposed
|
||||
here is unstable and will have frequent breaking changes.
|
||||
|
||||
This version (0.0.19) is a component of [Ruff 0.17.2](https://crates.io/crates/ruff/0.17.2). The
|
||||
source can be found [here](https://github.com/astral-sh/ruff/blob/0.17.2/crates/ruff_command_line).
|
||||
This version (0.0.20) is a component of [Ruff 0.17.3](https://crates.io/crates/ruff/0.17.3). The
|
||||
source can be found [here](https://github.com/astral-sh/ruff/blob/0.17.3/crates/ruff_command_line).
|
||||
|
||||
See Ruff's [crate versioning policy](https://docs.astral.sh/ruff/versioning/#crate-versioning) for
|
||||
details on versioning.
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_db"
|
||||
version = "0.0.19"
|
||||
version = "0.0.20"
|
||||
description = "This is an internal component crate of Ruff"
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
This crate is an internal component of [Ruff](https://crates.io/crates/ruff). The Rust API exposed
|
||||
here is unstable and will have frequent breaking changes.
|
||||
|
||||
This version (0.0.19) is a component of [Ruff 0.17.2](https://crates.io/crates/ruff/0.17.2). The
|
||||
source can be found [here](https://github.com/astral-sh/ruff/blob/0.17.2/crates/ruff_db).
|
||||
This version (0.0.20) is a component of [Ruff 0.17.3](https://crates.io/crates/ruff/0.17.3). The
|
||||
source can be found [here](https://github.com/astral-sh/ruff/blob/0.17.3/crates/ruff_db).
|
||||
|
||||
See Ruff's [crate versioning policy](https://docs.astral.sh/ruff/versioning/#crate-versioning) for
|
||||
details on versioning.
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_diagnostics"
|
||||
version = "0.0.19"
|
||||
version = "0.0.20"
|
||||
description = "This is an internal component crate of Ruff"
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
This crate is an internal component of [Ruff](https://crates.io/crates/ruff). The Rust API exposed
|
||||
here is unstable and will have frequent breaking changes.
|
||||
|
||||
This version (0.0.19) is a component of [Ruff 0.17.2](https://crates.io/crates/ruff/0.17.2). The
|
||||
source can be found [here](https://github.com/astral-sh/ruff/blob/0.17.2/crates/ruff_diagnostics).
|
||||
This version (0.0.20) is a component of [Ruff 0.17.3](https://crates.io/crates/ruff/0.17.3). The
|
||||
source can be found [here](https://github.com/astral-sh/ruff/blob/0.17.3/crates/ruff_diagnostics).
|
||||
|
||||
See Ruff's [crate versioning policy](https://docs.astral.sh/ruff/versioning/#crate-versioning) for
|
||||
details on versioning.
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_formatter"
|
||||
version = "0.0.19"
|
||||
version = "0.0.20"
|
||||
description = "This is an internal component crate of Ruff"
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
This crate is an internal component of [Ruff](https://crates.io/crates/ruff). The Rust API exposed
|
||||
here is unstable and will have frequent breaking changes.
|
||||
|
||||
This version (0.0.19) is a component of [Ruff 0.17.2](https://crates.io/crates/ruff/0.17.2). The
|
||||
source can be found [here](https://github.com/astral-sh/ruff/blob/0.17.2/crates/ruff_formatter).
|
||||
This version (0.0.20) is a component of [Ruff 0.17.3](https://crates.io/crates/ruff/0.17.3). The
|
||||
source can be found [here](https://github.com/astral-sh/ruff/blob/0.17.3/crates/ruff_formatter).
|
||||
|
||||
See Ruff's [crate versioning policy](https://docs.astral.sh/ruff/versioning/#crate-versioning) for
|
||||
details on versioning.
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_graph"
|
||||
version = "0.0.19"
|
||||
version = "0.0.20"
|
||||
description = "This is an internal component crate of Ruff"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
This crate is an internal component of [Ruff](https://crates.io/crates/ruff). The Rust API exposed
|
||||
here is unstable and will have frequent breaking changes.
|
||||
|
||||
This version (0.0.19) is a component of [Ruff 0.17.2](https://crates.io/crates/ruff/0.17.2). The
|
||||
source can be found [here](https://github.com/astral-sh/ruff/blob/0.17.2/crates/ruff_graph).
|
||||
This version (0.0.20) is a component of [Ruff 0.17.3](https://crates.io/crates/ruff/0.17.3). The
|
||||
source can be found [here](https://github.com/astral-sh/ruff/blob/0.17.3/crates/ruff_graph).
|
||||
|
||||
See Ruff's [crate versioning policy](https://docs.astral.sh/ruff/versioning/#crate-versioning) for
|
||||
details on versioning.
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_index"
|
||||
version = "0.0.19"
|
||||
version = "0.0.20"
|
||||
description = "This is an internal component crate of Ruff"
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
This crate is an internal component of [Ruff](https://crates.io/crates/ruff). The Rust API exposed
|
||||
here is unstable and will have frequent breaking changes.
|
||||
|
||||
This version (0.0.19) is a component of [Ruff 0.17.2](https://crates.io/crates/ruff/0.17.2). The
|
||||
source can be found [here](https://github.com/astral-sh/ruff/blob/0.17.2/crates/ruff_index).
|
||||
This version (0.0.20) is a component of [Ruff 0.17.3](https://crates.io/crates/ruff/0.17.3). The
|
||||
source can be found [here](https://github.com/astral-sh/ruff/blob/0.17.3/crates/ruff_index).
|
||||
|
||||
See Ruff's [crate versioning policy](https://docs.astral.sh/ruff/versioning/#crate-versioning) for
|
||||
details on versioning.
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_linter"
|
||||
version = "0.17.2"
|
||||
version = "0.17.3"
|
||||
description = "This is an internal component crate of Ruff"
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
This crate is an internal component of [Ruff](https://crates.io/crates/ruff). The Rust API exposed
|
||||
here is unstable and will have frequent breaking changes.
|
||||
|
||||
This version (0.17.2) is a component of [Ruff 0.17.2](https://crates.io/crates/ruff/0.17.2). The
|
||||
source can be found [here](https://github.com/astral-sh/ruff/blob/0.17.2/crates/ruff_linter).
|
||||
This version (0.17.3) is a component of [Ruff 0.17.3](https://crates.io/crates/ruff/0.17.3). The
|
||||
source can be found [here](https://github.com/astral-sh/ruff/blob/0.17.3/crates/ruff_linter).
|
||||
|
||||
See Ruff's [crate versioning policy](https://docs.astral.sh/ruff/versioning/#crate-versioning) for
|
||||
details on versioning.
|
||||
@@ -30,6 +30,19 @@ use crate::package::PackageRoot;
|
||||
/// `__init__.py` file is typically meant to be a regular package, and
|
||||
/// the absence of the `__init__.py` file is probably an oversight.
|
||||
///
|
||||
/// ## Preview
|
||||
///
|
||||
/// In preview, the rule also checks for packages without `__init__.py` files between two regular
|
||||
/// packages, such as `bar` in the project structure below.
|
||||
///
|
||||
/// ```text
|
||||
/// foo/
|
||||
/// ├── __init__.py
|
||||
/// └── bar/
|
||||
/// └── baz/
|
||||
/// └── __init__.py
|
||||
/// ```
|
||||
///
|
||||
/// ## Options
|
||||
/// - `namespace-packages`
|
||||
#[derive(ViolationMetadata)]
|
||||
|
||||
@@ -155,89 +155,59 @@ pub(crate) fn repeated_keys(checker: &Checker, dict: &ast::ExprDict) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Generate a map from key to (index, value).
|
||||
let mut seen: FxHashMap<HashableExpr, (&Expr, FxHashSet<ComparableExpr>)> =
|
||||
FxHashMap::with_capacity_and_hasher(dict.len(), FxBuildHasher);
|
||||
let mut seen_keys = FxHashMap::with_capacity_and_hasher(dict.len(), FxBuildHasher);
|
||||
let mut seen_values = FxHashMap::default();
|
||||
|
||||
// Detect duplicate keys.
|
||||
for (i, ast::DictItem { key, value }) in dict.iter().enumerate() {
|
||||
let Some(key) = key else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let comparable_key = HashableExpr::from(key);
|
||||
let comparable_value = ComparableExpr::from(value);
|
||||
|
||||
match seen.entry(comparable_key) {
|
||||
let (first_index, first_key) = match seen_keys.entry(HashableExpr::from(key)) {
|
||||
Entry::Vacant(entry) => {
|
||||
entry.insert((key, FxHashSet::from_iter([comparable_value])));
|
||||
entry.insert((i, key));
|
||||
continue;
|
||||
}
|
||||
Entry::Occupied(mut entry) => {
|
||||
let (seen_key, seen_values) = entry.get_mut();
|
||||
match key {
|
||||
key if is_literal_key(key)
|
||||
&& checker.is_rule_enabled(Rule::MultiValueRepeatedKeyLiteral) =>
|
||||
{
|
||||
let mut diagnostic = checker.report_diagnostic(
|
||||
MultiValueRepeatedKeyLiteral {
|
||||
name: SourceCodeSnippet::from_str(checker.locator().slice(key)),
|
||||
existing: SourceCodeSnippet::from_str(
|
||||
checker.locator().slice(*seen_key),
|
||||
),
|
||||
},
|
||||
key.range(),
|
||||
);
|
||||
if !seen_values.insert(comparable_value) {
|
||||
diagnostic.set_fix(Fix::unsafe_edit(Edit::deletion(
|
||||
parenthesized_range(
|
||||
dict.value(i - 1).into(),
|
||||
dict.into(),
|
||||
checker.tokens(),
|
||||
)
|
||||
.unwrap_or_else(|| dict.value(i - 1).range())
|
||||
.end(),
|
||||
parenthesized_range(
|
||||
dict.value(i).into(),
|
||||
dict.into(),
|
||||
checker.tokens(),
|
||||
)
|
||||
.unwrap_or_else(|| dict.value(i).range())
|
||||
.end(),
|
||||
)));
|
||||
}
|
||||
}
|
||||
Expr::Name(_)
|
||||
if checker.is_rule_enabled(Rule::MultiValueRepeatedKeyVariable) =>
|
||||
{
|
||||
let mut diagnostic = checker.report_diagnostic(
|
||||
MultiValueRepeatedKeyVariable {
|
||||
name: SourceCodeSnippet::from_str(checker.locator().slice(key)),
|
||||
},
|
||||
key.range(),
|
||||
);
|
||||
let comparable_value: ComparableExpr = dict.value(i).into();
|
||||
if !seen_values.insert(comparable_value) {
|
||||
diagnostic.set_fix(Fix::unsafe_edit(Edit::deletion(
|
||||
parenthesized_range(
|
||||
dict.value(i - 1).into(),
|
||||
dict.into(),
|
||||
checker.tokens(),
|
||||
)
|
||||
.unwrap_or_else(|| dict.value(i - 1).range())
|
||||
.end(),
|
||||
parenthesized_range(
|
||||
dict.value(i).into(),
|
||||
dict.into(),
|
||||
checker.tokens(),
|
||||
)
|
||||
.unwrap_or_else(|| dict.value(i).range())
|
||||
.end(),
|
||||
)));
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
Entry::Occupied(entry) => *entry.get(),
|
||||
};
|
||||
|
||||
let mut diagnostic = match key {
|
||||
key if is_literal_key(key)
|
||||
&& checker.is_rule_enabled(Rule::MultiValueRepeatedKeyLiteral) =>
|
||||
{
|
||||
checker.report_diagnostic(
|
||||
MultiValueRepeatedKeyLiteral {
|
||||
name: SourceCodeSnippet::from_str(checker.locator().slice(key)),
|
||||
existing: SourceCodeSnippet::from_str(checker.locator().slice(first_key)),
|
||||
},
|
||||
key.range(),
|
||||
)
|
||||
}
|
||||
Expr::Name(_) if checker.is_rule_enabled(Rule::MultiValueRepeatedKeyVariable) => {
|
||||
checker.report_diagnostic(
|
||||
MultiValueRepeatedKeyVariable {
|
||||
name: SourceCodeSnippet::from_str(checker.locator().slice(key)),
|
||||
},
|
||||
key.range(),
|
||||
)
|
||||
}
|
||||
_ => continue,
|
||||
};
|
||||
|
||||
// Comparing values is only necessary to decide whether a repeated key can be fixed.
|
||||
let values = seen_values.entry(first_index).or_insert_with(|| {
|
||||
FxHashSet::from_iter([ComparableExpr::from(dict.value(first_index))])
|
||||
});
|
||||
if !values.insert(ComparableExpr::from(value)) {
|
||||
let previous = dict.value(i - 1);
|
||||
diagnostic.set_fix(Fix::unsafe_edit(Edit::deletion(
|
||||
parenthesized_range(previous.into(), dict.into(), checker.tokens())
|
||||
.unwrap_or_else(|| previous.range())
|
||||
.end(),
|
||||
parenthesized_range(value.into(), dict.into(), checker.tokens())
|
||||
.unwrap_or_else(|| value.range())
|
||||
.end(),
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ use crate::codes::Category;
|
||||
/// - [Python documentation: Where to patch](https://docs.python.org/3/library/unittest.mock.html#where-to-patch)
|
||||
/// - [pytest documentation: Monkeypatching](https://docs.pytest.org/en/stable/how-to/monkeypatch.html)
|
||||
#[derive(ViolationMetadata)]
|
||||
#[violation_metadata(preview_since = "NEXT_RUFF_VERSION", category = Category::Suspicious)]
|
||||
#[violation_metadata(preview_since = "0.17.3", category = Category::Suspicious)]
|
||||
pub(crate) struct SharedClockMock {
|
||||
function: String,
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_macros"
|
||||
version = "0.0.19"
|
||||
version = "0.0.20"
|
||||
description = "This is an internal component crate of Ruff"
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
This crate is an internal component of [Ruff](https://crates.io/crates/ruff). The Rust API exposed
|
||||
here is unstable and will have frequent breaking changes.
|
||||
|
||||
This version (0.0.19) is a component of [Ruff 0.17.2](https://crates.io/crates/ruff/0.17.2). The
|
||||
source can be found [here](https://github.com/astral-sh/ruff/blob/0.17.2/crates/ruff_macros).
|
||||
This version (0.0.20) is a component of [Ruff 0.17.3](https://crates.io/crates/ruff/0.17.3). The
|
||||
source can be found [here](https://github.com/astral-sh/ruff/blob/0.17.3/crates/ruff_macros).
|
||||
|
||||
See Ruff's [crate versioning policy](https://docs.astral.sh/ruff/versioning/#crate-versioning) for
|
||||
details on versioning.
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_markdown"
|
||||
version = "0.0.19"
|
||||
version = "0.0.20"
|
||||
description = "This is an internal component crate of Ruff"
|
||||
edition = { workspace = true }
|
||||
rust-version = { workspace = true }
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
This crate is an internal component of [Ruff](https://crates.io/crates/ruff). The Rust API exposed
|
||||
here is unstable and will have frequent breaking changes.
|
||||
|
||||
This version (0.0.19) is a component of [Ruff 0.17.2](https://crates.io/crates/ruff/0.17.2). The
|
||||
source can be found [here](https://github.com/astral-sh/ruff/blob/0.17.2/crates/ruff_markdown).
|
||||
This version (0.0.20) is a component of [Ruff 0.17.3](https://crates.io/crates/ruff/0.17.3). The
|
||||
source can be found [here](https://github.com/astral-sh/ruff/blob/0.17.3/crates/ruff_markdown).
|
||||
|
||||
See Ruff's [crate versioning policy](https://docs.astral.sh/ruff/versioning/#crate-versioning) for
|
||||
details on versioning.
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_memory_usage"
|
||||
version = "0.0.19"
|
||||
version = "0.0.20"
|
||||
description = "This is an internal component crate of Ruff"
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
This crate is an internal component of [Ruff](https://crates.io/crates/ruff). The Rust API exposed
|
||||
here is unstable and will have frequent breaking changes.
|
||||
|
||||
This version (0.0.19) is a component of [Ruff 0.17.2](https://crates.io/crates/ruff/0.17.2). The
|
||||
source can be found [here](https://github.com/astral-sh/ruff/blob/0.17.2/crates/ruff_memory_usage).
|
||||
This version (0.0.20) is a component of [Ruff 0.17.3](https://crates.io/crates/ruff/0.17.3). The
|
||||
source can be found [here](https://github.com/astral-sh/ruff/blob/0.17.3/crates/ruff_memory_usage).
|
||||
|
||||
See Ruff's [crate versioning policy](https://docs.astral.sh/ruff/versioning/#crate-versioning) for
|
||||
details on versioning.
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_notebook"
|
||||
version = "0.0.19"
|
||||
version = "0.0.20"
|
||||
description = "This is an internal component crate of Ruff"
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
This crate is an internal component of [Ruff](https://crates.io/crates/ruff). The Rust API exposed
|
||||
here is unstable and will have frequent breaking changes.
|
||||
|
||||
This version (0.0.19) is a component of [Ruff 0.17.2](https://crates.io/crates/ruff/0.17.2). The
|
||||
source can be found [here](https://github.com/astral-sh/ruff/blob/0.17.2/crates/ruff_notebook).
|
||||
This version (0.0.20) is a component of [Ruff 0.17.3](https://crates.io/crates/ruff/0.17.3). The
|
||||
source can be found [here](https://github.com/astral-sh/ruff/blob/0.17.3/crates/ruff_notebook).
|
||||
|
||||
See Ruff's [crate versioning policy](https://docs.astral.sh/ruff/versioning/#crate-versioning) for
|
||||
details on versioning.
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_options_metadata"
|
||||
version = "0.0.19"
|
||||
version = "0.0.20"
|
||||
description = "This is an internal component crate of Ruff"
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
This crate is an internal component of [Ruff](https://crates.io/crates/ruff). The Rust API exposed
|
||||
here is unstable and will have frequent breaking changes.
|
||||
|
||||
This version (0.0.19) is a component of [Ruff 0.17.2](https://crates.io/crates/ruff/0.17.2). The
|
||||
source can be found [here](https://github.com/astral-sh/ruff/blob/0.17.2/crates/ruff_options_metadata).
|
||||
This version (0.0.20) is a component of [Ruff 0.17.3](https://crates.io/crates/ruff/0.17.3). The
|
||||
source can be found [here](https://github.com/astral-sh/ruff/blob/0.17.3/crates/ruff_options_metadata).
|
||||
|
||||
See Ruff's [crate versioning policy](https://docs.astral.sh/ruff/versioning/#crate-versioning) for
|
||||
details on versioning.
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_python_ast"
|
||||
version = "0.0.19"
|
||||
version = "0.0.20"
|
||||
description = "This is an internal component crate of Ruff"
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
This crate is an internal component of [Ruff](https://crates.io/crates/ruff). The Rust API exposed
|
||||
here is unstable and will have frequent breaking changes.
|
||||
|
||||
This version (0.0.19) is a component of [Ruff 0.17.2](https://crates.io/crates/ruff/0.17.2). The
|
||||
source can be found [here](https://github.com/astral-sh/ruff/blob/0.17.2/crates/ruff_python_ast).
|
||||
This version (0.0.20) is a component of [Ruff 0.17.3](https://crates.io/crates/ruff/0.17.3). The
|
||||
source can be found [here](https://github.com/astral-sh/ruff/blob/0.17.3/crates/ruff_python_ast).
|
||||
|
||||
See Ruff's [crate versioning policy](https://docs.astral.sh/ruff/versioning/#crate-versioning) for
|
||||
details on versioning.
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_python_codegen"
|
||||
version = "0.0.19"
|
||||
version = "0.0.20"
|
||||
description = "This is an internal component crate of Ruff"
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
This crate is an internal component of [Ruff](https://crates.io/crates/ruff). The Rust API exposed
|
||||
here is unstable and will have frequent breaking changes.
|
||||
|
||||
This version (0.0.19) is a component of [Ruff 0.17.2](https://crates.io/crates/ruff/0.17.2). The
|
||||
source can be found [here](https://github.com/astral-sh/ruff/blob/0.17.2/crates/ruff_python_codegen).
|
||||
This version (0.0.20) is a component of [Ruff 0.17.3](https://crates.io/crates/ruff/0.17.3). The
|
||||
source can be found [here](https://github.com/astral-sh/ruff/blob/0.17.3/crates/ruff_python_codegen).
|
||||
|
||||
See Ruff's [crate versioning policy](https://docs.astral.sh/ruff/versioning/#crate-versioning) for
|
||||
details on versioning.
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_python_edits"
|
||||
version = "0.0.19"
|
||||
version = "0.0.20"
|
||||
description = "This is an internal component crate of Ruff"
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
This crate is an internal component of [Ruff](https://crates.io/crates/ruff). The Rust API exposed
|
||||
here is unstable and will have frequent breaking changes.
|
||||
|
||||
This version (0.0.19) is a component of [Ruff 0.17.2](https://crates.io/crates/ruff/0.17.2). The
|
||||
source can be found [here](https://github.com/astral-sh/ruff/blob/0.17.2/crates/ruff_python_edits).
|
||||
This version (0.0.20) is a component of [Ruff 0.17.3](https://crates.io/crates/ruff/0.17.3). The
|
||||
source can be found [here](https://github.com/astral-sh/ruff/blob/0.17.3/crates/ruff_python_edits).
|
||||
|
||||
See Ruff's [crate versioning policy](https://docs.astral.sh/ruff/versioning/#crate-versioning) for
|
||||
details on versioning.
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_python_formatter"
|
||||
version = "0.0.19"
|
||||
version = "0.0.20"
|
||||
description = "This is an internal component crate of Ruff"
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
@@ -32,8 +32,8 @@ Head to [The Ruff Formatter](https://docs.astral.sh/ruff/formatter/) for usage i
|
||||
This crate is an internal component of [Ruff](https://crates.io/crates/ruff). The Rust API exposed
|
||||
here is unstable and will have frequent breaking changes.
|
||||
|
||||
This version (0.0.19) is a component of [Ruff 0.17.2](https://crates.io/crates/ruff/0.17.2). The
|
||||
source can be found [here](https://github.com/astral-sh/ruff/blob/0.17.2/crates/ruff_python_formatter).
|
||||
This version (0.0.20) is a component of [Ruff 0.17.3](https://crates.io/crates/ruff/0.17.3). The
|
||||
source can be found [here](https://github.com/astral-sh/ruff/blob/0.17.3/crates/ruff_python_formatter).
|
||||
|
||||
See Ruff's [crate versioning policy](https://docs.astral.sh/ruff/versioning/#crate-versioning) for
|
||||
details on versioning.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_python_importer"
|
||||
version = "0.0.19"
|
||||
version = "0.0.20"
|
||||
description = "This is an internal component crate of Ruff"
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
This crate is an internal component of [Ruff](https://crates.io/crates/ruff). The Rust API exposed
|
||||
here is unstable and will have frequent breaking changes.
|
||||
|
||||
This version (0.0.19) is a component of [Ruff 0.17.2](https://crates.io/crates/ruff/0.17.2). The
|
||||
source can be found [here](https://github.com/astral-sh/ruff/blob/0.17.2/crates/ruff_python_importer).
|
||||
This version (0.0.20) is a component of [Ruff 0.17.3](https://crates.io/crates/ruff/0.17.3). The
|
||||
source can be found [here](https://github.com/astral-sh/ruff/blob/0.17.3/crates/ruff_python_importer).
|
||||
|
||||
See Ruff's [crate versioning policy](https://docs.astral.sh/ruff/versioning/#crate-versioning) for
|
||||
details on versioning.
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_python_index"
|
||||
version = "0.0.19"
|
||||
version = "0.0.20"
|
||||
description = "This is an internal component crate of Ruff"
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
This crate is an internal component of [Ruff](https://crates.io/crates/ruff). The Rust API exposed
|
||||
here is unstable and will have frequent breaking changes.
|
||||
|
||||
This version (0.0.19) is a component of [Ruff 0.17.2](https://crates.io/crates/ruff/0.17.2). The
|
||||
source can be found [here](https://github.com/astral-sh/ruff/blob/0.17.2/crates/ruff_python_index).
|
||||
This version (0.0.20) is a component of [Ruff 0.17.3](https://crates.io/crates/ruff/0.17.3). The
|
||||
source can be found [here](https://github.com/astral-sh/ruff/blob/0.17.3/crates/ruff_python_index).
|
||||
|
||||
See Ruff's [crate versioning policy](https://docs.astral.sh/ruff/versioning/#crate-versioning) for
|
||||
details on versioning.
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_python_literal"
|
||||
version = "0.0.19"
|
||||
version = "0.0.20"
|
||||
description = "This is an internal component crate of Ruff"
|
||||
authors = ["Charlie Marsh <charlie.r.marsh@gmail.com>", "RustPython Team"]
|
||||
edition = { workspace = true }
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
This crate is an internal component of [Ruff](https://crates.io/crates/ruff). The Rust API exposed
|
||||
here is unstable and will have frequent breaking changes.
|
||||
|
||||
This version (0.0.19) is a component of [Ruff 0.17.2](https://crates.io/crates/ruff/0.17.2). The
|
||||
source can be found [here](https://github.com/astral-sh/ruff/blob/0.17.2/crates/ruff_python_literal).
|
||||
This version (0.0.20) is a component of [Ruff 0.17.3](https://crates.io/crates/ruff/0.17.3). The
|
||||
source can be found [here](https://github.com/astral-sh/ruff/blob/0.17.3/crates/ruff_python_literal).
|
||||
|
||||
See Ruff's [crate versioning policy](https://docs.astral.sh/ruff/versioning/#crate-versioning) for
|
||||
details on versioning.
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_python_parser"
|
||||
version = "0.0.19"
|
||||
version = "0.0.20"
|
||||
description = "This is an internal component crate of Ruff"
|
||||
authors = ["Charlie Marsh <charlie.r.marsh@gmail.com>", "RustPython Team"]
|
||||
edition = { workspace = true }
|
||||
|
||||
@@ -19,8 +19,8 @@ Refer to the [contributing guidelines](./CONTRIBUTING.md) to get started and Git
|
||||
This crate is an internal component of [Ruff](https://crates.io/crates/ruff). The Rust API exposed
|
||||
here is unstable and will have frequent breaking changes.
|
||||
|
||||
This version (0.0.19) is a component of [Ruff 0.17.2](https://crates.io/crates/ruff/0.17.2). The
|
||||
source can be found [here](https://github.com/astral-sh/ruff/blob/0.17.2/crates/ruff_python_parser).
|
||||
This version (0.0.20) is a component of [Ruff 0.17.3](https://crates.io/crates/ruff/0.17.3). The
|
||||
source can be found [here](https://github.com/astral-sh/ruff/blob/0.17.3/crates/ruff_python_parser).
|
||||
|
||||
See Ruff's [crate versioning policy](https://docs.astral.sh/ruff/versioning/#crate-versioning) for
|
||||
details on versioning.
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
try: work()
|
||||
except ValueError: raise; report()
|
||||
@@ -532,7 +532,10 @@ impl<'src> Parser<'src> {
|
||||
self.bump(TokenKind::Raise);
|
||||
|
||||
let exc = match self.current_token_kind() {
|
||||
TokenKind::Newline => None,
|
||||
// test_ok bare_raise_before_semicolon
|
||||
// try: work()
|
||||
// except ValueError: raise; report()
|
||||
TokenKind::Newline | TokenKind::Semi => None,
|
||||
TokenKind::From => {
|
||||
// test_err raise_stmt_from_without_exc
|
||||
// raise from exc
|
||||
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
---
|
||||
source: crates/ruff_python_parser/tests/fixtures.rs
|
||||
input_file: crates/ruff_python_parser/resources/inline/ok/bare_raise_before_semicolon.py
|
||||
---
|
||||
## AST
|
||||
|
||||
```
|
||||
Module(
|
||||
ModModule {
|
||||
node_index: NodeIndex(None),
|
||||
range: 0..47,
|
||||
body: [
|
||||
Try(
|
||||
StmtTry {
|
||||
node_index: NodeIndex(None),
|
||||
range: 0..46,
|
||||
body: [
|
||||
Expr(
|
||||
StmtExpr {
|
||||
node_index: NodeIndex(None),
|
||||
range: 5..11,
|
||||
value: Call(
|
||||
ExprCall {
|
||||
node_index: NodeIndex(None),
|
||||
range: 5..11,
|
||||
func: Name(
|
||||
ExprName {
|
||||
node_index: NodeIndex(None),
|
||||
range: 5..9,
|
||||
id: Name("work"),
|
||||
ctx: Load,
|
||||
},
|
||||
),
|
||||
arguments: Arguments {
|
||||
range: 9..11,
|
||||
node_index: NodeIndex(None),
|
||||
args: [],
|
||||
keywords: [],
|
||||
},
|
||||
},
|
||||
),
|
||||
},
|
||||
),
|
||||
],
|
||||
handlers: [
|
||||
ExceptHandler(
|
||||
ExceptHandlerExceptHandler {
|
||||
range: 12..46,
|
||||
node_index: NodeIndex(None),
|
||||
type_: Some(
|
||||
Name(
|
||||
ExprName {
|
||||
node_index: NodeIndex(None),
|
||||
range: 19..29,
|
||||
id: Name("ValueError"),
|
||||
ctx: Load,
|
||||
},
|
||||
),
|
||||
),
|
||||
name: None,
|
||||
body: [
|
||||
Raise(
|
||||
StmtRaise {
|
||||
node_index: NodeIndex(None),
|
||||
range: 31..36,
|
||||
exc: None,
|
||||
cause: None,
|
||||
},
|
||||
),
|
||||
Expr(
|
||||
StmtExpr {
|
||||
node_index: NodeIndex(None),
|
||||
range: 38..46,
|
||||
value: Call(
|
||||
ExprCall {
|
||||
node_index: NodeIndex(None),
|
||||
range: 38..46,
|
||||
func: Name(
|
||||
ExprName {
|
||||
node_index: NodeIndex(None),
|
||||
range: 38..44,
|
||||
id: Name("report"),
|
||||
ctx: Load,
|
||||
},
|
||||
),
|
||||
arguments: Arguments {
|
||||
range: 44..46,
|
||||
node_index: NodeIndex(None),
|
||||
args: [],
|
||||
keywords: [],
|
||||
},
|
||||
},
|
||||
),
|
||||
},
|
||||
),
|
||||
],
|
||||
},
|
||||
),
|
||||
],
|
||||
orelse: [],
|
||||
finalbody: [],
|
||||
is_star: false,
|
||||
},
|
||||
),
|
||||
],
|
||||
},
|
||||
)
|
||||
```
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_python_semantic"
|
||||
version = "0.0.19"
|
||||
version = "0.0.20"
|
||||
description = "This is an internal component crate of Ruff"
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
This crate is an internal component of [Ruff](https://crates.io/crates/ruff). The Rust API exposed
|
||||
here is unstable and will have frequent breaking changes.
|
||||
|
||||
This version (0.0.19) is a component of [Ruff 0.17.2](https://crates.io/crates/ruff/0.17.2). The
|
||||
source can be found [here](https://github.com/astral-sh/ruff/blob/0.17.2/crates/ruff_python_semantic).
|
||||
This version (0.0.20) is a component of [Ruff 0.17.3](https://crates.io/crates/ruff/0.17.3). The
|
||||
source can be found [here](https://github.com/astral-sh/ruff/blob/0.17.3/crates/ruff_python_semantic).
|
||||
|
||||
See Ruff's [crate versioning policy](https://docs.astral.sh/ruff/versioning/#crate-versioning) for
|
||||
details on versioning.
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_python_stdlib"
|
||||
version = "0.0.19"
|
||||
version = "0.0.20"
|
||||
description = "This is an internal component crate of Ruff"
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
This crate is an internal component of [Ruff](https://crates.io/crates/ruff). The Rust API exposed
|
||||
here is unstable and will have frequent breaking changes.
|
||||
|
||||
This version (0.0.19) is a component of [Ruff 0.17.2](https://crates.io/crates/ruff/0.17.2). The
|
||||
source can be found [here](https://github.com/astral-sh/ruff/blob/0.17.2/crates/ruff_python_stdlib).
|
||||
This version (0.0.20) is a component of [Ruff 0.17.3](https://crates.io/crates/ruff/0.17.3). The
|
||||
source can be found [here](https://github.com/astral-sh/ruff/blob/0.17.3/crates/ruff_python_stdlib).
|
||||
|
||||
See Ruff's [crate versioning policy](https://docs.astral.sh/ruff/versioning/#crate-versioning) for
|
||||
details on versioning.
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_python_trivia"
|
||||
version = "0.0.19"
|
||||
version = "0.0.20"
|
||||
description = "This is an internal component crate of Ruff"
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
This crate is an internal component of [Ruff](https://crates.io/crates/ruff). The Rust API exposed
|
||||
here is unstable and will have frequent breaking changes.
|
||||
|
||||
This version (0.0.19) is a component of [Ruff 0.17.2](https://crates.io/crates/ruff/0.17.2). The
|
||||
source can be found [here](https://github.com/astral-sh/ruff/blob/0.17.2/crates/ruff_python_trivia).
|
||||
This version (0.0.20) is a component of [Ruff 0.17.3](https://crates.io/crates/ruff/0.17.3). The
|
||||
source can be found [here](https://github.com/astral-sh/ruff/blob/0.17.3/crates/ruff_python_trivia).
|
||||
|
||||
See Ruff's [crate versioning policy](https://docs.astral.sh/ruff/versioning/#crate-versioning) for
|
||||
details on versioning.
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_ranged_value"
|
||||
version = "0.0.19"
|
||||
version = "0.0.20"
|
||||
description = "This is an internal component crate of Ruff"
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
This crate is an internal component of [Ruff](https://crates.io/crates/ruff). The Rust API exposed
|
||||
here is unstable and will have frequent breaking changes.
|
||||
|
||||
This version (0.0.19) is a component of [Ruff 0.17.2](https://crates.io/crates/ruff/0.17.2). The
|
||||
source can be found [here](https://github.com/astral-sh/ruff/blob/0.17.2/crates/ruff_ranged_value).
|
||||
This version (0.0.20) is a component of [Ruff 0.17.3](https://crates.io/crates/ruff/0.17.3). The
|
||||
source can be found [here](https://github.com/astral-sh/ruff/blob/0.17.3/crates/ruff_ranged_value).
|
||||
|
||||
See Ruff's [crate versioning policy](https://docs.astral.sh/ruff/versioning/#crate-versioning) for
|
||||
details on versioning.
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_server"
|
||||
version = "0.0.19"
|
||||
version = "0.0.20"
|
||||
description = "This is an internal component crate of Ruff"
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
@@ -24,8 +24,8 @@ You can also join us on [**Discord**](https://discord.com/invite/astral-sh).
|
||||
This crate is an internal component of [Ruff](https://crates.io/crates/ruff). The Rust API exposed
|
||||
here is unstable and will have frequent breaking changes.
|
||||
|
||||
This version (0.0.19) is a component of [Ruff 0.17.2](https://crates.io/crates/ruff/0.17.2). The
|
||||
source can be found [here](https://github.com/astral-sh/ruff/blob/0.17.2/crates/ruff_server).
|
||||
This version (0.0.20) is a component of [Ruff 0.17.3](https://crates.io/crates/ruff/0.17.3). The
|
||||
source can be found [here](https://github.com/astral-sh/ruff/blob/0.17.3/crates/ruff_server).
|
||||
|
||||
See Ruff's [crate versioning policy](https://docs.astral.sh/ruff/versioning/#crate-versioning) for
|
||||
details on versioning.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_source_file"
|
||||
version = "0.0.19"
|
||||
version = "0.0.20"
|
||||
description = "This is an internal component crate of Ruff"
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
This crate is an internal component of [Ruff](https://crates.io/crates/ruff). The Rust API exposed
|
||||
here is unstable and will have frequent breaking changes.
|
||||
|
||||
This version (0.0.19) is a component of [Ruff 0.17.2](https://crates.io/crates/ruff/0.17.2). The
|
||||
source can be found [here](https://github.com/astral-sh/ruff/blob/0.17.2/crates/ruff_source_file).
|
||||
This version (0.0.20) is a component of [Ruff 0.17.3](https://crates.io/crates/ruff/0.17.3). The
|
||||
source can be found [here](https://github.com/astral-sh/ruff/blob/0.17.3/crates/ruff_source_file).
|
||||
|
||||
See Ruff's [crate versioning policy](https://docs.astral.sh/ruff/versioning/#crate-versioning) for
|
||||
details on versioning.
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_text_size"
|
||||
version = "0.0.19"
|
||||
version = "0.0.20"
|
||||
description = "This is an internal component crate of Ruff"
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
This crate is an internal component of [Ruff](https://crates.io/crates/ruff). The Rust API exposed
|
||||
here is unstable and will have frequent breaking changes.
|
||||
|
||||
This version (0.0.19) is a component of [Ruff 0.17.2](https://crates.io/crates/ruff/0.17.2). The
|
||||
source can be found [here](https://github.com/astral-sh/ruff/blob/0.17.2/crates/ruff_text_size).
|
||||
This version (0.0.20) is a component of [Ruff 0.17.3](https://crates.io/crates/ruff/0.17.3). The
|
||||
source can be found [here](https://github.com/astral-sh/ruff/blob/0.17.3/crates/ruff_text_size).
|
||||
|
||||
See Ruff's [crate versioning policy](https://docs.astral.sh/ruff/versioning/#crate-versioning) for
|
||||
details on versioning.
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_unicode_names2"
|
||||
version = "0.0.19"
|
||||
version = "0.0.20"
|
||||
description = "This is an internal component crate of Ruff"
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_unicode_names2_generator"
|
||||
version = "0.0.19"
|
||||
version = "0.0.20"
|
||||
description = "This is an internal component crate of Ruff"
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
This crate is an internal component of [Ruff](https://crates.io/crates/ruff). The Rust API exposed
|
||||
here is unstable and will have frequent breaking changes.
|
||||
|
||||
This version (0.0.19) is a component of [Ruff 0.17.2](https://crates.io/crates/ruff/0.17.2). The
|
||||
source can be found [here](https://github.com/astral-sh/ruff/blob/0.17.2/crates/ruff_unicode_names2_generator).
|
||||
This version (0.0.20) is a component of [Ruff 0.17.3](https://crates.io/crates/ruff/0.17.3). The
|
||||
source can be found [here](https://github.com/astral-sh/ruff/blob/0.17.3/crates/ruff_unicode_names2_generator).
|
||||
|
||||
See Ruff's [crate versioning policy](https://docs.astral.sh/ruff/versioning/#crate-versioning) for
|
||||
details on versioning.
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_wasm"
|
||||
version = "0.17.2"
|
||||
version = "0.17.3"
|
||||
description = "WebAssembly bindings for Ruff"
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
@@ -55,8 +55,8 @@ const formatted = workspace.format(exampleDocument);
|
||||
This crate is an internal component of [Ruff](https://crates.io/crates/ruff). The Rust API exposed
|
||||
here is unstable and will have frequent breaking changes.
|
||||
|
||||
This version (0.17.2) is a component of [Ruff 0.17.2](https://crates.io/crates/ruff/0.17.2). The
|
||||
source can be found [here](https://github.com/astral-sh/ruff/blob/0.17.2/crates/ruff_wasm).
|
||||
This version (0.17.3) is a component of [Ruff 0.17.3](https://crates.io/crates/ruff/0.17.3). The
|
||||
source can be found [here](https://github.com/astral-sh/ruff/blob/0.17.3/crates/ruff_wasm).
|
||||
|
||||
See Ruff's [crate versioning policy](https://docs.astral.sh/ruff/versioning/#crate-versioning) for
|
||||
details on versioning.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_workspace"
|
||||
version = "0.0.19"
|
||||
version = "0.0.20"
|
||||
description = "This is an internal component crate of Ruff"
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
This crate is an internal component of [Ruff](https://crates.io/crates/ruff). The Rust API exposed
|
||||
here is unstable and will have frequent breaking changes.
|
||||
|
||||
This version (0.0.19) is a component of [Ruff 0.17.2](https://crates.io/crates/ruff/0.17.2). The
|
||||
source can be found [here](https://github.com/astral-sh/ruff/blob/0.17.2/crates/ruff_workspace).
|
||||
This version (0.0.20) is a component of [Ruff 0.17.3](https://crates.io/crates/ruff/0.17.3). The
|
||||
source can be found [here](https://github.com/astral-sh/ruff/blob/0.17.3/crates/ruff_workspace).
|
||||
|
||||
See Ruff's [crate versioning policy](https://docs.astral.sh/ruff/versioning/#crate-versioning) for
|
||||
details on versioning.
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ty_combine"
|
||||
version = "0.0.19"
|
||||
version = "0.0.20"
|
||||
description = "This is an internal component crate of Ruff"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
This crate is an internal component of [Ruff](https://crates.io/crates/ruff). The Rust API exposed
|
||||
here is unstable and will have frequent breaking changes.
|
||||
|
||||
This version (0.0.19) is a component of [Ruff 0.17.2](https://crates.io/crates/ruff/0.17.2). The
|
||||
source can be found [here](https://github.com/astral-sh/ruff/blob/0.17.2/crates/ty_combine).
|
||||
This version (0.0.20) is a component of [Ruff 0.17.3](https://crates.io/crates/ruff/0.17.3). The
|
||||
source can be found [here](https://github.com/astral-sh/ruff/blob/0.17.3/crates/ty_combine).
|
||||
|
||||
See Ruff's [crate versioning policy](https://docs.astral.sh/ruff/versioning/#crate-versioning) for
|
||||
details on versioning.
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ty_module_resolver"
|
||||
version = "0.0.19"
|
||||
version = "0.0.20"
|
||||
description = "This is an internal component crate of Ruff"
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
This crate is an internal component of [Ruff](https://crates.io/crates/ruff). The Rust API exposed
|
||||
here is unstable and will have frequent breaking changes.
|
||||
|
||||
This version (0.0.19) is a component of [Ruff 0.17.2](https://crates.io/crates/ruff/0.17.2). The
|
||||
source can be found [here](https://github.com/astral-sh/ruff/blob/0.17.2/crates/ty_module_resolver).
|
||||
This version (0.0.20) is a component of [Ruff 0.17.3](https://crates.io/crates/ruff/0.17.3). The
|
||||
source can be found [here](https://github.com/astral-sh/ruff/blob/0.17.3/crates/ty_module_resolver).
|
||||
|
||||
See Ruff's [crate versioning policy](https://docs.astral.sh/ruff/versioning/#crate-versioning) for
|
||||
details on versioning.
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ty_python_core"
|
||||
version = "0.0.19"
|
||||
version = "0.0.20"
|
||||
description = "This is an internal component crate of Ruff"
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
This crate is an internal component of [Ruff](https://crates.io/crates/ruff). The Rust API exposed
|
||||
here is unstable and will have frequent breaking changes.
|
||||
|
||||
This version (0.0.19) is a component of [Ruff 0.17.2](https://crates.io/crates/ruff/0.17.2). The
|
||||
source can be found [here](https://github.com/astral-sh/ruff/blob/0.17.2/crates/ty_python_core).
|
||||
This version (0.0.20) is a component of [Ruff 0.17.3](https://crates.io/crates/ruff/0.17.3). The
|
||||
source can be found [here](https://github.com/astral-sh/ruff/blob/0.17.3/crates/ty_python_core).
|
||||
|
||||
See Ruff's [crate versioning policy](https://docs.astral.sh/ruff/versioning/#crate-versioning) for
|
||||
details on versioning.
|
||||
@@ -3771,7 +3771,10 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> {
|
||||
fn visit_bool_expression(&mut self, node: &'ast ast::ExprBoolOp, context: ExpressionContext) {
|
||||
let ast::ExprBoolOp { values, op, .. } = node;
|
||||
let mut snapshots = vec![];
|
||||
let mut reachability_constraints = vec![];
|
||||
let has_named_expression = values
|
||||
.iter()
|
||||
.any(|value| any_over_expr(value, &ast::Expr::is_named_expr));
|
||||
let mut reachability_constraints = SmallVec::<[ScopedReachabilityConstraintId; 1]>::new();
|
||||
let mut last_condition_flow_snapshots = None;
|
||||
|
||||
for (index, value) in values.iter().enumerate() {
|
||||
@@ -3812,19 +3815,17 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> {
|
||||
self.flow_snapshot()
|
||||
};
|
||||
|
||||
// We first model the short-circuiting behavior. We take the short-circuit
|
||||
// path here if all of the previous short-circuit paths were not taken, so
|
||||
// we record all previously existing reachability constraints, and negate the
|
||||
// one for the current expression.
|
||||
|
||||
// This path reaches the current operand and then short-circuits.
|
||||
self.record_negated_reachability_constraint(reachability_constraint);
|
||||
snapshots.push(self.flow_snapshot());
|
||||
|
||||
// Then we model the non-short-circuiting behavior. Here, we need to delay
|
||||
// the application of the reachability constraint until after the expression
|
||||
// has been evaluated, so we only push it onto the stack here.
|
||||
self.flow_restore(continuation);
|
||||
self.record_narrowing_constraint_id_for_places(predicate_id, &possibly_narrowed);
|
||||
// The continuation carries the conditions that led to this operand. Named
|
||||
// expressions can introduce new bindings that still need the earlier gates.
|
||||
if !has_named_expression {
|
||||
reachability_constraints.clear();
|
||||
}
|
||||
reachability_constraints.push(reachability_constraint);
|
||||
} else {
|
||||
last_condition_flow_snapshots = self.take_condition_flow_snapshots(value);
|
||||
@@ -3838,13 +3839,7 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> {
|
||||
condition_flow_snapshots.into_short_circuit_and_continuation(*op);
|
||||
(Some(short_circuit), Some(no_short_circuit))
|
||||
} else {
|
||||
(
|
||||
None,
|
||||
values
|
||||
.iter()
|
||||
.any(|value| any_over_expr(value, &ast::Expr::is_named_expr))
|
||||
.then(|| self.flow_snapshot()),
|
||||
)
|
||||
(None, has_named_expression.then(|| self.flow_snapshot()))
|
||||
};
|
||||
|
||||
if let Some(last_short_circuit) = last_short_circuit {
|
||||
|
||||
@@ -91,7 +91,7 @@ pub enum ExpressionContext {
|
||||
pub enum ExpressionKind {
|
||||
/// An ordinary value expression, such as `1` in `self.x: int = 1`.
|
||||
Normal,
|
||||
/// A predicate whose truthiness is recorded in its evaluation context.
|
||||
/// A predicate evaluated in value or condition context.
|
||||
Predicate {
|
||||
context: ExpressionContext,
|
||||
in_loop: bool,
|
||||
|
||||
@@ -2193,7 +2193,8 @@ impl<'db> UseDefMapBuilder<'db> {
|
||||
}
|
||||
|
||||
pub(super) fn add_place(&mut self, place: ScopedPlaceId) {
|
||||
self.checkpoint_state.record_binding_change();
|
||||
// Earlier snapshots restore new places as undefined. The first definition
|
||||
// changes the bindings visible to an exception handler.
|
||||
match place {
|
||||
ScopedPlaceId::Symbol(symbol) => {
|
||||
let new_place = self.symbol_states.push(PendingPlaceState::new(
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ty_python_semantic"
|
||||
version = "0.0.19"
|
||||
version = "0.0.20"
|
||||
description = "This is an internal component crate of Ruff"
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
This crate is an internal component of [Ruff](https://crates.io/crates/ruff). The Rust API exposed
|
||||
here is unstable and will have frequent breaking changes.
|
||||
|
||||
This version (0.0.19) is a component of [Ruff 0.17.2](https://crates.io/crates/ruff/0.17.2). The
|
||||
source can be found [here](https://github.com/astral-sh/ruff/blob/0.17.2/crates/ty_python_semantic).
|
||||
This version (0.0.20) is a component of [Ruff 0.17.3](https://crates.io/crates/ruff/0.17.3). The
|
||||
source can be found [here](https://github.com/astral-sh/ruff/blob/0.17.3/crates/ty_python_semantic).
|
||||
|
||||
See Ruff's [crate versioning policy](https://docs.astral.sh/ruff/versioning/#crate-versioning) for
|
||||
details on versioning.
|
||||
@@ -0,0 +1,11 @@
|
||||
# Regression test for https://github.com/astral-sh/ty/issues/4834
|
||||
|
||||
class C("", (lambda: value, (predicate := lambda: value))):
|
||||
pass
|
||||
|
||||
# Read the lambda before the loop adds the class as another possible value of predicate.
|
||||
predicate
|
||||
while True:
|
||||
assert predicate
|
||||
value = 0
|
||||
predicate = C
|
||||
@@ -319,6 +319,15 @@ def nested_impossible_operands(other: object, value: bool, marker: int):
|
||||
reveal_type(marker) # revealed: Never
|
||||
```
|
||||
|
||||
An assignment expression can rebind a name tested earlier in the chain. Reaching the final operand
|
||||
requires the new value to be `None`, so the branch is unreachable.
|
||||
|
||||
```py
|
||||
def reassigned_operand(value: int | None, replacement: int | None, marker: int):
|
||||
if value is not None and (value := replacement) is None and value is None and isinstance(value, int):
|
||||
reveal_type(marker) # revealed: Never
|
||||
```
|
||||
|
||||
## Conditions with aliased `Never` operands
|
||||
|
||||
A call cannot produce a result when its return type is an alias of `Never`. Only the preceding
|
||||
|
||||
@@ -389,4 +389,156 @@ def _(article: NewsArticle):
|
||||
reveal_type(Article.get_previous_by_published(article)) # revealed: NewsArticle
|
||||
```
|
||||
|
||||
## Abstract models
|
||||
|
||||
### Basic
|
||||
|
||||
An abstract model provides reusable fields without creating its own database table. Its concrete
|
||||
subclasses receive an integer primary key unless they declare or inherit an explicit primary key.
|
||||
|
||||
```py
|
||||
from django.db import models
|
||||
|
||||
class AbstractBook(models.Model):
|
||||
title = models.CharField(max_length=200)
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
class Ebook(AbstractBook):
|
||||
file_size = models.IntegerField()
|
||||
|
||||
class Book(AbstractBook):
|
||||
isbn = models.CharField(max_length=13, primary_key=True)
|
||||
|
||||
def _(abstract: AbstractBook, ebook: Ebook, book: Book):
|
||||
# Abstract models do not have an automatic primary key.
|
||||
abstract.id # error: [unresolved-attribute]
|
||||
|
||||
# Concrete models without an explicit primary key have an automatic integer key.
|
||||
reveal_type(ebook.id) # revealed: int
|
||||
reveal_type(ebook.pk) # revealed: int
|
||||
reveal_type(ebook.title) # revealed: str
|
||||
reveal_type(ebook.file_size) # revealed: int
|
||||
|
||||
# The explicit ISBN primary key prevents an automatic `id`.
|
||||
book.id # error: [unresolved-attribute]
|
||||
reveal_type(book.pk) # revealed: str
|
||||
reveal_type(book.title) # revealed: str
|
||||
reveal_type(book.isbn) # revealed: str
|
||||
```
|
||||
|
||||
### Annotated abstract setting
|
||||
|
||||
A bare annotation for `Meta.abstract` does not make a model abstract.
|
||||
|
||||
```py
|
||||
from django.db import models
|
||||
|
||||
class Book(models.Model):
|
||||
class Meta:
|
||||
abstract: bool
|
||||
|
||||
def _(book: Book):
|
||||
reveal_type(book.id) # revealed: int
|
||||
reveal_type(book.pk) # revealed: int
|
||||
```
|
||||
|
||||
When an assignment follows the annotation, its value determines whether the model is abstract.
|
||||
|
||||
```py
|
||||
class AbstractBook(models.Model):
|
||||
class Meta:
|
||||
abstract: bool
|
||||
abstract = True
|
||||
|
||||
def _(abstract: AbstractBook):
|
||||
abstract.id # error: [unresolved-attribute]
|
||||
```
|
||||
|
||||
### Inherited Meta configuration
|
||||
|
||||
Django resets an abstract model's `Meta.abstract` to `False`. A child that subclasses this `Meta` is
|
||||
concrete unless it explicitly sets `abstract = True` again.
|
||||
|
||||
```py
|
||||
from django.db import models
|
||||
|
||||
class AbstractBook(models.Model):
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
class NotAbstract(AbstractBook):
|
||||
# error: [invalid-attribute-override] "Meta"
|
||||
class Meta(AbstractBook.Meta):
|
||||
ordering = ["id"]
|
||||
|
||||
class Abstract(AbstractBook):
|
||||
# error: [invalid-attribute-override] "Meta"
|
||||
class Meta(AbstractBook.Meta):
|
||||
abstract = True
|
||||
|
||||
def _(not_abstract: NotAbstract, abstract: Abstract):
|
||||
# concrete models have an automatic primary key
|
||||
not_abstract.id
|
||||
|
||||
# abstract models do not:
|
||||
abstract.id # error: [unresolved-attribute]
|
||||
```
|
||||
|
||||
Configuration inherited from an ordinary class retains its `abstract` setting.
|
||||
|
||||
```py
|
||||
class Options:
|
||||
abstract = True
|
||||
|
||||
class InheritedAbstract(models.Model):
|
||||
class Meta(Options):
|
||||
pass
|
||||
|
||||
def _(abstract: InheritedAbstract):
|
||||
abstract.id # error: [unresolved-attribute]
|
||||
```
|
||||
|
||||
### Multiple inheritance
|
||||
|
||||
An abstract base without a primary key does not shadow an inherited `id`, regardless of the base
|
||||
class order. Regression test for <https://github.com/astral-sh/ty/issues/4822>.
|
||||
|
||||
```py
|
||||
from uuid import uuid4
|
||||
from django.db import models
|
||||
|
||||
class AbstractHasTitle(models.Model):
|
||||
title = models.CharField(max_length=200)
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
class AbstractHasId(models.Model):
|
||||
id = models.UUIDField(primary_key=True, default=uuid4)
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
# Django allows separate `Meta` configurations, but ty currently reports a conflict between them.
|
||||
# error: [invalid-attribute-override] "Incompatible inherited attribute `Meta`"
|
||||
class Book1(AbstractHasTitle, AbstractHasId):
|
||||
pass
|
||||
|
||||
# (see above)
|
||||
# error: [invalid-attribute-override] "Incompatible inherited attribute `Meta`"
|
||||
class Book2(AbstractHasId, AbstractHasTitle):
|
||||
pass
|
||||
|
||||
def _(book1: Book1, book2: Book2):
|
||||
reveal_type(book1.id) # revealed: UUID
|
||||
reveal_type(book1.pk) # revealed: UUID
|
||||
reveal_type(book1.title) # revealed: str
|
||||
|
||||
reveal_type(book2.id) # revealed: UUID
|
||||
reveal_type(book2.pk) # revealed: UUID
|
||||
reveal_type(book2.title) # revealed: str
|
||||
```
|
||||
|
||||
[django-types]: https://github.com/sbdchd/django-types
|
||||
@@ -1943,6 +1943,21 @@ for _ in range(1_000_000):
|
||||
x = 2
|
||||
```
|
||||
|
||||
The condition can depend on an earlier iteration. Here `y` starts at `2` and only ever receives `1`
|
||||
or `2` from `x`, so the loop never decrements `x`.
|
||||
|
||||
```py
|
||||
def f(n: int):
|
||||
x = 1
|
||||
y = 2
|
||||
for _ in range(n):
|
||||
if not y:
|
||||
x, y = x - 1, x
|
||||
else:
|
||||
x, y = 2, x
|
||||
reveal_type(x) # revealed: Literal[1, 2]
|
||||
```
|
||||
|
||||
### Large reachability constraint graphs fall back to `Unknown`
|
||||
|
||||
```py
|
||||
|
||||
@@ -864,6 +864,29 @@ def loop_back():
|
||||
value = Integer.Y
|
||||
```
|
||||
|
||||
The comparison narrows a `str | int` to the matching builtin type. A plain string can equal
|
||||
`String.X` and reach the `.name` access.
|
||||
|
||||
```py
|
||||
def compare_with_enum_member(value: str | int):
|
||||
if value == String.X:
|
||||
reveal_type(value) # revealed: str
|
||||
value.name # error: [unresolved-attribute]
|
||||
|
||||
if Integer.X == value:
|
||||
reveal_type(value) # revealed: int
|
||||
```
|
||||
|
||||
Members of other `IntEnum` classes can also compare equal to `Integer.X`.
|
||||
|
||||
```py
|
||||
def compare_int_enums(value: IntEnum):
|
||||
if value != Integer.X:
|
||||
reveal_type(value) # revealed: IntEnum & ~Literal[Integer.X]
|
||||
else:
|
||||
reveal_type(value) # revealed: IntEnum
|
||||
```
|
||||
|
||||
A custom `__new__` can replace the value declared in an `IntEnum` class body. We can still narrow
|
||||
the members of `Foo`, whose runtime values are known, but must preserve all of `Shifted` because its
|
||||
members' runtime values cannot be determined statically:
|
||||
@@ -1501,6 +1524,27 @@ def correlated_materialized_pattern(left: Top[MaterializedT], right: Materialize
|
||||
return 1
|
||||
```
|
||||
|
||||
## `LiteralString` and string literals
|
||||
|
||||
A comparison can narrow a `LiteralString` operand to a particular value or exclude that value. The
|
||||
concrete string keeps its type in either branch.
|
||||
|
||||
```py
|
||||
from typing_extensions import LiteralString
|
||||
|
||||
def compare(other: LiteralString):
|
||||
value = "a"
|
||||
if value != other:
|
||||
reveal_type(value) # revealed: Literal["a"]
|
||||
reveal_type(other) # revealed: LiteralString & ~Literal["a"]
|
||||
value + 1 # error: [unsupported-operator]
|
||||
|
||||
if other == value:
|
||||
reveal_type(other) # revealed: Literal["a"]
|
||||
else:
|
||||
reveal_type(value) # revealed: Literal["a"]
|
||||
```
|
||||
|
||||
## `LiteralString` and string-valued enums
|
||||
|
||||
`LiteralString` can be narrowed by comparison with a string-valued enum member that inherits `str`'s
|
||||
@@ -1517,6 +1561,7 @@ from typing_extensions import LiteralString
|
||||
|
||||
class Color(StrEnum):
|
||||
RED = "red"
|
||||
BLUE = "blue"
|
||||
|
||||
def narrow_literal_string_with_enum(value: LiteralString | None):
|
||||
if value == Color.RED:
|
||||
@@ -1530,6 +1575,15 @@ def narrow_literal_string_with_enum(value: LiteralString | None):
|
||||
reveal_type(value) # revealed: Literal["red"]
|
||||
```
|
||||
|
||||
The enum member keeps its type when its value differs from the `LiteralString`.
|
||||
|
||||
```py
|
||||
def compare_member(other: LiteralString):
|
||||
color = Color.RED
|
||||
if color != other:
|
||||
reveal_type(color) # revealed: Literal[Color.RED]
|
||||
```
|
||||
|
||||
## Module literals
|
||||
|
||||
Modules compare equal only to the same module object:
|
||||
|
||||
@@ -1442,6 +1442,19 @@ def _(colors: list[Color]):
|
||||
reveal_type(inline) # revealed: list[Color]
|
||||
```
|
||||
|
||||
Membership succeeds for the plain integer `1` as well as for the enum member `Code.OK`.
|
||||
|
||||
```py
|
||||
from enum import IntEnum
|
||||
|
||||
class Code(IntEnum):
|
||||
OK = 1
|
||||
|
||||
def check_membership(value: int):
|
||||
if value in (Code.OK,):
|
||||
reveal_type(value) # revealed: int
|
||||
```
|
||||
|
||||
An enum that can have additional runtime members can still be narrowed by a membership test against
|
||||
an explicit member. The other branch excludes that member without assuming that the declared members
|
||||
are exhaustive.
|
||||
|
||||
@@ -2183,6 +2183,32 @@ def test_match_mapping_uses_get(value: CustomGet) -> None:
|
||||
reveal_type(item) # revealed: object
|
||||
```
|
||||
|
||||
Narrowing a mapping preserves the type of its entries when it still uses the standard `get` method.
|
||||
A truthiness check can exclude empty dictionaries, and an `isinstance` check can add another class
|
||||
to the subject's type.
|
||||
|
||||
```py
|
||||
def match_nonempty_dict(value: dict[str, int]) -> None:
|
||||
if value:
|
||||
match value:
|
||||
case {"item": item}:
|
||||
reveal_type(item) # revealed: int
|
||||
|
||||
class Tagged: ...
|
||||
|
||||
def match_tagged_mapping(value: Mapping[str, bytes]) -> None:
|
||||
if isinstance(value, Tagged):
|
||||
match value:
|
||||
case {"item": item}:
|
||||
reveal_type(item) # revealed: bytes
|
||||
|
||||
def match_nonempty_custom_get(value: CustomGet) -> None:
|
||||
if value:
|
||||
match value:
|
||||
case {"item": item}:
|
||||
reveal_type(item) # revealed: object
|
||||
```
|
||||
|
||||
A callable stored on the instance can also provide `get`. The captured value still has type
|
||||
`object`.
|
||||
|
||||
@@ -4032,6 +4058,12 @@ class Color(StrEnum):
|
||||
GREEN = "g"
|
||||
BLUE = "b"
|
||||
|
||||
def match_string(value: str):
|
||||
match value:
|
||||
case Color.GREEN as captured:
|
||||
reveal_type(value) # revealed: str
|
||||
reveal_type(captured) # revealed: str
|
||||
|
||||
def test_literal_as_enum(x: Literal["g"]) -> None:
|
||||
match x:
|
||||
case Color.RED:
|
||||
|
||||
@@ -399,6 +399,31 @@ static_assert(is_assignable_to(type[Any], type[Arbitrary]))
|
||||
static_assert(is_assignable_to(type[Any], type[object]))
|
||||
```
|
||||
|
||||
### Narrowed instances with a gradual base
|
||||
|
||||
A class with a gradual base can match a union arm outside its known inheritance hierarchy, even
|
||||
after narrowing rules out another type.
|
||||
|
||||
```py
|
||||
from typing import Any
|
||||
from missing import UnknownBase # error: [unresolved-import]
|
||||
from ty_extensions import Intersection, Not, static_assert
|
||||
from ty_extensions._internal import is_assignable_to
|
||||
|
||||
class A: ...
|
||||
class B: ...
|
||||
class FromAny(Any): ...
|
||||
class Mixed(A, UnknownBase): ...
|
||||
|
||||
static_assert(is_assignable_to(Intersection[FromAny, Not[int]], A | B))
|
||||
```
|
||||
|
||||
The first arm excludes `Mixed` itself, but its unknown base can still match `B`.
|
||||
|
||||
```py
|
||||
static_assert(is_assignable_to(Intersection[Mixed, Not[int]], Intersection[A, Not[Mixed]] | B))
|
||||
```
|
||||
|
||||
## Heterogeneous tuple types
|
||||
|
||||
```py
|
||||
|
||||
@@ -349,6 +349,124 @@ def f(
|
||||
reveal_type(h) # revealed: tuple[Any | str, ...]
|
||||
```
|
||||
|
||||
## Tuple unions with narrowed built-in elements
|
||||
|
||||
A tuple alternative is redundant when its element type is covered by another alternative, including
|
||||
when that element is a built-in type narrowed by an exclusion. The `float` annotation includes
|
||||
`int`, so it also covers `int & ~bool`:
|
||||
|
||||
```py
|
||||
from ty_extensions import Intersection, Not
|
||||
|
||||
def numeric(value: tuple[Intersection[int, Not[bool]]] | tuple[float]):
|
||||
reveal_type(value) # revealed: tuple[float]
|
||||
```
|
||||
|
||||
## Tuple unions with built-in exclusions
|
||||
|
||||
Matching the positive class is not enough to establish redundancy: its exclusions also matter. For
|
||||
example, a subclass of both `Marker` and `str` can inhabit the first tuple alternative below, but
|
||||
neither branch of the second:
|
||||
|
||||
```py
|
||||
from ty_extensions import Intersection, Not
|
||||
|
||||
class Marker: ...
|
||||
|
||||
def different_exclusions(
|
||||
value: tuple[Intersection[Marker, Not[int]]] | tuple[Intersection[Marker, Not[str]] | bytes],
|
||||
):
|
||||
reveal_type(value) # revealed: tuple[Marker & ~int] | tuple[(Marker & ~str) | bytes]
|
||||
```
|
||||
|
||||
## Tuple unions with different exclusions on the same base
|
||||
|
||||
`Base & ~Y` covers `Child & ~Y`, so adding the child's tuple leaves the union unchanged. Excluding a
|
||||
third class, or starting from an unrelated class, leaves an additional tuple in the union.
|
||||
|
||||
```py
|
||||
from ty_extensions import Intersection, Not
|
||||
|
||||
class Base: ...
|
||||
class Child(Base): ...
|
||||
class X: ...
|
||||
class Y: ...
|
||||
class Z: ...
|
||||
|
||||
def compare(
|
||||
flag: bool,
|
||||
target: tuple[Intersection[Base, Not[X]] | Intersection[Base, Not[Y]]],
|
||||
covered: tuple[Intersection[Child, Not[Y]]],
|
||||
different_exclusion: tuple[Intersection[Child, Not[Z]]],
|
||||
unrelated: tuple[Intersection[Z, Not[X]]],
|
||||
):
|
||||
reveal_type(covered if flag else target) # revealed: tuple[(Base & ~X) | (Base & ~Y)]
|
||||
reveal_type(different_exclusion if flag else target) # revealed: tuple[Child & ~Z] | tuple[(Base & ~X) | (Base & ~Y)]
|
||||
reveal_type(unrelated if flag else target) # revealed: tuple[Z & ~X] | tuple[(Base & ~X) | (Base & ~Y)]
|
||||
```
|
||||
|
||||
## Tuple unions with multiple inheritance
|
||||
|
||||
Either base of `Both` can cover the narrower tuple, depending on which type its element excludes. A
|
||||
`Left` that excludes `str` can still be an `int` outside `Right`, so its tuple stays in the union.
|
||||
|
||||
```py
|
||||
from ty_extensions import Intersection, Not
|
||||
|
||||
class Left: ...
|
||||
class Right: ...
|
||||
class Both(Left, Right): ...
|
||||
|
||||
def compare(
|
||||
flag: bool,
|
||||
target: tuple[Intersection[Left, Not[int]] | Intersection[Right, Not[str]]],
|
||||
via_left: tuple[Intersection[Both, Not[int]]],
|
||||
via_right: tuple[Intersection[Both, Not[str]]],
|
||||
left_excluding_str: tuple[Intersection[Left, Not[str]]],
|
||||
):
|
||||
reveal_type(via_left if flag else target) # revealed: tuple[(Left & ~int) | (Right & ~str)]
|
||||
reveal_type(via_right if flag else target) # revealed: tuple[(Left & ~int) | (Right & ~str)]
|
||||
reveal_type(left_excluding_str if flag else target) # revealed: tuple[Left & ~str] | tuple[(Left & ~int) | (Right & ~str)]
|
||||
```
|
||||
|
||||
## Tuple unions with a base of `Any`
|
||||
|
||||
Inheriting from `Any` preserves the distinction between a known subclass and an unrelated class when
|
||||
simplifying tuple unions.
|
||||
|
||||
```py
|
||||
from typing import Any
|
||||
from ty_extensions import Intersection, Not
|
||||
|
||||
class Base(Any): ...
|
||||
class Child(Base): ...
|
||||
|
||||
def compare(
|
||||
unrelated: tuple[Intersection[Base, Not[int]]] | tuple[str | bytes],
|
||||
inherited: tuple[Intersection[Child, Not[int]]] | tuple[Intersection[Base, Not[int]] | bytes],
|
||||
):
|
||||
reveal_type(unrelated) # revealed: tuple[Base & ~int] | tuple[str | bytes]
|
||||
reveal_type(inherited) # revealed: tuple[(Base & ~int) | bytes]
|
||||
```
|
||||
|
||||
## Tuple unions with a recursive generic base
|
||||
|
||||
`Node & ~Excluded` covers `Child & ~Excluded` even when `Node` appears in its own base class
|
||||
specialization.
|
||||
|
||||
```py
|
||||
from ty_extensions import Intersection, Not
|
||||
|
||||
class Node(list["Node"]): ...
|
||||
class Child(Node): ...
|
||||
class Excluded: ...
|
||||
|
||||
def recursive(
|
||||
value: tuple[Intersection[Child, Not[Excluded]]] | tuple[Intersection[Node, Not[Excluded]] | str],
|
||||
):
|
||||
reveal_type(value) # revealed: tuple[(Node & ~Excluded) | str]
|
||||
```
|
||||
|
||||
## Unions of other generic containers
|
||||
|
||||
```toml
|
||||
|
||||
@@ -3,10 +3,11 @@ use ruff_python_ast::name::Name;
|
||||
use ty_module_resolver::KnownModule;
|
||||
use ty_python_core::{
|
||||
definition::{Definition, DefinitionKind},
|
||||
place_table, use_def_map,
|
||||
place_table, semantic_index, use_def_map,
|
||||
};
|
||||
|
||||
use crate::place::known_module_symbol;
|
||||
use crate::place::{known_module_symbol, place_from_bindings};
|
||||
use crate::types::infer::nearest_enclosing_class;
|
||||
use crate::types::member::class_member;
|
||||
use crate::types::{
|
||||
BindingContext, BoundTypeVarInstance, ClassBase, GenericContext, KnownClass, Parameter,
|
||||
@@ -149,6 +150,71 @@ fn own_model_fields<'db>(db: &'db dyn Db, class: StaticClassLiteral<'db>) -> Box
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Metadata extracted from a Django model's `Meta` configuration.
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, get_size2::GetSize, salsa::SalsaValue)]
|
||||
struct ModelMetadata {
|
||||
is_abstract: bool,
|
||||
}
|
||||
|
||||
#[salsa::tracked(returns(copy), heap_size=ruff_memory_usage::heap_size)]
|
||||
fn model_metadata<'db>(db: &'db dyn Db, class: StaticClassLiteral<'db>) -> ModelMetadata {
|
||||
let env = ProgramEnvironment::from_scope(class.body_scope(db));
|
||||
// Subclasses do not inherit `abstract = True`, so only inspect this model's own Meta.
|
||||
let Some(meta) = own_meta(db, class) else {
|
||||
return ModelMetadata::default();
|
||||
};
|
||||
|
||||
for meta_base in meta.iter_mro(db, None).filter_map(ClassBase::into_class) {
|
||||
let Some((meta_base, _)) = meta_base.static_class_literal(db) else {
|
||||
return ModelMetadata::default();
|
||||
};
|
||||
// Django resets an abstract model's Meta.abstract to False during model creation.
|
||||
// A child can inherit other options from that Meta, but must set abstract=True itself.
|
||||
let enclosing_scope = meta_base.definition(db).scope(db);
|
||||
if let Some(owner) = nearest_enclosing_class(
|
||||
db,
|
||||
semantic_index(db, enclosing_scope.program_file(db)),
|
||||
enclosing_scope,
|
||||
) && owner != class
|
||||
&& is_model(db, owner)
|
||||
&& own_meta(db, owner) == Some(meta_base)
|
||||
{
|
||||
return ModelMetadata::default();
|
||||
}
|
||||
|
||||
let scope = meta_base.body_scope(db);
|
||||
let Some(symbol) = place_table(db, scope).symbol_id("abstract") else {
|
||||
continue;
|
||||
};
|
||||
// The public type is widened to `bool`, so read the bindings' literal values instead.
|
||||
let Some(abstract_flag) = place_from_bindings(
|
||||
db,
|
||||
&env,
|
||||
use_def_map(db, scope).end_of_scope_symbol_bindings(symbol),
|
||||
)
|
||||
.place
|
||||
.ignore_possibly_undefined() else {
|
||||
continue;
|
||||
};
|
||||
return ModelMetadata {
|
||||
is_abstract: abstract_flag == Type::bool_literal(true),
|
||||
};
|
||||
}
|
||||
ModelMetadata::default()
|
||||
}
|
||||
|
||||
fn own_meta<'db>(
|
||||
db: &'db dyn Db,
|
||||
class: StaticClassLiteral<'db>,
|
||||
) -> Option<StaticClassLiteral<'db>> {
|
||||
let meta = class_member(db, class.body_scope(db), "Meta")
|
||||
.ignore_possibly_undefined()?
|
||||
.as_class_literal()?
|
||||
.as_static()?;
|
||||
(meta.name(db).as_str() == "Meta" && meta.definition(db).scope(db) == class.body_scope(db))
|
||||
.then_some(meta)
|
||||
}
|
||||
|
||||
/// Provides synthesized attributes and methods for Django models.
|
||||
pub(in crate::types) fn synthesized_member<'db>(
|
||||
db: &'db dyn Db,
|
||||
@@ -226,6 +292,11 @@ fn primary_key_member<'db>(
|
||||
}
|
||||
}
|
||||
|
||||
// Abstract models do not have an automatic primary key.
|
||||
if model_metadata(db, class).is_abstract {
|
||||
return None;
|
||||
}
|
||||
|
||||
known_module_symbol(db, env, KnownModule::DjangoDbModelsFields, "AutoField")
|
||||
.place
|
||||
.ignore_possibly_undefined()?
|
||||
|
||||
@@ -54,7 +54,7 @@ enum ComparisonResult<'db> {
|
||||
/// The comparison allows the operand being constrained to be narrowed to the wrapped type.
|
||||
///
|
||||
/// For example, if an object of type `LiteralString` compares equal to an object of type
|
||||
/// `Literal["foo"]`, the equality branch can safely narrow either operand to `Literal["foo"]`.
|
||||
/// `Literal["foo"]`, the equality branch narrows the `LiteralString` operand to `Literal["foo"]`.
|
||||
CanNarrow(Type<'db>),
|
||||
|
||||
/// The comparison may evaluate to true or false, depending on runtime values.
|
||||
@@ -743,10 +743,8 @@ fn evaluate_structural_comparison<'db>(
|
||||
None => narrow_literal_comparison(
|
||||
db,
|
||||
env,
|
||||
left,
|
||||
right,
|
||||
left_literal.kind(),
|
||||
right_literal.kind(),
|
||||
left_literal,
|
||||
right_literal,
|
||||
operator.condition_expects_equality(branch),
|
||||
),
|
||||
}
|
||||
@@ -1434,30 +1432,22 @@ fn finite_alternatives<'db>(
|
||||
|
||||
/// Return a constraint for literal pairs whose equality cannot be decided statically.
|
||||
///
|
||||
/// This primarily handles `LiteralString`, which can be constrained by a concrete string literal
|
||||
/// or a string-valued enum member without having a single statically known runtime value.
|
||||
/// A concrete string or string-valued enum member can constrain a `LiteralString` operand.
|
||||
/// In the reverse direction, the unknown string value places no constraint on the concrete operand.
|
||||
fn narrow_literal_comparison<'db>(
|
||||
db: &'db dyn Db,
|
||||
env: &ProgramEnvironment<'db>,
|
||||
left: Type<'db>,
|
||||
right: Type<'db>,
|
||||
left_literal: LiteralValueTypeKind<'db>,
|
||||
right_literal: LiteralValueTypeKind<'db>,
|
||||
left: LiteralValueType<'db>,
|
||||
right: LiteralValueType<'db>,
|
||||
equality_is_positive: bool,
|
||||
) -> ComparisonResult<'db> {
|
||||
match (left_literal, right_literal) {
|
||||
match (left.kind(), right.kind()) {
|
||||
(LiteralValueTypeKind::LiteralString, LiteralValueTypeKind::String(_)) => {
|
||||
ComparisonResult::CanNarrow(right.negate_if(db, env, !equality_is_positive))
|
||||
}
|
||||
(LiteralValueTypeKind::String(_), LiteralValueTypeKind::LiteralString) => {
|
||||
ComparisonResult::CanNarrow(left.negate_if(db, env, !equality_is_positive))
|
||||
ComparisonResult::CanNarrow(Type::from(right).negate_if(db, env, !equality_is_positive))
|
||||
}
|
||||
(LiteralValueTypeKind::LiteralString, LiteralValueTypeKind::Enum(enum_literal)) => {
|
||||
narrow_literal_string_against_enum(db, env, enum_literal, equality_is_positive)
|
||||
}
|
||||
(LiteralValueTypeKind::Enum(enum_literal), LiteralValueTypeKind::LiteralString) => {
|
||||
narrow_literal_string_against_enum(db, env, enum_literal, equality_is_positive)
|
||||
}
|
||||
_ => ComparisonResult::Ambiguous,
|
||||
}
|
||||
}
|
||||
@@ -1540,7 +1530,20 @@ fn compare_literal_to_other<'db>(
|
||||
};
|
||||
let condition_expects_equality = operator.condition_expects_equality(branch);
|
||||
|
||||
// Treat broad builtin types as if only the literal itself can compare equal. This is
|
||||
let literal_constraint = || {
|
||||
if !condition_expects_equality {
|
||||
ComparisonResult::CanNarrow(literal_type.negate(db, env))
|
||||
} else if matches!(literal, LiteralValueTypeKind::Enum(_))
|
||||
&& literal_semantics != KnownComparisonSemantics::Object
|
||||
{
|
||||
// A plain string can compare equal to a `StrEnum` member.
|
||||
ComparisonResult::Ambiguous
|
||||
} else {
|
||||
ComparisonResult::CanNarrow(literal_type)
|
||||
}
|
||||
};
|
||||
|
||||
// Treat broad builtin types as if only the builtin literal itself can compare equal. This is
|
||||
// intentionally unsafe: subclasses, including `bool` for `int`, can compare equal without
|
||||
// inhabiting the literal type. Explicitly typed subclasses do not take this path.
|
||||
if evaluator.soundness_policy.allow_unsafe_equality
|
||||
@@ -1549,7 +1552,7 @@ fn compare_literal_to_other<'db>(
|
||||
&& let Some(other_semantics) = unsafe_narrowable_builtin_semantics(db, other)
|
||||
{
|
||||
return if literal_semantics == other_semantics {
|
||||
ComparisonResult::CanNarrow(literal_type)
|
||||
literal_constraint()
|
||||
} else {
|
||||
operator.result_from_equality(false)
|
||||
};
|
||||
@@ -1574,17 +1577,12 @@ fn compare_literal_to_other<'db>(
|
||||
if literal_operand == LiteralOperand::Other
|
||||
&& !other.is_disjoint_from(db, env, literal_type) =>
|
||||
{
|
||||
ComparisonResult::CanNarrow(literal_type.negate_if(
|
||||
db,
|
||||
env,
|
||||
!condition_expects_equality,
|
||||
))
|
||||
literal_constraint()
|
||||
}
|
||||
Some(_) => ComparisonResult::Ambiguous,
|
||||
None if literal_operand == LiteralOperand::Other && !condition_expects_equality => {
|
||||
ComparisonResult::CanNarrow(literal_type.negate(db, env))
|
||||
literal_constraint()
|
||||
}
|
||||
None => ComparisonResult::Ambiguous,
|
||||
_ => ComparisonResult::Ambiguous,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loaded 100 of 117 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user