Skip to content

Commit

Permalink
feat: allow inserting LSP inlay type hints (#5620)
Browse files Browse the repository at this point in the history
# Description

## Problem

Resolves #5527

## Summary

Makes type hints insertable, though not all of them can be inserted. For
example a for variable can't have a type annotation, and a struct member
pattern can't either.

I also added a test for when the type hint is shown for a struct member
pattern, which was missing (mainly to assert that the type there isn't
insertable).



https://github.com/user-attachments/assets/b3a02f2b-be82-49b5-9ac5-cebf8cb83214



## Additional Context

None.

## Documentation

Check one:
- [x] No documentation needed.
- [ ] Documentation included in this PR.
- [ ] **[For Experimental Features]** Documentation to be submitted in a
separate PR.

# PR Checklist\*

- [x] I have tested the changes locally.
- [x] I have formatted the changes with [Prettier](https://prettier.io/)
and/or `cargo fmt` on default settings.
  • Loading branch information
asterite authored Jul 29, 2024
1 parent b3c408b commit b33495d
Show file tree
Hide file tree
Showing 2 changed files with 78 additions and 13 deletions.
88 changes: 75 additions & 13 deletions tooling/lsp/src/requests/inlay_hint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ use std::future::{self, Future};
use async_lsp::ResponseError;
use fm::{FileId, FileMap, PathString};
use lsp_types::{
InlayHint, InlayHintKind, InlayHintLabel, InlayHintLabelPart, InlayHintParams, Position,
TextDocumentPositionParams,
InlayHint, InlayHintKind, InlayHintLabel, InlayHintLabelPart, InlayHintParams, Position, Range,
TextDocumentPositionParams, TextEdit,
};
use noirc_errors::{Location, Span};
use noirc_frontend::{
Expand Down Expand Up @@ -173,7 +173,7 @@ impl<'a> InlayHintCollector<'a> {
self.collect_in_expression(&assign_statement.expression);
}
StatementKind::For(for_loop_statement) => {
self.collect_in_ident(&for_loop_statement.identifier);
self.collect_in_ident(&for_loop_statement.identifier, false);
self.collect_in_expression(&for_loop_statement.block);
}
StatementKind::Comptime(statement) => self.collect_in_statement(statement),
Expand Down Expand Up @@ -276,7 +276,7 @@ impl<'a> InlayHintCollector<'a> {

match pattern {
Pattern::Identifier(ident) => {
self.collect_in_ident(ident);
self.collect_in_ident(ident, true);
}
Pattern::Mutable(pattern, _span, _is_synthesized) => {
self.collect_in_pattern(pattern);
Expand All @@ -294,7 +294,7 @@ impl<'a> InlayHintCollector<'a> {
}
}

fn collect_in_ident(&mut self, ident: &Ident) {
fn collect_in_ident(&mut self, ident: &Ident, editable: bool) {
if !self.options.type_hints.enabled {
return;
}
Expand All @@ -308,17 +308,17 @@ impl<'a> InlayHintCollector<'a> {
let global_info = self.interner.get_global(global_id);
let definition_id = global_info.definition_id;
let typ = self.interner.definition_type(definition_id);
self.push_type_hint(lsp_location, &typ);
self.push_type_hint(lsp_location, &typ, editable);
}
ReferenceId::Local(definition_id) => {
let typ = self.interner.definition_type(definition_id);
self.push_type_hint(lsp_location, &typ);
self.push_type_hint(lsp_location, &typ, editable);
}
ReferenceId::StructMember(struct_id, field_index) => {
let struct_type = self.interner.get_struct(struct_id);
let struct_type = struct_type.borrow();
let (_field_name, field_type) = struct_type.field_at(field_index);
self.push_type_hint(lsp_location, field_type);
self.push_type_hint(lsp_location, field_type, false);
}
ReferenceId::Module(_)
| ReferenceId::Struct(_)
Expand All @@ -331,7 +331,7 @@ impl<'a> InlayHintCollector<'a> {
}
}

fn push_type_hint(&mut self, location: lsp_types::Location, typ: &Type) {
fn push_type_hint(&mut self, location: lsp_types::Location, typ: &Type, editable: bool) {
let position = location.range.end;

let mut parts = Vec::new();
Expand All @@ -342,7 +342,14 @@ impl<'a> InlayHintCollector<'a> {
position,
label: InlayHintLabel::LabelParts(parts),
kind: Some(InlayHintKind::TYPE),
text_edits: None,
text_edits: if editable {
Some(vec![TextEdit {
range: Range { start: location.range.end, end: location.range.end },
new_text: format!(": {}", typ),
}])
} else {
None
},
tooltip: None,
padding_left: None,
padding_right: None,
Expand Down Expand Up @@ -756,8 +763,10 @@ mod inlay_hints_tests {
let inlay_hints = get_inlay_hints(0, 3, type_hints()).await;
assert_eq!(inlay_hints.len(), 1);

let position = Position { line: 1, character: 11 };

let inlay_hint = &inlay_hints[0];
assert_eq!(inlay_hint.position, Position { line: 1, character: 11 });
assert_eq!(inlay_hint.position, position);

if let InlayHintLabel::LabelParts(labels) = &inlay_hint.label {
assert_eq!(labels.len(), 2);
Expand All @@ -770,15 +779,25 @@ mod inlay_hints_tests {
} else {
panic!("Expected InlayHintLabel::LabelParts, got {:?}", inlay_hint.label);
}

assert_eq!(
inlay_hint.text_edits,
Some(vec![TextEdit {
range: Range { start: position, end: position },
new_text: ": Field".to_string(),
}])
);
}

#[test]
async fn test_type_inlay_hints_with_location() {
let inlay_hints = get_inlay_hints(12, 15, type_hints()).await;
assert_eq!(inlay_hints.len(), 1);

let position = Position { line: 13, character: 11 };

let inlay_hint = &inlay_hints[0];
assert_eq!(inlay_hint.position, Position { line: 13, character: 11 });
assert_eq!(inlay_hint.position, position);

if let InlayHintLabel::LabelParts(labels) = &inlay_hint.label {
assert_eq!(labels.len(), 2);
Expand All @@ -798,6 +817,34 @@ mod inlay_hints_tests {
} else {
panic!("Expected InlayHintLabel::LabelParts, got {:?}", inlay_hint.label);
}

assert_eq!(
inlay_hint.text_edits,
Some(vec![TextEdit {
range: Range { start: position, end: position },
new_text: ": Foo".to_string(),
}])
);
}

#[test]
async fn test_type_inlay_hints_in_struct_member_pattern() {
let inlay_hints = get_inlay_hints(94, 96, type_hints()).await;
assert_eq!(inlay_hints.len(), 1);

let inlay_hint = &inlay_hints[0];
assert_eq!(inlay_hint.position, Position { line: 95, character: 24 });

if let InlayHintLabel::LabelParts(labels) = &inlay_hint.label {
assert_eq!(labels.len(), 2);
assert_eq!(labels[0].value, ": ");
assert_eq!(labels[0].location, None);
assert_eq!(labels[1].value, "i32");
} else {
panic!("Expected InlayHintLabel::LabelParts, got {:?}", inlay_hint.label);
}

assert_eq!(inlay_hint.text_edits, None);
}

#[test]
Expand All @@ -816,15 +863,19 @@ mod inlay_hints_tests {
} else {
panic!("Expected InlayHintLabel::LabelParts, got {:?}", inlay_hint.label);
}

assert_eq!(inlay_hint.text_edits, None);
}

#[test]
async fn test_type_inlay_hints_in_global() {
let inlay_hints = get_inlay_hints(19, 21, type_hints()).await;
assert_eq!(inlay_hints.len(), 1);

let position = Position { line: 20, character: 10 };

let inlay_hint = &inlay_hints[0];
assert_eq!(inlay_hint.position, Position { line: 20, character: 10 });
assert_eq!(inlay_hint.position, position);

if let InlayHintLabel::LabelParts(labels) = &inlay_hint.label {
assert_eq!(labels.len(), 2);
Expand All @@ -834,6 +885,14 @@ mod inlay_hints_tests {
} else {
panic!("Expected InlayHintLabel::LabelParts, got {:?}", inlay_hint.label);
}

assert_eq!(
inlay_hint.text_edits,
Some(vec![TextEdit {
range: Range { start: position, end: position },
new_text: ": Field".to_string(),
}])
);
}

#[test]
Expand All @@ -855,6 +914,7 @@ mod inlay_hints_tests {

let inlay_hint = &inlay_hints[0];
assert_eq!(inlay_hint.position, Position { line: 25, character: 12 });
assert_eq!(inlay_hint.text_edits, None);
if let InlayHintLabel::String(label) = &inlay_hint.label {
assert_eq!(label, "one: ");
} else {
Expand All @@ -863,6 +923,7 @@ mod inlay_hints_tests {

let inlay_hint = &inlay_hints[1];
assert_eq!(inlay_hint.position, Position { line: 25, character: 15 });
assert_eq!(inlay_hint.text_edits, None);
if let InlayHintLabel::String(label) = &inlay_hint.label {
assert_eq!(label, "two: ");
} else {
Expand All @@ -877,6 +938,7 @@ mod inlay_hints_tests {

let inlay_hint = &inlay_hints[0];
assert_eq!(inlay_hint.position, Position { line: 38, character: 18 });
assert_eq!(inlay_hint.text_edits, None);
if let InlayHintLabel::String(label) = &inlay_hint.label {
assert_eq!(label, "one: ");
} else {
Expand Down
3 changes: 3 additions & 0 deletions tooling/lsp/test_programs/inlay_hints/src/main.nr
Original file line number Diff line number Diff line change
Expand Up @@ -92,3 +92,6 @@ fn call_yet_another_function() {
yet_another_function(some_name) // Should not show parameter names ("name" is a suffix of "some_name")
}

fn struct_member_hint() {
let SomeStruct { one } = SomeStruct { one: 1 };
}

0 comments on commit b33495d

Please sign in to comment.