diff --git a/compiler-core/src/ast.rs b/compiler-core/src/ast.rs index 56b0bf09cd6..95e5d6f5528 100644 --- a/compiler-core/src/ast.rs +++ b/compiler-core/src/ast.rs @@ -18,7 +18,7 @@ use crate::type_::error::VariableOrigin; use crate::type_::expression::Implementations; use crate::type_::printer::Names; use crate::type_::{ - self, Deprecation, ModuleValueConstructor, PatternConstructor, Type, ValueConstructor, + self, Deprecation, FieldMap, ModuleValueConstructor, PatternConstructor, Type, ValueConstructor, }; use std::sync::Arc; @@ -747,6 +747,23 @@ pub struct ModuleConstant { pub implementations: Implementations, } +impl TypedModuleConstant { + pub(crate) fn field_map(&self) -> Option<&FieldMap> { + match self.value.as_ref() { + Constant::Record { field_map, .. } => field_map.as_ref(), + Constant::Int { .. } + | Constant::Float { .. } + | Constant::String { .. } + | Constant::Tuple { .. } + | Constant::List { .. } + | Constant::BitArray { .. } + | Constant::Var { .. } + | Constant::StringConcatenation { .. } + | Constant::Invalid { .. } => None, + } + } +} + pub type UntypedCustomType = CustomType<()>; pub type TypedCustomType = CustomType>; diff --git a/compiler-core/src/language_server/engine.rs b/compiler-core/src/language_server/engine.rs index 277089ede64..99fa0dadfbd 100644 --- a/compiler-core/src/language_server/engine.rs +++ b/compiler-core/src/language_server/engine.rs @@ -14,8 +14,10 @@ use crate::{ line_numbers::LineNumbers, paths::ProjectPaths, type_::{ - self, Deprecation, ModuleInterface, Type, TypeConstructor, ValueConstructor, - ValueConstructorVariant, error::VariableOrigin, printer::Printer, + self, Deprecation, FieldMap, ModuleInterface, Type, TypeConstructor, ValueConstructor, + ValueConstructorVariant, + error::VariableOrigin, + printer::{Names, Printer}, }, }; use camino::Utf8PathBuf; @@ -27,7 +29,10 @@ use lsp_types::{ PrepareRenameResponse, Range, SignatureHelp, SymbolKind, SymbolTag, TextEdit, Url, WorkspaceEdit, }; -use std::{collections::HashSet, sync::Arc}; +use std::{ + collections::{HashMap, HashSet}, + sync::Arc, +}; use super::{ DownloadDependencies, MakeLocker, @@ -1069,10 +1074,18 @@ fn hover_for_function_head( .map(|(_, doc)| doc) .unwrap_or(&empty_str); let function_type = get_function_type(fun); - let formatted_type = Printer::new(&module.ast.names).print_type(&function_type); + + let index_to_label = fun + .arguments + .iter() + .enumerate() + .filter_map(|(i, arg)| arg.names.get_label().map(|label| (i as u32, label))) + .collect::>(); + + let type_ = Printer::new(&module.ast.names).print_with_labels(&function_type, index_to_label); let contents = format!( "```gleam -{formatted_type} +{type_} ``` {documentation}" ); @@ -1143,7 +1156,7 @@ fn hover_for_module_constant( module: &Module, ) -> Hover { let empty_str = EcoString::from(""); - let type_ = Printer::new(&module.ast.names).print_type(&constant.type_); + let type_ = print_type_with_field_map(&module.ast.names, &constant.type_, constant.field_map()); let documentation = constant .documentation .as_ref() @@ -1171,7 +1184,12 @@ fn hover_for_expression( .unwrap_or("".to_string()); // Show the type of the hovered node to the user - let type_ = Printer::new(&module.ast.names).print_type(expression.type_().as_ref()); + let type_ = print_type_with_field_map( + &module.ast.names, + &expression.type_(), + expression.field_map(), + ); + let contents = format!( "```gleam {type_} @@ -1199,7 +1217,7 @@ fn hover_for_imported_value( }); // Show the type of the hovered node to the user - let type_ = Printer::new(&module.ast.names).print_type(value.type_.as_ref()); + let type_ = print_type_with_field_map(&module.ast.names, &value.type_, value.field_map()); let contents = format!( "```gleam {type_} @@ -1212,6 +1230,19 @@ fn hover_for_imported_value( } } +fn print_type_with_field_map( + names: &Names, + type_: &Type, + field_map: Option<&FieldMap>, +) -> EcoString { + let mut printer = Printer::new(names); + if let Some(field_map) = field_map { + printer.print_with_labels(type_, field_map.indices_to_labels()) + } else { + printer.print_type(type_) + } +} + fn hover_for_module( module: &ModuleInterface, location: SrcSpan, diff --git a/compiler-core/src/language_server/tests/hover.rs b/compiler-core/src/language_server/tests/hover.rs index 084200d75f4..98ee51f3ae8 100644 --- a/compiler-core/src/language_server/tests/hover.rs +++ b/compiler-core/src/language_server/tests/hover.rs @@ -69,6 +69,96 @@ macro_rules! assert_hover { }; } +#[test] +fn hover_function_with_labelled_arguments() { + let code = " +pub fn main() { + labelled +} + +pub fn labelled(a: Int, label b: String) -> Int { 1 } +"; + + assert_hover!( + TestProject::for_source(code), + find_position_of("labelled").under_char('b') + ); +} + +#[test] +fn hover_constructor_with_labelled_arguments() { + let code = " +pub fn main() { + Labelled +} + +pub type Labelled { + Labelled(Int, label: String) +} +"; + + assert_hover!( + TestProject::for_source(code), + find_position_of("Labelled").under_char('b') + ); +} + +#[test] +fn hover_constant_with_labelled_constructor() { + let code = " +pub const wibble = Labelled + +pub type Labelled { + Labelled(Int, label: String) +} +"; + + assert_hover!( + TestProject::for_source(code), + find_position_of("wibble").under_char('i') + ); +} + +#[test] +fn hover_imported_labelled_function() { + let code = " +import wibble.{wobble} +"; + + assert_hover!( + TestProject::for_source(code) + .add_dep_module("wibble", "pub fn wobble(a: Int, label b: String) {}"), + find_position_of("wobble").under_char('i') + ); +} + +#[test] +fn hover_imported_labelled_constructor() { + let code = " +import wibble.{Wobble} +"; + + assert_hover!( + TestProject::for_source(code) + .add_dep_module("wibble", "pub type Wobble { Wobble(Int, label: String) }"), + find_position_of("Wobble").under_char('i') + ); +} + +#[test] +fn hover_labelled_function_head() { + let code = " +pub fn main(a, label b) { + a + b +} +"; + + assert_hover!( + TestProject::for_source(code), + find_position_of("main").under_char('i') + ); +} + #[test] fn hover_function_definition() { assert_hover!( diff --git a/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__hover__hover_constant_with_labelled_constructor.snap b/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__hover__hover_constant_with_labelled_constructor.snap new file mode 100644 index 00000000000..c1bffb44fde --- /dev/null +++ b/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__hover__hover_constant_with_labelled_constructor.snap @@ -0,0 +1,18 @@ +--- +source: compiler-core/src/language_server/tests/hover.rs +expression: "\npub const wibble = Labelled\n\npub type Labelled {\n Labelled(Int, label: String)\n}\n" +--- +pub const wibble = Labelled +▔▔▔▔▔▔▔▔▔▔▔↑▔▔▔▔ + +pub type Labelled { + Labelled(Int, label: String) +} + + +----- Hover content ----- +Scalar( + String( + "```gleam\nfn(Int, label: String) -> Labelled\n```\n", + ), +) diff --git a/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__hover__hover_constructor_with_labelled_arguments.snap b/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__hover__hover_constructor_with_labelled_arguments.snap new file mode 100644 index 00000000000..69659128295 --- /dev/null +++ b/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__hover__hover_constructor_with_labelled_arguments.snap @@ -0,0 +1,20 @@ +--- +source: compiler-core/src/language_server/tests/hover.rs +expression: "\npub fn main() {\n Labelled\n}\n\npub type Labelled {\n Labelled(Int, label: String)\n}\n" +--- +pub fn main() { + Labelled + ▔▔↑▔▔▔▔▔ +} + +pub type Labelled { + Labelled(Int, label: String) +} + + +----- Hover content ----- +Scalar( + String( + "```gleam\nfn(Int, label: String) -> Labelled\n```\n", + ), +) diff --git a/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__hover__hover_function_with_labelled_arguments.snap b/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__hover__hover_function_with_labelled_arguments.snap new file mode 100644 index 00000000000..af7e6395849 --- /dev/null +++ b/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__hover__hover_function_with_labelled_arguments.snap @@ -0,0 +1,18 @@ +--- +source: compiler-core/src/language_server/tests/hover.rs +expression: "\npub fn main() {\n labelled\n}\n\npub fn labelled(a: Int, label b: String) -> Int { 1 }\n" +--- +pub fn main() { + labelled + ▔▔↑▔▔▔▔▔ +} + +pub fn labelled(a: Int, label b: String) -> Int { 1 } + + +----- Hover content ----- +Scalar( + String( + "```gleam\nfn(Int, label: String) -> Int\n```\n", + ), +) diff --git a/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__hover__hover_imported_labelled_constructor.snap b/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__hover__hover_imported_labelled_constructor.snap new file mode 100644 index 00000000000..fca711eaaad --- /dev/null +++ b/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__hover__hover_imported_labelled_constructor.snap @@ -0,0 +1,14 @@ +--- +source: compiler-core/src/language_server/tests/hover.rs +expression: "\nimport wibble.{Wobble}\n" +--- +import wibble.{Wobble} + ↑▔▔▔▔▔ + + +----- Hover content ----- +Scalar( + String( + "```gleam\nfn(Int, label: String) -> wibble.Wobble\n```\n", + ), +) diff --git a/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__hover__hover_imported_labelled_function.snap b/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__hover__hover_imported_labelled_function.snap new file mode 100644 index 00000000000..4b876d6747d --- /dev/null +++ b/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__hover__hover_imported_labelled_function.snap @@ -0,0 +1,14 @@ +--- +source: compiler-core/src/language_server/tests/hover.rs +expression: "\nimport wibble.{wobble}\n" +--- +import wibble.{wobble} + ↑▔▔▔▔▔ + + +----- Hover content ----- +Scalar( + String( + "```gleam\nfn(Int, label: String) -> a\n```\n", + ), +) diff --git a/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__hover__hover_labelled_function_head.snap b/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__hover__hover_labelled_function_head.snap new file mode 100644 index 00000000000..ec9cc21f64e --- /dev/null +++ b/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__hover__hover_labelled_function_head.snap @@ -0,0 +1,16 @@ +--- +source: compiler-core/src/language_server/tests/hover.rs +expression: "\npub fn main(a, label b) {\n a + b\n}\n" +--- +pub fn main(a, label b) { +▔▔▔▔▔▔▔▔▔↑▔▔▔▔▔▔▔▔▔▔▔▔▔ + a + b +} + + +----- Hover content ----- +Scalar( + String( + "```gleam\nfn(Int, label: Int) -> Int\n```\n", + ), +) diff --git a/compiler-core/src/type_/printer.rs b/compiler-core/src/type_/printer.rs index 66685d65999..b66503c4ad9 100644 --- a/compiler-core/src/type_/printer.rs +++ b/compiler-core/src/type_/printer.rs @@ -1,7 +1,10 @@ use bimap::BiMap; use ecow::{EcoString, eco_format}; use im::HashMap; -use std::{collections::HashSet, sync::Arc}; +use std::{ + collections::{HashMap as MutHashMap, HashSet}, + sync::Arc, +}; use crate::type_::{Type, TypeVar}; @@ -314,9 +317,22 @@ impl<'a> Printer<'a> { } } + /// Prints the type of the expression, also adding labels if it's a function + /// with any labelled arguments. + /// + pub fn print_with_labels( + &mut self, + type_: &Type, + index_to_label: MutHashMap, + ) -> EcoString { + let mut buffer = EcoString::new(); + self.print(type_, &index_to_label, &mut buffer, PrintMode::Normal); + buffer + } + pub fn print_type(&mut self, type_: &Type) -> EcoString { let mut buffer = EcoString::new(); - self.print(type_, &mut buffer, PrintMode::Normal); + self.print(type_, &MutHashMap::new(), &mut buffer, PrintMode::Normal); buffer } @@ -329,11 +345,22 @@ impl<'a> Printer<'a> { pub fn print_type_without_aliases(&mut self, type_: &Type) -> EcoString { let mut buffer = EcoString::new(); - self.print(type_, &mut buffer, PrintMode::ExpandAliases); + self.print( + type_, + &MutHashMap::new(), + &mut buffer, + PrintMode::ExpandAliases, + ); buffer } - fn print(&mut self, type_: &Type, buffer: &mut EcoString, print_mode: PrintMode) { + fn print( + &mut self, + type_: &Type, + index_to_label: &MutHashMap, + buffer: &mut EcoString, + print_mode: PrintMode, + ) { match type_ { Type::Named { name, args, module, .. @@ -356,20 +383,22 @@ impl<'a> Printer<'a> { if !args.is_empty() { buffer.push('('); - self.print_arguments(args, buffer, print_mode); + self.print_arguments(args, index_to_label, buffer, print_mode); buffer.push(')'); } } Type::Fn { args, retrn } => { buffer.push_str("fn("); - self.print_arguments(args, buffer, print_mode); + self.print_arguments(args, index_to_label, buffer, print_mode); buffer.push_str(") -> "); - self.print(retrn, buffer, print_mode); + self.print(retrn, index_to_label, buffer, print_mode); } Type::Var { type_, .. } => match *type_.borrow() { - TypeVar::Link { ref type_, .. } => self.print(type_, buffer, print_mode), + TypeVar::Link { ref type_, .. } => { + self.print(type_, index_to_label, buffer, print_mode) + } TypeVar::Unbound { id, .. } | TypeVar::Generic { id, .. } => { buffer.push_str(&self.type_variable(id)) } @@ -377,7 +406,7 @@ impl<'a> Printer<'a> { Type::Tuple { elems, .. } => { buffer.push_str("#("); - self.print_arguments(elems, buffer, print_mode); + self.print_arguments(elems, index_to_label, buffer, print_mode); buffer.push(')'); } } @@ -401,11 +430,15 @@ impl<'a> Printer<'a> { fn print_arguments( &mut self, args: &[Arc], + index_to_label: &MutHashMap, typ_str: &mut EcoString, print_mode: PrintMode, ) { for (i, arg) in args.iter().enumerate() { - self.print(arg, typ_str, print_mode); + if let Some(label) = index_to_label.get(&(i as u32)) { + typ_str.push_str(&format!("{label}: ")); + } + self.print(arg, index_to_label, typ_str, print_mode); if i < args.len() - 1 { typ_str.push_str(", "); }