Skip to content

Add hover info for custom types #4458

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

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 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
13 changes: 7 additions & 6 deletions compiler-core/src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -912,16 +912,17 @@ impl TypedDefinition {
.iter()
.find(|constructor| constructor.location.contains(byte_index))
{
if let Some(annotation) = constructor
return match constructor
.arguments
.iter()
.find(|arg| arg.location.contains(byte_index))
.and_then(|arg| arg.ast.find_node(byte_index, arg.type_.clone()))
{
return Some(annotation);
}

return Some(Located::VariantConstructorDefinition(constructor));
Some(arg) => match arg.ast.find_node(byte_index, arg.type_.clone()) {
Some(annotation) => Some(annotation),
None => Some(Located::Label(arg.location, arg.type_.clone())),
},
None => Some(Located::VariantConstructorDefinition(constructor)),
};
}

// Note that the custom type `.location` covers the function
Expand Down
55 changes: 54 additions & 1 deletion compiler-core/src/language_server/engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use crate::{
ast::{
self, CustomType, Definition, DefinitionLocation, ModuleConstant, PatternUnusedArguments,
SrcSpan, TypedArg, TypedExpr, TypedFunction, TypedModule, TypedPattern,
TypedRecordConstructor,
},
build::{Located, Module, UnqualifiedImport, type_constructor_from_modules},
config::PackageConfig,
Expand Down Expand Up @@ -822,6 +823,9 @@ where
Located::ModuleStatement(Definition::Function(fun)) => {
Some(hover_for_function_head(fun, lines, module))
}
Located::ModuleStatement(Definition::CustomType(type_)) => {
Some(hover_for_custom_type(type_, lines))
}
Located::ModuleStatement(Definition::ModuleConstant(constant)) => {
Some(hover_for_module_constant(constant, lines, module))
}
Expand All @@ -837,7 +841,9 @@ where
))
}
Located::ModuleStatement(_) => None,
Located::VariantConstructorDefinition(_) => None,
Located::VariantConstructorDefinition(constructor) => {
Some(hover_for_constructor(constructor, lines, module))
}
Located::UnqualifiedImport(UnqualifiedImport {
name,
module: module_name,
Expand Down Expand Up @@ -1242,6 +1248,53 @@ fn hover_for_module_constant(
}
}

fn hover_for_custom_type(type_: &CustomType<Arc<Type>>, line_numbers: LineNumbers) -> Hover {
let name = &type_.name;
let empty_str = EcoString::from("");
let documentation = type_
.documentation
.as_ref()
.map(|(_, doc)| doc)
.unwrap_or(&empty_str);
let contents = format!("```gleam\n{name}\n```\n{documentation}");
Hover {
contents: HoverContents::Scalar(MarkedString::String(contents)),
range: Some(src_span_to_lsp_range(type_.full_location(), &line_numbers)),
}
}

fn hover_for_constructor(
constructor: &TypedRecordConstructor,
line_numbers: LineNumbers,
module: &Module,
) -> Hover {
let name = &constructor.name;
let mut arguments = Vec::with_capacity(constructor.arguments.len());
let mut printer = Printer::new(&module.ast.names);

for argument in &constructor.arguments {
let type_ = printer.print_type(&argument.type_);
let arg = match argument.label.as_ref() {
Some((_, label)) => format!("{label}: {type_}"),
None => format!("{type_}"),
};
arguments.push(arg);
}

let arguments = arguments.join(", ");
let empty_str = EcoString::from("");
let documentation = constructor
.documentation
.as_ref()
.map(|(_, doc)| doc)
.unwrap_or(&empty_str);
let contents = format!("```gleam\n{name}({arguments})\n```\n{documentation}");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is different from the format we use for variants!

image

Can we reuse the existing code to ensure they are the same?

Copy link
Contributor Author

@unknown unknown May 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I made a note of that here: #4451 (comment)

It's been a while since I worked on this, but if I remember correctly, the reason it differs is because RecordConstructor doesn't store any information about the custom type its a member of (aka the return type of the constructor). Should I add a string in RecordConstructor with the name of the custom type?

Not sure if there are other approaches, but adding a string wouldn't allow for code reuse. The return type would have to be a Type.

Hover {
contents: HoverContents::Scalar(MarkedString::String(contents)),
range: Some(src_span_to_lsp_range(constructor.location, &line_numbers)),
}
}

fn hover_for_expression(
expression: &TypedExpr,
line_numbers: LineNumbers,
Expand Down
68 changes: 68 additions & 0 deletions compiler-core/src/language_server/tests/hover.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1589,3 +1589,71 @@ import wibble
find_position_of("wibble")
);
}

