Update default and latest Python versions for 3.14 (#20725)
Some checks are pending
CI / cargo build (release) (push) Waiting to run
CI / Determine changes (push) Waiting to run
CI / cargo fmt (push) Waiting to run
CI / cargo clippy (push) Blocked by required conditions
CI / cargo test (linux) (push) Blocked by required conditions
CI / cargo test (linux, release) (push) Blocked by required conditions
CI / cargo test (windows) (push) Blocked by required conditions
CI / cargo test (wasm) (push) Blocked by required conditions
CI / cargo build (msrv) (push) Blocked by required conditions
CI / cargo fuzz build (push) Blocked by required conditions
CI / fuzz parser (push) Blocked by required conditions
CI / test scripts (push) Blocked by required conditions
CI / mkdocs (push) Waiting to run
CI / ecosystem (push) Blocked by required conditions
CI / Fuzz for new ty panics (push) Blocked by required conditions
CI / cargo shear (push) Blocked by required conditions
CI / python package (push) Waiting to run
CI / pre-commit (push) Waiting to run
CI / formatter instabilities and black similarity (push) Blocked by required conditions
CI / test ruff-lsp (push) Blocked by required conditions
CI / check playground (push) Blocked by required conditions
CI / benchmarks instrumented (ruff) (push) Blocked by required conditions
CI / benchmarks instrumented (ty) (push) Blocked by required conditions
CI / benchmarks-walltime (push) Blocked by required conditions
[ty Playground] Release / publish (push) Waiting to run

Summary
--

Closes #19467 and also removes the warning about using Python 3.14
without
preview enabled.

I also bumped `PythonVersion::default` to 3.9 because it reaches EOL
this month,
but we could also defer that for now if we wanted.

The first three commits are related to the `latest` bump to 3.14; the
fourth commit
bumps the default to 3.10.

Note that this PR also bumps the default Python version for ty to 3.10
because
there was a test asserting that it stays in sync with
`ast::PythonVersion`.

Test Plan
--

Existing tests

I spot-checked the ecosystem report, and I believe these are all
expected. Inbits doesn't specify a target Python version, so I guess
we're applying the default. UP007, UP035, and UP045 all use the new
default value to emit new diagnostics.
This commit is contained in:
Brent Westbrook 2025-10-07 12:23:11 -04:00 committed by GitHub
parent 8fb29eafb8
commit 88c0ce3e38
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
88 changed files with 1640 additions and 1123 deletions

View file

@ -9,7 +9,6 @@ info:
- concise
- "--show-settings"
- test.py
snapshot_kind: text
---
success: true
exit_code: 0
@ -265,7 +264,7 @@ linter.ruff.parenthesize_tuple_in_subscript = false
# Formatter Settings
formatter.exclude = []
formatter.unresolved_target_version = 3.9
formatter.unresolved_target_version = 3.10
formatter.per_file_target_version = {}
formatter.preview = disabled
formatter.line_width = 88
@ -280,7 +279,7 @@ formatter.docstring_code_line_width = dynamic
# Analyze Settings
analyze.exclude = []
analyze.preview = disabled
analyze.target_version = 3.9
analyze.target_version = 3.10
analyze.string_imports = disabled
analyze.extension = ExtensionMapping({})
analyze.include_dependencies = {}

View file

@ -9,7 +9,6 @@ info:
- concise
- "--show-settings"
- foo/test.py
snapshot_kind: text
---
success: true
exit_code: 0
@ -265,7 +264,7 @@ linter.ruff.parenthesize_tuple_in_subscript = false
# Formatter Settings
formatter.exclude = []
formatter.unresolved_target_version = 3.9
formatter.unresolved_target_version = 3.10
formatter.per_file_target_version = {}
formatter.preview = disabled
formatter.line_width = 88
@ -280,7 +279,7 @@ formatter.docstring_code_line_width = dynamic
# Analyze Settings
analyze.exclude = []
analyze.preview = disabled
analyze.target_version = 3.9
analyze.target_version = 3.10
analyze.string_imports = disabled
analyze.extension = ExtensionMapping({})
analyze.include_dependencies = {}

View file

@ -14,6 +14,6 @@ class Bar:
]
# OK: Allow named expressions in annotations.
# This is no longer allowed on Python 3.14+
x: (y := 1)
print(y)

View file

@ -13,16 +13,16 @@ CStr2: TypeAlias = Union["C", str] # always okay
# References to a class from inside the class:
class C:
other: C = ... # valid in a `.pyi` stub file, not in a `.py` runtime file
other: C = ... # valid in a `.pyi` stub file, and in a `.py` runtime file with deferred annotations
other2: "C" = ... # always okay
def from_str(self, s: str) -> C: ... # valid in a `.pyi` stub file, not in a `.py` runtime file
def from_str(self, s: str) -> C: ... # valid in a `.pyi` stub file, and in a `.py` runtime file with deferred annotations
def from_str2(self, s: str) -> "C": ... # always okay
# Circular references:
class A:
foo: B # valid in a `.pyi` stub file, not in a `.py` runtime file
foo: B # valid in a `.pyi` stub file, and in a `.py` runtime file with deferred annotations
foo2: "B" # always okay
bar: dict[str, B] # valid in a `.pyi` stub file, not in a `.py` runtime file
bar: dict[str, B] # valid in a `.pyi` stub file, and in a `.py` runtime file with deferred annotations
bar2: dict[str, "A"] # always okay
class B:

View file

