···52155215 self.visit_typed_expr(finally);
52165216 }
52175217}
52185218+52195219+/// Code action to replace a `..` in a pattern with all the missing fields that
52205220+/// have not been explicitly provided: the ignored missing fields are added as
52215221+/// `_`, while labelled ones are introduced with the shorthand syntax.
52225222+///
52235223+/// ```gleam
52245224+/// pub type Pokemon {
52255225+/// Pokemon(Int, name: String, moves: List(String))
52265226+/// }
52275227+///
52285228+/// pub fn main() {
52295229+/// let Pokemon(..) = todo
52305230+/// // ^^ Cursor over the spread
52315231+/// }
52325232+/// ```
52335233+/// Would become
52345234+/// ```gleam
52355235+/// pub fn main() {
52365236+/// let Pokemon(_, name:, moves:) = todo
52375237+/// }
52385238+///
52395239+pub struct ReplaceSpreadWithIgnoredFields<'a> {
52405240+ module: &'a Module,
52415241+ params: &'a CodeActionParams,
52425242+ edits: TextEdits<'a>,
52435243+ data: Option<ReplaceSpreadData>,
52445244+}
52455245+52465246+pub struct ReplaceSpreadData {
52475247+ /// All the missing positional and labelled fields.
52485248+ positional: Vec<Arc<Type>>,
52495249+ labelled: Vec<(EcoString, Arc<Type>)>,
52505250+ /// We need this in order to tell where the missing positional arguments
52515251+ /// should be inserted.
52525252+ first_labelled_argument_start: Option<u32>,
52535253+ /// The end of the final argument before the spread, if there's any.
52545254+ /// We'll use this to delete everything that comes after the final argument,
52555255+ /// after adding all the ignored fields.
52565256+ last_argument_end: Option<u32>,
52575257+ spread_location: SrcSpan,
52585258+}
52595259+52605260+impl<'a> ReplaceSpreadWithIgnoredFields<'a> {
52615261+ pub fn new(
52625262+ module: &'a Module,
52635263+ line_numbers: &'a LineNumbers,
52645264+ params: &'a CodeActionParams,
52655265+ ) -> Self {
52665266+ Self {
52675267+ module,
52685268+ params,
52695269+ edits: TextEdits::new(line_numbers),
52705270+ data: None,
52715271+ }
52725272+ }
52735273+52745274+ pub fn code_actions(mut self) -> Vec<CodeAction> {
52755275+ self.visit_typed_module(&self.module.ast);
52765276+52775277+ let Some(ReplaceSpreadData {
52785278+ positional,
52795279+ labelled,
52805280+ first_labelled_argument_start,
52815281+ last_argument_end,
52825282+ spread_location,
52835283+ }) = self.data
52845284+ else {
52855285+ return vec![];
52865286+ };
52875287+52885288+ // Do not suggest this code action if there's no ignored fields at all.
52895289+ if positional.is_empty() && labelled.is_empty() {
52905290+ return vec![];
52915291+ };
52925292+52935293+ // We add all the missing positional arguments as `_` before the first
52945294+ // labelled one (and so after all the already existing positional ones).
52955295+ if !positional.is_empty() {
52965296+ // We want to make sure that all positional args will have a name
52975297+ // that's different from any label. So we add those as already used
52985298+ // names.
52995299+ let mut names = NameGenerator::new();
53005300+ for (label, _) in labelled.iter() {
53015301+ names.add_used_name(label.clone());
53025302+ }
53035303+53045304+ let positional_args = positional
53055305+ .iter()
53065306+ .map(|type_| names.generate_name_from_type(type_))
53075307+ .join(", ");
53085308+ let insert_at = first_labelled_argument_start.unwrap_or(spread_location.start);
53095309+53105310+ // The positional arguments are going to be followed by some other
53115311+ // arguments if there's some already existing labelled args
53125312+ // (`last_argument_end.is_some`), of if we're adding those labelled args
53135313+ // ourselves (`!labelled.is_empty()`). So we need to put a comma after the
53145314+ // final positional argument we're adding to separate it from the ones that
53155315+ // are going to come after.
53165316+ let has_arguments_after = last_argument_end.is_some() || !labelled.is_empty();
53175317+ let positional_args = if has_arguments_after {
53185318+ format!("{positional_args}, ")
53195319+ } else {
53205320+ positional_args
53215321+ };
53225322+53235323+ self.edits.insert(insert_at, positional_args);
53245324+ }
53255325+53265326+ if !labelled.is_empty() {
53275327+ // If there's labelled arguments to add, we replace the existing spread
53285328+ // with the arguments to be added. This way commas and all should already
53295329+ // be correct.
53305330+ let labelled_args = labelled
53315331+ .iter()
53325332+ .map(|(label, _)| format!("{label}:"))
53335333+ .join(", ");
53345334+ self.edits.replace(spread_location, labelled_args);
53355335+ } else if let Some(delete_start) = last_argument_end {
53365336+ // However, if there's no labelled arguments to insert we still need
53375337+ // to delete the entire spread: we start deleting from the end of the
53385338+ // final argument, if there's one.
53395339+ // This way we also get rid of any comma separating the last argument
53405340+ // and the spread to be removed.
53415341+ self.edits
53425342+ .delete(SrcSpan::new(delete_start, spread_location.end))
53435343+ } else {
53445344+ // Otherwise we just delete the spread.
53455345+ self.edits.delete(spread_location)
53465346+ }
53475347+53485348+ let mut action = Vec::with_capacity(1);
53495349+ CodeActionBuilder::new("Replace `..` with ignored fields")
53505350+ .kind(CodeActionKind::REFACTOR_REWRITE)
53515351+ .changes(self.params.text_document.uri.clone(), self.edits.edits)
53525352+ .preferred(false)
53535353+ .push_to(&mut action);
53545354+ action
53555355+ }
53565356+}
53575357+53585358+impl<'ast> ast::visit::Visit<'ast> for ReplaceSpreadWithIgnoredFields<'ast> {
53595359+ fn visit_typed_pattern(&mut self, pattern: &'ast TypedPattern) {
53605360+ // We can only interpolate/split a string if the cursor is somewhere
53615361+ // within its location, otherwise we skip it.
53625362+ let pattern_range = self.edits.src_span_to_lsp_range(pattern.location());
53635363+ if !within(self.params.range, pattern_range) {
53645364+ return;
53655365+ }
53665366+53675367+ if let TypedPattern::Constructor {
53685368+ arguments,
53695369+ spread: Some(spread_location),
53705370+ ..
53715371+ } = pattern
53725372+ {
53735373+ if let Some((positional, labelled)) = pattern.unused_arguments() {
53745374+ // If there's any unused argument that's being ignored we want to
53755375+ // suggest the code action.
53765376+ let first_labelled_argument_start = arguments
53775377+ .iter()
53785378+ .find(|arg| !arg.is_implicit() && arg.label.is_some())
53795379+ .map(|arg| arg.location.start);
53805380+53815381+ let last_argument_end = arguments
53825382+ .iter()
53835383+ .filter(|arg| !arg.is_implicit())
53845384+ .last()
53855385+ .map(|arg| arg.location.end);
53865386+53875387+ self.data = Some(ReplaceSpreadData {
53885388+ positional,
53895389+ labelled,
53905390+ first_labelled_argument_start,
53915391+ last_argument_end,
53925392+ spread_location: *spread_location,
53935393+ });
53945394+ };
53955395+ }
53965396+53975397+ ast::visit::visit_typed_pattern(self, pattern);
53985398+ }
53995399+}