Skip to content

Commit 8432ec4

Browse files
authored
feat: references, completion for type casting (#9)
* feat: completion for type casting add completion for built-in function `<type>::into(input)` also refactor `completion::types::FunctionTemplate`, removed redundant field `snippet_base` and add new field `snippet`. * add check for `into` function, fixed docs using completion with function kind makes unpleasant overlap with word `into` and it structure `<Input>::into(input)`, so we used snippet completion kind for clean visual effect. * change hover description Include in description either it built-in or jet before function signature. Also add after signature `---` for clean separation with it's description. * add `references` feature for functions * remove unnecessary change for positions inside `find_all_references` * fix "RopeSlice to str conversion failed" Initially, `RopeSlice.as_str()` was used because it doesn't create a copy of the string, but it can fail if the `RopeSlice` is not continuous in memory. So, we convert the `RopeSlice` to a string, because this will not throw an error for large lines, and the `references` function is not called often enough for us to care about copying one line. * add "reference" feauture for any calls * clippy: remove unused import * make definition show itself on reference also change goto-definition to return range of symbol if we stay on definition, because this actually triggers reference
1 parent d0efeca commit 8432ec4

File tree

5 files changed

+264
-53
lines changed

5 files changed

+264
-53
lines changed

src/backend.rs

Lines changed: 81 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,11 @@ use tower_lsp_server::lsp_types::{
1313
DidChangeWorkspaceFoldersParams, DidCloseTextDocumentParams, DidOpenTextDocumentParams,
1414
DidSaveTextDocumentParams, ExecuteCommandParams, GotoDefinitionParams, GotoDefinitionResponse,
1515
Hover, HoverParams, HoverProviderCapability, InitializeParams, InitializeResult,
16-
InitializedParams, Location, MarkupContent, MarkupKind, MessageType, OneOf, Range, SaveOptions,
17-
SemanticTokensParams, SemanticTokensResult, ServerCapabilities, TextDocumentSyncCapability,
18-
TextDocumentSyncKind, TextDocumentSyncOptions, TextDocumentSyncSaveOptions, Uri,
19-
WorkDoneProgressOptions, WorkspaceFoldersServerCapabilities, WorkspaceServerCapabilities,
16+
InitializedParams, Location, MarkupContent, MarkupKind, MessageType, OneOf, Range,
17+
ReferenceParams, SaveOptions, SemanticTokensParams, SemanticTokensResult, ServerCapabilities,
18+
TextDocumentSyncCapability, TextDocumentSyncKind, TextDocumentSyncOptions,
19+
TextDocumentSyncSaveOptions, Uri, WorkDoneProgressOptions, WorkspaceFoldersServerCapabilities,
20+
WorkspaceServerCapabilities,
2021
};
2122
use tower_lsp_server::{Client, LanguageServer};
2223

@@ -31,7 +32,8 @@ use crate::completion::{self, CompletionProvider};
3132
use crate::error::LspError;
3233
use crate::function::Functions;
3334
use crate::utils::{
34-
find_related_call, get_call_span, get_comments_from_lines, position_to_span, span_to_positions,
35+
find_all_references, find_function_name_range, find_related_call, get_call_span,
36+
get_comments_from_lines, position_to_span, span_contains, span_to_positions,
3537
};
3638

3739
#[derive(Debug)]
@@ -86,6 +88,7 @@ impl LanguageServer for Backend {
8688
}),
8789
hover_provider: Some(HoverProviderCapability::Simple(true)),
8890
definition_provider: Some(OneOf::Left(true)),
91+
references_provider: Some(OneOf::Left(true)),
8992
..ServerCapabilities::default()
9093
},
9194
})
@@ -224,7 +227,7 @@ impl LanguageServer for Backend {
224227

225228
let template = completion::jet::jet_to_template(element);
226229
format!(
227-
"```simplicityhl\nfn jet::{}({}) -> {}\n```\n{}",
230+
"Jet function\n```simplicityhl\nfn {}({}) -> {}\n```\n---\n\n{}",
228231
template.display_name,
229232
template.args.join(", "),
230233
template.return_type,
@@ -241,7 +244,7 @@ impl LanguageServer for Backend {
241244

242245
let template = completion::function_to_template(function, function_doc);
243246
format!(
244-
"```simplicityhl\nfn {}({}) -> {}\n```\n{}",
247+
"```simplicityhl\nfn {}({}) -> {}\n```\n---\n{}",
245248
template.display_name,
246249
template.args.join(", "),
247250
template.return_type,
@@ -253,7 +256,7 @@ impl LanguageServer for Backend {
253256
return Ok(None);
254257
};
255258
format!(
256-
"```simplicityhl\nfn {}({}) -> {}\n```\n{}",
259+
"Built-in function\n```simplicityhl\nfn {}({}) -> {}\n```\n---\n{}",
257260
template.display_name,
258261
template.args.join(", "),
259262
template.return_type,
@@ -287,6 +290,20 @@ impl LanguageServer for Backend {
287290
let token_span = position_to_span(token_position)?;
288291

289292
let Ok(Some(call)) = find_related_call(&functions, token_span) else {
293+
let Some(func) = functions
294+
.iter()
295+
.find(|func| span_contains(func.span(), &token_span))
296+
else {
297+
return Ok(None);
298+
};
299+
let range = find_function_name_range(func, &doc.text)?;
300+
301+
if token_position <= range.end && token_position >= range.start {
302+
return Ok(Some(GotoDefinitionResponse::from(Location::new(
303+
uri.clone(),
304+
range,
305+
))));
306+
}
290307
return Ok(None);
291308
};
292309

@@ -308,6 +325,62 @@ impl LanguageServer for Backend {
308325
_ => Ok(None),
309326
}
310327
}
328+
329+
async fn references(&self, params: ReferenceParams) -> Result<Option<Vec<Location>>> {
330+
let documents = self.document_map.read().await;
331+
let uri = &params.text_document_position.text_document.uri;
332+
333+
let doc = documents
334+
.get(uri)
335+
.ok_or(LspError::DocumentNotFound(uri.to_owned()))?;
336+
let functions = doc.functions.functions();
337+
338+
let token_position = params.text_document_position.position;
339+
340+
let token_span = position_to_span(token_position)?;
341+
342+
let call_name =
343+
find_related_call(&functions, token_span)?.map(simplicityhl::parse::Call::name);
344+
345+
match call_name {
346+
Some(parse::CallName::Custom(_)) | None => {}
347+
Some(name) => {
348+
return Ok(Some(
349+
find_all_references(&functions, name)?
350+
.iter()
351+
.map(|range| Location {
352+
range: *range,
353+
uri: uri.clone(),
354+
})
355+
.collect(),
356+
));
357+
}
358+
}
359+
360+
let Some(func) = functions.iter().find(|func| match call_name {
361+
Some(parse::CallName::Custom(name)) => func.name() == name,
362+
_ => span_contains(func.span(), &token_span),
363+
}) else {
364+
return Ok(None);
365+
};
366+
367+
let range = find_function_name_range(func, &doc.text)?;
368+
369+
if (token_position <= range.end && token_position >= range.start) || call_name.is_some() {
370+
Ok(Some(
371+
find_all_references(&functions, &parse::CallName::Custom(func.name().clone()))?
372+
.into_iter()
373+
.chain(std::iter::once(range))
374+
.map(|range| Location {
375+
range,
376+
uri: uri.clone(),
377+
})
378+
.collect(),
379+
))
380+
} else {
381+
Ok(None)
382+
}
383+
}
311384
}
312385

313386
impl Backend {

src/completion/builtin.rs

Lines changed: 81 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use crate::completion::types::FunctionTemplate;
1212
/// Get completion of builtin functions. They are all defined in [`simplicityhl::parse::CallName`]
1313
pub fn get_builtin_functions() -> Vec<FunctionTemplate> {
1414
let ty = AliasedType::from(AliasName::from_str_unchecked("T"));
15+
let function_name = FunctionName::from_str_unchecked("fn");
1516
let Some(some) = NonZero::new(1) else {
1617
return vec![];
1718
};
@@ -24,12 +25,10 @@ pub fn get_builtin_functions() -> Vec<FunctionTemplate> {
2425
CallName::Assert,
2526
CallName::Debug,
2627
CallName::Panic,
27-
CallName::Fold(
28-
FunctionName::from_str_unchecked("name"),
29-
NonZeroPow2Usize::TWO,
30-
),
31-
CallName::ArrayFold(FunctionName::from_str_unchecked("name"), some),
32-
CallName::ForWhile(FunctionName::from_str_unchecked("name")),
28+
CallName::Fold(function_name.clone(), NonZeroPow2Usize::TWO),
29+
CallName::ArrayFold(function_name.clone(), some),
30+
CallName::ForWhile(function_name.clone()),
31+
CallName::TypeCast(ty.clone()),
3332
];
3433

3534
functions.iter().filter_map(match_callname).collect()
@@ -42,7 +41,6 @@ pub fn match_callname(call: &CallName) -> Option<FunctionTemplate> {
4241
CallName::UnwrapLeft(aliased_type) => {
4342
let ty = aliased_type.to_string();
4443
Some(FunctionTemplate::new(
45-
"unwrap_left",
4644
"unwrap_left",
4745
vec![format!("{ty}")],
4846
vec![format!("Either<{ty}, U>")],
@@ -53,7 +51,6 @@ pub fn match_callname(call: &CallName) -> Option<FunctionTemplate> {
5351
CallName::UnwrapRight(aliased_type) => {
5452
let ty = aliased_type.to_string();
5553
Some(FunctionTemplate::new(
56-
"unwrap_right",
5754
"unwrap_right",
5855
vec![format!("{ty}")],
5956
vec![format!("Either<T, {ty}>")],
@@ -71,7 +68,6 @@ pub fn match_callname(call: &CallName) -> Option<FunctionTemplate> {
7168
let ty = aliased_type.to_string();
7269
Some(FunctionTemplate::new(
7370
"is_none".to_string(),
74-
"is_none",
7571
vec![format!("{ty}")],
7672
vec![format!("Option<{ty}>")],
7773
"bool",
@@ -92,7 +88,6 @@ pub fn match_callname(call: &CallName) -> Option<FunctionTemplate> {
9288
doc,
9389
)),
9490
CallName::Fold(_, _) => Some(FunctionTemplate::new(
95-
"fold",
9691
"fold",
9792
vec!["f".to_string(), "N".to_string()],
9893
vec![
@@ -103,7 +98,6 @@ pub fn match_callname(call: &CallName) -> Option<FunctionTemplate> {
10398
doc,
10499
)),
105100
CallName::ArrayFold(_, _) => Some(FunctionTemplate::new(
106-
"array_fold",
107101
"array_fold",
108102
vec!["f".to_string(), "N".to_string()],
109103
vec![
@@ -114,18 +108,28 @@ pub fn match_callname(call: &CallName) -> Option<FunctionTemplate> {
114108
doc,
115109
)),
116110
CallName::ForWhile(_) => Some(FunctionTemplate::new(
117-
"for_while",
118111
"for_while",
119112
vec!["f".to_string()],
120113
vec!["accumulator: A".to_string(), "context: C".to_string()],
121114
"Either<B, A>",
122115
doc,
123116
)),
124-
// TODO: implement TypeCast definition
125-
CallName::Jet(_) | CallName::TypeCast(_) | CallName::Custom(_) => None,
117+
118+
// The `into` function has a different structure compared to the other built-ins,
119+
// so we defined a different snippet for it.
120+
CallName::TypeCast(_) => Some(FunctionTemplate {
121+
display_name: "into".into(),
122+
generics: vec!["Input".to_string()],
123+
args: vec!["input".to_string()],
124+
return_type: "Output".into(),
125+
description: doc,
126+
snippet: "<${1:Input}>::into".into(),
127+
}),
128+
CallName::Jet(_) | CallName::Custom(_) => None,
126129
}
127130
}
128131

132+
/// Return documentation for builtin function.
129133
fn builtin_documentation(call: &CallName) -> String {
130134
String::from(match call {
131135
CallName::UnwrapLeft(_) =>
@@ -212,6 +216,68 @@ fn main() {
212216
assert!(jet::eq_8(10, unwrap_left::<()>(out)));
213217
}
214218
```",
215-
CallName::Jet(_) | CallName::TypeCast(_) | CallName::Custom(_) => "",
219+
CallName::TypeCast(_) => type_casting_documentation(),
220+
CallName::Jet(_) | CallName::Custom(_) => "",
216221
})
217222
}
223+
224+
/// Return documentation for `into` casting.
225+
fn type_casting_documentation() -> &'static str {
226+
"A SimplicityHL type can be cast into another SimplicityHL type if both types share the same structure.
227+
228+
## Casting Rules
229+
230+
- Type `A` can be cast into itself (reflexivity).
231+
232+
- If type `A` can be cast into type `B`, then type `B` can be cast into type `A` (symmetry).
233+
234+
- If type `A` can be cast into type `B` and type `B` can be cast into type `C`, then type `A` can be cast into type `C` (transitivity).
235+
236+
Below is a table of types that can be cast into each other.
237+
238+
| Type | Casts To (And Back) |
239+
|----------------|------------------------------------|
240+
| `bool` | `Either<(), ()>` |
241+
| `Option<A>` | `Either<(), A>` |
242+
| `u1` | `bool` |
243+
| `u2` | `(u1, u1)` |
244+
| `u4` | `(u2, u2)` |
245+
| `u8` | `(u4, u4)` |
246+
| `u16` | `(u8, u8)` |
247+
| `u32` | `(u16, u16)` |
248+
| `u64` | `(u32, u32)` |
249+
| `u128` | `(u64, u64)` |
250+
| `u256` | `(u128, u128)` |
251+
| `(A)` | `A` |
252+
| `(A, B, C)` | `(A, (B, C))` |
253+
| `(A, B, C, D)` | `((A, B), (C, D))` |
254+
| ... | ... |
255+
| `[A; 0]` | `()` |
256+
| `[A; 1]` | `A` |
257+
| `[A; 2]` | `(A, A)` |
258+
| `[A; 3]` | `(A, (A, A))` |
259+
| `[A; 4]` | `((A, A), (A, A))` |
260+
| ... | ... |
261+
| `List<A, 2>` | `Option<A>` |
262+
| `List<A, 4>` | `(Option<[A; 2]>, List<A, 2>)` |
263+
| `List<A, 8>` | `(Option<[A; 4]>, List<A, 4>)` |
264+
| `List<A, 16>` | `(Option<[A; 8]>, List<A, 8>)` |
265+
| `List<A, 32>` | `(Option<[A; 16]>, List<A, 16>)` |
266+
| `List<A, 64>` | `(Option<[A; 32]>, List<A, 32>)` |
267+
| `List<A, 128>` | `(Option<[A; 64]>, List<A, 64>)` |
268+
| `List<A, 256>` | `(Option<[A; 128]>, List<A, 128>)` |
269+
| `List<A, 512>` | `(Option<[A; 256]>, List<A, 256>)` |
270+
| ... | ... |
271+
272+
## Casting Expression
273+
274+
All casting in SimplicityHL happens explicitly through a casting expression.
275+
276+
```simplicityhl
277+
<Input>::into(input)
278+
```
279+
280+
The above expression casts the value `input` of type `Input` into some output type.
281+
The input type of the cast is explicit while the output type is implicit.
282+
"
283+
}

src/completion/mod.rs

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use tower_lsp_server::lsp_types::{
88
CompletionItem, CompletionItemKind, Documentation, InsertTextFormat, MarkupContent, MarkupKind,
99
};
1010

11-
/// Build and provide `CompletionItem` for Jets and builtin functions.
11+
/// Build and provide [`CompletionItem`] for jets and builtin functions.
1212
#[derive(Debug)]
1313
pub struct CompletionProvider {
1414
/// All jets completions.
@@ -22,7 +22,7 @@ pub struct CompletionProvider {
2222
}
2323

2424
impl CompletionProvider {
25-
/// Create new `CompletionProvider` with evaluated jets and builtins completions.
25+
/// Create new [`CompletionProvider`] with evaluated jets and builtins completions.
2626
pub fn new() -> Self {
2727
let jets_completion = jet::get_jets_completions()
2828
.iter()
@@ -75,7 +75,7 @@ impl CompletionProvider {
7575
}
7676
}
7777

78-
/// Convert `simplicityhl::parse::Function` to `FunctionTemplate`.
78+
/// Convert [`simplicityhl::parse::Function`] to [`types::FunctionTemplate`].
7979
pub fn function_to_template(func: &Function, doc: &str) -> types::FunctionTemplate {
8080
types::FunctionTemplate::simple(
8181
func.name().to_string(),
@@ -88,11 +88,17 @@ pub fn function_to_template(func: &Function, doc: &str) -> types::FunctionTempla
8888
)
8989
}
9090

91-
/// Convert `FunctionCompletionTemplate` to `CompletionItem`.
91+
/// Convert [`types::FunctionTemplate`] to [`CompletionItem`].
9292
fn template_to_completion(func: &types::FunctionTemplate) -> CompletionItem {
9393
CompletionItem {
9494
label: func.display_name.clone(),
95-
kind: Some(CompletionItemKind::FUNCTION),
95+
// Because `into` has different structure, completion with CompletionItemKind::FUNCTION
96+
// have strange visual effects, so we use CompletionItemKind::SNIPPET
97+
kind: Some(if func.display_name == "into" {
98+
CompletionItemKind::SNIPPET
99+
} else {
100+
CompletionItemKind::FUNCTION
101+
}),
96102
detail: Some(func.get_signature()),
97103
documentation: Some(Documentation::MarkupContent(MarkupContent {
98104
kind: MarkupKind::Markdown,
@@ -104,7 +110,7 @@ fn template_to_completion(func: &types::FunctionTemplate) -> CompletionItem {
104110
}
105111
}
106112

107-
/// Convert module to `CompletionItem`.
113+
/// Convert module name to [`CompletionItem`].
108114
fn module_to_completion(module: String, detail: String) -> CompletionItem {
109115
CompletionItem {
110116
label: module.clone(),

0 commit comments

Comments
 (0)