internal: cleanup proc macro server error handlig

When dealing with proc macros, there are two very different kinds of
errors:

* first, usual errors of "proc macro panicked on this particular input"
* second, the proc macro server might day if the user, eg, kills it

First kind of errors are expected and are a normal output, while the
second kind are genuine IO-errors.

For this reason, we use a curious nested result here: `Result<Result<T,
E1>, E2>` pattern, which is 100% inspired by http://sled.rs/errors.html
This commit is contained in:
Aleksey Kladov 2021-08-31 19:01:39 +03:00
parent 722a2a4690
commit d8a3d6f378
12 changed files with 242 additions and 271 deletions

View file

@ -1,57 +1,53 @@
//! Defines messages for cross-process message passing based on `ndjson` wire protocol
pub(crate) mod flat;
use std::{
convert::TryFrom,
io::{self, BufRead, Write},
path::PathBuf,
};
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use crate::{
rpc::{ListMacrosResult, ListMacrosTask},
ExpansionResult, ExpansionTask,
};
use crate::ProcMacroKind;
pub use crate::msg::flat::FlatTree;
#[derive(Debug, Serialize, Deserialize)]
pub enum Request {
ListMacro(ListMacrosTask),
ExpansionMacro(ExpansionTask),
ListMacros { dylib_path: PathBuf },
ExpandMacro(ExpandMacro),
}
#[derive(Debug, Serialize, Deserialize)]
pub enum Response {
Error(ResponseError),
ListMacro(ListMacrosResult),
ExpansionMacro(ExpansionResult),
ListMacros(Result<Vec<(String, ProcMacroKind)>, String>),
ExpandMacro(Result<FlatTree, PanicMessage>),
}
macro_rules! impl_try_from_response {
($ty:ty, $tag:ident) => {
impl TryFrom<Response> for $ty {
type Error = &'static str;
fn try_from(value: Response) -> Result<Self, Self::Error> {
match value {
Response::$tag(res) => Ok(res),
_ => Err(concat!("Failed to convert response to ", stringify!($tag))),
}
}
}
};
}
#[derive(Debug, Serialize, Deserialize)]
pub struct PanicMessage(pub String);
impl_try_from_response!(ListMacrosResult, ListMacro);
impl_try_from_response!(ExpansionResult, ExpansionMacro);
#[derive(Debug, Serialize, Deserialize)]
pub struct ExpandMacro {
/// Argument of macro call.
///
/// In custom derive this will be a struct or enum; in attribute-like macro - underlying
/// item; in function-like macro - the macro body.
pub macro_body: FlatTree,
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct ResponseError {
pub code: ErrorCode,
pub message: String,
}
/// Name of macro to expand.
///
/// In custom derive this is the name of the derived trait (`Serialize`, `Getters`, etc.).
/// In attribute-like and function-like macros - single name of macro itself (`show_streams`).
pub macro_name: String,
#[derive(Debug, Serialize, Deserialize, Clone)]
pub enum ErrorCode {
ServerErrorEnd,
ExpansionError,
/// Possible attributes for the attribute-like macros.
pub attributes: Option<FlatTree>,
pub lib: PathBuf,
/// Environment variables to set during macro expansion.
pub env: Vec<(String, String)>,
}
pub trait Message: Serialize + DeserializeOwned {
@ -108,3 +104,51 @@ fn write_json(out: &mut impl Write, msg: &str) -> io::Result<()> {
out.flush()?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use tt::*;
fn fixture_token_tree() -> Subtree {
let mut subtree = Subtree::default();
subtree
.token_trees
.push(TokenTree::Leaf(Ident { text: "struct".into(), id: TokenId(0) }.into()));
subtree
.token_trees
.push(TokenTree::Leaf(Ident { text: "Foo".into(), id: TokenId(1) }.into()));
subtree.token_trees.push(TokenTree::Leaf(Leaf::Literal(Literal {
text: "Foo".into(),
id: TokenId::unspecified(),
})));
subtree.token_trees.push(TokenTree::Leaf(Leaf::Punct(Punct {
char: '@',
id: TokenId::unspecified(),
spacing: Spacing::Joint,
})));
subtree.token_trees.push(TokenTree::Subtree(Subtree {
delimiter: Some(Delimiter { id: TokenId(2), kind: DelimiterKind::Brace }),
token_trees: vec![],
}));
subtree
}
#[test]
fn test_proc_macro_rpc_works() {
let tt = fixture_token_tree();
let task = ExpandMacro {
macro_body: FlatTree::new(&tt),
macro_name: Default::default(),
attributes: None,
lib: std::env::current_dir().unwrap(),
env: Default::default(),
};
let json = serde_json::to_string(&task).unwrap();
// println!("{}", json);
let back: ExpandMacro = serde_json::from_str(&json).unwrap();
assert_eq!(tt, back.macro_body.to_subtree());
}
}