]> git.proxmox.com Git - rustc.git/blame - src/tools/rust-analyzer/crates/rust-analyzer/src/dispatch.rs
New upstream version 1.70.0+dfsg1
[rustc.git] / src / tools / rust-analyzer / crates / rust-analyzer / src / dispatch.rs
CommitLineData
064997fb
FG
1//! See [RequestDispatcher].
2use std::{fmt, panic, thread};
3
4use ide::Cancelled;
5use lsp_server::ExtractError;
6use serde::{de::DeserializeOwned, Serialize};
7
8use crate::{
9 global_state::{GlobalState, GlobalStateSnapshot},
10 main_loop::Task,
11 version::version,
12 LspError, Result,
13};
14
15/// A visitor for routing a raw JSON request to an appropriate handler function.
16///
17/// Most requests are read-only and async and are handled on the threadpool
18/// (`on` method).
19///
20/// Some read-only requests are latency sensitive, and are immediately handled
21/// on the main loop thread (`on_sync`). These are typically typing-related
22/// requests.
23///
24/// Some requests modify the state, and are run on the main thread to get
25/// `&mut` (`on_sync_mut`).
26///
27/// Read-only requests are wrapped into `catch_unwind` -- they don't modify the
28/// state, so it's OK to recover from their failures.
29pub(crate) struct RequestDispatcher<'a> {
30 pub(crate) req: Option<lsp_server::Request>,
31 pub(crate) global_state: &'a mut GlobalState,
32}
33
34impl<'a> RequestDispatcher<'a> {
35 /// Dispatches the request onto the current thread, given full access to
36 /// mutable global state. Unlike all other methods here, this one isn't
37 /// guarded by `catch_unwind`, so, please, don't make bugs :-)
38 pub(crate) fn on_sync_mut<R>(
39 &mut self,
40 f: fn(&mut GlobalState, R::Params) -> Result<R::Result>,
41 ) -> &mut Self
42 where
43 R: lsp_types::request::Request,
44 R::Params: DeserializeOwned + panic::UnwindSafe + fmt::Debug,
45 R::Result: Serialize,
46 {
47 let (req, params, panic_context) = match self.parse::<R>() {
48 Some(it) => it,
49 None => return self,
50 };
51 let result = {
52 let _pctx = stdx::panic_context::enter(panic_context);
53 f(self.global_state, params)
54 };
2b03887a 55 if let Ok(response) = result_to_response::<R>(req.id, result) {
064997fb
FG
56 self.global_state.respond(response);
57 }
58
59 self
60 }
61
62 /// Dispatches the request onto the current thread.
63 pub(crate) fn on_sync<R>(
64 &mut self,
65 f: fn(GlobalStateSnapshot, R::Params) -> Result<R::Result>,
66 ) -> &mut Self
67 where
68 R: lsp_types::request::Request,
69 R::Params: DeserializeOwned + panic::UnwindSafe + fmt::Debug,
70 R::Result: Serialize,
71 {
72 let (req, params, panic_context) = match self.parse::<R>() {
73 Some(it) => it,
74 None => return self,
75 };
76 let global_state_snapshot = self.global_state.snapshot();
77
78 let result = panic::catch_unwind(move || {
79 let _pctx = stdx::panic_context::enter(panic_context);
80 f(global_state_snapshot, params)
81 });
82
2b03887a 83 if let Ok(response) = thread_result_to_response::<R>(req.id, result) {
064997fb
FG
84 self.global_state.respond(response);
85 }
86
87 self
88 }
89
353b0b11
FG
90 /// Dispatches the request onto thread pool
91 pub(crate) fn on_no_retry<R>(
92 &mut self,
93 f: fn(GlobalStateSnapshot, R::Params) -> Result<R::Result>,
94 ) -> &mut Self
95 where
96 R: lsp_types::request::Request + 'static,
97 R::Params: DeserializeOwned + panic::UnwindSafe + Send + fmt::Debug,
98 R::Result: Serialize,
99 {
100 let (req, params, panic_context) = match self.parse::<R>() {
101 Some(it) => it,
102 None => return self,
103 };
104
105 self.global_state.task_pool.handle.spawn({
106 let world = self.global_state.snapshot();
107 move || {
108 let result = panic::catch_unwind(move || {
109 let _pctx = stdx::panic_context::enter(panic_context);
110 f(world, params)
111 });
112 match thread_result_to_response::<R>(req.id.clone(), result) {
113 Ok(response) => Task::Response(response),
114 Err(_) => Task::Response(lsp_server::Response::new_err(
115 req.id,
116 lsp_server::ErrorCode::ContentModified as i32,
117 "content modified".to_string(),
118 )),
119 }
120 }
121 });
122
123 self
124 }
125
064997fb
FG
126 /// Dispatches the request onto thread pool
127 pub(crate) fn on<R>(
128 &mut self,
129 f: fn(GlobalStateSnapshot, R::Params) -> Result<R::Result>,
130 ) -> &mut Self
131 where
132 R: lsp_types::request::Request + 'static,
133 R::Params: DeserializeOwned + panic::UnwindSafe + Send + fmt::Debug,
134 R::Result: Serialize,
135 {
136 let (req, params, panic_context) = match self.parse::<R>() {
137 Some(it) => it,
138 None => return self,
139 };
140
141 self.global_state.task_pool.handle.spawn({
142 let world = self.global_state.snapshot();
143 move || {
144 let result = panic::catch_unwind(move || {
145 let _pctx = stdx::panic_context::enter(panic_context);
146 f(world, params)
147 });
148 match thread_result_to_response::<R>(req.id.clone(), result) {
149 Ok(response) => Task::Response(response),
150 Err(_) => Task::Retry(req),
151 }
152 }
153 });
154
155 self
156 }
157
158 pub(crate) fn finish(&mut self) {
159 if let Some(req) = self.req.take() {
160 tracing::error!("unknown request: {:?}", req);
161 let response = lsp_server::Response::new_err(
162 req.id,
163 lsp_server::ErrorCode::MethodNotFound as i32,
164 "unknown request".to_string(),
165 );
166 self.global_state.respond(response);
167 }
168 }
169
170 fn parse<R>(&mut self) -> Option<(lsp_server::Request, R::Params, String)>
171 where
172 R: lsp_types::request::Request,
173 R::Params: DeserializeOwned + fmt::Debug,
174 {
175 let req = match &self.req {
176 Some(req) if req.method == R::METHOD => self.req.take()?,
177 _ => return None,
178 };
179
180 let res = crate::from_json(R::METHOD, &req.params);
181 match res {
182 Ok(params) => {
183 let panic_context =
9c376795 184 format!("\nversion: {}\nrequest: {} {params:#?}", version(), R::METHOD);
064997fb
FG
185 Some((req, params, panic_context))
186 }
187 Err(err) => {
188 let response = lsp_server::Response::new_err(
189 req.id,
190 lsp_server::ErrorCode::InvalidParams as i32,
191 err.to_string(),
192 );
193 self.global_state.respond(response);
194 None
195 }
196 }
197 }
198}
199
200fn thread_result_to_response<R>(
201 id: lsp_server::RequestId,
202 result: thread::Result<Result<R::Result>>,
203) -> Result<lsp_server::Response, Cancelled>
204where
205 R: lsp_types::request::Request,
206 R::Params: DeserializeOwned,
207 R::Result: Serialize,
208{
209 match result {
210 Ok(result) => result_to_response::<R>(id, result),
211 Err(panic) => {
212 let panic_message = panic
213 .downcast_ref::<String>()
214 .map(String::as_str)
215 .or_else(|| panic.downcast_ref::<&str>().copied());
216
217 let mut message = "request handler panicked".to_string();
218 if let Some(panic_message) = panic_message {
219 message.push_str(": ");
220 message.push_str(panic_message)
221 };
222
223 Ok(lsp_server::Response::new_err(
224 id,
225 lsp_server::ErrorCode::InternalError as i32,
226 message,
227 ))
228 }
229 }
230}
231
232fn result_to_response<R>(
233 id: lsp_server::RequestId,
234 result: Result<R::Result>,
235) -> Result<lsp_server::Response, Cancelled>
236where
237 R: lsp_types::request::Request,
238 R::Params: DeserializeOwned,
239 R::Result: Serialize,
240{
241 let res = match result {
242 Ok(resp) => lsp_server::Response::new_ok(id, &resp),
243 Err(e) => match e.downcast::<LspError>() {
244 Ok(lsp_error) => lsp_server::Response::new_err(id, lsp_error.code, lsp_error.message),
245 Err(e) => match e.downcast::<Cancelled>() {
246 Ok(cancelled) => return Err(*cancelled),
247 Err(e) => lsp_server::Response::new_err(
248 id,
249 lsp_server::ErrorCode::InternalError as i32,
250 e.to_string(),
251 ),
252 },
253 },
254 };
255 Ok(res)
256}
257
258pub(crate) struct NotificationDispatcher<'a> {
259 pub(crate) not: Option<lsp_server::Notification>,
260 pub(crate) global_state: &'a mut GlobalState,
261}
262
263impl<'a> NotificationDispatcher<'a> {
264 pub(crate) fn on<N>(
265 &mut self,
266 f: fn(&mut GlobalState, N::Params) -> Result<()>,
267 ) -> Result<&mut Self>
268 where
269 N: lsp_types::notification::Notification,
270 N::Params: DeserializeOwned + Send,
271 {
272 let not = match self.not.take() {
273 Some(it) => it,
274 None => return Ok(self),
275 };
276 let params = match not.extract::<N::Params>(N::METHOD) {
277 Ok(it) => it,
278 Err(ExtractError::JsonError { method, error }) => {
279 panic!("Invalid request\nMethod: {method}\n error: {error}",)
280 }
281 Err(ExtractError::MethodMismatch(not)) => {
282 self.not = Some(not);
283 return Ok(self);
284 }
285 };
286 let _pctx = stdx::panic_context::enter(format!(
287 "\nversion: {}\nnotification: {}",
288 version(),
289 N::METHOD
290 ));
291 f(self.global_state, params)?;
292 Ok(self)
293 }
294
295 pub(crate) fn finish(&mut self) {
296 if let Some(not) = &self.not {
297 if !not.method.starts_with("$/") {
298 tracing::error!("unhandled notification: {:?}", not);
299 }
300 }
301 }
302}