Skip to content

Commit 7cce514

Browse files
committed
Merge remote-tracking branch 'origin/main' into dcreager/generic-constructor
* origin/main: [red-knot] Trust module-level undeclared symbols in stubs (#17577) [`airflow`] Apply auto fixes to cases where the names have changed in Airflow 3 (`AIR301`) (#17355) [`pycodestyle`] Auto-fix redundant boolean comparison (`E712`) (#17090) [red-knot] Detect semantic syntax errors (#17463) Fix stale diagnostics in Ruff playground (#17583) [red-knot] Early return from `project.is_file_open` for vendored files (#17580)
2 parents 43ce8d5 + e91e2f4 commit 7cce514

34 files changed

+1647
-835
lines changed

crates/red_knot_project/src/lib.rs

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -314,20 +314,23 @@ impl Project {
314314
/// * It has a [`SystemPath`] and belongs to a package's `src` files
315315
/// * It has a [`SystemVirtualPath`](ruff_db::system::SystemVirtualPath)
316316
pub fn is_file_open(self, db: &dyn Db, file: File) -> bool {
317+
let path = file.path(db);
318+
319+
// Try to return early to avoid adding a dependency on `open_files` or `file_set` which
320+
// both have a durability of `LOW`.
321+
if path.is_vendored_path() {
322+
return false;
323+
}
324+
317325
if let Some(open_files) = self.open_files(db) {
318326
open_files.contains(&file)
319327
} else if file.path(db).is_system_path() {
320-
self.contains_file(db, file)
328+
self.files(db).contains(&file)
321329
} else {
322330
file.path(db).is_system_virtual_path()
323331
}
324332
}
325333

326-
/// Returns `true` if `file` is a first-party file part of this package.
327-
pub fn contains_file(self, db: &dyn Db, file: File) -> bool {
328-
self.files(db).contains(&file)
329-
}
330-
331334
#[tracing::instrument(level = "debug", skip(self, db))]
332335
pub fn remove_file(self, db: &mut dyn Db, file: File) {
333336
tracing::debug!(

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

Lines changed: 35 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -56,40 +56,41 @@ def _(
5656
def bar() -> None:
5757
return None
5858

59-
def _(
60-
a: 1, # error: [invalid-type-form] "Int literals are not allowed in this context in a type expression"
61-
b: 2.3, # error: [invalid-type-form] "Float literals are not allowed in type expressions"
62-
c: 4j, # error: [invalid-type-form] "Complex literals are not allowed in type expressions"
63-
d: True, # error: [invalid-type-form] "Boolean literals are not allowed in this context in a type expression"
64-
e: int | b"foo", # error: [invalid-type-form] "Bytes literals are not allowed in this context in a type expression"
65-
f: 1 and 2, # error: [invalid-type-form] "Boolean operations are not allowed in type expressions"
66-
g: 1 or 2, # error: [invalid-type-form] "Boolean operations are not allowed in type expressions"
67-
h: (foo := 1), # error: [invalid-type-form] "Named expressions are not allowed in type expressions"
68-
i: not 1, # error: [invalid-type-form] "Unary operations are not allowed in type expressions"
69-
j: lambda: 1, # error: [invalid-type-form] "`lambda` expressions are not allowed in type expressions"
70-
k: 1 if True else 2, # error: [invalid-type-form] "`if` expressions are not allowed in type expressions"
71-
l: await 1, # error: [invalid-type-form] "`await` expressions are not allowed in type expressions"
72-
m: (yield 1), # error: [invalid-type-form] "`yield` expressions are not allowed in type expressions"
73-
n: (yield from [1]), # error: [invalid-type-form] "`yield from` expressions are not allowed in type expressions"
74-
o: 1 < 2, # error: [invalid-type-form] "Comparison expressions are not allowed in type expressions"
75-
p: bar(), # error: [invalid-type-form] "Function calls are not allowed in type expressions"
76-
q: int | f"foo", # error: [invalid-type-form] "F-strings are not allowed in type expressions"
77-
r: [1, 2, 3][1:2], # error: [invalid-type-form] "Slices are not allowed in type expressions"
78-
):
79-
reveal_type(a) # revealed: Unknown
80-
reveal_type(b) # revealed: Unknown
81-
reveal_type(c) # revealed: Unknown
82-
reveal_type(d) # revealed: Unknown
83-
reveal_type(e) # revealed: int | Unknown
84-
reveal_type(f) # revealed: Unknown
85-
reveal_type(g) # revealed: Unknown
86-
reveal_type(h) # revealed: Unknown
87-
reveal_type(i) # revealed: Unknown
88-
reveal_type(j) # revealed: Unknown
89-
reveal_type(k) # revealed: Unknown
90-
reveal_type(p) # revealed: Unknown
91-
reveal_type(q) # revealed: int | Unknown
92-
reveal_type(r) # revealed: @Todo(unknown type subscript)
59+
async def outer(): # avoid unrelated syntax errors on yield, yield from, and await
60+
def _(
61+
a: 1, # error: [invalid-type-form] "Int literals are not allowed in this context in a type expression"
62+
b: 2.3, # error: [invalid-type-form] "Float literals are not allowed in type expressions"
63+
c: 4j, # error: [invalid-type-form] "Complex literals are not allowed in type expressions"
64+
d: True, # error: [invalid-type-form] "Boolean literals are not allowed in this context in a type expression"
65+
e: int | b"foo", # error: [invalid-type-form] "Bytes literals are not allowed in this context in a type expression"
66+
f: 1 and 2, # error: [invalid-type-form] "Boolean operations are not allowed in type expressions"
67+
g: 1 or 2, # error: [invalid-type-form] "Boolean operations are not allowed in type expressions"
68+
h: (foo := 1), # error: [invalid-type-form] "Named expressions are not allowed in type expressions"
69+
i: not 1, # error: [invalid-type-form] "Unary operations are not allowed in type expressions"
70+
j: lambda: 1, # error: [invalid-type-form] "`lambda` expressions are not allowed in type expressions"
71+
k: 1 if True else 2, # error: [invalid-type-form] "`if` expressions are not allowed in type expressions"
72+
l: await 1, # error: [invalid-type-form] "`await` expressions are not allowed in type expressions"
73+
m: (yield 1), # error: [invalid-type-form] "`yield` expressions are not allowed in type expressions"
74+
n: (yield from [1]), # error: [invalid-type-form] "`yield from` expressions are not allowed in type expressions"
75+
o: 1 < 2, # error: [invalid-type-form] "Comparison expressions are not allowed in type expressions"
76+
p: bar(), # error: [invalid-type-form] "Function calls are not allowed in type expressions"
77+
q: int | f"foo", # error: [invalid-type-form] "F-strings are not allowed in type expressions"
78+
r: [1, 2, 3][1:2], # error: [invalid-type-form] "Slices are not allowed in type expressions"
79+
):
80+
reveal_type(a) # revealed: Unknown
81+
reveal_type(b) # revealed: Unknown
82+
reveal_type(c) # revealed: Unknown
83+
reveal_type(d) # revealed: Unknown
84+
reveal_type(e) # revealed: int | Unknown
85+
reveal_type(f) # revealed: Unknown
86+
reveal_type(g) # revealed: Unknown
87+
reveal_type(h) # revealed: Unknown
88+
reveal_type(i) # revealed: Unknown
89+
reveal_type(j) # revealed: Unknown
90+
reveal_type(k) # revealed: Unknown
91+
reveal_type(p) # revealed: Unknown
92+
reveal_type(q) # revealed: int | Unknown
93+
reveal_type(r) # revealed: @Todo(unknown type subscript)
9394
```
9495

9596
## Invalid Collection based AST nodes

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

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -127,8 +127,9 @@ class AsyncIterable:
127127
def __aiter__(self) -> AsyncIterator:
128128
return AsyncIterator()
129129

130-
# revealed: @Todo(async iterables/iterators)
131-
[reveal_type(x) async for x in AsyncIterable()]
130+
async def _():
131+
# revealed: @Todo(async iterables/iterators)
132+
[reveal_type(x) async for x in AsyncIterable()]
132133
```
133134

134135
### Invalid async comprehension
@@ -145,6 +146,7 @@ class Iterable:
145146
def __iter__(self) -> Iterator:
146147
return Iterator()
147148

148-
# revealed: @Todo(async iterables/iterators)
149-
[reveal_type(x) async for x in Iterable()]
149+
async def _():
150+
# revealed: @Todo(async iterables/iterators)
151+
[reveal_type(x) async for x in Iterable()]
150152
```
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
# Semantic syntax error diagnostics
2+
3+
## `async` comprehensions in synchronous comprehensions
4+
5+
### Python 3.10
6+
7+
<!-- snapshot-diagnostics -->
8+
9+
Before Python 3.11, `async` comprehensions could not be used within outer sync comprehensions, even
10+
within an `async` function ([CPython issue](https://github.com/python/cpython/issues/77527)):
11+
12+
```toml
13+
[environment]
14+
python-version = "3.10"
15+
```
16+
17+
```py
18+
async def elements(n):
19+
yield n
20+
21+
async def f():
22+
# error: 19 [invalid-syntax] "cannot use an asynchronous comprehension outside of an asynchronous function on Python 3.10 (syntax was added in 3.11)"
23+
return {n: [x async for x in elements(n)] for n in range(3)}
24+
```
25+
26+
If all of the comprehensions are `async`, on the other hand, the code was still valid:
27+
28+
```py
29+
async def test():
30+
return [[x async for x in elements(n)] async for n in range(3)]
31+
```
32+
33+
These are a couple of tricky but valid cases to check that nested scope handling is wired up
34+
correctly in the `SemanticSyntaxContext` trait:
35+
36+
```py
37+
async def f():
38+
[x for x in [1]] and [x async for x in elements(1)]
39+
40+
async def f():
41+
def g():
42+
pass
43+
[x async for x in elements(1)]
44+
```
45+
46+
### Python 3.11
47+
48+
All of these same examples are valid after Python 3.11:
49+
50+
```toml
51+
[environment]
52+
python-version = "3.11"
53+
```
54+
55+
```py
56+
async def elements(n):
57+
yield n
58+
59+
async def f():
60+
return {n: [x async for x in elements(n)] for n in range(3)}
61+
```
62+
63+
## Late `__future__` import
64+
65+
```py
66+
from collections import namedtuple
67+
68+
# error: [invalid-syntax] "__future__ imports must be at the top of the file"
69+
from __future__ import print_function
70+
```
71+
72+
## Invalid annotation
73+
74+
This one might be a bit redundant with the `invalid-type-form` error.
75+
76+
```toml
77+
[environment]
78+
python-version = "3.12"
79+
```
80+
81+
```py
82+
from __future__ import annotations
83+
84+
# error: [invalid-type-form] "Named expressions are not allowed in type expressions"
85+
# error: [invalid-syntax] "named expression cannot be used within a type annotation"
86+
def f() -> (y := 3): ...
87+
```
88+
89+
## Duplicate `match` key
90+
91+
```toml
92+
[environment]
93+
python-version = "3.10"
94+
```
95+
96+
```py
97+
match 2:
98+
# error: [invalid-syntax] "mapping pattern checks duplicate key `"x"`"
99+
case {"x": 1, "x": 2}:
100+
...
101+
```
102+
103+
## `return`, `yield`, `yield from`, and `await` outside function
104+
105+
```py
106+
# error: [invalid-syntax] "`return` statement outside of a function"
107+
return
108+
109+
# error: [invalid-syntax] "`yield` statement outside of a function"
110+
yield
111+
112+
# error: [invalid-syntax] "`yield from` statement outside of a function"
113+
yield from []
114+
115+
# error: [invalid-syntax] "`await` statement outside of a function"
116+
# error: [invalid-syntax] "`await` outside of an asynchronous function"
117+
await 1
118+
119+
def f():
120+
# error: [invalid-syntax] "`await` outside of an asynchronous function"
121+
await 1
122+
```
123+
124+
Generators are evaluated lazily, so `await` is allowed, even outside of a function.
125+
126+
```py
127+
async def g():
128+
yield 1
129+
130+
(x async for x in g())
131+
```
132+
133+
## `await` outside async function
134+
135+
This error includes `await`, `async for`, `async with`, and `async` comprehensions.
136+
137+
```python
138+
async def elements(n):
139+
yield n
140+
141+
def _():
142+
# error: [invalid-syntax] "`await` outside of an asynchronous function"
143+
await 1
144+
# error: [invalid-syntax] "`async for` outside of an asynchronous function"
145+
async for _ in elements(1):
146+
...
147+
# error: [invalid-syntax] "`async with` outside of an asynchronous function"
148+
async with elements(1) as x:
149+
...
150+
# error: [invalid-syntax] "cannot use an asynchronous comprehension outside of an asynchronous function on Python 3.9 (syntax was added in 3.11)"
151+
# error: [invalid-syntax] "asynchronous comprehension outside of an asynchronous function"
152+
[x async for x in elements(1)]
153+
```
154+
155+
## Load before `global` declaration
156+
157+
This should be an error, but it's not yet.
158+
159+
TODO implement `SemanticSyntaxContext::global`
160+
161+
```py
162+
def f():
163+
x = 1
164+
global x
165+
```

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ match 42:
189189
...
190190
case [O]:
191191
...
192-
case P | Q:
192+
case P | Q: # error: [invalid-syntax] "name capture `P` makes remaining patterns unreachable"
193193
...
194194
case object(foo=R):
195195
...
@@ -289,7 +289,7 @@ match 42:
289289
...
290290
case [D]:
291291
...
292-
case E | F:
292+
case E | F: # error: [invalid-syntax] "name capture `E` makes remaining patterns unreachable"
293293
...
294294
case object(foo=G):
295295
...
@@ -357,7 +357,7 @@ match 42:
357357
...
358358
case [D]:
359359
...
360-
case E | F:
360+
case E | F: # error: [invalid-syntax] "name capture `E` makes remaining patterns unreachable"
361361
...
362362
case object(foo=G):
363363
...

crates/red_knot_python_semantic/resources/mdtest/scopes/eager.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -404,7 +404,7 @@ x = int
404404
class C:
405405
var: ClassVar[x]
406406

407-
reveal_type(C.var) # revealed: Unknown | str
407+
reveal_type(C.var) # revealed: str
408408

409409
x = str
410410
```
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
---
2+
source: crates/red_knot_test/src/lib.rs
3+
expression: snapshot
4+
---
5+
---
6+
mdtest name: semantic_syntax_errors.md - Semantic syntax error diagnostics - `async` comprehensions in synchronous comprehensions - Python 3.10
7+
mdtest path: crates/red_knot_python_semantic/resources/mdtest/diagnostics/semantic_syntax_errors.md
8+
---
9+
10+
# Python source files
11+
12+
## mdtest_snippet.py
13+
14+
```
15+
1 | async def elements(n):
16+
2 | yield n
17+
3 |
18+
4 | async def f():
19+
5 | # error: 19 [invalid-syntax] "cannot use an asynchronous comprehension outside of an asynchronous function on Python 3.10 (syntax was added in 3.11)"
20+
6 | return {n: [x async for x in elements(n)] for n in range(3)}
21+
7 | async def test():
22+
8 | return [[x async for x in elements(n)] async for n in range(3)]
23+
9 | async def f():
24+
10 | [x for x in [1]] and [x async for x in elements(1)]
25+
11 |
26+
12 | async def f():
27+
13 | def g():
28+
14 | pass
29+
15 | [x async for x in elements(1)]
30+
```
31+
32+
# Diagnostics
33+
34+
```
35+
error: invalid-syntax
36+
--> /src/mdtest_snippet.py:6:19
37+
|
38+
4 | async def f():
39+
5 | # error: 19 [invalid-syntax] "cannot use an asynchronous comprehension outside of an asynchronous function on Python 3.10 (syntax...
40+
6 | return {n: [x async for x in elements(n)] for n in range(3)}
41+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ cannot use an asynchronous comprehension outside of an asynchronous function on Python 3.10 (syntax was added in 3.11)
42+
7 | async def test():
43+
8 | return [[x async for x in elements(n)] async for n in range(3)]
44+
|
45+
46+
```

crates/red_knot_python_semantic/resources/mdtest/subscript/lists.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ x = [1, 2, 3]
1212
reveal_type(x) # revealed: list
1313

1414
# TODO reveal int
15-
reveal_type(x[0]) # revealed: Unknown
15+
reveal_type(x[0]) # revealed: @Todo(Support for `typing.TypeVar` instances in type expressions)
1616

1717
# TODO reveal list
1818
reveal_type(x[0:1]) # revealed: @Todo(specialized non-generic class)

crates/red_knot_python_semantic/resources/mdtest/type_properties/is_singleton.md

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -128,10 +128,7 @@ python-version = "3.10"
128128
import types
129129
from knot_extensions import static_assert, is_singleton
130130

131-
# TODO: types.NotImplementedType is a TypeAlias of builtins._NotImplementedType
132-
# Once TypeAlias support is added, it should satisfy `is_singleton`
133-
reveal_type(types.NotImplementedType) # revealed: Unknown | Literal[_NotImplementedType]
134-
static_assert(not is_singleton(types.NotImplementedType))
131+
static_assert(is_singleton(types.NotImplementedType))
135132
```
136133

137134
### Callables

0 commit comments

Comments
 (0)