Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 81 additions & 8 deletions src/backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,11 @@ use tower_lsp_server::lsp_types::{
DidChangeWorkspaceFoldersParams, DidCloseTextDocumentParams, DidOpenTextDocumentParams,
DidSaveTextDocumentParams, ExecuteCommandParams, GotoDefinitionParams, GotoDefinitionResponse,
Hover, HoverParams, HoverProviderCapability, InitializeParams, InitializeResult,
InitializedParams, Location, MarkupContent, MarkupKind, MessageType, OneOf, Range, SaveOptions,
SemanticTokensParams, SemanticTokensResult, ServerCapabilities, TextDocumentSyncCapability,
TextDocumentSyncKind, TextDocumentSyncOptions, TextDocumentSyncSaveOptions, Uri,
WorkDoneProgressOptions, WorkspaceFoldersServerCapabilities, WorkspaceServerCapabilities,
InitializedParams, Location, MarkupContent, MarkupKind, MessageType, OneOf, Range,
ReferenceParams, SaveOptions, SemanticTokensParams, SemanticTokensResult, ServerCapabilities,
TextDocumentSyncCapability, TextDocumentSyncKind, TextDocumentSyncOptions,
TextDocumentSyncSaveOptions, Uri, WorkDoneProgressOptions, WorkspaceFoldersServerCapabilities,
WorkspaceServerCapabilities,
};
use tower_lsp_server::{Client, LanguageServer};

Expand All @@ -31,7 +32,8 @@ use crate::completion::{self, CompletionProvider};
use crate::error::LspError;
use crate::function::Functions;
use crate::utils::{
find_related_call, get_call_span, get_comments_from_lines, position_to_span, span_to_positions,
find_all_references, find_function_name_range, find_related_call, get_call_span,
get_comments_from_lines, position_to_span, span_contains, span_to_positions,
};

