diff --git a/crates/ruff_linter/resources/test/fixtures/ruff/RUF013_2.py b/crates/ruff_linter/resources/test/fixtures/ruff/RUF013_2.py new file mode 100644 index 00000000000000..94ac861eb5bdaf --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/ruff/RUF013_2.py @@ -0,0 +1,5 @@ +from typing_extensions import Optional + + +def f(arg: Optional[int] = None): + pass diff --git a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI050_PYI050.py.snap b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI050_PYI050.py.snap index 11b5d19b5ebd76..1f70291a11d983 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI050_PYI050.py.snap +++ b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI050_PYI050.py.snap @@ -8,6 +8,15 @@ PYI050.py:13:24: PYI050 Prefer `typing.Never` over `NoReturn` for argument annot 14 | ... | +PYI050.py:18:10: PYI050 Prefer `typing.Never` over `NoReturn` for argument annotations + | +17 | def foo_no_return_typing_extensions( +18 | arg: typing_extensions.NoReturn, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI050 +19 | ): +20 | ... + | + PYI050.py:23:44: PYI050 Prefer `typing.Never` over `NoReturn` for argument annotations | 23 | def foo_no_return_kwarg(arg: int, *, arg2: NoReturn): diff --git a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI050_PYI050.pyi.snap b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI050_PYI050.pyi.snap index caafc0254ea8c8..9e6e3ea29006cb 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI050_PYI050.pyi.snap +++ b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI050_PYI050.pyi.snap @@ -11,6 +11,16 @@ PYI050.pyi:6:24: PYI050 Prefer `typing.Never` over `NoReturn` for argument annot 8 | arg: typing_extensions.NoReturn, | +PYI050.pyi:8:10: PYI050 Prefer `typing.Never` over `NoReturn` for argument annotations + | + 6 | def foo_no_return(arg: NoReturn): ... # Error: PYI050 + 7 | def foo_no_return_typing_extensions( + 8 | arg: typing_extensions.NoReturn, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI050 + 9 | ): ... # Error: PYI050 +10 | def foo_no_return_kwarg(arg: int, *, arg2: NoReturn): ... # Error: PYI050 + | + PYI050.pyi:10:44: PYI050 Prefer `typing.Never` over `NoReturn` for argument annotations | 8 | arg: typing_extensions.NoReturn, diff --git a/crates/ruff_linter/src/rules/ruff/mod.rs b/crates/ruff_linter/src/rules/ruff/mod.rs index 2c26ab0bcb33f6..d11722ea34fef1 100644 --- a/crates/ruff_linter/src/rules/ruff/mod.rs +++ b/crates/ruff_linter/src/rules/ruff/mod.rs @@ -27,6 +27,7 @@ mod tests { #[test_case(Rule::FunctionCallInDataclassDefaultArgument, Path::new("RUF009.py"))] #[test_case(Rule::ImplicitOptional, Path::new("RUF013_0.py"))] #[test_case(Rule::ImplicitOptional, Path::new("RUF013_1.py"))] + #[test_case(Rule::ImplicitOptional, Path::new("RUF013_2.py"))] #[test_case(Rule::MutableClassDefault, Path::new("RUF012.py"))] #[test_case(Rule::MutableDataclassDefault, Path::new("RUF008.py"))] #[test_case(Rule::PairwiseOverZipped, Path::new("RUF007.py"))] diff --git a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF013_RUF013_2.py.snap b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF013_RUF013_2.py.snap new file mode 100644 index 00000000000000..7f58cfd7246a31 --- /dev/null +++ b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF013_RUF013_2.py.snap @@ -0,0 +1,4 @@ +--- +source: crates/ruff_linter/src/rules/ruff/mod.rs +--- + diff --git a/crates/ruff_python_semantic/src/model.rs b/crates/ruff_python_semantic/src/model.rs index 1f9d3715ef1cab..b5d076097cbc0e 100644 --- a/crates/ruff_python_semantic/src/model.rs +++ b/crates/ruff_python_semantic/src/model.rs @@ -8,7 +8,6 @@ use ruff_python_ast::call_path::{collect_call_path, from_unqualified_name, CallP use ruff_python_ast::helpers::from_relative_import; use ruff_python_ast::{self as ast, Expr, Operator, Stmt}; use ruff_python_stdlib::path::is_python_stub_file; -use ruff_python_stdlib::typing::is_typing_extension; use ruff_text_size::{Ranged, TextRange, TextSize}; use crate::binding::{ @@ -175,20 +174,13 @@ impl<'a> SemanticModel<'a> { /// Return `true` if the call path is a reference to `typing.${target}`. pub fn match_typing_call_path(&self, call_path: &CallPath, target: &str) -> bool { - if call_path.as_slice() == ["typing", target] { + if matches!( + call_path.as_slice(), + ["typing" | "_typeshed" | "typing_extensions", member] if *member == target + ) { return true; } - if call_path.as_slice() == ["_typeshed", target] { - return true; - } - - if is_typing_extension(target) { - if call_path.as_slice() == ["typing_extensions", target] { - return true; - } - } - if self.typing_modules.iter().any(|module| { let mut module: CallPath = from_unqualified_name(module); module.push(target); diff --git a/crates/ruff_python_stdlib/src/typing.rs b/crates/ruff_python_stdlib/src/typing.rs index d210964efbebcf..45b472ce707d6f 100644 --- a/crates/ruff_python_stdlib/src/typing.rs +++ b/crates/ruff_python_stdlib/src/typing.rs @@ -1,63 +1,3 @@ -/// Returns `true` if a name is a member of Python's `typing_extensions` module. -/// -/// See: -pub fn is_typing_extension(member: &str) -> bool { - matches!( - member, - "Annotated" - | "Any" - | "AsyncContextManager" - | "AsyncGenerator" - | "AsyncIterable" - | "AsyncIterator" - | "Awaitable" - | "ChainMap" - | "ClassVar" - | "Concatenate" - | "ContextManager" - | "Coroutine" - | "Counter" - | "DefaultDict" - | "Deque" - | "Final" - | "Literal" - | "LiteralString" - | "NamedTuple" - | "Never" - | "NewType" - | "NotRequired" - | "OrderedDict" - | "ParamSpec" - | "ParamSpecArgs" - | "ParamSpecKwargs" - | "Protocol" - | "Required" - | "Self" - | "TYPE_CHECKING" - | "Text" - | "Type" - | "TypeAlias" - | "TypeGuard" - | "TypeVar" - | "TypeVarTuple" - | "TypedDict" - | "Unpack" - | "assert_never" - | "assert_type" - | "clear_overloads" - | "final" - | "get_type_hints" - | "get_args" - | "get_origin" - | "get_overloads" - | "is_typeddict" - | "overload" - | "override" - | "reveal_type" - | "runtime_checkable" - ) -} - /// Returns `true` if a call path is a generic from the Python standard library (e.g. `list`, which /// can be used as `list[int]`). ///