#[test]
fn hover_custom_type() {
assert_hover!(
"
/// Exciting documentation
/// Maybe even multiple lines
type Wibble {
/// Some more exciting documentation
Wibble(String)
/// The most exciting documentation
Wobble(arg: Int)
}
",
find_position_of("Wibble")
);
}

#[test]
fn hover_type_constructor() {
assert_hover!(
"
/// Exciting documentation
/// Maybe even multiple lines
type Wibble {
/// Some more exciting documentation
Wibble(arg: String)
/// The most exciting documentation
Wobble(Int)
}
",
find_position_of("Wibble").nth_occurrence(2)
);
}

#[test]
fn hover_type_constructor_with_label() {
assert_hover!(
"
/// Exciting documentation
/// Maybe even multiple lines
type Wibble {
/// Some more exciting documentation
Wibble(arg: String)
/// The most exciting documentation
Wobble(Int)
}
",
find_position_of("Wobble")
);
}

#[test]
fn hover_for_label_in_type_constructor() {
assert_hover!(
"
/// Exciting documentation
/// Maybe even multiple lines
type Wibble {
/// Some more exciting documentation
Wibble(arg: String)
/// The most exciting documentation
Wobble(Int)
}
",
find_position_of("arg")
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
---
source: compiler-core/src/language_server/tests/hover.rs
expression: "\n/// Exciting documentation\n/// Maybe even multiple lines\ntype Wibble {\n /// Some more exciting documentation\n Wibble(String)\n /// The most exciting documentation\n Wobble(arg: Int)\n}\n"
---
/// Exciting documentation
/// Maybe even multiple lines
type Wibble {
▔▔▔▔▔↑▔▔▔▔▔▔▔
/// Some more exciting documentation
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
Wibble(String)
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
/// The most exciting documentation
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
Wobble(arg: Int)
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
}


----- Hover content -----
Scalar(
String(
"```gleam\nWibble\n```\n Exciting documentation\n Maybe even multiple lines",
),
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
---
source: compiler-core/src/language_server/tests/hover.rs
expression: "\n/// Exciting documentation\n/// Maybe even multiple lines\ntype Wibble {\n /// Some more exciting documentation\n Wibble(arg: String)\n /// The most exciting documentation\n Wobble(Int)\n}\n"
---
/// Exciting documentation
/// Maybe even multiple lines
type Wibble {
/// Some more exciting documentation
Wibble(arg: String)
↑▔▔▔▔▔▔▔▔▔▔
/// The most exciting documentation
Wobble(Int)
}


----- Hover content -----
Scalar(
String(
"```gleam\nString\n```",
),
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
---
source: compiler-core/src/language_server/tests/hover.rs
expression: "\n/// Exciting documentation\n/// Maybe even multiple lines\ntype Wibble {\n /// Some more exciting documentation\n Wibble(arg: String)\n /// The most exciting documentation\n Wobble(Int)\n}\n"
---
/// Exciting documentation
/// Maybe even multiple lines
type Wibble {
/// Some more exciting documentation
Wibble(arg: String)
↑▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
/// The most exciting documentation
Wobble(Int)
}


----- Hover content -----
Scalar(
String(
"```gleam\nWibble(arg: String)\n```\n Some more exciting documentation",
),
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
---
source: compiler-core/src/language_server/tests/hover.rs
expression: "\n/// Exciting documentation\n/// Maybe even multiple lines\ntype Wibble {\n /// Some more exciting documentation\n Wibble(arg: String)\n /// The most exciting documentation\n Wobble(Int)\n}\n"
---
/// Exciting documentation
/// Maybe even multiple lines
type Wibble {
/// Some more exciting documentation
Wibble(arg: String)
/// The most exciting documentation
Wobble(Int)
↑▔▔▔▔▔▔▔▔▔▔
}


----- Hover content -----
Scalar(
String(
"```gleam\nWobble(Int)\n```\n The most exciting documentation",
),
)