diff --git a/crates/squawk_ide/src/classify.rs b/crates/squawk_ide/src/classify.rs new file mode 100644 index 00000000..9d87c59b --- /dev/null +++ b/crates/squawk_ide/src/classify.rs @@ -0,0 +1,320 @@ +use squawk_syntax::ast::{self, AstNode}; + +#[derive(Debug)] +pub(crate) enum NameRefClass { + DropTable, + Table, + DropIndex, + DropType, + DropView, + DropFunction, + DropAggregate, + DropProcedure, + DropRoutine, + CallProcedure, + DropSchema, + CreateIndex, + CreateIndexColumn, + SelectFunctionCall, + SelectFromTable, + SelectColumn, + SelectQualifiedColumnTable, + SelectQualifiedColumn, + InsertTable, + InsertColumn, + DeleteTable, + DeleteWhereColumn, + UpdateTable, + UpdateWhereColumn, + UpdateSetColumn, + UpdateFromTable, + SchemaQualifier, + TypeReference, +} + +pub(crate) fn classify_name_ref(name_ref: &ast::NameRef) -> Option { + let mut in_partition_item = false; + let mut in_call_expr = false; + let mut in_arg_list = false; + let mut in_column_list = false; + let mut in_where_clause = false; + let mut in_from_clause = false; + let mut in_set_clause = false; + + // TODO: can we combine this if and the one that follows? + if let Some(parent) = name_ref.syntax().parent() + && let Some(field_expr) = ast::FieldExpr::cast(parent.clone()) + && let Some(base) = field_expr.base() + && let ast::Expr::NameRef(base_name_ref) = base + // check that the name_ref we're looking at in the field expr is the + // base name_ref, i.e., the schema, rather than the item + && base_name_ref.syntax() == name_ref.syntax() + { + let is_function_call = field_expr + .syntax() + .parent() + .and_then(ast::CallExpr::cast) + .is_some(); + let is_schema_table_col = field_expr + .syntax() + .parent() + .and_then(ast::FieldExpr::cast) + .is_some(); + + let mut in_from_clause = false; + for ancestor in parent.ancestors() { + if ast::FromClause::can_cast(ancestor.kind()) { + in_from_clause = true; + } + if ast::Select::can_cast(ancestor.kind()) && !in_from_clause { + if is_function_call || is_schema_table_col { + return Some(NameRefClass::SchemaQualifier); + } else { + return Some(NameRefClass::SelectQualifiedColumnTable); + } + } + } + return Some(NameRefClass::SchemaQualifier); + } + + if let Some(parent) = name_ref.syntax().parent() + && let Some(field_expr) = ast::FieldExpr::cast(parent.clone()) + && field_expr + .field() + // we're at the field in a FieldExpr, i.e., foo.bar + // ^^^ + .is_some_and(|field_name_ref| field_name_ref.syntax() == name_ref.syntax()) + // we're not inside a call expr + && field_expr + .syntax() + .parent() + .and_then(ast::CallExpr::cast) + .is_none() + { + let is_base_of_outer_field_expr = field_expr + .syntax() + .parent() + .and_then(ast::FieldExpr::cast) + .is_some(); + + let mut in_from_clause = false; + let mut in_cast_expr = false; + for ancestor in parent.ancestors() { + if ast::CastExpr::can_cast(ancestor.kind()) { + in_cast_expr = true; + } + if ast::FromClause::can_cast(ancestor.kind()) { + in_from_clause = true; + } + if ast::Select::can_cast(ancestor.kind()) && !in_from_clause { + if in_cast_expr { + return Some(NameRefClass::TypeReference); + } + if is_base_of_outer_field_expr { + return Some(NameRefClass::SelectQualifiedColumnTable); + } else if let Some(base) = field_expr.base() + && matches!(base, ast::Expr::NameRef(_) | ast::Expr::FieldExpr(_)) + { + return Some(NameRefClass::SelectQualifiedColumn); + } else { + return Some(NameRefClass::SelectQualifiedColumnTable); + } + } + } + } + + if let Some(parent) = name_ref.syntax().parent() + && let Some(inner_path) = ast::PathSegment::cast(parent) + .and_then(|p| p.syntax().parent().and_then(ast::Path::cast)) + && let Some(outer_path) = inner_path + .syntax() + .parent() + .and_then(|p| ast::Path::cast(p).and_then(|p| p.qualifier())) + && outer_path.syntax() == inner_path.syntax() + { + return Some(NameRefClass::SchemaQualifier); + } + + let mut in_type = false; + for ancestor in name_ref.syntax().ancestors() { + if ast::PathType::can_cast(ancestor.kind()) || ast::ExprType::can_cast(ancestor.kind()) { + in_type = true; + } + if ast::DropTable::can_cast(ancestor.kind()) { + return Some(NameRefClass::DropTable); + } + if ast::Table::can_cast(ancestor.kind()) { + return Some(NameRefClass::Table); + } + if ast::DropIndex::can_cast(ancestor.kind()) { + return Some(NameRefClass::DropIndex); + } + if ast::DropType::can_cast(ancestor.kind()) { + return Some(NameRefClass::DropType); + } + if ast::DropView::can_cast(ancestor.kind()) { + return Some(NameRefClass::DropView); + } + if ast::CastExpr::can_cast(ancestor.kind()) && in_type { + return Some(NameRefClass::TypeReference); + } + if ast::DropFunction::can_cast(ancestor.kind()) { + return Some(NameRefClass::DropFunction); + } + if ast::DropAggregate::can_cast(ancestor.kind()) { + return Some(NameRefClass::DropAggregate); + } + if ast::DropProcedure::can_cast(ancestor.kind()) { + return Some(NameRefClass::DropProcedure); + } + if ast::DropRoutine::can_cast(ancestor.kind()) { + return Some(NameRefClass::DropRoutine); + } + if ast::Call::can_cast(ancestor.kind()) { + return Some(NameRefClass::CallProcedure); + } + if ast::DropSchema::can_cast(ancestor.kind()) { + return Some(NameRefClass::DropSchema); + } + if ast::PartitionItem::can_cast(ancestor.kind()) { + in_partition_item = true; + } + if ast::CreateIndex::can_cast(ancestor.kind()) { + if in_partition_item { + return Some(NameRefClass::CreateIndexColumn); + } + return Some(NameRefClass::CreateIndex); + } + if ast::ArgList::can_cast(ancestor.kind()) { + in_arg_list = true; + } + if ast::CallExpr::can_cast(ancestor.kind()) { + in_call_expr = true; + } + if ast::FromClause::can_cast(ancestor.kind()) { + in_from_clause = true; + } + if ast::Select::can_cast(ancestor.kind()) { + if in_call_expr && !in_arg_list { + return Some(NameRefClass::SelectFunctionCall); + } + if in_from_clause { + return Some(NameRefClass::SelectFromTable); + } + // Classify as SelectColumn for target list, WHERE, ORDER BY, GROUP BY, etc. + // (anything in SELECT except FROM clause) + return Some(NameRefClass::SelectColumn); + } + if ast::ColumnList::can_cast(ancestor.kind()) { + in_column_list = true; + } + if ast::Insert::can_cast(ancestor.kind()) { + if in_column_list { + return Some(NameRefClass::InsertColumn); + } + return Some(NameRefClass::InsertTable); + } + if ast::WhereClause::can_cast(ancestor.kind()) { + in_where_clause = true; + } + if ast::SetClause::can_cast(ancestor.kind()) { + in_set_clause = true; + } + if ast::Delete::can_cast(ancestor.kind()) { + if in_where_clause { + return Some(NameRefClass::DeleteWhereColumn); + } + return Some(NameRefClass::DeleteTable); + } + if ast::Update::can_cast(ancestor.kind()) { + if in_where_clause { + return Some(NameRefClass::UpdateWhereColumn); + } + if in_set_clause { + return Some(NameRefClass::UpdateSetColumn); + } + if in_from_clause { + return Some(NameRefClass::UpdateFromTable); + } + return Some(NameRefClass::UpdateTable); + } + } + + None +} + +#[derive(Debug)] +pub(crate) enum NameClass { + ColumnDefinition { + create_table: ast::CreateTable, + column: ast::Column, + }, + CreateTable(ast::CreateTable), + WithTable(ast::WithTable), + CreateIndex(ast::CreateIndex), + CreateType(ast::CreateType), + CreateFunction(ast::CreateFunction), + CreateAggregate(ast::CreateAggregate), + CreateProcedure(ast::CreateProcedure), + CreateSchema(ast::CreateSchema), + ViewColumnList { + create_view: ast::CreateView, + name: ast::Name, + }, + CreateView(ast::CreateView), +} + +pub(crate) fn classify_name(name: &ast::Name) -> Option { + let parent = name.syntax().parent(); + let column_parent = parent.clone().and_then(ast::Column::cast); + let with_table_parent = parent.and_then(ast::WithTable::cast); + let mut has_column_list = false; + + for ancestor in name.syntax().ancestors() { + if !has_column_list && ast::ColumnList::can_cast(ancestor.kind()) { + has_column_list = true; + } + if let Some(create_table) = ast::CreateTable::cast(ancestor.clone()) { + if let Some(column) = column_parent { + return Some(NameClass::ColumnDefinition { + create_table, + column, + }); + } + return Some(NameClass::CreateTable(create_table)); + } + if let Some(create_index) = ast::CreateIndex::cast(ancestor.clone()) { + return Some(NameClass::CreateIndex(create_index)); + } + if let Some(create_type) = ast::CreateType::cast(ancestor.clone()) { + return Some(NameClass::CreateType(create_type)); + } + if let Some(create_function) = ast::CreateFunction::cast(ancestor.clone()) { + return Some(NameClass::CreateFunction(create_function)); + } + if let Some(create_aggregate) = ast::CreateAggregate::cast(ancestor.clone()) { + return Some(NameClass::CreateAggregate(create_aggregate)); + } + if let Some(create_procedure) = ast::CreateProcedure::cast(ancestor.clone()) { + return Some(NameClass::CreateProcedure(create_procedure)); + } + if let Some(create_schema) = ast::CreateSchema::cast(ancestor.clone()) { + return Some(NameClass::CreateSchema(create_schema)); + } + if let Some(create_view) = ast::CreateView::cast(ancestor.clone()) { + if has_column_list { + return Some(NameClass::ViewColumnList { + create_view, + name: name.clone(), + }); + } + return Some(NameClass::CreateView(create_view)); + } + } + + if let Some(with_table) = with_table_parent { + return Some(NameClass::WithTable(with_table)); + } + + None +} diff --git a/crates/squawk_ide/src/hover.rs b/crates/squawk_ide/src/hover.rs index 4e1b50a7..7164ffb7 100644 --- a/crates/squawk_ide/src/hover.rs +++ b/crates/squawk_ide/src/hover.rs @@ -1,3 +1,4 @@ +use crate::classify::{NameClass, NameRefClass, classify_name, classify_name_ref}; use crate::offsets::token_from_offset; use crate::resolve; use crate::{binder, symbols::Name}; @@ -10,145 +11,97 @@ pub fn hover(file: &ast::SourceFile, offset: TextSize) -> Option { let binder = binder::bind(file); - // TODO: can we use the classify_name_ref_context function from goto def here? if let Some(name_ref) = ast::NameRef::cast(parent.clone()) { - if is_column_ref(&name_ref) { - return hover_column(file, &name_ref, &binder); - } - - if is_type_ref(&name_ref) { - return hover_type(file, &name_ref, &binder); - } - - if is_select_column(&name_ref) { - // Try hover as column first - if let Some(result) = hover_column(file, &name_ref, &binder) { - return Some(result); + let context = classify_name_ref(&name_ref)?; + match context { + NameRefClass::CreateIndexColumn + | NameRefClass::InsertColumn + | NameRefClass::DeleteWhereColumn + | NameRefClass::UpdateWhereColumn + | NameRefClass::UpdateSetColumn => return hover_column(file, &name_ref, &binder), + NameRefClass::TypeReference | NameRefClass::DropType => { + return hover_type(file, &name_ref, &binder); } - // If no column, try as function (handles field-style function calls like `t.b`) - if let Some(result) = hover_function(file, &name_ref, &binder) { - return Some(result); + NameRefClass::SelectColumn | NameRefClass::SelectQualifiedColumn => { + // Try hover as column first + if let Some(result) = hover_column(file, &name_ref, &binder) { + return Some(result); + } + // If no column, try as function (handles field-style function calls like `t.b`) + if let Some(result) = hover_function(file, &name_ref, &binder) { + return Some(result); + } + // Finally try as table (handles case like `select t from t;` where t is the table) + return hover_table(file, &name_ref, &binder); } - // Finally try as table (handles case like `select t from t;` where t is the table) - return hover_table(file, &name_ref, &binder); - } - - if is_table_ref(&name_ref) { - return hover_table(file, &name_ref, &binder); - } - - if is_select_from_table(&name_ref) { - return hover_table(file, &name_ref, &binder); - } - - if is_update_from_table(&name_ref) { - return hover_table(file, &name_ref, &binder); - } - - if is_index_ref(&name_ref) { - return hover_index(file, &name_ref, &binder); - } - - if is_function_ref(&name_ref) { - return hover_function(file, &name_ref, &binder); - } - - if is_aggregate_ref(&name_ref) { - return hover_aggregate(file, &name_ref, &binder); - } - - if is_procedure_ref(&name_ref) { - return hover_procedure(file, &name_ref, &binder); - } - - if is_routine_ref(&name_ref) { - return hover_routine(file, &name_ref, &binder); - } - - if is_call_procedure(&name_ref) { - return hover_procedure(file, &name_ref, &binder); - } - - if is_select_function_call(&name_ref) { - // Try function first, but fall back to column if no function found - // (handles function-call-style column access like `select a(t)`) - if let Some(result) = hover_function(file, &name_ref, &binder) { - return Some(result); + NameRefClass::Table + | NameRefClass::DropTable + | NameRefClass::DropView + | NameRefClass::CreateIndex + | NameRefClass::InsertTable + | NameRefClass::DeleteTable + | NameRefClass::UpdateTable + | NameRefClass::SelectFromTable + | NameRefClass::UpdateFromTable + | NameRefClass::SelectQualifiedColumnTable => { + return hover_table(file, &name_ref, &binder); + } + NameRefClass::DropIndex => return hover_index(file, &name_ref, &binder), + NameRefClass::DropFunction => return hover_function(file, &name_ref, &binder), + NameRefClass::DropAggregate => return hover_aggregate(file, &name_ref, &binder), + NameRefClass::DropProcedure | NameRefClass::CallProcedure => { + return hover_procedure(file, &name_ref, &binder); + } + NameRefClass::DropRoutine => return hover_routine(file, &name_ref, &binder), + NameRefClass::SelectFunctionCall => { + // Try function first, but fall back to column if no function found + // (handles function-call-style column access like `select a(t)`) + if let Some(result) = hover_function(file, &name_ref, &binder) { + return Some(result); + } + return hover_column(file, &name_ref, &binder); + } + NameRefClass::SchemaQualifier | NameRefClass::DropSchema => { + return hover_schema(file, &name_ref, &binder); } - return hover_column(file, &name_ref, &binder); - } - - if is_schema_ref(&name_ref) { - return hover_schema(file, &name_ref, &binder); } } if let Some(name) = ast::Name::cast(parent) { - if let Some(column) = name.syntax().parent().and_then(ast::Column::cast) - && let Some(create_table) = column.syntax().ancestors().find_map(ast::CreateTable::cast) - { - return hover_column_definition(&create_table, &column, &binder); - } - - if let Some(create_table) = name.syntax().ancestors().find_map(ast::CreateTable::cast) { - return format_create_table(&create_table, &binder); - } - - if let Some(with_table) = name.syntax().parent().and_then(ast::WithTable::cast) { - return format_with_table(&with_table); - } - - if let Some(create_index) = name.syntax().ancestors().find_map(ast::CreateIndex::cast) { - return format_create_index(&create_index, &binder); - } - - if let Some(create_type) = name.syntax().ancestors().find_map(ast::CreateType::cast) { - return format_create_type(&create_type, &binder); - } - - if let Some(create_function) = name - .syntax() - .ancestors() - .find_map(ast::CreateFunction::cast) - { - return format_create_function(&create_function, &binder); - } - - if let Some(create_aggregate) = name - .syntax() - .ancestors() - .find_map(ast::CreateAggregate::cast) - { - return format_create_aggregate(&create_aggregate, &binder); - } - - if let Some(create_procedure) = name - .syntax() - .ancestors() - .find_map(ast::CreateProcedure::cast) - { - return format_create_procedure(&create_procedure, &binder); - } - - if let Some(create_schema) = name.syntax().ancestors().find_map(ast::CreateSchema::cast) { - return format_create_schema(&create_schema); - } - - // create view t(x) as select 1; - // ^ - if let Some(column_list) = name.syntax().ancestors().find_map(ast::ColumnList::cast) - && let Some(create_view) = column_list - .syntax() - .ancestors() - .find_map(ast::CreateView::cast) - { - return format_view_column(&create_view, Name::from_node(&name), &binder); - } - - // create view t as select 1; - // ^ - if let Some(create_view) = name.syntax().ancestors().find_map(ast::CreateView::cast) { - return format_create_view(&create_view, &binder); + let context = classify_name(&name)?; + match context { + NameClass::ColumnDefinition { + create_table, + column, + } => return hover_column_definition(&create_table, &column, &binder), + NameClass::CreateTable(create_table) => { + return format_create_table(&create_table, &binder); + } + NameClass::WithTable(with_table) => return format_with_table(&with_table), + NameClass::CreateIndex(create_index) => { + return format_create_index(&create_index, &binder); + } + NameClass::CreateType(create_type) => { + return format_create_type(&create_type, &binder); + } + NameClass::CreateFunction(create_function) => { + return format_create_function(&create_function, &binder); + } + NameClass::CreateAggregate(create_aggregate) => { + return format_create_aggregate(&create_aggregate, &binder); + } + NameClass::CreateProcedure(create_procedure) => { + return format_create_procedure(&create_procedure, &binder); + } + NameClass::CreateSchema(create_schema) => { + return format_create_schema(&create_schema); + } + NameClass::ViewColumnList { create_view, name } => { + return format_view_column(&create_view, Name::from_node(&name), &binder); + } + NameClass::CreateView(create_view) => { + return format_create_view(&create_view, &binder); + } } } @@ -455,248 +408,6 @@ fn type_schema(create_type: &ast::CreateType, binder: &binder::Binder) -> Option search_path.first().map(|s| s.to_string()) } -fn is_column_ref(name_ref: &ast::NameRef) -> bool { - let mut in_partition_item = false; - let mut in_column_list = false; - let mut in_where_clause = false; - let mut in_set_clause = false; - - for ancestor in name_ref.syntax().ancestors() { - if ast::PartitionItem::can_cast(ancestor.kind()) { - in_partition_item = true; - } - if ast::CreateIndex::can_cast(ancestor.kind()) { - return in_partition_item; - } - if ast::ColumnList::can_cast(ancestor.kind()) { - in_column_list = true; - } - if ast::Insert::can_cast(ancestor.kind()) { - return in_column_list; - } - if ast::WhereClause::can_cast(ancestor.kind()) { - in_where_clause = true; - } - if ast::SetClause::can_cast(ancestor.kind()) { - in_set_clause = true; - } - if ast::Delete::can_cast(ancestor.kind()) { - return in_where_clause; - } - if ast::Update::can_cast(ancestor.kind()) { - return in_where_clause || in_set_clause; - } - } - false -} - -fn is_table_ref(name_ref: &ast::NameRef) -> bool { - let mut in_partition_item = false; - let mut in_column_list = false; - let mut in_where_clause = false; - let mut in_set_clause = false; - let mut in_from_clause = false; - - for ancestor in name_ref.syntax().ancestors() { - if ast::DropTable::can_cast(ancestor.kind()) { - return true; - } - if ast::DropView::can_cast(ancestor.kind()) { - return true; - } - if ast::Table::can_cast(ancestor.kind()) { - return true; - } - if ast::ColumnList::can_cast(ancestor.kind()) { - in_column_list = true; - } - if ast::Insert::can_cast(ancestor.kind()) { - return !in_column_list; - } - if ast::WhereClause::can_cast(ancestor.kind()) { - in_where_clause = true; - } - if ast::SetClause::can_cast(ancestor.kind()) { - in_set_clause = true; - } - if ast::FromClause::can_cast(ancestor.kind()) { - in_from_clause = true; - } - if ast::Delete::can_cast(ancestor.kind()) { - return !in_where_clause; - } - if ast::Update::can_cast(ancestor.kind()) { - return !in_where_clause && !in_set_clause && !in_from_clause; - } - if ast::DropIndex::can_cast(ancestor.kind()) { - return false; - } - if ast::PartitionItem::can_cast(ancestor.kind()) { - in_partition_item = true; - } - if ast::CreateIndex::can_cast(ancestor.kind()) { - return !in_partition_item; - } - } - false -} - -fn is_index_ref(name_ref: &ast::NameRef) -> bool { - for ancestor in name_ref.syntax().ancestors() { - if ast::DropIndex::can_cast(ancestor.kind()) { - return true; - } - } - false -} - -fn is_type_ref(name_ref: &ast::NameRef) -> bool { - let mut in_type = false; - for ancestor in name_ref.syntax().ancestors() { - if ast::PathType::can_cast(ancestor.kind()) || ast::ExprType::can_cast(ancestor.kind()) { - in_type = true; - } - if ast::DropType::can_cast(ancestor.kind()) { - return true; - } - if ast::CastExpr::can_cast(ancestor.kind()) && in_type { - return true; - } - } - false -} - -fn is_function_ref(name_ref: &ast::NameRef) -> bool { - for ancestor in name_ref.syntax().ancestors() { - if ast::DropFunction::can_cast(ancestor.kind()) { - return true; - } - } - false -} - -fn is_aggregate_ref(name_ref: &ast::NameRef) -> bool { - for ancestor in name_ref.syntax().ancestors() { - if ast::DropAggregate::can_cast(ancestor.kind()) { - return true; - } - } - false -} - -fn is_procedure_ref(name_ref: &ast::NameRef) -> bool { - for ancestor in name_ref.syntax().ancestors() { - if ast::DropProcedure::can_cast(ancestor.kind()) { - return true; - } - } - false -} - -fn is_routine_ref(name_ref: &ast::NameRef) -> bool { - for ancestor in name_ref.syntax().ancestors() { - if ast::DropRoutine::can_cast(ancestor.kind()) { - return true; - } - } - false -} - -fn is_call_procedure(name_ref: &ast::NameRef) -> bool { - for ancestor in name_ref.syntax().ancestors() { - if ast::Call::can_cast(ancestor.kind()) { - return true; - } - } - false -} - -fn is_select_function_call(name_ref: &ast::NameRef) -> bool { - let mut in_call_expr = false; - let mut in_arg_list = false; - - for ancestor in name_ref.syntax().ancestors() { - if ast::ArgList::can_cast(ancestor.kind()) { - in_arg_list = true; - } - if ast::CallExpr::can_cast(ancestor.kind()) { - in_call_expr = true; - } - if ast::Select::can_cast(ancestor.kind()) && in_call_expr && !in_arg_list { - return true; - } - } - false -} - -fn is_select_from_table(name_ref: &ast::NameRef) -> bool { - let mut in_from_clause = false; - - for ancestor in name_ref.syntax().ancestors() { - if ast::FromClause::can_cast(ancestor.kind()) { - in_from_clause = true; - } - if ast::Select::can_cast(ancestor.kind()) && in_from_clause { - return true; - } - } - false -} - -fn is_update_from_table(name_ref: &ast::NameRef) -> bool { - let mut in_from_clause = false; - - for ancestor in name_ref.syntax().ancestors() { - if ast::FromClause::can_cast(ancestor.kind()) { - in_from_clause = true; - } - if ast::Update::can_cast(ancestor.kind()) && in_from_clause { - return true; - } - } - false -} - -fn is_select_column(name_ref: &ast::NameRef) -> bool { - let mut in_call_expr = false; - let mut in_arg_list = false; - let mut in_from_clause = false; - - for ancestor in name_ref.syntax().ancestors() { - if ast::ArgList::can_cast(ancestor.kind()) { - in_arg_list = true; - } - if ast::CallExpr::can_cast(ancestor.kind()) { - in_call_expr = true; - } - if ast::FromClause::can_cast(ancestor.kind()) { - in_from_clause = true; - } - if ast::Select::can_cast(ancestor.kind()) { - // if we're inside a call expr but not inside an arg list, this is a function call - if in_call_expr && !in_arg_list { - return false; - } - // if we're in FROM clause, this is a table reference, not a column - if in_from_clause { - return false; - } - // anything else in SELECT (target list, WHERE, ORDER BY, etc.) is a column - return true; - } - } - false -} - -fn is_schema_ref(name_ref: &ast::NameRef) -> bool { - for ancestor in name_ref.syntax().ancestors() { - if ast::DropSchema::can_cast(ancestor.kind()) { - return true; - } - } - false -} - fn hover_schema( file: &ast::SourceFile, name_ref: &ast::NameRef, diff --git a/crates/squawk_ide/src/lib.rs b/crates/squawk_ide/src/lib.rs index 0399f921..56519b8b 100644 --- a/crates/squawk_ide/src/lib.rs +++ b/crates/squawk_ide/src/lib.rs @@ -1,4 +1,5 @@ mod binder; +mod classify; pub mod code_actions; pub mod column_name; pub mod document_symbols; diff --git a/crates/squawk_ide/src/resolve.rs b/crates/squawk_ide/src/resolve.rs index f9187ac7..e14b573b 100644 --- a/crates/squawk_ide/src/resolve.rs +++ b/crates/squawk_ide/src/resolve.rs @@ -5,59 +5,28 @@ use squawk_syntax::{ }; use crate::binder::Binder; +use crate::classify::{NameRefClass, classify_name_ref}; use crate::column_name::ColumnName; pub(crate) use crate::symbols::Schema; use crate::symbols::{Name, SymbolKind}; -#[derive(Debug)] -enum NameRefContext { - DropTable, - Table, - DropIndex, - DropType, - DropView, - DropFunction, - DropAggregate, - DropProcedure, - DropRoutine, - CallProcedure, - DropSchema, - CreateIndex, - CreateIndexColumn, - SelectFunctionCall, - SelectFromTable, - SelectColumn, - SelectQualifiedColumnTable, - SelectQualifiedColumn, - InsertTable, - InsertColumn, - DeleteTable, - DeleteWhereColumn, - UpdateTable, - UpdateWhereColumn, - UpdateSetColumn, - UpdateFromTable, - SchemaQualifier, - TypeReference, -} - pub(crate) fn resolve_name_ref(binder: &Binder, name_ref: &ast::NameRef) -> Option { - let context = classify_name_ref_context(name_ref)?; + let context = classify_name_ref(name_ref)?; match context { - NameRefContext::DropTable - | NameRefContext::Table - | NameRefContext::CreateIndex - | NameRefContext::InsertTable - | NameRefContext::DeleteTable - | NameRefContext::UpdateTable => { + NameRefClass::DropTable + | NameRefClass::Table + | NameRefClass::CreateIndex + | NameRefClass::InsertTable + | NameRefClass::DeleteTable + | NameRefClass::UpdateTable => { let path = find_containing_path(name_ref)?; let table_name = extract_table_name(&path)?; let schema = extract_schema_name(&path); let position = name_ref.syntax().text_range().start(); resolve_table(binder, &table_name, &schema, position) } - NameRefContext::SelectFromTable => { + NameRefClass::SelectFromTable => { let table_name = Name::from_node(name_ref); let schema = if let Some(parent) = name_ref.syntax().parent() && let Some(field_expr) = ast::FieldExpr::cast(parent) @@ -83,14 +52,14 @@ pub(crate) fn resolve_name_ref(binder: &Binder, name_ref: &ast::NameRef) -> Opti resolve_view(binder, &table_name, &schema, position) } - NameRefContext::DropIndex => { + NameRefClass::DropIndex => { let path = find_containing_path(name_ref)?; let index_name = extract_table_name(&path)?; let schema = extract_schema_name(&path); let position = name_ref.syntax().text_range().start(); resolve_index(binder, &index_name, &schema, position) } - NameRefContext::DropType | NameRefContext::TypeReference => { + NameRefClass::DropType | NameRefClass::TypeReference => { let (type_name, schema) = if let Some(parent) = name_ref.syntax().parent() && let Some(field_expr) = ast::FieldExpr::cast(parent) && field_expr @@ -115,14 +84,14 @@ pub(crate) fn resolve_name_ref(binder: &Binder, name_ref: &ast::NameRef) -> Opti let position = name_ref.syntax().text_range().start(); resolve_type(binder, &type_name, &schema, position) } - NameRefContext::DropView => { + NameRefClass::DropView => { let path = find_containing_path(name_ref)?; let view_name = extract_table_name(&path)?; let schema = extract_schema_name(&path); let position = name_ref.syntax().text_range().start(); resolve_view(binder, &view_name, &schema, position) } - NameRefContext::DropFunction => { + NameRefClass::DropFunction => { let function_sig = name_ref .syntax() .ancestors() @@ -134,7 +103,7 @@ pub(crate) fn resolve_name_ref(binder: &Binder, name_ref: &ast::NameRef) -> Opti let position = name_ref.syntax().text_range().start(); resolve_function(binder, &function_name, &schema, params.as_deref(), position) } - NameRefContext::DropAggregate => { + NameRefClass::DropAggregate => { let aggregate = name_ref .syntax() .ancestors() @@ -152,7 +121,7 @@ pub(crate) fn resolve_name_ref(binder: &Binder, name_ref: &ast::NameRef) -> Opti position, ) } - NameRefContext::DropProcedure => { + NameRefClass::DropProcedure => { let function_sig = name_ref .syntax() .ancestors() @@ -170,7 +139,7 @@ pub(crate) fn resolve_name_ref(binder: &Binder, name_ref: &ast::NameRef) -> Opti position, ) } - NameRefContext::DropRoutine => { + NameRefClass::DropRoutine => { let function_sig = name_ref .syntax() .ancestors() @@ -195,7 +164,7 @@ pub(crate) fn resolve_name_ref(binder: &Binder, name_ref: &ast::NameRef) -> Opti resolve_procedure(binder, &routine_name, &schema, params.as_deref(), position) } - NameRefContext::CallProcedure => { + NameRefClass::CallProcedure => { let call = name_ref.syntax().ancestors().find_map(ast::Call::cast)?; let path = call.path()?; let procedure_name = extract_table_name(&path)?; @@ -203,11 +172,11 @@ pub(crate) fn resolve_name_ref(binder: &Binder, name_ref: &ast::NameRef) -> Opti let position = name_ref.syntax().text_range().start(); resolve_procedure(binder, &procedure_name, &schema, None, position) } - NameRefContext::DropSchema | NameRefContext::SchemaQualifier => { + NameRefClass::DropSchema | NameRefClass::SchemaQualifier => { let schema_name = Name::from_node(name_ref); resolve_schema(binder, &schema_name) } - NameRefContext::SelectFunctionCall => { + NameRefClass::SelectFunctionCall => { let schema = if let Some(parent_node) = name_ref.syntax().parent() && let Some(field_expr) = ast::FieldExpr::cast(parent_node) { @@ -238,18 +207,18 @@ pub(crate) fn resolve_name_ref(binder: &Binder, name_ref: &ast::NameRef) -> Opti None } - NameRefContext::CreateIndexColumn => resolve_create_index_column(binder, name_ref), - NameRefContext::SelectColumn => resolve_select_column(binder, name_ref), - NameRefContext::SelectQualifiedColumnTable => { + NameRefClass::CreateIndexColumn => resolve_create_index_column(binder, name_ref), + NameRefClass::SelectColumn => resolve_select_column(binder, name_ref), + NameRefClass::SelectQualifiedColumnTable => { resolve_select_qualified_column_table(binder, name_ref) } - NameRefContext::SelectQualifiedColumn => resolve_select_qualified_column(binder, name_ref), - NameRefContext::InsertColumn => resolve_insert_column(binder, name_ref), - NameRefContext::DeleteWhereColumn => resolve_delete_where_column(binder, name_ref), - NameRefContext::UpdateWhereColumn | NameRefContext::UpdateSetColumn => { + NameRefClass::SelectQualifiedColumn => resolve_select_qualified_column(binder, name_ref), + NameRefClass::InsertColumn => resolve_insert_column(binder, name_ref), + NameRefClass::DeleteWhereColumn => resolve_delete_where_column(binder, name_ref), + NameRefClass::UpdateWhereColumn | NameRefClass::UpdateSetColumn => { resolve_update_where_column(binder, name_ref) } - NameRefContext::UpdateFromTable => { + NameRefClass::UpdateFromTable => { let table_name = Name::from_node(name_ref); let schema = if let Some(parent) = name_ref.syntax().parent() && let Some(field_expr) = ast::FieldExpr::cast(parent) @@ -278,217 +247,6 @@ pub(crate) fn resolve_name_ref(binder: &Binder, name_ref: &ast::NameRef) -> Opti } } -fn classify_name_ref_context(name_ref: &ast::NameRef) -> Option { - let mut in_partition_item = false; - let mut in_call_expr = false; - let mut in_arg_list = false; - let mut in_column_list = false; - let mut in_where_clause = false; - let mut in_from_clause = false; - let mut in_set_clause = false; - - // TODO: can we combine this if and the one that follows? - if let Some(parent) = name_ref.syntax().parent() - && let Some(field_expr) = ast::FieldExpr::cast(parent.clone()) - && let Some(base) = field_expr.base() - && let ast::Expr::NameRef(base_name_ref) = base - // check that the name_ref we're looking at in the field expr is the - // base name_ref, i.e., the schema, rather than the item - && base_name_ref.syntax() == name_ref.syntax() - { - let is_function_call = field_expr - .syntax() - .parent() - .and_then(ast::CallExpr::cast) - .is_some(); - let is_schema_table_col = field_expr - .syntax() - .parent() - .and_then(ast::FieldExpr::cast) - .is_some(); - - let mut in_from_clause = false; - for ancestor in parent.ancestors() { - if ast::FromClause::can_cast(ancestor.kind()) { - in_from_clause = true; - } - if ast::Select::can_cast(ancestor.kind()) && !in_from_clause { - if is_function_call || is_schema_table_col { - return Some(NameRefContext::SchemaQualifier); - } else { - return Some(NameRefContext::SelectQualifiedColumnTable); - } - } - } - return Some(NameRefContext::SchemaQualifier); - } - - if let Some(parent) = name_ref.syntax().parent() - && let Some(field_expr) = ast::FieldExpr::cast(parent.clone()) - && field_expr - .field() - // we're at the field in a FieldExpr, i.e., foo.bar - // ^^^ - .is_some_and(|field_name_ref| field_name_ref.syntax() == name_ref.syntax()) - // we're not inside a call expr - && field_expr - .syntax() - .parent() - .and_then(ast::CallExpr::cast) - .is_none() - { - let is_base_of_outer_field_expr = field_expr - .syntax() - .parent() - .and_then(ast::FieldExpr::cast) - .is_some(); - - let mut in_from_clause = false; - let mut in_cast_expr = false; - for ancestor in parent.ancestors() { - if ast::CastExpr::can_cast(ancestor.kind()) { - in_cast_expr = true; - } - if ast::FromClause::can_cast(ancestor.kind()) { - in_from_clause = true; - } - if ast::Select::can_cast(ancestor.kind()) && !in_from_clause { - if in_cast_expr { - return Some(NameRefContext::TypeReference); - } - if is_base_of_outer_field_expr { - return Some(NameRefContext::SelectQualifiedColumnTable); - } else if let Some(base) = field_expr.base() - && matches!(base, ast::Expr::NameRef(_) | ast::Expr::FieldExpr(_)) - { - return Some(NameRefContext::SelectQualifiedColumn); - } else { - return Some(NameRefContext::SelectQualifiedColumnTable); - } - } - } - } - - if let Some(parent) = name_ref.syntax().parent() - && let Some(inner_path) = ast::PathSegment::cast(parent) - .and_then(|p| p.syntax().parent().and_then(ast::Path::cast)) - && let Some(outer_path) = inner_path - .syntax() - .parent() - .and_then(|p| ast::Path::cast(p).and_then(|p| p.qualifier())) - && outer_path.syntax() == inner_path.syntax() - { - return Some(NameRefContext::SchemaQualifier); - } - - let mut in_type = false; - for ancestor in name_ref.syntax().ancestors() { - if ast::PathType::can_cast(ancestor.kind()) || ast::ExprType::can_cast(ancestor.kind()) { - in_type = true; - } - if ast::DropTable::can_cast(ancestor.kind()) { - return Some(NameRefContext::DropTable); - } - if ast::Table::can_cast(ancestor.kind()) { - return Some(NameRefContext::Table); - } - if ast::DropIndex::can_cast(ancestor.kind()) { - return Some(NameRefContext::DropIndex); - } - if ast::DropType::can_cast(ancestor.kind()) { - return Some(NameRefContext::DropType); - } - if ast::DropView::can_cast(ancestor.kind()) { - return Some(NameRefContext::DropView); - } - if ast::CastExpr::can_cast(ancestor.kind()) && in_type { - return Some(NameRefContext::TypeReference); - } - if ast::DropFunction::can_cast(ancestor.kind()) { - return Some(NameRefContext::DropFunction); - } - if ast::DropAggregate::can_cast(ancestor.kind()) { - return Some(NameRefContext::DropAggregate); - } - if ast::DropProcedure::can_cast(ancestor.kind()) { - return Some(NameRefContext::DropProcedure); - } - if ast::DropRoutine::can_cast(ancestor.kind()) { - return Some(NameRefContext::DropRoutine); - } - if ast::Call::can_cast(ancestor.kind()) { - return Some(NameRefContext::CallProcedure); - } - if ast::DropSchema::can_cast(ancestor.kind()) { - return Some(NameRefContext::DropSchema); - } - if ast::PartitionItem::can_cast(ancestor.kind()) { - in_partition_item = true; - } - if ast::CreateIndex::can_cast(ancestor.kind()) { - if in_partition_item { - return Some(NameRefContext::CreateIndexColumn); - } - return Some(NameRefContext::CreateIndex); - } - if ast::ArgList::can_cast(ancestor.kind()) { - in_arg_list = true; - } - if ast::CallExpr::can_cast(ancestor.kind()) { - in_call_expr = true; - } - if ast::FromClause::can_cast(ancestor.kind()) { - in_from_clause = true; - } - if ast::Select::can_cast(ancestor.kind()) { - if in_call_expr && !in_arg_list { - return Some(NameRefContext::SelectFunctionCall); - } - if in_from_clause { - return Some(NameRefContext::SelectFromTable); - } - // Classify as SelectColumn for target list, WHERE, ORDER BY, GROUP BY, etc. - // (anything in SELECT except FROM clause) - return Some(NameRefContext::SelectColumn); - } - if ast::ColumnList::can_cast(ancestor.kind()) { - in_column_list = true; - } - if ast::Insert::can_cast(ancestor.kind()) { - if in_column_list { - return Some(NameRefContext::InsertColumn); - } - return Some(NameRefContext::InsertTable); - } - if ast::WhereClause::can_cast(ancestor.kind()) { - in_where_clause = true; - } - if ast::SetClause::can_cast(ancestor.kind()) { - in_set_clause = true; - } - if ast::Delete::can_cast(ancestor.kind()) { - if in_where_clause { - return Some(NameRefContext::DeleteWhereColumn); - } - return Some(NameRefContext::DeleteTable); - } - if ast::Update::can_cast(ancestor.kind()) { - if in_where_clause { - return Some(NameRefContext::UpdateWhereColumn); - } - if in_set_clause { - return Some(NameRefContext::UpdateSetColumn); - } - if in_from_clause { - return Some(NameRefContext::UpdateFromTable); - } - return Some(NameRefContext::UpdateTable); - } - } - - None -} - fn resolve_table( binder: &Binder, table_name: &Name,