Skip to content

Commit b4a9c9b

Browse files
committed
fix(lsp): preserve annotated type-variable names when adding annotations
``` fn wibble(a, b, c) { ↑ let x: a = todo fn(a: b, b: c) -> d { todo } } ``` After action: ```diff - fn wibble(a: e, b: f, c: g) -> fn(b, c) -> h + fn wibble(a: e, b: f, c: g) -> fn(b, c) -> d ^ original name is preserved ``` When the "add annotations" code-action is used, the compiler now retains the original type-variable names from the user's annotation instead of replacing them with fresh or default names. This ensures that added annotations reflect the user's naming exactly and prevents confusing or misleading variable renaming.
1 parent 26617cc commit b4a9c9b

3 files changed

Lines changed: 181 additions & 1 deletion

File tree

compiler-core/src/type_/printer.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -454,6 +454,12 @@ impl<'a> Printer<'a> {
454454
_ = self.printed_type_variable_names.insert(name);
455455
}
456456

457+
/// Record that the given type-variable id is already using the supplied name.
458+
pub fn register_type_variable_with_id(&mut self, id: u64, name: EcoString) {
459+
_ = self.printed_type_variable_names.insert(name.clone());
460+
_ = self.printed_type_variables.insert(id, name);
461+
}
462+
457463
pub fn print_type(&mut self, type_: &Type) -> EcoString {
458464
let mut buffer = EcoString::new();
459465
self.print(type_, &mut buffer, PrintMode::Normal);

language-server/src/code_action.rs

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1483,13 +1483,187 @@ fn collect_type_variables(printer: &mut Printer<'_>, function: &ast::TypedFuncti
14831483
}
14841484

14851485
impl<'ast, 'a, 'b> ast::visit::Visit<'ast> for TypeVariableCollector<'a, 'b> {
1486+
fn visit_typed_function(&mut self, fun: &'ast ast::TypedFunction) {
1487+
for argument in fun.arguments.iter() {
1488+
if let Some(annotation) = &argument.annotation {
1489+
register_type_variables_from_annotation(
1490+
self.printer,
1491+
annotation,
1492+
argument.type_.as_ref(),
1493+
);
1494+
}
1495+
}
1496+
1497+
if let Some(annotation) = &fun.return_annotation {
1498+
register_type_variables_from_annotation(
1499+
self.printer,
1500+
annotation,
1501+
fun.return_type.as_ref(),
1502+
);
1503+
}
1504+
1505+
ast::visit::visit_typed_function(self, fun);
1506+
}
1507+
1508+
fn visit_typed_expr_fn(
1509+
&mut self,
1510+
location: &'ast SrcSpan,
1511+
type_: &'ast Arc<Type>,
1512+
kind: &'ast FunctionLiteralKind,
1513+
arguments: &'ast [TypedArg],
1514+
body: &'ast Vec1<TypedStatement>,
1515+
return_annotation: &'ast Option<ast::TypeAst>,
1516+
) {
1517+
if let Type::Fn {
1518+
arguments: argument_types,
1519+
return_: return_type,
1520+
..
1521+
} = type_.as_ref()
1522+
{
1523+
for (argument, argument_type) in arguments.iter().zip(argument_types) {
1524+
if let Some(annotation) = &argument.annotation {
1525+
register_type_variables_from_annotation(
1526+
self.printer,
1527+
annotation,
1528+
argument_type.as_ref(),
1529+
);
1530+
}
1531+
}
1532+
1533+
if let Some(annotation) = return_annotation {
1534+
register_type_variables_from_annotation(
1535+
self.printer,
1536+
annotation,
1537+
return_type.as_ref(),
1538+
);
1539+
}
1540+
}
1541+
1542+
ast::visit::visit_typed_expr_fn(
1543+
self,
1544+
location,
1545+
type_,
1546+
kind,
1547+
arguments,
1548+
body,
1549+
return_annotation,
1550+
);
1551+
}
1552+
14861553
fn visit_type_ast_var(&mut self, _location: &'ast SrcSpan, name: &'ast EcoString) {
14871554
// Register this type variable so that we don't duplicate names when
14881555
// adding annotations.
14891556
self.printer.register_type_variable(name.clone());
14901557
}
14911558
}
14921559

1560+
fn register_type_variables_from_annotation(
1561+
printer: &mut Printer<'_>,
1562+
annotation: &ast::TypeAst,
1563+
type_: &Type,
1564+
) {
1565+
// fn wibble(a, b, c) {
1566+
// fn(a: b, b: c) -> d { ... }
1567+
// ^
1568+
// Without this tracking the printer could rename `d` to a fresh `h`.
1569+
match (annotation, type_) {
1570+
(ast::TypeAst::Var(ast::TypeAstVar { name, .. }), Type::Var { type_ }) => {
1571+
match &*type_.borrow() {
1572+
TypeVar::Generic { id } | TypeVar::Unbound { id } => {
1573+
let id = *id;
1574+
printer.register_type_variable(name.clone());
1575+
printer.register_type_variable_with_id(id, name.clone());
1576+
}
1577+
TypeVar::Link { type_ } => {
1578+
register_type_variables_from_annotation(printer, annotation, type_.as_ref());
1579+
}
1580+
}
1581+
}
1582+
1583+
(
1584+
ast::TypeAst::Fn(ast::TypeAstFn {
1585+
arguments: annotation_arguments,
1586+
return_: annotation_return,
1587+
..
1588+
}),
1589+
Type::Fn {
1590+
arguments: type_arguments,
1591+
return_: type_return,
1592+
..
1593+
},
1594+
) => {
1595+
for (argument_annotation, argument_type) in
1596+
annotation_arguments.iter().zip(type_arguments)
1597+
{
1598+
// Maintain the names from each `fn(arg: name, ...)` position.
1599+
register_type_variables_from_annotation(
1600+
printer,
1601+
argument_annotation,
1602+
argument_type.as_ref(),
1603+
);
1604+
}
1605+
1606+
// And likewise propagate the annotated return variable.
1607+
register_type_variables_from_annotation(
1608+
printer,
1609+
annotation_return.as_ref(),
1610+
type_return.as_ref(),
1611+
);
1612+
}
1613+
1614+
(
1615+
ast::TypeAst::Constructor(ast::TypeAstConstructor {
1616+
arguments: annotation_arguments,
1617+
..
1618+
}),
1619+
Type::Named {
1620+
arguments: type_arguments,
1621+
..
1622+
},
1623+
) => {
1624+
for (argument_annotation, argument_type) in
1625+
annotation_arguments.iter().zip(type_arguments)
1626+
{
1627+
// Track aliases introduced inside named type arguments.
1628+
register_type_variables_from_annotation(
1629+
printer,
1630+
argument_annotation,
1631+
argument_type.as_ref(),
1632+
);
1633+
}
1634+
}
1635+
1636+
(
1637+
ast::TypeAst::Tuple(ast::TypeAstTuple {
1638+
elements: annotation_elements,
1639+
..
1640+
}),
1641+
Type::Tuple {
1642+
elements: type_elements,
1643+
..
1644+
},
1645+
) => {
1646+
for (element_annotation, element_type) in annotation_elements.iter().zip(type_elements)
1647+
{
1648+
// Tuples can hide extra annotations; ensure each slot retains its label.
1649+
register_type_variables_from_annotation(
1650+
printer,
1651+
element_annotation,
1652+
element_type.as_ref(),
1653+
);
1654+
}
1655+
}
1656+
1657+
(_, Type::Var { type_ }) => {
1658+
if let TypeVar::Link { type_ } = &*type_.borrow() {
1659+
register_type_variables_from_annotation(printer, annotation, type_.as_ref());
1660+
}
1661+
}
1662+
1663+
_ => {}
1664+
}
1665+
}
1666+
14931667
pub struct QualifiedConstructor<'a> {
14941668
import: &'a Import<EcoString>,
14951669
used_name: EcoString,

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ fn wibble(a, b, c) {
1515

1616
----- AFTER ACTION
1717

18-
fn wibble(a: e, b: f, c: g) -> fn(b, c) -> h {
18+
fn wibble(a: e, b: f, c: g) -> fn(b, c) -> d {
1919
let x: a = todo
2020
fn(a: b, b: c) -> d {
2121
todo

0 commit comments

Comments
 (0)