Skip to content

Commit 9c47b6d

Browse files
authored
[red-knot] Detect version-related syntax errors (#16379)
## Summary This PR extends version-related syntax error detection to red-knot. The main changes here are: 1. Passing `ParseOptions` specifying a `PythonVersion` to parser calls 2. Adding a `python_version` method to the `Db` trait to make this possible 3. Converting `UnsupportedSyntaxError`s to `Diagnostic`s 4. Updating existing mdtests to avoid unrelated syntax errors My initial draft of (1) and (2) in #16090 instead tried passing a `PythonVersion` down to every parser call, but @MichaReiser suggested the `Db` approach instead [here](#16090 (comment)), and I think it turned out much nicer. All of the new `python_version` methods look like this: ```rust fn python_version(&self) -> ruff_python_ast::PythonVersion { Program::get(self).python_version(self) } ``` with the exception of the `TestDb` in `ruff_db`, which hard-codes `PythonVersion::latest()`. ## Test Plan Existing mdtests, plus a new mdtest to see at least one of the new diagnostics.
1 parent d2ebfd6 commit 9c47b6d

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+353
-14
lines changed

crates/red_knot_ide/src/db.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ pub(crate) mod tests {
1010

1111
use super::Db;
1212
use red_knot_python_semantic::lint::{LintRegistry, RuleSelection};
13-
use red_knot_python_semantic::{default_lint_registry, Db as SemanticDb};
13+
use red_knot_python_semantic::{default_lint_registry, Db as SemanticDb, Program};
1414
use ruff_db::files::{File, Files};
1515
use ruff_db::system::{DbWithTestSystem, System, TestSystem};
1616
use ruff_db::vendored::VendoredFileSystem;
@@ -83,6 +83,10 @@ pub(crate) mod tests {
8383
fn files(&self) -> &Files {
8484
&self.files
8585
}
86+
87+
fn python_version(&self) -> ruff_python_ast::PythonVersion {
88+
Program::get(self).python_version(self)
89+
}
8690
}
8791

8892
impl Upcast<dyn SourceDb> for TestDb {

crates/red_knot_project/src/db.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,10 @@ impl SourceDb for ProjectDatabase {
149149
fn files(&self) -> &Files {
150150
&self.files
151151
}
152+
153+
fn python_version(&self) -> ruff_python_ast::PythonVersion {
154+
Program::get(self).python_version(self)
155+
}
152156
}
153157

154158
#[salsa::db]
@@ -207,7 +211,7 @@ pub(crate) mod tests {
207211
use salsa::Event;
208212

209213
use red_knot_python_semantic::lint::{LintRegistry, RuleSelection};
210-
use red_knot_python_semantic::Db as SemanticDb;
214+
use red_knot_python_semantic::{Db as SemanticDb, Program};
211215
use ruff_db::files::Files;
212216
use ruff_db::system::{DbWithTestSystem, System, TestSystem};
213217
use ruff_db::vendored::VendoredFileSystem;
@@ -281,6 +285,10 @@ pub(crate) mod tests {
281285
fn files(&self) -> &Files {
282286
&self.files
283287
}
288+
289+
fn python_version(&self) -> ruff_python_ast::PythonVersion {
290+
Program::get(self).python_version(self)
291+
}
284292
}
285293

286294
impl Upcast<dyn SemanticDb> for TestDb {

crates/red_knot_project/src/lib.rs

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ use red_knot_python_semantic::lint::{LintRegistry, LintRegistryBuilder, RuleSele
1010
use red_knot_python_semantic::register_lints;
1111
use red_knot_python_semantic::types::check_types;
1212
use ruff_db::diagnostic::{
13-
create_parse_diagnostic, Annotation, Diagnostic, DiagnosticId, Severity, Span,
13+
create_parse_diagnostic, create_unsupported_syntax_diagnostic, Annotation, Diagnostic,
14+
DiagnosticId, Severity, Span,
1415
};
1516
use ruff_db::files::File;
1617
use ruff_db::parsed::parsed_module;
@@ -424,6 +425,13 @@ fn check_file_impl(db: &dyn Db, file: File) -> Vec<Diagnostic> {
424425
.map(|error| create_parse_diagnostic(file, error)),
425426
);
426427

428+
diagnostics.extend(
429+
parsed
430+
.unsupported_syntax_errors()
431+
.iter()
432+
.map(|error| create_unsupported_syntax_diagnostic(file, error)),
433+
);
434+
427435
diagnostics.extend(check_types(db.upcast(), file).into_iter().cloned());
428436

429437
diagnostics.sort_unstable_by_key(|diagnostic| {
@@ -520,18 +528,30 @@ mod tests {
520528
use crate::db::tests::TestDb;
521529
use crate::{check_file_impl, ProjectMetadata};
522530
use red_knot_python_semantic::types::check_types;
531+
use red_knot_python_semantic::{Program, ProgramSettings, PythonPlatform, SearchPathSettings};
523532
use ruff_db::files::system_path_to_file;
524533
use ruff_db::source::source_text;
525534
use ruff_db::system::{DbWithTestSystem, DbWithWritableSystem as _, SystemPath, SystemPathBuf};
526535
use ruff_db::testing::assert_function_query_was_not_run;
527536
use ruff_python_ast::name::Name;
537+
use ruff_python_ast::PythonVersion;
528538

529539
#[test]
530540
fn check_file_skips_type_checking_when_file_cant_be_read() -> ruff_db::system::Result<()> {
531541
let project = ProjectMetadata::new(Name::new_static("test"), SystemPathBuf::from("/"));
532542
let mut db = TestDb::new(project);
533543
let path = SystemPath::new("test.py");
534544

545+
Program::from_settings(
546+
&db,
547+
ProgramSettings {
548+
python_version: PythonVersion::default(),
549+
python_platform: PythonPlatform::default(),
550+
search_paths: SearchPathSettings::new(vec![SystemPathBuf::from(".")]),
551+
},
552+
)
553+
.expect("Failed to configure program settings");
554+
535555
db.write_file(path, "x = 10")?;
536556
let file = system_path_to_file(&db, path).unwrap();
537557

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,11 @@ def _(c: Callable[[Concatenate[int, str, ...], int], int]):
237237

238238
## Using `typing.ParamSpec`
239239

240+
```toml
241+
[environment]
242+
python-version = "3.12"
243+
```
244+
240245
Using a `ParamSpec` in a `Callable` annotation:
241246

242247
```py

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,11 @@ reveal_type(get_foo()) # revealed: Foo
4848

4949
## Deferred self-reference annotations in a class definition
5050

51+
```toml
52+
[environment]
53+
python-version = "3.12"
54+
```
55+
5156
```py
5257
from __future__ import annotations
5358

@@ -94,6 +99,11 @@ class Foo:
9499

95100
## Non-deferred self-reference annotations in a class definition
96101

102+
```toml
103+
[environment]
104+
python-version = "3.12"
105+
```
106+
97107
```py
98108
class Foo:
99109
# error: [unresolved-reference]

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
# Starred expression annotations
22

3+
```toml
4+
[environment]
5+
python-version = "3.11"
6+
```
7+
38
Type annotations for `*args` can be starred expressions themselves:
49

510
```py

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,11 @@ reveal_type(F.__mro__) # revealed: tuple[Literal[F], @Todo(Support for Callable
7979

8080
## Subscriptability
8181

82+
```toml
83+
[environment]
84+
python-version = "3.12"
85+
```
86+
8287
Some of these are not subscriptable:
8388

8489
```py

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@ x = "foo" # error: [invalid-assignment] "Object of type `Literal["foo"]` is not
2525

2626
## Tuple annotations are understood
2727

28+
```toml
29+
[environment]
30+
python-version = "3.12"
31+
```
32+
2833
`module.py`:
2934

3035
```py

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,11 @@ reveal_type(get_int_async()) # revealed: @Todo(generic types.CoroutineType)
2121

2222
## Generic
2323

24+
```toml
25+
[environment]
26+
python-version = "3.12"
27+
```
28+
2429
```py
2530
def get_int[T]() -> int:
2631
return 42

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,11 @@ reveal_type(getattr_static(C, "f").__get__("dummy", C)) # revealed: bound metho
399399

400400
### Classmethods mixed with other decorators
401401

402+
```toml
403+
[environment]
404+
python-version = "3.12"
405+
```
406+
402407
When a `@classmethod` is additionally decorated with another decorator, it is still treated as a
403408
class method:
404409

0 commit comments

Comments
 (0)