Skip to content

Commit ada594c

Browse files
committed
add custom error handling
methods in `utils.rs` was returning different kind of error: `TryFromIntError` and `jsonrpc::Error` which was difficult work with. `LspError` support converting from `TryFromIntError` and to `jsonrpc::Error`
1 parent b383a1a commit ada594c

File tree

4 files changed

+144
-87
lines changed

4 files changed

+144
-87
lines changed

src/backend.rs

Lines changed: 48 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ use simplicityhl::{
2828
};
2929

3030
use crate::completion::{self, CompletionProvider};
31+
use crate::error::LspError;
3132
use crate::function::Functions;
3233
use crate::utils::{
3334
find_related_call, get_call_span, get_comments_from_lines, position_to_span, span_to_positions,
@@ -145,22 +146,30 @@ impl LanguageServer for Backend {
145146
}
146147

147148
async fn completion(&self, params: CompletionParams) -> Result<Option<CompletionResponse>> {
149+
let documents = self.document_map.read().await;
148150
let uri = &params.text_document_position.text_document.uri;
151+
152+
let doc = documents
153+
.get(uri)
154+
.ok_or(LspError::DocumentNotFound(uri.to_owned()))?;
155+
149156
let pos = params.text_document_position.position;
150-
let documents = self.document_map.read().await;
151157

152-
let Some(doc) = documents.get(uri) else {
153-
return Ok(Some(CompletionResponse::Array(vec![])));
154-
};
155-
let Some(line) = doc.text.lines().nth(pos.line as usize) else {
156-
return Ok(Some(CompletionResponse::Array(vec![])));
157-
};
158-
let Some(slice) = line.get_slice(..pos.character as usize) else {
159-
return Ok(Some(CompletionResponse::Array(vec![])));
160-
};
161-
let Some(prefix) = slice.as_str() else {
162-
return Ok(Some(CompletionResponse::Array(vec![])));
163-
};
158+
let line = doc
159+
.text
160+
.lines()
161+
.nth(pos.line as usize)
162+
.ok_or(LspError::Internal("Rope proccesing error".into()))?;
163+
164+
let slice = line
165+
.get_slice(..pos.character as usize)
166+
.ok_or(LspError::ConversionFailed(
167+
"Rope to slice conversion failed".into(),
168+
))?;
169+
170+
let prefix = slice.as_str().ok_or(LspError::ConversionFailed(
171+
"RopeSlice to str conversion failed".into(),
172+
))?;
164173

165174
let trimmed_prefix = prefix.trim_end();
166175

@@ -190,30 +199,27 @@ impl LanguageServer for Backend {
190199

191200
async fn hover(&self, params: HoverParams) -> Result<Option<Hover>> {
192201
let documents = self.document_map.read().await;
193-
let Some(document) = documents.get(&params.text_document_position_params.text_document.uri)
194-
else {
195-
return Err(tower_lsp_server::jsonrpc::Error::internal_error());
196-
};
202+
let uri = &params.text_document_position_params.text_document.uri;
203+
204+
let doc = documents
205+
.get(uri)
206+
.ok_or(LspError::DocumentNotFound(uri.to_owned()))?;
197207

198208
let token_pos = params.text_document_position_params.position;
199-
let token_span = position_to_span(token_pos)?;
200209

201-
let Ok(call) = find_related_call(&document.functions.functions(), token_span) else {
210+
let token_span = position_to_span(token_pos)?;
211+
let Ok(Some(call)) = find_related_call(&doc.functions.functions(), token_span) else {
202212
return Ok(None);
203213
};
204214

205-
let Ok(call_span) = get_call_span(&call) else {
206-
return Ok(None);
207-
};
215+
let call_span = get_call_span(&call)?;
208216
let (start, end) = span_to_positions(&call_span)?;
209217

210218
let description = match call.name() {
211219
parse::CallName::Jet(jet) => {
212-
let Ok(element) =
220+
let element =
213221
simplicityhl::simplicity::jet::Elements::from_str(format!("{jet}").as_str())
214-
else {
215-
return Ok(None);
216-
};
222+
.map_err(|err| LspError::ConversionFailed(err.to_string().into()))?;
217223

218224
let template = completion::jet::jet_to_template(element);
219225
format!(
@@ -225,9 +231,12 @@ impl LanguageServer for Backend {
225231
)
226232
}
227233
parse::CallName::Custom(func) => {
228-
let Some((function, function_doc)) = document.functions.get(func.as_inner()) else {
229-
return Ok(None);
230-
};
234+
let (function, function_doc) =
235+
doc.functions
236+
.get(func.as_inner())
237+
.ok_or(LspError::FunctionNotFound(
238+
format!("Function {func} is not found").into(),
239+
))?;
231240

232241
let template = completion::function_to_template(function, function_doc);
233242
format!(
@@ -268,22 +277,25 @@ impl LanguageServer for Backend {
268277
let documents = self.document_map.read().await;
269278
let uri = &params.text_document_position_params.text_document.uri;
270279

271-
let Some(document) = documents.get(uri) else {
272-
return Err(tower_lsp_server::jsonrpc::Error::internal_error());
273-
};
280+
let doc = documents
281+
.get(uri)
282+
.ok_or(LspError::DocumentNotFound(uri.to_owned()))?;
274283

275284
let token_position = params.text_document_position_params.position;
276285
let token_span = position_to_span(token_position)?;
277286

278-
let Ok(call) = find_related_call(&document.functions.functions(), token_span) else {
287+
let Ok(Some(call)) = find_related_call(&doc.functions.functions(), token_span) else {
279288
return Ok(None);
280289
};
281290

282291
match call.name() {
283292
simplicityhl::parse::CallName::Custom(func) => {
284-
let Some(function) = document.functions.get_func(func.as_inner()) else {
285-
return Ok(None);
286-
};
293+
let function =
294+
doc.functions
295+
.get_func(func.as_inner())
296+
.ok_or(LspError::FunctionNotFound(
297+
format!("Function {func} is not found").into(),
298+
))?;
287299

288300
let (start, end) = span_to_positions(function.as_ref())?;
289301
Ok(Some(GotoDefinitionResponse::from(Location::new(

src/error.rs

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
use std::borrow::Cow;
2+
use std::fmt::Display;
3+
use std::num::TryFromIntError;
4+
5+
use tower_lsp_server::jsonrpc::Error;
6+
use tower_lsp_server::lsp_types::Uri;
7+
8+
type Message = Cow<'static, str>;
9+
10+
/// The main error type for our language server.
11+
#[derive(Debug, Clone)]
12+
pub enum LspError {
13+
/// An error during the conversion between LSP positions and compiler spans.
14+
ConversionFailed(Message),
15+
16+
/// Failed to find a relevant item in the AST for a request.
17+
FunctionNotFound(Message),
18+
19+
/// Call not found inside function.
20+
CallNotFound(Message),
21+
22+
/// The requested document URI was not found in the server's state.
23+
DocumentNotFound(Uri),
24+
25+
/// A generic or unexpected internal error.
26+
Internal(Message),
27+
}
28+
29+
impl LspError {
30+
pub fn code(&self) -> i64 {
31+
match self {
32+
LspError::ConversionFailed(_) => 1,
33+
LspError::FunctionNotFound(_) => 2,
34+
LspError::CallNotFound(_) => 3,
35+
LspError::DocumentNotFound(_) => 4,
36+
LspError::Internal(_) => 100,
37+
}
38+
}
39+
40+
pub fn description(&self) -> String {
41+
match self {
42+
LspError::DocumentNotFound(uri) => {
43+
format!("Document not found: {}", uri.as_str())
44+
}
45+
LspError::ConversionFailed(cow)
46+
| LspError::FunctionNotFound(cow)
47+
| LspError::CallNotFound(cow)
48+
| LspError::Internal(cow) => cow.to_string(),
49+
}
50+
}
51+
}
52+
53+
impl From<LspError> for Error {
54+
fn from(err: LspError) -> Self {
55+
let code = err.code();
56+
let msg = err.description();
57+
58+
Error {
59+
code: code.into(),
60+
message: msg.into(),
61+
data: None,
62+
}
63+
}
64+
}
65+
66+
impl From<TryFromIntError> for LspError {
67+
fn from(value: TryFromIntError) -> Self {
68+
LspError::ConversionFailed(value.to_string().into())
69+
}
70+
}
71+
72+
impl Display for LspError {
73+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
74+
f.write_str(format!("{}: {}", self.code(), self.description()).as_str())
75+
}
76+
}

src/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
mod backend;
44
mod completion;
5+
mod error;
56
mod function;
67
mod utils;
78

src/utils.rs

Lines changed: 19 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ use std::num::NonZeroUsize;
22

33
use miniscript::iter::TreeLike;
44

5+
use crate::error::LspError;
56
use ropey::Rope;
67
use simplicityhl::parse;
7-
use tower_lsp_server::jsonrpc::{Error, Result};
88
use tower_lsp_server::lsp_types;
99

1010
fn position_le(a: &simplicityhl::error::Position, b: &simplicityhl::error::Position) -> bool {
@@ -26,15 +26,11 @@ pub fn span_contains(a: &simplicityhl::error::Span, b: &simplicityhl::error::Spa
2626
/// `Position` required for diagnostic starts with zero
2727
pub fn span_to_positions(
2828
span: &simplicityhl::error::Span,
29-
) -> Result<(lsp_types::Position, lsp_types::Position)> {
30-
let start_line = u32::try_from(span.start.line.get())
31-
.map_err(|e| Error::invalid_params(format!("line overflow: {e}")))?;
32-
let start_col = u32::try_from(span.start.col.get())
33-
.map_err(|e| Error::invalid_params(format!("col overflow: {e}")))?;
34-
let end_line = u32::try_from(span.end.line.get())
35-
.map_err(|e| Error::invalid_params(format!("line overflow: {e}")))?;
36-
let end_col = u32::try_from(span.end.col.get())
37-
.map_err(|e| Error::invalid_params(format!("col overflow: {e}")))?;
29+
) -> Result<(lsp_types::Position, lsp_types::Position), LspError> {
30+
let start_line = u32::try_from(span.start.line.get())?;
31+
let start_col = u32::try_from(span.start.col.get())?;
32+
let end_line = u32::try_from(span.end.line.get())?;
33+
let end_col = u32::try_from(span.end.col.get())?;
3834

3935
Ok((
4036
lsp_types::Position {
@@ -48,43 +44,14 @@ pub fn span_to_positions(
4844
))
4945
}
5046

51-
#[allow(dead_code)]
52-
/// Convert pair of [`tower_lsp_server::lsp_types::Position`] to [`simplicityhl::error::Span`]
53-
pub fn positions_to_span(
54-
positions: (lsp_types::Position, lsp_types::Position),
55-
) -> Result<simplicityhl::error::Span> {
56-
let start_line = NonZeroUsize::new((positions.0.line + 1) as usize)
57-
.ok_or_else(|| Error::invalid_params("start line must be non-zero".to_string()))?;
58-
59-
let start_col = NonZeroUsize::new((positions.0.character + 1) as usize)
60-
.ok_or_else(|| Error::invalid_params("start column must be non-zero".to_string()))?;
61-
62-
let end_line = NonZeroUsize::new((positions.1.line + 1) as usize)
63-
.ok_or_else(|| Error::invalid_params("end line must be non-zero".to_string()))?;
64-
65-
let end_col = NonZeroUsize::new((positions.1.character + 1) as usize)
66-
.ok_or_else(|| Error::invalid_params("end column must be non-zero".to_string()))?;
67-
Ok(simplicityhl::error::Span {
68-
start: simplicityhl::error::Position {
69-
line: start_line,
70-
col: start_col,
71-
},
72-
end: simplicityhl::error::Position {
73-
line: end_line,
74-
col: end_col,
75-
},
76-
})
77-
}
78-
7947
/// Convert [`tower_lsp_server::lsp_types::Position`] to [`simplicityhl::error::Span`]
8048
///
8149
/// Useful when [`tower_lsp_server::lsp_types::Position`] represents some singular point.
82-
pub fn position_to_span(position: lsp_types::Position) -> Result<simplicityhl::error::Span> {
83-
let start_line = NonZeroUsize::new((position.line + 1) as usize)
84-
.ok_or_else(|| Error::invalid_params("start line must be non-zero".to_string()))?;
85-
86-
let start_col = NonZeroUsize::new((position.character + 1) as usize)
87-
.ok_or_else(|| Error::invalid_params("start column must be non-zero".to_string()))?;
50+
pub fn position_to_span(
51+
position: lsp_types::Position,
52+
) -> Result<simplicityhl::error::Span, LspError> {
53+
let start_line = NonZeroUsize::try_from((position.line + 1) as usize)?;
54+
let start_col = NonZeroUsize::try_from((position.character + 1) as usize)?;
8855

8956
Ok(simplicityhl::error::Span {
9057
start: simplicityhl::error::Position {
@@ -161,11 +128,13 @@ pub fn get_comments_from_lines(line: u32, rope: &Rope) -> String {
161128
pub fn find_related_call(
162129
functions: &[&parse::Function],
163130
token_span: simplicityhl::error::Span,
164-
) -> std::result::Result<simplicityhl::parse::Call, &'static str> {
131+
) -> Result<Option<simplicityhl::parse::Call>, LspError> {
165132
let func = functions
166133
.iter()
167134
.find(|func| span_contains(func.span(), &token_span))
168-
.ok_or("given span not inside function")?;
135+
.ok_or(LspError::CallNotFound(
136+
"Span of the call is not inside function.".into(),
137+
))?;
169138

170139
let call = parse::ExprTree::Expression(func.body())
171140
.pre_order_iter()
@@ -178,16 +147,15 @@ pub fn find_related_call(
178147
}
179148
})
180149
.filter(|(_, span)| span_contains(span, &token_span))
181-
.map(|(call, _)| call)
182-
.last()
183-
.ok_or("no related call found")?;
150+
.map(|(call, _)| call.clone())
151+
.last();
184152

185-
Ok(call.to_owned())
153+
Ok(call)
186154
}
187155

188156
pub fn get_call_span(
189157
call: &simplicityhl::parse::Call,
190-
) -> std::result::Result<simplicityhl::error::Span, std::num::TryFromIntError> {
158+
) -> Result<simplicityhl::error::Span, LspError> {
191159
let length = call.name().to_string().len();
192160

193161
let end_column = usize::from(call.span().start.col) + length;

0 commit comments

Comments
 (0)