Skip to content

Commit 36eba9d

Browse files
committed
Merge branch 'main' into dcreager/visconand
* main: [`flake8-pyi`] Fix several correctness issues with `custom-type-var-return-type` (`PYI019`) (#15851) [`pyupgrade`] Reuse replacement logic from `UP046` and `UP047` (`UP040`) (#15840) [`refurb`] Avoid `None | None` as well as better detection and fix (`FURB168`) (#15779) Remove non-existing `lint.extendIgnore` editor setting (#15844) [`refurb`] Mark fix as unsafe if there are comments (`FURB171`) (#15832) [`flake8-comprehensions`] Skip when `TypeError` present from too many (kw)args for `C410`,`C411`, and `C418` (#15838) [`pyflakes`] Visit forward annotations in `TypeAliasType` as types (`F401`) (#15829)
2 parents 93efda5 + 44ac17b commit 36eba9d

33 files changed

+1030
-199
lines changed

crates/ruff_linter/resources/test/fixtures/flake8_comprehensions/C410.py

+4
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,7 @@
1111
list([ # comment
1212
1, 2
1313
])
14+
15+
# Skip when too many positional arguments
16+
# See https://github.com/astral-sh/ruff/issues/15810
17+
list([1],[2])
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,8 @@
11
x = [1, 2, 3]
22
list([i for i in x])
3+
4+
# Skip when too many positional arguments
5+
# or keyword argument present.
6+
# See https://github.com/astral-sh/ruff/issues/15810
7+
list([x for x in "XYZ"],[])
8+
list([x for x in "XYZ"],foo=[])

crates/ruff_linter/resources/test/fixtures/flake8_comprehensions/C418.py

+3
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,6 @@
88
dict({}, a=1)
99
dict({x: 1 for x in range(1)}, a=1)
1010

11+
# Skip when too many positional arguments
12+
# See https://github.com/astral-sh/ruff/issues/15810
13+
dict({"A": 1}, {"B": 2})

crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI019.py

+25
Original file line numberDiff line numberDiff line change
@@ -87,3 +87,28 @@ def instance_method_unbound_with_another_parameter[S](self: S, other: S) -> S: .
8787
def multiple_type_vars[S, *Ts, T](self: S, other: S, /, *args: *Ts, a: T, b: list[T]) -> S: ...
8888

8989
def mixing_old_and_new_style_type_vars[T](self: _S695, a: T, b: T) -> _S695: ...
90+
91+
92+
class InvalidButWeDoNotPanic:
93+
@classmethod
94+
def m[S](cls: type[S], /) -> S[int]: ...
95+
def n(self: S) -> S[int]: ...
96+
97+
98+
import builtins
99+
100+
class UsesFullyQualifiedType:
101+
@classmethod
102+
def m[S](cls: builtins.type[S]) -> S: ... # PYI019
103+
104+
105+
def shadowed_type():
106+
type = 1
107+
class A:
108+
@classmethod
109+
def m[S](cls: type[S]) -> S: ... # no error here
110+
111+
112+
class SubscriptReturnType:
113+
@classmethod
114+
def m[S](cls: type[S]) -> type[S]: ... # PYI019, but no autofix (yet)

crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI019.pyi

+25
Original file line numberDiff line numberDiff line change
@@ -87,3 +87,28 @@ class PEP695Fix:
8787
def multiple_type_vars[S, *Ts, T](self: S, other: S, /, *args: *Ts, a: T, b: list[T]) -> S: ...
8888

8989
def mixing_old_and_new_style_type_vars[T](self: _S695, a: T, b: T) -> _S695: ...
90+
91+
92+
class InvalidButWeDoNotPanic:
93+
@classmethod
94+
def m[S](cls: type[S], /) -> S[int]: ...
95+
def n(self: S) -> S[int]: ...
96+
97+
98+
import builtins
99+
100+
class UsesFullyQualifiedType:
101+
@classmethod
102+
def m[S](cls: builtins.type[S]) -> S: ... # PYI019
103+
104+
105+
def shadowed_type():
106+
type = 1
107+
class A:
108+
@classmethod
109+
def m[S](cls: type[S]) -> S: ... # no error here
110+
111+
112+
class SubscriptReturnType:
113+
@classmethod
114+
def m[S](cls: type[S]) -> type[S]: ... # PYI019, but no autofix (yet)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
"""Regression tests for https://github.com/astral-sh/ruff/issues/15812"""
2+
3+
4+
def f():
5+
from typing import Union
6+
7+
from typing_extensions import TypeAliasType
8+
9+
Json = TypeAliasType(
10+
"Json",
11+
"Union[dict[str, Json], list[Json], str, int, float, bool, None]",
12+
)
13+
14+
15+
def f():
16+
from typing import Union
17+
18+
from typing_extensions import TypeAliasType, TypeVar
19+
20+
T = TypeVar("T")
21+
V = TypeVar("V")
22+
Json = TypeAliasType(
23+
"Json",
24+
"Union[dict[str, Json], list[Json], str, int, float, bool, T, V, None]",
25+
type_params=(T, V),
26+
)
27+
28+
29+
def f():
30+
from typing import Union
31+
32+
from typing_extensions import TypeAliasType
33+
34+
Json = TypeAliasType(
35+
value="Union[dict[str, Json], list[Json], str, int, float, bool, None]",
36+
name="Json",
37+
)
38+
39+
40+
# strictly speaking it's a false positive to emit F401 for both of these, but
41+
# we can't really be expected to understand that the strings here are type
42+
# expressions (and type checkers probably wouldn't understand them as type
43+
# expressions either!)
44+
def f():
45+
from typing import Union
46+
47+
from typing_extensions import TypeAliasType
48+
49+
args = [
50+
"Json",
51+
"Union[dict[str, Json], list[Json], str, int, float, bool, None]",
52+
]
53+
54+
Json = TypeAliasType(*args)
55+
56+
57+
def f():
58+
from typing import Union
59+
60+
from typing_extensions import TypeAliasType
61+
62+
kwargs = {
63+
"name": "Json",
64+
"value": "Union[dict[str, Json], list[Json], str, int, float, bool, None]",
65+
}
66+
67+
Json = TypeAliasType(**kwargs)

crates/ruff_linter/resources/test/fixtures/pyupgrade/UP040.py

+20
Original file line numberDiff line numberDiff line change
@@ -93,3 +93,23 @@ class Foo:
9393
# `default` should be skipped for now, added in Python 3.13
9494
T = typing.TypeVar("T", default=Any)
9595
AnyList = TypeAliasType("AnyList", list[T], typep_params=(T,))
96+
97+
# unsafe fix if comments within the fix
98+
T = TypeVar("T")
99+
PositiveList = TypeAliasType( # eaten comment
100+
"PositiveList", list[Annotated[T, Gt(0)]], type_params=(T,)
101+
)
102+
103+
T = TypeVar("T")
104+
PositiveList = TypeAliasType(
105+
"PositiveList", list[Annotated[T, Gt(0)]], type_params=(T,)
106+
) # this comment should be okay
107+
108+
109+
# this comment will actually be preserved because it's inside the "value" part
110+
T = TypeVar("T")
111+
PositiveList = TypeAliasType(
112+
"PositiveList", list[
113+
Annotated[T, Gt(0)], # preserved comment
114+
], type_params=(T,)
115+
)

crates/ruff_linter/resources/test/fixtures/pyupgrade/UP040.pyi

+7
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,10 @@ from typing import TypeAlias
55
# Fixes in type stub files should be safe to apply unlike in regular code where runtime behavior could change
66
x: typing.TypeAlias = int
77
x: TypeAlias = int
8+
9+
10+
# comments in the value are preserved
11+
x: TypeAlias = tuple[
12+
int, # preserved
13+
float,
14+
]

crates/ruff_linter/resources/test/fixtures/refurb/FURB168.py

+40-4
Original file line numberDiff line numberDiff line change
@@ -5,28 +5,45 @@
55
if isinstance(foo, type(None)):
66
pass
77

8-
if isinstance(foo, (type(None))):
8+
if isinstance(foo and bar, type(None)):
99
pass
1010

1111
if isinstance(foo, (type(None), type(None), type(None))):
1212
pass
1313

14-
if isinstance(foo, None | None):
14+
if isinstance(foo, type(None)) is True:
1515
pass
1616

17-
if isinstance(foo, (None | None)):
17+
if -isinstance(foo, type(None)):
1818
pass
1919

2020
if isinstance(foo, None | type(None)):
2121
pass
2222

23-
if isinstance(foo, (None | type(None))):
23+
if isinstance(foo, type(None) | type(None)):
2424
pass
2525

2626
# A bit contrived, but is both technically valid and equivalent to the above.
2727
if isinstance(foo, (type(None) | ((((type(None))))) | ((None | type(None))))):
2828
pass
2929

30+
if isinstance(
31+
foo, # Comment
32+
None
33+
):
34+
...
35+
36+
from typing import Union
37+
38+
if isinstance(foo, Union[None]):
39+
...
40+
41+
if isinstance(foo, Union[None, None]):
42+
...
43+
44+
if isinstance(foo, Union[None, type(None)]):
45+
...
46+
3047

3148
# Okay.
3249

@@ -42,10 +59,29 @@
4259
if isinstance(foo, (int, type(None), str)):
4360
pass
4461

62+
if isinstance(foo, str | None):
63+
pass
64+
65+
if isinstance(foo, Union[None, str]):
66+
...
67+
4568
# This is a TypeError, which the rule ignores.
4669
if isinstance(foo, None):
4770
pass
4871

4972
# This is also a TypeError, which the rule ignores.
5073
if isinstance(foo, (None,)):
5174
pass
75+
76+
if isinstance(foo, None | None):
77+
pass
78+
79+
if isinstance(foo, (type(None) | ((((type(None))))) | ((None | None | type(None))))):
80+
pass
81+
82+
# https://github.com/astral-sh/ruff/issues/15776
83+
def _():
84+
def type(*args): ...
85+
86+
if isinstance(foo, type(None)):
87+
...

crates/ruff_linter/resources/test/fixtures/refurb/FURB171.py

+71
Original file line numberDiff line numberDiff line change
@@ -46,3 +46,74 @@
4646

4747
if 1 in {*[1]}:
4848
pass
49+
50+
51+
# https://github.com/astral-sh/ruff/issues/10063
52+
_ = a in (
53+
# Foo
54+
b,
55+
)
56+
57+
_ = a in ( # Foo1
58+
( # Foo2
59+
# Foo3
60+
( # Tuple
61+
( # Bar
62+
(b
63+
# Bar
64+
)
65+
)
66+
# Foo4
67+
# Foo5
68+
,
69+
)
70+
# Foo6
71+
)
72+
)
73+
74+
foo = (
75+
lorem()
76+
.ipsum()
77+
.dolor(lambda sit: sit in (
78+
# Foo1
79+
# Foo2
80+
amet,
81+
))
82+
)
83+
84+
foo = (
85+
lorem()
86+
.ipsum()
87+
.dolor(lambda sit: sit in (
88+
(
89+
# Foo1
90+
# Foo2
91+
amet
92+
),
93+
))
94+
)
95+
96+
foo = lorem() \
97+
.ipsum() \
98+
.dolor(lambda sit: sit in (
99+
# Foo1
100+
# Foo2
101+
amet,
102+
))
103+
104+
def _():
105+
if foo not \
106+
in [
107+
# Before
108+
bar
109+
# After
110+
]: ...
111+
112+
def _():
113+
if foo not \
114+
in [
115+
# Before
116+
bar
117+
# After
118+
] and \
119+
0 < 1: ...

crates/ruff_linter/src/checkers/ast/analyze/expression.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -826,7 +826,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
826826
flake8_comprehensions::rules::unnecessary_literal_within_dict_call(checker, call);
827827
}
828828
if checker.enabled(Rule::UnnecessaryListCall) {
829-
flake8_comprehensions::rules::unnecessary_list_call(checker, expr, func, args);
829+
flake8_comprehensions::rules::unnecessary_list_call(checker, expr, call);
830830
}
831831
if checker.enabled(Rule::UnnecessaryCallAroundSorted) {
832832
flake8_comprehensions::rules::unnecessary_call_around_sorted(

crates/ruff_linter/src/checkers/ast/mod.rs

+26-3
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,9 @@ use ruff_python_ast::name::QualifiedName;
4141
use ruff_python_ast::str::Quote;
4242
use ruff_python_ast::visitor::{walk_except_handler, walk_pattern, Visitor};
4343
use ruff_python_ast::{
44-
self as ast, AnyParameterRef, Comprehension, ElifElseClause, ExceptHandler, Expr, ExprContext,
45-
FStringElement, Keyword, MatchCase, ModModule, Parameter, Parameters, Pattern, Stmt, Suite,
46-
UnaryOp,
44+
self as ast, AnyParameterRef, ArgOrKeyword, Comprehension, ElifElseClause, ExceptHandler, Expr,
45+
ExprContext, FStringElement, Keyword, MatchCase, ModModule, Parameter, Parameters, Pattern,
46+
Stmt, Suite, UnaryOp,
4747
};
4848
use ruff_python_ast::{helpers, str, visitor, PySourceType};
4949
use ruff_python_codegen::{Generator, Stylist};
@@ -1269,6 +1269,11 @@ impl<'a> Visitor<'a> for Checker<'a> {
12691269
.match_typing_qualified_name(&qualified_name, "TypeVar")
12701270
{
12711271
Some(typing::Callable::TypeVar)
1272+
} else if self
1273+
.semantic
1274+
.match_typing_qualified_name(&qualified_name, "TypeAliasType")
1275+
{
1276+
Some(typing::Callable::TypeAliasType)
12721277
} else if self
12731278
.semantic
12741279
.match_typing_qualified_name(&qualified_name, "NamedTuple")
@@ -1354,6 +1359,24 @@ impl<'a> Visitor<'a> for Checker<'a> {
13541359
}
13551360
}
13561361
}
1362+
Some(typing::Callable::TypeAliasType) => {
1363+
// Ex) TypeAliasType("Json", "Union[dict[str, Json]]", type_params=())
1364+
for (i, arg) in arguments.arguments_source_order().enumerate() {
1365+
match (i, arg) {
1366+
(1, ArgOrKeyword::Arg(arg)) => self.visit_type_definition(arg),
1367+
(_, ArgOrKeyword::Arg(arg)) => self.visit_non_type_definition(arg),
1368+
(_, ArgOrKeyword::Keyword(Keyword { arg, value, .. })) => {
1369+
if let Some(id) = arg {
1370+
if matches!(&**id, "value" | "type_params") {
1371+
self.visit_type_definition(value);
1372+
} else {
1373+
self.visit_non_type_definition(value);
1374+
}
1375+
}
1376+
}
1377+
}
1378+
}
1379+
}
13571380
Some(typing::Callable::NamedTuple) => {
13581381
// Ex) NamedTuple("a", [("a", int)])
13591382
let mut args = arguments.args.iter();

0 commit comments

Comments
 (0)