[pyupgrade] Stabilize non-pep604-annotation-optional (UP045) and preview behavior for non-pep604-annotation-union (UP007) (#18505)

This commit is contained in:
Dylan 2025-06-09 17:25:14 -05:00 committed by Brent Westbrook
parent 56f2aaaebc
commit de4fc5b171
8 changed files with 45 additions and 73 deletions

View file

@ -549,7 +549,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Pyupgrade, "042") => (RuleGroup::Preview, rules::pyupgrade::rules::ReplaceStrEnum),
(Pyupgrade, "043") => (RuleGroup::Stable, rules::pyupgrade::rules::UnnecessaryDefaultTypeArgs),
(Pyupgrade, "044") => (RuleGroup::Stable, rules::pyupgrade::rules::NonPEP646Unpack),
(Pyupgrade, "045") => (RuleGroup::Preview, rules::pyupgrade::rules::NonPEP604AnnotationOptional),
(Pyupgrade, "045") => (RuleGroup::Stable, rules::pyupgrade::rules::NonPEP604AnnotationOptional),
(Pyupgrade, "046") => (RuleGroup::Preview, rules::pyupgrade::rules::NonPEP695GenericClass),
(Pyupgrade, "047") => (RuleGroup::Preview, rules::pyupgrade::rules::NonPEP695GenericFunction),
(Pyupgrade, "049") => (RuleGroup::Stable, rules::pyupgrade::rules::PrivateTypeParameter),

View file

