From 709ce93c13cf7c321587121478e98958882ab347 Mon Sep 17 00:00:00 2001 From: Steve Dignam Date: Thu, 23 Apr 2026 09:50:02 -0400 Subject: [PATCH] internal: add AstPtr & InFile from rust analyzer we'll use these in a bit --- Cargo.lock | 1 + Cargo.toml | 7 +- crates/squawk_ide/src/file.rs | 41 ++++++ crates/squawk_syntax/Cargo.toml | 1 + crates/squawk_syntax/src/lib.rs | 4 +- crates/squawk_syntax/src/ptr.rs | 178 ++++++++++++++++++++++++ crates/squawk_syntax/src/syntax_node.rs | 1 - 7 files changed, 230 insertions(+), 3 deletions(-) create mode 100644 crates/squawk_ide/src/file.rs create mode 100644 crates/squawk_syntax/src/ptr.rs diff --git a/Cargo.lock b/Cargo.lock index 6235cdc2..e1ceebc4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2530,6 +2530,7 @@ dependencies = [ "annotate-snippets", "camino", "dir-test", + "either", "insta", "rowan", "smol_str", diff --git a/Cargo.toml b/Cargo.toml index 8dbbeaf6..70c302ee 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,11 @@ glob = "0.3.1" insta = "1.39.0" jsonwebtoken = { version = "10.3.0", features = ["rust_crypto"] } log = "0.4.25" -reqwest = { version = "0.11.27", features = ["native-tls-vendored", "blocking", "json"] } +reqwest = { version = "0.11.27", features = [ + "native-tls-vendored", + "blocking", + "json", +] } la-arena = "0.3.1" serde = { version = "1.0.215", features = ["derive"] } serde_json = "1.0" @@ -52,6 +56,7 @@ console_error_panic_hook = "0.1.7" console_log = "1.0.0" annotate-snippets = "0.12.4" anyhow = "1.0.94" +either = "1.15.0" convert_case = "0.7.1" clap = { version = "4.5.8", features = ["derive", "env"] } ungrammar = "1.1.4" diff --git a/crates/squawk_ide/src/file.rs b/crates/squawk_ide/src/file.rs new file mode 100644 index 00000000..9e8740b6 --- /dev/null +++ b/crates/squawk_ide/src/file.rs @@ -0,0 +1,41 @@ +// via https://github.com/rust-lang/rust-analyzer/blob/2efc80078029894eec0699f62ec8d5c1a56af763/crates/hir-expand/src/files.rs#L21C21-L21C21 +// +// Permission is hereby granted, free of charge, to any +// person obtaining a copy of this software and associated +// documentation files (the "Software"), to deal in the +// Software without restriction, including without +// limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software +// is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice +// shall be included in all copies or substantial portions +// of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +// ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +// SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +use crate::db::File; + +/// `InFile` stores a value of `T` inside a particular file/syntax tree. +/// +/// Typical usages are: +/// +/// * `InFile` -- syntax node in a file +/// * `InFile` -- ast node in a file +/// * `InFile` -- offset in a file +#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] +pub struct InFileWrapper { + pub file_id: FileKind, + pub value: T, +} +pub type InFile = InFileWrapper; diff --git a/crates/squawk_syntax/Cargo.toml b/crates/squawk_syntax/Cargo.toml index 44c2c014..0e8605c6 100644 --- a/crates/squawk_syntax/Cargo.toml +++ b/crates/squawk_syntax/Cargo.toml @@ -17,6 +17,7 @@ doctest = false squawk-parser.workspace = true rowan.workspace = true smol_str.workspace = true +either.workspace = true [dev-dependencies] annotate-snippets.workspace = true diff --git a/crates/squawk_syntax/src/lib.rs b/crates/squawk_syntax/src/lib.rs index 22c6d83a..e52bb0f4 100644 --- a/crates/squawk_syntax/src/lib.rs +++ b/crates/squawk_syntax/src/lib.rs @@ -27,6 +27,7 @@ pub mod ast; pub mod identifier; mod parsing; +mod ptr; pub mod syntax_error; mod syntax_node; mod token_text; @@ -40,9 +41,10 @@ use std::{marker::PhantomData, sync::Arc}; pub use squawk_parser::SyntaxKind; use ast::AstNode; +pub use ptr::{AstPtr, SyntaxNodePtr}; use rowan::GreenNode; use syntax_error::SyntaxError; -pub use syntax_node::{SyntaxElement, SyntaxNode, SyntaxNodePtr, SyntaxToken}; +pub use syntax_node::{SyntaxElement, SyntaxNode, SyntaxToken}; pub use token_text::TokenText; /// `Parse` is the result of the parsing: a syntax tree and a collection of diff --git a/crates/squawk_syntax/src/ptr.rs b/crates/squawk_syntax/src/ptr.rs new file mode 100644 index 00000000..1ca0d56a --- /dev/null +++ b/crates/squawk_syntax/src/ptr.rs @@ -0,0 +1,178 @@ +// via https://github.com/rust-lang/rust-analyzer/blob/2efc80078029894eec0699f62ec8d5c1a56af763/crates/syntax/src/ptr.rs#L22C19-L22C19 +// +// Permission is hereby granted, free of charge, to any +// person obtaining a copy of this software and associated +// documentation files (the "Software"), to deal in the +// Software without restriction, including without +// limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software +// is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice +// shall be included in all copies or substantial portions +// of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +// ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +// SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +//! In squawk, syntax trees are transient objects. +//! +//! That means that we create trees when we need them, and tear them down to +//! save memory. In this architecture, hanging on to a particular syntax node +//! for a long time is ill-advisable, as that keeps the whole tree resident. +//! +//! Instead, we provide a [`SyntaxNodePtr`] type, which stores information about +//! *location* of a particular syntax node in a tree. Its a small type which can +//! be cheaply stored, and which can be resolved to a real [`SyntaxNode`] when +//! necessary. + +use std::{ + hash::{Hash, Hasher}, + marker::PhantomData, +}; + +use rowan::TextRange; + +use crate::{AstNode, SyntaxNode, syntax_node::Sql}; + +/// A "pointer" to a [`SyntaxNode`], via location in the source code. +pub type SyntaxNodePtr = rowan::ast::SyntaxNodePtr; + +/// Like [`SyntaxNodePtr`], but remembers the type of node. +pub struct AstPtr { + raw: SyntaxNodePtr, + _ty: PhantomData N>, +} + +impl std::fmt::Debug for AstPtr { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("AstPtr").field(&self.raw).finish() + } +} + +impl Copy for AstPtr {} +impl Clone for AstPtr { + fn clone(&self) -> AstPtr { + *self + } +} + +impl Eq for AstPtr {} + +impl PartialEq for AstPtr { + fn eq(&self, other: &AstPtr) -> bool { + self.raw == other.raw + } +} + +impl Hash for AstPtr { + fn hash(&self, state: &mut H) { + self.raw.hash(state); + } +} + +impl AstPtr { + pub fn new(node: &N) -> AstPtr { + AstPtr { + raw: SyntaxNodePtr::new(node.syntax()), + _ty: PhantomData, + } + } + + pub fn to_node(&self, root: &SyntaxNode) -> N { + let syntax_node = self.raw.to_node(root); + N::cast(syntax_node).unwrap() + } + + pub fn syntax_node_ptr(&self) -> SyntaxNodePtr { + self.raw + } + + pub fn text_range(&self) -> TextRange { + self.raw.text_range() + } + + pub fn cast(self) -> Option> { + if !U::can_cast(self.raw.kind()) { + return None; + } + Some(AstPtr { + raw: self.raw, + _ty: PhantomData, + }) + } + + pub fn kind(&self) -> squawk_parser::SyntaxKind { + self.raw.kind() + } + + pub fn upcast(self) -> AstPtr + where + N: Into, + { + AstPtr { + raw: self.raw, + _ty: PhantomData, + } + } + + /// Like `SyntaxNodePtr::cast` but the trait bounds work out. + pub fn try_from_raw(raw: SyntaxNodePtr) -> Option> { + N::can_cast(raw.kind()).then_some(AstPtr { + raw, + _ty: PhantomData, + }) + } + + pub fn wrap_left(self) -> AstPtr> + where + either::Either: AstNode, + { + AstPtr { + raw: self.raw, + _ty: PhantomData, + } + } + + pub fn wrap_right(self) -> AstPtr> + where + either::Either: AstNode, + { + AstPtr { + raw: self.raw, + _ty: PhantomData, + } + } +} + +impl From> for SyntaxNodePtr { + fn from(ptr: AstPtr) -> SyntaxNodePtr { + ptr.raw + } +} + +#[test] +fn test_local_syntax_ptr() { + use crate::{AstNode, ast}; + + let file = ast::SourceFile::parse("create table t(c int8);") + .ok() + .unwrap(); + let field = file + .syntax() + .descendants() + .find_map(ast::Column::cast) + .unwrap(); + let ptr = SyntaxNodePtr::new(field.syntax()); + let field_syntax = ptr.to_node(file.syntax()); + assert_eq!(field.syntax(), &field_syntax); +} diff --git a/crates/squawk_syntax/src/syntax_node.rs b/crates/squawk_syntax/src/syntax_node.rs index 0413eb7b..854f9aa5 100644 --- a/crates/squawk_syntax/src/syntax_node.rs +++ b/crates/squawk_syntax/src/syntax_node.rs @@ -44,7 +44,6 @@ impl Language for Sql { pub type SyntaxNode = rowan::SyntaxNode; pub type SyntaxToken = rowan::SyntaxToken; -pub type SyntaxNodePtr = rowan::ast::SyntaxNodePtr; pub type SyntaxElement = rowan::SyntaxElement; pub type SyntaxNodeChildren = rowan::SyntaxNodeChildren; // pub type SyntaxElementChildren = rowan::SyntaxElementChildren;