aboutsummaryrefslogtreecommitdiff
path: root/crates/proc_macro_api
diff options
context:
space:
mode:
authorAleksey Kladov <[email protected]>2020-08-13 11:07:28 +0100
committerAleksey Kladov <[email protected]>2020-08-13 11:39:27 +0100
commit2119dc23e80d77f1abc789e3d99c34d429e17905 (patch)
tree977254cd7b4fa53da65836cd0f119ef404e7dd62 /crates/proc_macro_api
parent4abdf323af5bc693f8b9ff3455e19ee1dff572a8 (diff)
Rename ra_proc_macro -> proc_macro_api
Diffstat (limited to 'crates/proc_macro_api')
-rw-r--r--crates/proc_macro_api/Cargo.toml19
-rw-r--r--crates/proc_macro_api/src/lib.rs111
-rw-r--r--crates/proc_macro_api/src/msg.rs89
-rw-r--r--crates/proc_macro_api/src/process.rs201
-rw-r--r--crates/proc_macro_api/src/rpc.rs267
5 files changed, 687 insertions, 0 deletions
diff --git a/crates/proc_macro_api/Cargo.toml b/crates/proc_macro_api/Cargo.toml
new file mode 100644
index 000000000..c1abb5627
--- /dev/null
+++ b/crates/proc_macro_api/Cargo.toml
@@ -0,0 +1,19 @@
1[package]
2edition = "2018"
3name = "proc_macro_api"
4version = "0.1.0"
5authors = ["rust-analyzer developers"]
6publish = false
7license = "MIT OR Apache-2.0"
8
9[lib]
10doctest = false
11
12[dependencies]
13serde = { version = "1.0", features = ["derive"] }
14serde_json = "1.0"
15log = "0.4.8"
16crossbeam-channel = "0.4.0"
17jod-thread = "0.1.1"
18
19tt = { path = "../tt" }
diff --git a/crates/proc_macro_api/src/lib.rs b/crates/proc_macro_api/src/lib.rs
new file mode 100644
index 000000000..15db57eb2
--- /dev/null
+++ b/crates/proc_macro_api/src/lib.rs
@@ -0,0 +1,111 @@
1//! Client-side Proc-Macro crate
2//!
3//! We separate proc-macro expanding logic to an extern program to allow
4//! different implementations (e.g. wasm or dylib loading). And this crate
5//! is used to provide basic infrastructure for communication between two
6//! processes: Client (RA itself), Server (the external program)
7
8mod rpc;
9mod process;
10pub mod msg;
11
12use std::{
13 ffi::OsStr,
14 io,
15 path::{Path, PathBuf},
16 sync::Arc,
17};
18
19use tt::{SmolStr, Subtree};
20
21use crate::process::{ProcMacroProcessSrv, ProcMacroProcessThread};
22
23pub use rpc::{ExpansionResult, ExpansionTask, ListMacrosResult, ListMacrosTask, ProcMacroKind};
24
25#[derive(Debug, Clone)]
26pub struct ProcMacroProcessExpander {
27 process: Arc<ProcMacroProcessSrv>,
28 dylib_path: PathBuf,
29 name: SmolStr,
30}
31
32impl Eq for ProcMacroProcessExpander {}
33impl PartialEq for ProcMacroProcessExpander {
34 fn eq(&self, other: &Self) -> bool {
35 self.name == other.name
36 && self.dylib_path == other.dylib_path
37 && Arc::ptr_eq(&self.process, &other.process)
38 }
39}
40
41impl tt::TokenExpander for ProcMacroProcessExpander {
42 fn expand(
43 &self,
44 subtree: &Subtree,
45 _attr: Option<&Subtree>,
46 ) -> Result<Subtree, tt::ExpansionError> {
47 self.process.custom_derive(&self.dylib_path, subtree, &self.name)
48 }
49}
50
51#[derive(Debug)]
52enum ProcMacroClientKind {
53 Process { process: Arc<ProcMacroProcessSrv>, thread: ProcMacroProcessThread },
54 Dummy,
55}
56
57#[derive(Debug)]
58pub struct ProcMacroClient {
59 kind: ProcMacroClientKind,
60}
61
62impl ProcMacroClient {
63 pub fn extern_process(
64 process_path: PathBuf,
65 args: impl IntoIterator<Item = impl AsRef<OsStr>>,
66 ) -> io::Result<ProcMacroClient> {
67 let (thread, process) = ProcMacroProcessSrv::run(process_path, args)?;
68 Ok(ProcMacroClient {
69 kind: ProcMacroClientKind::Process { process: Arc::new(process), thread },
70 })
71 }
72
73 pub fn dummy() -> ProcMacroClient {
74 ProcMacroClient { kind: ProcMacroClientKind::Dummy }
75 }
76
77 pub fn by_dylib_path(&self, dylib_path: &Path) -> Vec<(SmolStr, Arc<dyn tt::TokenExpander>)> {
78 match &self.kind {
79 ProcMacroClientKind::Dummy => vec![],
80 ProcMacroClientKind::Process { process, .. } => {
81 let macros = match process.find_proc_macros(dylib_path) {
82 Err(err) => {
83 eprintln!("Failed to find proc macros. Error: {:#?}", err);
84 return vec![];
85 }
86 Ok(macros) => macros,
87 };
88
89 macros
90 .into_iter()
91 .filter_map(|(name, kind)| {
92 // FIXME: Support custom derive only for now.
93 match kind {
94 ProcMacroKind::CustomDerive => {
95 let name = SmolStr::new(&name);
96 let expander: Arc<dyn tt::TokenExpander> =
97 Arc::new(ProcMacroProcessExpander {
98 process: process.clone(),
99 name: name.clone(),
100 dylib_path: dylib_path.into(),
101 });
102 Some((name, expander))
103 }
104 _ => None,
105 }
106 })
107 .collect()
108 }
109 }
110 }
111}
diff --git a/crates/proc_macro_api/src/msg.rs b/crates/proc_macro_api/src/msg.rs
new file mode 100644
index 000000000..f84ebdbc5
--- /dev/null
+++ b/crates/proc_macro_api/src/msg.rs
@@ -0,0 +1,89 @@
1//! Defines messages for cross-process message passing based on `ndjson` wire protocol
2
3use std::{
4 convert::TryFrom,
5 io::{self, BufRead, Write},
6};
7
8use serde::{de::DeserializeOwned, Deserialize, Serialize};
9
10use crate::{
11 rpc::{ListMacrosResult, ListMacrosTask},
12 ExpansionResult, ExpansionTask,
13};
14
15#[derive(Debug, Serialize, Deserialize, Clone)]
16pub enum Request {
17 ListMacro(ListMacrosTask),
18 ExpansionMacro(ExpansionTask),
19}
20
21#[derive(Debug, Serialize, Deserialize, Clone)]
22pub enum Response {
23 Error(ResponseError),
24 ListMacro(ListMacrosResult),
25 ExpansionMacro(ExpansionResult),
26}
27
28macro_rules! impl_try_from_response {
29 ($ty:ty, $tag:ident) => {
30 impl TryFrom<Response> for $ty {
31 type Error = &'static str;
32 fn try_from(value: Response) -> Result<Self, Self::Error> {
33 match value {
34 Response::$tag(res) => Ok(res),
35 _ => Err(concat!("Failed to convert response to ", stringify!($tag))),
36 }
37 }
38 }
39 };
40}
41
42impl_try_from_response!(ListMacrosResult, ListMacro);
43impl_try_from_response!(ExpansionResult, ExpansionMacro);
44
45#[derive(Debug, Serialize, Deserialize, Clone)]
46pub struct ResponseError {
47 pub code: ErrorCode,
48 pub message: String,
49}
50
51#[derive(Debug, Serialize, Deserialize, Clone)]
52pub enum ErrorCode {
53 ServerErrorEnd,
54 ExpansionError,
55}
56
57pub trait Message: Serialize + DeserializeOwned {
58 fn read(inp: &mut impl BufRead) -> io::Result<Option<Self>> {
59 Ok(match read_json(inp)? {
60 None => None,
61 Some(text) => Some(serde_json::from_str(&text)?),
62 })
63 }
64 fn write(self, out: &mut impl Write) -> io::Result<()> {
65 let text = serde_json::to_string(&self)?;
66 write_json(out, &text)
67 }
68}
69
70impl Message for Request {}
71impl Message for Response {}
72
73fn read_json(inp: &mut impl BufRead) -> io::Result<Option<String>> {
74 let mut buf = String::new();
75 inp.read_line(&mut buf)?;
76 buf.pop(); // Remove traling '\n'
77 Ok(match buf.len() {
78 0 => None,
79 _ => Some(buf),
80 })
81}
82
83fn write_json(out: &mut impl Write, msg: &str) -> io::Result<()> {
84 log::debug!("> {}", msg);
85 out.write_all(msg.as_bytes())?;
86 out.write_all(b"\n")?;
87 out.flush()?;
88 Ok(())
89}
diff --git a/crates/proc_macro_api/src/process.rs b/crates/proc_macro_api/src/process.rs
new file mode 100644
index 000000000..51ffcaa78
--- /dev/null
+++ b/crates/proc_macro_api/src/process.rs
@@ -0,0 +1,201 @@
1//! Handle process life-time and message passing for proc-macro client
2
3use std::{
4 convert::{TryFrom, TryInto},
5 ffi::{OsStr, OsString},
6 io::{self, BufRead, BufReader, Write},
7 path::{Path, PathBuf},
8 process::{Child, Command, Stdio},
9 sync::{Arc, Weak},
10};
11
12use crossbeam_channel::{bounded, Receiver, Sender};
13use tt::Subtree;
14
15use crate::{
16 msg::{ErrorCode, Message, Request, Response, ResponseError},
17 rpc::{ExpansionResult, ExpansionTask, ListMacrosResult, ListMacrosTask, ProcMacroKind},
18};
19
20#[derive(Debug, Default)]
21pub(crate) struct ProcMacroProcessSrv {
22 inner: Option<Weak<Sender<Task>>>,
23}
24
25#[derive(Debug)]
26pub(crate) struct ProcMacroProcessThread {
27 // XXX: drop order is significant
28 sender: Arc<Sender<Task>>,
29 handle: jod_thread::JoinHandle<()>,
30}
31
32impl ProcMacroProcessSrv {
33 pub fn run(
34 process_path: PathBuf,
35 args: impl IntoIterator<Item = impl AsRef<OsStr>>,
36 ) -> io::Result<(ProcMacroProcessThread, ProcMacroProcessSrv)> {
37 let process = Process::run(process_path, args)?;
38
39 let (task_tx, task_rx) = bounded(0);
40 let handle = jod_thread::spawn(move || {
41 client_loop(task_rx, process);
42 });
43
44 let task_tx = Arc::new(task_tx);
45 let srv = ProcMacroProcessSrv { inner: Some(Arc::downgrade(&task_tx)) };
46 let thread = ProcMacroProcessThread { handle, sender: task_tx };
47
48 Ok((thread, srv))
49 }
50
51 pub fn find_proc_macros(
52 &self,
53 dylib_path: &Path,
54 ) -> Result<Vec<(String, ProcMacroKind)>, tt::ExpansionError> {
55 let task = ListMacrosTask { lib: dylib_path.to_path_buf() };
56
57 let result: ListMacrosResult = self.send_task(Request::ListMacro(task))?;
58 Ok(result.macros)
59 }
60
61 pub fn custom_derive(
62 &self,
63 dylib_path: &Path,
64 subtree: &Subtree,
65 derive_name: &str,
66 ) -> Result<Subtree, tt::ExpansionError> {
67 let task = ExpansionTask {
68 macro_body: subtree.clone(),
69 macro_name: derive_name.to_string(),
70 attributes: None,
71 lib: dylib_path.to_path_buf(),
72 };
73
74 let result: ExpansionResult = self.send_task(Request::ExpansionMacro(task))?;
75 Ok(result.expansion)
76 }
77
78 pub fn send_task<R>(&self, req: Request) -> Result<R, tt::ExpansionError>
79 where
80 R: TryFrom<Response, Error = &'static str>,
81 {
82 let sender = match &self.inner {
83 None => return Err(tt::ExpansionError::Unknown("No sender is found.".to_string())),
84 Some(it) => it,
85 };
86
87 let (result_tx, result_rx) = bounded(0);
88 let sender = match sender.upgrade() {
89 None => {
90 return Err(tt::ExpansionError::Unknown("Proc macro process is closed.".into()))
91 }
92 Some(it) => it,
93 };
94 sender.send(Task { req, result_tx }).unwrap();
95 let res = result_rx
96 .recv()
97 .map_err(|_| tt::ExpansionError::Unknown("Proc macro thread is closed.".into()))?;
98
99 match res {
100 Some(Response::Error(err)) => {
101 return Err(tt::ExpansionError::ExpansionError(err.message));
102 }
103 Some(res) => Ok(res.try_into().map_err(|err| {
104 tt::ExpansionError::Unknown(format!("Fail to get response, reason : {:#?} ", err))
105 })?),
106 None => Err(tt::ExpansionError::Unknown("Empty result".into())),
107 }
108 }
109}
110
111fn client_loop(task_rx: Receiver<Task>, mut process: Process) {
112 let (mut stdin, mut stdout) = match process.stdio() {
113 None => return,
114 Some(it) => it,
115 };
116
117 for task in task_rx {
118 let Task { req, result_tx } = task;
119
120 match send_request(&mut stdin, &mut stdout, req) {
121 Ok(res) => result_tx.send(res).unwrap(),
122 Err(_err) => {
123 let res = Response::Error(ResponseError {
124 code: ErrorCode::ServerErrorEnd,
125 message: "Server closed".into(),
126 });
127 result_tx.send(res.into()).unwrap();
128 // Restart the process
129 if process.restart().is_err() {
130 break;
131 }
132 let stdio = match process.stdio() {
133 None => break,
134 Some(it) => it,
135 };
136 stdin = stdio.0;
137 stdout = stdio.1;
138 }
139 }
140 }
141}
142
143struct Task {
144 req: Request,
145 result_tx: Sender<Option<Response>>,
146}
147
148struct Process {
149 path: PathBuf,
150 args: Vec<OsString>,
151 child: Child,
152}
153
154impl Drop for Process {
155 fn drop(&mut self) {
156 let _ = self.child.kill();
157 }
158}
159
160impl Process {
161 fn run(
162 path: PathBuf,
163 args: impl IntoIterator<Item = impl AsRef<OsStr>>,
164 ) -> io::Result<Process> {
165 let args = args.into_iter().map(|s| s.as_ref().into()).collect();
166 let child = mk_child(&path, &args)?;
167 Ok(Process { path, args, child })
168 }
169
170 fn restart(&mut self) -> io::Result<()> {
171 let _ = self.child.kill();
172 self.child = mk_child(&self.path, &self.args)?;
173 Ok(())
174 }
175
176 fn stdio(&mut self) -> Option<(impl Write, impl BufRead)> {
177 let stdin = self.child.stdin.take()?;
178 let stdout = self.child.stdout.take()?;
179 let read = BufReader::new(stdout);
180
181 Some((stdin, read))
182 }
183}
184
185fn mk_child(path: &Path, args: impl IntoIterator<Item = impl AsRef<OsStr>>) -> io::Result<Child> {
186 Command::new(&path)
187 .args(args)
188 .stdin(Stdio::piped())
189 .stdout(Stdio::piped())
190 .stderr(Stdio::inherit())
191 .spawn()
192}
193
194fn send_request(
195 mut writer: &mut impl Write,
196 mut reader: &mut impl BufRead,
197 req: Request,
198) -> io::Result<Option<Response>> {
199 req.write(&mut writer)?;
200 Ok(Response::read(&mut reader)?)
201}
diff --git a/crates/proc_macro_api/src/rpc.rs b/crates/proc_macro_api/src/rpc.rs
new file mode 100644
index 000000000..47624163e
--- /dev/null
+++ b/crates/proc_macro_api/src/rpc.rs
@@ -0,0 +1,267 @@
1//! Data structure serialization related stuff for RPC
2//!
3//! Defines all necessary rpc serialization data structures,
4//! which includes `tt` related data and some task messages.
5//! Although adding `Serialize` and `Deserialize` traits to `tt` directly seems
6//! to be much easier, we deliberately duplicate `tt` structs with `#[serde(with = "XXDef")]`
7//! for separation of code responsibility.
8
9use std::path::PathBuf;
10
11use serde::{Deserialize, Serialize};
12use tt::{
13 Delimiter, DelimiterKind, Ident, Leaf, Literal, Punct, SmolStr, Spacing, Subtree, TokenId,
14 TokenTree,
15};
16
17#[derive(Clone, Eq, PartialEq, Debug, Serialize, Deserialize)]
18pub struct ListMacrosTask {
19 pub lib: PathBuf,
20}
21
22#[derive(Clone, Eq, PartialEq, Debug, Serialize, Deserialize)]
23pub enum ProcMacroKind {
24 CustomDerive,
25 FuncLike,
26 Attr,
27}
28
29#[derive(Clone, Eq, PartialEq, Debug, Default, Serialize, Deserialize)]
30pub struct ListMacrosResult {
31 pub macros: Vec<(String, ProcMacroKind)>,
32}
33
34#[derive(Clone, Eq, PartialEq, Debug, Serialize, Deserialize)]
35pub struct ExpansionTask {
36 /// Argument of macro call.
37 ///
38 /// In custom derive this will be a struct or enum; in attribute-like macro - underlying
39 /// item; in function-like macro - the macro body.
40 #[serde(with = "SubtreeDef")]
41 pub macro_body: Subtree,
42
43 /// Name of macro to expand.
44 ///
45 /// In custom derive this is the name of the derived trait (`Serialize`, `Getters`, etc.).
46 /// In attribute-like and function-like macros - single name of macro itself (`show_streams`).
47 pub macro_name: String,
48
49 /// Possible attributes for the attribute-like macros.
50 #[serde(with = "opt_subtree_def")]
51 pub attributes: Option<Subtree>,
52
53 pub lib: PathBuf,
54}
55
56#[derive(Clone, Eq, PartialEq, Debug, Default, Serialize, Deserialize)]
57pub struct ExpansionResult {
58 #[serde(with = "SubtreeDef")]
59 pub expansion: Subtree,
60}
61
62#[derive(Serialize, Deserialize)]
63#[serde(remote = "DelimiterKind")]
64enum DelimiterKindDef {
65 Parenthesis,
66 Brace,
67 Bracket,
68}
69
70#[derive(Serialize, Deserialize)]
71#[serde(remote = "TokenId")]
72struct TokenIdDef(u32);
73
74#[derive(Serialize, Deserialize)]
75#[serde(remote = "Delimiter")]
76struct DelimiterDef {
77 #[serde(with = "TokenIdDef")]
78 pub id: TokenId,
79 #[serde(with = "DelimiterKindDef")]
80 pub kind: DelimiterKind,
81}
82
83#[derive(Serialize, Deserialize)]
84#[serde(remote = "Subtree")]
85struct SubtreeDef {
86 #[serde(default, with = "opt_delimiter_def")]
87 pub delimiter: Option<Delimiter>,
88 #[serde(with = "vec_token_tree")]
89 pub token_trees: Vec<TokenTree>,
90}
91
92#[derive(Serialize, Deserialize)]
93#[serde(remote = "TokenTree")]
94enum TokenTreeDef {
95 #[serde(with = "LeafDef")]
96 Leaf(Leaf),
97 #[serde(with = "SubtreeDef")]
98 Subtree(Subtree),
99}
100
101#[derive(Serialize, Deserialize)]
102#[serde(remote = "Leaf")]
103enum LeafDef {
104 #[serde(with = "LiteralDef")]
105 Literal(Literal),
106 #[serde(with = "PunctDef")]
107 Punct(Punct),
108 #[serde(with = "IdentDef")]
109 Ident(Ident),
110}
111
112#[derive(Serialize, Deserialize)]
113#[serde(remote = "Literal")]
114struct LiteralDef {
115 pub text: SmolStr,
116 #[serde(with = "TokenIdDef")]
117 pub id: TokenId,
118}
119
120#[derive(Serialize, Deserialize)]
121#[serde(remote = "Punct")]
122struct PunctDef {
123 pub char: char,
124 #[serde(with = "SpacingDef")]
125 pub spacing: Spacing,
126 #[serde(with = "TokenIdDef")]
127 pub id: TokenId,
128}
129
130#[derive(Serialize, Deserialize)]
131#[serde(remote = "Spacing")]
132enum SpacingDef {
133 Alone,
134 Joint,
135}
136
137#[derive(Serialize, Deserialize)]
138#[serde(remote = "Ident")]
139struct IdentDef {
140 pub text: SmolStr,
141 #[serde(with = "TokenIdDef")]
142 pub id: TokenId,
143}
144
145mod opt_delimiter_def {
146 use super::{Delimiter, DelimiterDef};
147 use serde::{Deserialize, Deserializer, Serialize, Serializer};
148
149 pub fn serialize<S>(value: &Option<Delimiter>, serializer: S) -> Result<S::Ok, S::Error>
150 where
151 S: Serializer,
152 {
153 #[derive(Serialize)]
154 struct Helper<'a>(#[serde(with = "DelimiterDef")] &'a Delimiter);
155 value.as_ref().map(Helper).serialize(serializer)
156 }
157
158 pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<Delimiter>, D::Error>
159 where
160 D: Deserializer<'de>,
161 {
162 #[derive(Deserialize)]
163 struct Helper(#[serde(with = "DelimiterDef")] Delimiter);
164 let helper = Option::deserialize(deserializer)?;
165 Ok(helper.map(|Helper(external)| external))
166 }
167}
168
169mod opt_subtree_def {
170 use super::{Subtree, SubtreeDef};
171 use serde::{Deserialize, Deserializer, Serialize, Serializer};
172
173 pub fn serialize<S>(value: &Option<Subtree>, serializer: S) -> Result<S::Ok, S::Error>
174 where
175 S: Serializer,
176 {
177 #[derive(Serialize)]
178 struct Helper<'a>(#[serde(with = "SubtreeDef")] &'a Subtree);
179 value.as_ref().map(Helper).serialize(serializer)
180 }
181
182 pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<Subtree>, D::Error>
183 where
184 D: Deserializer<'de>,
185 {
186 #[derive(Deserialize)]
187 struct Helper(#[serde(with = "SubtreeDef")] Subtree);
188 let helper = Option::deserialize(deserializer)?;
189 Ok(helper.map(|Helper(external)| external))
190 }
191}
192
193mod vec_token_tree {
194 use super::{TokenTree, TokenTreeDef};
195 use serde::{ser::SerializeSeq, Deserialize, Deserializer, Serialize, Serializer};
196
197 pub fn serialize<S>(value: &Vec<TokenTree>, serializer: S) -> Result<S::Ok, S::Error>
198 where
199 S: Serializer,
200 {
201 #[derive(Serialize)]
202 struct Helper<'a>(#[serde(with = "TokenTreeDef")] &'a TokenTree);
203
204 let items: Vec<_> = value.iter().map(Helper).collect();
205 let mut seq = serializer.serialize_seq(Some(items.len()))?;
206 for element in items {
207 seq.serialize_element(&element)?;
208 }
209 seq.end()
210 }
211
212 pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<TokenTree>, D::Error>
213 where
214 D: Deserializer<'de>,
215 {
216 #[derive(Deserialize)]
217 struct Helper(#[serde(with = "TokenTreeDef")] TokenTree);
218
219 let helper = Vec::deserialize(deserializer)?;
220 Ok(helper.into_iter().map(|Helper(external)| external).collect())
221 }
222}
223
224#[cfg(test)]
225mod tests {
226 use super::*;
227
228 fn fixture_token_tree() -> Subtree {
229 let mut subtree = Subtree::default();
230 subtree
231 .token_trees
232 .push(TokenTree::Leaf(Ident { text: "struct".into(), id: TokenId(0) }.into()));
233 subtree
234 .token_trees
235 .push(TokenTree::Leaf(Ident { text: "Foo".into(), id: TokenId(1) }.into()));
236 subtree.token_trees.push(TokenTree::Subtree(
237 Subtree {
238 delimiter: Some(Delimiter { id: TokenId(2), kind: DelimiterKind::Brace }),
239 token_trees: vec![],
240 }
241 .into(),
242 ));
243 subtree
244 }
245
246 #[test]
247 fn test_proc_macro_rpc_works() {
248 let tt = fixture_token_tree();
249 let task = ExpansionTask {
250 macro_body: tt.clone(),
251 macro_name: Default::default(),
252 attributes: None,
253 lib: Default::default(),
254 };
255
256 let json = serde_json::to_string(&task).unwrap();
257 let back: ExpansionTask = serde_json::from_str(&json).unwrap();
258
259 assert_eq!(task.macro_body, back.macro_body);
260
261 let result = ExpansionResult { expansion: tt.clone() };
262 let json = serde_json::to_string(&result).unwrap();
263 let back: ExpansionResult = serde_json::from_str(&json).unwrap();
264
265 assert_eq!(result, back);
266 }
267}