diff --git a/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP043.pyi b/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP043.pyi new file mode 100644 index 0000000000..5613053d81 --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP043.pyi @@ -0,0 +1,59 @@ +from collections.abc import Generator, AsyncGenerator + + +def func() -> Generator[int, None, None]: + yield 42 + + +def func() -> Generator[int, None]: + yield 42 + + +def func() -> Generator[int]: + yield 42 + + +def func() -> Generator[int, int, int]: + foo = yield 42 + return foo + + +def func() -> Generator[int, int, None]: + _ = yield 42 + return None + + +def func() -> Generator[int, None, int]: + yield 42 + return 42 + + +async def func() -> AsyncGenerator[int, None]: + yield 42 + + +async def func() -> AsyncGenerator[int]: + yield 42 + + +async def func() -> AsyncGenerator[int, int]: + foo = yield 42 + return foo + + +from typing import Generator, AsyncGenerator + + +def func() -> Generator[str, None, None]: + yield "hello" + + +async def func() -> AsyncGenerator[str, None]: + yield "hello" + + +async def func() -> AsyncGenerator[ # type: ignore + str, + None +]: + yield "hello" diff --git a/crates/ruff_linter/src/checkers/ast/analyze/expression.rs b/crates/ruff_linter/src/checkers/ast/analyze/expression.rs index 1851959029..c542c9510a 100644 --- a/crates/ruff_linter/src/checkers/ast/analyze/expression.rs +++ b/crates/ruff_linter/src/checkers/ast/analyze/expression.rs @@ -9,6 +9,7 @@ use ruff_text_size::Ranged; use crate::checkers::ast::Checker; use crate::preview::{ is_assert_raises_exception_call_enabled, is_optional_as_none_in_union_enabled, + is_unnecessary_default_type_args_stubs_enabled, }; use crate::registry::Rule; use crate::rules::{ @@ -142,7 +143,10 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) { } if checker.is_rule_enabled(Rule::UnnecessaryDefaultTypeArgs) { - if checker.target_version() >= PythonVersion::PY313 { + if checker.target_version() >= PythonVersion::PY313 + || is_unnecessary_default_type_args_stubs_enabled(checker.settings()) + && checker.semantic().in_stub_file() + { pyupgrade::rules::unnecessary_default_type_args(checker, expr); } } diff --git a/crates/ruff_linter/src/preview.rs b/crates/ruff_linter/src/preview.rs index f932b2b059..466482cd48 100644 --- a/crates/ruff_linter/src/preview.rs +++ b/crates/ruff_linter/src/preview.rs @@ -260,3 +260,10 @@ pub(crate) const fn is_maxsplit_without_separator_fix_enabled(settings: &LinterS pub(crate) const fn is_bidi_forbid_arabic_letter_mark_enabled(settings: &LinterSettings) -> bool { settings.preview.is_enabled() } + +// https://github.com/astral-sh/ruff/pull/20027 +pub(crate) const fn is_unnecessary_default_type_args_stubs_enabled( + settings: &LinterSettings, +) -> bool { + settings.preview.is_enabled() +} diff --git a/crates/ruff_linter/src/rules/pyupgrade/mod.rs b/crates/ruff_linter/src/rules/pyupgrade/mod.rs index 94a40baa9d..aa2f24313c 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/mod.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/mod.rs @@ -357,4 +357,19 @@ mod tests { 2 | from pipes import quote, Template "); } + + #[test] + fn unnecessary_default_type_args_stubs_py312_preview() -> Result<()> { + let snapshot = format!("{}__preview", "UP043.pyi"); + let diagnostics = test_path( + Path::new("pyupgrade/UP043.pyi"), + &settings::LinterSettings { + preview: PreviewMode::Enabled, + unresolved_target_version: PythonVersion::PY312.into(), + ..settings::LinterSettings::for_rule(Rule::UnnecessaryDefaultTypeArgs) + }, + )?; + assert_diagnostics!(snapshot, diagnostics); + Ok(()) + } } diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/unnecessary_default_type_args.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/unnecessary_default_type_args.rs index 1bfebfe727..e5d739d5a6 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/unnecessary_default_type_args.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/unnecessary_default_type_args.rs @@ -8,6 +8,7 @@ use crate::{AlwaysFixableViolation, Applicability, Edit, Fix}; /// ## What it does /// Checks for unnecessary default type arguments for `Generator` and /// `AsyncGenerator` on Python 3.13+. +/// In [preview], this rule will also apply to stub files. /// /// ## Why is this bad? /// Python 3.13 introduced the ability for type parameters to specify default @@ -59,6 +60,8 @@ use crate::{AlwaysFixableViolation, Applicability, Edit, Fix}; /// - [Annotating generators and coroutines](https://docs.python.org/3/library/typing.html#annotating-generators-and-coroutines) /// - [Python documentation: `typing.Generator`](https://docs.python.org/3/library/typing.html#typing.Generator) /// - [Python documentation: `typing.AsyncGenerator`](https://docs.python.org/3/library/typing.html#typing.AsyncGenerator) +/// +/// [preview]: https://docs.astral.sh/ruff/preview/ #[derive(ViolationMetadata)] pub(crate) struct UnnecessaryDefaultTypeArgs; diff --git a/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP043.pyi__preview.snap b/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP043.pyi__preview.snap new file mode 100644 index 0000000000..37b80bf63d --- /dev/null +++ b/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP043.pyi__preview.snap @@ -0,0 +1,128 @@ +--- +source: crates/ruff_linter/src/rules/pyupgrade/mod.rs +--- +UP043 [*] Unnecessary default type arguments + --> UP043.pyi:4:15 + | +4 | def func() -> Generator[int, None, None]: + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ +5 | yield 42 + | +help: Remove default type arguments +1 | from collections.abc import Generator, AsyncGenerator +2 | +3 | + - def func() -> Generator[int, None, None]: +4 + def func() -> Generator[int]: +5 | yield 42 +6 | +7 | + +UP043 [*] Unnecessary default type arguments + --> UP043.pyi:8:15 + | +8 | def func() -> Generator[int, None]: + | ^^^^^^^^^^^^^^^^^^^^ +9 | yield 42 + | +help: Remove default type arguments +5 | yield 42 +6 | +7 | + - def func() -> Generator[int, None]: +8 + def func() -> Generator[int]: +9 | yield 42 +10 | +11 | + +UP043 [*] Unnecessary default type arguments + --> UP043.pyi:21:15 + | +21 | def func() -> Generator[int, int, None]: + | ^^^^^^^^^^^^^^^^^^^^^^^^^ +22 | _ = yield 42 +23 | return None + | +help: Remove default type arguments +18 | return foo +19 | +20 | + - def func() -> Generator[int, int, None]: +21 + def func() -> Generator[int, int]: +22 | _ = yield 42 +23 | return None +24 | + +UP043 [*] Unnecessary default type arguments + --> UP043.pyi:31:21 + | +31 | async def func() -> AsyncGenerator[int, None]: + | ^^^^^^^^^^^^^^^^^^^^^^^^^ +32 | yield 42 + | +help: Remove default type arguments +28 | return 42 +29 | +30 | + - async def func() -> AsyncGenerator[int, None]: +31 + async def func() -> AsyncGenerator[int]: +32 | yield 42 +33 | +34 | + +UP043 [*] Unnecessary default type arguments + --> UP043.pyi:47:15 + | +47 | def func() -> Generator[str, None, None]: + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ +48 | yield "hello" + | +help: Remove default type arguments +44 | from typing import Generator, AsyncGenerator +45 | +46 | + - def func() -> Generator[str, None, None]: +47 + def func() -> Generator[str]: +48 | yield "hello" +49 | +50 | + +UP043 [*] Unnecessary default type arguments + --> UP043.pyi:51:21 + | +51 | async def func() -> AsyncGenerator[str, None]: + | ^^^^^^^^^^^^^^^^^^^^^^^^^ +52 | yield "hello" + | +help: Remove default type arguments +48 | yield "hello" +49 | +50 | + - async def func() -> AsyncGenerator[str, None]: +51 + async def func() -> AsyncGenerator[str]: +52 | yield "hello" +53 | +54 | + +UP043 [*] Unnecessary default type arguments + --> UP043.pyi:55:21 + | +55 | async def func() -> AsyncGenerator[ # type: ignore + | _____________________^ +56 | | str, +57 | | None +58 | | ]: + | |_^ +59 | yield "hello" + | +help: Remove default type arguments +52 | yield "hello" +53 | +54 | + - async def func() -> AsyncGenerator[ # type: ignore + - str, + - None + - ]: +55 + async def func() -> AsyncGenerator[str]: +56 | yield "hello" +note: This is an unsafe fix and may change runtime behavior