Fork of daniellemaywood.uk/gleam — Wasm codegen work
2

Configure Feed

Select the types of activity you want to include in your feed.

show unchanged fields in record update

author
Giacomo Cavalieri
committer
Louis Pilfold
date (May 18, 2026, 3:51 PM +0100) commit a88023f3 parent d58686c7 change-id xoposlum
+135 -7
+50 -1
compiler-core/src/ast/typed.rs
··· 444 444 .filter(|argument| argument.implicit.is_none()) 445 445 .find_map(|argument| argument.find_node(byte_index, constructor, arguments)) 446 446 .or_else(|| constructor.find_node(byte_index)) 447 - .or_else(|| updated_record.find_node(byte_index)) 447 + .or_else(|| { 448 + // If the updated record contains the index, then we update 449 + // the found position to be `UpdatedRecord` 450 + updated_record.find_node(byte_index).map(|found| { 451 + if let Located::Expression { 452 + expression, 453 + position: 454 + ExpressionPosition::ArgumentOrLabel { .. } 455 + | ExpressionPosition::Expression, 456 + } = found 457 + { 458 + Located::Expression { 459 + expression, 460 + position: ExpressionPosition::UpdatedRecord { 461 + unchanged_record_fields: self 462 + .unchanged_record_fields() 463 + .unwrap_or_default(), 464 + }, 465 + } 466 + } else { 467 + found 468 + } 469 + }) 470 + }) 448 471 .or_else(|| self.self_if_contains_location(byte_index)), 449 472 } 450 473 } ··· 1656 1679 1657 1680 pub fn is_todo_with_no_message(&self) -> bool { 1658 1681 matches!(self, TypedExpr::Todo { message: None, .. }) 1682 + } 1683 + 1684 + /// If the expression is a record update, this returns a list with the names 1685 + /// of its labelled arguments that are not being changed by the record 1686 + /// update. 1687 + pub fn unchanged_record_fields(&self) -> Option<Vec<(EcoString, Arc<Type>)>> { 1688 + let TypedExpr::RecordUpdate { arguments, .. } = self else { 1689 + return None; 1690 + }; 1691 + 1692 + let mut unchanged_arguments = vec![]; 1693 + for argument in arguments { 1694 + // All arguments that stay the same are generated as "implicit" 1695 + // arguments, we only care about them! 1696 + if !argument.is_implicit() { 1697 + continue; 1698 + } 1699 + // The label should always be present for these implicit arguments, 1700 + // technically this will never fail, but rather than panicking I 1701 + // just "continue". 1702 + let Some(label) = argument.label.as_ref() else { 1703 + continue; 1704 + }; 1705 + unchanged_arguments.push((label.clone(), argument.value.type_())) 1706 + } 1707 + Some(unchanged_arguments) 1659 1708 } 1660 1709 } 1661 1710
+11 -1
compiler-core/src/build.rs
··· 426 426 /// The position of a located expression. Used to determine extra context, 427 427 /// such as whether to provide label completions if the expression is in 428 428 /// argument position. 429 - #[derive(Debug, Clone, Copy, PartialEq)] 429 + #[derive(Debug, Clone, PartialEq)] 430 430 pub enum ExpressionPosition<'a> { 431 431 Expression, 432 432 ArgumentOrLabel { 433 433 called_function: &'a TypedExpr, 434 434 function_arguments: &'a [TypedCallArg], 435 + }, 436 + /// This is for an expression that is used in a record update spread: 437 + /// ```gleam 438 + /// Wibble(..wibble, a: 1) 439 + /// // ^^^^^^^^ This! 440 + /// ``` 441 + UpdatedRecord { 442 + /// These are all the record fields that are not being updated in the 443 + /// update. 444 + unchanged_record_fields: Vec<(EcoString, Arc<Type>)>, 435 445 }, 436 446 } 437 447
+35 -5
language-server/src/engine.rs
··· 1161 1161 Located::StringPrefixPatternVariable { location, .. } => Some( 1162 1162 hover_for_string_prefix_pattern_variable(location, &lines, module), 1163 1163 ), 1164 - Located::Expression { expression, .. } => Some(hover_for_expression( 1164 + Located::Expression { 1165 1165 expression, 1166 + position, 1167 + } => Some(hover_for_expression( 1168 + expression, 1169 + position, 1166 1170 lines, 1167 1171 module, 1168 1172 &this.hex_deps, ··· 1624 1628 } 1625 1629 } 1626 1630 1627 - fn hover_for_expression( 1628 - expression: &TypedExpr, 1631 + fn hover_for_expression<'a>( 1632 + expression: &'a TypedExpr, 1633 + position: ExpressionPosition<'a>, 1629 1634 line_numbers: LineNumbers, 1630 1635 module: &Module, 1631 1636 hex_deps: &HashSet<EcoString>, ··· 1639 1644 .unwrap_or("".to_string()); 1640 1645 1641 1646 // Show the type of the hovered node to the user 1642 - let type_ = Printer::new(&module.ast.names).print_type(expression.type_().as_ref()); 1647 + let mut printer = Printer::new(&module.ast.names); 1648 + let type_ = printer.print_type(expression.type_().as_ref()); 1649 + 1650 + // If the expression is a record update and there's record fields that are 1651 + // begin implicitly updated, then we want to list them in the hover. 1652 + let unchanged_record_update_fields = match position { 1653 + ExpressionPosition::Expression | ExpressionPosition::ArgumentOrLabel { .. } => "", 1654 + 1655 + ExpressionPosition::UpdatedRecord { 1656 + unchanged_record_fields, 1657 + } if !unchanged_record_fields.is_empty() => &format!( 1658 + "Unchanged record fields:\n{}", 1659 + unchanged_record_fields 1660 + .iter() 1661 + .map(|(label, type_)| format!("- `{}: {}`", label, printer.print_type(type_))) 1662 + .join("\n") 1663 + ), 1664 + ExpressionPosition::UpdatedRecord { .. } => "", 1665 + }; 1666 + 1667 + let description = [documentation, unchanged_record_update_fields, &link_section] 1668 + .iter() 1669 + .filter(|string| !string.is_empty()) 1670 + .join("\n\n"); 1671 + 1643 1672 let contents = format!( 1644 1673 "```gleam 1645 1674 {type_} 1646 1675 ``` 1647 - {documentation}{link_section}" 1676 + {description}" 1648 1677 ); 1678 + 1649 1679 Hover { 1650 1680 contents: Contents::MarkedString(MarkedString::String(contents)), 1651 1681 range: Some(src_span_to_lsp_range(expression.location(), &line_numbers)),
+16
language-server/src/tests/hover.rs
··· 2104 2104 } 2105 2105 2106 2106 #[test] 2107 + fn hover_on_record_being_updated_shows_ignored_fields() { 2108 + assert_hover!( 2109 + " 2110 + type Wibble { 2111 + Wibble(a: Int, b: Bool) 2112 + } 2113 + 2114 + pub fn go(wibble: Wibble) { 2115 + Wibble(..wibble, a: 1) 2116 + } 2117 + ", 2118 + find_position_of("..wibble").under_last_char() 2119 + ); 2120 + } 2121 + 2122 + #[test] 2107 2123 fn hover_for_invalid_record_update_1() { 2108 2124 assert_hover!( 2109 2125 "
+23
language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_on_record_being_updated_shows_ignored_fields.snap
··· 1 + --- 2 + source: language-server/src/tests/hover.rs 3 + expression: "\ntype Wibble {\n Wibble(a: Int, b: Bool)\n}\n\npub fn go(wibble: Wibble) {\n Wibble(..wibble, a: 1)\n}\n" 4 + --- 5 + 6 + type Wibble { 7 + Wibble(a: Int, b: Bool) 8 + } 9 + 10 + pub fn go(wibble: Wibble) { 11 + Wibble(..wibble, a: 1) 12 + ▔▔▔▔▔↑ 13 + } 14 + 15 + 16 + ----- Hover content (markdown) ----- 17 + ```gleam 18 + Wibble 19 + ``` 20 + A locally defined variable. 21 + 22 + Unchanged record fields: 23 + - `b: Bool`