-
Notifications
You must be signed in to change notification settings - Fork 1.6k
[red-knot] Detect semantic syntax errors #17463
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
1b1ff1f
74353f0
b09fcee
f37022a
46b022e
2764c45
507b737
60acba6
63fd71d
773e7a1
c916bc3
82f848c
f6a4213
bd1d9b1
4b2fac9
bfbc79f
add4f6b
9ac43a9
7db6685
3d11ef7
5e172cc
af43757
7a1e1ab
b32372c
02dfc23
e32c188
ebccf41
57866a0
931fcf1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,165 @@ | ||
| # Semantic syntax error diagnostics | ||
|
|
||
| ## `async` comprehensions in synchronous comprehensions | ||
|
|
||
| ### Python 3.10 | ||
|
|
||
| <!-- snapshot-diagnostics --> | ||
|
|
||
| Before Python 3.11, `async` comprehensions could not be used within outer sync comprehensions, even | ||
| within an `async` function ([CPython issue](https://github.com/python/cpython/issues/77527)): | ||
|
|
||
| ```toml | ||
| [environment] | ||
| python-version = "3.10" | ||
| ``` | ||
|
|
||
| ```py | ||
| async def elements(n): | ||
| yield n | ||
|
|
||
| async def f(): | ||
| # error: 19 [invalid-syntax] "cannot use an asynchronous comprehension outside of an asynchronous function on Python 3.10 (syntax was added in 3.11)" | ||
| return {n: [x async for x in elements(n)] for n in range(3)} | ||
| ``` | ||
|
|
||
| If all of the comprehensions are `async`, on the other hand, the code was still valid: | ||
|
|
||
| ```py | ||
| async def test(): | ||
| return [[x async for x in elements(n)] async for n in range(3)] | ||
| ``` | ||
|
|
||
| These are a couple of tricky but valid cases to check that nested scope handling is wired up | ||
| correctly in the `SemanticSyntaxContext` trait: | ||
|
|
||
| ```py | ||
| async def f(): | ||
| [x for x in [1]] and [x async for x in elements(1)] | ||
|
|
||
| async def f(): | ||
| def g(): | ||
| pass | ||
| [x async for x in elements(1)] | ||
| ``` | ||
|
|
||
| ### Python 3.11 | ||
|
|
||
| All of these same examples are valid after Python 3.11: | ||
|
|
||
| ```toml | ||
| [environment] | ||
| python-version = "3.11" | ||
| ``` | ||
|
|
||
| ```py | ||
| async def elements(n): | ||
| yield n | ||
|
|
||
| async def f(): | ||
| return {n: [x async for x in elements(n)] for n in range(3)} | ||
| ``` | ||
|
|
||
| ## Late `__future__` import | ||
|
|
||
| ```py | ||
| from collections import namedtuple | ||
|
|
||
| # error: [invalid-syntax] "__future__ imports must be at the top of the file" | ||
| from __future__ import print_function | ||
| ``` | ||
|
|
||
| ## Invalid annotation | ||
|
|
||
| This one might be a bit redundant with the `invalid-type-form` error. | ||
|
|
||
| ```toml | ||
| [environment] | ||
| python-version = "3.12" | ||
| ``` | ||
|
|
||
| ```py | ||
| from __future__ import annotations | ||
|
|
||
| # error: [invalid-type-form] "Named expressions are not allowed in type expressions" | ||
| # error: [invalid-syntax] "named expression cannot be used within a type annotation" | ||
ntBre marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| def f() -> (y := 3): ... | ||
| ``` | ||
|
|
||
| ## Duplicate `match` key | ||
|
|
||
| ```toml | ||
| [environment] | ||
| python-version = "3.10" | ||
| ``` | ||
|
|
||
| ```py | ||
| match 2: | ||
| # error: [invalid-syntax] "mapping pattern checks duplicate key `"x"`" | ||
| case {"x": 1, "x": 2}: | ||
| ... | ||
| ``` | ||
|
|
||
| ## `return`, `yield`, `yield from`, and `await` outside function | ||
|
|
||
| ```py | ||
| # error: [invalid-syntax] "`return` statement outside of a function" | ||
| return | ||
|
|
||
| # error: [invalid-syntax] "`yield` statement outside of a function" | ||
| yield | ||
|
|
||
| # error: [invalid-syntax] "`yield from` statement outside of a function" | ||
| yield from [] | ||
|
|
||
| # error: [invalid-syntax] "`await` statement outside of a function" | ||
| # error: [invalid-syntax] "`await` outside of an asynchronous function" | ||
| await 1 | ||
|
|
||
| def f(): | ||
| # error: [invalid-syntax] "`await` outside of an asynchronous function" | ||
| await 1 | ||
| ``` | ||
|
|
||
| Generators are evaluated lazily, so `await` is allowed, even outside of a function. | ||
|
|
||
| ```py | ||
| async def g(): | ||
| yield 1 | ||
|
|
||
| (x async for x in g()) | ||
| ``` | ||
|
|
||
| ## `await` outside async function | ||
|
|
||
| This error includes `await`, `async for`, `async with`, and `async` comprehensions. | ||
|
|
||
| ```python | ||
| async def elements(n): | ||
| yield n | ||
|
|
||
| def _(): | ||
| # error: [invalid-syntax] "`await` outside of an asynchronous function" | ||
| await 1 | ||
| # error: [invalid-syntax] "`async for` outside of an asynchronous function" | ||
| async for _ in elements(1): | ||
| ... | ||
| # error: [invalid-syntax] "`async with` outside of an asynchronous function" | ||
| async with elements(1) as x: | ||
| ... | ||
| # error: [invalid-syntax] "cannot use an asynchronous comprehension outside of an asynchronous function on Python 3.9 (syntax was added in 3.11)" | ||
| # error: [invalid-syntax] "asynchronous comprehension outside of an asynchronous function" | ||
| [x async for x in elements(1)] | ||
| ``` | ||
|
|
||
| ## Load before `global` declaration | ||
|
|
||
| This should be an error, but it's not yet. | ||
|
|
||
| TODO implement `SemanticSyntaxContext::global` | ||
|
|
||
| ```py | ||
| def f(): | ||
| x = 1 | ||
| global x | ||
| ``` | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| --- | ||
| source: crates/red_knot_test/src/lib.rs | ||
| expression: snapshot | ||
| --- | ||
| --- | ||
| mdtest name: semantic_syntax_errors.md - Semantic syntax error diagnostics - `async` comprehensions in synchronous comprehensions - Python 3.10 | ||
| mdtest path: crates/red_knot_python_semantic/resources/mdtest/diagnostics/semantic_syntax_errors.md | ||
| --- | ||
|
|
||
| # Python source files | ||
|
|
||
| ## mdtest_snippet.py | ||
|
|
||
| ``` | ||
| 1 | async def elements(n): | ||
| 2 | yield n | ||
| 3 | | ||
| 4 | async def f(): | ||
| 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)" | ||
| 6 | return {n: [x async for x in elements(n)] for n in range(3)} | ||
| 7 | async def test(): | ||
| 8 | return [[x async for x in elements(n)] async for n in range(3)] | ||
| 9 | async def f(): | ||
| 10 | [x for x in [1]] and [x async for x in elements(1)] | ||
| 11 | | ||
| 12 | async def f(): | ||
| 13 | def g(): | ||
| 14 | pass | ||
| 15 | [x async for x in elements(1)] | ||
| ``` | ||
|
|
||
| # Diagnostics | ||
|
|
||
| ``` | ||
| error: invalid-syntax | ||
| --> /src/mdtest_snippet.py:6:19 | ||
| | | ||
| 4 | async def f(): | ||
| 5 | # error: 19 [invalid-syntax] "cannot use an asynchronous comprehension outside of an asynchronous function on Python 3.10 (syntax... | ||
| 6 | return {n: [x async for x in elements(n)] for n in range(3)} | ||
| | ^^^^^^^^^^^^^^^^^^^^^^^^^^ cannot use an asynchronous comprehension outside of an asynchronous function on Python 3.10 (syntax was added in 3.11) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm a bit confused by this message. Isn't It's a bit a shame that we can't make use of the multi range diagnostics because it would be nice to have a second frame outlining "why" the context isn't async (which would address my confusion). I don't think this is something we should solve as part of this PR but maybe something to come back once we also use the new diagnostics in Ruff.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the "synchronous function" that the async list comprehension is immediately nested inside of here is the function-like scope created by the synchronous dict comprehension. But I agree that it would be great if we could have a clearer error and a multi-span diagnostic
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, That's a good point about the multi-span diagnostic too. We could definitely highlight the outer sync comprehension, as well as the inner async one. |
||
| 7 | async def test(): | ||
| 8 | return [[x async for x in elements(n)] async for n in range(3)] | ||
| | | ||
| ``` | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,6 +5,7 @@ use ruff_db::files::File; | |
| use ruff_db::parsed::parsed_module; | ||
| use ruff_index::{IndexSlice, IndexVec}; | ||
|
|
||
| use ruff_python_parser::semantic_errors::SemanticSyntaxError; | ||
| use rustc_hash::{FxBuildHasher, FxHashMap, FxHashSet}; | ||
| use salsa::plumbing::AsId; | ||
| use salsa::Update; | ||
|
|
@@ -175,6 +176,9 @@ pub(crate) struct SemanticIndex<'db> { | |
|
|
||
| /// Map of all of the eager bindings that appear in this file. | ||
| eager_bindings: FxHashMap<EagerBindingsKey, ScopedEagerBindingsId>, | ||
|
|
||
| /// List of all semantic syntax errors in this file. | ||
| semantic_syntax_errors: Vec<SemanticSyntaxError>, | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This could be a good use case for a |
||
| } | ||
|
|
||
| impl<'db> SemanticIndex<'db> { | ||
|
|
@@ -399,6 +403,10 @@ impl<'db> SemanticIndex<'db> { | |
| None => EagerBindingsResult::NotFound, | ||
| } | ||
| } | ||
|
|
||
| pub(crate) fn semantic_syntax_errors(&self) -> &[SemanticSyntaxError] { | ||
| &self.semantic_syntax_errors | ||
| } | ||
| } | ||
|
|
||
| pub struct AncestorsIter<'a> { | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.