#[derive(Debug)]
Expand Down Expand Up @@ -86,6 +88,7 @@ impl LanguageServer for Backend {
}),
hover_provider: Some(HoverProviderCapability::Simple(true)),
definition_provider: Some(OneOf::Left(true)),
references_provider: Some(OneOf::Left(true)),
..ServerCapabilities::default()
},
})
Expand Down Expand Up @@ -224,7 +227,7 @@ impl LanguageServer for Backend {

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

let template = completion::function_to_template(function, function_doc);
format!(
"```simplicityhl\nfn {}({}) -> {}\n```\n{}",
"```simplicityhl\nfn {}({}) -> {}\n```\n---\n{}",
template.display_name,
template.args.join(", "),
template.return_type,
Expand All @@ -253,7 +256,7 @@ impl LanguageServer for Backend {
return Ok(None);
};
format!(
"```simplicityhl\nfn {}({}) -> {}\n```\n{}",
"Built-in function\n```simplicityhl\nfn {}({}) -> {}\n```\n---\n{}",
template.display_name,
template.args.join(", "),
template.return_type,
Expand Down Expand Up @@ -287,6 +290,20 @@ impl LanguageServer for Backend {
let token_span = position_to_span(token_position)?;

let Ok(Some(call)) = find_related_call(&functions, token_span) else {
let Some(func) = functions
.iter()
.find(|func| span_contains(func.span(), &token_span))
else {
return Ok(None);
};
let range = find_function_name_range(func, &doc.text)?;

if token_position <= range.end && token_position >= range.start {
return Ok(Some(GotoDefinitionResponse::from(Location::new(
uri.clone(),
range,
))));
}
return Ok(None);
};

Expand All @@ -308,6 +325,62 @@ impl LanguageServer for Backend {
_ => Ok(None),
}
}

async fn references(&self, params: ReferenceParams) -> Result<Option<Vec<Location>>> {
let documents = self.document_map.read().await;
let uri = &params.text_document_position.text_document.uri;

let doc = documents
.get(uri)
.ok_or(LspError::DocumentNotFound(uri.to_owned()))?;
let functions = doc.functions.functions();

let token_position = params.text_document_position.position;

let token_span = position_to_span(token_position)?;

let call_name =
find_related_call(&functions, token_span)?.map(simplicityhl::parse::Call::name);

match call_name {
Some(parse::CallName::Custom(_)) | None => {}
Some(name) => {
return Ok(Some(
find_all_references(&functions, name)?
.iter()
.map(|range| Location {
range: *range,
uri: uri.clone(),
})
.collect(),
));
}
}

let Some(func) = functions.iter().find(|func| match call_name {
Some(parse::CallName::Custom(name)) => func.name() == name,
_ => span_contains(func.span(), &token_span),
}) else {
return Ok(None);
};

let range = find_function_name_range(func, &doc.text)?;

if (token_position <= range.end && token_position >= range.start) || call_name.is_some() {
Ok(Some(
find_all_references(&functions, &parse::CallName::Custom(func.name().clone()))?
.into_iter()
.chain(std::iter::once(range))
.map(|range| Location {
range,
uri: uri.clone(),
})
.collect(),
))
} else {
Ok(None)
}
}
}

impl Backend {
Expand Down
96 changes: 81 additions & 15 deletions src/completion/builtin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use crate::completion::types::FunctionTemplate;
/// Get completion of builtin functions. They are all defined in [`simplicityhl::parse::CallName`]
pub fn get_builtin_functions() -> Vec<FunctionTemplate> {
let ty = AliasedType::from(AliasName::from_str_unchecked("T"));
let function_name = FunctionName::from_str_unchecked("fn");
let Some(some) = NonZero::new(1) else {
return vec![];
};
Expand All @@ -24,12 +25,10 @@ pub fn get_builtin_functions() -> Vec<FunctionTemplate> {
CallName::Assert,
CallName::Debug,
CallName::Panic,
CallName::Fold(
FunctionName::from_str_unchecked("name"),
NonZeroPow2Usize::TWO,
),
CallName::ArrayFold(FunctionName::from_str_unchecked("name"), some),
CallName::ForWhile(FunctionName::from_str_unchecked("name")),
CallName::Fold(function_name.clone(), NonZeroPow2Usize::TWO),
CallName::ArrayFold(function_name.clone(), some),
CallName::ForWhile(function_name.clone()),
CallName::TypeCast(ty.clone()),
];

functions.iter().filter_map(match_callname).collect()
Expand All @@ -42,7 +41,6 @@ pub fn match_callname(call: &CallName) -> Option<FunctionTemplate> {
CallName::UnwrapLeft(aliased_type) => {
let ty = aliased_type.to_string();
Some(FunctionTemplate::new(
"unwrap_left",
"unwrap_left",
vec![format!("{ty}")],
vec![format!("Either<{ty}, U>")],
Expand All @@ -53,7 +51,6 @@ pub fn match_callname(call: &CallName) -> Option<FunctionTemplate> {
CallName::UnwrapRight(aliased_type) => {
let ty = aliased_type.to_string();
Some(FunctionTemplate::new(
"unwrap_right",
"unwrap_right",
vec![format!("{ty}")],
vec![format!("Either<T, {ty}>")],
Expand All @@ -71,7 +68,6 @@ pub fn match_callname(call: &CallName) -> Option<FunctionTemplate> {
let ty = aliased_type.to_string();
Some(FunctionTemplate::new(
"is_none".to_string(),
"is_none",
vec![format!("{ty}")],
vec![format!("Option<{ty}>")],
"bool",
Expand All @@ -92,7 +88,6 @@ pub fn match_callname(call: &CallName) -> Option<FunctionTemplate> {
doc,
)),
CallName::Fold(_, _) => Some(FunctionTemplate::new(
"fold",
"fold",
vec!["f".to_string(), "N".to_string()],
vec![
Expand All @@ -103,7 +98,6 @@ pub fn match_callname(call: &CallName) -> Option<FunctionTemplate> {
doc,
)),
CallName::ArrayFold(_, _) => Some(FunctionTemplate::new(
"array_fold",
"array_fold",
vec!["f".to_string(), "N".to_string()],
vec![
Expand All @@ -114,18 +108,28 @@ pub fn match_callname(call: &CallName) -> Option<FunctionTemplate> {
doc,
)),
CallName::ForWhile(_) => Some(FunctionTemplate::new(
"for_while",
"for_while",
vec!["f".to_string()],
vec!["accumulator: A".to_string(), "context: C".to_string()],
"Either<B, A>",
doc,
)),
// TODO: implement TypeCast definition
CallName::Jet(_) | CallName::TypeCast(_) | CallName::Custom(_) => None,

// The `into` function has a different structure compared to the other built-ins,
// so we defined a different snippet for it.
CallName::TypeCast(_) => Some(FunctionTemplate {
display_name: "into".into(),
generics: vec!["Input".to_string()],
args: vec!["input".to_string()],
return_type: "Output".into(),
description: doc,
snippet: "<${1:Input}>::into".into(),
}),
CallName::Jet(_) | CallName::Custom(_) => None,
}
}

/// Return documentation for builtin function.
fn builtin_documentation(call: &CallName) -> String {
String::from(match call {
CallName::UnwrapLeft(_) =>
Expand Down Expand Up @@ -212,6 +216,68 @@ fn main() {
assert!(jet::eq_8(10, unwrap_left::<()>(out)));
}
```",
CallName::Jet(_) | CallName::TypeCast(_) | CallName::Custom(_) => "",
CallName::TypeCast(_) => type_casting_documentation(),
CallName::Jet(_) | CallName::Custom(_) => "",
})
}

/// Return documentation for `into` casting.
fn type_casting_documentation() -> &'static str {
"A SimplicityHL type can be cast into another SimplicityHL type if both types share the same structure.

## Casting Rules

- Type `A` can be cast into itself (reflexivity).

- If type `A` can be cast into type `B`, then type `B` can be cast into type `A` (symmetry).

- 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).

Below is a table of types that can be cast into each other.

| Type | Casts To (And Back) |
|----------------|------------------------------------|
| `bool` | `Either<(), ()>` |
| `Option<A>` | `Either<(), A>` |
| `u1` | `bool` |
| `u2` | `(u1, u1)` |
| `u4` | `(u2, u2)` |
| `u8` | `(u4, u4)` |
| `u16` | `(u8, u8)` |
| `u32` | `(u16, u16)` |
| `u64` | `(u32, u32)` |
| `u128` | `(u64, u64)` |
| `u256` | `(u128, u128)` |
| `(A)` | `A` |
| `(A, B, C)` | `(A, (B, C))` |
| `(A, B, C, D)` | `((A, B), (C, D))` |
| ... | ... |
| `[A; 0]` | `()` |
| `[A; 1]` | `A` |
| `[A; 2]` | `(A, A)` |
| `[A; 3]` | `(A, (A, A))` |
| `[A; 4]` | `((A, A), (A, A))` |
| ... | ... |
| `List<A, 2>` | `Option<A>` |
| `List<A, 4>` | `(Option<[A; 2]>, List<A, 2>)` |
| `List<A, 8>` | `(Option<[A; 4]>, List<A, 4>)` |
| `List<A, 16>` | `(Option<[A; 8]>, List<A, 8>)` |
| `List<A, 32>` | `(Option<[A; 16]>, List<A, 16>)` |
| `List<A, 64>` | `(Option<[A; 32]>, List<A, 32>)` |
| `List<A, 128>` | `(Option<[A; 64]>, List<A, 64>)` |
| `List<A, 256>` | `(Option<[A; 128]>, List<A, 128>)` |
| `List<A, 512>` | `(Option<[A; 256]>, List<A, 256>)` |
| ... | ... |

## Casting Expression

All casting in SimplicityHL happens explicitly through a casting expression.

```simplicityhl
<Input>::into(input)
```

The above expression casts the value `input` of type `Input` into some output type.
The input type of the cast is explicit while the output type is implicit.
"
}
18 changes: 12 additions & 6 deletions src/completion/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use tower_lsp_server::lsp_types::{
CompletionItem, CompletionItemKind, Documentation, InsertTextFormat, MarkupContent, MarkupKind,
};

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

impl CompletionProvider {
/// Create new `CompletionProvider` with evaluated jets and builtins completions.
/// Create new [`CompletionProvider`] with evaluated jets and builtins completions.
pub fn new() -> Self {
let jets_completion = jet::get_jets_completions()
.iter()
Expand Down Expand Up @@ -75,7 +75,7 @@ impl CompletionProvider {
}
}

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

/// Convert `FunctionCompletionTemplate` to `CompletionItem`.
/// Convert [`types::FunctionTemplate`] to [`CompletionItem`].
fn template_to_completion(func: &types::FunctionTemplate) -> CompletionItem {
CompletionItem {
label: func.display_name.clone(),
kind: Some(CompletionItemKind::FUNCTION),
// Because `into` has different structure, completion with CompletionItemKind::FUNCTION
// have strange visual effects, so we use CompletionItemKind::SNIPPET
kind: Some(if func.display_name == "into" {
CompletionItemKind::SNIPPET
} else {
CompletionItemKind::FUNCTION
}),
detail: Some(func.get_signature()),
documentation: Some(Documentation::MarkupContent(MarkupContent {
kind: MarkupKind::Markdown,
Expand All @@ -104,7 +110,7 @@ fn template_to_completion(func: &types::FunctionTemplate) -> CompletionItem {
}
}

/// Convert module to `CompletionItem`.
/// Convert module name to [`CompletionItem`].
fn module_to_completion(module: String, detail: String) -> CompletionItem {
CompletionItem {
label: module.clone(),
Expand Down
Loading