Skip to content

Commit b41099e

Browse files
committed
Merge branch 'main' into dcreager/relative-nested-imports
* main: [red-knot] Explicitly test diagnostics are emitted for unresolvable submodule imports (#15035) Fix stale File status in tests (#15030) [red-knot] Basic support for other legacy `typing` aliases (#14998) feat(AIR302): extend the following rules (#15015) [`perflint`] Simplify finding the loop target in `PERF401` (#15025) [red-knot] Avoid undeclared path when raising conflicting declarations (#14958)
2 parents c5fd3c6 + 463046a commit b41099e

File tree

18 files changed

+1465
-648
lines changed

18 files changed

+1465
-648
lines changed

crates/red_knot_python_semantic/resources/mdtest/annotations/stdlib_typing_aliases.md

Lines changed: 88 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -3,43 +3,59 @@
33
The `typing` module has various aliases to other stdlib classes. These are a legacy feature, but
44
still need to be supported by a type checker.
55

6-
## Currently unsupported
6+
## Correspondence
77

8-
Support for most of these symbols is currently a TODO:
8+
All of the following symbols can be mapped one-to-one with the actual type:
99

1010
```py
1111
import typing
1212

1313
def f(
14-
a: typing.List,
15-
b: typing.List[int],
16-
c: typing.Dict,
17-
d: typing.Dict[int, str],
18-
e: typing.DefaultDict,
19-
f: typing.DefaultDict[str, int],
20-
g: typing.Set,
21-
h: typing.Set[int],
22-
i: typing.FrozenSet,
23-
j: typing.FrozenSet[str],
24-
k: typing.OrderedDict,
25-
l: typing.OrderedDict[int, str],
26-
m: typing.Counter,
27-
n: typing.Counter[int],
14+
list_bare: typing.List,
15+
list_parametrized: typing.List[int],
16+
dict_bare: typing.Dict,
17+
dict_parametrized: typing.Dict[int, str],
18+
set_bare: typing.Set,
19+
set_parametrized: typing.Set[int],
20+
frozen_set_bare: typing.FrozenSet,
21+
frozen_set_parametrized: typing.FrozenSet[str],
22+
chain_map_bare: typing.ChainMap,
23+
chain_map_parametrized: typing.ChainMap[int],
24+
counter_bare: typing.Counter,
25+
counter_parametrized: typing.Counter[int],
26+
default_dict_bare: typing.DefaultDict,
27+
default_dict_parametrized: typing.DefaultDict[str, int],
28+
deque_bare: typing.Deque,
29+
deque_parametrized: typing.Deque[str],
30+
ordered_dict_bare: typing.OrderedDict,
31+
ordered_dict_parametrized: typing.OrderedDict[int, str],
2832
):
29-
reveal_type(a) # revealed: @Todo(Unsupported or invalid type in a type expression)
30-
reveal_type(b) # revealed: @Todo(typing.List alias)
31-
reveal_type(c) # revealed: @Todo(Unsupported or invalid type in a type expression)
32-
reveal_type(d) # revealed: @Todo(typing.Dict alias)
33-
reveal_type(e) # revealed: @Todo(Unsupported or invalid type in a type expression)
34-
reveal_type(f) # revealed: @Todo(typing.DefaultDict[] alias)
35-
reveal_type(g) # revealed: @Todo(Unsupported or invalid type in a type expression)
36-
reveal_type(h) # revealed: @Todo(typing.Set alias)
37-
reveal_type(i) # revealed: @Todo(Unsupported or invalid type in a type expression)
38-
reveal_type(j) # revealed: @Todo(typing.FrozenSet alias)
39-
reveal_type(k) # revealed: @Todo(Unsupported or invalid type in a type expression)
40-
reveal_type(l) # revealed: @Todo(typing.OrderedDict alias)
41-
reveal_type(m) # revealed: @Todo(Unsupported or invalid type in a type expression)
42-
reveal_type(n) # revealed: @Todo(typing.Counter[] alias)
33+
reveal_type(list_bare) # revealed: list
34+
reveal_type(list_parametrized) # revealed: list
35+
36+
reveal_type(dict_bare) # revealed: dict
37+
reveal_type(dict_parametrized) # revealed: dict
38+
39+
reveal_type(set_bare) # revealed: set
40+
reveal_type(set_parametrized) # revealed: set
41+
42+
reveal_type(frozen_set_bare) # revealed: frozenset
43+
reveal_type(frozen_set_parametrized) # revealed: frozenset
44+
45+
reveal_type(chain_map_bare) # revealed: ChainMap
46+
reveal_type(chain_map_parametrized) # revealed: ChainMap
47+
48+
reveal_type(counter_bare) # revealed: Counter
49+
reveal_type(counter_parametrized) # revealed: Counter
50+
51+
reveal_type(default_dict_bare) # revealed: defaultdict
52+
reveal_type(default_dict_parametrized) # revealed: defaultdict
53+
54+
reveal_type(deque_bare) # revealed: deque
55+
reveal_type(deque_parametrized) # revealed: deque
56+
57+
reveal_type(ordered_dict_bare) # revealed: OrderedDict
58+
reveal_type(ordered_dict_parametrized) # revealed: OrderedDict
4359
```
4460

4561
## Inheritance
@@ -49,35 +65,63 @@ The aliases can be inherited from. Some of these are still partially or wholly T
4965
```py
5066
import typing
5167

52-
class A(typing.Dict): ...
68+
####################
69+
### Built-ins
70+
71+
class ListSubclass(typing.List): ...
5372

5473
# TODO: should have `Generic`, should not have `Unknown`
55-
reveal_type(A.__mro__) # revealed: tuple[Literal[A], Literal[dict], Unknown, Literal[object]]
74+
# revealed: tuple[Literal[ListSubclass], Literal[list], Unknown, Literal[object]]
75+
reveal_type(ListSubclass.__mro__)
5676

57-
class B(typing.List): ...
77+
class DictSubclass(typing.Dict): ...
5878

5979
# TODO: should have `Generic`, should not have `Unknown`
60-
reveal_type(B.__mro__) # revealed: tuple[Literal[B], Literal[list], Unknown, Literal[object]]
80+
# revealed: tuple[Literal[DictSubclass], Literal[dict], Unknown, Literal[object]]
81+
reveal_type(DictSubclass.__mro__)
6182

62-
class C(typing.Set): ...
83+
class SetSubclass(typing.Set): ...
6384

6485
# TODO: should have `Generic`, should not have `Unknown`
65-
reveal_type(C.__mro__) # revealed: tuple[Literal[C], Literal[set], Unknown, Literal[object]]
86+
# revealed: tuple[Literal[SetSubclass], Literal[set], Unknown, Literal[object]]
87+
reveal_type(SetSubclass.__mro__)
6688

67-
class D(typing.FrozenSet): ...
89+
class FrozenSetSubclass(typing.FrozenSet): ...
6890

6991
# TODO: should have `Generic`, should not have `Unknown`
70-
reveal_type(D.__mro__) # revealed: tuple[Literal[D], Literal[frozenset], Unknown, Literal[object]]
92+
# revealed: tuple[Literal[FrozenSetSubclass], Literal[frozenset], Unknown, Literal[object]]
93+
reveal_type(FrozenSetSubclass.__mro__)
94+
95+
####################
96+
### `collections`
97+
98+
class ChainMapSubclass(typing.ChainMap): ...
99+
100+
# TODO: Should be (ChainMapSubclass, ChainMap, MutableMapping, Mapping, Collection, Sized, Iterable, Container, Generic, object)
101+
# revealed: tuple[Literal[ChainMapSubclass], Literal[ChainMap], Unknown, Literal[object]]
102+
reveal_type(ChainMapSubclass.__mro__)
103+
104+
class CounterSubclass(typing.Counter): ...
105+
106+
# TODO: Should be (CounterSubclass, Counter, dict, MutableMapping, Mapping, Collection, Sized, Iterable, Container, Generic, object)
107+
# revealed: tuple[Literal[CounterSubclass], Literal[Counter], Unknown, Literal[object]]
108+
reveal_type(CounterSubclass.__mro__)
71109

72-
class E(typing.DefaultDict): ...
110+
class DefaultDictSubclass(typing.DefaultDict): ...
73111

74-
reveal_type(E.__mro__) # revealed: tuple[Literal[E], @Todo(Support for more typing aliases as base classes), Literal[object]]
112+
# TODO: Should be (DefaultDictSubclass, defaultdict, dict, MutableMapping, Mapping, Collection, Sized, Iterable, Container, Generic, object)
113+
# revealed: tuple[Literal[DefaultDictSubclass], Literal[defaultdict], Unknown, Literal[object]]
114+
reveal_type(DefaultDictSubclass.__mro__)
75115

76-
class F(typing.OrderedDict): ...
116+
class DequeSubclass(typing.Deque): ...
77117

78-
reveal_type(F.__mro__) # revealed: tuple[Literal[F], @Todo(Support for more typing aliases as base classes), Literal[object]]
118+
# TODO: Should be (DequeSubclass, deque, MutableSequence, Sequence, Reversible, Collection, Sized, Iterable, Container, Generic, object)
119+
# revealed: tuple[Literal[DequeSubclass], Literal[deque], Unknown, Literal[object]]
120+
reveal_type(DequeSubclass.__mro__)
79121

80-
class G(typing.Counter): ...
122+
class OrderedDictSubclass(typing.OrderedDict): ...
81123

82-
reveal_type(G.__mro__) # revealed: tuple[Literal[G], @Todo(Support for more typing aliases as base classes), Literal[object]]
124+
# TODO: Should be (OrderedDictSubclass, OrderedDict, dict, MutableMapping, Mapping, Collection, Sized, Iterable, Container, Generic, object)
125+
# revealed: tuple[Literal[OrderedDictSubclass], Literal[OrderedDict], Unknown, Literal[object]]
126+
reveal_type(OrderedDictSubclass.__mro__)
83127
```

crates/red_knot_python_semantic/resources/mdtest/annotations/unsupported_special_forms.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ class D(TypeIs): ... # error: [invalid-base]
5151
class E(Concatenate): ... # error: [invalid-base]
5252
class F(Callable): ...
5353

54-
reveal_type(F.__mro__) # revealed: tuple[Literal[F], @Todo(Support for more typing aliases as base classes), Literal[object]]
54+
reveal_type(F.__mro__) # revealed: tuple[Literal[F], @Todo(Support for Callable as a base class), Literal[object]]
5555
```
5656

5757
## Subscriptability

crates/red_knot_python_semantic/resources/mdtest/call/callable_instance.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,6 @@ def _(flag: bool):
6767
def __call__(self) -> int: ...
6868

6969
a = NonCallable()
70-
# error: "Object of type `Literal[1] | Literal[__call__]` is not callable (due to union element `Literal[1]`)"
71-
reveal_type(a()) # revealed: Unknown | int
70+
# error: "Object of type `Literal[__call__] | Literal[1]` is not callable (due to union element `Literal[1]`)"
71+
reveal_type(a()) # revealed: int | Unknown
7272
```

crates/red_knot_python_semantic/resources/mdtest/declaration/error.md

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,17 @@ def _(flag: bool):
1919
x = 1 # error: [conflicting-declarations] "Conflicting declared types for `x`: str, int"
2020
```
2121

22-
## Partial declarations
22+
## Incompatible declarations for 2 (out of 3) types
2323

2424
```py
25-
def _(flag: bool):
26-
if flag:
25+
def _(flag1: bool, flag2: bool):
26+
if flag1:
27+
x: str
28+
elif flag2:
2729
x: int
2830

29-
x = 1 # error: [conflicting-declarations] "Conflicting declared types for `x`: Unknown, int"
31+
# Here, the declared type for `x` is `int | str | Unknown`.
32+
x = 1 # error: [conflicting-declarations] "Conflicting declared types for `x`: str, int"
3033
```
3134

3235
## Incompatible declarations with bad assignment
@@ -42,3 +45,31 @@ def _(flag: bool):
4245
# error: [invalid-assignment]
4346
x = b"foo"
4447
```
48+
49+
## No errors
50+
51+
Currently, we avoid raising the conflicting-declarations for the following cases:
52+
53+
### Partial declarations
54+
55+
```py
56+
def _(flag: bool):
57+
if flag:
58+
x: int
59+
60+
x = 1
61+
```
62+
63+
### Partial declarations in try-except
64+
65+
Refer to <https://github.com/astral-sh/ruff/issues/13966>
66+
67+
```py
68+
def _():
69+
try:
70+
x: int = 1
71+
except:
72+
x = 2
73+
74+
x = 3
75+
```

crates/red_knot_python_semantic/resources/mdtest/import/basic.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,3 +91,16 @@ reveal_type(c.C) # revealed: Literal[C]
9191
```py path=a/b/c.py
9292
class C: ...
9393
```
94+
95+
## Unresolvable submodule imports
96+
97+
```py
98+
# Topmost component resolvable, submodule not resolvable:
99+
import a.foo # error: [unresolved-import] "Cannot resolve import `a.foo`"
100+
101+
# Topmost component unresolvable:
102+
import b.foo # error: [unresolved-import] "Cannot resolve import `b.foo`"
103+
```
104+
105+
```py path=a/__init__.py
106+
```

crates/red_knot_python_semantic/src/module_resolver/resolver.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,15 @@ enum SystemOrVendoredPathRef<'a> {
7373
Vendored(&'a VendoredPath),
7474
}
7575

76+
impl std::fmt::Display for SystemOrVendoredPathRef<'_> {
77+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
78+
match self {
79+
SystemOrVendoredPathRef::System(system) => system.fmt(f),
80+
SystemOrVendoredPathRef::Vendored(vendored) => vendored.fmt(f),
81+
}
82+
}
83+
}
84+
7685
/// Resolves the module for the file with the given id.
7786
///
7887
/// Returns `None` if the file is not a module locatable via any of the known search paths.

crates/red_knot_python_semantic/src/stdlib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ pub(crate) enum CoreStdlibModule {
1717
Sys,
1818
#[allow(dead_code)]
1919
Abc, // currently only used in tests
20+
Collections,
2021
}
2122

2223
impl CoreStdlibModule {
@@ -29,6 +30,7 @@ impl CoreStdlibModule {
2930
Self::TypingExtensions => "typing_extensions",
3031
Self::Sys => "sys",
3132
Self::Abc => "abc",
33+
Self::Collections => "collections",
3234
}
3335
}
3436

0 commit comments

Comments
 (0)