Treat all typing_extensions members as typing aliases (#9335)

## Summary

Historically, we encoded this list by extracting the `__all__`. I went
to update it, but... is there really any value in it? Seems easier to
just treat `typing_extensions` as an alias for `typing`.

Closes https://github.com/astral-sh/ruff/issues/9334.
This commit is contained in:
Charlie Marsh 2023-12-31 14:23:33 -04:00 committed by GitHub
parent 772e5d587d
commit 195f7c097a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 33 additions and 72 deletions

View file

@ -0,0 +1,5 @@
from typing_extensions import Optional
def f(arg: Optional[int] = None):
pass

View file

@ -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):

View file

@ -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,

View file

@ -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"))]

View file

@ -0,0 +1,4 @@
---
source: crates/ruff_linter/src/rules/ruff/mod.rs
---

View file

@ -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);

View file

@ -1,63 +1,3 @@
/// Returns `true` if a name is a member of Python's `typing_extensions` module.
///
/// See: <https://pypi.org/project/typing-extensions/>
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]`).
///