Skip to content

Commit 0a41ccb

Browse files
committed
Add bidirectional messaging proc-macro-srv
1 parent a3f0193 commit 0a41ccb

File tree

26 files changed

+1108
-92
lines changed

26 files changed

+1108
-92
lines changed

Cargo.lock

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/hir-def/src/macro_expansion_tests/mod.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ mod proc_macros;
1616

1717
use std::{any::TypeId, iter, ops::Range, sync};
1818

19-
use base_db::RootQueryDb;
19+
use base_db::{RootQueryDb, SourceDatabase};
2020
use expect_test::Expect;
2121
use hir_expand::{
2222
AstId, InFile, MacroCallId, MacroCallKind, MacroKind,
@@ -374,6 +374,7 @@ struct IdentityWhenValidProcMacroExpander;
374374
impl ProcMacroExpander for IdentityWhenValidProcMacroExpander {
375375
fn expand(
376376
&self,
377+
_: &dyn SourceDatabase,
377378
subtree: &TopSubtree,
378379
_: Option<&TopSubtree>,
379380
_: &base_db::Env,

crates/hir-expand/src/proc_macro.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use core::fmt;
44
use std::any::Any;
55
use std::{panic::RefUnwindSafe, sync};
66

7-
use base_db::{Crate, CrateBuilderId, CratesIdMap, Env, ProcMacroLoadingError};
7+
use base_db::{Crate, CrateBuilderId, CratesIdMap, Env, ProcMacroLoadingError, SourceDatabase};
88
use intern::Symbol;
99
use rustc_hash::FxHashMap;
1010
use span::Span;
@@ -25,6 +25,7 @@ pub trait ProcMacroExpander: fmt::Debug + Send + Sync + RefUnwindSafe + Any {
2525
/// [`ProcMacroKind::Attr`]), environment variables, and span information.
2626
fn expand(
2727
&self,
28+
db: &dyn SourceDatabase,
2829
subtree: &tt::TopSubtree,
2930
attrs: Option<&tt::TopSubtree>,
3031
env: &Env,
@@ -309,6 +310,7 @@ impl CustomProcMacroExpander {
309310
let current_dir = calling_crate.data(db).proc_macro_cwd.to_string();
310311

311312
match proc_macro.expander.expand(
313+
db,
312314
tt,
313315
attr_arg,
314316
env,

crates/load-cargo/src/lib.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@ use hir_expand::proc_macro::{
1111
};
1212
use ide_db::{
1313
ChangeWithProcMacros, FxHashMap, RootDatabase,
14-
base_db::{CrateGraphBuilder, Env, ProcMacroLoadingError, SourceRoot, SourceRootId},
14+
base_db::{
15+
CrateGraphBuilder, Env, ProcMacroLoadingError, SourceDatabase, SourceRoot, SourceRootId,
16+
},
1517
prime_caches,
1618
};
1719
use itertools::Itertools;
@@ -516,6 +518,7 @@ struct Expander(proc_macro_api::ProcMacro);
516518
impl ProcMacroExpander for Expander {
517519
fn expand(
518520
&self,
521+
db: &dyn SourceDatabase,
519522
subtree: &tt::TopSubtree<Span>,
520523
attrs: Option<&tt::TopSubtree<Span>>,
521524
env: &Env,
@@ -525,6 +528,7 @@ impl ProcMacroExpander for Expander {
525528
current_dir: String,
526529
) -> Result<tt::TopSubtree<Span>, ProcMacroExpansionError> {
527530
match self.0.expand(
531+
db,
528532
subtree.view(),
529533
attrs.map(|attrs| attrs.view()),
530534
env.clone().into(),

crates/proc-macro-api/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ serde_json = { workspace = true, features = ["unbounded_depth"] }
1919
tracing.workspace = true
2020
rustc-hash.workspace = true
2121
indexmap.workspace = true
22+
base-db.workspace = true
2223

2324
# local deps
2425
paths = { workspace = true, features = ["serde1"] }
Lines changed: 294 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,294 @@
1+
use std::{
2+
io::{self, BufRead, Write},
3+
sync::Arc,
4+
};
5+
6+
use base_db::SourceDatabase;
7+
use paths::AbsPath;
8+
use span::{FileId, Span};
9+
10+
use crate::{
11+
Codec, ProcMacro, ProcMacroKind, ServerError,
12+
bidirectional_protocol::msg::{
13+
Envelope, ExpandMacro, ExpandMacroData, ExpnGlobals, Kind, Payload, Request, RequestId,
14+
Response, SubRequest, SubResponse,
15+
},
16+
legacy_protocol::{
17+
SpanMode,
18+
msg::{
19+
FlatTree, ServerConfig, SpanDataIndexMap, deserialize_span_data_index_map,
20+
serialize_span_data_index_map,
21+
},
22+
},
23+
process::ProcMacroServerProcess,
24+
transport::codec::{json::JsonProtocol, postcard::PostcardProtocol},
25+
version,
26+
};
27+
28+
pub mod msg;
29+
30+
pub trait ClientCallbacks {
31+
fn handle_sub_request(&mut self, id: u64, req: SubRequest) -> Result<SubResponse, ServerError>;
32+
}
33+
34+
pub fn run_conversation<C: Codec>(
35+
writer: &mut dyn Write,
36+
reader: &mut dyn BufRead,
37+
buf: &mut C::Buf,
38+
id: RequestId,
39+
initial: Payload,
40+
callbacks: &mut dyn ClientCallbacks,
41+
) -> Result<Payload, ServerError> {
42+
let msg = Envelope { id, kind: Kind::Request, payload: initial };
43+
let encoded = C::encode(&msg).map_err(wrap_encode)?;
44+
C::write(writer, &encoded).map_err(wrap_io("failed to write initial request"))?;
45+
46+
loop {
47+
let maybe_buf = C::read(reader, buf).map_err(wrap_io("failed to read message"))?;
48+
let Some(b) = maybe_buf else {
49+
return Err(ServerError {
50+
message: "proc-macro server closed the stream".into(),
51+
io: Some(Arc::new(io::Error::new(io::ErrorKind::UnexpectedEof, "closed"))),
52+
});
53+
};
54+
55+
let msg: Envelope = C::decode(b).map_err(wrap_decode)?;
56+
57+
if msg.id != id {
58+
return Err(ServerError {
59+
message: format!("unexpected message id {}, expected {}", msg.id, id),
60+
io: None,
61+
});
62+
}
63+
64+
match (msg.kind, msg.payload) {
65+
(Kind::SubRequest, Payload::SubRequest(sr)) => {
66+
let resp = callbacks.handle_sub_request(id, sr)?;
67+
let reply =
68+
Envelope { id, kind: Kind::SubResponse, payload: Payload::SubResponse(resp) };
69+
let encoded = C::encode(&reply).map_err(wrap_encode)?;
70+
C::write(writer, &encoded).map_err(wrap_io("failed to write sub-response"))?;
71+
}
72+
(Kind::Response, payload) => {
73+
return Ok(payload);
74+
}
75+
(kind, payload) => {
76+
return Err(ServerError {
77+
message: format!(
78+
"unexpected message kind {:?} with payload {:?}",
79+
kind, payload
80+
),
81+
io: None,
82+
});
83+
}
84+
}
85+
}
86+
}
87+
88+
fn wrap_io(msg: &'static str) -> impl Fn(io::Error) -> ServerError {
89+
move |err| ServerError { message: msg.into(), io: Some(Arc::new(err)) }
90+
}
91+
92+
fn wrap_encode(err: io::Error) -> ServerError {
93+
ServerError { message: "failed to encode message".into(), io: Some(Arc::new(err)) }
94+
}
95+
96+
fn wrap_decode(err: io::Error) -> ServerError {
97+
ServerError { message: "failed to decode message".into(), io: Some(Arc::new(err)) }
98+
}
99+
100+
pub(crate) fn version_check(srv: &ProcMacroServerProcess) -> Result<u32, ServerError> {
101+
let request = Payload::Request(Request::ApiVersionCheck {});
102+
103+
struct NoCallbacks;
104+
impl ClientCallbacks for NoCallbacks {
105+
fn handle_sub_request(
106+
&mut self,
107+
_id: u64,
108+
_req: SubRequest,
109+
) -> Result<SubResponse, ServerError> {
110+
Err(ServerError { message: "sub-request not supported here".into(), io: None })
111+
}
112+
}
113+
114+
let mut callbacks = NoCallbacks;
115+
116+
let response_payload =
117+
run_bidirectional(srv, (0, Kind::Request, request).into(), &mut callbacks)?;
118+
119+
match response_payload {
120+
Payload::Response(Response::ApiVersionCheck(version)) => Ok(version),
121+
other => {
122+
Err(ServerError { message: format!("unexpected response: {:?}", other), io: None })
123+
}
124+
}
125+
}
126+
127+
/// Enable support for rust-analyzer span mode if the server supports it.
128+
pub(crate) fn enable_rust_analyzer_spans(
129+
srv: &ProcMacroServerProcess,
130+
) -> Result<SpanMode, ServerError> {
131+
let request =
132+
Payload::Request(Request::SetConfig(ServerConfig { span_mode: SpanMode::RustAnalyzer }));
133+
134+
struct NoCallbacks;
135+
impl ClientCallbacks for NoCallbacks {
136+
fn handle_sub_request(
137+
&mut self,
138+
_id: u64,
139+
_req: SubRequest,
140+
) -> Result<SubResponse, ServerError> {
141+
Err(ServerError { message: "sub-request not supported here".into(), io: None })
142+
}
143+
}
144+
145+
let mut callbacks = NoCallbacks;
146+
147+
let response_payload =
148+
run_bidirectional(srv, (0, Kind::Request, request).into(), &mut callbacks)?;
149+
150+
match response_payload {
151+
Payload::Response(Response::SetConfig(ServerConfig { span_mode })) => Ok(span_mode),
152+
_ => Err(ServerError { message: "unexpected response".to_owned(), io: None }),
153+
}
154+
}
155+
156+
/// Finds proc-macros in a given dynamic library.
157+
pub(crate) fn find_proc_macros(
158+
srv: &ProcMacroServerProcess,
159+
dylib_path: &AbsPath,
160+
) -> Result<Result<Vec<(String, ProcMacroKind)>, String>, ServerError> {
161+
let request =
162+
Payload::Request(Request::ListMacros { dylib_path: dylib_path.to_path_buf().into() });
163+
164+
struct NoCallbacks;
165+
impl ClientCallbacks for NoCallbacks {
166+
fn handle_sub_request(
167+
&mut self,
168+
_id: u64,
169+
_req: SubRequest,
170+
) -> Result<SubResponse, ServerError> {
171+
Err(ServerError { message: "sub-request not supported here".into(), io: None })
172+
}
173+
}
174+
175+
let mut callbacks = NoCallbacks;
176+
177+
let response_payload =
178+
run_bidirectional(srv, (0, Kind::Request, request).into(), &mut callbacks)?;
179+
180+
match response_payload {
181+
Payload::Response(Response::ListMacros(it)) => Ok(it),
182+
_ => Err(ServerError { message: "unexpected response".to_owned(), io: None }),
183+
}
184+
}
185+
186+
pub(crate) fn expand(
187+
proc_macro: &ProcMacro,
188+
db: &dyn SourceDatabase,
189+
subtree: tt::SubtreeView<'_, Span>,
190+
attr: Option<tt::SubtreeView<'_, Span>>,
191+
env: Vec<(String, String)>,
192+
def_site: Span,
193+
call_site: Span,
194+
mixed_site: Span,
195+
current_dir: String,
196+
) -> Result<Result<tt::TopSubtree<span::SpanData<span::SyntaxContext>>, String>, crate::ServerError>
197+
{
198+
let version = proc_macro.process.version();
199+
let mut span_data_table = SpanDataIndexMap::default();
200+
let def_site = span_data_table.insert_full(def_site).0;
201+
let call_site = span_data_table.insert_full(call_site).0;
202+
let mixed_site = span_data_table.insert_full(mixed_site).0;
203+
let task = Payload::Request(Request::ExpandMacro(Box::new(ExpandMacro {
204+
data: ExpandMacroData {
205+
macro_body: FlatTree::from_subtree(subtree, version, &mut span_data_table),
206+
macro_name: proc_macro.name.to_string(),
207+
attributes: attr
208+
.map(|subtree| FlatTree::from_subtree(subtree, version, &mut span_data_table)),
209+
has_global_spans: ExpnGlobals {
210+
serialize: version >= version::HAS_GLOBAL_SPANS,
211+
def_site,
212+
call_site,
213+
mixed_site,
214+
},
215+
span_data_table: if proc_macro.process.rust_analyzer_spans() {
216+
serialize_span_data_index_map(&span_data_table)
217+
} else {
218+
Vec::new()
219+
},
220+
},
221+
lib: proc_macro.dylib_path.to_path_buf().into(),
222+
env,
223+
current_dir: Some(current_dir),
224+
})));
225+
226+
struct Callbacks<'de> {
227+
db: &'de dyn SourceDatabase,
228+
}
229+
impl<'db> ClientCallbacks for Callbacks<'db> {
230+
fn handle_sub_request(
231+
&mut self,
232+
_id: u64,
233+
req: SubRequest,
234+
) -> Result<SubResponse, ServerError> {
235+
match req {
236+
SubRequest::SourceText { file_id, start, end } => {
237+
let file = FileId::from_raw(file_id);
238+
let text = self.db.file_text(file).text(self.db);
239+
240+
let slice = text.get(start as usize..end as usize).map(|s| s.to_owned());
241+
242+
Ok(SubResponse::SourceTextResult { text: slice })
243+
}
244+
}
245+
}
246+
}
247+
248+
let mut callbacks = Callbacks { db };
249+
250+
let response_payload =
251+
run_bidirectional(&proc_macro.process, (0, Kind::Request, task).into(), &mut callbacks)?;
252+
253+
match response_payload {
254+
Payload::Response(Response::ExpandMacro(it)) => Ok(it
255+
.map(|tree| {
256+
let mut expanded = FlatTree::to_subtree_resolved(tree, version, &span_data_table);
257+
if proc_macro.needs_fixup_change() {
258+
proc_macro.change_fixup_to_match_old_server(&mut expanded);
259+
}
260+
expanded
261+
})
262+
.map_err(|msg| msg.0)),
263+
Payload::Response(Response::ExpandMacroExtended(it)) => Ok(it
264+
.map(|resp| {
265+
let mut expanded = FlatTree::to_subtree_resolved(
266+
resp.tree,
267+
version,
268+
&deserialize_span_data_index_map(&resp.span_data_table),
269+
);
270+
if proc_macro.needs_fixup_change() {
271+
proc_macro.change_fixup_to_match_old_server(&mut expanded);
272+
}
273+
expanded
274+
})
275+
.map_err(|msg| msg.0)),
276+
_ => Err(ServerError { message: "unexpected response".to_owned(), io: None }),
277+
}
278+
}
279+
280+
fn run_bidirectional(
281+
srv: &ProcMacroServerProcess,
282+
msg: Envelope,
283+
callbacks: &mut dyn ClientCallbacks,
284+
) -> Result<Payload, ServerError> {
285+
if let Some(server_error) = srv.exited() {
286+
return Err(server_error.clone());
287+
}
288+
289+
if srv.use_postcard() {
290+
srv.run_bidirectional::<PostcardProtocol>(msg.id, msg.payload, callbacks)
291+
} else {
292+
srv.run_bidirectional::<JsonProtocol>(msg.id, msg.payload, callbacks)
293+
}
294+
}

0 commit comments

Comments
 (0)