diff --git a/src/api.rs b/src/api.rs index d6411d30..bf85fa0f 100644 --- a/src/api.rs +++ b/src/api.rs @@ -247,6 +247,10 @@ impl SyntaxNode { SyntaxNode::from(self.raw.clone_for_update()) } + pub fn is_mutable(&self) -> bool { + self.raw.is_mutable() + } + pub fn detach(&self) { self.raw.detach() } diff --git a/src/ast.rs b/src/ast.rs index 856a9f63..e142395b 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -55,6 +55,11 @@ pub trait AstNode { } /// A "pointer" to a [`SyntaxNode`], via location in the source code. +/// +/// ## Note +/// Since the location is source code dependent, this must not be used +/// with mutable syntax trees. Any changes made in such trees causes +/// the pointed node's source location to change, invalidating the pointer. #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub struct SyntaxNodePtr { kind: L::Kind, @@ -63,7 +68,10 @@ pub struct SyntaxNodePtr { impl SyntaxNodePtr { /// Returns a [`SyntaxNodePtr`] for the node. + /// + /// Panics if the provided node is mutable pub fn new(node: &SyntaxNode) -> Self { + assert!(!node.is_mutable(), "tree is mutable"); Self { kind: node.kind(), range: node.text_range() } } @@ -82,10 +90,13 @@ impl SyntaxNodePtr { /// Also returns `None` if `root` is not actually a root (i.e. it has a /// parent). /// + /// NOTE: If this function is called on a mutable tree, it will panic + /// /// The complexity is linear in the depth of the tree and logarithmic in /// tree width. As most trees are shallow, thinking about this as /// `O(log(N))` in the size of the tree is not too wrong! pub fn try_to_node(&self, root: &SyntaxNode) -> Option> { + assert!(!root.is_mutable(), "tree is mutable"); if root.parent().is_some() { return None; } @@ -113,13 +124,21 @@ impl SyntaxNodePtr { } /// Like [`SyntaxNodePtr`], but remembers the type of node. +/// +/// ## Note +/// As with [`SyntaxNodePtr`], this must not be used on mutable +/// syntax trees, since any mutation can cause the pointed node's +/// source location to change, invalidating the pointer pub struct AstPtr { raw: SyntaxNodePtr, } impl AstPtr { /// Returns an [`AstPtr`] for the node. + /// + /// Panics if the provided node is mutable pub fn new(node: &N) -> Self { + // The above mentioned panic is handled by SyntaxNodePtr Self { raw: SyntaxNodePtr::new(node.syntax()) } } @@ -129,8 +148,9 @@ impl AstPtr { } /// Given the root node containing the node `n` that `self` is a pointer to, - /// returns `n` if possible. See [`SyntaxNodePtr::try_to_node`]. + /// returns `n` if possible. Panics if `root` is mutable. See [`SyntaxNodePtr::try_to_node`]. pub fn try_to_node(&self, root: &SyntaxNode) -> Option { + // The above mentioned panic is handled by SyntaxNodePtr N::cast(self.raw.try_to_node(root)?) } @@ -215,3 +235,53 @@ pub mod support { parent.children_with_tokens().filter_map(|it| it.into_token()).find(|it| it.kind() == kind) } } + +#[cfg(test)] +mod tests { + use crate::{GreenNodeBuilder, Language, SyntaxKind, SyntaxNode}; + + use super::SyntaxNodePtr; + + #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] + struct TestLanguage; + impl Language for TestLanguage { + type Kind = SyntaxKind; + + fn kind_from_raw(raw: SyntaxKind) -> Self::Kind { + raw + } + + fn kind_to_raw(kind: Self::Kind) -> SyntaxKind { + kind + } + } + + fn build_immut_tree() -> SyntaxNode { + // Creates a single-node tree + let mut builder = GreenNodeBuilder::new(); + builder.start_node(SyntaxKind(0)); + builder.finish_node(); + + SyntaxNode::::new_root(builder.finish()) + } + + #[test] + #[should_panic = "tree is mutable"] + fn ensure_mut_panic_on_create() { + // Make a mutable version + let tree = build_immut_tree().clone_for_update(); + + SyntaxNodePtr::new(&tree); + } + + #[test] + #[should_panic = "tree is mutable"] + fn ensure_mut_panic_on_deref() { + let tree = build_immut_tree(); + let tree_mut = tree.clone_for_update(); + + // Create on immutable, convert on mutable + let syn_ptr = SyntaxNodePtr::new(&tree); + syn_ptr.to_node(&tree_mut); + } +} diff --git a/src/cursor.rs b/src/cursor.rs index 671be234..9060507e 100644 --- a/src/cursor.rs +++ b/src/cursor.rs @@ -544,6 +544,10 @@ impl SyntaxNode { SyntaxNode { ptr: NodeData::new(Some(parent), index, offset, green, mutable) } } + pub fn is_mutable(&self) -> bool { + self.data().mutable + } + pub fn clone_for_update(&self) -> SyntaxNode { assert!(!self.data().mutable); match self.parent() {