Skip to content

Prevent use of SyntaxNodePtr and AstPtr on mutable trees #151

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,10 @@ impl<L: Language> SyntaxNode<L> {
SyntaxNode::from(self.raw.clone_for_update())
}

pub fn is_mutable(&self) -> bool {
self.raw.is_mutable()
}

pub fn detach(&self) {
self.raw.detach()
}
Expand Down
72 changes: 71 additions & 1 deletion src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<L: Language> {
kind: L::Kind,
Expand All @@ -63,7 +68,10 @@ pub struct SyntaxNodePtr<L: Language> {

impl<L: Language> SyntaxNodePtr<L> {
/// Returns a [`SyntaxNodePtr`] for the node.
///
/// Panics if the provided node is mutable
pub fn new(node: &SyntaxNode<L>) -> Self {
assert!(!node.is_mutable(), "tree is mutable");
Self { kind: node.kind(), range: node.text_range() }
}

Expand All @@ -82,10 +90,13 @@ impl<L: Language> SyntaxNodePtr<L> {
/// 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<L>) -> Option<SyntaxNode<L>> {
assert!(!root.is_mutable(), "tree is mutable");
if root.parent().is_some() {
return None;
}
Expand Down Expand Up @@ -113,13 +124,21 @@ impl<L: Language> SyntaxNodePtr<L> {
}

/// 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<N: AstNode> {
raw: SyntaxNodePtr<N::Language>,
}

impl<N: AstNode> AstPtr<N> {
/// 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()) }
}

Expand All @@ -129,8 +148,9 @@ impl<N: AstNode> AstPtr<N> {
}

/// 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<N::Language>) -> Option<N> {
// The above mentioned panic is handled by SyntaxNodePtr
N::cast(self.raw.try_to_node(root)?)
}

Expand Down Expand Up @@ -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<TestLanguage> {
// Creates a single-node tree
let mut builder = GreenNodeBuilder::new();
builder.start_node(SyntaxKind(0));
builder.finish_node();

SyntaxNode::<TestLanguage>::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);
}
}
4 changes: 4 additions & 0 deletions src/cursor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
Loading