//! Bidirectional protocol methods use std::{ io::{self, BufRead, Write}, panic::{AssertUnwindSafe, catch_unwind}, sync::Arc, }; use paths::AbsPath; use span::Span; use crate::{ ProcMacro, ProcMacroKind, ServerError, bidirectional_protocol::msg::{ BidirectionalMessage, ExpandMacro, ExpandMacroData, ExpnGlobals, Request, Response, SubRequest, SubResponse, }, legacy_protocol::{ SpanMode, msg::{ FlatTree, ServerConfig, SpanDataIndexMap, deserialize_span_data_index_map, serialize_span_data_index_map, }, }, process::ProcMacroServerProcess, transport::postcard, }; pub mod msg; pub type SubCallback<'a> = &'a dyn Fn(SubRequest) -> Result; pub fn run_conversation( writer: &mut dyn Write, reader: &mut dyn BufRead, buf: &mut Vec, msg: BidirectionalMessage, callback: SubCallback<'_>, ) -> Result { let encoded = postcard::encode(&msg).map_err(wrap_encode)?; postcard::write(writer, &encoded).map_err(wrap_io("failed to write initial request"))?; loop { let maybe_buf = postcard::read(reader, buf).map_err(wrap_io("failed to read message"))?; let Some(b) = maybe_buf else { return Err(ServerError { message: "proc-macro server closed the stream".into(), io: Some(Arc::new(io::Error::new(io::ErrorKind::UnexpectedEof, "closed"))), }); }; let msg: BidirectionalMessage = postcard::decode(b).map_err(wrap_decode)?; match msg { BidirectionalMessage::Response(response) => { return Ok(BidirectionalMessage::Response(response)); } BidirectionalMessage::SubRequest(sr) => { // TODO: Avoid `AssertUnwindSafe` by making the callback `UnwindSafe` once `ExpandDatabase` // becomes unwind-safe (currently blocked by `parking_lot::RwLock` in the VFS). let resp = match catch_unwind(AssertUnwindSafe(|| callback(sr))) { Ok(Ok(resp)) => BidirectionalMessage::SubResponse(resp), Ok(Err(err)) => BidirectionalMessage::SubResponse(SubResponse::Cancel { reason: err.to_string(), }), Err(_) => BidirectionalMessage::SubResponse(SubResponse::Cancel { reason: "callback panicked or was cancelled".into(), }), }; let encoded = postcard::encode(&resp).map_err(wrap_encode)?; postcard::write(writer, &encoded) .map_err(wrap_io("failed to write sub-response"))?; } _ => { return Err(ServerError { message: format!("unexpected message {:?}", msg), io: None, }); } } } } fn wrap_io(msg: &'static str) -> impl Fn(io::Error) -> ServerError { move |err| ServerError { message: msg.into(), io: Some(Arc::new(err)) } } fn wrap_encode(err: io::Error) -> ServerError { ServerError { message: "failed to encode message".into(), io: Some(Arc::new(err)) } } fn wrap_decode(err: io::Error) -> ServerError { ServerError { message: "failed to decode message".into(), io: Some(Arc::new(err)) } } pub(crate) fn version_check( srv: &ProcMacroServerProcess, callback: SubCallback<'_>, ) -> Result { let request = BidirectionalMessage::Request(Request::ApiVersionCheck {}); let response_payload = run_request(srv, request, callback)?; match response_payload { BidirectionalMessage::Response(Response::ApiVersionCheck(version)) => Ok(version), other => { Err(ServerError { message: format!("unexpected response: {:?}", other), io: None }) } } } /// Enable support for rust-analyzer span mode if the server supports it. pub(crate) fn enable_rust_analyzer_spans( srv: &ProcMacroServerProcess, callback: SubCallback<'_>, ) -> Result { let request = BidirectionalMessage::Request(Request::SetConfig(ServerConfig { span_mode: SpanMode::RustAnalyzer, })); let response_payload = run_request(srv, request, callback)?; match response_payload { BidirectionalMessage::Response(Response::SetConfig(ServerConfig { span_mode })) => { Ok(span_mode) } _ => Err(ServerError { message: "unexpected response".to_owned(), io: None }), } } /// Finds proc-macros in a given dynamic library. pub(crate) fn find_proc_macros( srv: &ProcMacroServerProcess, dylib_path: &AbsPath, callback: SubCallback<'_>, ) -> Result, String>, ServerError> { let request = BidirectionalMessage::Request(Request::ListMacros { dylib_path: dylib_path.to_path_buf().into(), }); let response_payload = run_request(srv, request, callback)?; match response_payload { BidirectionalMessage::Response(Response::ListMacros(it)) => Ok(it), _ => Err(ServerError { message: "unexpected response".to_owned(), io: None }), } } pub(crate) fn expand( proc_macro: &ProcMacro, process: &ProcMacroServerProcess, subtree: tt::SubtreeView<'_>, attr: Option>, env: Vec<(String, String)>, def_site: Span, call_site: Span, mixed_site: Span, current_dir: String, callback: SubCallback<'_>, ) -> Result, crate::ServerError> { let version = process.version(); let mut span_data_table = SpanDataIndexMap::default(); let def_site = span_data_table.insert_full(def_site).0; let call_site = span_data_table.insert_full(call_site).0; let mixed_site = span_data_table.insert_full(mixed_site).0; let task = BidirectionalMessage::Request(Request::ExpandMacro(Box::new(ExpandMacro { data: ExpandMacroData { macro_body: FlatTree::from_subtree(subtree, version, &mut span_data_table), macro_name: proc_macro.name.to_string(), attributes: attr .map(|subtree| FlatTree::from_subtree(subtree, version, &mut span_data_table)), has_global_spans: ExpnGlobals { def_site, call_site, mixed_site }, span_data_table: if process.rust_analyzer_spans() { serialize_span_data_index_map(&span_data_table) } else { Vec::new() }, }, lib: proc_macro.dylib_path.to_path_buf().into(), env, current_dir: Some(current_dir), }))); let response_payload = run_request(process, task, callback)?; match response_payload { BidirectionalMessage::Response(Response::ExpandMacro(it)) => Ok(it .map(|tree| { let mut expanded = FlatTree::to_subtree_resolved(tree, version, &span_data_table); if proc_macro.needs_fixup_change() { proc_macro.change_fixup_to_match_old_server(&mut expanded); } expanded }) .map_err(|msg| msg.0)), BidirectionalMessage::Response(Response::ExpandMacroExtended(it)) => Ok(it .map(|resp| { let mut expanded = FlatTree::to_subtree_resolved( resp.tree, version, &deserialize_span_data_index_map(&resp.span_data_table), ); if proc_macro.needs_fixup_change() { proc_macro.change_fixup_to_match_old_server(&mut expanded); } expanded }) .map_err(|msg| msg.0)), _ => Err(ServerError { message: "unexpected response".to_owned(), io: None }), } } fn run_request( srv: &ProcMacroServerProcess, msg: BidirectionalMessage, callback: SubCallback<'_>, ) -> Result { if let Some(err) = srv.exited() { return Err(err.clone()); } srv.run_bidirectional(msg, callback) } pub fn reject_subrequests(req: SubRequest) -> Result { Err(ServerError { message: format!("{req:?} sub-request not supported here"), io: None }) }