| //! The main loop of the proc-macro server. | |
| use proc_macro_api::bidirectional_protocol::msg::{ApiVersionCheck, ListMacros}; | |
| use proc_macro_api::{ | |
| ProtocolFormat, bidirectional_protocol::msg as bidirectional, legacy_protocol::msg as legacy, | |
| version::CURRENT_API_VERSION, | |
| }; | |
| use std::panic::{panic_any, resume_unwind}; | |
| use std::{ | |
| io::{self, BufRead, Write}, | |
| ops::Range, | |
| }; | |
| use legacy::Message; | |
| use proc_macro_srv::{EnvSnapshot, ProcMacroClientError, ProcMacroPanicMarker, SpanId}; | |
| struct SpanTrans; | |
| impl legacy::SpanTransformer for SpanTrans { | |
| type Table = (); | |
| type Span = SpanId; | |
| fn token_id_of( | |
| _: &mut Self::Table, | |
| span: Self::Span, | |
| ) -> proc_macro_api::legacy_protocol::SpanId { | |
| proc_macro_api::legacy_protocol::SpanId(span.0) | |
| } | |
| fn span_for_token_id( | |
| _: &Self::Table, | |
| id: proc_macro_api::legacy_protocol::SpanId, | |
| ) -> Self::Span { | |
| SpanId(id.0) | |
| } | |
| } | |
| pub fn run( | |
| stdin: &mut (dyn BufRead + Send + Sync), | |
| stdout: &mut (dyn Write + Send + Sync), | |
| format: ProtocolFormat, | |
| ) -> io::Result<()> { | |
| match format { | |
| ProtocolFormat::JsonLegacy => run_old(stdin, stdout), | |
| ProtocolFormat::BidirectionalPostcardPrototype => run_new(stdin, stdout), | |
| } | |
| } | |
| fn run_new( | |
| stdin: &mut (dyn BufRead + Send + Sync), | |
| stdout: &mut (dyn Write + Send + Sync), | |
| ) -> io::Result<()> { | |
| fn macro_kind_to_api(kind: proc_macro_srv::ProcMacroKind) -> proc_macro_api::ProcMacroKind { | |
| match kind { | |
| proc_macro_srv::ProcMacroKind::CustomDerive => { | |
| proc_macro_api::ProcMacroKind::CustomDerive | |
| } | |
| proc_macro_srv::ProcMacroKind::Bang => proc_macro_api::ProcMacroKind::Bang, | |
| proc_macro_srv::ProcMacroKind::Attr => proc_macro_api::ProcMacroKind::Attr, | |
| } | |
| } | |
| let mut buf = Vec::default(); | |
| let env_snapshot = EnvSnapshot::default(); | |
| let srv = proc_macro_srv::ProcMacroSrv::new(&env_snapshot); | |
| let mut span_mode = legacy::SpanMode::Id; | |
| 'outer: loop { | |
| let req_opt = bidirectional::BidirectionalMessage::read(stdin, &mut buf)?; | |
| let Some(req) = req_opt else { | |
| break 'outer; | |
| }; | |
| match req { | |
| bidirectional::BidirectionalMessage::Request(request) => match request { | |
| bidirectional::Request::ListMacros(ListMacros { dylib_path }) => { | |
| let res = srv.list_macros(&dylib_path).map(|macros| { | |
| macros | |
| .into_iter() | |
| .map(|(name, kind)| (name, macro_kind_to_api(kind))) | |
| .collect() | |
| }); | |
| send_response(stdout, bidirectional::Response::ListMacros(res))?; | |
| } | |
| bidirectional::Request::ApiVersionCheck(ApiVersionCheck {}) => { | |
| send_response( | |
| stdout, | |
| bidirectional::Response::ApiVersionCheck(CURRENT_API_VERSION), | |
| )?; | |
| } | |
| bidirectional::Request::SetConfig(config) => { | |
| span_mode = config.span_mode; | |
| send_response(stdout, bidirectional::Response::SetConfig(config))?; | |
| } | |
| bidirectional::Request::ExpandMacro(task) => { | |
| handle_expand(&srv, stdin, stdout, &mut buf, span_mode, *task)?; | |
| } | |
| }, | |
| _ => continue, | |
| } | |
| } | |
| Ok(()) | |
| } | |
| fn handle_expand( | |
| srv: &proc_macro_srv::ProcMacroSrv<'_>, | |
| stdin: &mut (dyn BufRead + Send + Sync), | |
| stdout: &mut (dyn Write + Send + Sync), | |
| buf: &mut Vec<u8>, | |
| span_mode: legacy::SpanMode, | |
| task: bidirectional::ExpandMacro, | |
| ) -> io::Result<()> { | |
| match span_mode { | |
| legacy::SpanMode::Id => handle_expand_id(srv, stdout, task), | |
| legacy::SpanMode::RustAnalyzer => handle_expand_ra(srv, stdin, stdout, buf, task), | |
| } | |
| } | |
| fn handle_expand_id( | |
| srv: &proc_macro_srv::ProcMacroSrv<'_>, | |
| stdout: &mut dyn Write, | |
| task: bidirectional::ExpandMacro, | |
| ) -> io::Result<()> { | |
| let bidirectional::ExpandMacro { lib, env, current_dir, data } = task; | |
| let bidirectional::ExpandMacroData { | |
| macro_body, | |
| macro_name, | |
| attributes, | |
| has_global_spans: bidirectional::ExpnGlobals { def_site, call_site, mixed_site, .. }, | |
| .. | |
| } = data; | |
| let def_site = SpanId(def_site as u32); | |
| let call_site = SpanId(call_site as u32); | |
| let mixed_site = SpanId(mixed_site as u32); | |
| let macro_body = | |
| macro_body.to_tokenstream_unresolved::<SpanTrans>(CURRENT_API_VERSION, |_, b| b); | |
| let attributes = attributes | |
| .map(|it| it.to_tokenstream_unresolved::<SpanTrans>(CURRENT_API_VERSION, |_, b| b)); | |
| let mut tracked_env = Default::default(); | |
| let res = srv | |
| .expand( | |
| lib, | |
| &env, | |
| current_dir, | |
| ¯o_name, | |
| macro_body, | |
| attributes, | |
| def_site, | |
| call_site, | |
| mixed_site, | |
| &mut tracked_env, | |
| None, | |
| ) | |
| .map(|it| { | |
| legacy::FlatTree::from_tokenstream_raw::<SpanTrans>(it, call_site, CURRENT_API_VERSION) | |
| }) | |
| .map(|tree| bidirectional::ExpandMacroResponse { | |
| tree, | |
| span_data_table: vec![], | |
| tracked_env_vars: tracked_env.env_vars, | |
| tracked_paths: tracked_env.paths, | |
| }) | |
| .map_err(|e| legacy::PanicMessage(e.into_string().unwrap_or_default())); | |
| send_response(stdout, bidirectional::Response::ExpandMacro(res)) | |
| } | |
| struct ProcMacroClientHandle<'a> { | |
| stdin: &'a mut (dyn BufRead + Send + Sync), | |
| stdout: &'a mut (dyn Write + Send + Sync), | |
| buf: &'a mut Vec<u8>, | |
| } | |
| impl<'a> ProcMacroClientHandle<'a> { | |
| fn roundtrip( | |
| &mut self, | |
| req: bidirectional::SubRequest, | |
| ) -> Result<bidirectional::SubResponse, ProcMacroClientError> { | |
| let msg = bidirectional::BidirectionalMessage::SubRequest(req); | |
| msg.write(&mut *self.stdout).map_err(ProcMacroClientError::Io)?; | |
| let msg = bidirectional::BidirectionalMessage::read(&mut *self.stdin, self.buf) | |
| .map_err(ProcMacroClientError::Io)? | |
| .ok_or(ProcMacroClientError::Eof)?; | |
| match msg { | |
| bidirectional::BidirectionalMessage::SubResponse(resp) => match resp { | |
| bidirectional::SubResponse::Cancel { reason } => { | |
| Err(ProcMacroClientError::Cancelled { reason }) | |
| } | |
| other => Ok(other), | |
| }, | |
| other => { | |
| Err(ProcMacroClientError::Protocol(format!("expected SubResponse, got {other:?}"))) | |
| } | |
| } | |
| } | |
| } | |
| fn handle_failure(failure: Result<bidirectional::SubResponse, ProcMacroClientError>) -> ! { | |
| match failure { | |
| Err(ProcMacroClientError::Cancelled { reason }) => { | |
| resume_unwind(Box::new(ProcMacroPanicMarker::Cancelled { reason })); | |
| } | |
| Err(err) => { | |
| panic_any(ProcMacroPanicMarker::Internal { | |
| reason: format!("proc-macro IPC error: {err:?}"), | |
| }); | |
| } | |
| Ok(other) => { | |
| panic_any(ProcMacroPanicMarker::Internal { | |
| reason: format!("unexpected SubResponse {other:?}"), | |
| }); | |
| } | |
| } | |
| } | |
| impl proc_macro_srv::ProcMacroClientInterface for ProcMacroClientHandle<'_> { | |
| fn file(&mut self, file_id: proc_macro_srv::span::FileId) -> String { | |
| match self.roundtrip(bidirectional::SubRequest::FilePath { file_id: file_id.index() }) { | |
| Ok(bidirectional::SubResponse::FilePathResult { name }) => name, | |
| other => handle_failure(other), | |
| } | |
| } | |
| fn source_text( | |
| &mut self, | |
| proc_macro_srv::span::Span { range, anchor, ctx: _ }: proc_macro_srv::span::Span, | |
| ) -> Option<String> { | |
| match self.roundtrip(bidirectional::SubRequest::SourceText { | |
| file_id: anchor.file_id.as_u32(), | |
| ast_id: anchor.ast_id.into_raw(), | |
| start: range.start().into(), | |
| end: range.end().into(), | |
| }) { | |
| Ok(bidirectional::SubResponse::SourceTextResult { text }) => text, | |
| other => handle_failure(other), | |
| } | |
| } | |
| fn local_file(&mut self, file_id: proc_macro_srv::span::FileId) -> Option<String> { | |
| match self.roundtrip(bidirectional::SubRequest::LocalFilePath { file_id: file_id.index() }) | |
| { | |
| Ok(bidirectional::SubResponse::LocalFilePathResult { name }) => name, | |
| other => handle_failure(other), | |
| } | |
| } | |
| fn line_column(&mut self, span: proc_macro_srv::span::Span) -> Option<(u32, u32)> { | |
| let proc_macro_srv::span::Span { range, anchor, ctx: _ } = span; | |
| match self.roundtrip(bidirectional::SubRequest::LineColumn { | |
| file_id: anchor.file_id.as_u32(), | |
| ast_id: anchor.ast_id.into_raw(), | |
| offset: range.start().into(), | |
| }) { | |
| Ok(bidirectional::SubResponse::LineColumnResult { line, column }) => { | |
| Some((line, column)) | |
| } | |
| other => handle_failure(other), | |
| } | |
| } | |
| fn byte_range( | |
| &mut self, | |
| proc_macro_srv::span::Span { range, anchor, ctx: _ }: proc_macro_srv::span::Span, | |
| ) -> Range<usize> { | |
| match self.roundtrip(bidirectional::SubRequest::ByteRange { | |
| file_id: anchor.file_id.as_u32(), | |
| ast_id: anchor.ast_id.into_raw(), | |
| start: range.start().into(), | |
| end: range.end().into(), | |
| }) { | |
| Ok(bidirectional::SubResponse::ByteRangeResult { range }) => range, | |
| other => handle_failure(other), | |
| } | |
| } | |
| fn span_source( | |
| &mut self, | |
| proc_macro_srv::span::Span { range, anchor, ctx }: proc_macro_srv::span::Span, | |
| ) -> proc_macro_srv::span::Span { | |
| match self.roundtrip(bidirectional::SubRequest::SpanSource { | |
| file_id: anchor.file_id.as_u32(), | |
| ast_id: anchor.ast_id.into_raw(), | |
| start: range.start().into(), | |
| end: range.end().into(), | |
| ctx: ctx.into_u32(), | |
| }) { | |
| Ok(bidirectional::SubResponse::SpanSourceResult { | |
| file_id, | |
| ast_id, | |
| start, | |
| end, | |
| ctx, | |
| }) => { | |
| proc_macro_srv::span::Span { | |
| range: proc_macro_srv::span::TextRange::new( | |
| proc_macro_srv::span::TextSize::new(start), | |
| proc_macro_srv::span::TextSize::new(end), | |
| ), | |
| anchor: proc_macro_srv::span::SpanAnchor { | |
| file_id: proc_macro_srv::span::EditionedFileId::from_raw(file_id), | |
| ast_id: proc_macro_srv::span::ErasedFileAstId::from_raw(ast_id), | |
| }, | |
| // SAFETY: We only receive spans from the server. If someone mess up the communication UB can happen, | |
| // but that will be their problem. | |
| ctx: unsafe { proc_macro_srv::span::SyntaxContext::from_u32(ctx) }, | |
| } | |
| } | |
| other => handle_failure(other), | |
| } | |
| } | |
| fn span_parent( | |
| &mut self, | |
| proc_macro_srv::span::Span { range, anchor, ctx }: proc_macro_srv::span::Span, | |
| ) -> Option<proc_macro_srv::span::Span> { | |
| let response = self.roundtrip(bidirectional::SubRequest::SpanParent { | |
| file_id: anchor.file_id.as_u32(), | |
| ast_id: anchor.ast_id.into_raw(), | |
| start: range.start().into(), | |
| end: range.end().into(), | |
| ctx: ctx.into_u32(), | |
| }); | |
| match response { | |
| Ok(bidirectional::SubResponse::SpanParentResult { parent_span }) => { | |
| parent_span.map(|bidirectional::ParentSpan { file_id, ast_id, start, end, ctx }| { | |
| proc_macro_srv::span::Span { | |
| range: proc_macro_srv::span::TextRange::new( | |
| proc_macro_srv::span::TextSize::new(start), | |
| proc_macro_srv::span::TextSize::new(end), | |
| ), | |
| anchor: proc_macro_srv::span::SpanAnchor { | |
| file_id: proc_macro_srv::span::EditionedFileId::from_raw(file_id), | |
| ast_id: proc_macro_srv::span::ErasedFileAstId::from_raw(ast_id), | |
| }, | |
| // SAFETY: spans originate from the server. If the protocol is violated, | |
| // undefined behavior is the caller’s responsibility. | |
| ctx: unsafe { proc_macro_srv::span::SyntaxContext::from_u32(ctx) }, | |
| } | |
| }) | |
| } | |
| other => handle_failure(other), | |
| } | |
| } | |
| fn span_join( | |
| &mut self, | |
| first: proc_macro_srv::span::Span, | |
| second: proc_macro_srv::span::Span, | |
| ) -> Option<proc_macro_srv::span::Span> { | |
| assert_eq!(first.anchor.file_id, second.anchor.file_id); | |
| let response = self.roundtrip(bidirectional::SubRequest::SpanJoin { | |
| file_id: first.anchor.file_id.as_u32(), | |
| ast_id_first: first.anchor.ast_id.into_raw(), | |
| start_first: first.range.start().into(), | |
| end_first: first.range.end().into(), | |
| ctx_first: first.ctx.into_u32(), | |
| ast_id_second: second.anchor.ast_id.into_raw(), | |
| start_second: second.range.start().into(), | |
| end_second: second.range.end().into(), | |
| ctx_second: second.ctx.into_u32(), | |
| }); | |
| match response { | |
| Ok(bidirectional::SubResponse::SpanJoinResult { span }) => { | |
| span.map(|bidirectional::SpanJoin { ast_id, start, end, ctx }| { | |
| proc_macro_srv::span::Span { | |
| range: proc_macro_srv::span::TextRange::new( | |
| proc_macro_srv::span::TextSize::new(start), | |
| proc_macro_srv::span::TextSize::new(end), | |
| ), | |
| anchor: proc_macro_srv::span::SpanAnchor { | |
| file_id: first.anchor.file_id, | |
| ast_id: proc_macro_srv::span::ErasedFileAstId::from_raw(ast_id), | |
| }, | |
| // SAFETY: spans originate from the server. If the protocol is violated, | |
| // undefined behavior is the caller’s responsibility. | |
| ctx: unsafe { proc_macro_srv::span::SyntaxContext::from_u32(ctx) }, | |
| } | |
| }) | |
| } | |
| other => handle_failure(other), | |
| } | |
| } | |
| } | |
| fn handle_expand_ra( | |
| srv: &proc_macro_srv::ProcMacroSrv<'_>, | |
| stdin: &mut (dyn BufRead + Send + Sync), | |
| stdout: &mut (dyn Write + Send + Sync), | |
| buf: &mut Vec<u8>, | |
| task: bidirectional::ExpandMacro, | |
| ) -> io::Result<()> { | |
| let bidirectional::ExpandMacro { | |
| lib, | |
| env, | |
| current_dir, | |
| data: | |
| bidirectional::ExpandMacroData { | |
| macro_body, | |
| macro_name, | |
| attributes, | |
| has_global_spans: bidirectional::ExpnGlobals { def_site, call_site, mixed_site, .. }, | |
| span_data_table, | |
| }, | |
| } = task; | |
| let mut span_data_table = legacy::deserialize_span_data_index_map(&span_data_table); | |
| let def_site = span_data_table[def_site]; | |
| let call_site = span_data_table[call_site]; | |
| let mixed_site = span_data_table[mixed_site]; | |
| let macro_body = | |
| macro_body.to_tokenstream_resolved(CURRENT_API_VERSION, &span_data_table, |a, b| { | |
| srv.join_spans(a, b).unwrap_or(b) | |
| }); | |
| let attributes = attributes.map(|it| { | |
| it.to_tokenstream_resolved(CURRENT_API_VERSION, &span_data_table, |a, b| { | |
| srv.join_spans(a, b).unwrap_or(b) | |
| }) | |
| }); | |
| let mut tracked_env = Default::default(); | |
| let res = srv | |
| .expand( | |
| lib, | |
| &env, | |
| current_dir, | |
| ¯o_name, | |
| macro_body, | |
| attributes, | |
| def_site, | |
| call_site, | |
| mixed_site, | |
| &mut tracked_env, | |
| Some(&mut ProcMacroClientHandle { stdin, stdout, buf }), | |
| ) | |
| .map(|it| { | |
| ( | |
| legacy::FlatTree::from_tokenstream( | |
| it, | |
| CURRENT_API_VERSION, | |
| call_site, | |
| &mut span_data_table, | |
| ), | |
| legacy::serialize_span_data_index_map(&span_data_table), | |
| ) | |
| }) | |
| .map(|(tree, span_data_table)| bidirectional::ExpandMacroResponse { | |
| tree, | |
| span_data_table, | |
| tracked_env_vars: tracked_env.env_vars, | |
| tracked_paths: tracked_env.paths, | |
| }) | |
| .map_err(|e| legacy::PanicMessage(e.into_string().unwrap_or_default())); | |
| send_response(stdout, bidirectional::Response::ExpandMacro(res)) | |
| } | |
| fn run_old( | |
| stdin: &mut (dyn BufRead + Send + Sync), | |
| stdout: &mut (dyn Write + Send + Sync), | |
| ) -> io::Result<()> { | |
| fn macro_kind_to_api(kind: proc_macro_srv::ProcMacroKind) -> proc_macro_api::ProcMacroKind { | |
| match kind { | |
| proc_macro_srv::ProcMacroKind::CustomDerive => { | |
| proc_macro_api::ProcMacroKind::CustomDerive | |
| } | |
| proc_macro_srv::ProcMacroKind::Bang => proc_macro_api::ProcMacroKind::Bang, | |
| proc_macro_srv::ProcMacroKind::Attr => proc_macro_api::ProcMacroKind::Attr, | |
| } | |
| } | |
| let mut buf = String::default(); | |
| let mut read_request = || legacy::Request::read(stdin, &mut buf); | |
| let mut write_response = |msg: legacy::Response| msg.write(stdout); | |
| let env = EnvSnapshot::default(); | |
| let srv = proc_macro_srv::ProcMacroSrv::new(&env); | |
| let mut span_mode = legacy::SpanMode::Id; | |
| while let Some(req) = read_request()? { | |
| let res = match req { | |
| legacy::Request::ListMacros { dylib_path } => { | |
| legacy::Response::ListMacros(srv.list_macros(&dylib_path).map(|macros| { | |
| macros.into_iter().map(|(name, kind)| (name, macro_kind_to_api(kind))).collect() | |
| })) | |
| } | |
| legacy::Request::ExpandMacro(task) => { | |
| let legacy::ExpandMacro { | |
| lib, | |
| env, | |
| current_dir, | |
| data: | |
| legacy::ExpandMacroData { | |
| macro_body, | |
| macro_name, | |
| attributes, | |
| has_global_spans: | |
| legacy::ExpnGlobals { serialize: _, def_site, call_site, mixed_site }, | |
| span_data_table, | |
| }, | |
| } = *task; | |
| match span_mode { | |
| legacy::SpanMode::Id => legacy::Response::ExpandMacro({ | |
| let def_site = SpanId(def_site as u32); | |
| let call_site = SpanId(call_site as u32); | |
| let mixed_site = SpanId(mixed_site as u32); | |
| let macro_body = macro_body | |
| .to_tokenstream_unresolved::<SpanTrans>(CURRENT_API_VERSION, |_, b| b); | |
| let attributes = attributes.map(|it| { | |
| it.to_tokenstream_unresolved::<SpanTrans>(CURRENT_API_VERSION, |_, b| b) | |
| }); | |
| srv.expand( | |
| lib, | |
| &env, | |
| current_dir, | |
| ¯o_name, | |
| macro_body, | |
| attributes, | |
| def_site, | |
| call_site, | |
| mixed_site, | |
| &mut Default::default(), | |
| None, | |
| ) | |
| .map(|it| { | |
| legacy::FlatTree::from_tokenstream_raw::<SpanTrans>( | |
| it, | |
| call_site, | |
| CURRENT_API_VERSION, | |
| ) | |
| }) | |
| .map_err(|e| e.into_string().unwrap_or_default()) | |
| .map_err(legacy::PanicMessage) | |
| }), | |
| legacy::SpanMode::RustAnalyzer => legacy::Response::ExpandMacroExtended({ | |
| let mut span_data_table = | |
| legacy::deserialize_span_data_index_map(&span_data_table); | |
| let def_site = span_data_table[def_site]; | |
| let call_site = span_data_table[call_site]; | |
| let mixed_site = span_data_table[mixed_site]; | |
| let macro_body = macro_body.to_tokenstream_resolved( | |
| CURRENT_API_VERSION, | |
| &span_data_table, | |
| |a, b| srv.join_spans(a, b).unwrap_or(b), | |
| ); | |
| let attributes = attributes.map(|it| { | |
| it.to_tokenstream_resolved( | |
| CURRENT_API_VERSION, | |
| &span_data_table, | |
| |a, b| srv.join_spans(a, b).unwrap_or(b), | |
| ) | |
| }); | |
| srv.expand( | |
| lib, | |
| &env, | |
| current_dir, | |
| ¯o_name, | |
| macro_body, | |
| attributes, | |
| def_site, | |
| call_site, | |
| mixed_site, | |
| &mut Default::default(), | |
| None, | |
| ) | |
| .map(|it| { | |
| ( | |
| legacy::FlatTree::from_tokenstream( | |
| it, | |
| CURRENT_API_VERSION, | |
| call_site, | |
| &mut span_data_table, | |
| ), | |
| legacy::serialize_span_data_index_map(&span_data_table), | |
| ) | |
| }) | |
| .map(|(tree, span_data_table)| legacy::ExpandMacroExtended { | |
| tree, | |
| span_data_table, | |
| }) | |
| .map_err(|e| e.into_string().unwrap_or_default()) | |
| .map_err(legacy::PanicMessage) | |
| }), | |
| } | |
| } | |
| legacy::Request::ApiVersionCheck {} => { | |
| legacy::Response::ApiVersionCheck(CURRENT_API_VERSION) | |
| } | |
| legacy::Request::SetConfig(config) => { | |
| span_mode = config.span_mode; | |
| legacy::Response::SetConfig(config) | |
| } | |
| }; | |
| write_response(res)? | |
| } | |
| Ok(()) | |
| } | |
| fn send_response(stdout: &mut dyn Write, resp: bidirectional::Response) -> io::Result<()> { | |
| let resp = bidirectional::BidirectionalMessage::Response(resp); | |
| resp.write(stdout) | |
| } |