Skip to content

Commit 608a182

Browse files
authored
Red knot: Add file watching and cancellation (#11127)
1 parent a53269b commit 608a182

File tree

15 files changed

+714
-150
lines changed

15 files changed

+714
-150
lines changed

Cargo.lock

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

Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ console_error_panic_hook = { version = "0.1.7" }
3030
console_log = { version = "1.0.0" }
3131
countme = { version = "3.0.1" }
3232
criterion = { version = "0.5.1", default-features = false }
33-
crossbeam = { version = "0.8.4" }
33+
crossbeam-channel = { version = "0.5.12" }
3434
dashmap = { version = "5.5.3" }
3535
dirs = { version = "5.0.0" }
3636
drop_bomb = { version = "0.1.5" }

crates/red_knot/Cargo.toml

+6
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,18 @@ ruff_python_ast = { path = "../ruff_python_ast" }
1717
ruff_python_trivia = { path = "../ruff_python_trivia" }
1818
ruff_text_size = { path = "../ruff_text_size" }
1919
ruff_index = { path = "../ruff_index" }
20+
ruff_notebook = { path = "../ruff_notebook" }
2021

2122
anyhow = { workspace = true }
23+
bitflags = { workspace = true }
24+
ctrlc = "3.4.4"
25+
crossbeam-channel = { workspace = true }
2226
dashmap = { workspace = true }
2327
hashbrown = { workspace = true }
2428
log = { workspace = true }
29+
notify = { workspace = true }
2530
parking_lot = { workspace = true }
31+
rayon = { workspace = true }
2632
rustc-hash = { workspace = true }
2733
smallvec = { workspace = true }
2834
smol_str = "0.2.1"

crates/red_knot/src/cancellation.rs

+65
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
use std::sync::{Arc, Condvar, Mutex};
2+
3+
#[derive(Debug, Default)]
4+
pub struct CancellationSource {
5+
signal: Arc<(Mutex<bool>, Condvar)>,
6+
}
7+
8+
impl CancellationSource {
9+
pub fn new() -> Self {
10+
Self {
11+
signal: Arc::new((Mutex::new(false), Condvar::default())),
12+
}
13+
}
14+
15+
pub fn cancel(&self) {
16+
let (cancelled, condvar) = &*self.signal;
17+
18+
let mut cancelled = cancelled.lock().unwrap();
19+
20+
if *cancelled {
21+
return;
22+
}
23+
24+
*cancelled = true;
25+
condvar.notify_all();
26+
}
27+
28+
pub fn is_cancelled(&self) -> bool {
29+
let (cancelled, _) = &*self.signal;
30+
31+
*cancelled.lock().unwrap()
32+
}
33+
34+
pub fn token(&self) -> CancellationToken {
35+
CancellationToken {
36+
signal: self.signal.clone(),
37+
}
38+
}
39+
}
40+
41+
#[derive(Clone, Debug)]
42+
pub struct CancellationToken {
43+
signal: Arc<(Mutex<bool>, Condvar)>,
44+
}
45+
46+
impl CancellationToken {
47+
/// Returns `true` if cancellation has been requested.
48+
pub fn is_cancelled(&self) -> bool {
49+
let (cancelled, _) = &*self.signal;
50+
51+
*cancelled.lock().unwrap()
52+
}
53+
54+
pub fn wait(&self) {
55+
let (bool, condvar) = &*self.signal;
56+
57+
let lock = condvar
58+
.wait_while(bool.lock().unwrap(), |bool| !*bool)
59+
.unwrap();
60+
61+
debug_assert!(*lock);
62+
63+
drop(lock);
64+
}
65+
}

crates/red_knot/src/db.rs

+9
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use std::path::Path;
22
use std::sync::Arc;
33

44
use crate::files::FileId;
5+
use crate::lint::{Diagnostics, LintSyntaxStorage};
56
use crate::module::{Module, ModuleData, ModuleName, ModuleResolver, ModuleSearchPath};
67
use crate::parse::{Parsed, ParsedStorage};
78
use crate::source::{Source, SourceStorage};
@@ -16,6 +17,8 @@ pub trait SourceDb {
1617
fn source(&self, file_id: FileId) -> Source;
1718

1819
fn parse(&self, file_id: FileId) -> Parsed;
20+
21+
fn lint_syntax(&self, file_id: FileId) -> Diagnostics;
1922
}
2023

2124
pub trait SemanticDb: SourceDb {
@@ -38,6 +41,7 @@ pub trait Db: SemanticDb {}
3841
pub struct SourceJar {
3942
pub sources: SourceStorage,
4043
pub parsed: ParsedStorage,
44+
pub lint_syntax: LintSyntaxStorage,
4145
}
4246

4347
#[derive(Debug, Default)]
@@ -70,6 +74,7 @@ pub trait HasJar<T> {
7074
pub(crate) mod tests {
7175
use crate::db::{HasJar, SourceDb, SourceJar};
7276
use crate::files::{FileId, Files};
77+
use crate::lint::{lint_syntax, Diagnostics};
7378
use crate::module::{
7479
add_module, path_to_module, resolve_module, set_module_search_paths, Module, ModuleData,
7580
ModuleName, ModuleSearchPath,
@@ -127,6 +132,10 @@ pub(crate) mod tests {
127132
fn parse(&self, file_id: FileId) -> Parsed {
128133
parse(self, file_id)
129134
}
135+
136+
fn lint_syntax(&self, file_id: FileId) -> Diagnostics {
137+
lint_syntax(self, file_id)
138+
}
130139
}
131140

132141
impl SemanticDb for TestDb {

crates/red_knot/src/lib.rs

+3
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,18 @@ use crate::files::FileId;
99

1010
pub mod ast_ids;
1111
pub mod cache;
12+
pub mod cancellation;
1213
pub mod db;
1314
pub mod files;
1415
pub mod hir;
16+
pub mod lint;
1517
pub mod module;
1618
mod parse;
1719
pub mod program;
1820
pub mod source;
1921
mod symbols;
2022
mod types;
23+
pub mod watch;
2124

2225
pub(crate) type FxDashMap<K, V> = dashmap::DashMap<K, V, BuildHasherDefault<FxHasher>>;
2326
#[allow(unused)]

crates/red_knot/src/lint.rs

+124
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
use std::ops::{Deref, DerefMut};
2+
use std::sync::Arc;
3+
4+
use ruff_python_ast::visitor::Visitor;
5+
use ruff_python_ast::StringLiteral;
6+
7+
use crate::cache::KeyValueCache;
8+
use crate::db::{HasJar, SourceDb, SourceJar};
9+
use crate::files::FileId;
10+
11+
pub(crate) fn lint_syntax<Db>(db: &Db, file_id: FileId) -> Diagnostics
12+
where
13+
Db: SourceDb + HasJar<SourceJar>,
14+
{
15+
let storage = &db.jar().lint_syntax;
16+
17+
storage.get(&file_id, |file_id| {
18+
let mut diagnostics = Vec::new();
19+
20+
let source = db.source(*file_id);
21+
lint_lines(source.text(), &mut diagnostics);
22+
23+
let parsed = db.parse(*file_id);
24+
25+
if parsed.errors().is_empty() {
26+
let ast = parsed.ast();
27+
28+
let mut visitor = SyntaxLintVisitor {
29+
diagnostics,
30+
source: source.text(),
31+
};
32+
visitor.visit_body(&ast.body);
33+
diagnostics = visitor.diagnostics
34+
} else {
35+
diagnostics.extend(parsed.errors().iter().map(|err| err.to_string()));
36+
}
37+
38+
Diagnostics::from(diagnostics)
39+
})
40+
}
41+
42+
pub(crate) fn lint_lines(source: &str, diagnostics: &mut Vec<String>) {
43+
for (line_number, line) in source.lines().enumerate() {
44+
if line.len() < 88 {
45+
continue;
46+
}
47+
48+
let char_count = line.chars().count();
49+
if char_count > 88 {
50+
diagnostics.push(format!(
51+
"Line {} is too long ({} characters)",
52+
line_number + 1,
53+
char_count
54+
));
55+
}
56+
}
57+
}
58+
59+
#[derive(Debug)]
60+
struct SyntaxLintVisitor<'a> {
61+
diagnostics: Vec<String>,
62+
source: &'a str,
63+
}
64+
65+
impl Visitor<'_> for SyntaxLintVisitor<'_> {
66+
fn visit_string_literal(&mut self, string_literal: &'_ StringLiteral) {
67+
// A very naive implementation of use double quotes
68+
let text = &self.source[string_literal.range];
69+
70+
if text.starts_with('\'') {
71+
self.diagnostics
72+
.push("Use double quotes for strings".to_string());
73+
}
74+
}
75+
}
76+
77+
#[derive(Debug, Clone)]
78+
pub enum Diagnostics {
79+
Empty,
80+
List(Arc<Vec<String>>),
81+
}
82+
83+
impl Diagnostics {
84+
pub fn as_slice(&self) -> &[String] {
85+
match self {
86+
Diagnostics::Empty => &[],
87+
Diagnostics::List(list) => list.as_slice(),
88+
}
89+
}
90+
}
91+
92+
impl Deref for Diagnostics {
93+
type Target = [String];
94+
fn deref(&self) -> &Self::Target {
95+
self.as_slice()
96+
}
97+
}
98+
99+
impl From<Vec<String>> for Diagnostics {
100+
fn from(value: Vec<String>) -> Self {
101+
if value.is_empty() {
102+
Diagnostics::Empty
103+
} else {
104+
Diagnostics::List(Arc::new(value))
105+
}
106+
}
107+
}
108+
109+
#[derive(Default, Debug)]
110+
pub struct LintSyntaxStorage(KeyValueCache<FileId, Diagnostics>);
111+
112+
impl Deref for LintSyntaxStorage {
113+
type Target = KeyValueCache<FileId, Diagnostics>;
114+
115+
fn deref(&self) -> &Self::Target {
116+
&self.0
117+
}
118+
}
119+
120+
impl DerefMut for LintSyntaxStorage {
121+
fn deref_mut(&mut self) -> &mut Self::Target {
122+
&mut self.0
123+
}
124+
}

0 commit comments

Comments
 (0)