Skip to content

Commit afd7ae8

Browse files
authored
ide: add goto def & hover support for types in casts (#799)
1 parent 317435f commit afd7ae8

File tree

7 files changed

+261
-17
lines changed

7 files changed

+261
-17
lines changed

crates/squawk_ide/src/binder.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -375,7 +375,7 @@ fn bind_set(b: &mut Binder, set: ast::Set) {
375375
}
376376
}
377377

378-
fn extract_string_literal(literal: &ast::Literal) -> Option<String> {
378+
pub(crate) fn extract_string_literal(literal: &ast::Literal) -> Option<String> {
379379
let text = literal.syntax().text().to_string();
380380

381381
if text.starts_with('\'') && text.ends_with('\'') && text.len() >= 2 {

crates/squawk_ide/src/document_symbols.rs

Lines changed: 129 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use rowan::TextRange;
22
use squawk_syntax::ast::{self, AstNode};
33

4-
use crate::binder;
4+
use crate::binder::{self, extract_string_literal};
55
use crate::resolve::{resolve_function_info, resolve_table_info, resolve_type_info};
66

77
#[derive(Debug)]
@@ -10,6 +10,7 @@ pub enum DocumentSymbolKind {
1010
Function,
1111
Type,
1212
Column,
13+
Variant,
1314
}
1415

1516
#[derive(Debug)]
@@ -126,13 +127,28 @@ fn create_type_symbol(
126127
let full_range = create_type.syntax().text_range();
127128
let focus_range = name_node.syntax().text_range();
128129

130+
let mut children = vec![];
131+
if let Some(variant_list) = create_type.variant_list() {
132+
for variant in variant_list.variants() {
133+
if let Some(variant_symbol) = create_variant_symbol(variant) {
134+
children.push(variant_symbol);
135+
}
136+
}
137+
} else if let Some(column_list) = create_type.column_list() {
138+
for column in column_list.columns() {
139+
if let Some(column_symbol) = create_column_symbol(column) {
140+
children.push(column_symbol);
141+
}
142+
}
143+
}
144+
129145
Some(DocumentSymbol {
130146
name,
131147
detail: None,
132148
kind: DocumentSymbolKind::Type,
133149
full_range,
134150
focus_range,
135-
children: vec![],
151+
children,
136152
})
137153
}
138154

@@ -155,6 +171,23 @@ fn create_column_symbol(column: ast::Column) -> Option<DocumentSymbol> {
155171
})
156172
}
157173

174+
fn create_variant_symbol(variant: ast::Variant) -> Option<DocumentSymbol> {
175+
let literal = variant.literal()?;
176+
let name = extract_string_literal(&literal)?;
177+
178+
let full_range = variant.syntax().text_range();
179+
let focus_range = literal.syntax().text_range();
180+
181+
Some(DocumentSymbol {
182+
name,
183+
detail: None,
184+
kind: DocumentSymbolKind::Variant,
185+
full_range,
186+
focus_range,
187+
children: vec![],
188+
})
189+
}
190+
158191
#[cfg(test)]
159192
mod tests {
160193
use super::*;
@@ -197,6 +230,7 @@ mod tests {
197230
DocumentSymbolKind::Function => "function",
198231
DocumentSymbolKind::Type => "type",
199232
DocumentSymbolKind::Column => "column",
233+
DocumentSymbolKind::Variant => "variant",
200234
};
201235

202236
let title = if let Some(detail) = &symbol.detail {
@@ -227,10 +261,14 @@ mod tests {
227261
.map(|child| {
228262
let kind = match child.kind {
229263
DocumentSymbolKind::Column => "column",
230-
_ => unreachable!("only columns can be children"),
264+
DocumentSymbolKind::Variant => "variant",
265+
_ => unreachable!("only columns and variants can be children"),
231266
};
232-
let detail = &child.detail.as_ref().unwrap();
233-
format!("{}: {} {}", kind, child.name, detail)
267+
if let Some(detail) = &child.detail {
268+
format!("{}: {} {}", kind, child.name, detail)
269+
} else {
270+
format!("{}: {}", kind, child.name)
271+
}
234272
})
235273
.collect();
236274

@@ -392,7 +430,16 @@ create function my_schema.hello() returns void as $$ select 1; $$ language sql;
392430
│ ┬───────────┯━━━━━───────────────────────────────
393431
│ │ │
394432
│ │ focus range
395-
╰╴full range
433+
│ full range
434+
435+
436+
1 │ create type status as enum ('active', 'inactive');
437+
│ ┯━━━━━━━ ┯━━━━━━━━━
438+
│ │ │
439+
│ │ full range for `variant: inactive`
440+
│ │ focus range
441+
│ full range for `variant: active`
442+
╰╴ focus range
396443
"
397444
);
398445
}
@@ -408,7 +455,43 @@ create function my_schema.hello() returns void as $$ select 1; $$ language sql;
408455
│ ┬───────────┯━━━━━────────────────────────
409456
│ │ │
410457
│ │ focus range
411-
╰╴full range
458+
│ full range
459+
460+
461+
1 │ create type person as (name text, age int);
462+
│ ┯━━━───── ┯━━────
463+
│ │ │
464+
│ │ full range for `column: age int`
465+
│ │ focus range
466+
│ full range for `column: name text`
467+
╰╴ focus range
468+
"
469+
);
470+
}
471+
472+
#[test]
473+
fn create_type_composite_multiple_columns() {
474+
assert_snapshot!(
475+
symbols("create type address as (street text, city text, zip varchar(10));"),
476+
@r"
477+
info: type: public.address
478+
╭▸
479+
1 │ create type address as (street text, city text, zip varchar(10));
480+
│ ┬───────────┯━━━━━━─────────────────────────────────────────────
481+
│ │ │
482+
│ │ focus range
483+
│ full range
484+
485+
486+
1 │ create type address as (street text, city text, zip varchar(10));
487+
│ ┯━━━━━───── ┯━━━───── ┯━━────────────
488+
│ │ │ │
489+
│ │ │ full range for `column: zip varchar(10)`
490+
│ │ │ focus range
491+
│ │ full range for `column: city text`
492+
│ │ focus range
493+
│ full range for `column: street text`
494+
╰╴ focus range
412495
"
413496
);
414497
}
@@ -424,7 +507,45 @@ create function my_schema.hello() returns void as $$ select 1; $$ language sql;
424507
│ ┬────────────────────┯━━━━━───────────────────────────────
425508
│ │ │
426509
│ │ focus range
427-
╰╴full range
510+
│ full range
511+
512+
513+
1 │ create type myschema.status as enum ('active', 'inactive');
514+
│ ┯━━━━━━━ ┯━━━━━━━━━
515+
│ │ │
516+
│ │ full range for `variant: inactive`
517+
│ │ focus range
518+
│ full range for `variant: active`
519+
╰╴ focus range
520+
"
521+
);
522+
}
523+
524+
#[test]
525+
fn create_type_enum_multiple_variants() {
526+
assert_snapshot!(
527+
symbols("create type priority as enum ('low', 'medium', 'high', 'urgent');"),
528+
@r"
529+
info: type: public.priority
530+
╭▸
531+
1 │ create type priority as enum ('low', 'medium', 'high', 'urgent');
532+
│ ┬───────────┯━━━━━━━────────────────────────────────────────────
533+
│ │ │
534+
│ │ focus range
535+
│ full range
536+
537+
538+
1 │ create type priority as enum ('low', 'medium', 'high', 'urgent');
539+
│ ┯━━━━ ┯━━━━━━━ ┯━━━━━ ┯━━━━━━━
540+
│ │ │ │ │
541+
│ │ │ │ full range for `variant: urgent`
542+
│ │ │ │ focus range
543+
│ │ │ full range for `variant: high`
544+
│ │ │ focus range
545+
│ │ full range for `variant: medium`
546+
│ │ focus range
547+
│ full range for `variant: low`
548+
╰╴ focus range
428549
"
429550
);
430551
}

