From 3dbfe22beddcaf3889fcf1208d7697b9850d4e64 Mon Sep 17 00:00:00 2001 From: Stu Date: Wed, 27 Jan 2021 11:44:46 +0100 Subject: [PATCH] Serializable trees (#11) Clean up #4. Now everything is moved to the new design. --- Cargo.lock | 91 +++++++++++++- Cargo.toml | 15 ++- src/serde_impls.rs | 270 +++++++++++++++++++++++++++++++++++------ src/syntax.rs | 42 +++++++ tests/basic.rs | 54 ++------- tests/common.rs | 41 ++++++- tests/serde.rs | 291 +++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 711 insertions(+), 93 deletions(-) create mode 100644 tests/serde.rs diff --git a/Cargo.lock b/Cargo.lock index c8c8bcc..054d460 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -36,6 +36,8 @@ dependencies = [ "m_lexer", "parking_lot", "serde", + "serde_json", + "serde_test", "servo_arc", "text-size", ] @@ -67,6 +69,12 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "itoa" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" + [[package]] name = "lasso" version = "0.4.1" @@ -143,6 +151,24 @@ dependencies = [ "winapi", ] +[[package]] +name = "proc-macro2" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "991431c3519a3f36861882da93630ce66b52918dcf1b8e2fd66b397fc96f28df" +dependencies = [ + "proc-macro2", +] + [[package]] name = "redox_syscall" version = "0.1.57" @@ -167,6 +193,12 @@ version = "0.6.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5eb417147ba9860a96cfe72a0b93bf88fee1744b5636ec99ab20c1aa9376581" +[[package]] +name = "ryu" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" + [[package]] name = "scopeguard" version = "1.1.0" @@ -175,15 +207,50 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "serde" -version = "1.0.119" +version = "1.0.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bdd36f49e35b61d49efd8aa7fc068fd295961fd2286d0b2ee9a4c7a14e99cc3" +checksum = "166b2349061381baf54a58e4b13c89369feb0ef2eaa57198899e2312aac30aab" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.120" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ca2a8cb5805ce9e3b95435e3765b7b553cecc762d938d409434338386cb5775" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fceb2595057b6891a4ee808f70054bd2d12f0e97f1cbb78689b59f676df325a" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_test" +version = "1.0.120" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dd7d96489b14fa2f4a89be299ac117c8023d1ead9aaee963a2dde72dad4d14b" +dependencies = [ + "serde", +] [[package]] name = "servo_arc" version = "0.1.1" dependencies = [ "nodrop", + "serde", "stable_deref_trait", ] @@ -199,14 +266,22 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +[[package]] +name = "syn" +version = "1.0.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc60a3d73ea6594cd712d830cc1f0390fd71542d8c8cd24e70cc54cdfd5e05d5" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + [[package]] name = "text-size" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "288cb548dbe72b652243ea797201f3d481a0609a967980fcc5b2315ea811560a" -dependencies = [ - "serde", -] [[package]] name = "thread_local" @@ -217,6 +292,12 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "unicode-xid" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" + [[package]] name = "winapi" version = "0.3.9" diff --git a/Cargo.toml b/Cargo.toml index 4acd706..77a23cc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,15 +8,26 @@ license = "MIT OR Apache-2.0" repository = "https://github.com/domenicquirl/cstree" [dependencies] -serde = { version = "1.0.89", optional = true, default-features = false } lasso = "0.4.1" text-size = "1.0.0" fxhash= "0.2.1" servo_arc = { path = "vendor/servo_arc" } parking_lot= "0.11.1" +[dependencies.serde] +version = "1.0" +optional = true +default-features = false +features = ["derive"] + [dev-dependencies] m_lexer = "0.0.4" +serde_json = "1.0.61" +serde_test = "1.0.119" [features] -serde1 = ["serde", "text-size/serde"] +default = [] +serde1 = ["serde", "servo_arc/servo"] + +[package.metadata.docs.rs] +features = ["serde1"] diff --git a/src/serde_impls.rs b/src/serde_impls.rs index 4c9a3a7..654a691 100644 --- a/src/serde_impls.rs +++ b/src/serde_impls.rs @@ -1,63 +1,255 @@ -use serde::ser::{Serialize, SerializeMap, SerializeSeq, Serializer}; -use std::fmt; +//! Serialization and Deserialization for syntax trees. -use crate::{Language, NodeOrToken, SyntaxNode, SyntaxToken}; +use crate::{interning::Resolver, GreenNodeBuilder, Language, NodeOrToken, SyntaxKind, SyntaxNode, WalkEvent}; +use serde::{ + de::{Error, SeqAccess, Visitor}, + ser::SerializeTuple, + Deserialize, Serialize, +}; +use std::{collections::VecDeque, fmt, marker::PhantomData}; -struct SerDisplay(T); -impl Serialize for SerDisplay { +type Rodeo = lasso::Rodeo; +type RodeoResolver = lasso::RodeoResolver; + +/// Expands to the first expression, if there's +/// no expression following, otherwise return the second expression. +/// +/// Required for having two different values if the argument is `$(...)?`. +macro_rules! data_list { + ($_:expr, $list:expr) => { + $list + }; + + ($list:expr,) => { + $list + }; +} + +/// Generate the code that should be put inside the [`Serialize`] implementation +/// of a [`SyntaxNode`]-like type. +/// +/// It serializes a [`SyntaxNode`] into a tuple with 2 elements. +/// The first element is the serialized event stream that was generated +/// by [`SyntaxNode::preorder_with_tokens()`]. +/// The second element is a list of `D`s, where `D` is the data of the nodes. +/// The data may only be serialized if it's `Some(data)`. Each `EnterNode` event +/// contains a boolean which indicates if this node has a data. If it has one, +/// the deserializer should pop the first element from the data list and continue. +/// +/// Takes the `Language` (`$l`), `SyntaxNode` (`$node`), `Resolver` (`$resolver`), +/// `Serializer` (`$serializer`), and an optional `data_list` which must be a `mut Vec`. +macro_rules! gen_serialize { + ($l:ident, $node:expr, $resolver:expr, $ser:ident, $($data_list:ident)?) => {{ + #[allow(unused_variables)] + let events = $node.preorder_with_tokens().filter_map(|event| match event { + WalkEvent::Enter(NodeOrToken::Node(node)) => { + let has_data = false; + $(let has_data = node + .get_data() + .map(|data| { + $data_list.push(data); + true + }) + .unwrap_or(false);)? + + Some(Event::EnterNode($l::kind_to_raw(node.kind()), has_data)) + } + WalkEvent::Enter(NodeOrToken::Token(tok)) => Some(Event::Token($l::kind_to_raw(tok.kind()), tok.resolve_text($resolver))), + + WalkEvent::Leave(NodeOrToken::Node(_)) => Some(Event::LeaveNode), + WalkEvent::Leave(NodeOrToken::Token(_)) => None, + }); + + let mut tuple = $ser.serialize_tuple(2)?; + + // TODO(Stupremee): We can easily avoid this allocation but it would + // require more weird and annoying-to-write code, so I'll skip it for now. + tuple.serialize_element(&events.collect::>())?; + tuple.serialize_element(&data_list!(Vec::<()>::new(), $($data_list)?))?; + + tuple.end() + }}; +} + +#[derive(Deserialize, Serialize)] +#[serde(tag = "t", content = "c")] +enum Event<'text> { + /// The second parameter indicates if this node needs data. + /// If the boolean is true, the next element inside the data list + /// must be attached to this node. + EnterNode(SyntaxKind, bool), + Token(SyntaxKind, &'text str), + LeaveNode, +} + +/// Make a `SyntaxNode` serializable but without serializing the data. +pub(crate) struct SerializeWithResolver<'node, 'resolver, L: Language, D: 'static, RN: 'static, R> { + pub(crate) node: &'node SyntaxNode, + pub(crate) resolver: &'resolver R, +} + +/// Make a `SyntaxNode` serializable which will include the data for serialization. +pub(crate) struct SerializeWithData<'node, 'resolver, L: Language, D: 'static, RN: 'static, R> { + pub(crate) node: &'node SyntaxNode, + pub(crate) resolver: &'resolver R, +} + +impl Serialize for SerializeWithData<'_, '_, L, D, RN, R> +where + L: Language, + R: Resolver, + D: Serialize, +{ fn serialize(&self, serializer: S) -> Result where - S: Serializer, + S: serde::Serializer, { - serializer.collect_str(&self.0) + let mut data_list = Vec::new(); + gen_serialize!(L, self.node, self.resolver, serializer, data_list) } } -struct DisplayDebug(T); -impl fmt::Display for DisplayDebug { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Debug::fmt(&self.0, f) +impl Serialize for SerializeWithResolver<'_, '_, L, D, RN, R> +where + L: Language, + R: Resolver, +{ + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + gen_serialize!(L, self.node, self.resolver, serializer,) } } -impl Serialize for SyntaxNode { +impl Serialize for SyntaxNode +where + L: Language, + D: Serialize, + R: Resolver, +{ fn serialize(&self, serializer: S) -> Result where - S: Serializer, + S: serde::Serializer, { - let mut state = serializer.serialize_map(Some(3))?; - state.serialize_entry("kind", &SerDisplay(DisplayDebug(self.kind())))?; - state.serialize_entry("text_range", &self.text_range())?; - state.serialize_entry("children", &Children(self))?; - state.end() + let node = SerializeWithResolver { + node: self, + resolver: self.resolver().as_ref(), + }; + node.serialize(serializer) } } -impl Serialize for SyntaxToken { - fn serialize(&self, serializer: S) -> Result +impl<'de, L, D> Deserialize<'de> for SyntaxNode +where + L: Language, + D: Deserialize<'de>, +{ + // Deserialization is done by walking down the deserialized event stream, + // which is the first element inside the tuple. The events + // are then passed to a `GreenNodeBuilder` which will do all + // the hard work for use. + // + // While walking the event stream, we also store a list of booleans, + // which indicate which node needs to set data. After creating the tree, + // we walk down the nodes, check if the bool at `data_list[idx]` is true, + // and if so, pop the first element of the data list and attach the data + // to the current node. + fn deserialize(deserializer: DE) -> Result where - S: Serializer, + DE: serde::Deserializer<'de>, { - let mut state = serializer.serialize_map(Some(3))?; - state.serialize_entry("kind", &SerDisplay(DisplayDebug(self.kind())))?; - state.serialize_entry("text_range", &self.text_range())?; - state.serialize_entry("text", &self.text().as_str())?; - state.end() - } -} + struct EventVisitor { + _marker: PhantomData>, + } -struct Children(T); + impl<'de, L, D> Visitor<'de> for EventVisitor + where + L: Language, + D: Deserialize<'de>, + { + type Value = (SyntaxNode, VecDeque); -impl Serialize for Children<&'_ SyntaxNode> { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - let mut state = serializer.serialize_seq(None)?; - self.0.children_with_tokens().try_for_each(|element| match element { - NodeOrToken::Node(it) => state.serialize_element(&it), - NodeOrToken::Token(it) => state.serialize_element(&it), + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a list of tree events") + } + + fn visit_seq(self, mut seq: A) -> Result + where + A: SeqAccess<'de>, + { + let mut builder = GreenNodeBuilder::new(); + let mut data_indices = VecDeque::new(); + + while let Some(next) = seq.next_element::>()? { + match next { + Event::EnterNode(kind, has_data) => { + builder.start_node(kind); + data_indices.push_back(has_data); + } + Event::Token(kind, text) => builder.token(kind, text), + Event::LeaveNode => builder.finish_node(), + } + } + + let (tree, resolver) = builder.finish(); + let tree = SyntaxNode::new_root_with_resolver(tree, resolver.unwrap().into_resolver()); + Ok((tree, data_indices)) + } + } + + struct ProcessedEvents(SyntaxNode, VecDeque); + impl<'de, L, D> Deserialize<'de> for ProcessedEvents + where + L: Language, + D: Deserialize<'de>, + { + fn deserialize(deserializer: DE) -> Result + where + DE: serde::Deserializer<'de>, + { + let (tree, ids) = deserializer.deserialize_seq(EventVisitor { _marker: PhantomData })?; + Ok(Self(tree, ids)) + } + } + + let (ProcessedEvents(tree, data_indices), mut data) = + <(ProcessedEvents, VecDeque)>::deserialize(deserializer)?; + + tree.descendants().zip(data_indices).try_for_each(|(node, has_data)| { + if has_data { + let data = data + .pop_front() + .ok_or_else(|| DE::Error::custom("invalid serialized tree"))?; + node.set_data(data); + } + >::Ok(()) })?; - state.end() + + if !data.is_empty() { + Err(DE::Error::custom( + "serialized SyntaxNode contained too many data elements", + )) + } else { + Ok(tree) + } + } +} + +impl Serialize for SyntaxKind { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_u16(self.0) + } +} + +impl<'de> Deserialize<'de> for SyntaxKind { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + Ok(Self(u16::deserialize(deserializer)?)) } } diff --git a/src/syntax.rs b/src/syntax.rs index 0e93588..56b4f92 100644 --- a/src/syntax.rs +++ b/src/syntax.rs @@ -6,6 +6,8 @@ use std::{ sync::atomic::{AtomicU32, Ordering}, }; +#[cfg(feature = "serde1")] +use crate::serde_impls::{SerializeWithData, SerializeWithResolver}; use parking_lot::RwLock; use servo_arc::Arc; @@ -872,6 +874,46 @@ where } } +#[cfg(feature = "serde1")] +impl SyntaxNode +where + L: Language, +{ + /// Return an anonymous object that can be used to serialize this node, + /// including the data for each node. + pub fn as_serialize_with_data(&self) -> impl serde::Serialize + '_ + where + R: Resolver, + D: serde::Serialize, + { + SerializeWithData { + node: self, + resolver: self.resolver().as_ref(), + } + } + + /// Return an anonymous object that can be used to serialize this node, + /// including the data and by using an external resolver. + pub fn as_serialize_with_data_with_resolver<'node>( + &'node self, + resolver: &'node impl Resolver, + ) -> impl serde::Serialize + 'node + where + D: serde::Serialize, + { + SerializeWithData { node: self, resolver } + } + + /// Return an anonymous object that can be used to serialize this node, + /// which uses the given resolver instead of the resolver inside the tree. + pub fn as_serialize_with_resolver<'node>( + &'node self, + resolver: &'node impl Resolver, + ) -> impl serde::Serialize + 'node { + SerializeWithResolver { node: self, resolver } + } +} + impl fmt::Debug for SyntaxNode where R: Resolver, diff --git a/tests/basic.rs b/tests/basic.rs index 54ea9f5..b404b05 100644 --- a/tests/basic.rs +++ b/tests/basic.rs @@ -1,15 +1,14 @@ mod common; -use common::TestLang; -use cstree::{GreenNode, GreenNodeBuilder, NodeCache, SyntaxKind, TextRange}; -use lasso::{Interner, Resolver, Rodeo}; +use common::{build_recursive, build_tree_with_cache, Element, SyntaxNode}; +use cstree::{GreenNodeBuilder, NodeCache, SyntaxKind, TextRange}; +use lasso::{Resolver, Rodeo}; -type SyntaxNode = cstree::SyntaxNode; - -#[derive(Debug)] -enum Element<'s> { - Node(Vec>), - Token(&'s str), +fn build_tree(root: &Element<'_>) -> (SyntaxNode, impl Resolver) { + let mut builder = GreenNodeBuilder::new(); + build_recursive(root, &mut builder, 0); + let (node, interner) = builder.finish(); + (SyntaxNode::new_root(node), interner.unwrap()) } fn two_level_tree() -> Element<'static> { @@ -21,43 +20,6 @@ fn two_level_tree() -> Element<'static> { ]) } -fn build_tree(root: &Element<'_>) -> (SyntaxNode, impl Resolver) { - let mut builder = GreenNodeBuilder::new(); - build_recursive(root, &mut builder, 0); - let (node, interner) = builder.finish(); - (SyntaxNode::new_root(node), interner.unwrap()) -} - -fn build_tree_with_cache<'c, 'i, I>(root: &Element<'_>, cache: &'c mut NodeCache<'i, I>) -> GreenNode -where - I: Interner, -{ - let mut builder = GreenNodeBuilder::with_cache(cache); - build_recursive(root, &mut builder, 0); - let (node, interner) = builder.finish(); - assert!(interner.is_none()); - node -} - -fn build_recursive<'c, 'i, I>(root: &Element<'_>, builder: &mut GreenNodeBuilder<'c, 'i, I>, mut from: u16) -> u16 -where - I: Interner, -{ - match root { - Element::Node(children) => { - builder.start_node(SyntaxKind(from)); - for child in children { - from = build_recursive(child, builder, from + 1); - } - builder.finish_node(); - } - Element::Token(text) => { - builder.token(SyntaxKind(from), *text); - } - } - from -} - #[test] fn create() { let tree = two_level_tree(); diff --git a/tests/common.rs b/tests/common.rs index 70492fc..ffd340a 100644 --- a/tests/common.rs +++ b/tests/common.rs @@ -1,4 +1,13 @@ -use cstree::{Language, SyntaxKind}; +use cstree::{GreenNode, GreenNodeBuilder, Language, NodeCache, SyntaxKind}; +use lasso::Interner; + +pub type SyntaxNode = cstree::SyntaxNode; + +#[derive(Debug)] +pub enum Element<'s> { + Node(Vec>), + Token(&'s str), +} #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] pub enum TestLang {} @@ -13,3 +22,33 @@ impl Language for TestLang { kind } } + +pub fn build_tree_with_cache<'c, 'i, I>(root: &Element<'_>, cache: &'c mut NodeCache<'i, I>) -> GreenNode +where + I: Interner, +{ + let mut builder = GreenNodeBuilder::with_cache(cache); + build_recursive(root, &mut builder, 0); + let (node, interner) = builder.finish(); + assert!(interner.is_none()); + node +} + +pub fn build_recursive<'c, 'i, I>(root: &Element<'_>, builder: &mut GreenNodeBuilder<'c, 'i, I>, mut from: u16) -> u16 +where + I: Interner, +{ + match root { + Element::Node(children) => { + builder.start_node(SyntaxKind(from)); + for child in children { + from = build_recursive(child, builder, from + 1); + } + builder.finish_node(); + } + Element::Token(text) => { + builder.token(SyntaxKind(from), *text); + } + } + from +} diff --git a/tests/serde.rs b/tests/serde.rs new file mode 100644 index 0000000..f415121 --- /dev/null +++ b/tests/serde.rs @@ -0,0 +1,291 @@ +#![cfg(feature = "serde1")] + +mod common; + +use common::{Element, SyntaxNode}; +use cstree::{GreenNodeBuilder, NodeCache, NodeOrToken}; +use lasso::Resolver; +use serde_test::Token; +use std::fmt; + +type Rodeo = lasso::Rodeo; +type RodeoResolver = lasso::RodeoResolver; + +/// Macro for generating a list of `serde_test::Token`s using a simpler DSL. +macro_rules! event_tokens { + ($($name:ident($($token:tt)*)),*) => { + [ + $( + event_tokens!(@token, $name($($token)*)) + ),* + ].concat() + }; + + (@token, token($kind:expr, $str:expr)) => { + [ + Token::Struct { name: "Event", len: 2 }, + Token::BorrowedStr("t"), + Token::BorrowedStr("Token"), + Token::BorrowedStr("c"), + Token::Tuple { len: 2 }, + Token::U16($kind), + Token::BorrowedStr($str), + Token::TupleEnd, + Token::StructEnd, + ].as_ref() + }; + + (@token, node($kind:expr, $data:expr)) => { + [ + Token::Struct { name: "Event", len: 2 }, + Token::BorrowedStr("t"), + Token::BorrowedStr("EnterNode"), + Token::BorrowedStr("c"), + Token::Tuple { len: 2 }, + Token::U16($kind), + Token::Bool($data), + Token::TupleEnd, + Token::StructEnd, + ].as_ref() + }; + + (@token, leave_node()) => { + [ + Token::Struct { name: "Event", len: 1 }, + Token::BorrowedStr("t"), + Token::BorrowedStr("LeaveNode"), + Token::StructEnd, + ].as_ref() + }; + + (@token, data($data:expr)) => { + [Token::Str($data)].as_ref() + }; + + (@token, seq($len:expr)) => { + [Token::Seq { len: Option::Some($len) }].as_ref() + }; + + (@token, seq_end()) => { + [Token::SeqEnd].as_ref() + }; + + (@token, tuple($len:expr)) => { + [Token::Tuple { len: $len }].as_ref() + }; + + (@token, tuple_end()) => { + [Token::TupleEnd].as_ref() + }; + + (@token,) => {}; +} + +fn three_level_tree_with_data_tokens() -> Vec { + event_tokens!( + tuple(2), + seq(14), + node(0, true), + node(1, true), + node(2, true), + token(3, "foo"), + token(4, "bar"), + leave_node(), + token(5, "baz"), + leave_node(), + node(6, true), + token(7, "pub"), + token(8, "fn"), + token(9, "tree"), + leave_node(), + leave_node(), + seq_end(), + seq(4), + data("1"), + data("2"), + data("3"), + data("4"), + seq_end(), + tuple_end() + ) +} + +fn three_level_tree_tokens() -> Vec { + event_tokens!( + tuple(2), + seq(14), + node(0, false), + node(1, false), + node(2, false), + token(3, "foo"), + token(4, "bar"), + leave_node(), + token(5, "baz"), + leave_node(), + node(6, false), + token(7, "pub"), + token(8, "fn"), + token(9, "tree"), + leave_node(), + leave_node(), + seq_end(), + seq(0), + seq_end(), + tuple_end() + ) +} + +struct NonSerializable; + +/// Serializable SyntaxNode that doesn't have a identity `PartialEq` implementation, +/// but checks if both trees have equal nodes and tokens. +struct TestNode { + node: SyntaxNode, + with_data: bool, +} + +impl TestNode { + fn new(node: SyntaxNode) -> Self { + Self { node, with_data: false } + } + + fn with_data(node: SyntaxNode) -> Self { + Self { node, with_data: true } + } +} + +impl fmt::Debug for TestNode { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(&self.node, f) + } +} + +impl serde::Serialize for TestNode { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + if self.with_data { + self.node.as_serialize_with_data().serialize(serializer) + } else { + self.node.serialize(serializer) + } + } +} + +impl<'de> serde::Deserialize<'de> for TestNode { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + Ok(Self { + node: SyntaxNode::deserialize(deserializer)?, + with_data: true, + }) + } +} + +impl PartialEq> for TestNode { + fn eq(&self, other: &TestNode) -> bool { + self.node.kind() == other.node.kind() + && self.node.get_data() == other.node.get_data() + && self.node.text_range() == other.node.text_range() + && self + .node + .children_with_tokens() + .zip(other.node.children_with_tokens()) + .all(|(this, other)| match (this, other) { + (NodeOrToken::Node(this), NodeOrToken::Node(other)) => { + TestNode::new(this.clone()) == TestNode::new(other.clone()) + } + (NodeOrToken::Token(this), NodeOrToken::Token(other)) => { + this.kind() == other.kind() && this.text_range() == other.text_range() + } + _ => unreachable!(), + }) + } +} + +#[rustfmt::skip] +fn three_level_tree() -> Element<'static> { + use Element::*; + + Node(vec![ + Node(vec![ + Node(vec![ + Token("foo"), + Token("bar") + ]), + Token("baz") + ]), + Node(vec![ + Token("pub"), + Token("fn"), + Token("tree") + ]), + ]) +} + +fn build_tree(root: Element<'_>) -> SyntaxNode { + let mut builder = GreenNodeBuilder::new(); + common::build_recursive(&root, &mut builder, 0); + let (node, interner) = builder.finish(); + SyntaxNode::new_root_with_resolver(node, interner.unwrap().into_resolver()) +} + +fn attach_data(node: &SyntaxNode) { + node.descendants().enumerate().for_each(|(idx, node)| { + node.set_data(format!("{}", idx + 1)); + }); +} + +#[test] +fn serialize_tree_with_data_with_resolver() { + let mut interner = Rodeo::with_hasher(Default::default()); + let mut cache = NodeCache::with_interner(&mut interner); + + let root = three_level_tree(); + let root = common::build_tree_with_cache(&root, &mut cache); + let tree = SyntaxNode::::new_root(root.clone()); + attach_data(&tree); + + let serialized = serde_json::to_string(&tree.as_serialize_with_data_with_resolver(&interner)).unwrap(); + let deserialized: TestNode<_> = serde_json::from_str(&serialized).unwrap(); + + let expected = SyntaxNode::new_root_with_resolver(root, interner); + attach_data(&expected); + assert_eq!(TestNode::new(expected), deserialized); +} + +#[test] +fn serialize_tree_with_resolver() { + let mut interner = Rodeo::with_hasher(Default::default()); + let mut cache = NodeCache::with_interner(&mut interner); + + let root = three_level_tree(); + let root = common::build_tree_with_cache(&root, &mut cache); + let tree = SyntaxNode::::new_root(root.clone()); + + let serialized = serde_json::to_string(&tree.as_serialize_with_resolver(&interner)).unwrap(); + let deserialized: TestNode<_> = serde_json::from_str(&serialized).unwrap(); + + let expected = SyntaxNode::new_root_with_resolver(root, interner); + assert_eq!(TestNode::new(expected), deserialized); +} + +#[test] +fn serialize_tree_with_data() { + let tree = build_tree(three_level_tree()); + let tree = TestNode::with_data(tree); + attach_data(&tree.node); + + serde_test::assert_tokens(&tree, three_level_tree_with_data_tokens().as_slice()); +} + +#[test] +fn serialize_tree_without_data() { + let tree = build_tree(three_level_tree()); + let tree = TestNode::new(tree); + + serde_test::assert_tokens(&tree, three_level_tree_tokens().as_slice()); +}