Skip to content

Commit 886db18

Browse files
committed
Merge branch 'main' into brent/syntax-decorators-39
2 parents 4b5a706 + d062388 commit 886db18

File tree

77 files changed

+3483
-787
lines changed

Some content is hidden

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

77 files changed

+3483
-787
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/red_knot/src/main.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ use red_knot_project::watch::ProjectWatcher;
1515
use red_knot_project::{watch, Db};
1616
use red_knot_project::{ProjectDatabase, ProjectMetadata};
1717
use red_knot_server::run_server;
18-
use ruff_db::diagnostic::{Diagnostic, DisplayDiagnosticConfig, Severity};
18+
use ruff_db::diagnostic::{DisplayDiagnosticConfig, OldDiagnosticTrait, Severity};
1919
use ruff_db::system::{OsSystem, SystemPath, SystemPathBuf};
2020
use salsa::plumbing::ZalsaDatabase;
2121

@@ -354,8 +354,7 @@ enum MainLoopMessage {
354354
CheckWorkspace,
355355
CheckCompleted {
356356
/// The diagnostics that were found during the check.
357-
result: Vec<Box<dyn Diagnostic>>,
358-
357+
result: Vec<Box<dyn OldDiagnosticTrait>>,
359358
revision: u64,
360359
},
361360
ApplyChanges(Vec<watch::ChangeEvent>),

crates/red_knot_project/src/db.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use crate::DEFAULT_LINT_REGISTRY;
55
use crate::{Project, ProjectMetadata};
66
use red_knot_python_semantic::lint::{LintRegistry, RuleSelection};
77
use red_knot_python_semantic::{Db as SemanticDb, Program};
8-
use ruff_db::diagnostic::Diagnostic;
8+
use ruff_db::diagnostic::OldDiagnosticTrait;
99
use ruff_db::files::{File, Files};
1010
use ruff_db::system::System;
1111
use ruff_db::vendored::VendoredFileSystem;
@@ -55,11 +55,11 @@ impl ProjectDatabase {
5555
}
5656

5757
/// Checks all open files in the project and its dependencies.
58-
pub fn check(&self) -> Result<Vec<Box<dyn Diagnostic>>, Cancelled> {
58+
pub fn check(&self) -> Result<Vec<Box<dyn OldDiagnosticTrait>>, Cancelled> {
5959
self.with_db(|db| db.project().check(db))
6060
}
6161

62-
pub fn check_file(&self, file: File) -> Result<Vec<Box<dyn Diagnostic>>, Cancelled> {
62+
pub fn check_file(&self, file: File) -> Result<Vec<Box<dyn OldDiagnosticTrait>>, Cancelled> {
6363
let _span = tracing::debug_span!("check_file", file=%file.path(self)).entered();
6464

6565
self.with_db(|db| self.project().check_file(db, file))

crates/red_knot_project/src/lib.rs

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ pub use metadata::{ProjectDiscoveryError, ProjectMetadata};
99
use red_knot_python_semantic::lint::{LintRegistry, LintRegistryBuilder, RuleSelection};
1010
use red_knot_python_semantic::register_lints;
1111
use red_knot_python_semantic::types::check_types;
12-
use ruff_db::diagnostic::{Diagnostic, DiagnosticId, ParseDiagnostic, Severity, Span};
12+
use ruff_db::diagnostic::{DiagnosticId, OldDiagnosticTrait, OldParseDiagnostic, Severity, Span};
1313
use ruff_db::files::File;
1414
use ruff_db::parsed::parsed_module;
1515
use ruff_db::source::{source_text, SourceTextError};
@@ -163,22 +163,22 @@ impl Project {
163163
}
164164

165165
/// Checks all open files in the project and its dependencies.
166-
pub(crate) fn check(self, db: &ProjectDatabase) -> Vec<Box<dyn Diagnostic>> {
166+
pub(crate) fn check(self, db: &ProjectDatabase) -> Vec<Box<dyn OldDiagnosticTrait>> {
167167
let project_span = tracing::debug_span!("Project::check");
168168
let _span = project_span.enter();
169169

170170
tracing::debug!("Checking project '{name}'", name = self.name(db));
171171

172-
let mut diagnostics: Vec<Box<dyn Diagnostic>> = Vec::new();
172+
let mut diagnostics: Vec<Box<dyn OldDiagnosticTrait>> = Vec::new();
173173
diagnostics.extend(self.settings_diagnostics(db).iter().map(|diagnostic| {
174-
let diagnostic: Box<dyn Diagnostic> = Box::new(diagnostic.clone());
174+
let diagnostic: Box<dyn OldDiagnosticTrait> = Box::new(diagnostic.clone());
175175
diagnostic
176176
}));
177177

178178
let files = ProjectFiles::new(db, self);
179179

180180
diagnostics.extend(files.diagnostics().iter().cloned().map(|diagnostic| {
181-
let diagnostic: Box<dyn Diagnostic> = Box::new(diagnostic);
181+
let diagnostic: Box<dyn OldDiagnosticTrait> = Box::new(diagnostic);
182182
diagnostic
183183
}));
184184

@@ -207,12 +207,12 @@ impl Project {
207207
Arc::into_inner(result).unwrap().into_inner().unwrap()
208208
}
209209

210-
pub(crate) fn check_file(self, db: &dyn Db, file: File) -> Vec<Box<dyn Diagnostic>> {
210+
pub(crate) fn check_file(self, db: &dyn Db, file: File) -> Vec<Box<dyn OldDiagnosticTrait>> {
211211
let mut file_diagnostics: Vec<_> = self
212212
.settings_diagnostics(db)
213213
.iter()
214214
.map(|diagnostic| {
215-
let diagnostic: Box<dyn Diagnostic> = Box::new(diagnostic.clone());
215+
let diagnostic: Box<dyn OldDiagnosticTrait> = Box::new(diagnostic.clone());
216216
diagnostic
217217
})
218218
.collect();
@@ -397,8 +397,8 @@ impl Project {
397397
}
398398
}
399399

400-
fn check_file_impl(db: &dyn Db, file: File) -> Vec<Box<dyn Diagnostic>> {
401-
let mut diagnostics: Vec<Box<dyn Diagnostic>> = Vec::new();
400+
fn check_file_impl(db: &dyn Db, file: File) -> Vec<Box<dyn OldDiagnosticTrait>> {
401+
let mut diagnostics: Vec<Box<dyn OldDiagnosticTrait>> = Vec::new();
402402

403403
// Abort checking if there are IO errors.
404404
let source = source_text(db.upcast(), file);
@@ -413,12 +413,13 @@ fn check_file_impl(db: &dyn Db, file: File) -> Vec<Box<dyn Diagnostic>> {
413413

414414
let parsed = parsed_module(db.upcast(), file);
415415
diagnostics.extend(parsed.errors().iter().map(|error| {
416-
let diagnostic: Box<dyn Diagnostic> = Box::new(ParseDiagnostic::new(file, error.clone()));
416+
let diagnostic: Box<dyn OldDiagnosticTrait> =
417+
Box::new(OldParseDiagnostic::new(file, error.clone()));
417418
diagnostic
418419
}));
419420

420421
diagnostics.extend(check_types(db.upcast(), file).iter().map(|diagnostic| {
421-
let boxed: Box<dyn Diagnostic> = Box::new(diagnostic.clone());
422+
let boxed: Box<dyn OldDiagnosticTrait> = Box::new(diagnostic.clone());
422423
boxed
423424
}));
424425

@@ -492,7 +493,7 @@ pub struct IOErrorDiagnostic {
492493
error: IOErrorKind,
493494
}
494495

495-
impl Diagnostic for IOErrorDiagnostic {
496+
impl OldDiagnosticTrait for IOErrorDiagnostic {
496497
fn id(&self) -> DiagnosticId {
497498
DiagnosticId::Io
498499
}
@@ -524,7 +525,7 @@ mod tests {
524525
use crate::db::tests::TestDb;
525526
use crate::{check_file_impl, ProjectMetadata};
526527
use red_knot_python_semantic::types::check_types;
527-
use ruff_db::diagnostic::Diagnostic;
528+
use ruff_db::diagnostic::OldDiagnosticTrait;
528529
use ruff_db::files::system_path_to_file;
529530
use ruff_db::source::source_text;
530531
use ruff_db::system::{DbWithTestSystem, SystemPath, SystemPathBuf};

crates/red_knot_project/src/metadata/options.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use crate::metadata::value::{RangedValue, RelativePathBuf, ValueSource, ValueSou
22
use crate::Db;
33
use red_knot_python_semantic::lint::{GetLintError, Level, LintSource, RuleSelection};
44
use red_knot_python_semantic::{ProgramSettings, PythonPath, PythonPlatform, SearchPathSettings};
5-
use ruff_db::diagnostic::{Diagnostic, DiagnosticId, Severity, Span};
5+
use ruff_db::diagnostic::{DiagnosticId, OldDiagnosticTrait, Severity, Span};
66
use ruff_db::files::system_path_to_file;
77
use ruff_db::system::{System, SystemPath};
88
use ruff_macros::Combine;
@@ -376,7 +376,7 @@ impl OptionDiagnostic {
376376
}
377377
}
378378

379-
impl Diagnostic for OptionDiagnostic {
379+
impl OldDiagnosticTrait for OptionDiagnostic {
380380
fn id(&self) -> DiagnosticId {
381381
self.id
382382
}

crates/red_knot_python_semantic/resources/mdtest/known_constants.md

Lines changed: 106 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,29 +26,126 @@ from typing import TYPE_CHECKING as TC
2626
reveal_type(TC) # revealed: Literal[True]
2727
```
2828

29-
### Must originate from `typing`
29+
### `typing_extensions` re-export
3030

31-
Make sure we only use our special handling for `typing.TYPE_CHECKING` and not for other constants
32-
with the same name:
31+
This should behave in the same way as `typing.TYPE_CHECKING`:
3332

34-
`constants.py`:
33+
```py
34+
from typing_extensions import TYPE_CHECKING
35+
36+
reveal_type(TYPE_CHECKING) # revealed: Literal[True]
37+
```
38+
39+
## User-defined `TYPE_CHECKING`
40+
41+
If we set `TYPE_CHECKING = False` directly instead of importing it from the `typing` module, it will
42+
still be treated as `True` during type checking. This behavior is for compatibility with other major
43+
type checkers, e.g. mypy and pyright.
44+
45+
### With no type annotation
46+
47+
```py
48+
TYPE_CHECKING = False
49+
reveal_type(TYPE_CHECKING) # revealed: Literal[True]
50+
if TYPE_CHECKING:
51+
type_checking = True
52+
if not TYPE_CHECKING:
53+
runtime = True
54+
55+
# type_checking is treated as unconditionally assigned.
56+
reveal_type(type_checking) # revealed: Literal[True]
57+
# error: [unresolved-reference]
58+
reveal_type(runtime) # revealed: Unknown
59+
```
60+
61+
### With a type annotation
62+
63+
We can also define `TYPE_CHECKING` with a type annotation. The type must be one to which `bool` can
64+
be assigned. Even in this case, the type of `TYPE_CHECKING` is still inferred to be `Literal[True]`.
3565

3666
```py
3767
TYPE_CHECKING: bool = False
68+
reveal_type(TYPE_CHECKING) # revealed: Literal[True]
69+
if TYPE_CHECKING:
70+
type_checking = True
71+
if not TYPE_CHECKING:
72+
runtime = True
73+
74+
reveal_type(type_checking) # revealed: Literal[True]
75+
# error: [unresolved-reference]
76+
reveal_type(runtime) # revealed: Unknown
77+
```
78+
79+
### Importing user-defined `TYPE_CHECKING`
80+
81+
`constants.py`:
82+
83+
```py
84+
TYPE_CHECKING = False
85+
```
86+
87+
`stub.pyi`:
88+
89+
```pyi
90+
TYPE_CHECKING: bool
91+
# or
92+
TYPE_CHECKING: bool = ...
3893
```
3994

4095
```py
4196
from constants import TYPE_CHECKING
4297

43-
reveal_type(TYPE_CHECKING) # revealed: bool
98+
reveal_type(TYPE_CHECKING) # revealed: Literal[True]
99+
100+
from stub import TYPE_CHECKING
101+
102+
reveal_type(TYPE_CHECKING) # revealed: Literal[True]
44103
```
45104

46-
### `typing_extensions` re-export
105+
### Invalid assignment to `TYPE_CHECKING`
47106

48-
This should behave in the same way as `typing.TYPE_CHECKING`:
107+
Only `False` can be assigned to `TYPE_CHECKING`; any assignment other than `False` will result in an
108+
error. A type annotation to which `bool` is not assignable is also an error.
49109

50110
```py
51-
from typing_extensions import TYPE_CHECKING
111+
from typing import Literal
52112

53-
reveal_type(TYPE_CHECKING) # revealed: Literal[True]
113+
# error: [invalid-type-checking-constant]
114+
TYPE_CHECKING = True
115+
116+
# error: [invalid-type-checking-constant]
117+
TYPE_CHECKING: bool = True
118+
119+
# error: [invalid-type-checking-constant]
120+
TYPE_CHECKING: int = 1
121+
122+
# error: [invalid-type-checking-constant]
123+
TYPE_CHECKING: str = "str"
124+
125+
# error: [invalid-type-checking-constant]
126+
TYPE_CHECKING: str = False
127+
128+
# error: [invalid-type-checking-constant]
129+
TYPE_CHECKING: Literal[False] = False
130+
131+
# error: [invalid-type-checking-constant]
132+
TYPE_CHECKING: Literal[True] = False
133+
```
134+
135+
The same rules apply in a stub file:
136+
137+
```pyi
138+
from typing import Literal
139+
140+
# error: [invalid-type-checking-constant]
141+
TYPE_CHECKING: str
142+
143+
# error: [invalid-type-checking-constant]
144+
TYPE_CHECKING: str = False
145+
146+
# error: [invalid-type-checking-constant]
147+
TYPE_CHECKING: Literal[False] = ...
148+
149+
# error: [invalid-type-checking-constant]
150+
TYPE_CHECKING: object = "str"
54151
```

crates/red_knot_python_semantic/src/symbol.rs

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -458,8 +458,14 @@ fn symbol_by_id<'db>(
458458
// a diagnostic if we see it being modified externally. In type inference, we
459459
// can assign a "narrow" type to it even if it is not *declared*. This means, we
460460
// do not have to call [`widen_type_for_undeclared_public_symbol`].
461-
let is_considered_non_modifiable =
462-
symbol_table(db, scope).symbol(symbol_id).name() == "__slots__";
461+
//
462+
// `TYPE_CHECKING` is a special variable that should only be assigned `False`
463+
// at runtime, but is always considered `True` in type checking.
464+
// See mdtest/known_constants.md#user-defined-type_checking for details.
465+
let is_considered_non_modifiable = matches!(
466+
symbol_table(db, scope).symbol(symbol_id).name().as_str(),
467+
"__slots__" | "TYPE_CHECKING"
468+
);
463469

464470
widen_type_for_undeclared_public_symbol(db, inferred, is_considered_non_modifiable)
465471
.into()
@@ -500,14 +506,6 @@ fn symbol_impl<'db>(
500506
) -> Symbol<'db> {
501507
let _span = tracing::trace_span!("symbol", ?name).entered();
502508

503-
// We don't need to check for `typing_extensions` here, because `typing_extensions.TYPE_CHECKING`
504-
// is just a re-export of `typing.TYPE_CHECKING`.
505-
if name == "TYPE_CHECKING"
506-
&& file_to_module(db, scope.file(db))
507-
.is_some_and(|module| module.is_known(KnownModule::Typing))
508-
{
509-
return Symbol::bound(Type::BooleanLiteral(true));
510-
}
511509
if name == "platform"
512510
&& file_to_module(db, scope.file(db))
513511
.is_some_and(|module| module.is_known(KnownModule::Sys))

crates/red_knot_python_semantic/src/types/call/bind.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use crate::types::diagnostic::{
66
};
77
use crate::types::signatures::Parameter;
88
use crate::types::{todo_type, CallableType, UnionType};
9-
use ruff_db::diagnostic::{SecondaryDiagnosticMessage, Span};
9+
use ruff_db::diagnostic::{OldSecondaryDiagnosticMessage, Span};
1010
use ruff_python_ast as ast;
1111
use ruff_text_size::Ranged;
1212

@@ -388,7 +388,7 @@ impl<'db> CallBindingError<'db> {
388388
if let Some(span) =
389389
Self::parameter_span_from_index(context.db(), callable_ty, parameter.index)
390390
{
391-
messages.push(SecondaryDiagnosticMessage::new(
391+
messages.push(OldSecondaryDiagnosticMessage::new(
392392
span,
393393
"parameter declared in function definition here",
394394
));

crates/red_knot_python_semantic/src/types/context.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use std::fmt;
22

33
use drop_bomb::DebugDropBomb;
44
use ruff_db::{
5-
diagnostic::{DiagnosticId, SecondaryDiagnosticMessage, Severity},
5+
diagnostic::{DiagnosticId, OldSecondaryDiagnosticMessage, Severity},
66
files::File,
77
};
88
use ruff_text_size::{Ranged, TextRange};
@@ -84,7 +84,7 @@ impl<'db> InferContext<'db> {
8484
lint: &'static LintMetadata,
8585
ranged: T,
8686
message: fmt::Arguments,
87-
secondary_messages: Vec<SecondaryDiagnosticMessage>,
87+
secondary_messages: Vec<OldSecondaryDiagnosticMessage>,
8888
) where
8989
T: Ranged,
9090
{
@@ -136,7 +136,7 @@ impl<'db> InferContext<'db> {
136136
id: DiagnosticId,
137137
severity: Severity,
138138
message: fmt::Arguments,
139-
secondary_messages: Vec<SecondaryDiagnosticMessage>,
139+
secondary_messages: Vec<OldSecondaryDiagnosticMessage>,
140140
) where
141141
T: Ranged,
142142
{

0 commit comments

Comments
 (0)