crates/squawk_ide/src/goto_definition.rs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -457,6 +457,48 @@ drop type int4_range$0;
457457
");
458458
}
459459

460+
#[test]
461+
fn goto_cast_operator() {
462+
assert_snapshot!(goto("
463+
create type foo as enum ('a', 'b');
464+
select x::foo$0;
465+
"), @r"
466+
╭▸
467+
2 │ create type foo as enum ('a', 'b');
468+
│ ─── 2. destination
469+
3 │ select x::foo;
470+
╰╴ ─ 1. source
471+
");
472+
}
473+
474+
#[test]
475+
fn goto_cast_function() {
476+
assert_snapshot!(goto("
477+
create type bar as enum ('x', 'y');
478+
select cast(x as bar$0);
479+
"), @r"
480+
╭▸
481+
2 │ create type bar as enum ('x', 'y');
482+
│ ─── 2. destination
483+
3 │ select cast(x as bar);
484+
╰╴ ─ 1. source
485+
");
486+
}
487+
488+
#[test]
489+
fn goto_cast_with_schema() {
490+
assert_snapshot!(goto("
491+
create type public.baz as enum ('m', 'n');
492+
select x::public.baz$0;
493+
"), @r"
494+
╭▸
495+
2 │ create type public.baz as enum ('m', 'n');
496+
│ ─── 2. destination
497+
3 │ select x::public.baz;
498+
╰╴ ─ 1. source
499+
");
500+
}
501+
460502
#[test]
461503
fn begin_to_rollback() {
462504
assert_snapshot!(goto(

crates/squawk_ide/src/hover.rs

Lines changed: 50 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ pub fn hover(file: &ast::SourceFile, offset: TextSize) -> Option<String> {
1616
return hover_column(file, &name_ref, &binder);
1717
}
1818

19+
if is_type_ref(&name_ref) {
20+
return hover_type(file, &name_ref, &binder);
21+
}
22+
1923
if is_select_column(&name_ref) {
2024
// Try hover as column first
2125
if let Some(result) = hover_column(file, &name_ref, &binder) {
@@ -45,10 +49,6 @@ pub fn hover(file: &ast::SourceFile, offset: TextSize) -> Option<String> {
4549
return hover_index(file, &name_ref, &binder);
4650
}
4751

48-
if is_type_ref(&name_ref) {
49-
return hover_type(file, &name_ref, &binder);
50-
}
51-
5252
if is_function_ref(&name_ref) {
5353
return hover_function(file, &name_ref, &binder);
5454
}
@@ -461,10 +461,17 @@ fn is_index_ref(name_ref: &ast::NameRef) -> bool {
461461
}
462462

463463
fn is_type_ref(name_ref: &ast::NameRef) -> bool {
464+
let mut in_type = false;
464465
for ancestor in name_ref.syntax().ancestors() {
466+
if ast::PathType::can_cast(ancestor.kind()) || ast::ExprType::can_cast(ancestor.kind()) {
467+
in_type = true;
468+
}
465469
if ast::DropType::can_cast(ancestor.kind()) {
466470
return true;
467471
}
472+
if ast::CastExpr::can_cast(ancestor.kind()) && in_type {
473+
return true;
474+
}
468475
}
469476
false
470477
}
@@ -1342,6 +1349,45 @@ drop type int4_range$0;
13421349
");
13431350
}
13441351

1352+
#[test]
1353+
fn hover_on_cast_operator() {
1354+
assert_snapshot!(check_hover("
1355+
create type foo as enum ('a', 'b');
1356+
select x::foo$0;
1357+
"), @r"
1358+
hover: type public.foo as enum ('a', 'b')
1359+
╭▸
1360+
3 │ select x::foo;
1361+
╰╴ ─ hover
1362+
");
1363+
}
1364+
1365+
#[test]
1366+
fn hover_on_cast_function() {
1367+
assert_snapshot!(check_hover("
1368+
create type bar as enum ('x', 'y');
1369+
select cast(x as bar$0);
1370+
"), @r"
1371+
hover: type public.bar as enum ('x', 'y')
1372+
╭▸
1373+
3 │ select cast(x as bar);
1374+
╰╴ ─ hover
1375+
");
1376+
}
1377+
1378+
#[test]
1379+
fn hover_on_cast_with_schema() {
1380+
assert_snapshot!(check_hover("
1381+
create type myschema.baz as enum ('m', 'n');
1382+
select x::myschema.baz$0;
1383+
"), @r"
1384+
hover: type myschema.baz as enum ('m', 'n')
1385+
╭▸
1386+
3 │ select x::myschema.baz;
1387+
╰╴ ─ hover
1388+
");
1389+
}
1390+
13451391
#[test]
13461392
fn hover_on_drop_function() {
13471393
assert_snapshot!(check_hover("

0 commit comments

Comments
 (0)