From e9d40dcf79fe4572ef3230b351ad7b9f5610d4e4 Mon Sep 17 00:00:00 2001 From: Steve Dignam Date: Sat, 25 Apr 2026 11:48:41 -0400 Subject: [PATCH] ide: cleanup name helpers I think we need a better type, something like QualifiedName that includes Schema and Name, and probably a position in the ast. That's for another PR. --- crates/squawk_ide/src/collect.rs | 17 +- crates/squawk_ide/src/completion.rs | 7 +- crates/squawk_ide/src/file.rs | 2 + crates/squawk_ide/src/lib.rs | 2 + crates/squawk_ide/src/name.rs | 168 +++++++++++++ crates/squawk_ide/src/resolve.rs | 351 ++++++---------------------- crates/squawk_ide/src/symbols.rs | 87 ++----- 7 files changed, 272 insertions(+), 362 deletions(-) create mode 100644 crates/squawk_ide/src/name.rs diff --git a/crates/squawk_ide/src/collect.rs b/crates/squawk_ide/src/collect.rs index b328667e..d4213483 100644 --- a/crates/squawk_ide/src/collect.rs +++ b/crates/squawk_ide/src/collect.rs @@ -3,11 +3,12 @@ use crate::builtins::builtins_file; use crate::column_name::ColumnName; use crate::db::{File, bind, parse}; use crate::infer::{Type, infer_type_from_expr, infer_type_from_ty}; +use crate::name::{self, Name}; use crate::resolve::{ - ResolvedTableName, extract_table_schema_from_path, find_from_item_in_from_clause, - qualified_star_table_name, resolve_cte_table, resolve_table_name, table_ptr_from_from_item, + ResolvedTableName, find_from_item_in_from_clause, qualified_star_table_name, resolve_cte_table, + resolve_table_name, table_ptr_from_from_item, }; -use crate::symbols::{Name, SymbolKind}; +use crate::symbols::SymbolKind; use salsa::Database as Db; use squawk_syntax::{ SyntaxNodePtr, @@ -38,7 +39,7 @@ fn columns_from_create_table_impl( if let Some(inherits) = create_table.inherits() { for path in inherits.paths() { - if let Some((table_name, schema)) = extract_table_schema_from_path(&path) { + if let Some((schema, table_name)) = name::schema_and_name_path(&path) { let position = path.syntax().text_range().start(); if let Some(ResolvedTableName::Table(parent_table)) = resolve_table_name(db, file, &table_name, &schema, position) @@ -60,7 +61,7 @@ fn columns_from_create_table_impl( } ast::TableArg::LikeClause(like_clause) => { if let Some(path) = like_clause.path() - && let Some((table_name, schema)) = extract_table_schema_from_path(&path) + && let Some((schema, table_name)) = name::schema_and_name_path(&path) { let position = path.syntax().text_range().start(); if let Some(ResolvedTableName::Table(source_table)) = @@ -106,7 +107,7 @@ fn table_columns_impl( if let Some(inherits) = create_table.inherits() { for path in inherits.paths() { - if let Some((table_name, schema)) = extract_table_schema_from_path(&path) { + if let Some((schema, table_name)) = name::schema_and_name_path(&path) { let position = path.syntax().text_range().start(); if let Some(ResolvedTableName::Table(parent_table)) = resolve_table_name(db, file, &table_name, &schema, position) @@ -129,7 +130,7 @@ fn table_columns_impl( } ast::TableArg::LikeClause(like_clause) => { if let Some(path) = like_clause.path() - && let Some((table_name, schema)) = extract_table_schema_from_path(&path) + && let Some((schema, table_name)) = name::schema_and_name_path(&path) { let position = path.syntax().text_range().start(); if let Some(ResolvedTableName::Table(source_table)) = @@ -513,7 +514,7 @@ fn select_variant_columns_with_types( let Some(path) = table.relation_name().and_then(|r| r.path()) else { return vec![]; }; - let Some((table_name, schema)) = extract_table_schema_from_path(&path) else { + let Some((schema, table_name)) = name::schema_and_name_path(&path) else { return vec![]; }; diff --git a/crates/squawk_ide/src/completion.rs b/crates/squawk_ide/src/completion.rs index ff67f37c..ca8a307d 100644 --- a/crates/squawk_ide/src/completion.rs +++ b/crates/squawk_ide/src/completion.rs @@ -7,8 +7,9 @@ use crate::ast_nav; use crate::binder; use crate::collect; use crate::db::{File, bind, parse}; +use crate::name::{self, Name, Schema}; use crate::resolve; -use crate::symbols::{Name, Schema, SymbolKind}; +use crate::symbols::SymbolKind; use crate::tokens::is_string_or_comment; const COMPLETION_MARKER: &str = "squawkCompletionMarker"; @@ -625,7 +626,7 @@ fn delete_expr_completions( return completions; }; - let Some(delete_table_name) = resolve::extract_table_name(&path) else { + let Some(delete_table_name) = name::table_name(&path) else { return completions; }; @@ -667,7 +668,7 @@ fn delete_expr_completions( }); } - let schema = resolve::extract_schema_name(&path); + let schema = name::schema_name(&path); if let Some(table_ptr) = binder.lookup_with(&delete_table_name, SymbolKind::Table, position, &schema) && let Some(create_table) = table_ptr diff --git a/crates/squawk_ide/src/file.rs b/crates/squawk_ide/src/file.rs index 9e8740b6..72bc599f 100644 --- a/crates/squawk_ide/src/file.rs +++ b/crates/squawk_ide/src/file.rs @@ -33,9 +33,11 @@ use crate::db::File; /// * `InFile` -- syntax node in a file /// * `InFile` -- ast node in a file /// * `InFile` -- offset in a file +#[allow(dead_code)] #[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] pub struct InFileWrapper { pub file_id: FileKind, pub value: T, } +#[expect(unused)] pub type InFile = InFileWrapper; diff --git a/crates/squawk_ide/src/lib.rs b/crates/squawk_ide/src/lib.rs index 193866c6..86a9680e 100644 --- a/crates/squawk_ide/src/lib.rs +++ b/crates/squawk_ide/src/lib.rs @@ -10,6 +10,7 @@ pub mod completion; pub mod db; pub mod document_symbols; pub mod expand_selection; +mod file; pub mod find_references; pub mod folding_ranges; mod generated; @@ -18,6 +19,7 @@ pub mod hover; mod infer; pub mod inlay_hints; pub mod location; +mod name; mod offsets; mod quote; mod resolve; diff --git a/crates/squawk_ide/src/name.rs b/crates/squawk_ide/src/name.rs new file mode 100644 index 00000000..a75b01bc --- /dev/null +++ b/crates/squawk_ide/src/name.rs @@ -0,0 +1,168 @@ +use smol_str::SmolStr; +use squawk_syntax::ast::{self, AstNode}; +use std::fmt; + +use crate::quote::normalize_identifier; + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub(crate) struct Name(pub(crate) SmolStr); + +#[derive(Clone, Debug, PartialEq, Eq)] +pub(crate) struct Schema(pub(crate) Name); + +impl Schema { + pub(crate) fn new(name: impl Into) -> Self { + Schema(Name::from_string(name)) + } +} + +impl fmt::Display for Schema { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0.0) + } +} + +impl Name { + pub(crate) fn from_string(text: impl Into) -> Self { + let text = text.into(); + let normalized = normalize_identifier(&text); + Name(normalized.into()) + } + pub(crate) fn from_node(node: &impl ast::NameLike) -> Self { + let text = node.syntax().text().to_string(); + let normalized = normalize_identifier(&text); + Name(normalized.into()) + } +} + +impl fmt::Display for Name { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0) + } +} + +pub(crate) fn schema_and_name_path(path: &ast::Path) -> Option<(Option, Name)> { + Some((schema_name(path), table_name(path)?)) +} + +pub(crate) fn schema_and_table_name(name_ref: &ast::NameRef) -> Option<(Option, Name)> { + if let Some(path) = name_ref.syntax().ancestors().find_map(ast::Path::cast) { + return schema_and_name_path(&path); + } + + Some((None, Name::from_node(name_ref))) +} + +pub(crate) fn schema_and_name(name_ref: &ast::NameRef) -> (Option, Name) { + 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) + && let Some(base) = field_expr.base() + && let Some(schema_name_ref) = ast::NameRef::cast(base.syntax().clone()) + { + Some(Schema(Name::from_node(&schema_name_ref))) + } else { + None + }; + + (schema, table_name) +} + +pub(crate) fn schema_and_func_name(call_expr: &ast::CallExpr) -> Option<(Option, Name)> { + match call_expr.expr()? { + ast::Expr::NameRef(name_ref) => Some((None, Name::from_node(&name_ref))), + ast::Expr::FieldExpr(field_expr) => { + let function_name = Name::from_node(&field_expr.field()?); + let ast::Expr::NameRef(schema_name_ref) = field_expr.base()? else { + return None; + }; + let schema = Schema(Name::from_node(&schema_name_ref)); + Some((Some(schema), function_name)) + } + _ => None, + } +} + +pub(crate) fn table_name(path: &ast::Path) -> Option { + let segment = path.segment()?; + let name_ref = segment.name_ref()?; + Some(Name::from_node(&name_ref)) +} + +pub(crate) fn schema_name(path: &ast::Path) -> Option { + path.qualifier() + .and_then(|q| q.segment()) + .and_then(|s| s.name_ref()) + .map(|name_ref| Schema(Name::from_node(&name_ref))) +} + +pub(crate) fn schema_and_type_name(ty: &ast::Type) -> Option<(Option, Name)> { + match ty { + ast::Type::ArrayType(array_type) => { + let inner = array_type.ty()?; + schema_and_type_name(&inner) + } + ast::Type::BitType(bit_type) => { + let name = if bit_type.varying_token().is_some() { + "varbit" + } else { + "bit" + }; + Some((None, Name::from_string(name))) + } + ast::Type::IntervalType(_) => Some((None, Name::from_string("interval"))), + ast::Type::PathType(path_type) => { + let path = path_type.path()?; + schema_and_name_path(&path) + } + ast::Type::ExprType(expr_type) => { + let expr = expr_type.expr()?; + if let ast::Expr::FieldExpr(field_expr) = expr + && let Some(field) = field_expr.field() + && let Some(ast::Expr::NameRef(schema_name_ref)) = field_expr.base() + { + let type_name = Name::from_node(&field); + let schema = Some(Schema(Name::from_node(&schema_name_ref))); + Some((schema, type_name)) + } else { + None + } + } + ast::Type::CharType(char_type) => { + let name = if char_type.varchar_token().is_some() || char_type.varying_token().is_some() + { + "varchar" + } else { + "bpchar" + }; + Some((None, Name::from_string(name))) + } + ast::Type::DoubleType(_) => Some((None, Name::from_string("float8"))), + ast::Type::TimeType(time_type) => { + let mut name = if time_type.timestamp_token().is_some() { + "timestamp".to_string() + } else { + "time".to_string() + }; + if let Some(ast::Timezone::WithTimezone(_)) = time_type.timezone() { + name.push_str("tz"); + } + Some((None, Name::from_string(name))) + } + ast::Type::PercentType(_) => None, + } +} + +#[cfg(test)] +mod test { + use super::*; + #[test] + fn name_case_insensitive_compare() { + assert_eq!(Name::from_string("foo"), Name::from_string("FOO")); + } + + #[test] + fn name_quote_comparing() { + assert_eq!(Name::from_string(r#""foo""#), Name::from_string("foo")); + } +} diff --git a/crates/squawk_ide/src/resolve.rs b/crates/squawk_ide/src/resolve.rs index d990faa0..a87dc314 100644 --- a/crates/squawk_ide/src/resolve.rs +++ b/crates/squawk_ide/src/resolve.rs @@ -9,8 +9,8 @@ use squawk_syntax::{ use crate::column_name::ColumnName; use crate::db::File; use crate::location::{Location, LocationKind}; -pub(crate) use crate::symbols::Schema; -use crate::symbols::{Name, SymbolKind}; +use crate::name::{self, Name, Schema}; +use crate::symbols::SymbolKind; use crate::{ classify::{NameRefClass, classify_name_ref}, db::{bind, parse}, @@ -37,7 +37,7 @@ pub(crate) fn resolve_name_ref( match context { NameRefClass::Table => { - let (table_name, schema) = extract_table_schema_from_name_ref(name_ref)?; + let (schema, table_name) = name::schema_and_table_name(name_ref)?; let position = name_ref.syntax().text_range().start(); if schema.is_none() @@ -58,7 +58,15 @@ pub(crate) fn resolve_name_ref( )]) } NameRefClass::NamedArgParameter => { - let (function_name, schema) = find_func_call_from_named_arg(name_ref)?; + let (schema, function_name) = name_ref.syntax().ancestors().find_map(|a| { + if let Some(call_expr) = ast::CallExpr::cast(a.clone()) { + name::schema_and_func_name(&call_expr) + } else if let Some(call) = ast::Call::cast(a) { + name::schema_and_name_path(&call.path()?) + } else { + None + } + })?; let param_name = Name::from_node(name_ref); let position = name_ref.syntax().text_range().start(); @@ -109,17 +117,7 @@ pub(crate) fn resolve_name_ref( )]) } NameRefClass::FromTable => { - 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) - && let Some(base) = field_expr.base() - && let Some(schema_name_ref) = ast::NameRef::cast(base.syntax().clone()) - { - Some(Schema(Name::from_node(&schema_name_ref))) - } else { - None - }; - + let (schema, table_name) = name::schema_and_name(name_ref); if schema.is_none() && let Some(cte_ptr) = resolve_cte_table(name_ref, &table_name) { @@ -161,24 +159,7 @@ pub(crate) fn resolve_name_ref( )]) } NameRefClass::Type => { - let (type_name, schema) = if let Some(parent) = name_ref.syntax().parent() - && let Some(field_expr) = ast::FieldExpr::cast(parent) - && field_expr - .field() - .is_some_and(|field| field.syntax() == name_ref.syntax()) - { - let type_name = Name::from_node(name_ref); - let schema = if let Some(base) = field_expr.base() - && let ast::Expr::NameRef(schema_name_ref) = base - { - Some(Schema(Name::from_node(&schema_name_ref))) - } else { - None - }; - (type_name, schema) - } else { - extract_table_schema_from_name_ref(name_ref)? - }; + let (schema, type_name) = name::schema_and_table_name(name_ref)?; let type_name = resolve_float_precision(name_ref, type_name); let position = name_ref.syntax().text_range().start(); let ptr = resolve_type_name_ptr(db, file, &type_name, &schema, position)?; @@ -189,7 +170,7 @@ pub(crate) fn resolve_name_ref( )]) } NameRefClass::View => { - let (view_name, schema) = extract_table_schema_from_name_ref(name_ref)?; + let (schema, view_name) = name::schema_and_table_name(name_ref)?; let position = name_ref.syntax().text_range().start(); let ptr = resolve_view_name_ptr(db, file, &view_name, &schema, position)?; Some(smallvec![Location::new( @@ -216,7 +197,7 @@ pub(crate) fn resolve_name_ref( None } NameRefClass::Sequence => { - let (sequence_name, schema) = extract_table_schema_from_name_ref(name_ref)?; + let (schema, sequence_name) = name::schema_and_table_name(name_ref)?; let position = name_ref.syntax().text_range().start(); let binder = bind(db, file); let ptr = @@ -233,14 +214,14 @@ pub(crate) fn resolve_name_ref( .ancestors() .find_map(ast::DropTrigger::cast)?; let path = drop_trigger.path()?; - let (trigger_name, mut schema) = extract_table_schema_from_path(&path)?; + let (mut schema, trigger_name) = name::schema_and_name_path(&path)?; let on_table_path = drop_trigger .on_table() .and_then(|on_table| on_table.path())?; if schema.is_none() { - schema = extract_schema_name(&on_table_path); + schema = name::schema_name(&on_table_path); } - let table_name = extract_table_name(&on_table_path)?; + let table_name = name::table_name(&on_table_path)?; let position = name_ref.syntax().text_range().start(); let binder = bind(db, file); let ptr = binder.lookup_with_table( @@ -266,7 +247,7 @@ pub(crate) fn resolve_name_ref( })?; let policy_name = Name::from_node(&policy_name?); let on_table_path = on_table.and_then(|on_table| on_table.path())?; - let (table_name, schema) = extract_table_schema_from_path(&on_table_path)?; + let (schema, table_name) = name::schema_and_name_path(&on_table_path)?; let position = name_ref.syntax().text_range().start(); let binder = bind(db, file); let ptr = binder.lookup_with_table( @@ -294,7 +275,7 @@ pub(crate) fn resolve_name_ref( } NameRefClass::PropertyGraph => { let path = name_ref.syntax().ancestors().find_map(ast::Path::cast)?; - let (property_graph_name, schema) = extract_table_schema_from_path(&path)?; + let (schema, property_graph_name) = name::schema_and_name_path(&path)?; let position = name_ref.syntax().text_range().start(); let binder = bind(db, file); let ptr = binder.lookup_with( @@ -367,7 +348,7 @@ pub(crate) fn resolve_name_ref( } NameRefClass::ForeignKeyTable => { let path = name_ref.syntax().ancestors().find_map(ast::Path::cast)?; - let (table_name, schema) = extract_table_schema_from_path(&path)?; + let (schema, table_name) = name::schema_and_name_path(&path)?; let position = name_ref.syntax().text_range().start(); let ptr = resolve_table_name_ptr(db, file, &table_name, &schema, position)?; Some(smallvec![Location::new( @@ -432,7 +413,7 @@ pub(crate) fn resolve_name_ref( None } })?; - let (table_name, schema) = extract_table_schema_from_path(&on_table_path)?; + let (schema, table_name) = name::schema_and_name_path(&on_table_path)?; let position = name_ref.syntax().text_range().start(); let ptr = resolve_table_name_ptr(db, file, &table_name, &schema, position)?; Some(smallvec![Location::new( @@ -447,7 +428,7 @@ pub(crate) fn resolve_name_ref( .ancestors() .find_map(ast::LikeClause::cast)?; let path = like_clause.path()?; - let (table_name, schema) = extract_table_schema_from_path(&path)?; + let (schema, table_name) = name::schema_and_name_path(&path)?; let position = name_ref.syntax().text_range().start(); let ptr = resolve_table_name_ptr(db, file, &table_name, &schema, position)?; Some(smallvec![Location::new( @@ -462,7 +443,7 @@ pub(crate) fn resolve_name_ref( .ancestors() .find_map(ast::FunctionSig::cast)?; let path = function_sig.path()?; - let (function_name, schema) = extract_table_schema_from_path(&path)?; + let (schema, function_name) = name::schema_and_name_path(&path)?; let params = extract_param_signature(&function_sig); let position = name_ref.syntax().text_range().start(); resolve_function( @@ -480,7 +461,7 @@ pub(crate) fn resolve_name_ref( .ancestors() .find_map(ast::Aggregate::cast)?; let path = aggregate.path()?; - let (aggregate_name, schema) = extract_table_schema_from_path(&path)?; + let (schema, aggregate_name) = name::schema_and_name_path(&path)?; let params = extract_param_signature(&aggregate); let position = name_ref.syntax().text_range().start(); resolve_aggregate( @@ -498,7 +479,7 @@ pub(crate) fn resolve_name_ref( .ancestors() .find_map(ast::FunctionSig::cast)?; let path = function_sig.path()?; - let (procedure_name, schema) = extract_table_schema_from_path(&path)?; + let (schema, procedure_name) = name::schema_and_name_path(&path)?; let params = extract_param_signature(&function_sig); let position = name_ref.syntax().text_range().start(); resolve_procedure( @@ -516,7 +497,7 @@ pub(crate) fn resolve_name_ref( .ancestors() .find_map(ast::FunctionSig::cast)?; let path = function_sig.path()?; - let (routine_name, schema) = extract_table_schema_from_path(&path)?; + let (schema, routine_name) = name::schema_and_name_path(&path)?; let params = extract_param_signature(&function_sig); let position = name_ref.syntax().text_range().start(); @@ -554,7 +535,7 @@ pub(crate) fn resolve_name_ref( NameRefClass::CallProcedure => { let call = name_ref.syntax().ancestors().find_map(ast::Call::cast)?; let path = call.path()?; - let (procedure_name, schema) = extract_table_schema_from_path(&path)?; + let (schema, procedure_name) = name::schema_and_name_path(&path)?; let position = name_ref.syntax().text_range().start(); resolve_procedure(db, file, &procedure_name, &schema, None, position) } @@ -569,30 +550,12 @@ pub(crate) fn resolve_name_ref( )]) } NameRefClass::FunctionCall => { - let schema = if let Some(parent_node) = name_ref.syntax().parent() - && let Some(field_expr) = ast::FieldExpr::cast(parent_node) - { - let base = field_expr.base()?; - let schema_name_ref = ast::NameRef::cast(base.syntax().clone())?; - Some(Schema(Name::from_node(&schema_name_ref))) - } else { - None - }; - let function_name = Name::from_node(name_ref); + let (schema, function_name) = name::schema_and_name(name_ref); let position = name_ref.syntax().text_range().start(); resolve_function(db, file, &function_name, &schema, None, position) } NameRefClass::ProcedureCall => { - let schema = if let Some(parent_node) = name_ref.syntax().parent() - && let Some(field_expr) = ast::FieldExpr::cast(parent_node) - { - let base = field_expr.base()?; - let schema_name_ref = ast::NameRef::cast(base.syntax().clone())?; - Some(Schema(Name::from_node(&schema_name_ref))) - } else { - None - }; - let procedure_name = Name::from_node(name_ref); + let (schema, procedure_name) = name::schema_and_name(name_ref); let position = name_ref.syntax().text_range().start(); resolve_procedure(db, file, &procedure_name, &schema, None, position) @@ -603,21 +566,12 @@ pub(crate) fn resolve_name_ref( .ancestors() .find_map(ast::PathType::cast)?; let path = path_type.path()?; - let (function_name, schema) = extract_table_schema_from_path(&path)?; + let (schema, function_name) = name::schema_and_name_path(&path)?; let position = name_ref.syntax().text_range().start(); resolve_function(db, file, &function_name, &schema, None, position) } NameRefClass::SelectFunctionCall => { - let schema = if let Some(parent_node) = name_ref.syntax().parent() - && let Some(field_expr) = ast::FieldExpr::cast(parent_node) - { - let base = field_expr.base()?; - let schema_name_ref = ast::NameRef::cast(base.syntax().clone())?; - Some(Schema(Name::from_node(&schema_name_ref))) - } else { - None - }; - let function_name = Name::from_node(name_ref); + let (schema, function_name) = name::schema_and_name(name_ref); let position = name_ref.syntax().text_range().start(); // functions take precedence @@ -791,67 +745,10 @@ pub(crate) fn resolve_type_ptr_from_type( ty: &ast::Type, position: TextSize, ) -> Option { - let (type_name, schema) = type_name_and_schema_from_type(ty)?; + let (schema, type_name) = name::schema_and_type_name(ty)?; resolve_type_name_ptr(db, file, &type_name, &schema, position) } -fn type_name_and_schema_from_type(ty: &ast::Type) -> Option<(Name, Option)> { - match ty { - ast::Type::ArrayType(array_type) => { - let inner = array_type.ty()?; - type_name_and_schema_from_type(&inner) - } - ast::Type::BitType(bit_type) => { - let name = if bit_type.varying_token().is_some() { - "varbit" - } else { - "bit" - }; - Some((Name::from_string(name), None)) - } - ast::Type::IntervalType(_) => Some((Name::from_string("interval"), None)), - ast::Type::PathType(path_type) => { - let path = path_type.path()?; - extract_table_schema_from_path(&path) - } - ast::Type::ExprType(expr_type) => { - let expr = expr_type.expr()?; - if let ast::Expr::FieldExpr(field_expr) = expr - && let Some(field) = field_expr.field() - && let Some(ast::Expr::NameRef(schema_name_ref)) = field_expr.base() - { - let type_name = Name::from_node(&field); - let schema = Some(Schema(Name::from_node(&schema_name_ref))); - Some((type_name, schema)) - } else { - None - } - } - ast::Type::CharType(char_type) => { - let name = if char_type.varchar_token().is_some() || char_type.varying_token().is_some() - { - "varchar" - } else { - "bpchar" - }; - Some((Name::from_string(name), None)) - } - ast::Type::DoubleType(_) => Some((Name::from_string("float8"), None)), - ast::Type::TimeType(time_type) => { - let mut name = if time_type.timestamp_token().is_some() { - "timestamp".to_string() - } else { - "time".to_string() - }; - if let Some(ast::Timezone::WithTimezone(_)) = time_type.timezone() { - name.push_str("tz"); - } - Some((Name::from_string(name), None)) - } - ast::Type::PercentType(_) => None, - } -} - fn fallback_type_alias(type_name: &Name) -> Option { match type_name.0.as_str() { "bigint" | "bigserial" | "serial8" => Some(Name::from_string("int8")), @@ -1058,7 +955,7 @@ fn resolve_column_for_path( path: &ast::Path, column_name: Name, ) -> Option> { - let (table_name, schema) = extract_table_schema_from_path(path)?; + let (schema, table_name) = name::schema_and_name_path(path)?; let position = path.syntax().text_range().start(); resolve_column_for_table(db, file, &table_name, &schema, &column_name, position) @@ -1112,8 +1009,7 @@ fn resolve_select_qualified_column_table_name_ptr( } if let Some(call_expr) = from_item.call_expr() - && let Some((function_name, function_schema)) = - function_name_and_schema_from_call_expr(&call_expr) + && let Some((function_schema, function_name)) = name::schema_and_func_name(&call_expr) && function_name == table_name && function_schema == explicit_schema { @@ -1131,7 +1027,7 @@ fn resolve_select_qualified_column_table_name_ptr( )]); } - let (table_name, schema) = if let Some(name_ref_node) = from_item.name_ref() { + let (schema, table_name) = if let Some(name_ref_node) = from_item.name_ref() { if let Some(cte_ptr) = resolve_cte_table(table_name_ref, &table_name) { return Some(smallvec![Location::new( file, @@ -1143,7 +1039,7 @@ fn resolve_select_qualified_column_table_name_ptr( // `from foo` let from_table_name = Name::from_node(&name_ref_node); if from_table_name == table_name { - (from_table_name, None) + (None, from_table_name) } else { return None; } @@ -1158,7 +1054,7 @@ fn resolve_select_qualified_column_table_name_ptr( return None; }; let schema = Schema(Name::from_node(&schema_name_ref)); - (from_table_name, Some(schema)) + (Some(schema), from_table_name) }; let position = table_name_ref.syntax().text_range().start(); @@ -1223,18 +1119,6 @@ fn match_table_in_returning_clause( None } -pub(crate) fn extract_table_schema_from_path(path: &ast::Path) -> Option<(Name, Option)> { - Some((extract_table_name(path)?, extract_schema_name(path))) -} - -fn extract_table_schema_from_name_ref(name_ref: &ast::NameRef) -> Option<(Name, Option)> { - if let Some(path) = name_ref.syntax().ancestors().find_map(ast::Path::cast) { - return extract_table_schema_from_path(&path); - } - - Some((Name::from_node(name_ref), None)) -} - fn resolve_select_qualified_column_ptr( db: &dyn Db, file: File, @@ -1247,12 +1131,12 @@ fn resolve_select_qualified_column_ptr( .parent() .and_then(ast::FieldExpr::cast)?; - let (column_table_name, explicit_schema) = + let (explicit_schema, column_table_name) = // if we're at `base` in `base.field` if let Some(base) = field_expr.base() && let ast::Expr::NameRef(table_name_ref) = base { - (Name::from_node(&table_name_ref), None) + (None, Name::from_node(&table_name_ref)) // we have `foo.bar.buzz` } else if let Some(base) = field_expr.base() && let ast::Expr::FieldExpr(inner_field_expr) = base @@ -1260,18 +1144,15 @@ fn resolve_select_qualified_column_ptr( && let Some(inner_base) = inner_field_expr.base() && let ast::Expr::NameRef(schema_name_ref) = inner_base { - ( - Name::from_node(&table_field), - Some(Schema(Name::from_node(&schema_name_ref))), - ) + (Some(Schema(Name::from_node(&schema_name_ref))), Name::from_node(&table_field)) } else { return None; }; let position = column_name_ref.syntax().text_range().start(); - let (mut table_name, schema) = if let Some(schema) = explicit_schema { - (column_table_name, Some(schema)) + let (schema, mut table_name) = if let Some(schema) = explicit_schema { + (Some(schema), column_table_name) } else { match ast_nav::node_parent_query(column_name_ref.syntax())? { ast_nav::ParentQuery::Select(_select) => { @@ -1354,7 +1235,7 @@ fn resolve_select_qualified_column_ptr( // `from t as u` if let Some(name_ref_node) = from_item.name_ref() { - (Name::from_node(&name_ref_node), None) + (None, Name::from_node(&name_ref_node)) // `from foo.t as u` } else if let Some(from_field_expr) = from_item.field_expr() { let table_name = Name::from_node(&from_field_expr.field()?); @@ -1362,7 +1243,7 @@ fn resolve_select_qualified_column_ptr( return None; }; let schema = Schema(Name::from_node(&schema_name_ref)); - (table_name, Some(schema)) + (Some(schema), table_name) } else { return None; } @@ -1370,7 +1251,7 @@ fn resolve_select_qualified_column_ptr( // `from bar` let from_table_name = Name::from_node(&name_ref_node); if from_table_name == column_table_name { - (from_table_name, None) + (None, from_table_name) } else { return None; } @@ -1385,20 +1266,20 @@ fn resolve_select_qualified_column_ptr( return None; }; let schema = Schema(Name::from_node(&schema_name_ref)); - (from_table_name, Some(schema)) + (Some(schema), from_table_name) } } ast_nav::ParentQuery::Update(update) => { let path = update.relation_name()?.path()?; - extract_table_schema_from_path(&path)? + name::schema_and_name_path(&path)? } ast_nav::ParentQuery::Delete(delete) => { let path = delete.relation_name()?.path()?; - extract_table_schema_from_path(&path)? + name::schema_and_name_path(&path)? } ast_nav::ParentQuery::Insert(insert) => { let path = insert.path()?; - extract_table_schema_from_path(&path)? + name::schema_and_name_path(&path)? } ast_nav::ParentQuery::Merge(merge) => { let found_in_using = if let Some(using_on) = merge.using_on_clause() @@ -1408,13 +1289,13 @@ fn resolve_select_qualified_column_ptr( && let item_name = Name::from_node(&item_name_ref) && item_name == column_table_name { - Some((item_name, None)) + Some((None, item_name)) } else if let Some(alias) = from_item.alias() && let Some(alias_name) = alias.name() && let alias_name = Name::from_node(&alias_name) && alias_name == column_table_name { - Some((alias_name, None)) + Some((None, alias_name)) } else { None } @@ -1426,7 +1307,7 @@ fn resolve_select_qualified_column_ptr( result } else { let path = merge.relation_name()?.path()?; - extract_table_schema_from_path(&path)? + name::schema_and_name_path(&path)? } } } @@ -1760,8 +1641,7 @@ fn resolve_column_from_table_or_view_impl( && let Some(partition_of) = create_table_node.partition_of() && let Some(parent_path) = partition_of.path() { - let (parent_table_name, parent_schema) = - extract_table_schema_from_path(&parent_path)?; + let (parent_schema, parent_table_name) = name::schema_and_name_path(&parent_path)?; return resolve_column_from_table_or_view_impl( db, file, @@ -1966,7 +1846,7 @@ fn is_from_item_match(from_item: &ast::FromItem, qualifier: &Name) -> bool { } if let Some(call_expr) = from_item.call_expr() - && let Some((function_name, _schema)) = function_name_and_schema_from_call_expr(&call_expr) + && let Some((_schema, function_name)) = name::schema_and_func_name(&call_expr) && function_name == *qualifier { return true; @@ -2024,19 +1904,6 @@ fn find_from_item_for_select_qualified_name_ref( None } -pub(crate) fn extract_table_name(path: &ast::Path) -> Option { - let segment = path.segment()?; - let name_ref = segment.name_ref()?; - Some(Name::from_node(&name_ref)) -} - -pub(crate) fn extract_schema_name(path: &ast::Path) -> Option { - path.qualifier() - .and_then(|q| q.segment()) - .and_then(|s| s.name_ref()) - .map(|name_ref| Schema(Name::from_node(&name_ref))) -} - pub(crate) fn find_column_in_create_table( db: &dyn Db, file: File, @@ -2060,7 +1927,7 @@ fn find_column_in_create_table_impl( if let Some(inherits) = create_table.inherits() { for path in inherits.paths() { - let (table_name, schema) = extract_table_schema_from_path(&path)?; + let (schema, table_name) = name::schema_and_name_path(&path)?; let position = path.syntax().text_range().start(); if let Some(resolved) = resolve_table_name(db, file, &table_name, &schema, position) { @@ -2102,7 +1969,7 @@ fn find_column_in_create_table_impl( } ast::TableArg::LikeClause(like_clause) => { let path = like_clause.path()?; - let (table_name, schema) = extract_table_schema_from_path(&path)?; + let (schema, table_name) = name::schema_and_name_path(&path)?; let position = path.syntax().text_range().start(); if let Some(resolved) = resolve_table_name(db, file, &table_name, &schema, position) @@ -2239,7 +2106,7 @@ pub(crate) fn resolve_cte_table(name_ref: &ast::NameRef, cte_name: &Name) -> Opt } fn count_columns_for_path(db: &dyn Db, file: File, path: &ast::Path) -> Option { - let (table_name, schema) = extract_table_schema_from_path(path)?; + let (schema, table_name) = name::schema_and_name_path(path)?; let position = path.syntax().text_range().start(); count_columns_for_table_name(db, file, &table_name, &schema, position) @@ -2366,7 +2233,7 @@ fn resolve_cte_column( if let ast::WithQuery::Table(table) = query { let path = table.relation_name()?.path()?; - let (table_name, schema) = extract_table_schema_from_path(&path)?; + let (schema, table_name) = name::schema_and_name_path(&path)?; if schema.is_none() && let Some(nested_cte_column) = @@ -2561,7 +2428,7 @@ fn resolve_subquery_column_ptr( // TODO: this should just be a match stmt if let ast::SelectVariant::Table(table) = select_variant { let path = table.relation_name()?.path()?; - let (table_name, schema) = extract_table_schema_from_path(&path)?; + let (schema, table_name) = name::schema_and_name_path(&path)?; if schema.is_none() && let Some(cte_column_ptr) = @@ -2740,7 +2607,7 @@ fn resolve_table_or_view_or_cte_ptrs( position: TextSize, path: &ast::Path, ) -> Option> { - let (table_name, schema) = extract_table_schema_from_path(path)?; + let (schema, table_name) = name::schema_and_name_path(path)?; let mut results = vec![]; @@ -3110,7 +2977,7 @@ fn resolve_column_from_call_expr_return_table( min_index: usize, ) -> Option> { let position = name_ref.syntax().text_range().start(); - let (function_name, schema) = function_name_and_schema_from_call_expr(call_expr)?; + let (schema, function_name) = name::schema_and_func_name(call_expr)?; let function_locs = resolve_function(db, file, &function_name, &schema, None, position)?; let function_node = function_locs.first()?.to_node(db)?; let create_function = function_node @@ -3136,23 +3003,6 @@ fn resolve_column_from_call_expr_return_table( None } -fn function_name_and_schema_from_call_expr( - call_expr: &ast::CallExpr, -) -> Option<(Name, Option)> { - match call_expr.expr()? { - ast::Expr::NameRef(name_ref) => Some((Name::from_node(&name_ref), None)), - ast::Expr::FieldExpr(field_expr) => { - let function_name = Name::from_node(&field_expr.field()?); - let ast::Expr::NameRef(schema_name_ref) = field_expr.base()? else { - return None; - }; - let schema = Schema(Name::from_node(&schema_name_ref)); - Some((function_name, Some(schema))) - } - _ => None, - } -} - pub(crate) fn resolve_table_info( db: &dyn Db, file: File, @@ -3305,12 +3155,8 @@ fn resolve_composite_type_field_ptr( let column_locs = resolve_select_column_ptr(db, file, &base_name_ref)?; let column_node = column_locs.first()?.to_node(db)?; - let (type_name, schema) = - if let Some(type_info) = resolve_composite_type_from_column_node(&column_node) { - type_info - } else { - resolve_composite_type_from_cast_node(&column_node)? - }; + let (schema, type_name) = resolve_composite_type_from_column_node(&column_node) + .or_else(|| resolve_composite_type_from_cast_node(&column_node))?; let position = field_name_ref.syntax().text_range().start(); let type_name_ptr = resolve_type_name_ptr(db, file, &type_name, &schema, position)?; @@ -3337,45 +3183,21 @@ fn resolve_composite_type_field_ptr( fn resolve_composite_type_from_column_node( column_node: &SyntaxNode, -) -> Option<(Name, Option)> { +) -> Option<(Option, Name)> { let column = column_node.ancestors().find_map(ast::Column::cast)?; let ty = column.ty()?; - extract_type_name_and_schema(&ty) + name::schema_and_type_name(&ty) } fn resolve_composite_type_from_cast_node( column_node: &SyntaxNode, -) -> Option<(Name, Option)> { +) -> Option<(Option, Name)> { let target = column_node.ancestors().find_map(ast::Target::cast)?; let ast::Expr::CastExpr(cast_expr) = target.expr()? else { return None; }; let ty = cast_expr.ty()?; - extract_type_name_and_schema(&ty) -} - -fn extract_type_name_and_schema(ty: &ast::Type) -> Option<(Name, Option)> { - match ty { - ast::Type::PathType(path_type) => { - let path = path_type.path()?; - let (type_name, schema) = extract_table_schema_from_path(&path)?; - Some((type_name, schema)) - } - ast::Type::ExprType(expr_type) => { - let expr = expr_type.expr()?; - if let ast::Expr::FieldExpr(field_expr) = expr - && let Some(field) = field_expr.field() - && let Some(ast::Expr::NameRef(schema_name_ref)) = field_expr.base() - { - let type_name = Name::from_node(&field); - let schema = Some(Schema(Name::from_node(&schema_name_ref))); - Some((type_name, schema)) - } else { - None - } - } - _ => None, - } + name::schema_and_type_name(&ty) } fn resolve_merge_column_ptr( @@ -3412,7 +3234,7 @@ fn resolve_table_in_returning_clause( returning_clause: Option, ) -> Option> { let table_name = Name::from_node(table_name_ref); - let (stmt_table_name, schema) = extract_table_schema_from_path(path)?; + let (schema, stmt_table_name) = name::schema_and_name_path(path)?; let matched = match_table_in_returning_clause(&table_name, &stmt_table_name, alias, returning_clause)?; @@ -3515,39 +3337,6 @@ fn resolve_merge_table_name_ptr( ) } -fn find_func_call_from_named_arg(name_ref: &ast::NameRef) -> Option<(Name, Option)> { - for a in name_ref.syntax().ancestors() { - if let Some(call_expr) = ast::CallExpr::cast(a.clone()) { - return match call_expr.expr()? { - ast::Expr::NameRef(func_name_ref) => { - let func_name = Name::from_node(&func_name_ref); - Some((func_name, None)) - } - ast::Expr::FieldExpr(field_expr) => { - let func_name_ref = field_expr.field()?; - let func_name = Name::from_node(&func_name_ref); - - let schema = if let Some(base) = field_expr.base() - && let ast::Expr::NameRef(schema_name_ref) = base - { - Some(Schema(Name::from_node(&schema_name_ref))) - } else { - None - }; - - Some((func_name, schema)) - } - _ => None, - }; - } else if let Some(call) = ast::Call::cast(a) { - let path = call.path()?; - let (function_name, schema) = extract_table_schema_from_path(&path)?; - return Some((function_name, schema)); - } - } - None -} - fn find_param_in_func_def( db: &dyn Db, file: File, diff --git a/crates/squawk_ide/src/symbols.rs b/crates/squawk_ide/src/symbols.rs index a68b9370..e99f62db 100644 --- a/crates/squawk_ide/src/symbols.rs +++ b/crates/squawk_ide/src/symbols.rs @@ -1,70 +1,31 @@ use la_arena::Idx; -use smol_str::SmolStr; -use squawk_syntax::{SyntaxNodePtr, ast}; -use std::fmt; +use squawk_syntax::SyntaxNodePtr; -use crate::quote::normalize_identifier; - -#[derive(Clone, Debug, PartialEq, Eq, Hash)] -pub(crate) struct Name(pub(crate) SmolStr); - -#[derive(Clone, Debug, PartialEq, Eq)] -pub(crate) struct Schema(pub(crate) Name); - -impl Schema { - pub(crate) fn new(name: impl Into) -> Self { - Schema(Name::from_string(name)) - } -} - -impl fmt::Display for Schema { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.0.0) - } -} - -impl Name { - pub(crate) fn from_string(text: impl Into) -> Self { - let text = text.into(); - let normalized = normalize_identifier(&text); - Name(normalized.into()) - } - pub(crate) fn from_node(node: &impl ast::NameLike) -> Self { - let text = node.syntax().text().to_string(); - let normalized = normalize_identifier(&text); - Name(normalized.into()) - } -} - -impl fmt::Display for Name { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.0) - } -} +pub(crate) use crate::name::{Name, Schema}; #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] pub(crate) enum SymbolKind { - Table, - Index, - Function, Aggregate, - Procedure, - Schema, - Type, - View, - Sequence, - Cursor, - PreparedStatement, Channel, - Tablespace, + Cursor, Database, - Server, - Extension, - Trigger, EventTrigger, - Role, + Extension, + Function, + Index, Policy, + PreparedStatement, + Procedure, PropertyGraph, + Role, + Schema, + Sequence, + Server, + Table, + Tablespace, + Trigger, + Type, + View, } #[derive(Clone, Debug, PartialEq)] @@ -77,17 +38,3 @@ pub(crate) struct Symbol { } pub(crate) type SymbolId = Idx; - -#[cfg(test)] -mod test { - use super::*; - #[test] - fn name_case_insensitive_compare() { - assert_eq!(Name::from_string("foo"), Name::from_string("FOO")); - } - - #[test] - fn name_quote_comparing() { - assert_eq!(Name::from_string(r#""foo""#), Name::from_string("foo")); - } -}