Skip to content

Commit 770bfa9

Browse files
scristoballpil
authored andcommitted
Function signature helper now respects original definition generic names when arguments are unbound
1 parent de7944d commit 770bfa9

10 files changed

+203
-17
lines changed

CHANGELOG.md

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,7 @@
204204
([Hari Mohan](https://github.com/seafoamteal))
205205

206206
- The language server now supports renaming, go to definition, hover, and
207-
finding references from expressions in case clause guards.
207+
finding references from expressions in case clause guards.
208208
([Surya Rose](https://github.com/GearsDatapacks))
209209

210210
- The language server now supports `textDocument/foldingRange`, enabling
@@ -227,6 +227,33 @@
227227

228228
([Aayush Tripathi](https://github.com/aayush-tripathi))
229229

230+
- The function signature helper now displays original function definition
231+
generic names when arguments are unbound. For example, in this code:
232+
233+
```gleam
234+
pub fn wibble(x: something, y: fn() -> something, z: anything) { Nil }
235+
236+
pub fn main() {
237+
wibble( )
238+
// ↑
239+
}
240+
```
241+
242+
will show a signature help
243+
244+
```gleam
245+
wibble(something, fn() -> something, anything)
246+
247+
```
248+
249+
instead of
250+
251+
```gleam
252+
wibble(a, fn() -> a, b) -> Nil
253+
```
254+
255+
([Samuel Cristobal](https://github.com/scristobal))
256+
230257
### Formatter
231258

232259
- The formatter no longer wraps multiple tuple or field access into a block.

compiler-core/src/analyse.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,10 @@ impl<'a, A> ModuleAnalyzer<'a, A> {
346346
self.check_for_type_leaks(value)
347347
}
348348

349+
// Resolve deferred type variable aliases now that all unification is
350+
// done and link chains are stable.
351+
env.resolve_deferred_type_variable_aliases();
352+
349353
let Environment {
350354
module_types: types,
351355
module_types_constructors: types_constructors,

compiler-core/src/type_/environment.rs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,13 @@ pub struct Environment<'a> {
8686

8787
pub names: Names,
8888

89+
/// Deferred type variable aliases: `(source_generic_id, instantiated_type)`.
90+
/// During `instantiate`, when a generic type variable is replaced with a new
91+
/// unbound var, we record the pair here. After type checking, we resolve
92+
/// these by following link chains in the types to find the final unbound var
93+
/// IDs and register the original names for them.
94+
pub deferred_type_variable_aliases: Vec<(u64, Arc<Type>)>,
95+
8996
/// Wether we ran into an `echo` or not while analysing the current module.
9097
pub echo_found: bool,
9198

@@ -143,6 +150,7 @@ impl<'a> Environment<'a> {
143150
local_variable_usages: vec![HashMap::new()],
144151
target_support,
145152
names,
153+
deferred_type_variable_aliases: Vec::new(),
146154
module_type_aliases: HashMap::new(),
147155
echo_found: false,
148156
references: ReferenceTracker::new(),
@@ -258,6 +266,38 @@ impl Environment<'_> {
258266
self.previous_id
259267
}
260268

269+
/// Resolve deferred type variable aliases. During `instantiate`, generic
270+
/// type variables are replaced with new unbound vars, but those vars may
271+
/// later get linked to other vars during unification. This method follows
272+
/// the link chains to find the final unbound var IDs and registers the
273+
/// original names for them.
274+
pub fn resolve_deferred_type_variable_aliases(&mut self) {
275+
for (source_id, type_) in self.deferred_type_variable_aliases.drain(..) {
276+
if let Some(alias) = self.names.get_type_variable(source_id).cloned() {
277+
let final_id = Self::resolve_type_var_id(&type_);
278+
if let Some(id) = final_id {
279+
// Only register if the target doesn't already have a name.
280+
// This avoids overwriting user-provided names (e.g. from
281+
// annotations) with names from instantiated type parameters.
282+
if self.names.get_type_variable(id).is_none() {
283+
self.names.type_variable_in_scope(id, alias);
284+
}
285+
}
286+
}
287+
}
288+
}
289+
290+
/// Follow link chains in a type to find the final unbound or generic var ID.
291+
fn resolve_type_var_id(type_: &Type) -> Option<u64> {
292+
match type_ {
293+
Type::Var { type_ } => match type_.borrow().deref() {
294+
TypeVar::Unbound { id } | TypeVar::Generic { id } => Some(*id),
295+
TypeVar::Link { type_ } => Self::resolve_type_var_id(type_),
296+
},
297+
Type::Named { .. } | Type::Fn { .. } | Type::Tuple { .. } => None,
298+
}
299+
}
300+
261301
/// Create a new unbound type that is a specific type, we just don't
262302
/// know which one yet.
263303
///
@@ -609,6 +649,12 @@ impl Environment<'_> {
609649
// Check this in the hydrator, i.e. is it a created type
610650
let v = self.new_unbound_var();
611651
let _ = ids.insert(*id, v.clone());
652+
653+
// Record this pairing so that after type checking
654+
// we can resolve any link chains and register the
655+
// original name for the final unbound variable.
656+
self.deferred_type_variable_aliases.push((*id, v.clone()));
657+
612658
return v;
613659
}
614660
}

language-server/src/engine.rs

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1129,15 +1129,21 @@ Unused labelled fields:
11291129
&mut self,
11301130
params: lsp_types::SignatureHelpParams,
11311131
) -> Response<Option<SignatureHelp>> {
1132-
self.respond(
1133-
|this| match this.node_at_position(&params.text_document_position_params) {
1132+
self.respond(|this| {
1133+
let Some(module) =
1134+
this.module_for_uri(&params.text_document_position_params.text_document.uri)
1135+
else {
1136+
return Ok(None);
1137+
};
1138+
1139+
match this.node_at_position(&params.text_document_position_params) {
11341140
Some((_lines, Located::Expression { expression, .. })) => {
1135-
Ok(signature_help::for_expression(expression))
1141+
Ok(signature_help::for_expression(expression, module))
11361142
}
11371143
Some((_lines, _located)) => Ok(None),
11381144
None => Ok(None),
1139-
},
1140-
)
1145+
}
1146+
})
11411147
}
11421148

11431149
fn module_node_at_position(

language-server/src/signature_help.rs

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,11 @@ use lsp_types::{
1111

1212
use gleam_core::{
1313
ast::{CallArg, ImplicitCallArgOrigin, TypedExpr},
14-
type_::{FieldMap, ModuleValueConstructor, Type, pretty::Printer},
14+
build::Module,
15+
type_::{FieldMap, ModuleValueConstructor, Type, printer::Printer},
1516
};
1617

17-
pub fn for_expression(expr: &TypedExpr) -> Option<SignatureHelp> {
18+
pub fn for_expression(expr: &TypedExpr, module: &Module) -> Option<SignatureHelp> {
1819
// If we're inside a function call we can provide signature help,
1920
// otherwise we don't want anything to pop up.
2021
let TypedExpr::Call { fun, arguments, .. } = expr else {
@@ -27,7 +28,13 @@ pub fn for_expression(expr: &TypedExpr) -> Option<SignatureHelp> {
2728
// help.
2829
TypedExpr::Var {
2930
constructor, name, ..
30-
} => signature_help(name.clone(), fun, arguments, constructor.field_map()),
31+
} => signature_help(
32+
name.clone(),
33+
fun,
34+
arguments,
35+
constructor.field_map(),
36+
module,
37+
),
3138

3239
// If we're making a qualified call to another module's function
3340
// then we want to show its type, documentation and the exact name
@@ -50,7 +57,7 @@ pub fn for_expression(expr: &TypedExpr) -> Option<SignatureHelp> {
5057
| ModuleValueConstructor::Fn { field_map, .. } => field_map.into(),
5158
};
5259
let name = format!("{module_alias}.{label}").into();
53-
signature_help(name, fun, arguments, field_map)
60+
signature_help(name, fun, arguments, field_map, module)
5461
}
5562

5663
// If the function being called is an invalid node we don't want to
@@ -87,7 +94,7 @@ pub fn for_expression(expr: &TypedExpr) -> Option<SignatureHelp> {
8794
| TypedExpr::BitArray { .. }
8895
| TypedExpr::RecordUpdate { .. }
8996
| TypedExpr::NegateBool { .. }
90-
| TypedExpr::NegateInt { .. } => signature_help("fn".into(), fun, arguments, None),
97+
| TypedExpr::NegateInt { .. } => signature_help("fn".into(), fun, arguments, None, module),
9198
}
9299
}
93100

@@ -109,6 +116,7 @@ fn signature_help(
109116
fun: &TypedExpr,
110117
supplied_arguments: &[CallArg<TypedExpr>],
111118
field_map: Option<&FieldMap>,
119+
module: &Module,
112120
) -> Option<SignatureHelp> {
113121
let (arguments, return_) = fun.type_().fn_types()?;
114122

@@ -127,7 +135,7 @@ fn signature_help(
127135
None => HashMap::new(),
128136
};
129137

130-
let printer = Printer::new();
138+
let printer = Printer::new(&module.ast.names);
131139
let (label, parameters) =
132140
print_signature_help(printer, fun_name, arguments, return_, &index_to_label);
133141

@@ -240,7 +248,7 @@ fn active_parameter_index(
240248
/// `ParameterInformation` for all its arguments.
241249
///
242250
fn print_signature_help(
243-
mut printer: Printer,
251+
mut printer: Printer<'_>,
244252
function_name: EcoString,
245253
arguments: Vec<Arc<Type>>,
246254
return_: Arc<Type>,
@@ -256,7 +264,7 @@ fn print_signature_help(
256264
signature.push_str(label);
257265
signature.push_str(": ");
258266
}
259-
signature.push_str(&printer.pretty_print(argument, 0));
267+
signature.push_str(&printer.print_type(argument));
260268
let arg_end = signature.len();
261269
let label = ParameterLabel::LabelOffsets([arg_start as u32, arg_end as u32]);
262270

@@ -272,6 +280,6 @@ fn print_signature_help(
272280
}
273281

274282
signature.push_str(") -> ");
275-
signature.push_str(&printer.pretty_print(&return_, 0));
283+
signature.push_str(&printer.print_type(&return_));
276284
(signature, parameter_informations)
277285
}

language-server/src/tests/signature_help.rs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -451,3 +451,45 @@ pub fn main() {
451451
find_position_of(r#"Pokemon(name: "Jirachi",)"#).under_last_char()
452452
);
453453
}
454+
455+
#[test]
456+
pub fn help_for_use_function_call_uses_generic_names_when_missing_all_arguments() {
457+
assert_signature_help!(
458+
r#"
459+
pub fn wibble(x: something, y: fn() -> something, z: anything) { Nil }
460+
pub fn main() {
461+
wibble( )
462+
}
463+
"#,
464+
find_position_of("wibble( ").under_last_char()
465+
);
466+
}
467+
468+
#[test]
469+
pub fn help_for_use_function_call_uses_concrete_types_when_possible_or_generic_names_when_unbound()
470+
{
471+
assert_signature_help!(
472+
r#"
473+
pub fn wibble(x: something, y: fn() -> something, z: anything) { Nil }
474+
pub fn main() {
475+
wibble(1, )
476+
}
477+
"#,
478+
find_position_of("wibble(1, )").under_last_char()
479+
);
480+
}
481+
482+
#[test]
483+
pub fn help_for_use_function_call_uses_concrete_types_when_late_ubound() {
484+
assert_signature_help!(
485+
r#"
486+
fn identity(x: a) -> a { x }
487+
488+
fn main() {
489+
let a = Ok(10)
490+
identity( a)
491+
}
492+
"#,
493+
find_position_of("identity( ").under_last_char()
494+
);
495+
}

language-server/src/tests/snapshots/gleam_language_server__tests__action__generate_function_capture.snap

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
---
2-
source: compiler-core/src/language_server/tests/action.rs
2+
source: language-server/src/tests/action.rs
33
expression: "\nfn map(list: List(a), f: fn(a) -> b) -> List(b) {\n todo\n}\n\npub fn main() {\n map([1, 2, 3], add(_, 1))\n}\n"
4+
snapshot_kind: text
45
---
56
----- BEFORE ACTION
67

@@ -24,6 +25,6 @@ pub fn main() {
2425
map([1, 2, 3], add(_, 1))
2526
}
2627

27-
fn add(int: Int, int_2: Int) -> a {
28+
fn add(int: Int, int_2: Int) -> b {
2829
todo
2930
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
---
2+
source: language-server/src/tests/signature_help.rs
3+
expression: "\nfn identity(x: a) -> a { x }\n\nfn main() {\n let a = Ok(10)\n identity( a)\n}\n"
4+
snapshot_kind: text
5+
---
6+
fn identity(x: a) -> a { x }
7+
8+
fn main() {
9+
let a = Ok(10)
10+
identity( a)
11+
12+
}
13+
14+
15+
----- Signature help -----
16+
identity(Result(Int, b)) -> Result(Int, b)
17+
18+
No documentation
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
---
2+
source: language-server/src/tests/signature_help.rs
3+
expression: "\npub fn wibble(x: something, y: fn() -> something, z: anything) { Nil }\npub fn main() {\n wibble(1, )\n}\n"
4+
snapshot_kind: text
5+
---
6+
pub fn wibble(x: something, y: fn() -> something, z: anything) { Nil }
7+
pub fn main() {
8+
wibble(1, )
9+
10+
}
11+
12+
13+
----- Signature help -----
14+
wibble(Int, fn() -> Int, anything) -> Nil
15+
▔▔▔▔▔▔▔▔▔▔▔
16+
17+
No documentation
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
---
2+
source: language-server/src/tests/signature_help.rs
3+
expression: "\npub fn wibble(x: something, y: fn() -> something, z: anything) { Nil }\npub fn main() {\n wibble( )\n}\n"
4+
snapshot_kind: text
5+
---
6+
pub fn wibble(x: something, y: fn() -> something, z: anything) { Nil }
7+
pub fn main() {
8+
wibble( )
9+
10+
}
11+
12+
13+
----- Signature help -----
14+
wibble(something, fn() -> something, anything) -> Nil
15+
▔▔▔▔▔▔▔▔▔
16+
17+
No documentation

0 commit comments

Comments
 (0)