@ -27,14 +27,13 @@ use crate::fix::{FixResult, fix_file};
use crate::message::create_syntax_error_diagnostic;
use crate::noqa::add_noqa;
use crate::package::PackageRoot;
use crate::preview::is_py314_support_enabled;
use crate::registry::Rule;
#[cfg(any(feature = "test-rules", test))]
use crate::rules::ruff::rules::test_rules::{self, TEST_RULES, TestRule};
use crate::settings::types::UnsafeFixes;
use crate::settings::{LinterSettings, TargetVersion, flags};
use crate::source_kind::SourceKind;
use crate::{Locator, directives, fs, warn_user_once};
use crate::{Locator, directives, fs};
pub(crate) mod float;
@ -442,14 +441,6 @@ pub fn lint_only(
) -> LinterResult {
let target_version = settings.resolve_target_version(path);
if matches!(target_version, TargetVersion(Some(PythonVersion::PY314)))
&& !is_py314_support_enabled(settings)
{
warn_user_once!(
"Support for Python 3.14 is in preview and may undergo breaking changes. Enable `preview` to remove this warning."
);
}
let parsed = source.into_parsed(source_kind, source_type, target_version.parser_version());
// Map row and column locations to byte slices (lazily).
@ -551,14 +542,6 @@ pub fn lint_fix<'a>(
let target_version = settings.resolve_target_version(path);
if matches!(target_version, TargetVersion(Some(PythonVersion::PY314)))
&& !is_py314_support_enabled(settings)
{
warn_user_once!(
"Support for Python 3.14 is in preview and may undergo breaking changes. Enable `preview` to remove this warning."
);
}
// Continuously fix until the source code stabilizes.
loop {
// Parse once.

View file

@ -7,10 +7,6 @@
use crate::settings::LinterSettings;
pub(crate) const fn is_py314_support_enabled(settings: &LinterSettings) -> bool {
settings.preview.is_enabled()
}
// Rule-specific behavior
// https://github.com/astral-sh/ruff/pull/15541

View file

@ -8,9 +8,12 @@ mod tests {
use anyhow::Result;
use test_case::test_case;
use ruff_python_ast::PythonVersion;
use crate::registry::Rule;
use crate::settings::LinterSettings;
use crate::test::test_path;
use crate::{assert_diagnostics, settings};
use crate::{assert_diagnostics, assert_diagnostics_diff};
#[test_case(Rule::FastApiRedundantResponseModel, Path::new("FAST001.py"))]
#[test_case(Rule::FastApiNonAnnotatedDependency, Path::new("FAST002_0.py"))]
@ -20,12 +23,35 @@ mod tests {
let snapshot = format!("{}_{}", rule_code.name(), path.to_string_lossy());
let diagnostics = test_path(
Path::new("fastapi").join(path).as_path(),
&settings::LinterSettings::for_rule(rule_code),
&LinterSettings::for_rule(rule_code),
)?;
assert_diagnostics!(snapshot, diagnostics);
Ok(())
}
#[test_case(Rule::FastApiRedundantResponseModel, Path::new("FAST001.py"))]
#[test_case(Rule::FastApiUnusedPathParameter, Path::new("FAST003.py"))]
fn deferred_annotations_diff(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!(
"deferred_annotations_diff_{}_{}",
rule_code.name(),
path.to_string_lossy()
);
assert_diagnostics_diff!(
snapshot,
Path::new("fastapi").join(path).as_path(),
&LinterSettings {
unresolved_target_version: PythonVersion::PY313.into(),
..LinterSettings::for_rule(rule_code)
},
&LinterSettings {
unresolved_target_version: PythonVersion::PY314.into(),
..LinterSettings::for_rule(rule_code)
},
);
Ok(())
}
// FAST002 autofixes use `typing_extensions` on Python 3.8,
// since `typing.Annotated` was added in Python 3.9
#[test_case(Rule::FastApiNonAnnotatedDependency, Path::new("FAST002_0.py"))]
@ -34,9 +60,9 @@ mod tests {
let snapshot = format!("{}_{}_py38", rule_code.name(), path.to_string_lossy());
let diagnostics = test_path(
Path::new("fastapi").join(path).as_path(),
&settings::LinterSettings {
unresolved_target_version: ruff_python_ast::PythonVersion::PY38.into(),
..settings::LinterSettings::for_rule(rule_code)
&LinterSettings {
unresolved_target_version: PythonVersion::PY38.into(),
..LinterSettings::for_rule(rule_code)
},
)?;
assert_diagnostics!(snapshot, diagnostics);

View file

@ -0,0 +1,213 @@
---
source: crates/ruff_linter/src/rules/fastapi/mod.rs
---
--- Linter settings ---
-linter.unresolved_target_version = 3.13
+linter.unresolved_target_version = 3.14
--- Summary ---
Removed: 10
Added: 0
--- Removed ---
FAST001 [*] FastAPI route with redundant `response_model` argument
--> FAST001.py:17:22
|
17 | @app.post("/items/", response_model=Item)
| ^^^^^^^^^^^^^^^^^^^
18 | async def create_item(item: Item) -> Item:
19 | return item
|
help: Remove argument
14 | # Errors
15 |
16 |
- @app.post("/items/", response_model=Item)
17 + @app.post("/items/")
18 | async def create_item(item: Item) -> Item:
19 | return item
20 |
note: This is an unsafe fix and may change runtime behavior
FAST001 [*] FastAPI route with redundant `response_model` argument
--> FAST001.py:22:22
|
22 | @app.post("/items/", response_model=list[Item])
| ^^^^^^^^^^^^^^^^^^^^^^^^^
23 | async def create_item(item: Item) -> list[Item]:
24 | return item
|
help: Remove argument
19 | return item
20 |
21 |
- @app.post("/items/", response_model=list[Item])
22 + @app.post("/items/")
23 | async def create_item(item: Item) -> list[Item]:
24 | return item
25 |
note: This is an unsafe fix and may change runtime behavior
FAST001 [*] FastAPI route with redundant `response_model` argument
--> FAST001.py:27:22
|
27 | @app.post("/items/", response_model=List[Item])
| ^^^^^^^^^^^^^^^^^^^^^^^^^
28 | async def create_item(item: Item) -> List[Item]:
29 | return item
|
help: Remove argument
24 | return item
25 |
26 |
- @app.post("/items/", response_model=List[Item])
27 + @app.post("/items/")
28 | async def create_item(item: Item) -> List[Item]:
29 | return item
30 |
note: This is an unsafe fix and may change runtime behavior
FAST001 [*] FastAPI route with redundant `response_model` argument
--> FAST001.py:32:22
|
32 | @app.post("/items/", response_model=Dict[str, Item])
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
33 | async def create_item(item: Item) -> Dict[str, Item]:
34 | return item
|
help: Remove argument
29 | return item
30 |
31 |
- @app.post("/items/", response_model=Dict[str, Item])
32 + @app.post("/items/")
33 | async def create_item(item: Item) -> Dict[str, Item]:
34 | return item
35 |
note: This is an unsafe fix and may change runtime behavior
FAST001 [*] FastAPI route with redundant `response_model` argument
--> FAST001.py:37:22
|
37 | @app.post("/items/", response_model=str)
| ^^^^^^^^^^^^^^^^^^
38 | async def create_item(item: Item) -> str:
39 | return item
|
help: Remove argument
34 | return item
35 |
36 |
- @app.post("/items/", response_model=str)
37 + @app.post("/items/")
38 | async def create_item(item: Item) -> str:
39 | return item
40 |
note: This is an unsafe fix and may change runtime behavior
FAST001 [*] FastAPI route with redundant `response_model` argument
--> FAST001.py:42:21
|
42 | @app.get("/items/", response_model=Item)
| ^^^^^^^^^^^^^^^^^^^
43 | async def create_item(item: Item) -> Item:
44 | return item
|
help: Remove argument
39 | return item
40 |
41 |
- @app.get("/items/", response_model=Item)
42 + @app.get("/items/")
43 | async def create_item(item: Item) -> Item:
44 | return item
45 |
note: This is an unsafe fix and may change runtime behavior
FAST001 [*] FastAPI route with redundant `response_model` argument
--> FAST001.py:47:21
|
47 | @app.get("/items/", response_model=Item)
| ^^^^^^^^^^^^^^^^^^^
48 | @app.post("/items/", response_model=Item)
49 | async def create_item(item: Item) -> Item:
|
help: Remove argument
44 | return item
45 |
46 |
- @app.get("/items/", response_model=Item)
47 + @app.get("/items/")
48 | @app.post("/items/", response_model=Item)
49 | async def create_item(item: Item) -> Item:
50 | return item
note: This is an unsafe fix and may change runtime behavior
FAST001 [*] FastAPI route with redundant `response_model` argument
--> FAST001.py:48:22
|
47 | @app.get("/items/", response_model=Item)
48 | @app.post("/items/", response_model=Item)
| ^^^^^^^^^^^^^^^^^^^
49 | async def create_item(item: Item) -> Item:
50 | return item
|
help: Remove argument
45 |
46 |
47 | @app.get("/items/", response_model=Item)
- @app.post("/items/", response_model=Item)
48 + @app.post("/items/")
49 | async def create_item(item: Item) -> Item:
50 | return item
51 |
note: This is an unsafe fix and may change runtime behavior
FAST001 [*] FastAPI route with redundant `response_model` argument
--> FAST001.py:53:24
|
53 | @router.get("/items/", response_model=Item)
| ^^^^^^^^^^^^^^^^^^^
54 | async def create_item(item: Item) -> Item:
55 | return item
|
help: Remove argument
50 | return item
51 |
52 |
- @router.get("/items/", response_model=Item)
53 + @router.get("/items/")
54 | async def create_item(item: Item) -> Item:
55 | return item
56 |
note: This is an unsafe fix and may change runtime behavior
FAST001 [*] FastAPI route with redundant `response_model` argument
--> FAST001.py:118:23
|
116 | def setup_app(app_arg: FastAPI, non_app: str) -> None:
117 | # Error
118 | @app_arg.get("/", response_model=str)
| ^^^^^^^^^^^^^^^^^^
119 | async def get_root() -> str:
120 | return "Hello World!"
|
help: Remove argument
115 |
116 | def setup_app(app_arg: FastAPI, non_app: str) -> None:
117 | # Error
- @app_arg.get("/", response_model=str)
118 + @app_arg.get("/")
119 | async def get_root() -> str:
120 | return "Hello World!"
121 |
note: This is an unsafe fix and may change runtime behavior

View file

@ -0,0 +1,74 @@
---
source: crates/ruff_linter/src/rules/fastapi/mod.rs
---
--- Linter settings ---
-linter.unresolved_target_version = 3.13
+linter.unresolved_target_version = 3.14
--- Summary ---
Removed: 3
Added: 0
--- Removed ---
FAST003 [*] Parameter `thing_id` appears in route path, but not in `single` signature
--> FAST003.py:158:19
|
157 | ### Errors
158 | @app.get("/things/{thing_id}")
| ^^^^^^^^^^
159 | async def single(other: Annotated[str, Depends(something_else)]): ...
160 | @app.get("/things/{thing_id}")
|
help: Add `thing_id` to function signature
156 |
157 | ### Errors
158 | @app.get("/things/{thing_id}")
- async def single(other: Annotated[str, Depends(something_else)]): ...
159 + async def single(other: Annotated[str, Depends(something_else)], thing_id): ...
160 | @app.get("/things/{thing_id}")
161 | async def default(other: str = Depends(something_else)): ...
162 |
note: This is an unsafe fix and may change runtime behavior
FAST003 [*] Parameter `id` appears in route path, but not in `get_id_pydantic_full` signature
--> FAST003.py:197:12
|
196 | # Errors
197 | @app.get("/{id}")
| ^^^^
198 | async def get_id_pydantic_full(
199 | params: Annotated[PydanticParams, Depends(PydanticParams)],
|
help: Add `id` to function signature
196 | # Errors
197 | @app.get("/{id}")
198 | async def get_id_pydantic_full(
- params: Annotated[PydanticParams, Depends(PydanticParams)],
199 + params: Annotated[PydanticParams, Depends(PydanticParams)], id,
200 | ): ...
201 | @app.get("/{id}")
202 | async def get_id_pydantic_short(params: Annotated[PydanticParams, Depends()]): ...
note: This is an unsafe fix and may change runtime behavior
FAST003 [*] Parameter `id` appears in route path, but not in `get_id_pydantic_short` signature
--> FAST003.py:201:12
|
199 | params: Annotated[PydanticParams, Depends(PydanticParams)],
200 | ): ...
201 | @app.get("/{id}")
| ^^^^
202 | async def get_id_pydantic_short(params: Annotated[PydanticParams, Depends()]): ...
203 | @app.get("/{id}")
|
help: Add `id` to function signature
199 | params: Annotated[PydanticParams, Depends(PydanticParams)],
200 | ): ...
201 | @app.get("/{id}")
- async def get_id_pydantic_short(params: Annotated[PydanticParams, Depends()]): ...
202 + async def get_id_pydantic_short(params: Annotated[PydanticParams, Depends()], id): ...
203 | @app.get("/{id}")
204 | async def get_id_init_not_annotated(params = Depends(InitParams)): ...
205 |
note: This is an unsafe fix and may change runtime behavior

View file

@ -1,195 +1,4 @@
---
source: crates/ruff_linter/src/rules/fastapi/mod.rs
---
FAST001 [*] FastAPI route with redundant `response_model` argument
--> FAST001.py:17:22
|
17 | @app.post("/items/", response_model=Item)
| ^^^^^^^^^^^^^^^^^^^
18 | async def create_item(item: Item) -> Item:
19 | return item
|
help: Remove argument
14 | # Errors
15 |
16 |
- @app.post("/items/", response_model=Item)
17 + @app.post("/items/")
18 | async def create_item(item: Item) -> Item:
19 | return item
20 |
note: This is an unsafe fix and may change runtime behavior
FAST001 [*] FastAPI route with redundant `response_model` argument
--> FAST001.py:22:22
|
22 | @app.post("/items/", response_model=list[Item])
| ^^^^^^^^^^^^^^^^^^^^^^^^^
23 | async def create_item(item: Item) -> list[Item]:
24 | return item
|
help: Remove argument
19 | return item
20 |
21 |
- @app.post("/items/", response_model=list[Item])
22 + @app.post("/items/")
23 | async def create_item(item: Item) -> list[Item]:
24 | return item
25 |
note: This is an unsafe fix and may change runtime behavior
FAST001 [*] FastAPI route with redundant `response_model` argument
--> FAST001.py:27:22
|
27 | @app.post("/items/", response_model=List[Item])
| ^^^^^^^^^^^^^^^^^^^^^^^^^
28 | async def create_item(item: Item) -> List[Item]:
29 | return item
|
help: Remove argument
24 | return item
25 |
26 |
- @app.post("/items/", response_model=List[Item])
27 + @app.post("/items/")
28 | async def create_item(item: Item) -> List[Item]:
29 | return item
30 |
note: This is an unsafe fix and may change runtime behavior
FAST001 [*] FastAPI route with redundant `response_model` argument
--> FAST001.py:32:22
|
32 | @app.post("/items/", response_model=Dict[str, Item])
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
33 | async def create_item(item: Item) -> Dict[str, Item]:
34 | return item
|
help: Remove argument
29 | return item
30 |
31 |
- @app.post("/items/", response_model=Dict[str, Item])
32 + @app.post("/items/")
33 | async def create_item(item: Item) -> Dict[str, Item]:
34 | return item
35 |
note: This is an unsafe fix and may change runtime behavior
FAST001 [*] FastAPI route with redundant `response_model` argument
--> FAST001.py:37:22
|
37 | @app.post("/items/", response_model=str)
| ^^^^^^^^^^^^^^^^^^
38 | async def create_item(item: Item) -> str:
39 | return item
|
help: Remove argument
34 | return item
35 |
36 |
- @app.post("/items/", response_model=str)
37 + @app.post("/items/")
38 | async def create_item(item: Item) -> str:
39 | return item
40 |
note: This is an unsafe fix and may change runtime behavior
FAST001 [*] FastAPI route with redundant `response_model` argument
--> FAST001.py:42:21
|
42 | @app.get("/items/", response_model=Item)
| ^^^^^^^^^^^^^^^^^^^
43 | async def create_item(item: Item) -> Item:
44 | return item
|
help: Remove argument
39 | return item
40 |
41 |
- @app.get("/items/", response_model=Item)
42 + @app.get("/items/")
43 | async def create_item(item: Item) -> Item:
44 | return item
45 |
note: This is an unsafe fix and may change runtime behavior
FAST001 [*] FastAPI route with redundant `response_model` argument
--> FAST001.py:47:21
|
47 | @app.get("/items/", response_model=Item)
| ^^^^^^^^^^^^^^^^^^^
48 | @app.post("/items/", response_model=Item)
49 | async def create_item(item: Item) -> Item:
|
help: Remove argument
44 | return item
45 |
46 |
- @app.get("/items/", response_model=Item)
47 + @app.get("/items/")
48 | @app.post("/items/", response_model=Item)
49 | async def create_item(item: Item) -> Item:
50 | return item
note: This is an unsafe fix and may change runtime behavior
FAST001 [*] FastAPI route with redundant `response_model` argument
--> FAST001.py:48:22
|
47 | @app.get("/items/", response_model=Item)
48 | @app.post("/items/", response_model=Item)
| ^^^^^^^^^^^^^^^^^^^
49 | async def create_item(item: Item) -> Item:
50 | return item
|
help: Remove argument
45 |
46 |
47 | @app.get("/items/", response_model=Item)
- @app.post("/items/", response_model=Item)
48 + @app.post("/items/")
49 | async def create_item(item: Item) -> Item:
50 | return item
51 |
note: This is an unsafe fix and may change runtime behavior
FAST001 [*] FastAPI route with redundant `response_model` argument
--> FAST001.py:53:24
|
53 | @router.get("/items/", response_model=Item)
| ^^^^^^^^^^^^^^^^^^^
54 | async def create_item(item: Item) -> Item:
55 | return item
|
help: Remove argument
50 | return item
51 |
52 |
- @router.get("/items/", response_model=Item)
53 + @router.get("/items/")
54 | async def create_item(item: Item) -> Item:
55 | return item
56 |
note: This is an unsafe fix and may change runtime behavior
FAST001 [*] FastAPI route with redundant `response_model` argument
--> FAST001.py:118:23
|
116 | def setup_app(app_arg: FastAPI, non_app: str) -> None:
117 | # Error
118 | @app_arg.get("/", response_model=str)
| ^^^^^^^^^^^^^^^^^^
119 | async def get_root() -> str:
120 | return "Hello World!"
|
help: Remove argument
115 |
116 | def setup_app(app_arg: FastAPI, non_app: str) -> None:
117 | # Error
- @app_arg.get("/", response_model=str)
118 + @app_arg.get("/")
119 | async def get_root() -> str:
120 | return "Hello World!"
121 |
note: This is an unsafe fix and may change runtime behavior

View file

@ -324,26 +324,6 @@ help: Add `name` to function signature
91 |
note: This is an unsafe fix and may change runtime behavior
FAST003 [*] Parameter `thing_id` appears in route path, but not in `single` signature
--> FAST003.py:158:19
|
157 | ### Errors
158 | @app.get("/things/{thing_id}")
| ^^^^^^^^^^
159 | async def single(other: Annotated[str, Depends(something_else)]): ...
160 | @app.get("/things/{thing_id}")
|
help: Add `thing_id` to function signature
156 |
157 | ### Errors
158 | @app.get("/things/{thing_id}")
- async def single(other: Annotated[str, Depends(something_else)]): ...
159 + async def single(other: Annotated[str, Depends(something_else)], thing_id): ...
160 | @app.get("/things/{thing_id}")
161 | async def default(other: str = Depends(something_else)): ...
162 |
note: This is an unsafe fix and may change runtime behavior
FAST003 [*] Parameter `thing_id` appears in route path, but not in `default` signature
--> FAST003.py:160:19
|
@ -364,47 +344,6 @@ help: Add `thing_id` to function signature
164 | ### No errors
note: This is an unsafe fix and may change runtime behavior
FAST003 [*] Parameter `id` appears in route path, but not in `get_id_pydantic_full` signature
--> FAST003.py:197:12
|
196 | # Errors
197 | @app.get("/{id}")
| ^^^^
198 | async def get_id_pydantic_full(
199 | params: Annotated[PydanticParams, Depends(PydanticParams)],
|
help: Add `id` to function signature
196 | # Errors
197 | @app.get("/{id}")
198 | async def get_id_pydantic_full(
- params: Annotated[PydanticParams, Depends(PydanticParams)],
199 + params: Annotated[PydanticParams, Depends(PydanticParams)], id,
200 | ): ...
201 | @app.get("/{id}")
202 | async def get_id_pydantic_short(params: Annotated[PydanticParams, Depends()]): ...
note: This is an unsafe fix and may change runtime behavior
FAST003 [*] Parameter `id` appears in route path, but not in `get_id_pydantic_short` signature
--> FAST003.py:201:12
|
199 | params: Annotated[PydanticParams, Depends(PydanticParams)],
200 | ): ...
201 | @app.get("/{id}")
| ^^^^
202 | async def get_id_pydantic_short(params: Annotated[PydanticParams, Depends()]): ...
203 | @app.get("/{id}")
|
help: Add `id` to function signature
199 | params: Annotated[PydanticParams, Depends(PydanticParams)],
200 | ): ...
201 | @app.get("/{id}")
- async def get_id_pydantic_short(params: Annotated[PydanticParams, Depends()]): ...
202 + async def get_id_pydantic_short(params: Annotated[PydanticParams, Depends()], id): ...
203 | @app.get("/{id}")
204 | async def get_id_init_not_annotated(params = Depends(InitParams)): ...
205 |
note: This is an unsafe fix and may change runtime behavior
FAST003 [*] Parameter `id` appears in route path, but not in `get_id_init_not_annotated` signature
--> FAST003.py:203:12
|

View file

@ -10,12 +10,12 @@ mod tests {
use anyhow::Result;
use test_case::test_case;
use crate::assert_diagnostics;
use crate::registry::Rule;
use crate::rules::flake8_builtins;
use crate::settings::LinterSettings;
use crate::settings::types::PreviewMode;
use crate::test::{test_path, test_resource_path};
use crate::{assert_diagnostics, assert_diagnostics_diff};
use ruff_python_ast::PythonVersion;
#[test_case(Rule::BuiltinVariableShadowing, Path::new("A001.py"))]
@ -64,6 +64,28 @@ mod tests {
Ok(())
}
#[test_case(Rule::BuiltinAttributeShadowing, Path::new("A003.py"))]
fn deferred_annotations_diff(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!(
"deferred_annotations_diff_{}_{}",
rule_code.name(),
path.to_string_lossy()
);
assert_diagnostics_diff!(
snapshot,
Path::new("flake8_builtins").join(path).as_path(),
&LinterSettings {
unresolved_target_version: PythonVersion::PY313.into(),
..LinterSettings::for_rule(rule_code)
},
&LinterSettings {
unresolved_target_version: PythonVersion::PY314.into(),
..LinterSettings::for_rule(rule_code)
},
);
Ok(())
}
#[test_case(Rule::BuiltinAttributeShadowing, Path::new("A003.py"))]
fn preview_rules(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!(

View file

@ -1,22 +1,4 @@
---
source: crates/ruff_linter/src/rules/flake8_builtins/mod.rs
---
A003 Python builtin is shadowed by method `str` from line 14
--> A003.py:17:31
|
15 | pass
16 |
17 | def method_usage(self) -> str:
| ^^^
18 | pass
|
A003 Python builtin is shadowed by class attribute `id` from line 3
--> A003.py:20:34
|
18 | pass
19 |
20 | def attribute_usage(self) -> id:
| ^^
21 | pass
|

View file

@ -1,12 +1,4 @@
---
source: crates/ruff_linter/src/rules/flake8_builtins/mod.rs
---
A003 Python builtin is shadowed by method `str` from line 14
--> A003.py:17:31
|
15 | pass
16 |
17 | def method_usage(self) -> str:
| ^^^
18 | pass
|

View file

@ -0,0 +1,32 @@
---
source: crates/ruff_linter/src/rules/flake8_builtins/mod.rs
---
--- Linter settings ---
-linter.unresolved_target_version = 3.13
+linter.unresolved_target_version = 3.14
--- Summary ---
Removed: 2
Added: 0
--- Removed ---
A003 Python builtin is shadowed by method `str` from line 14
--> A003.py:17:31
|
15 | pass
16 |
17 | def method_usage(self) -> str:
| ^^^
18 | pass
|
A003 Python builtin is shadowed by class attribute `id` from line 3
--> A003.py:20:34
|
18 | pass
19 |
20 | def attribute_usage(self) -> id:
| ^^
21 | pass
|

View file

@ -1,26 +1,6 @@
---
source: crates/ruff_linter/src/rules/flake8_builtins/mod.rs
---
A003 Python builtin is shadowed by method `str` from line 14
--> A003.py:17:31
|
15 | pass
16 |
17 | def method_usage(self) -> str:
| ^^^
18 | pass
|
A003 Python builtin is shadowed by class attribute `id` from line 3
--> A003.py:20:34
|
18 | pass
19 |
20 | def attribute_usage(self) -> id:
| ^^
21 | pass
|
A003 Python builtin is shadowed by method `property` from line 26
--> A003.py:31:7
|

View file

@ -1,6 +1,196 @@
---
source: crates/ruff_linter/src/rules/flake8_pyi/mod.rs
---
PYI055 [*] Multiple `type` members in a union. Combine them into one, e.g., `type[int | str | complex]`.
--> PYI055.py:4:4
|
2 | from typing import Union
3 |
4 | s: builtins.type[int] | builtins.type[str] | builtins.type[complex]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
5 | t: type[int] | type[str] | type[float]
6 | u: builtins.type[int] | type[str] | builtins.type[complex]
|
help: Combine multiple `type` members
1 | import builtins
2 | from typing import Union
3 |
- s: builtins.type[int] | builtins.type[str] | builtins.type[complex]
4 + s: type[int | str | complex]
5 | t: type[int] | type[str] | type[float]
6 | u: builtins.type[int] | type[str] | builtins.type[complex]
7 | v: Union[type[float], type[complex]]
PYI055 [*] Multiple `type` members in a union. Combine them into one, e.g., `type[int | str | float]`.
--> PYI055.py:5:4
|
4 | s: builtins.type[int] | builtins.type[str] | builtins.type[complex]
5 | t: type[int] | type[str] | type[float]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
6 | u: builtins.type[int] | type[str] | builtins.type[complex]
7 | v: Union[type[float], type[complex]]
|
help: Combine multiple `type` members
2 | from typing import Union
3 |
4 | s: builtins.type[int] | builtins.type[str] | builtins.type[complex]
- t: type[int] | type[str] | type[float]
5 + t: type[int | str | float]
6 | u: builtins.type[int] | type[str] | builtins.type[complex]
7 | v: Union[type[float], type[complex]]
8 | w: Union[type[float | int], type[complex]]
PYI055 [*] Multiple `type` members in a union. Combine them into one, e.g., `type[int | str | complex]`.
--> PYI055.py:6:4
|
4 | s: builtins.type[int] | builtins.type[str] | builtins.type[complex]
5 | t: type[int] | type[str] | type[float]
6 | u: builtins.type[int] | type[str] | builtins.type[complex]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
7 | v: Union[type[float], type[complex]]
8 | w: Union[type[float | int], type[complex]]
|
help: Combine multiple `type` members
3 |
4 | s: builtins.type[int] | builtins.type[str] | builtins.type[complex]
5 | t: type[int] | type[str] | type[float]
- u: builtins.type[int] | type[str] | builtins.type[complex]
6 + u: type[int | str | complex]
7 | v: Union[type[float], type[complex]]
8 | w: Union[type[float | int], type[complex]]
9 | x: Union[Union[type[Union[float, int]], type[complex]]]
PYI055 [*] Multiple `type` members in a union. Combine them into one, e.g., `type[Union[float, complex]]`.
--> PYI055.py:7:4
|
5 | t: type[int] | type[str] | type[float]
6 | u: builtins.type[int] | type[str] | builtins.type[complex]
7 | v: Union[type[float], type[complex]]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
8 | w: Union[type[float | int], type[complex]]
9 | x: Union[Union[type[Union[float, int]], type[complex]]]
|
help: Combine multiple `type` members
4 | s: builtins.type[int] | builtins.type[str] | builtins.type[complex]
5 | t: type[int] | type[str] | type[float]
6 | u: builtins.type[int] | type[str] | builtins.type[complex]
- v: Union[type[float], type[complex]]
7 + v: type[Union[float, complex]]
8 | w: Union[type[float | int], type[complex]]
9 | x: Union[Union[type[Union[float, int]], type[complex]]]
10 | y: Union[Union[Union[type[float | int], type[complex]]]]
PYI055 [*] Multiple `type` members in a union. Combine them into one, e.g., `type[Union[float | int, complex]]`.
--> PYI055.py:8:4
|
6 | u: builtins.type[int] | type[str] | builtins.type[complex]
7 | v: Union[type[float], type[complex]]
8 | w: Union[type[float | int], type[complex]]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
9 | x: Union[Union[type[Union[float, int]], type[complex]]]
10 | y: Union[Union[Union[type[float | int], type[complex]]]]
|
help: Combine multiple `type` members
5 | t: type[int] | type[str] | type[float]
6 | u: builtins.type[int] | type[str] | builtins.type[complex]
7 | v: Union[type[float], type[complex]]
- w: Union[type[float | int], type[complex]]
8 + w: type[Union[float | int, complex]]
9 | x: Union[Union[type[Union[float, int]], type[complex]]]
10 | y: Union[Union[Union[type[float | int], type[complex]]]]
11 | z: Union[type[complex], Union[Union[type[Union[float, int]]]]]
PYI055 [*] Multiple `type` members in a union. Combine them into one, e.g., `type[Union[Union[float, int], complex]]`.
--> PYI055.py:9:4
|
7 | v: Union[type[float], type[complex]]
8 | w: Union[type[float | int], type[complex]]
9 | x: Union[Union[type[Union[float, int]], type[complex]]]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
10 | y: Union[Union[Union[type[float | int], type[complex]]]]
11 | z: Union[type[complex], Union[Union[type[Union[float, int]]]]]
|
help: Combine multiple `type` members
6 | u: builtins.type[int] | type[str] | builtins.type[complex]
7 | v: Union[type[float], type[complex]]
8 | w: Union[type[float | int], type[complex]]
- x: Union[Union[type[Union[float, int]], type[complex]]]
9 + x: type[Union[Union[float, int], complex]]
10 | y: Union[Union[Union[type[float | int], type[complex]]]]
11 | z: Union[type[complex], Union[Union[type[Union[float, int]]]]]
12 |
PYI055 [*] Multiple `type` members in a union. Combine them into one, e.g., `type[Union[float | int, complex]]`.
--> PYI055.py:10:4
|
8 | w: Union[type[float | int], type[complex]]
9 | x: Union[Union[type[Union[float, int]], type[complex]]]
10 | y: Union[Union[Union[type[float | int], type[complex]]]]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
11 | z: Union[type[complex], Union[Union[type[Union[float, int]]]]]
|
help: Combine multiple `type` members
7 | v: Union[type[float], type[complex]]
8 | w: Union[type[float | int], type[complex]]
9 | x: Union[Union[type[Union[float, int]], type[complex]]]
- y: Union[Union[Union[type[float | int], type[complex]]]]
10 + y: type[Union[float | int, complex]]
11 | z: Union[type[complex], Union[Union[type[Union[float, int]]]]]
12 |
13 |
PYI055 [*] Multiple `type` members in a union. Combine them into one, e.g., `type[Union[complex, Union[float, int]]]`.
--> PYI055.py:11:4
|
9 | x: Union[Union[type[Union[float, int]], type[complex]]]
10 | y: Union[Union[Union[type[float | int], type[complex]]]]
11 | z: Union[type[complex], Union[Union[type[Union[float, int]]]]]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
help: Combine multiple `type` members
8 | w: Union[type[float | int], type[complex]]
9 | x: Union[Union[type[Union[float, int]], type[complex]]]
10 | y: Union[Union[Union[type[float | int], type[complex]]]]
- z: Union[type[complex], Union[Union[type[Union[float, int]]]]]
11 + z: type[Union[complex, Union[float, int]]]
12 |
13 |
14 | def func(arg: type[int] | str | type[float]) -> None:
PYI055 [*] Multiple `type` members in a union. Combine them into one, e.g., `type[int | float]`.
--> PYI055.py:14:15
|
14 | def func(arg: type[int] | str | type[float]) -> None:
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
15 | ...
|
help: Combine multiple `type` members
11 | z: Union[type[complex], Union[Union[type[Union[float, int]]]]]
12 |
13 |
- def func(arg: type[int] | str | type[float]) -> None:
14 + def func(arg: type[int | float] | str) -> None:
15 | ...
16 |
17 |
PYI055 [*] Multiple `type` members in a union. Combine them into one, e.g., `type[requests_mock.Mocker | httpretty]`.
--> PYI055.py:29:7
|
28 | # OK
29 | item: type[requests_mock.Mocker] | type[httpretty] = requests_mock.Mocker
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
help: Combine multiple `type` members
26 |
27 |
28 | # OK
- item: type[requests_mock.Mocker] | type[httpretty] = requests_mock.Mocker
29 + item: type[requests_mock.Mocker | httpretty] = requests_mock.Mocker
30 |
31 |
32 | def func():
PYI055 [*] Multiple `type` members in a union. Combine them into one, e.g., `type[requests_mock.Mocker | httpretty | str]`.
--> PYI055.py:34:8
|

View file

@ -11,18 +11,17 @@ TC003 [*] Move standard library import `collections.Counter` into a type-checkin
|
help: Move into type-checking block
- from collections import Counter
1 + from __future__ import annotations
2 |
3 | from elsewhere import third_party
4 |
5 | from . import first_party
6 + from typing import TYPE_CHECKING
7 +
8 + if TYPE_CHECKING:
9 + from collections import Counter
1 |
2 | from elsewhere import third_party
3 |
4 | from . import first_party
5 + from typing import TYPE_CHECKING
6 +
7 + if TYPE_CHECKING:
8 + from collections import Counter
9 |
10 |
11 |
12 | def f(x: first_party.foo): ...
11 | def f(x: first_party.foo): ...
note: This is an unsafe fix and may change runtime behavior
TC002 [*] Move third-party import `elsewhere.third_party` into a type-checking block
@ -36,19 +35,18 @@ TC002 [*] Move third-party import `elsewhere.third_party` into a type-checking b
5 | from . import first_party
|
help: Move into type-checking block
1 + from __future__ import annotations
2 | from collections import Counter
3 |
1 | from collections import Counter
2 |
- from elsewhere import third_party
4 |
5 | from . import first_party
6 + from typing import TYPE_CHECKING
7 +
8 + if TYPE_CHECKING:
9 + from elsewhere import third_party
3 |
4 | from . import first_party
5 + from typing import TYPE_CHECKING
6 +
7 + if TYPE_CHECKING:
8 + from elsewhere import third_party
9 |
10 |
11 |
12 | def f(x: first_party.foo): ...
11 | def f(x: first_party.foo): ...
note: This is an unsafe fix and may change runtime behavior
TC001 [*] Move application import `.first_party` into a type-checking block
@ -60,17 +58,15 @@ TC001 [*] Move application import `.first_party` into a type-checking block
| ^^^^^^^^^^^
|
help: Move into type-checking block
1 + from __future__ import annotations
2 | from collections import Counter
3 |
4 | from elsewhere import third_party
5 |
2 |
3 | from elsewhere import third_party
4 |
- from . import first_party
6 + from typing import TYPE_CHECKING
7 +
8 + if TYPE_CHECKING:
9 + from . import first_party
5 + from typing import TYPE_CHECKING
6 +
7 + if TYPE_CHECKING:
8 + from . import first_party
9 |
10 |
11 |
12 | def f(x: first_party.foo): ...
11 | def f(x: first_party.foo): ...
note: This is an unsafe fix and may change runtime behavior

View file

@ -12,15 +12,14 @@ TC001 [*] Move application import `.first_party` into a type-checking block
|
help: Move into type-checking block
- def f():
1 + from __future__ import annotations
2 + from typing import TYPE_CHECKING
3 +
4 + if TYPE_CHECKING:
5 | from . import first_party
6 + def f():
7 |
8 | def f(x: first_party.foo): ...
9 |
1 + from typing import TYPE_CHECKING
2 +
3 + if TYPE_CHECKING:
4 | from . import first_party
5 + def f():
6 |
7 | def f(x: first_party.foo): ...
8 |
note: This is an unsafe fix and may change runtime behavior
TC001 [*] Move application import `.foo` into a type-checking block
@ -33,24 +32,19 @@ TC001 [*] Move application import `.foo` into a type-checking block
59 | def f(x: Union[foo.Ty, int]): ...
|
help: Move into type-checking block
1 + from __future__ import annotations
2 | def f():
3 | from . import first_party
4 |
--------------------------------------------------------------------------------
50 |
51 |
52 |
53 | # unions
52 | # unions
- from typing import Union
54 + from typing import Union, TYPE_CHECKING
55 |
56 + if TYPE_CHECKING:
57 + from . import foo
58 +
59 |
60 | def n():
53 + from typing import Union, TYPE_CHECKING
54 +
55 + if TYPE_CHECKING:
56 + from . import foo
57 |
58 |
59 | def n():
- from . import foo
61 |
62 | def f(x: Union[foo.Ty, int]): ...
63 | def g(x: foo.Ty | int): ...
60 |
61 | def f(x: Union[foo.Ty, int]): ...
62 | def g(x: foo.Ty | int): ...
note: This is an unsafe fix and may change runtime behavior

View file

@ -220,32 +220,3 @@ help: Move into type-checking block
52 | x = dict["pd.DataFrame", "pd.DataFrame"]
53 |
note: This is an unsafe fix and may change runtime behavior
TC002 [*] Move third-party import `module.Member` into a type-checking block
--> TC002.py:172:24
|
170 | global Member
171 |
172 | from module import Member
| ^^^^^^
173 |
174 | x: Member = 1
|
help: Move into type-checking block
1 | """Tests to determine accurate detection of typing-only imports."""
2 + from typing import TYPE_CHECKING
3 +
4 + if TYPE_CHECKING:
5 + from module import Member
6 |
7 |
8 | def f():
--------------------------------------------------------------------------------
173 | def f():
174 | global Member
175 |
- from module import Member
176 |
177 | x: Member = 1
178 |
note: This is an unsafe fix and may change runtime behavior

View file

@ -1,26 +1,6 @@
---
source: crates/ruff_linter/src/rules/flake8_type_checking/mod.rs
---
TC004 [*] Quote references to `pandas.DataFrame`. Import is in a type-checking block.
--> quote.py:57:28
|
56 | if TYPE_CHECKING:
57 | from pandas import DataFrame
| ^^^^^^^^^
58 |
59 | def func(value: DataFrame):
|
help: Quote references
56 | if TYPE_CHECKING:
57 | from pandas import DataFrame
58 |
- def func(value: DataFrame):
59 + def func(value: "DataFrame"):
60 | ...
61 |
62 |
note: This is an unsafe fix and may change runtime behavior
TC004 [*] Move import `pandas.DataFrame` out of type-checking block. Import is used for more than type hinting.
--> quote.py:110:28
|

View file

@ -11,18 +11,15 @@ TC002 [*] Move third-party import `pandas.DataFrame` into a type-checking block
4 | def baz() -> DataFrame:
|
help: Move into type-checking block
- def f():
1 + from typing import TYPE_CHECKING
2 +
3 + if TYPE_CHECKING:
4 | from pandas import DataFrame
5 + def f():
6 |
- def baz() -> DataFrame:
7 + def baz() -> "DataFrame":
8 | ...
9 |
10 |
- def f():
1 + from typing import TYPE_CHECKING
2 +
3 + if TYPE_CHECKING:
4 | from pandas import DataFrame
5 + def f():
6 |
7 | def baz() -> DataFrame:
8 | ...
note: This is an unsafe fix and may change runtime behavior
TC002 [*] Move third-party import `pandas.DataFrame` into a type-checking block
@ -48,11 +45,8 @@ help: Move into type-checking block
12 | def f():
- from pandas import DataFrame
13 |
- def baz() -> DataFrame[int]:
14 + def baz() -> "DataFrame[int]":
14 | def baz() -> DataFrame[int]:
15 | ...
16 |
17 |
note: This is an unsafe fix and may change runtime behavior
TC002 [*] Move third-party import `pandas` into a type-checking block
@ -78,11 +72,8 @@ help: Move into type-checking block
19 | def f():
- import pandas as pd
20 |
- def baz() -> pd.DataFrame:
21 + def baz() -> "pd.DataFrame":
21 | def baz() -> pd.DataFrame:
22 | ...
23 |
24 |
note: This is an unsafe fix and may change runtime behavior
TC002 [*] Move third-party import `pandas` into a type-checking block
@ -108,11 +99,8 @@ help: Move into type-checking block
26 | def f():
- import pandas as pd
27 |
- def baz() -> pd.DataFrame.Extra:
28 + def baz() -> "pd.DataFrame.Extra":
28 | def baz() -> pd.DataFrame.Extra:
29 | ...
30 |
31 |
note: This is an unsafe fix and may change runtime behavior
TC002 [*] Move third-party import `pandas` into a type-checking block
@ -138,11 +126,8 @@ help: Move into type-checking block
33 | def f():
- import pandas as pd
34 |
- def baz() -> pd.DataFrame | int:
35 + def baz() -> "pd.DataFrame | int":
35 | def baz() -> pd.DataFrame | int:
36 | ...
37 |
38 |
note: This is an unsafe fix and may change runtime behavior
TC002 [*] Move third-party import `pandas.DataFrame` into a type-checking block
@ -168,11 +153,8 @@ help: Move into type-checking block
41 | def f():
- from pandas import DataFrame
42 |
- def baz() -> DataFrame():
43 + def baz() -> "DataFrame()":
43 | def baz() -> DataFrame():
44 | ...
45 |
46 |
note: This is an unsafe fix and may change runtime behavior
TC002 [*] Move third-party import `pandas.DataFrame` into a type-checking block
@ -199,11 +181,8 @@ help: Move into type-checking block
50 |
- from pandas import DataFrame
51 |
- def baz() -> DataFrame[Literal["int"]]:
52 + def baz() -> "DataFrame[Literal['int']]":
52 | def baz() -> DataFrame[Literal["int"]]:
53 | ...
54 |
55 |
note: This is an unsafe fix and may change runtime behavior
TC002 [*] Move third-party import `pandas.DataFrame` into a type-checking block
@ -229,11 +208,8 @@ help: Move into type-checking block
67 | def f():
- from pandas import DataFrame, Series
68 |
- def baz() -> DataFrame | Series:
69 + def baz() -> "DataFrame | Series":
69 | def baz() -> DataFrame | Series:
70 | ...
71 |
72 |
note: This is an unsafe fix and may change runtime behavior
TC002 [*] Move third-party import `pandas.Series` into a type-checking block
@ -259,11 +235,8 @@ help: Move into type-checking block
67 | def f():
- from pandas import DataFrame, Series
68 |
- def baz() -> DataFrame | Series:
69 + def baz() -> "DataFrame | Series":
69 | def baz() -> DataFrame | Series:
70 | ...
71 |
72 |
note: This is an unsafe fix and may change runtime behavior
TC002 [*] Move third-party import `pandas.DataFrame` into a type-checking block
@ -290,23 +263,7 @@ help: Move into type-checking block
- from pandas import DataFrame, Series
75 |
76 | def baz() -> (
- DataFrame |
- Series
77 + "DataFrame | Series"
78 | ):
79 | ...
80 |
81 | class C:
- x: DataFrame[
- int
- ] = 1
82 + x: "DataFrame[int]" = 1
83 |
- def func() -> DataFrame[[DataFrame[_P, _R]], DataFrame[_P, _R]]:
84 + def func() -> "DataFrame[[DataFrame[_P, _R]], DataFrame[_P, _R]]":
85 | ...
86 |
87 |
77 | DataFrame |
note: This is an unsafe fix and may change runtime behavior
TC002 [*] Move third-party import `pandas.Series` into a type-checking block
@ -333,23 +290,7 @@ help: Move into type-checking block
- from pandas import DataFrame, Series
75 |
76 | def baz() -> (
- DataFrame |
- Series
77 + "DataFrame | Series"
78 | ):
79 | ...
80 |
81 | class C:
- x: DataFrame[
- int
- ] = 1
82 + x: "DataFrame[int]" = 1
83 |
- def func() -> DataFrame[[DataFrame[_P, _R]], DataFrame[_P, _R]]:
84 + def func() -> "DataFrame[[DataFrame[_P, _R]], DataFrame[_P, _R]]":
85 | ...
86 |
87 |
77 | DataFrame |
note: This is an unsafe fix and may change runtime behavior
TC002 [*] Move third-party import `pandas.DataFrame` into a type-checking block
@ -375,11 +316,8 @@ help: Move into type-checking block
92 | def f():
- from pandas import DataFrame, Series
93 |
- def func(self) -> DataFrame | list[Series]:
94 + def func(self) -> "DataFrame | list[Series]":
94 | def func(self) -> DataFrame | list[Series]:
95 | pass
96 |
97 |
note: This is an unsafe fix and may change runtime behavior
TC002 [*] Move third-party import `pandas.Series` into a type-checking block
@ -405,9 +343,6 @@ help: Move into type-checking block
92 | def f():
- from pandas import DataFrame, Series
93 |
- def func(self) -> DataFrame | list[Series]:
94 + def func(self) -> "DataFrame | list[Series]":
94 | def func(self) -> DataFrame | list[Series]:
95 | pass
96 |
97 |
note: This is an unsafe fix and may change runtime behavior

View file

@ -11,18 +11,15 @@ TC002 [*] Move third-party import `django.contrib.auth.models.AbstractBaseUser`
4 | def test_remove_inner_quotes_double(self, user: AbstractBaseUser["int"]):
|
help: Move into type-checking block
- def f():
1 + from typing import TYPE_CHECKING
2 +
3 + if TYPE_CHECKING:
4 | from django.contrib.auth.models import AbstractBaseUser
5 + def f():
6 |
- def test_remove_inner_quotes_double(self, user: AbstractBaseUser["int"]):
7 + def test_remove_inner_quotes_double(self, user: "AbstractBaseUser[int]"):
8 | pass
9 |
10 |
- def f():
1 + from typing import TYPE_CHECKING
2 +
3 + if TYPE_CHECKING:
4 | from django.contrib.auth.models import AbstractBaseUser
5 + def f():
6 |
7 | def test_remove_inner_quotes_double(self, user: AbstractBaseUser["int"]):
8 | pass
note: This is an unsafe fix and may change runtime behavior
TC002 [*] Move third-party import `django.contrib.auth.models.AbstractBaseUser` into a type-checking block
@ -48,11 +45,8 @@ help: Move into type-checking block
12 | def f():
- from django.contrib.auth.models import AbstractBaseUser
13 |
- def test_remove_inner_quotes_single(self, user: AbstractBaseUser['int']):
14 + def test_remove_inner_quotes_single(self, user: "AbstractBaseUser[int]"):
14 | def test_remove_inner_quotes_single(self, user: AbstractBaseUser['int']):
15 | pass
16 |
17 |
note: This is an unsafe fix and may change runtime behavior
TC002 [*] Move third-party import `django.contrib.auth.models.AbstractBaseUser` into a type-checking block
@ -78,11 +72,8 @@ help: Move into type-checking block
19 | def f():
- from django.contrib.auth.models import AbstractBaseUser
20 |
- def test_remove_inner_quotes_mixed(self, user: AbstractBaseUser['int', "str"]):
21 + def test_remove_inner_quotes_mixed(self, user: "AbstractBaseUser[int, str]"):
21 | def test_remove_inner_quotes_mixed(self, user: AbstractBaseUser['int', "str"]):
22 | pass
23 |
24 |
note: This is an unsafe fix and may change runtime behavior
TC002 [*] Move third-party import `django.contrib.auth.models.AbstractBaseUser` into a type-checking block
@ -109,11 +100,8 @@ help: Move into type-checking block
28 |
- from django.contrib.auth.models import AbstractBaseUser
29 |
- def test_literal_annotation_args_contain_quotes(self, type1: AbstractBaseUser[Literal["user", "admin"], Annotated["int", "1", 2]]):
30 + def test_literal_annotation_args_contain_quotes(self, type1: "AbstractBaseUser[Literal['user', 'admin'], Annotated[int, '1', 2]]"):
30 | def test_literal_annotation_args_contain_quotes(self, type1: AbstractBaseUser[Literal["user", "admin"], Annotated["int", "1", 2]]):
31 | pass
32 |
33 |
note: This is an unsafe fix and may change runtime behavior
TC002 [*] Move third-party import `django.contrib.auth.models.AbstractBaseUser` into a type-checking block
@ -140,11 +128,8 @@ help: Move into type-checking block
37 |
- from django.contrib.auth.models import AbstractBaseUser
38 |
- def test_union_contain_inner_quotes(self, type1: AbstractBaseUser["int" | Literal["int"]]):
39 + def test_union_contain_inner_quotes(self, type1: "AbstractBaseUser[int | Literal['int']]"):
39 | def test_union_contain_inner_quotes(self, type1: AbstractBaseUser["int" | Literal["int"]]):
40 | pass
41 |
42 |
note: This is an unsafe fix and may change runtime behavior
TC002 [*] Move third-party import `django.contrib.auth.models.AbstractBaseUser` into a type-checking block
@ -171,11 +156,8 @@ help: Move into type-checking block
46 |
- from django.contrib.auth.models import AbstractBaseUser
47 |
- def test_inner_literal_mixed_quotes(user: AbstractBaseUser[Literal['user', "admin"]]):
48 + def test_inner_literal_mixed_quotes(user: "AbstractBaseUser[Literal['user', 'admin']]"):
48 | def test_inner_literal_mixed_quotes(user: AbstractBaseUser[Literal['user', "admin"]]):
49 | pass
50 |
51 |
note: This is an unsafe fix and may change runtime behavior
TC002 [*] Move third-party import `django.contrib.auth.models.AbstractBaseUser` into a type-checking block
@ -202,11 +184,8 @@ help: Move into type-checking block
55 |
- from django.contrib.auth.models import AbstractBaseUser
56 |
- def test_inner_literal_single_quote(user: AbstractBaseUser[Literal['int'], str]):
57 + def test_inner_literal_single_quote(user: "AbstractBaseUser[Literal['int'], str]"):
57 | def test_inner_literal_single_quote(user: AbstractBaseUser[Literal['int'], str]):
58 | pass
59 |
60 |
note: This is an unsafe fix and may change runtime behavior
TC002 [*] Move third-party import `django.contrib.auth.models.AbstractBaseUser` into a type-checking block
@ -233,11 +212,8 @@ help: Move into type-checking block
64 |
- from django.contrib.auth.models import AbstractBaseUser
65 |
- def test_mixed_quotes_literal(user: AbstractBaseUser[Literal['user'], "int"]):
66 + def test_mixed_quotes_literal(user: "AbstractBaseUser[Literal['user'], int]"):
66 | def test_mixed_quotes_literal(user: AbstractBaseUser[Literal['user'], "int"]):
67 | pass
68 |
69 |
note: This is an unsafe fix and may change runtime behavior
TC002 [*] Move third-party import `django.contrib.auth.models.AbstractBaseUser` into a type-checking block
@ -264,9 +240,6 @@ help: Move into type-checking block
73 |
- from django.contrib.auth.models import AbstractBaseUser
74 |
- def test_annotated_literal_mixed_quotes(user: AbstractBaseUser[Annotated[str, "max_length=20", Literal['user', "admin"]]]):
75 + def test_annotated_literal_mixed_quotes(user: "AbstractBaseUser[Annotated[str, 'max_length=20', Literal['user', 'admin']]]"):
75 | def test_annotated_literal_mixed_quotes(user: AbstractBaseUser[Annotated[str, "max_length=20", Literal['user', "admin"]]]):
76 | pass
77 |
78 |
note: This is an unsafe fix and may change runtime behavior

View file

@ -21,11 +21,8 @@ help: Move into type-checking block
7 |
- from django.contrib.auth.models import AbstractBaseUser
8 |
- def test_union_literal_mixed_quotes(user: AbstractBaseUser[Union[Literal['active', "inactive"], str]]):
9 + def test_union_literal_mixed_quotes(user: 'AbstractBaseUser[Union[Literal["active", "inactive"], str]]'):
9 | def test_union_literal_mixed_quotes(user: AbstractBaseUser[Union[Literal['active', "inactive"], str]]):
10 | pass
11 |
12 |
note: This is an unsafe fix and may change runtime behavior
TC002 [*] Move third-party import `django.contrib.auth.models.AbstractBaseUser` into a type-checking block
@ -52,11 +49,8 @@ help: Move into type-checking block
16 |
- from django.contrib.auth.models import AbstractBaseUser
17 |
- def test_callable_literal_mixed_quotes(callable_fn: AbstractBaseUser[Callable[["int", Literal['admin', "user"]], 'bool']]):
18 + def test_callable_literal_mixed_quotes(callable_fn: 'AbstractBaseUser[Callable[[int, Literal["admin", "user"]], bool]]'):
18 | def test_callable_literal_mixed_quotes(callable_fn: AbstractBaseUser[Callable[["int", Literal['admin', "user"]], 'bool']]):
19 | pass
20 |
21 |
note: This is an unsafe fix and may change runtime behavior
TC002 [*] Move third-party import `django.contrib.auth.models.AbstractBaseUser` into a type-checking block
@ -83,11 +77,8 @@ help: Move into type-checking block
25 |
- from django.contrib.auth.models import AbstractBaseUser
26 |
- def test_callable_annotated_literal(callable_fn: AbstractBaseUser[Callable[[int, Annotated[str, Literal['active', "inactive"]]], bool]]):
27 + def test_callable_annotated_literal(callable_fn: 'AbstractBaseUser[Callable[[int, Annotated[str, Literal["active", "inactive"]]], bool]]'):
27 | def test_callable_annotated_literal(callable_fn: AbstractBaseUser[Callable[[int, Annotated[str, Literal['active', "inactive"]]], bool]]):
28 | pass
29 |
30 |
note: This is an unsafe fix and may change runtime behavior
TC002 [*] Move third-party import `django.contrib.auth.models` into a type-checking block
@ -114,11 +105,8 @@ help: Move into type-checking block
34 |
- from django.contrib.auth import models
35 |
- def test_attribute(arg: models.AbstractBaseUser["int"]):
36 + def test_attribute(arg: 'models.AbstractBaseUser[int]'):
36 | def test_attribute(arg: models.AbstractBaseUser["int"]):
37 | pass
38 |
39 |
note: This is an unsafe fix and may change runtime behavior
TC002 [*] Move third-party import `django.contrib.auth.models` into a type-checking block
@ -145,11 +133,8 @@ help: Move into type-checking block
43 |
- from django.contrib.auth import models
44 |
- def test_attribute_typing_literal(arg: models.AbstractBaseUser[Literal["admin"]]):
45 + def test_attribute_typing_literal(arg: 'models.AbstractBaseUser[Literal["admin"]]'):
45 | def test_attribute_typing_literal(arg: models.AbstractBaseUser[Literal["admin"]]):
46 | pass
47 |
48 |
note: This is an unsafe fix and may change runtime behavior
TC002 [*] Move third-party import `third_party.Type` into a type-checking block
@ -176,11 +161,8 @@ help: Move into type-checking block
62 | from typing import Literal
- from third_party import Type
63 |
- def test_string_contains_opposite_quote(self, type1: Type[Literal["'"]], type2: Type[Literal["\'"]]):
64 + def test_string_contains_opposite_quote(self, type1: 'Type[Literal["\'"]]', type2: 'Type[Literal["\'"]]'):
64 | def test_string_contains_opposite_quote(self, type1: Type[Literal["'"]], type2: Type[Literal["\'"]]):
65 | pass
66 |
67 |
note: This is an unsafe fix and may change runtime behavior
TC002 [*] Move third-party import `third_party.Type` into a type-checking block
@ -207,7 +189,6 @@ help: Move into type-checking block
70 | from typing import Literal
- from third_party import Type
71 |
- def test_quote_contains_backslash(self, type1: Type[Literal["\n"]], type2: Type[Literal["\""]]):
72 + def test_quote_contains_backslash(self, type1: 'Type[Literal["\\n"]]', type2: 'Type[Literal[\'"\']]'):
72 | def test_quote_contains_backslash(self, type1: Type[Literal["\n"]], type2: Type[Literal["\""]]):
73 | pass
note: This is an unsafe fix and may change runtime behavior

View file

@ -1,21 +1,4 @@
---
source: crates/ruff_linter/src/rules/flake8_type_checking/mod.rs
---
TC004 [*] Move import `typing.Any` out of type-checking block. Import is used for more than type hinting.
--> TC004_4.py:4:24
|
3 | if TYPE_CHECKING:
4 | from typing import Any
| ^^^
|
help: Move out of type-checking block
1 | from typing import TYPE_CHECKING, Type
2 + from typing import Any
3 |
4 | if TYPE_CHECKING:
- from typing import Any
5 + pass
6 |
7 |
8 | def example(*args: Any, **kwargs: Any):
note: This is an unsafe fix and may change runtime behavior

View file

@ -1,59 +1,4 @@
---
source: crates/ruff_linter/src/rules/flake8_type_checking/mod.rs
---
TC004 [*] Move import `typing.List` out of type-checking block. Import is used for more than type hinting.
--> TC004_5.py:4:24
|
3 | if TYPE_CHECKING:
4 | from typing import List, Sequence, Set
| ^^^^
|
help: Move out of type-checking block
1 | from typing import TYPE_CHECKING
2 + from typing import List, Sequence, Set
3 |
4 | if TYPE_CHECKING:
- from typing import List, Sequence, Set
5 + pass
6 |
7 |
8 | def example(a: List[int], /, b: Sequence[int], *, c: Set[int]):
note: This is an unsafe fix and may change runtime behavior
TC004 [*] Move import `typing.Sequence` out of type-checking block. Import is used for more than type hinting.
--> TC004_5.py:4:30
|
3 | if TYPE_CHECKING:
4 | from typing import List, Sequence, Set
| ^^^^^^^^
|
help: Move out of type-checking block
1 | from typing import TYPE_CHECKING
2 + from typing import List, Sequence, Set
3 |
4 | if TYPE_CHECKING:
- from typing import List, Sequence, Set
5 + pass
6 |
7 |
8 | def example(a: List[int], /, b: Sequence[int], *, c: Set[int]):
note: This is an unsafe fix and may change runtime behavior
TC004 [*] Move import `typing.Set` out of type-checking block. Import is used for more than type hinting.
--> TC004_5.py:4:40
|
3 | if TYPE_CHECKING:
4 | from typing import List, Sequence, Set
| ^^^
|
help: Move out of type-checking block
1 | from typing import TYPE_CHECKING
2 + from typing import List, Sequence, Set
3 |
4 | if TYPE_CHECKING:
- from typing import List, Sequence, Set
5 + pass
6 |
7 |
8 | def example(a: List[int], /, b: Sequence[int], *, c: Set[int]):
note: This is an unsafe fix and may change runtime behavior

View file

@ -1,44 +1,4 @@
---
source: crates/ruff_linter/src/rules/flake8_type_checking/mod.rs
---
TC004 [*] Move import `typing.Tuple` out of type-checking block. Import is used for more than type hinting.
--> TC004_9.py:4:24
|
3 | if TYPE_CHECKING:
4 | from typing import Tuple, List, Dict
| ^^^^^
5 |
6 | x: Tuple
|
help: Move out of type-checking block
1 | from typing import TYPE_CHECKING
2 + from typing import Tuple, List
3 |
4 | if TYPE_CHECKING:
- from typing import Tuple, List, Dict
5 + from typing import Dict
6 |
7 | x: Tuple
8 |
note: This is an unsafe fix and may change runtime behavior
TC004 [*] Move import `typing.List` out of type-checking block. Import is used for more than type hinting.
--> TC004_9.py:4:31
|
3 | if TYPE_CHECKING:
4 | from typing import Tuple, List, Dict
| ^^^^
5 |
6 | x: Tuple
|
help: Move out of type-checking block
1 | from typing import TYPE_CHECKING
2 + from typing import Tuple, List
3 |
4 | if TYPE_CHECKING:
- from typing import Tuple, List, Dict
5 + from typing import Dict
6 |
7 | x: Tuple
8 |
note: This is an unsafe fix and may change runtime behavior

View file

@ -1,31 +1,6 @@
---
source: crates/ruff_linter/src/rules/flake8_type_checking/mod.rs
---
TC004 [*] Move import `pandas.DataFrame` out of type-checking block. Import is used for more than type hinting.
--> quote.py:57:28
|
56 | if TYPE_CHECKING:
57 | from pandas import DataFrame
| ^^^^^^^^^
58 |
59 | def func(value: DataFrame):
|
help: Move out of type-checking block
1 + from pandas import DataFrame
2 | def f():
3 | from pandas import DataFrame
4 |
--------------------------------------------------------------------------------
55 | from typing import TYPE_CHECKING
56 |
57 | if TYPE_CHECKING:
- from pandas import DataFrame
58 + pass
59 |
60 | def func(value: DataFrame):
61 | ...
note: This is an unsafe fix and may change runtime behavior
TC004 [*] Move import `pandas.DataFrame` out of type-checking block. Import is used for more than type hinting.
--> quote.py:110:28
|

View file

@ -1,39 +1,6 @@
---
source: crates/ruff_linter/src/rules/flake8_type_checking/mod.rs
---
TC010 Invalid string member in `X | Y`-style union type
--> TC010_2.py:4:4
|
4 | x: "int" | str # TC010
| ^^^^^
5 | x: ("int" | str) | "bool" # TC010
|
TC010 Invalid string member in `X | Y`-style union type
--> TC010_2.py:5:5
|
4 | x: "int" | str # TC010
5 | x: ("int" | str) | "bool" # TC010
| ^^^^^
|
TC010 Invalid string member in `X | Y`-style union type
--> TC010_2.py:5:20
|
4 | x: "int" | str # TC010
5 | x: ("int" | str) | "bool" # TC010
| ^^^^^^
|
TC010 Invalid string member in `X | Y`-style union type
--> TC010_2.py:12:20
|
12 | z: list[str, str | "int"] = [] # TC010
| ^^^^^
13 |
14 | type A = Value["int" | str] # OK
|
TC010 Invalid string member in `X | Y`-style union type
--> TC010_2.py:16:30
|

View file

@ -220,32 +220,3 @@ help: Move into type-checking block
52 | x = dict["pd.DataFrame", "pd.DataFrame"]
53 |
note: This is an unsafe fix and may change runtime behavior
TC002 [*] Move third-party import `module.Member` into a type-checking block
--> TC002.py:172:24
|
170 | global Member
171 |
172 | from module import Member
| ^^^^^^
173 |
174 | x: Member = 1
|
help: Move into type-checking block
1 | """Tests to determine accurate detection of typing-only imports."""
2 + from typing import TYPE_CHECKING
3 +
4 + if TYPE_CHECKING:
5 + from module import Member
6 |
7 |
8 | def f():
--------------------------------------------------------------------------------
173 | def f():
174 | global Member
175 |
- from module import Member
176 |
177 | x: Member = 1
178 |
note: This is an unsafe fix and may change runtime behavior

View file

@ -1,5 +1,348 @@
---
source: crates/ruff_linter/src/rules/flake8_type_checking/mod.rs
snapshot_kind: text
---
TC002 [*] Move third-party import `pandas.DataFrame` into a type-checking block
--> quote.py:2:24
|
1 | def f():
2 | from pandas import DataFrame
| ^^^^^^^^^
3 |
4 | def baz() -> DataFrame:
|
help: Move into type-checking block
- def f():
1 + from typing import TYPE_CHECKING
2 +
3 + if TYPE_CHECKING:
4 | from pandas import DataFrame
5 + def f():
6 |
7 | def baz() -> DataFrame:
8 | ...
note: This is an unsafe fix and may change runtime behavior
TC002 [*] Move third-party import `pandas.DataFrame` into a type-checking block
--> quote.py:9:24
|
8 | def f():
9 | from pandas import DataFrame
| ^^^^^^^^^
10 |
11 | def baz() -> DataFrame[int]:
|
help: Move into type-checking block
1 + from typing import TYPE_CHECKING
2 +
3 + if TYPE_CHECKING:
4 + from pandas import DataFrame
5 | def f():
6 | from pandas import DataFrame
7 |
--------------------------------------------------------------------------------
10 |
11 |
12 | def f():
- from pandas import DataFrame
13 |
14 | def baz() -> DataFrame[int]:
15 | ...
note: This is an unsafe fix and may change runtime behavior
TC002 [*] Move third-party import `pandas` into a type-checking block
--> quote.py:16:22
|
15 | def f():
16 | import pandas as pd
| ^^
17 |
18 | def baz() -> pd.DataFrame:
|
help: Move into type-checking block
1 + from typing import TYPE_CHECKING
2 +
3 + if TYPE_CHECKING:
4 + import pandas as pd
5 | def f():
6 | from pandas import DataFrame
7 |
--------------------------------------------------------------------------------
17 |
18 |
19 | def f():
- import pandas as pd
20 |
21 | def baz() -> pd.DataFrame:
22 | ...
note: This is an unsafe fix and may change runtime behavior
TC002 [*] Move third-party import `pandas` into a type-checking block
--> quote.py:23:22
|
22 | def f():
23 | import pandas as pd
| ^^
24 |
25 | def baz() -> pd.DataFrame.Extra:
|
help: Move into type-checking block
1 + from typing import TYPE_CHECKING
2 +
3 + if TYPE_CHECKING:
4 + import pandas as pd
5 | def f():
6 | from pandas import DataFrame
7 |
--------------------------------------------------------------------------------
24 |
25 |
26 | def f():
- import pandas as pd
27 |
28 | def baz() -> pd.DataFrame.Extra:
29 | ...
note: This is an unsafe fix and may change runtime behavior
TC002 [*] Move third-party import `pandas` into a type-checking block
--> quote.py:30:22
|
29 | def f():
30 | import pandas as pd
| ^^
31 |
32 | def baz() -> pd.DataFrame | int:
|
help: Move into type-checking block
1 + from typing import TYPE_CHECKING
2 +
3 + if TYPE_CHECKING:
4 + import pandas as pd
5 | def f():
6 | from pandas import DataFrame
7 |
--------------------------------------------------------------------------------
31 |
32 |
33 | def f():
- import pandas as pd
34 |
35 | def baz() -> pd.DataFrame | int:
36 | ...
note: This is an unsafe fix and may change runtime behavior
TC002 [*] Move third-party import `pandas.DataFrame` into a type-checking block
--> quote.py:38:24
|
37 | def f():
38 | from pandas import DataFrame
| ^^^^^^^^^
39 |
40 | def baz() -> DataFrame():
|
help: Move into type-checking block
1 + from typing import TYPE_CHECKING
2 +
3 + if TYPE_CHECKING:
4 + from pandas import DataFrame
5 | def f():
6 | from pandas import DataFrame
7 |
--------------------------------------------------------------------------------
39 |
40 |
41 | def f():
- from pandas import DataFrame
42 |
43 | def baz() -> DataFrame():
44 | ...
note: This is an unsafe fix and may change runtime behavior
TC002 [*] Move third-party import `pandas.DataFrame` into a type-checking block
--> quote.py:47:24
|
45 | from typing import Literal
46 |
47 | from pandas import DataFrame
| ^^^^^^^^^
48 |
49 | def baz() -> DataFrame[Literal["int"]]:
|
help: Move into type-checking block
1 + from typing import TYPE_CHECKING
2 +
3 + if TYPE_CHECKING:
4 + from pandas import DataFrame
5 | def f():
6 | from pandas import DataFrame
7 |
--------------------------------------------------------------------------------
48 | def f():
49 | from typing import Literal
50 |
- from pandas import DataFrame
51 |
52 | def baz() -> DataFrame[Literal["int"]]:
53 | ...
note: This is an unsafe fix and may change runtime behavior
TC002 [*] Move third-party import `pandas.DataFrame` into a type-checking block
--> quote.py:64:24
|
63 | def f():
64 | from pandas import DataFrame, Series
| ^^^^^^^^^
65 |
66 | def baz() -> DataFrame | Series:
|
help: Move into type-checking block
1 + from typing import TYPE_CHECKING
2 +
3 + if TYPE_CHECKING:
4 + from pandas import DataFrame, Series
5 | def f():
6 | from pandas import DataFrame
7 |
--------------------------------------------------------------------------------
65 |
66 |
67 | def f():
- from pandas import DataFrame, Series
68 |
69 | def baz() -> DataFrame | Series:
70 | ...
note: This is an unsafe fix and may change runtime behavior
TC002 [*] Move third-party import `pandas.Series` into a type-checking block
--> quote.py:64:35
|
63 | def f():
64 | from pandas import DataFrame, Series
| ^^^^^^
65 |
66 | def baz() -> DataFrame | Series:
|
help: Move into type-checking block
1 + from typing import TYPE_CHECKING
2 +
3 + if TYPE_CHECKING:
4 + from pandas import DataFrame, Series
5 | def f():
6 | from pandas import DataFrame
7 |
--------------------------------------------------------------------------------
65 |
66 |
67 | def f():
- from pandas import DataFrame, Series
68 |
69 | def baz() -> DataFrame | Series:
70 | ...
note: This is an unsafe fix and may change runtime behavior
TC002 [*] Move third-party import `pandas.DataFrame` into a type-checking block
--> quote.py:71:24
|
70 | def f():
71 | from pandas import DataFrame, Series
| ^^^^^^^^^
72 |
73 | def baz() -> (
|
help: Move into type-checking block
1 + from typing import TYPE_CHECKING
2 +
3 + if TYPE_CHECKING:
4 + from pandas import DataFrame, Series
5 | def f():
6 | from pandas import DataFrame
7 |
--------------------------------------------------------------------------------
72 |
73 |
74 | def f():
- from pandas import DataFrame, Series
75 |
76 | def baz() -> (
77 | DataFrame |
note: This is an unsafe fix and may change runtime behavior
TC002 [*] Move third-party import `pandas.Series` into a type-checking block
--> quote.py:71:35
|
70 | def f():
71 | from pandas import DataFrame, Series
| ^^^^^^
72 |
73 | def baz() -> (
|
help: Move into type-checking block
1 + from typing import TYPE_CHECKING
2 +
3 + if TYPE_CHECKING:
4 + from pandas import DataFrame, Series
5 | def f():
6 | from pandas import DataFrame
7 |
--------------------------------------------------------------------------------
72 |
73 |
74 | def f():
- from pandas import DataFrame, Series
75 |
76 | def baz() -> (
77 | DataFrame |
note: This is an unsafe fix and may change runtime behavior
TC002 [*] Move third-party import `pandas.DataFrame` into a type-checking block
--> quote.py:89:24
|
88 | def f():
89 | from pandas import DataFrame, Series
| ^^^^^^^^^
90 |
91 | def func(self) -> DataFrame | list[Series]:
|
help: Move into type-checking block
1 + from typing import TYPE_CHECKING
2 +
3 + if TYPE_CHECKING:
4 + from pandas import DataFrame, Series
5 | def f():
6 | from pandas import DataFrame
7 |
--------------------------------------------------------------------------------
90 |
91 |
92 | def f():
- from pandas import DataFrame, Series
93 |
94 | def func(self) -> DataFrame | list[Series]:
95 | pass
note: This is an unsafe fix and may change runtime behavior
TC002 [*] Move third-party import `pandas.Series` into a type-checking block
--> quote.py:89:35
|
88 | def f():
89 | from pandas import DataFrame, Series
| ^^^^^^
90 |
91 | def func(self) -> DataFrame | list[Series]:
|
help: Move into type-checking block
1 + from typing import TYPE_CHECKING
2 +
3 + if TYPE_CHECKING:
4 + from pandas import DataFrame, Series
5 | def f():
6 | from pandas import DataFrame
7 |
--------------------------------------------------------------------------------
90 |
91 |
92 | def f():
- from pandas import DataFrame, Series
93 |
94 | def func(self) -> DataFrame | list[Series]:
95 | pass
note: This is an unsafe fix and may change runtime behavior

View file

@ -11,11 +11,11 @@ mod tests {
use ruff_python_ast::PythonVersion;
use test_case::test_case;
use crate::assert_diagnostics;
use crate::registry::Rule;
use crate::settings;
use crate::settings::types::PreviewMode;
use crate::test::test_path;
use crate::{assert_diagnostics, assert_diagnostics_diff};
#[test_case(Path::new("full_name.py"))]
#[test_case(Path::new("import_as.py"))]
@ -82,6 +82,29 @@ mod tests {
Ok(())
}
#[test_case(Rule::InvalidPathlibWithSuffix, Path::new("PTH210.py"))]
#[test_case(Rule::InvalidPathlibWithSuffix, Path::new("PTH210_1.py"))]
fn deferred_annotations_diff(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!(
"deferred_annotations_diff_{}_{}",
rule_code.name(),
path.to_string_lossy()
);
assert_diagnostics_diff!(
snapshot,
Path::new("flake8_use_pathlib").join(path).as_path(),
&settings::LinterSettings {
unresolved_target_version: PythonVersion::PY313.into(),
..settings::LinterSettings::for_rule(rule_code)
},
&settings::LinterSettings {
unresolved_target_version: PythonVersion::PY314.into(),
..settings::LinterSettings::for_rule(rule_code)
},
);
Ok(())
}
#[test_case(Path::new("full_name.py"))]
#[test_case(Path::new("import_as.py"))]
#[test_case(Path::new("import_from_as.py"))]

View file

@ -1,17 +1,6 @@
---
source: crates/ruff_linter/src/rules/flake8_use_pathlib/mod.rs
---
PTH210 Invalid suffix passed to `.with_suffix()`
--> PTH210.py:21:1
|
20 | ### Errors
21 | path.with_suffix(".")
| ^^^^^^^^^^^^^^^^^^^^^
22 | path.with_suffix("py")
23 | path.with_suffix(r"s")
|
help: Remove "." or extend to valid suffix
PTH210 [*] Dotless suffix passed to `.with_suffix()`
--> PTH210.py:22:1
|
@ -95,18 +84,6 @@ help: Add a leading dot
28 | posix_path.with_suffix("py")
note: This is an unsafe fix and may change runtime behavior
PTH210 Invalid suffix passed to `.with_suffix()`
--> PTH210.py:27:1
|
25 | path.with_suffix(suffix="js")
26 |
27 | posix_path.with_suffix(".")
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
28 | posix_path.with_suffix("py")
29 | posix_path.with_suffix(r"s")
|
help: Remove "." or extend to valid suffix
PTH210 [*] Dotless suffix passed to `.with_suffix()`
--> PTH210.py:28:1
|
@ -189,18 +166,6 @@ help: Add a leading dot
34 | pure_path.with_suffix("py")
note: This is an unsafe fix and may change runtime behavior
PTH210 Invalid suffix passed to `.with_suffix()`
--> PTH210.py:33:1
|
31 | posix_path.with_suffix(suffix="js")
32 |
33 | pure_path.with_suffix(".")
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
34 | pure_path.with_suffix("py")
35 | pure_path.with_suffix(r"s")
|
help: Remove "." or extend to valid suffix
PTH210 [*] Dotless suffix passed to `.with_suffix()`
--> PTH210.py:34:1
|
@ -283,18 +248,6 @@ help: Add a leading dot
40 | pure_posix_path.with_suffix("py")
note: This is an unsafe fix and may change runtime behavior
PTH210 Invalid suffix passed to `.with_suffix()`
--> PTH210.py:39:1
|
37 | pure_path.with_suffix(suffix="js")
38 |
39 | pure_posix_path.with_suffix(".")
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
40 | pure_posix_path.with_suffix("py")
41 | pure_posix_path.with_suffix(r"s")
|
help: Remove "." or extend to valid suffix
PTH210 [*] Dotless suffix passed to `.with_suffix()`
--> PTH210.py:40:1
|
@ -377,18 +330,6 @@ help: Add a leading dot
46 | pure_windows_path.with_suffix("py")
note: This is an unsafe fix and may change runtime behavior
PTH210 Invalid suffix passed to `.with_suffix()`
--> PTH210.py:45:1
|
43 | pure_posix_path.with_suffix(suffix="js")
44 |
45 | pure_windows_path.with_suffix(".")
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
46 | pure_windows_path.with_suffix("py")
47 | pure_windows_path.with_suffix(r"s")
|
help: Remove "." or extend to valid suffix
PTH210 [*] Dotless suffix passed to `.with_suffix()`
--> PTH210.py:46:1
|
@ -471,18 +412,6 @@ help: Add a leading dot
52 | windows_path.with_suffix("py")
note: This is an unsafe fix and may change runtime behavior
PTH210 Invalid suffix passed to `.with_suffix()`
--> PTH210.py:51:1
|
49 | pure_windows_path.with_suffix(suffix="js")
50 |
51 | windows_path.with_suffix(".")
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
52 | windows_path.with_suffix("py")
53 | windows_path.with_suffix(r"s")
|
help: Remove "." or extend to valid suffix
PTH210 [*] Dotless suffix passed to `.with_suffix()`
--> PTH210.py:52:1
|
@ -565,18 +494,6 @@ help: Add a leading dot
58 | Path().with_suffix("py")
note: This is an unsafe fix and may change runtime behavior
PTH210 Invalid suffix passed to `.with_suffix()`
--> PTH210.py:57:1
|
55 | windows_path.with_suffix(suffix="js")
56 |
57 | Path().with_suffix(".")
| ^^^^^^^^^^^^^^^^^^^^^^^
58 | Path().with_suffix("py")
59 | PosixPath().with_suffix("py")
|
help: Remove "." or extend to valid suffix
PTH210 [*] Dotless suffix passed to `.with_suffix()`
--> PTH210.py:58:1
|

View file

@ -1,18 +1,6 @@
---
source: crates/ruff_linter/src/rules/flake8_use_pathlib/mod.rs
---
PTH210 Invalid suffix passed to `.with_suffix()`
--> PTH210_1.py:13:5
|
11 | def test_path(p: Path) -> None:
12 | ## Errors
13 | p.with_suffix(".")
| ^^^^^^^^^^^^^^^^^^
14 | p.with_suffix("py")
15 | p.with_suffix(r"s")
|
help: Remove "." or extend to valid suffix
PTH210 [*] Dotless suffix passed to `.with_suffix()`
--> PTH210_1.py:14:5
|
@ -96,18 +84,6 @@ help: Add a leading dot
20 | p.with_suffix()
note: This is an unsafe fix and may change runtime behavior
PTH210 Invalid suffix passed to `.with_suffix()`
--> PTH210_1.py:31:5
|
29 | def test_posix_path(p: PosixPath) -> None:
30 | ## Errors
31 | p.with_suffix(".")
| ^^^^^^^^^^^^^^^^^^
32 | p.with_suffix("py")
33 | p.with_suffix(r"s")
|
help: Remove "." or extend to valid suffix
PTH210 [*] Dotless suffix passed to `.with_suffix()`
--> PTH210_1.py:32:5
|
@ -191,18 +167,6 @@ help: Add a leading dot
38 | p.with_suffix()
note: This is an unsafe fix and may change runtime behavior
PTH210 Invalid suffix passed to `.with_suffix()`
--> PTH210_1.py:49:5
|
47 | def test_pure_path(p: PurePath) -> None:
48 | ## Errors
49 | p.with_suffix(".")
| ^^^^^^^^^^^^^^^^^^
50 | p.with_suffix("py")
51 | p.with_suffix(r"s")
|
help: Remove "." or extend to valid suffix
PTH210 [*] Dotless suffix passed to `.with_suffix()`
--> PTH210_1.py:50:5
|
@ -286,18 +250,6 @@ help: Add a leading dot
56 | p.with_suffix()
note: This is an unsafe fix and may change runtime behavior
PTH210 Invalid suffix passed to `.with_suffix()`
--> PTH210_1.py:67:5
|
65 | def test_pure_posix_path(p: PurePosixPath) -> None:
66 | ## Errors
67 | p.with_suffix(".")
| ^^^^^^^^^^^^^^^^^^
68 | p.with_suffix("py")
69 | p.with_suffix(r"s")
|
help: Remove "." or extend to valid suffix
PTH210 [*] Dotless suffix passed to `.with_suffix()`
--> PTH210_1.py:68:5
|
@ -381,18 +333,6 @@ help: Add a leading dot
74 | p.with_suffix()
note: This is an unsafe fix and may change runtime behavior
PTH210 Invalid suffix passed to `.with_suffix()`
--> PTH210_1.py:85:5
|
83 | def test_pure_windows_path(p: PureWindowsPath) -> None:
84 | ## Errors
85 | p.with_suffix(".")
| ^^^^^^^^^^^^^^^^^^
86 | p.with_suffix("py")
87 | p.with_suffix(r"s")
|
help: Remove "." or extend to valid suffix
PTH210 [*] Dotless suffix passed to `.with_suffix()`
--> PTH210_1.py:86:5
|
@ -476,18 +416,6 @@ help: Add a leading dot
92 | p.with_suffix()
note: This is an unsafe fix and may change runtime behavior
PTH210 Invalid suffix passed to `.with_suffix()`
--> PTH210_1.py:103:5
|
101 | def test_windows_path(p: WindowsPath) -> None:
102 | ## Errors
103 | p.with_suffix(".")
| ^^^^^^^^^^^^^^^^^^
104 | p.with_suffix("py")
105 | p.with_suffix(r"s")
|
help: Remove "." or extend to valid suffix
PTH210 [*] Dotless suffix passed to `.with_suffix()`
--> PTH210_1.py:104:5
|

View file

@ -0,0 +1,100 @@
---
source: crates/ruff_linter/src/rules/flake8_use_pathlib/mod.rs
---
--- Linter settings ---
-linter.unresolved_target_version = 3.13
+linter.unresolved_target_version = 3.14
--- Summary ---
Removed: 7
Added: 0
--- Removed ---
PTH210 Invalid suffix passed to `.with_suffix()`
--> PTH210.py:21:1
|
20 | ### Errors
21 | path.with_suffix(".")
| ^^^^^^^^^^^^^^^^^^^^^
22 | path.with_suffix("py")
23 | path.with_suffix(r"s")
|
help: Remove "." or extend to valid suffix
PTH210 Invalid suffix passed to `.with_suffix()`
--> PTH210.py:27:1
|
25 | path.with_suffix(suffix="js")
26 |
27 | posix_path.with_suffix(".")
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
28 | posix_path.with_suffix("py")
29 | posix_path.with_suffix(r"s")
|
help: Remove "." or extend to valid suffix
PTH210 Invalid suffix passed to `.with_suffix()`
--> PTH210.py:33:1
|
31 | posix_path.with_suffix(suffix="js")
32 |
33 | pure_path.with_suffix(".")
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
34 | pure_path.with_suffix("py")
35 | pure_path.with_suffix(r"s")
|
help: Remove "." or extend to valid suffix
PTH210 Invalid suffix passed to `.with_suffix()`
--> PTH210.py:39:1
|
37 | pure_path.with_suffix(suffix="js")
38 |
39 | pure_posix_path.with_suffix(".")
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
40 | pure_posix_path.with_suffix("py")
41 | pure_posix_path.with_suffix(r"s")
|
help: Remove "." or extend to valid suffix
PTH210 Invalid suffix passed to `.with_suffix()`
--> PTH210.py:45:1
|
43 | pure_posix_path.with_suffix(suffix="js")
44 |
45 | pure_windows_path.with_suffix(".")
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
46 | pure_windows_path.with_suffix("py")
47 | pure_windows_path.with_suffix(r"s")
|
help: Remove "." or extend to valid suffix
PTH210 Invalid suffix passed to `.with_suffix()`
--> PTH210.py:51:1
|
49 | pure_windows_path.with_suffix(suffix="js")
50 |
51 | windows_path.with_suffix(".")
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
52 | windows_path.with_suffix("py")
53 | windows_path.with_suffix(r"s")
|
help: Remove "." or extend to valid suffix
PTH210 Invalid suffix passed to `.with_suffix()`
--> PTH210.py:57:1
|
55 | windows_path.with_suffix(suffix="js")
56 |
57 | Path().with_suffix(".")
| ^^^^^^^^^^^^^^^^^^^^^^^
58 | Path().with_suffix("py")
59 | PosixPath().with_suffix("py")
|
help: Remove "." or extend to valid suffix

View file

@ -0,0 +1,88 @@
---
source: crates/ruff_linter/src/rules/flake8_use_pathlib/mod.rs
---
--- Linter settings ---
-linter.unresolved_target_version = 3.13
+linter.unresolved_target_version = 3.14
--- Summary ---
Removed: 6
Added: 0
--- Removed ---
PTH210 Invalid suffix passed to `.with_suffix()`
--> PTH210_1.py:13:5
|
11 | def test_path(p: Path) -> None:
12 | ## Errors
13 | p.with_suffix(".")
| ^^^^^^^^^^^^^^^^^^
14 | p.with_suffix("py")
15 | p.with_suffix(r"s")
|
help: Remove "." or extend to valid suffix
PTH210 Invalid suffix passed to `.with_suffix()`
--> PTH210_1.py:31:5
|
29 | def test_posix_path(p: PosixPath) -> None:
30 | ## Errors
31 | p.with_suffix(".")
| ^^^^^^^^^^^^^^^^^^
32 | p.with_suffix("py")
33 | p.with_suffix(r"s")
|
help: Remove "." or extend to valid suffix
PTH210 Invalid suffix passed to `.with_suffix()`
--> PTH210_1.py:49:5
|
47 | def test_pure_path(p: PurePath) -> None:
48 | ## Errors
49 | p.with_suffix(".")
| ^^^^^^^^^^^^^^^^^^
50 | p.with_suffix("py")
51 | p.with_suffix(r"s")
|
help: Remove "." or extend to valid suffix
PTH210 Invalid suffix passed to `.with_suffix()`
--> PTH210_1.py:67:5
|
65 | def test_pure_posix_path(p: PurePosixPath) -> None:
66 | ## Errors
67 | p.with_suffix(".")
| ^^^^^^^^^^^^^^^^^^
68 | p.with_suffix("py")
69 | p.with_suffix(r"s")
|
help: Remove "." or extend to valid suffix
PTH210 Invalid suffix passed to `.with_suffix()`
--> PTH210_1.py:85:5
|
83 | def test_pure_windows_path(p: PureWindowsPath) -> None:
84 | ## Errors
85 | p.with_suffix(".")
| ^^^^^^^^^^^^^^^^^^
86 | p.with_suffix("py")
87 | p.with_suffix(r"s")
|
help: Remove "." or extend to valid suffix
PTH210 Invalid suffix passed to `.with_suffix()`
--> PTH210_1.py:103:5
|
101 | def test_windows_path(p: WindowsPath) -> None:
102 | ## Errors
103 | p.with_suffix(".")
| ^^^^^^^^^^^^^^^^^^
104 | p.with_suffix("py")
105 | p.with_suffix(r"s")
|
help: Remove "." or extend to valid suffix

View file

@ -3769,7 +3769,7 @@ lambda: fu
def f(a: A) -> A: pass
class A: pass
",
&[Rule::UndefinedName, Rule::UndefinedName],
&[],
);
flakes(
r"
@ -3783,7 +3783,7 @@ lambda: fu
a: A
class A: pass
",
&[Rule::UndefinedName],
&[],
);
flakes(
r"

View file

@ -1,5 +1,11 @@
---
source: crates/ruff_linter/src/rules/pyflakes/mod.rs
snapshot_kind: text
---
F821 Undefined name `y`
--> F821_18.py:19:7
|
17 | # This is no longer allowed on Python 3.14+
18 | x: (y := 1)
19 | print(y)
| ^
|

View file

@ -1,12 +1,4 @@
---
source: crates/ruff_linter/src/rules/pyflakes/mod.rs
---
F821 Undefined name `Model`
--> F821_2.py:5:13
|
4 | # F821 Undefined name `Model`
5 | x: Literal["Model"]
| ^^^^^
6 |
7 | from typing_extensions import Literal
|

View file

@ -21,48 +21,6 @@ F821 Undefined name `C`
12 | CStr2: TypeAlias = Union["C", str] # always okay
|
F821 Undefined name `C`
--> F821_26.py:16:12
|
14 | # References to a class from inside the class:
15 | class C:
16 | other: C = ... # valid in a `.pyi` stub file, not in a `.py` runtime file
| ^
17 | other2: "C" = ... # always okay
18 | def from_str(self, s: str) -> C: ... # valid in a `.pyi` stub file, not in a `.py` runtime file
|
F821 Undefined name `C`
--> F821_26.py:18:35
|
16 | other: C = ... # valid in a `.pyi` stub file, not in a `.py` runtime file
17 | other2: "C" = ... # always okay
18 | def from_str(self, s: str) -> C: ... # valid in a `.pyi` stub file, not in a `.py` runtime file
| ^
19 | def from_str2(self, s: str) -> "C": ... # always okay
|
F821 Undefined name `B`
--> F821_26.py:23:10
|
21 | # Circular references:
22 | class A:
23 | foo: B # valid in a `.pyi` stub file, not in a `.py` runtime file
| ^
24 | foo2: "B" # always okay
25 | bar: dict[str, B] # valid in a `.pyi` stub file, not in a `.py` runtime file
|
F821 Undefined name `B`
--> F821_26.py:25:20
|
23 | foo: B # valid in a `.pyi` stub file, not in a `.py` runtime file
24 | foo2: "B" # always okay
25 | bar: dict[str, B] # valid in a `.pyi` stub file, not in a `.py` runtime file
| ^
26 | bar2: dict[str, "A"] # always okay
|
F821 Undefined name `Tree`
--> F821_26.py:33:17
|

View file

@ -1,7 +1,7 @@
---
source: crates/ruff_linter/src/rules/pyupgrade/mod.rs
---
UP006 Use `collections.defaultdict` instead of `typing.DefaultDict` for type annotation
UP006 [*] Use `collections.defaultdict` instead of `typing.DefaultDict` for type annotation
--> UP006_2.py:7:10
|
7 | def f(x: typing.DefaultDict[str, str]) -> None:
@ -9,3 +9,9 @@ UP006 Use `collections.defaultdict` instead of `typing.DefaultDict` for type ann
8 | ...
|
help: Replace with `collections.defaultdict`
4 | from collections import defaultdict
5 |
6 |
- def f(x: typing.DefaultDict[str, str]) -> None:
7 + def f(x: defaultdict[str, str]) -> None:
8 | ...

View file

@ -171,6 +171,40 @@ help: Convert to `X | Y`
35 |
36 |
UP007 [*] Use `X | Y` for type annotations
--> UP007.py:37:10
|
37 | def f(x: Union["str", int]) -> None:
| ^^^^^^^^^^^^^^^^^
38 | ...
|
help: Convert to `X | Y`
34 | ...
35 |
36 |
- def f(x: Union["str", int]) -> None:
37 + def f(x: "str" | int) -> None:
38 | ...
39 |
40 |
UP007 [*] Use `X | Y` for type annotations
--> UP007.py:41:10
|
41 | def f(x: Union[("str", "int"), float]) -> None:
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
42 | ...
|
help: Convert to `X | Y`
38 | ...
39 |
40 |
- def f(x: Union[("str", "int"), float]) -> None:
41 + def f(x: "str" | "int" | float) -> None:
42 | ...
43 |
44 |
UP007 Use `X | Y` for type annotations
--> UP007.py:46:9
|

View file

@ -685,6 +685,46 @@ help: Remove outdated version block
184 | print("py3")
note: This is an unsafe fix and may change runtime behavior
UP036 [*] Version block is outdated for minimum Python version
--> UP036_0.py:185:4
|
183 | print("py3")
184 |
185 | if sys.version_info <= (3,13):
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
186 | print("py3")
|
help: Remove outdated version block
182 | if sys.version_info < (3,13):
183 | print("py3")
184 |
- if sys.version_info <= (3,13):
- print("py3")
185 |
186 | if sys.version_info <= (3,13):
187 | print("py3")
note: This is an unsafe fix and may change runtime behavior
UP036 [*] Version block is outdated for minimum Python version
--> UP036_0.py:188:4
|
186 | print("py3")
187 |
188 | if sys.version_info <= (3,13):
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
189 | print("py3")
|
help: Remove outdated version block
185 | if sys.version_info <= (3,13):
186 | print("py3")
187 |
- if sys.version_info <= (3,13):
- print("py3")
188 |
189 | if sys.version_info == 10000000:
190 | print("py3")
note: This is an unsafe fix and may change runtime behavior
UP036 Version specifier is invalid
--> UP036_0.py:191:24
|
@ -715,6 +755,27 @@ UP036 Version specifier is invalid
198 | print("py3")
|
UP036 [*] Version block is outdated for minimum Python version
--> UP036_0.py:200:4
|
198 | print("py3")
199 |
200 | if sys.version_info > (3,13):
| ^^^^^^^^^^^^^^^^^^^^^^^^^
201 | print("py3")
|
help: Remove outdated version block
197 | if sys.version_info <= (3,10000000):
198 | print("py3")
199 |
- if sys.version_info > (3,13):
- print("py3")
200 + print("py3")
201 |
202 | if sys.version_info >= (3,13):
203 | print("py3")
note: This is an unsafe fix and may change runtime behavior
UP036 [*] Version block is outdated for minimum Python version
--> UP036_0.py:203:4
|

View file

@ -159,6 +159,66 @@ UP036 Version specifier is invalid
49 | print()
|
UP036 [*] Version block is outdated for minimum Python version
--> UP036_5.py:60:4
|
58 | print()
59 |
60 | if sys.version_info <= (3, 13, 0):
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
61 | print()
|
help: Remove outdated version block
57 | if sys.version_info <= (3, 13, 'final'):
58 | print()
59 |
- if sys.version_info <= (3, 13, 0):
- print()
60 |
61 | if sys.version_info < (3, 13, 37):
62 | print()
note: This is an unsafe fix and may change runtime behavior
UP036 [*] Version block is outdated for minimum Python version
--> UP036_5.py:63:4
|
61 | print()
62 |
63 | if sys.version_info < (3, 13, 37):
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
64 | print()
|
help: Remove outdated version block
60 | if sys.version_info <= (3, 13, 0):
61 | print()
62 |
- if sys.version_info < (3, 13, 37):
- print()
63 |
64 | if sys.version_info <= (3, 13, 37):
65 | print()
note: This is an unsafe fix and may change runtime behavior
UP036 [*] Version block is outdated for minimum Python version
--> UP036_5.py:66:4
|
64 | print()
65 |
66 | if sys.version_info <= (3, 13, 37):
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
67 | print()
|
help: Remove outdated version block
63 | if sys.version_info < (3, 13, 37):
64 | print()
65 |
- if sys.version_info <= (3, 13, 37):
- print()
66 |
67 | if sys.version_info <= (3, 14, 0):
68 | print()
note: This is an unsafe fix and may change runtime behavior
UP036 [*] Version block is outdated for minimum Python version
--> UP036_5.py:77:4
|
@ -278,3 +338,27 @@ help: Remove outdated version block
99 | # Semantically incorrect, skip fixing
100 |
note: This is an unsafe fix and may change runtime behavior
UP036 [*] Version block is outdated for minimum Python version
--> UP036_5.py:109:4
|
107 | print(2)
108 |
109 | if sys.version_info.major > (3, 13):
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
110 | print(3)
111 | else:
|
help: Remove outdated version block
106 | else:
107 | print(2)
108 |
- if sys.version_info.major > (3, 13):
- print(3)
- else:
- print(2)
109 + print(3)
110 |
111 | if sys.version_info.major[:2] > (3, 13):
112 | print(3)
note: This is an unsafe fix and may change runtime behavior

View file

@ -18,4 +18,18 @@ help: Remove quotes
9 + x: Tuple[int, int] = (0, 0)
10 | print(x)
11 |
12 |
12 |
UP037 [*] Remove quotes from type annotation
--> UP037_1.py:14:4
|
13 | # OK
14 | X: "Tuple[int, int]" = (0, 0)
| ^^^^^^^^^^^^^^^^^
|
help: Remove quotes
11 |
12 |
13 | # OK
- X: "Tuple[int, int]" = (0, 0)
14 + X: Tuple[int, int] = (0, 0)

View file

@ -28,14 +28,8 @@ UP037 [*] Remove quotes from type annotation
| ^^^^^^^^^^^^^^^^^
|
help: Remove quotes
1 + from __future__ import annotations
2 | from typing import TYPE_CHECKING
3 |
4 | if TYPE_CHECKING:
--------------------------------------------------------------------------------
11 |
12 |
13 |
14 | # OK
13 | # OK
- X: "Tuple[int, int]" = (0, 0)
15 + X: Tuple[int, int] = (0, 0)
note: This is an unsafe fix and may change runtime behavior
14 + X: Tuple[int, int] = (0, 0)

View file

@ -22,7 +22,7 @@ mod tests {
use crate::settings::LinterSettings;
use crate::settings::types::{CompiledPerFileIgnoreList, PerFileIgnore, PreviewMode};
use crate::test::{test_path, test_resource_path};
use crate::{assert_diagnostics, settings};
use crate::{assert_diagnostics, assert_diagnostics_diff, settings};
#[test_case(Rule::CollectionLiteralConcatenation, Path::new("RUF005.py"))]
#[test_case(Rule::CollectionLiteralConcatenation, Path::new("RUF005_slices.py"))]
@ -245,6 +245,32 @@ mod tests {
Ok(())
}
#[test]
fn confusables_deferred_annotations_diff() -> Result<()> {
assert_diagnostics_diff!(
Path::new("ruff/confusables.py"),
&LinterSettings {
unresolved_target_version: PythonVersion::PY313.into(),
allowed_confusables: FxHashSet::from_iter(['', 'ρ', '']),
..settings::LinterSettings::for_rules(vec![
Rule::AmbiguousUnicodeCharacterString,
Rule::AmbiguousUnicodeCharacterDocstring,
Rule::AmbiguousUnicodeCharacterComment,
])
},
&LinterSettings {
unresolved_target_version: PythonVersion::PY314.into(),
allowed_confusables: FxHashSet::from_iter(['', 'ρ', '']),
..settings::LinterSettings::for_rules(vec![
Rule::AmbiguousUnicodeCharacterString,
Rule::AmbiguousUnicodeCharacterDocstring,
Rule::AmbiguousUnicodeCharacterComment,
])
},
);
Ok(())
}
#[test]
fn preview_confusables() -> Result<()> {
let diagnostics = test_path(

View file

@ -215,4 +215,62 @@ help: Use `map` instead
59 +
60 | )
61 |
62 |
62 |
RUF058 [*] `itertools.starmap` called on `zip` iterable
--> RUF058_0.py:71:1
|
69 | starmap(func, zip(a, b, c), lorem=ipsum)
70 |
71 | starmap(func, zip(a, b, c, strict=True))
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
72 | starmap(func, zip(a, b, c, strict=False))
73 | starmap(func, zip(a, b, c, strict=strict))
|
help: Use `map` instead
68 | starmap(func, zip(a, b, c, lorem=ipsum))
69 | starmap(func, zip(a, b, c), lorem=ipsum)
70 |
- starmap(func, zip(a, b, c, strict=True))
71 + map(func, a, b, c, strict=True)
72 | starmap(func, zip(a, b, c, strict=False))
73 | starmap(func, zip(a, b, c, strict=strict))
74 |
RUF058 [*] `itertools.starmap` called on `zip` iterable
--> RUF058_0.py:72:1
|
71 | starmap(func, zip(a, b, c, strict=True))
72 | starmap(func, zip(a, b, c, strict=False))
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
73 | starmap(func, zip(a, b, c, strict=strict))
|
help: Use `map` instead
69 | starmap(func, zip(a, b, c), lorem=ipsum)
70 |
71 | starmap(func, zip(a, b, c, strict=True))
- starmap(func, zip(a, b, c, strict=False))
72 + map(func, a, b, c, strict=False)
73 | starmap(func, zip(a, b, c, strict=strict))
74 |
75 | # https://github.com/astral-sh/ruff/issues/15742
RUF058 [*] `itertools.starmap` called on `zip` iterable
--> RUF058_0.py:73:1
|
71 | starmap(func, zip(a, b, c, strict=True))
72 | starmap(func, zip(a, b, c, strict=False))
73 | starmap(func, zip(a, b, c, strict=strict))
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
74 |
75 | # https://github.com/astral-sh/ruff/issues/15742
|
help: Use `map` instead
70 |
71 | starmap(func, zip(a, b, c, strict=True))
72 | starmap(func, zip(a, b, c, strict=False))
- starmap(func, zip(a, b, c, strict=strict))
73 + map(func, a, b, c, strict=strict)
74 |
75 | # https://github.com/astral-sh/ruff/issues/15742
76 | starmap(func, zip(*a))

View file

@ -191,11 +191,3 @@ RUF001 String contains ambiguous `𝐁` (MATHEMATICAL BOLD CAPITAL B). Did you m
59 |
60 | from typing import Literal
|
RUF001 String contains ambiguous `` (ARABIC LETTER HEH GOAL INITIAL FORM). Did you mean `o` (LATIN SMALL LETTER O)?
--> confusables.py:61:20
|
60 | from typing import Literal
61 | x: '''"""'Literal[""]'"""'''
| ^
|

View file

@ -0,0 +1,19 @@
---
source: crates/ruff_linter/src/rules/ruff/mod.rs
---
--- Linter settings ---
-linter.unresolved_target_version = 3.13
+linter.unresolved_target_version = 3.14
--- Summary ---
Removed: 1
Added: 0
--- Removed ---
RUF001 String contains ambiguous `` (ARABIC LETTER HEH GOAL INITIAL FORM). Did you mean `o` (LATIN SMALL LETTER O)?
--> confusables.py:61:20
|
60 | from typing import Literal
61 | x: '''"""'Literal[""]'"""'''
| ^
|

View file

@ -200,11 +200,3 @@ RUF001 String contains ambiguous `𝐁` (MATHEMATICAL BOLD CAPITAL B). Did you m
59 |
60 | from typing import Literal
|
RUF001 String contains ambiguous `` (ARABIC LETTER HEH GOAL INITIAL FORM). Did you mean `o` (LATIN SMALL LETTER O)?
--> confusables.py:61:20
|
60 | from typing import Literal
61 | x: '''"""'Literal[""]'"""'''
| ^
|

View file

@ -508,12 +508,18 @@ macro_rules! assert_diagnostics {
#[macro_export]
macro_rules! assert_diagnostics_diff {
($snapshot:expr, $path:expr, $settings_before:expr, $settings_after:expr) => {{
($snapshot:expr, $path:expr, $settings_before:expr, $settings_after:expr $(,)?) => {{
let diff = $crate::test::test_path_with_settings_diff($path, $settings_before, $settings_after)?;
insta::with_settings!({ omit_expression => true }, {
insta::assert_snapshot!($snapshot, format!("{}", diff));
});
}};
($path:expr, $settings_before:expr, $settings_after:expr $(,)?) => {{
let diff = $crate::test::test_path_with_settings_diff($path, $settings_before, $settings_after)?;
insta::with_settings!({ omit_expression => true }, {
insta::assert_snapshot!(format!("{}", diff));
});
}};
}
#[cfg(test)]

View file

@ -55,9 +55,8 @@ impl PythonVersion {
Self::PY37
}
// TODO: change this to 314 when it is released
pub const fn latest() -> Self {
Self::PY313
Self::PY314
}
/// The latest Python version supported in preview
@ -94,7 +93,7 @@ impl PythonVersion {
impl Default for PythonVersion {
fn default() -> Self {
Self::PY39
Self::PY310
}
}

View file

@ -56,7 +56,7 @@ magic-trailing-comma = Respect
docstring-code = Disabled
docstring-code-line-width = "dynamic"
preview = Enabled
target_version = 3.9
target_version = 3.10
source_type = Python
```

View file

@ -175,7 +175,7 @@ magic-trailing-comma = Respect
docstring-code = Disabled
docstring-code-line-width = "dynamic"
preview = Disabled
target_version = 3.9
target_version = 3.10
source_type = Python
```
@ -351,7 +351,7 @@ magic-trailing-comma = Respect
docstring-code = Disabled
docstring-code-line-width = "dynamic"
preview = Disabled
target_version = 3.9
target_version = 3.10
source_type = Python
```
@ -527,7 +527,7 @@ magic-trailing-comma = Respect
docstring-code = Disabled
docstring-code-line-width = "dynamic"
preview = Disabled
target_version = 3.9
target_version = 3.10
source_type = Python
```
@ -703,7 +703,7 @@ magic-trailing-comma = Respect
docstring-code = Disabled
docstring-code-line-width = "dynamic"
preview = Disabled
target_version = 3.9
target_version = 3.10
source_type = Python
```
@ -879,7 +879,7 @@ magic-trailing-comma = Respect
docstring-code = Disabled
docstring-code-line-width = "dynamic"
preview = Disabled
target_version = 3.9
target_version = 3.10
source_type = Python
```

View file

@ -1368,7 +1368,7 @@ magic-trailing-comma = Respect
docstring-code = Disabled
docstring-code-line-width = "dynamic"
preview = Disabled
target_version = 3.9
target_version = 3.10
source_type = Python
```
@ -2740,7 +2740,7 @@ magic-trailing-comma = Respect
docstring-code = Disabled
docstring-code-line-width = "dynamic"
preview = Disabled
target_version = 3.9
target_version = 3.10
source_type = Python
```
@ -4112,7 +4112,7 @@ magic-trailing-comma = Respect
docstring-code = Disabled
docstring-code-line-width = "dynamic"
preview = Disabled
target_version = 3.9
target_version = 3.10
source_type = Python
```
@ -5484,7 +5484,7 @@ magic-trailing-comma = Respect
docstring-code = Disabled
docstring-code-line-width = "dynamic"
preview = Disabled
target_version = 3.9
target_version = 3.10
source_type = Python
```
@ -6856,7 +6856,7 @@ magic-trailing-comma = Respect
docstring-code = Enabled
docstring-code-line-width = "dynamic"
preview = Disabled
target_version = 3.9
target_version = 3.10
source_type = Python
```
@ -8221,7 +8221,7 @@ magic-trailing-comma = Respect
docstring-code = Enabled
docstring-code-line-width = "dynamic"
preview = Disabled
target_version = 3.9
target_version = 3.10
source_type = Python
```
@ -9586,7 +9586,7 @@ magic-trailing-comma = Respect
docstring-code = Enabled
docstring-code-line-width = "dynamic"
preview = Disabled
target_version = 3.9
target_version = 3.10
source_type = Python
```
@ -10960,7 +10960,7 @@ magic-trailing-comma = Respect
docstring-code = Enabled
docstring-code-line-width = "dynamic"
preview = Disabled
target_version = 3.9
target_version = 3.10
source_type = Python
```
@ -12325,7 +12325,7 @@ magic-trailing-comma = Respect
docstring-code = Enabled
docstring-code-line-width = 60
preview = Disabled
target_version = 3.9
target_version = 3.10
source_type = Python
```
@ -13699,7 +13699,7 @@ magic-trailing-comma = Respect
docstring-code = Enabled
docstring-code-line-width = "dynamic"
preview = Disabled
target_version = 3.9
target_version = 3.10
source_type = Python
```

View file

@ -27,7 +27,7 @@ magic-trailing-comma = Respect
docstring-code = Enabled
docstring-code-line-width = "dynamic"
preview = Disabled
target_version = 3.9
target_version = 3.10
source_type = Python
```

View file

@ -308,7 +308,7 @@ magic-trailing-comma = Respect
docstring-code = Enabled
docstring-code-line-width = "dynamic"
preview = Disabled
target_version = 3.9
target_version = 3.10
source_type = Python
```
@ -879,7 +879,7 @@ magic-trailing-comma = Respect
docstring-code = Enabled
docstring-code-line-width = "dynamic"
preview = Disabled
target_version = 3.9
target_version = 3.10
source_type = Python
```
@ -1425,7 +1425,7 @@ magic-trailing-comma = Respect
docstring-code = Enabled
docstring-code-line-width = "dynamic"
preview = Disabled
target_version = 3.9
target_version = 3.10
source_type = Python
```
@ -1996,7 +1996,7 @@ magic-trailing-comma = Respect
docstring-code = Enabled
docstring-code-line-width = "dynamic"
preview = Disabled
target_version = 3.9
target_version = 3.10
source_type = Python
```

View file

@ -90,7 +90,7 @@ magic-trailing-comma = Respect
docstring-code = Disabled
docstring-code-line-width = "dynamic"
preview = Disabled
target_version = 3.9
target_version = 3.10
source_type = Python
```
@ -184,7 +184,7 @@ magic-trailing-comma = Respect
docstring-code = Disabled
docstring-code-line-width = "dynamic"
preview = Disabled
target_version = 3.9
target_version = 3.10
source_type = Python
```

View file

@ -140,7 +140,7 @@ magic-trailing-comma = Respect
docstring-code = Disabled
docstring-code-line-width = "dynamic"
preview = Disabled
target_version = 3.9
target_version = 3.10
source_type = Python
```
@ -296,7 +296,7 @@ magic-trailing-comma = Respect
docstring-code = Disabled
docstring-code-line-width = "dynamic"
preview = Disabled
target_version = 3.9
target_version = 3.10
source_type = Python
```

View file

@ -768,7 +768,7 @@ magic-trailing-comma = Respect
docstring-code = Disabled
docstring-code-line-width = "dynamic"
preview = Enabled
target_version = 3.9
target_version = 3.10
source_type = Python
```
@ -1595,7 +1595,7 @@ magic-trailing-comma = Respect
docstring-code = Disabled
docstring-code-line-width = "dynamic"
preview = Disabled
target_version = 3.9
target_version = 3.10
source_type = Python
```

View file

@ -38,7 +38,7 @@ magic-trailing-comma = Respect
docstring-code = Disabled
docstring-code-line-width = "dynamic"
preview = Enabled
target_version = 3.9
target_version = 3.10
source_type = Python
```

View file

@ -40,7 +40,7 @@ magic-trailing-comma = Respect
docstring-code = Disabled
docstring-code-line-width = "dynamic"
preview = Disabled
target_version = 3.9
target_version = 3.10
source_type = Python
```

View file

@ -232,7 +232,7 @@ magic-trailing-comma = Respect
docstring-code = Disabled
docstring-code-line-width = "dynamic"
preview = Disabled
target_version = 3.9
target_version = 3.10
source_type = Python
```
@ -482,7 +482,7 @@ magic-trailing-comma = Respect
docstring-code = Disabled
docstring-code-line-width = "dynamic"
preview = Disabled
target_version = 3.9
target_version = 3.10
source_type = Python
```

View file

@ -37,7 +37,7 @@ magic-trailing-comma = Respect
docstring-code = Disabled
docstring-code-line-width = "dynamic"
preview = Disabled
target_version = 3.9
target_version = 3.10
source_type = Python
```
@ -75,7 +75,7 @@ magic-trailing-comma = Respect
docstring-code = Disabled
docstring-code-line-width = "dynamic"
preview = Disabled
target_version = 3.9
target_version = 3.10
source_type = Python
```

View file

@ -18,7 +18,7 @@ magic-trailing-comma = Respect
docstring-code = Disabled
docstring-code-line-width = "dynamic"
preview = Disabled
target_version = 3.9
target_version = 3.10
source_type = Python
```
@ -37,7 +37,7 @@ magic-trailing-comma = Respect
docstring-code = Disabled
docstring-code-line-width = "dynamic"
preview = Disabled
target_version = 3.9
target_version = 3.10
source_type = Python
```
@ -56,7 +56,7 @@ magic-trailing-comma = Respect
docstring-code = Disabled
docstring-code-line-width = "dynamic"
preview = Disabled
target_version = 3.9
target_version = 3.10
source_type = Python
```

View file

@ -33,7 +33,7 @@ magic-trailing-comma = Respect
docstring-code = Disabled
docstring-code-line-width = "dynamic"
preview = Disabled
target_version = 3.9
target_version = 3.10
source_type = Python
```
@ -68,7 +68,7 @@ magic-trailing-comma = Respect
docstring-code = Disabled
docstring-code-line-width = "dynamic"
preview = Disabled
target_version = 3.9
target_version = 3.10
source_type = Python
```
@ -103,7 +103,7 @@ magic-trailing-comma = Respect
docstring-code = Disabled
docstring-code-line-width = "dynamic"
preview = Disabled
target_version = 3.9
target_version = 3.10
source_type = Python
```

View file

@ -24,7 +24,7 @@ magic-trailing-comma = Respect
docstring-code = Disabled
docstring-code-line-width = "dynamic"
preview = Disabled
target_version = 3.9
target_version = 3.10
source_type = Ipynb
```
@ -48,7 +48,7 @@ magic-trailing-comma = Respect
docstring-code = Disabled
docstring-code-line-width = "dynamic"
preview = Disabled
target_version = 3.9
target_version = 3.10
source_type = Python
```

View file

@ -84,7 +84,7 @@ magic-trailing-comma = Respect
docstring-code = Disabled
docstring-code-line-width = "dynamic"
preview = Disabled
target_version = 3.9
target_version = 3.10
source_type = Python
```
@ -167,7 +167,7 @@ magic-trailing-comma = Respect
docstring-code = Disabled
docstring-code-line-width = "dynamic"
preview = Enabled
target_version = 3.9
target_version = 3.10
source_type = Python
```

View file

@ -68,7 +68,7 @@ magic-trailing-comma = Respect
docstring-code = Disabled
docstring-code-line-width = "dynamic"
preview = Disabled
target_version = 3.9
target_version = 3.10
source_type = Python
```
@ -142,7 +142,7 @@ magic-trailing-comma = Respect
docstring-code = Disabled
docstring-code-line-width = "dynamic"
preview = Disabled
target_version = 3.9
target_version = 3.10
source_type = Python
```
@ -216,7 +216,7 @@ magic-trailing-comma = Respect
docstring-code = Disabled
docstring-code-line-width = "dynamic"
preview = Disabled
target_version = 3.9
target_version = 3.10
source_type = Python
```

View file

@ -121,7 +121,7 @@ magic-trailing-comma = Respect
docstring-code = Enabled
docstring-code-line-width = "dynamic"
preview = Disabled
target_version = 3.9
target_version = 3.10
source_type = Python
```
@ -273,7 +273,7 @@ magic-trailing-comma = Respect
docstring-code = Enabled
docstring-code-line-width = 88
preview = Disabled
target_version = 3.9
target_version = 3.10
source_type = Python
```

View file

@ -81,7 +81,7 @@ magic-trailing-comma = Respect
docstring-code = Disabled
docstring-code-line-width = "dynamic"
preview = Disabled
target_version = 3.9
target_version = 3.10
source_type = Python
```
@ -160,7 +160,7 @@ magic-trailing-comma = Respect
docstring-code = Disabled
docstring-code-line-width = "dynamic"
preview = Disabled
target_version = 3.9
target_version = 3.10
source_type = Python
```
@ -239,7 +239,7 @@ magic-trailing-comma = Respect
docstring-code = Disabled
docstring-code-line-width = "dynamic"
preview = Disabled
target_version = 3.9
target_version = 3.10
source_type = Python
```

View file

@ -34,7 +34,7 @@ magic-trailing-comma = Respect
docstring-code = Disabled
docstring-code-line-width = "dynamic"
preview = Disabled
target_version = 3.9
target_version = 3.10
source_type = Stub
```

View file

@ -51,7 +51,7 @@ magic-trailing-comma = Respect
docstring-code = Disabled
docstring-code-line-width = "dynamic"
preview = Disabled
target_version = 3.9
target_version = 3.10
source_type = Python
```
@ -109,7 +109,7 @@ magic-trailing-comma = Ignore
docstring-code = Disabled
docstring-code-line-width = "dynamic"
preview = Disabled
target_version = 3.9
target_version = 3.10
source_type = Python
```

View file

@ -201,7 +201,7 @@ magic-trailing-comma = Respect
docstring-code = Disabled
docstring-code-line-width = "dynamic"
preview = Enabled
target_version = 3.9
target_version = 3.10
source_type = Stub
```

View file

@ -35,7 +35,7 @@ magic-trailing-comma = Respect
docstring-code = Disabled
docstring-code-line-width = "dynamic"
preview = Enabled
target_version = 3.9
target_version = 3.10
source_type = Stub
```

View file

@ -26,7 +26,7 @@ magic-trailing-comma = Respect
docstring-code = Disabled
docstring-code-line-width = "dynamic"
preview = Disabled
target_version = 3.9
target_version = 3.10
source_type = Python
```
@ -53,7 +53,7 @@ magic-trailing-comma = Respect
docstring-code = Disabled
docstring-code-line-width = "dynamic"
preview = Disabled
target_version = 3.9
target_version = 3.10
source_type = Python
```
@ -83,7 +83,7 @@ magic-trailing-comma = Respect
docstring-code = Disabled
docstring-code-line-width = "dynamic"
preview = Disabled
target_version = 3.9
target_version = 3.10
source_type = Python
```

View file

@ -299,8 +299,8 @@ pub struct Options {
/// code upgrades, like rewriting type annotations. Ruff will not propose
/// changes using features that are not available in the given version.
///
/// For example, to represent supporting Python >=3.10 or ==3.10
/// specify `target-version = "py310"`.
/// For example, to represent supporting Python >=3.11 or ==3.11
/// specify `target-version = "py311"`.
///
/// If you're already using a `pyproject.toml` file, we recommend
/// `project.requires-python` instead, as it's based on Python packaging
@ -327,8 +327,8 @@ pub struct Options {
/// file than it would for an equivalent runtime file with the same target
/// version.
#[option(
default = r#""py39""#,
value_type = r#""py37" | "py38" | "py39" | "py310" | "py311" | "py312" | "py313""#,
default = r#""py310""#,
value_type = r#""py37" | "py38" | "py39" | "py310" | "py311" | "py312" | "py313" | "py314""#,
example = r#"
# Always generate Python 3.7-compatible code.
target-version = "py37"

1
crates/ty/docs/cli.md generated
View file

@ -86,6 +86,7 @@ over all configuration files.</p>
<li><code>3.11</code></li>
<li><code>3.12</code></li>
<li><code>3.13</code></li>
<li><code>3.14</code></li>
</ul></dd><dt id="ty-check--quiet"><a href="#ty-check--quiet"><code>--quiet</code></a>, <code>-q</code></dt><dd><p>Use quiet output (or <code>-qq</code> for silent output)</p>
</dd><dt id="ty-check--respect-ignore-files"><a href="#ty-check--respect-ignore-files"><code>--respect-ignore-files</code></a></dt><dd><p>Respect file exclusions via <code>.gitignore</code> and other standard ignore files. Use <code>--no-respect-gitignore</code> to disable</p>
</dd><dt id="ty-check--typeshed"><a href="#ty-check--typeshed"><code>--typeshed</code></a>, <code>--custom-typeshed-dir</code> <i>path</i></dt><dd><p>Custom directory to use for stdlib typeshed stubs</p>

View file

@ -7,9 +7,9 @@ pub enum PythonVersion {
Py37,
#[value(name = "3.8")]
Py38,
#[default]
#[value(name = "3.9")]
Py39,
#[default]
#[value(name = "3.10")]
Py310,
#[value(name = "3.11")]
@ -18,6 +18,8 @@ pub enum PythonVersion {
Py312,
#[value(name = "3.13")]
Py313,
#[value(name = "3.14")]
Py314,
}
impl PythonVersion {
@ -30,6 +32,7 @@ impl PythonVersion {
Self::Py311 => "3.11",
Self::Py312 => "3.12",
Self::Py313 => "3.13",
Self::Py314 => "3.14",
}
}
}
@ -50,6 +53,7 @@ impl From<PythonVersion> for ruff_python_ast::PythonVersion {
PythonVersion::Py311 => Self::PY311,
PythonVersion::Py312 => Self::PY312,
PythonVersion::Py313 => Self::PY313,
PythonVersion::Py314 => Self::PY314,
}
}
}

View file

@ -2109,7 +2109,7 @@ All attribute access on literal `bytes` types is currently delegated to `builtin
```py
# revealed: bound method Literal[b"foo"].join(iterable_of_bytes: Iterable[@Todo(Support for `typing.TypeAlias`)], /) -> bytes
reveal_type(b"foo".join)
# revealed: bound method Literal[b"foo"].endswith(suffix: @Todo(Support for `typing.TypeAlias`) | tuple[@Todo(Support for `typing.TypeAlias`), ...], start: SupportsIndex | None = ellipsis, end: SupportsIndex | None = ellipsis, /) -> bool
# revealed: bound method Literal[b"foo"].endswith(suffix: @Todo(Support for `typing.TypeAlias`) | tuple[@Todo(Support for `typing.TypeAlias`), ...], start: SupportsIndex | None = EllipsisType, end: SupportsIndex | None = EllipsisType, /) -> bool
reveal_type(b"foo".endswith)
```

View file

@ -18,6 +18,11 @@ reveal_type(A | B) # revealed: UnionType
## Union of two classes (prior to 3.10)
```toml
[environment]
python-version = "3.9"
```
```py
class A: ...
class B: ...

View file

@ -57,7 +57,7 @@ We can access attributes on objects of all kinds:
import sys
reveal_type(inspect.getattr_static(sys, "dont_write_bytecode")) # revealed: bool
# revealed: def getattr_static(obj: object, attr: str, default: Any | None = ellipsis) -> Any
# revealed: def getattr_static(obj: object, attr: str, default: Any | None = EllipsisType) -> Any
reveal_type(inspect.getattr_static(inspect, "getattr_static"))
reveal_type(inspect.getattr_static(1, "real")) # revealed: property

View file

@ -651,7 +651,7 @@ static_assert(is_assignable_to(TypeOf[property.__set__], Callable))
reveal_type(MyClass.my_property.__set__)
static_assert(is_assignable_to(TypeOf[MyClass.my_property.__set__], Callable))
# revealed: def startswith(self, prefix: str | tuple[str, ...], start: SupportsIndex | None = ellipsis, end: SupportsIndex | None = ellipsis, /) -> bool
# revealed: def startswith(self, prefix: str | tuple[str, ...], start: SupportsIndex | None = EllipsisType, end: SupportsIndex | None = EllipsisType, /) -> bool
reveal_type(str.startswith)
static_assert(is_assignable_to(TypeOf[str.startswith], Callable))
@ -707,7 +707,7 @@ def _(
# revealed: (instance: object, value: object, /) -> Unknown
reveal_type(j)
# revealed: (self, prefix: str | tuple[str, ...], start: SupportsIndex | None = ellipsis, end: SupportsIndex | None = ellipsis, /) -> bool
# revealed: (self, prefix: str | tuple[str, ...], start: SupportsIndex | None = EllipsisType, end: SupportsIndex | None = EllipsisType, /) -> bool
reveal_type(k)
# revealed: (prefix: str | tuple[str, ...], start: SupportsIndex | None = None, end: SupportsIndex | None = None, /) -> bool

View file

@ -97,7 +97,7 @@ inside the module:
import typing
reveal_type(typing.__name__) # revealed: str
reveal_type(typing.__init__) # revealed: bound method ModuleType.__init__(name: str, doc: str | None = ellipsis) -> None
reveal_type(typing.__init__) # revealed: bound method ModuleType.__init__(name: str, doc: str | None = EllipsisType) -> None
# For a stub module, we don't know that `__file__` is a string (at runtime it may be entirely
# unset, but we follow typeshed here):

View file

@ -37,7 +37,7 @@ error[invalid-return-type]: Return type does not match returned value
| --- Expected `int` because of return type
2 | # error: [invalid-return-type]
3 | return ...
| ^^^ expected `int`, found `ellipsis`
| ^^^ expected `int`, found `EllipsisType`
4 |
5 | # error: [invalid-return-type]
|

View file

@ -47,7 +47,7 @@ Iterating over an ellipsis literal as part of a `for` loop in a stub is invalid,
results in a diagnostic:
```pyi
# error: [not-iterable] "Object of type `ellipsis` is not iterable"
# error: [not-iterable] "Object of type `EllipsisType` is not iterable"
for a, b in ...:
reveal_type(a) # revealed: Unknown
reveal_type(b) # revealed: Unknown
@ -59,13 +59,13 @@ In a non-stub file, there's no special treatment of ellipsis literals. An ellips
be assigned if `EllipsisType` is actually assignable to the annotated type.
```py
# error: 7 [invalid-parameter-default] "Default value of type `ellipsis` is not assignable to annotated parameter type `int`"
# error: 7 [invalid-parameter-default] "Default value of type `EllipsisType` is not assignable to annotated parameter type `int`"
def f(x: int = ...) -> None: ...
# error: 1 [invalid-assignment] "Object of type `ellipsis` is not assignable to `int`"
# error: 1 [invalid-assignment] "Object of type `EllipsisType` is not assignable to `int`"
a: int = ...
b = ...
reveal_type(b) # revealed: ellipsis
reveal_type(b) # revealed: EllipsisType
```
## Use of `Ellipsis` symbol
@ -73,6 +73,6 @@ reveal_type(b) # revealed: ellipsis
There is no special treatment of the builtin name `Ellipsis` in stubs, only of `...` literals.
```pyi
# error: 7 [invalid-parameter-default] "Default value of type `ellipsis` is not assignable to annotated parameter type `int`"
# error: 7 [invalid-parameter-default] "Default value of type `EllipsisType` is not assignable to annotated parameter type `int`"
def f(x: int = Ellipsis) -> None: ...
```

2
ruff.schema.json generated
View file

@ -702,7 +702,7 @@
}
},
"target-version": {
"description": "The minimum Python version to target, e.g., when considering automatic code upgrades, like rewriting type annotations. Ruff will not propose changes using features that are not available in the given version.\n\nFor example, to represent supporting Python >=3.10 or ==3.10 specify `target-version = \"py310\"`.\n\nIf you're already using a `pyproject.toml` file, we recommend `project.requires-python` instead, as it's based on Python packaging standards, and will be respected by other tools. For example, Ruff treats the following as identical to `target-version = \"py38\"`:\n\n```toml [project] requires-python = \">=3.8\" ```\n\nIf both are specified, `target-version` takes precedence over `requires-python`. See [_Inferring the Python version_](https://docs.astral.sh/ruff/configuration/#inferring-the-python-version) for a complete description of how the `target-version` is determined when left unspecified.\n\nNote that a stub file can [sometimes make use of a typing feature](https://typing.python.org/en/latest/spec/distributing.html#syntax) before it is available at runtime, as long as the stub does not make use of new *syntax*. For example, a type checker will understand `int | str` in a stub as being a `Union` type annotation, even if the type checker is run using Python 3.9, despite the fact that the `|` operator can only be used to create union types at runtime on Python 3.10+. As such, Ruff will often recommend newer features in a stub file than it would for an equivalent runtime file with the same target version.",
"description": "The minimum Python version to target, e.g., when considering automatic code upgrades, like rewriting type annotations. Ruff will not propose changes using features that are not available in the given version.\n\nFor example, to represent supporting Python >=3.11 or ==3.11 specify `target-version = \"py311\"`.\n\nIf you're already using a `pyproject.toml` file, we recommend `project.requires-python` instead, as it's based on Python packaging standards, and will be respected by other tools. For example, Ruff treats the following as identical to `target-version = \"py38\"`:\n\n```toml [project] requires-python = \">=3.8\" ```\n\nIf both are specified, `target-version` takes precedence over `requires-python`. See [_Inferring the Python version_](https://docs.astral.sh/ruff/configuration/#inferring-the-python-version) for a complete description of how the `target-version` is determined when left unspecified.\n\nNote that a stub file can [sometimes make use of a typing feature](https://typing.python.org/en/latest/spec/distributing.html#syntax) before it is available at runtime, as long as the stub does not make use of new *syntax*. For example, a type checker will understand `int | str` in a stub as being a `Union` type annotation, even if the type checker is run using Python 3.9, despite the fact that the `|` operator can only be used to create union types at runtime on Python 3.10+. As such, Ruff will often recommend newer features in a stub file than it would for an equivalent runtime file with the same target version.",
"anyOf": [
{
"$ref": "#/definitions/PythonVersion"