Skip to content

Commit

Permalink
feat: LSP autocompletion for TypePath (#6117)
Browse files Browse the repository at this point in the history
# Description

## Problem

Autocompletion for `TypePath` didn't exist because it's a recently added
feature.

## Summary

Offers suggestions when you type, for example, `u32::`. For this I had
to allow parsing a `TypePath` without any ident following it (like we do
for paths), still producing a parsing error.


![lsp-complete-type-path](https://github.com/user-attachments/assets/8126f181-aad1-4e57-b94b-53b7b668e083)

Also fixes the issue that built-in types weren't suggest in any position
(only where types are expected, like function argument types, but now
that you can call static methods on these types it makes sense to always
offer them):


![lsp-suggest-type](https://github.com/user-attachments/assets/76748dcb-20ea-485c-92cf-aab6487d6ab4)


## Additional Context

## 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 Sep 20, 2024
1 parent 5b27ea4 commit 3f79d8f
Show file tree
Hide file tree
Showing 3 changed files with 106 additions and 18 deletions.
16 changes: 13 additions & 3 deletions compiler/noirc_frontend/src/parser/parser/path.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
use crate::ast::{AsTraitPath, Path, PathKind, PathSegment, TypePath, UnresolvedType};
use crate::ast::{AsTraitPath, Ident, Path, PathKind, PathSegment, TypePath, UnresolvedType};
use crate::macros_api::ExpressionKind;
use crate::parser::{NoirParser, ParserError, ParserErrorReason};

use crate::token::{Keyword, Token};

use chumsky::prelude::*;
use noirc_errors::Span;

use super::keyword;
use super::primitives::{ident, path_segment, path_segment_no_turbofish, turbofish};
Expand Down Expand Up @@ -78,10 +79,19 @@ pub(super) fn type_path<'a>(
) -> impl NoirParser<ExpressionKind> + 'a {
primitive_type()
.then_ignore(just(Token::DoubleColon))
.then(ident())
.then(ident().or_not())
.then(turbofish(type_parser))
.map(|((typ, item), turbofish)| {
.validate(|((typ, item), turbofish), span, emit| {
let turbofish = turbofish.unwrap_or_default();
let item = if let Some(item) = item {
item
} else {
emit(ParserError::with_reason(
ParserErrorReason::ExpectedIdentifierAfterColons,
span,
));
Ident::new(String::new(), Span::from(span.end()..span.end()))
};
ExpressionKind::TypePath(TypePath { typ, item, turbofish })
})
}
Expand Down
61 changes: 55 additions & 6 deletions tooling/lsp/src/requests/completion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,11 @@ use noirc_frontend::{
ast::{
AsTraitPath, AttributeTarget, BlockExpression, CallExpression, ConstructorExpression,
Expression, ExpressionKind, ForLoopStatement, GenericTypeArgs, Ident, IfExpression,
ItemVisibility, LValue, Lambda, LetStatement, MemberAccessExpression, MethodCallExpression,
NoirFunction, NoirStruct, NoirTraitImpl, Path, PathKind, Pattern, Statement,
TraitImplItemKind, TypeImpl, UnresolvedGeneric, UnresolvedGenerics, UnresolvedType,
UnresolvedTypeData, UseTree, UseTreeKind, Visitor,
IntegerBitSize, ItemVisibility, LValue, Lambda, LetStatement, MemberAccessExpression,
MethodCallExpression, NoirFunction, NoirStruct, NoirTraitImpl, Path, PathKind, Pattern,
Signedness, Statement, TraitImplItemKind, TypeImpl, TypePath, UnresolvedGeneric,
UnresolvedGenerics, UnresolvedType, UnresolvedTypeData, UnresolvedTypeExpression, UseTree,
UseTreeKind, Visitor,
},
graph::{CrateId, Dependency},
hir::def_map::{CrateDefMap, LocalModuleId, ModuleId},
Expand All @@ -29,7 +30,7 @@ use noirc_frontend::{
node_interner::ReferenceId,
parser::{Item, ItemKind, ParsedSubModule},
token::{CustomAttribute, Token, Tokens},
ParsedModule, StructType, Type, TypeBinding,
Kind, ParsedModule, StructType, Type, TypeBinding,
};
use sort_text::underscore_sort_text;

Expand Down Expand Up @@ -362,6 +363,8 @@ impl<'a> NodeFinder<'a> {
self.local_variables_completion(&prefix);
self.builtin_functions_completion(&prefix, function_completion_kind);
self.builtin_values_completion(&prefix);
self.builtin_types_completion(&prefix);
self.type_parameters_completion(&prefix);
if let Some(self_type) = &self.self_type {
let self_prefix = true;
self.complete_type_fields_and_methods(
Expand Down Expand Up @@ -627,7 +630,15 @@ impl<'a> NodeFinder<'a> {
};

for (name, methods) in methods_by_name {
for (func_id, _method_type) in methods.iter() {
for (func_id, method_type) in methods.iter() {
if function_kind == FunctionKind::Any {
if let Some(method_type) = method_type {
if method_type.unify(typ).is_err() {
continue;
}
}
}

if name_matches(name, prefix) {
let completion_items = self.function_completion_items(
name,
Expand Down Expand Up @@ -1553,6 +1564,44 @@ impl<'a> Visitor for NodeFinder<'a> {
false
}

fn visit_type_path(&mut self, type_path: &TypePath, _: Span) -> bool {
if type_path.item.span().end() as usize != self.byte_index {
return true;
}

let typ = match &type_path.typ.typ {
UnresolvedTypeData::FieldElement => Some(Type::FieldElement),
UnresolvedTypeData::Integer(signedness, integer_bit_size) => {
Some(Type::Integer(*signedness, *integer_bit_size))
}
UnresolvedTypeData::Bool => Some(Type::Bool),
UnresolvedTypeData::String(UnresolvedTypeExpression::Constant(value, _)) => {
Some(Type::String(Box::new(Type::Constant(
*value,
Kind::Numeric(Box::new(Type::Integer(
Signedness::Unsigned,
IntegerBitSize::ThirtyTwo,
))),
))))
}
UnresolvedTypeData::Quoted(quoted_type) => Some(Type::Quoted(*quoted_type)),
_ => None,
};

if let Some(typ) = typ {
let prefix = &type_path.item.0.contents;
self.complete_type_methods(
&typ,
prefix,
FunctionKind::Any,
FunctionCompletionKind::NameAndParameters,
false, // self_prefix
);
}

false
}

fn visit_custom_attribute(&mut self, attribute: &CustomAttribute, target: AttributeTarget) {
if self.byte_index != attribute.contents_span.end() as usize {
return;
Expand Down
47 changes: 38 additions & 9 deletions tooling/lsp/src/requests/completion/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,27 @@ mod completion_tests {
.await;
}

#[test]
async fn test_complete_type_path_with_non_empty_name() {
let src = r#"
trait One {
fn one() -> Self;
}
impl One for Field {
fn one() -> Self {
1
}
}
fn main() {
Field::o>|<
}
"#;
assert_completion(src, vec![function_completion_item("one()", "one()", "fn() -> Field")])
.await;
}

#[test]
async fn test_complete_function_without_arguments() {
let src = r#"
Expand Down Expand Up @@ -756,22 +777,30 @@ mod completion_tests {
);
}

#[test]
async fn test_suggest_builtin_types_in_any_position() {
let src = r#"
fn foo() {
i>|<
}
"#;

let items = get_completions(src).await;
assert!(items.iter().any(|item| item.label == "i8"));
}

#[test]
async fn test_suggest_true() {
let src = r#"
fn main() {
let x = t>|<
}
"#;
assert_completion_excluding_auto_import(
src,
vec![simple_completion_item(
"true",
CompletionItemKind::KEYWORD,
Some("bool".to_string()),
)],
)
.await;

let items = get_completions(src).await;
assert!(items
.iter()
.any(|item| item.label == "true" && item.kind == Some(CompletionItemKind::KEYWORD)));
}

#[test]
Expand Down

0 comments on commit 3f79d8f

Please sign in to comment.