@ -61,11 +61,6 @@ pub(crate) const fn is_dunder_init_fix_unused_import_enabled(settings: &LinterSe
settings.preview.is_enabled()
}
// https://github.com/astral-sh/ruff/pull/15313
pub(crate) const fn is_defer_optional_to_up045_enabled(settings: &LinterSettings) -> bool {
settings.preview.is_enabled()
}
// https://github.com/astral-sh/ruff/pull/8473
pub(crate) const fn is_unicode_to_unicode_confusables_enabled(settings: &LinterSettings) -> bool {
settings.preview.is_enabled()

View file

@ -15,7 +15,7 @@ mod tests {
use crate::registry::Rule;
use crate::rules::pyupgrade;
use crate::settings::types::PreviewMode;
use crate::test::test_path;
use crate::{assert_messages, settings};
@ -43,7 +43,7 @@ mod tests {
#[test_case(Rule::NonPEP585Annotation, Path::new("UP006_2.py"))]
#[test_case(Rule::NonPEP585Annotation, Path::new("UP006_3.py"))]
#[test_case(Rule::NonPEP604AnnotationUnion, Path::new("UP007.py"))]
#[test_case(Rule::NonPEP604AnnotationUnion, Path::new("UP045.py"))]
#[test_case(Rule::NonPEP604AnnotationOptional, Path::new("UP045.py"))]
#[test_case(Rule::NonPEP604Isinstance, Path::new("UP038.py"))]
#[test_case(Rule::OSErrorAlias, Path::new("UP024_0.py"))]
#[test_case(Rule::OSErrorAlias, Path::new("UP024_1.py"))]
@ -122,19 +122,6 @@ mod tests {
Ok(())
}
#[test]
fn up007_preview() -> Result<()> {
let diagnostics = test_path(
Path::new("pyupgrade/UP045.py"),
&settings::LinterSettings {
preview: PreviewMode::Enabled,
..settings::LinterSettings::for_rule(Rule::NonPEP604AnnotationUnion)
},
)?;
assert_messages!(diagnostics);
Ok(())
}
#[test]
fn async_timeout_error_alias_not_applied_py310() -> Result<()> {
let diagnostics = test_path(

View file

@ -8,7 +8,6 @@ use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
use crate::codes::Rule;
use crate::fix::edits::pad;
use crate::preview::is_defer_optional_to_up045_enabled;
use crate::{Applicability, Edit, Fix, FixAvailability, Violation};
/// ## What it does
@ -39,8 +38,7 @@ use crate::{Applicability, Edit, Fix, FixAvailability, Violation};
/// foo: int | str = 1
/// ```
///
/// ## Preview
/// In preview mode, this rule only checks for usages of `typing.Union`,
/// Note that this rule only checks for usages of `typing.Union`,
/// while `UP045` checks for `typing.Optional`.
///
/// ## Fix safety
@ -149,11 +147,8 @@ pub(crate) fn non_pep604_annotation(
match operator {
Pep604Operator::Optional => {
let guard = if is_defer_optional_to_up045_enabled(checker.settings) {
checker.report_diagnostic_if_enabled(NonPEP604AnnotationOptional, expr.range())
} else {
checker.report_diagnostic_if_enabled(NonPEP604AnnotationUnion, expr.range())
};
let guard =
checker.report_diagnostic_if_enabled(NonPEP604AnnotationOptional, expr.range());
let Some(mut diagnostic) = guard else {
return;

View file

@ -1,13 +1,13 @@
---
source: crates/ruff_linter/src/rules/pyupgrade/mod.rs
---
UP045.py:5:10: UP007 [*] Use `X | Y` for type annotations
UP045.py:5:10: UP045 [*] Use `X | None` for type annotations
|
5 | def f(x: Optional[str]) -> None:
| ^^^^^^^^^^^^^ UP007
| ^^^^^^^^^^^^^ UP045
6 | ...
|
= help: Convert to `X | Y`
= help: Convert to `X | None`
Safe fix
2 2 | from typing import Optional
@ -19,13 +19,13 @@ UP045.py:5:10: UP007 [*] Use `X | Y` for type annotations
7 7 |
8 8 |
UP045.py:9:10: UP007 [*] Use `X | Y` for type annotations
UP045.py:9:10: UP045 [*] Use `X | None` for type annotations
|
9 | def f(x: typing.Optional[str]) -> None:
| ^^^^^^^^^^^^^^^^^^^^ UP007
| ^^^^^^^^^^^^^^^^^^^^ UP045
10 | ...
|
= help: Convert to `X | Y`
= help: Convert to `X | None`
Safe fix
6 6 | ...
@ -37,14 +37,14 @@ UP045.py:9:10: UP007 [*] Use `X | Y` for type annotations
11 11 |
12 12 |
UP045.py:14:8: UP007 [*] Use `X | Y` for type annotations
UP045.py:14:8: UP045 [*] Use `X | None` for type annotations
|
13 | def f() -> None:
14 | x: Optional[str]
| ^^^^^^^^^^^^^ UP007
| ^^^^^^^^^^^^^ UP045
15 | x = Optional[str]
|
= help: Convert to `X | Y`
= help: Convert to `X | None`
Safe fix
11 11 |
@ -56,22 +56,22 @@ UP045.py:14:8: UP007 [*] Use `X | Y` for type annotations
16 16 |
17 17 |
UP045.py:15:9: UP007 Use `X | Y` for type annotations
UP045.py:15:9: UP045 Use `X | None` for type annotations
|
13 | def f() -> None:
14 | x: Optional[str]
15 | x = Optional[str]
| ^^^^^^^^^^^^^ UP007
| ^^^^^^^^^^^^^ UP045
|
= help: Convert to `X | Y`
= help: Convert to `X | None`
UP045.py:18:15: UP007 [*] Use `X | Y` for type annotations
UP045.py:18:15: UP045 [*] Use `X | None` for type annotations
|
18 | def f(x: list[Optional[int]]) -> None:
| ^^^^^^^^^^^^^ UP007
| ^^^^^^^^^^^^^ UP045
19 | ...
|
= help: Convert to `X | Y`
= help: Convert to `X | None`
Safe fix
15 15 | x = Optional[str]
@ -83,31 +83,31 @@ UP045.py:18:15: UP007 [*] Use `X | Y` for type annotations
20 20 |
21 21 |
UP045.py:22:10: UP007 Use `X | Y` for type annotations
UP045.py:22:10: UP045 Use `X | None` for type annotations
|
22 | def f(x: Optional[int : float]) -> None:
| ^^^^^^^^^^^^^^^^^^^^^ UP007
| ^^^^^^^^^^^^^^^^^^^^^ UP045
23 | ...
|
= help: Convert to `X | Y`
= help: Convert to `X | None`
UP045.py:26:10: UP007 Use `X | Y` for type annotations
UP045.py:26:10: UP045 Use `X | None` for type annotations
|
26 | def f(x: Optional[str, int : float]) -> None:
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ UP007
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ UP045
27 | ...
|
= help: Convert to `X | Y`
= help: Convert to `X | None`
UP045.py:30:10: UP007 Use `X | Y` for type annotations
UP045.py:30:10: UP045 Use `X | None` for type annotations
|
30 | def f(x: Optional[int, float]) -> None:
| ^^^^^^^^^^^^^^^^^^^^ UP007
| ^^^^^^^^^^^^^^^^^^^^ UP045
31 | ...
|
= help: Convert to `X | Y`
= help: Convert to `X | None`
UP045.py:36:28: UP007 [*] Use `X | Y` for type annotations
UP045.py:36:28: UP045 [*] Use `X | None` for type annotations
|
34 | # Regression test for: https://github.com/astral-sh/ruff/issues/7131
35 | class ServiceRefOrValue:
@ -116,9 +116,9 @@ UP045.py:36:28: UP007 [*] Use `X | Y` for type annotations
37 | | list[ServiceSpecificationRef]
38 | | | list[ServiceSpecification]
39 | | ] = None
| |_____^ UP007
| |_____^ UP045
|
= help: Convert to `X | Y`
= help: Convert to `X | None`
Safe fix
33 33 |
@ -133,14 +133,14 @@ UP045.py:36:28: UP007 [*] Use `X | Y` for type annotations
41 38 |
42 39 | # Regression test for: https://github.com/astral-sh/ruff/issues/7201
UP045.py:44:28: UP007 [*] Use `X | Y` for type annotations
UP045.py:44:28: UP045 [*] Use `X | None` for type annotations
|
42 | # Regression test for: https://github.com/astral-sh/ruff/issues/7201
43 | class ServiceRefOrValue:
44 | service_specification: Optional[str]is not True = None
| ^^^^^^^^^^^^^ UP007
| ^^^^^^^^^^^^^ UP045
|
= help: Convert to `X | Y`
= help: Convert to `X | None`
Safe fix
41 41 |
@ -152,11 +152,11 @@ UP045.py:44:28: UP007 [*] Use `X | Y` for type annotations
46 46 |
47 47 | # Test for: https://github.com/astral-sh/ruff/issues/18508
UP045.py:49:6: UP007 Use `X | Y` for type annotations
UP045.py:49:6: UP045 Use `X | None` for type annotations
|
47 | # Test for: https://github.com/astral-sh/ruff/issues/18508
48 | # Optional[None] should not be offered a fix
49 | foo: Optional[None] = None
| ^^^^^^^^^^^^^^ UP007
| ^^^^^^^^^^^^^^ UP045
|
= help: Convert to `X | Y`
= help: Convert to `X | None`

View file

@ -1,14 +1,14 @@
---
source: crates/ruff_linter/src/rules/pyupgrade/mod.rs
---
future_annotations.py:40:4: UP007 [*] Use `X | Y` for type annotations
future_annotations.py:40:4: UP045 [*] Use `X | None` for type annotations
|
40 | x: Optional[int] = None
| ^^^^^^^^^^^^^ UP007
| ^^^^^^^^^^^^^ UP045
41 |
42 | MyList: TypeAlias = Union[List[int], List[str]]
|
= help: Convert to `X | Y`
= help: Convert to `X | None`
Unsafe fix
37 37 | return y

View file

@ -1,14 +1,14 @@
---
source: crates/ruff_linter/src/rules/pyupgrade/mod.rs
---
future_annotations.py:40:4: UP007 [*] Use `X | Y` for type annotations
future_annotations.py:40:4: UP045 [*] Use `X | None` for type annotations
|
40 | x: Optional[int] = None
| ^^^^^^^^^^^^^ UP007
| ^^^^^^^^^^^^^ UP045
41 |
42 | MyList: TypeAlias = Union[List[int], List[str]]
|
= help: Convert to `X | Y`
= help: Convert to `X | None`
Safe fix
37 37 | return y

View file

@ -1,5 +0,0 @@
---
source: crates/ruff_linter/src/rules/pyupgrade/mod.rs
snapshot_kind: text
---