Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add inlay hints for function parameters #6513

Merged
merged 18 commits into from
Sep 23, 2024
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
146 changes: 111 additions & 35 deletions sway-lsp/src/capabilities/inlay_hints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,16 @@ use crate::{
};
use lsp_types::{self, Range, Url};
use std::sync::Arc;
use sway_core::{language::ty::TyDecl, type_system::TypeInfo};
use sway_types::Spanned;
use sway_core::{
language::ty::{TyDecl, TyExpression, TyExpressionVariant},
type_system::TypeInfo,
};
use sway_types::{Ident, Spanned};

// Future PR's will add more kinds
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum InlayKind {
TypeHint,
Parameter,
}

#[derive(Debug)]
Expand All @@ -23,81 +26,154 @@ pub struct InlayHint {
pub label: String,
}

/// Generates inlay hints for the provided range.
pub fn inlay_hints(
session: Arc<Session>,
uri: &Url,
range: &Range,
config: &InlayHintsConfig,
) -> Option<Vec<lsp_types::InlayHint>> {
let _p = tracing::trace_span!("inlay_hints").entered();
// 1. Loop through all our tokens and filter out all tokens that aren't TypedVariableDeclaration tokens
// 2. Also filter out all tokens that have a span that fall outside of the provided range
// 3. Filter out all variable tokens that have a type_ascription
// 4. Look up the type id for the remaining tokens
// 5. Convert the type into a string
let _span = tracing::trace_span!("inlay_hints").entered();

if !config.type_hints {
return None;
}

// 1. Iterate through all tokens in the file
// 2. Filter for TypedVariableDeclaration tokens within the provided range
// 3. For each variable declaration:
// a. If it's a function application, generate parameter hints
// b. If it doesn't have a type ascription and its type is known:
// - Look up the type information
// - Generate a type hint
// 4. Collect all generated hints into a single vector
let hints: Vec<lsp_types::InlayHint> = session
.token_map()
.tokens_for_file(uri)
.filter_map(|item| {
let token = item.value();
token.typed.as_ref().and_then(|t| match t {
TypedAstToken::TypedDeclaration(TyDecl::VariableDecl(var_decl)) => {
if var_decl.type_ascription.call_path_tree.is_some() {
None
let var_range = get_range_from_span(&var_decl.name.span());
if var_range.start >= range.start && var_range.end <= range.end {
Some(var_decl.clone())
} else {
let var_range = get_range_from_span(&var_decl.name.span());
if var_range.start >= range.start && var_range.end <= range.end {
Some(var_decl.clone())
} else {
None
}
None
}
}
_ => None,
})
})
.filter_map(|var| {
let type_info = session.engines.read().te().get(var.type_ascription.type_id);
match &*type_info {
TypeInfo::Unknown | TypeInfo::UnknownGeneric { .. } => None,
_ => Some(var),
.flat_map(|var| {
let mut hints = Vec::new();

// Function parameter hints
if let TyExpressionVariant::FunctionApplication { arguments, .. } = &var.body.expression
{
hints.extend(handle_function_parameters(arguments, config));
}
})
.map(|var| {
let range = get_range_from_span(&var.name.span());
let kind = InlayKind::TypeHint;
let label = format!("{}", session.engines.read().help_out(var.type_ascription));
let inlay_hint = InlayHint { range, kind, label };
self::inlay_hint(config.render_colons, inlay_hint)

// Variable declaration hints
if var.type_ascription.call_path_tree.is_none() {
let type_info = session.engines.read().te().get(var.type_ascription.type_id);
if !matches!(
*type_info,
TypeInfo::Unknown | TypeInfo::UnknownGeneric { .. }
) {
let range = get_range_from_span(&var.name.span());
let kind = InlayKind::TypeHint;
let label = format!("{}", session.engines.read().help_out(var.type_ascription));
let inlay_hint = InlayHint { range, kind, label };
hints.push(self::inlay_hint(config, inlay_hint));
}
}
hints
})
.collect();
IGI-111 marked this conversation as resolved.
Show resolved Hide resolved

Some(hints)
}

fn inlay_hint(render_colons: bool, inlay_hint: InlayHint) -> lsp_types::InlayHint {
fn handle_function_parameters(
arguments: &[(Ident, TyExpression)],
config: &InlayHintsConfig,
) -> Vec<lsp_types::InlayHint> {
arguments
.iter()
.flat_map(|(name, exp)| {
let mut hints = Vec::new();
let (should_create_hint, span) = match &exp.expression {
TyExpressionVariant::Literal(_)
| TyExpressionVariant::ConstantExpression { .. }
| TyExpressionVariant::Tuple { .. }
| TyExpressionVariant::Array { .. }
| TyExpressionVariant::ArrayIndex { .. }
| TyExpressionVariant::FunctionApplication { .. }
| TyExpressionVariant::StructFieldAccess { .. }
| TyExpressionVariant::TupleElemAccess { .. } => (true, &exp.span),
TyExpressionVariant::EnumInstantiation {
call_path_binding, ..
} => (true, &call_path_binding.span),
_ => (false, &exp.span),
};
if should_create_hint {
let range = get_range_from_span(span);
let kind = InlayKind::Parameter;
let label = name.as_str().to_string();
let inlay_hint = InlayHint { range, kind, label };
hints.push(self::inlay_hint(config, inlay_hint));
}
// Handle nested function applications
if let TyExpressionVariant::FunctionApplication {
arguments: nested_args,
..
} = &exp.expression
{
hints.extend(handle_function_parameters(nested_args, config));
}
hints
})
.collect::<Vec<_>>()
}

fn inlay_hint(config: &InlayHintsConfig, inlay_hint: InlayHint) -> lsp_types::InlayHint {
let truncate_label = |label: String| -> String {
if let Some(max_length) = config.max_length {
if label.len() > max_length {
format!("{}...", &label[..max_length.saturating_sub(3)])
} else {
label
}
} else {
label
}
};
IGI-111 marked this conversation as resolved.
Show resolved Hide resolved

let label = match inlay_hint.kind {
InlayKind::TypeHint if config.render_colons => format!(": {}", inlay_hint.label),
InlayKind::Parameter if config.render_colons => format!("{}: ", inlay_hint.label),
_ => inlay_hint.label,
};

lsp_types::InlayHint {
position: match inlay_hint.kind {
// after annotated thing
InlayKind::TypeHint => inlay_hint.range.end,
InlayKind::Parameter => inlay_hint.range.start,
},
label: lsp_types::InlayHintLabel::String(match inlay_hint.kind {
InlayKind::TypeHint if render_colons => format!(": {}", inlay_hint.label),
InlayKind::TypeHint => inlay_hint.label,
}),
label: lsp_types::InlayHintLabel::String(truncate_label(label)),
kind: match inlay_hint.kind {
InlayKind::TypeHint => Some(lsp_types::InlayHintKind::TYPE),
InlayKind::Parameter => Some(lsp_types::InlayHintKind::PARAMETER),
},
tooltip: None,
padding_left: Some(match inlay_hint.kind {
InlayKind::TypeHint => !render_colons,
InlayKind::TypeHint => !config.render_colons,
InlayKind::Parameter => false,
}),
padding_right: Some(match inlay_hint.kind {
InlayKind::TypeHint => false,
InlayKind::Parameter => !config.render_colons,
}),
text_edits: None,
data: None,
Expand Down
2 changes: 1 addition & 1 deletion sway-lsp/src/handlers/request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,7 @@ pub async fn handle_semantic_tokens_full(
}
}

pub(crate) async fn handle_inlay_hints(
pub async fn handle_inlay_hints(
state: &ServerState,
params: InlayHintParams,
) -> Result<Option<Vec<InlayHint>>> {
Expand Down
2 changes: 2 additions & 0 deletions sway-lsp/tests/fixtures/inlay_hints/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
out
target
9 changes: 9 additions & 0 deletions sway-lsp/tests/fixtures/inlay_hints/Forc.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[project]
authors = ["Fuel Labs <[email protected]>"]
entry = "main.sw"
license = "Apache-2.0"
name = "inlay_hints"
implicit-std = false

[dependencies]
std = { git = "https://github.com/FuelLabs/sway", tag = "v0.63.5" }
51 changes: 51 additions & 0 deletions sway-lsp/tests/fixtures/inlay_hints/src/main.sw
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
script;

const CONSTANT: u64 = 42;

enum MyEnum {
A: u64,
}
struct MyStruct {
a: u64,
}
fn my_function(foo: u64, bar: u64, long_argument_name: u64) -> u64 {
foo + bar + long_argument_name
}
fn identity<T>(x: T) -> T {
x
}
fn two_generics<A, B>(_a: A, b: B) -> B {
b
}
fn three_generics<A, B, C>(a: A, b: B, _c: C) -> B {
let _a: A = a;
b
}

fn main() {
let _x = my_function(1, 2, 3);
let foo = 1;
let _y = my_function(foo, 2, 3);
let bar = 2;
let _function_call = identity(my_function(1, bar, 3));
let _z = my_function(foo, bar, 3);
let long_argument_name = 3;
let _w = my_function(foo, bar, long_argument_name);
let _a: bool = identity(true);
let _b: u32 = identity(10u32);
let _c: u64 = identity(42);
let _e: str = identity("foo");
let _f: u64 = two_generics(true, 10);
let _g: str = three_generics(true, "foo", 10);
let _const = identity(CONSTANT);
let _tuple = identity((1, 2, 3));
let _array = identity([1, 2, 3]);
let _enum = identity(MyEnum::A(1));
let s = MyStruct { a: 1 };
let _struct_field_access = identity(s.a);
let t = (0, 1, 9);
let _tuple_elem_access = identity(t.2);
let a = [1, 2, 3];
let _array_index = identity(a[1]);
}

Loading
Loading