Skip to content

Commit 1d6a917

Browse files
committed
Merge remote-tracking branch 'origin/main' into dcreager/typevar-type
* origin/main: (35 commits) [red-knot] Callable types are disjoint from literals (#17160) [red-knot] Fix inference for `pow` between two literal integers (#17161) [red-knot] Add GitHub PR annotations when mdtests fail in CI (#17150) [red-knot] Fix equivalence of differently ordered unions that contain `Callable` types (#17145) [red-knot] Add initial set of tests for unreachable code (#17159) [`airflow`] Move `AIR302` to `AIR301` and `AIR303` to `AIR302` (#17151) ruff_db: simplify lifetimes on `DiagnosticDisplay` [red-knot] Detect division-by-zero in unions and intersections (#17157) [`airflow`] Add autofix infrastructure to `AIR302` name checks (#16965) [`flake8-bandit`] Mark `str` and `list[str]` literals as trusted input (`S603`) (#17136) [`airflow`] Add autofix for `AIR302` attribute checks (#16977) [`airflow`] Extend `AIR302` with additional symbols (#17085) [`airflow`] Move `AIR301` to `AIR002` (#16978) [`airflow`] Add autofix for `AIR302` method checks (#16976) ruff_db: switch diagnostic rendering over to `std::fmt::Display` [red-knot] Add 'Goto type definition' to the playground (#17055) red_knot_ide: update snapshots red_knot_python_semantic: remove comment about `TypeCheckDiagnostic` ruff_db: delete most of the old diagnostic code red_knot: use `Diagnostic` inside of red knot ...
2 parents 6bd69f1 + 177afab commit 1d6a917

File tree

150 files changed

+8279
-4110
lines changed

Some content is hidden

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

150 files changed

+8279
-4110
lines changed

.github/workflows/ci.yaml

+33
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ jobs:
3636
code: ${{ steps.check_code.outputs.changed }}
3737
# Flag that is raised when any code that affects the fuzzer is changed
3838
fuzz: ${{ steps.check_fuzzer.outputs.changed }}
39+
# Flag that is set to "true" when code related to red-knot changes.
40+
red_knot: ${{ steps.check_red_knot.outputs.changed }}
3941

4042
# Flag that is set to "true" when code related to the playground changes.
4143
playground: ${{ steps.check_playground.outputs.changed }}
@@ -166,6 +168,29 @@ jobs:
166168
echo "changed=true" >> "$GITHUB_OUTPUT"
167169
fi
168170
171+
- name: Check if the red-knot code changed
172+
id: check_red_knot
173+
env:
174+
MERGE_BASE: ${{ steps.merge_base.outputs.sha }}
175+
run: |
176+
if git diff --quiet "${MERGE_BASE}...HEAD" -- \
177+
':Cargo.toml' \
178+
':Cargo.lock' \
179+
':crates/red_knot*/**' \
180+
':crates/ruff_db/**' \
181+
':crates/ruff_annotate_snippets/**' \
182+
':crates/ruff_python_ast/**' \
183+
':crates/ruff_python_parser/**' \
184+
':crates/ruff_python_trivia/**' \
185+
':crates/ruff_source_file/**' \
186+
':crates/ruff_text_size/**' \
187+
':.github/workflows/ci.yaml' \
188+
; then
189+
echo "changed=false" >> "$GITHUB_OUTPUT"
190+
else
191+
echo "changed=true" >> "$GITHUB_OUTPUT"
192+
fi
193+
169194
cargo-fmt:
170195
name: "cargo fmt"
171196
runs-on: ubuntu-latest
@@ -221,6 +246,14 @@ jobs:
221246
uses: taiki-e/install-action@6aca1cfa12ef3a6b98ee8c70e0171bfa067604f5 # v2
222247
with:
223248
tool: cargo-insta
249+
- name: Red-knot mdtests (GitHub annotations)
250+
if: ${{ needs.determine_changes.outputs.red_knot == 'true' }}
251+
env:
252+
NO_COLOR: 1
253+
MDTEST_GITHUB_ANNOTATIONS_FORMAT: 1
254+
# Ignore errors if this step fails; we want to continue to later steps in the workflow anyway.
255+
# This step is just to get nice GitHub annotations on the PR diff in the files-changed tab.
256+
run: cargo test -p red_knot_python_semantic --test mdtest || true
224257
- name: "Run tests"
225258
shell: bash
226259
env:

Cargo.lock

+8
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/red_knot/src/main.rs

+3-3
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::{DisplayDiagnosticConfig, OldDiagnosticTrait, Severity};
18+
use ruff_db::diagnostic::{Diagnostic, DisplayDiagnosticConfig, Severity};
1919
use ruff_db::system::{OsSystem, SystemPath, SystemPathBuf};
2020
use salsa::plumbing::ZalsaDatabase;
2121

@@ -288,7 +288,7 @@ impl MainLoop {
288288
let diagnostics_count = result.len();
289289

290290
for diagnostic in result {
291-
writeln!(stdout, "{}", diagnostic.display(db, &display_config))?;
291+
write!(stdout, "{}", diagnostic.display(db, &display_config))?;
292292

293293
failed |= diagnostic.severity() >= min_error_severity;
294294
}
@@ -359,7 +359,7 @@ enum MainLoopMessage {
359359
CheckWorkspace,
360360
CheckCompleted {
361361
/// The diagnostics that were found during the check.
362-
result: Vec<Box<dyn OldDiagnosticTrait>>,
362+
result: Vec<Diagnostic>,
363363
revision: u64,
364364
},
365365
ApplyChanges(Vec<watch::ChangeEvent>),

crates/red_knot/tests/file_watching.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -1125,11 +1125,11 @@ print(sys.last_exc, os.getegid())
11251125

11261126
assert_eq!(diagnostics.len(), 2);
11271127
assert_eq!(
1128-
diagnostics[0].message(),
1128+
diagnostics[0].primary_message(),
11291129
"Type `<module 'sys'>` has no attribute `last_exc`"
11301130
);
11311131
assert_eq!(
1132-
diagnostics[1].message(),
1132+
diagnostics[1].primary_message(),
11331133
"Type `<module 'os'>` has no attribute `getegid`"
11341134
);
11351135

crates/red_knot_ide/Cargo.toml

+6
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,19 @@ license = { workspace = true }
1212

1313
[dependencies]
1414
ruff_db = { workspace = true }
15+
ruff_python_ast = { workspace = true }
16+
ruff_python_parser = { workspace = true }
17+
ruff_text_size = { workspace = true }
1518
red_knot_python_semantic = { workspace = true }
1619

1720
salsa = { workspace = true }
21+
smallvec = { workspace = true }
1822
tracing = { workspace = true }
1923

2024
[dev-dependencies]
2125
red_knot_vendored = { workspace = true }
2226

27+
insta = { workspace = true, features = ["filters"] }
28+
2329
[lints]
2430
workspace = true

crates/red_knot_ide/src/db.rs

+11-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use red_knot_python_semantic::Db as SemanticDb;
22
use ruff_db::{Db as SourceDb, Upcast};
33

44
#[salsa::db]
5-
pub trait Db: SemanticDb + Upcast<dyn SourceDb> {}
5+
pub trait Db: SemanticDb + Upcast<dyn SemanticDb> + Upcast<dyn SourceDb> {}
66

77
#[cfg(test)]
88
pub(crate) mod tests {
@@ -94,6 +94,16 @@ pub(crate) mod tests {
9494
}
9595
}
9696

97+
impl Upcast<dyn SemanticDb> for TestDb {
98+
fn upcast(&self) -> &(dyn SemanticDb + 'static) {
99+
self
100+
}
101+
102+
fn upcast_mut(&mut self) -> &mut dyn SemanticDb {
103+
self
104+
}
105+
}
106+
97107
#[salsa::db]
98108
impl SemanticDb for TestDb {
99109
fn is_file_open(&self, file: File) -> bool {

crates/red_knot_ide/src/find_node.rs

+106
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
use ruff_python_ast::visitor::source_order::{SourceOrderVisitor, TraversalSignal};
2+
use ruff_python_ast::AnyNodeRef;
3+
use ruff_text_size::{Ranged, TextRange};
4+
use std::fmt;
5+
use std::fmt::Formatter;
6+
7+
/// Returns the node with a minimal range that fully contains `range`.
8+
///
9+
/// If `range` is empty and falls within a parser *synthesized* node generated during error recovery,
10+
/// then the first node with the given range is returned.
11+
///
12+
/// ## Panics
13+
/// Panics if `range` is not contained within `root`.
14+
pub(crate) fn covering_node(root: AnyNodeRef, range: TextRange) -> CoveringNode {
15+
struct Visitor<'a> {
16+
range: TextRange,
17+
found: bool,
18+
ancestors: Vec<AnyNodeRef<'a>>,
19+
}
20+
21+
impl<'a> SourceOrderVisitor<'a> for Visitor<'a> {
22+
fn enter_node(&mut self, node: AnyNodeRef<'a>) -> TraversalSignal {
23+
// If the node fully contains the range, than it is a possible match but traverse into its children
24+
// to see if there's a node with a narrower range.
25+
if !self.found && node.range().contains_range(self.range) {
26+
self.ancestors.push(node);
27+
TraversalSignal::Traverse
28+
} else {
29+
TraversalSignal::Skip
30+
}
31+
}
32+
33+
fn leave_node(&mut self, node: AnyNodeRef<'a>) {
34+
if !self.found && self.ancestors.last() == Some(&node) {
35+
self.found = true;
36+
}
37+
}
38+
}
39+
40+
assert!(
41+
root.range().contains_range(range),
42+
"Range is not contained within root"
43+
);
44+
45+
let mut visitor = Visitor {
46+
range,
47+
found: false,
48+
ancestors: Vec::new(),
49+
};
50+
51+
root.visit_source_order(&mut visitor);
52+
53+
let minimal = visitor.ancestors.pop().unwrap_or(root);
54+
CoveringNode {
55+
node: minimal,
56+
ancestors: visitor.ancestors,
57+
}
58+
}
59+
60+
/// The node with a minimal range that fully contains the search range.
61+
pub(crate) struct CoveringNode<'a> {
62+
/// The node with a minimal range that fully contains the search range.
63+
node: AnyNodeRef<'a>,
64+
65+
/// The node's ancestor (the spine up to the root).
66+
ancestors: Vec<AnyNodeRef<'a>>,
67+
}
68+
69+
impl<'a> CoveringNode<'a> {
70+
pub(crate) fn node(&self) -> AnyNodeRef<'a> {
71+
self.node
72+
}
73+
74+
/// Returns the node's parent.
75+
pub(crate) fn parent(&self) -> Option<AnyNodeRef<'a>> {
76+
self.ancestors.last().copied()
77+
}
78+
79+
/// Finds the minimal node that fully covers the range and fulfills the given predicate.
80+
pub(crate) fn find(mut self, f: impl Fn(AnyNodeRef<'a>) -> bool) -> Result<Self, Self> {
81+
if f(self.node) {
82+
return Ok(self);
83+
}
84+
85+
match self.ancestors.iter().rposition(|node| f(*node)) {
86+
Some(index) => {
87+
let node = self.ancestors[index];
88+
self.ancestors.truncate(index);
89+
90+
Ok(Self {
91+
node,
92+
ancestors: self.ancestors,
93+
})
94+
}
95+
None => Err(self),
96+
}
97+
}
98+
}
99+
100+
impl fmt::Debug for CoveringNode<'_> {
101+
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
102+
f.debug_tuple("NodeWithAncestors")
103+
.field(&self.node)
104+
.finish()
105+
}
106+
}

0 commit comments

Comments
 (0)