Skip to content

Commit 9b92948

Browse files
authored
migrate typechecker to new lsp (#169)
* migrate typechecker to new lsp * refactor: run async in parallel * lint-fix * fixes from review
1 parent 680689c commit 9b92948

File tree

17 files changed

+440
-150
lines changed

17 files changed

+440
-150
lines changed

Diff for: Cargo.lock

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

Diff for: crates/pg_completions/src/relevance.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ impl CompletionRelevance<'_> {
7070
Some(ct) => ct,
7171
};
7272

73-
let has_mentioned_tables = ctx.mentioned_relations.len() > 0;
73+
let has_mentioned_tables = !ctx.mentioned_relations.is_empty();
7474

7575
self.score += match self.data {
7676
CompletionRelevanceData::Table(_) => match clause_type {

Diff for: crates/pg_completions/src/test_helper.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ pub(crate) async fn get_test_deps(
5151
.set_language(tree_sitter_sql::language())
5252
.expect("Error loading sql language");
5353

54-
let tree = parser.parse(&input.to_string(), None).unwrap();
54+
let tree = parser.parse(input.to_string(), None).unwrap();
5555

5656
(tree, schema_cache)
5757
}

Diff for: crates/pg_diagnostics_categories/src/categories.rs

+1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ define_categories! {
2626
"internalError/fs",
2727
"flags/invalid",
2828
"project",
29+
"typecheck",
2930
"internalError/panic",
3031
"syntax",
3132
"dummy",

Diff for: crates/pg_test_utils/src/test_database.rs

+1-3
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,7 @@ use uuid::Uuid;
44
// TODO: Work with proper config objects instead of a connection_string.
55
// With the current implementation, we can't parse the password from the connection string.
66
pub async fn get_new_test_db() -> PgPool {
7-
dotenv::dotenv()
8-
.ok()
9-
.expect("Unable to load .env file for tests");
7+
dotenv::dotenv().expect("Unable to load .env file for tests");
108

119
let connection_string = std::env::var("DATABASE_URL").expect("DATABASE_URL not set");
1210
let password = std::env::var("DB_PASSWORD").unwrap_or("postgres".into());

Diff for: crates/pg_treesitter_queries/src/lib.rs

+5-5
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ impl<'a> TreeSitterQueriesExecutor<'a> {
2121

2222
#[allow(private_bounds)]
2323
pub fn add_query_results<Q: Query<'a>>(&mut self) {
24-
let mut results = Q::execute(self.root_node, &self.stmt);
24+
let mut results = Q::execute(self.root_node, self.stmt);
2525
self.results.append(&mut results);
2626
}
2727

@@ -104,9 +104,9 @@ where
104104
let mut parser = tree_sitter::Parser::new();
105105
parser.set_language(tree_sitter_sql::language()).unwrap();
106106

107-
let tree = parser.parse(&sql, None).unwrap();
107+
let tree = parser.parse(sql, None).unwrap();
108108

109-
let mut executor = TreeSitterQueriesExecutor::new(tree.root_node(), &sql);
109+
let mut executor = TreeSitterQueriesExecutor::new(tree.root_node(), sql);
110110

111111
executor.add_query_results::<RelationMatch>();
112112

@@ -152,7 +152,7 @@ on sq1.id = pt.id;
152152
let mut parser = tree_sitter::Parser::new();
153153
parser.set_language(tree_sitter_sql::language()).unwrap();
154154

155-
let tree = parser.parse(&sql, None).unwrap();
155+
let tree = parser.parse(sql, None).unwrap();
156156

157157
// trust me bro
158158
let range = {
@@ -172,7 +172,7 @@ on sq1.id = pt.id;
172172
cursor.node().range()
173173
};
174174

175-
let mut executor = TreeSitterQueriesExecutor::new(tree.root_node(), &sql);
175+
let mut executor = TreeSitterQueriesExecutor::new(tree.root_node(), sql);
176176

177177
executor.add_query_results::<RelationMatch>();
178178

Diff for: crates/pg_treesitter_queries/src/queries/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ pub enum QueryResult<'a> {
77
Relation(RelationMatch<'a>),
88
}
99

10-
impl<'a> QueryResult<'a> {
10+
impl QueryResult<'_> {
1111
pub fn within_range(&self, range: &tree_sitter::Range) -> bool {
1212
match self {
1313
Self::Relation(rm) => {

Diff for: crates/pg_treesitter_queries/src/queries/relations.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use crate::{Query, QueryResult};
55
use super::QueryTryFrom;
66

77
static TS_QUERY: LazyLock<tree_sitter::Query> = LazyLock::new(|| {
8-
static QUERY_STR: &'static str = r#"
8+
static QUERY_STR: &str = r#"
99
(relation
1010
(object_reference
1111
.
@@ -15,7 +15,7 @@ static TS_QUERY: LazyLock<tree_sitter::Query> = LazyLock::new(|| {
1515
)+
1616
)
1717
"#;
18-
tree_sitter::Query::new(tree_sitter_sql::language(), &QUERY_STR).expect("Invalid TS Query")
18+
tree_sitter::Query::new(tree_sitter_sql::language(), QUERY_STR).expect("Invalid TS Query")
1919
});
2020

2121
#[derive(Debug)]
@@ -24,7 +24,7 @@ pub struct RelationMatch<'a> {
2424
pub(crate) table: tree_sitter::Node<'a>,
2525
}
2626

27-
impl<'a> RelationMatch<'a> {
27+
impl RelationMatch<'_> {
2828
pub fn get_schema(&self, sql: &str) -> Option<String> {
2929
let str = self
3030
.schema
@@ -48,7 +48,7 @@ impl<'a> TryFrom<&'a QueryResult<'a>> for &'a RelationMatch<'a> {
4848

4949
fn try_from(q: &'a QueryResult<'a>) -> Result<Self, Self::Error> {
5050
match q {
51-
QueryResult::Relation(r) => Ok(&r),
51+
QueryResult::Relation(r) => Ok(r),
5252

5353
#[allow(unreachable_patterns)]
5454
_ => Err("Invalid QueryResult type".into()),

Diff for: crates/pg_typecheck/Cargo.toml

+7-7
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,16 @@ version = "0.0.0"
1212

1313

1414
[dependencies]
15-
pg_base_db.workspace = true
15+
insta = "1.31.0"
16+
pg_console.workspace = true
17+
pg_diagnostics.workspace = true
1618
pg_query_ext.workspace = true
1719
pg_schema_cache.workspace = true
18-
pg_syntax.workspace = true
20+
sqlx.workspace = true
1921
text-size.workspace = true
20-
21-
sqlx.workspace = true
22-
23-
async-std = "1.12.0"
24-
22+
tokio.workspace = true
23+
tree-sitter.workspace = true
24+
tree_sitter_sql.workspace = true
2525

2626
[dev-dependencies]
2727
pg_test_utils.workspace = true

Diff for: crates/pg_typecheck/src/diagnostics.rs

+217
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
use std::io;
2+
3+
use pg_console::markup;
4+
use pg_diagnostics::{Advices, Diagnostic, LogCategory, MessageAndDescription, Severity, Visit};
5+
use sqlx::postgres::{PgDatabaseError, PgSeverity};
6+
use text_size::TextRange;
7+
8+
/// A specialized diagnostic for the typechecker.
9+
///
10+
/// Type diagnostics are always **errors**.
11+
#[derive(Clone, Debug, Diagnostic)]
12+
#[diagnostic(category = "typecheck")]
13+
pub struct TypecheckDiagnostic {
14+
#[location(span)]
15+
span: Option<TextRange>,
16+
#[description]
17+
#[message]
18+
message: MessageAndDescription,
19+
#[advice]
20+
advices: TypecheckAdvices,
21+
#[severity]
22+
severity: Severity,
23+
}
24+
25+
#[derive(Debug, Clone)]
26+
struct TypecheckAdvices {
27+
code: String,
28+
schema: Option<String>,
29+
table: Option<String>,
30+
column: Option<String>,
31+
data_type: Option<String>,
32+
constraint: Option<String>,
33+
line: Option<usize>,
34+
file: Option<String>,
35+
detail: Option<String>,
36+
routine: Option<String>,
37+
where_: Option<String>,
38+
hint: Option<String>,
39+
}
40+
41+
impl Advices for TypecheckAdvices {
42+
fn record(&self, visitor: &mut dyn Visit) -> io::Result<()> {
43+
// First, show the error code
44+
visitor.record_log(
45+
LogCategory::Error,
46+
&markup! { "Error Code: " <Emphasis>{&self.code}</Emphasis> },
47+
)?;
48+
49+
// Show detailed message if available
50+
if let Some(detail) = &self.detail {
51+
visitor.record_log(LogCategory::Info, &detail)?;
52+
}
53+
54+
// Show object location information
55+
if let (Some(schema), Some(table)) = (&self.schema, &self.table) {
56+
let mut location = format!("In table: {schema}.{table}");
57+
if let Some(column) = &self.column {
58+
location.push_str(&format!(", column: {column}"));
59+
}
60+
visitor.record_log(LogCategory::Info, &location)?;
61+
}
62+
63+
// Show constraint information
64+
if let Some(constraint) = &self.constraint {
65+
visitor.record_log(
66+
LogCategory::Info,
67+
&markup! { "Constraint: " <Emphasis>{constraint}</Emphasis> },
68+
)?;
69+
}
70+
71+
// Show data type information
72+
if let Some(data_type) = &self.data_type {
73+
visitor.record_log(
74+
LogCategory::Info,
75+
&markup! { "Data type: " <Emphasis>{data_type}</Emphasis> },
76+
)?;
77+
}
78+
79+
// Show context information
80+
if let Some(where_) = &self.where_ {
81+
visitor.record_log(LogCategory::Info, &markup! { "Context:\n"{where_}"" })?;
82+
}
83+
84+
// Show hint if available
85+
if let Some(hint) = &self.hint {
86+
visitor.record_log(LogCategory::Info, &markup! { "Hint: "{hint}"" })?;
87+
}
88+
89+
// Show source location if available
90+
if let (Some(file), Some(line)) = (&self.file, &self.line) {
91+
if let Some(routine) = &self.routine {
92+
visitor.record_log(
93+
LogCategory::Info,
94+
&markup! { "Source: "{file}":"{line}" in "{routine}"" },
95+
)?;
96+
} else {
97+
visitor.record_log(LogCategory::Info, &markup! { "Source: "{file}":"{line}"" })?;
98+
}
99+
}
100+
101+
Ok(())
102+
}
103+
}
104+
105+
pub(crate) fn create_type_error(
106+
pg_err: &PgDatabaseError,
107+
ts: Option<&tree_sitter::Tree>,
108+
) -> TypecheckDiagnostic {
109+
let position = pg_err.position().and_then(|pos| match pos {
110+
sqlx::postgres::PgErrorPosition::Original(pos) => Some(pos - 1),
111+
_ => None,
112+
});
113+
114+
let range = position.and_then(|pos| {
115+
ts.and_then(|tree| {
116+
tree.root_node()
117+
.named_descendant_for_byte_range(pos, pos)
118+
.map(|node| {
119+
TextRange::new(
120+
node.start_byte().try_into().unwrap(),
121+
node.end_byte().try_into().unwrap(),
122+
)
123+
})
124+
})
125+
});
126+
127+
let severity = match pg_err.severity() {
128+
PgSeverity::Panic => Severity::Error,
129+
PgSeverity::Fatal => Severity::Error,
130+
PgSeverity::Error => Severity::Error,
131+
PgSeverity::Warning => Severity::Warning,
132+
PgSeverity::Notice => Severity::Hint,
133+
PgSeverity::Debug => Severity::Hint,
134+
PgSeverity::Info => Severity::Information,
135+
PgSeverity::Log => Severity::Information,
136+
};
137+
138+
TypecheckDiagnostic {
139+
message: pg_err.to_string().into(),
140+
severity,
141+
span: range,
142+
advices: TypecheckAdvices {
143+
code: pg_err.code().to_string(),
144+
hint: pg_err.hint().and_then(|s| {
145+
if !s.is_empty() {
146+
Some(s.to_string())
147+
} else {
148+
None
149+
}
150+
}),
151+
schema: pg_err.schema().and_then(|s| {
152+
if !s.is_empty() {
153+
Some(s.to_string())
154+
} else {
155+
None
156+
}
157+
}),
158+
table: pg_err.table().and_then(|s| {
159+
if !s.is_empty() {
160+
Some(s.to_string())
161+
} else {
162+
None
163+
}
164+
}),
165+
detail: pg_err.detail().and_then(|s| {
166+
if !s.is_empty() {
167+
Some(s.to_string())
168+
} else {
169+
None
170+
}
171+
}),
172+
column: pg_err.column().and_then(|s| {
173+
if !s.is_empty() {
174+
Some(s.to_string())
175+
} else {
176+
None
177+
}
178+
}),
179+
data_type: pg_err.data_type().and_then(|s| {
180+
if !s.is_empty() {
181+
Some(s.to_string())
182+
} else {
183+
None
184+
}
185+
}),
186+
constraint: pg_err.constraint().and_then(|s| {
187+
if !s.is_empty() {
188+
Some(s.to_string())
189+
} else {
190+
None
191+
}
192+
}),
193+
line: pg_err.line(),
194+
file: pg_err.file().and_then(|s| {
195+
if !s.is_empty() {
196+
Some(s.to_string())
197+
} else {
198+
None
199+
}
200+
}),
201+
routine: pg_err.routine().and_then(|s| {
202+
if !s.is_empty() {
203+
Some(s.to_string())
204+
} else {
205+
None
206+
}
207+
}),
208+
where_: pg_err.r#where().and_then(|s| {
209+
if !s.is_empty() {
210+
Some(s.to_string())
211+
} else {
212+
None
213+
}
214+
}),
215+
},
216+
}
217+
}

0 commit comments

Comments
 (0)