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

Configure Feed

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

Add replace spread with ignored fields action

+397 -1
+182
compiler-core/src/language_server/code_action.rs
··· 5215 5215 self.visit_typed_expr(finally); 5216 5216 } 5217 5217 } 5218 + 5219 + /// Code action to replace a `..` in a pattern with all the missing fields that 5220 + /// have not been explicitly provided: the ignored missing fields are added as 5221 + /// `_`, while labelled ones are introduced with the shorthand syntax. 5222 + /// 5223 + /// ```gleam 5224 + /// pub type Pokemon { 5225 + /// Pokemon(Int, name: String, moves: List(String)) 5226 + /// } 5227 + /// 5228 + /// pub fn main() { 5229 + /// let Pokemon(..) = todo 5230 + /// // ^^ Cursor over the spread 5231 + /// } 5232 + /// ``` 5233 + /// Would become 5234 + /// ```gleam 5235 + /// pub fn main() { 5236 + /// let Pokemon(_, name:, moves:) = todo 5237 + /// } 5238 + /// 5239 + pub struct ReplaceSpreadWithIgnoredFields<'a> { 5240 + module: &'a Module, 5241 + params: &'a CodeActionParams, 5242 + edits: TextEdits<'a>, 5243 + data: Option<ReplaceSpreadData>, 5244 + } 5245 + 5246 + pub struct ReplaceSpreadData { 5247 + /// All the missing positional and labelled fields. 5248 + positional: Vec<Arc<Type>>, 5249 + labelled: Vec<(EcoString, Arc<Type>)>, 5250 + /// We need this in order to tell where the missing positional arguments 5251 + /// should be inserted. 5252 + first_labelled_argument_start: Option<u32>, 5253 + /// The end of the final argument before the spread, if there's any. 5254 + /// We'll use this to delete everything that comes after the final argument, 5255 + /// after adding all the ignored fields. 5256 + last_argument_end: Option<u32>, 5257 + spread_location: SrcSpan, 5258 + } 5259 + 5260 + impl<'a> ReplaceSpreadWithIgnoredFields<'a> { 5261 + pub fn new( 5262 + module: &'a Module, 5263 + line_numbers: &'a LineNumbers, 5264 + params: &'a CodeActionParams, 5265 + ) -> Self { 5266 + Self { 5267 + module, 5268 + params, 5269 + edits: TextEdits::new(line_numbers), 5270 + data: None, 5271 + } 5272 + } 5273 + 5274 + pub fn code_actions(mut self) -> Vec<CodeAction> { 5275 + self.visit_typed_module(&self.module.ast); 5276 + 5277 + let Some(ReplaceSpreadData { 5278 + positional, 5279 + labelled, 5280 + first_labelled_argument_start, 5281 + last_argument_end, 5282 + spread_location, 5283 + }) = self.data 5284 + else { 5285 + return vec![]; 5286 + }; 5287 + 5288 + // Do not suggest this code action if there's no ignored fields at all. 5289 + if positional.is_empty() && labelled.is_empty() { 5290 + return vec![]; 5291 + }; 5292 + 5293 + // We add all the missing positional arguments as `_` before the first 5294 + // labelled one (and so after all the already existing positional ones). 5295 + if !positional.is_empty() { 5296 + // We want to make sure that all positional args will have a name 5297 + // that's different from any label. So we add those as already used 5298 + // names. 5299 + let mut names = NameGenerator::new(); 5300 + for (label, _) in labelled.iter() { 5301 + names.add_used_name(label.clone()); 5302 + } 5303 + 5304 + let positional_args = positional 5305 + .iter() 5306 + .map(|type_| names.generate_name_from_type(type_)) 5307 + .join(", "); 5308 + let insert_at = first_labelled_argument_start.unwrap_or(spread_location.start); 5309 + 5310 + // The positional arguments are going to be followed by some other 5311 + // arguments if there's some already existing labelled args 5312 + // (`last_argument_end.is_some`), of if we're adding those labelled args 5313 + // ourselves (`!labelled.is_empty()`). So we need to put a comma after the 5314 + // final positional argument we're adding to separate it from the ones that 5315 + // are going to come after. 5316 + let has_arguments_after = last_argument_end.is_some() || !labelled.is_empty(); 5317 + let positional_args = if has_arguments_after { 5318 + format!("{positional_args}, ") 5319 + } else { 5320 + positional_args 5321 + }; 5322 + 5323 + self.edits.insert(insert_at, positional_args); 5324 + } 5325 + 5326 + if !labelled.is_empty() { 5327 + // If there's labelled arguments to add, we replace the existing spread 5328 + // with the arguments to be added. This way commas and all should already 5329 + // be correct. 5330 + let labelled_args = labelled 5331 + .iter() 5332 + .map(|(label, _)| format!("{label}:")) 5333 + .join(", "); 5334 + self.edits.replace(spread_location, labelled_args); 5335 + } else if let Some(delete_start) = last_argument_end { 5336 + // However, if there's no labelled arguments to insert we still need 5337 + // to delete the entire spread: we start deleting from the end of the 5338 + // final argument, if there's one. 5339 + // This way we also get rid of any comma separating the last argument 5340 + // and the spread to be removed. 5341 + self.edits 5342 + .delete(SrcSpan::new(delete_start, spread_location.end)) 5343 + } else { 5344 + // Otherwise we just delete the spread. 5345 + self.edits.delete(spread_location) 5346 + } 5347 + 5348 + let mut action = Vec::with_capacity(1); 5349 + CodeActionBuilder::new("Replace `..` with ignored fields") 5350 + .kind(CodeActionKind::REFACTOR_REWRITE) 5351 + .changes(self.params.text_document.uri.clone(), self.edits.edits) 5352 + .preferred(false) 5353 + .push_to(&mut action); 5354 + action 5355 + } 5356 + } 5357 + 5358 + impl<'ast> ast::visit::Visit<'ast> for ReplaceSpreadWithIgnoredFields<'ast> { 5359 + fn visit_typed_pattern(&mut self, pattern: &'ast TypedPattern) { 5360 + // We can only interpolate/split a string if the cursor is somewhere 5361 + // within its location, otherwise we skip it. 5362 + let pattern_range = self.edits.src_span_to_lsp_range(pattern.location()); 5363 + if !within(self.params.range, pattern_range) { 5364 + return; 5365 + } 5366 + 5367 + if let TypedPattern::Constructor { 5368 + arguments, 5369 + spread: Some(spread_location), 5370 + .. 5371 + } = pattern 5372 + { 5373 + if let Some((positional, labelled)) = pattern.unused_arguments() { 5374 + // If there's any unused argument that's being ignored we want to 5375 + // suggest the code action. 5376 + let first_labelled_argument_start = arguments 5377 + .iter() 5378 + .find(|arg| !arg.is_implicit() && arg.label.is_some()) 5379 + .map(|arg| arg.location.start); 5380 + 5381 + let last_argument_end = arguments 5382 + .iter() 5383 + .filter(|arg| !arg.is_implicit()) 5384 + .last() 5385 + .map(|arg| arg.location.end); 5386 + 5387 + self.data = Some(ReplaceSpreadData { 5388 + positional, 5389 + labelled, 5390 + first_labelled_argument_start, 5391 + last_argument_end, 5392 + spread_location: *spread_location, 5393 + }); 5394 + }; 5395 + } 5396 + 5397 + ast::visit::visit_typed_pattern(self, pattern); 5398 + } 5399 + }
+4 -1
compiler-core/src/language_server/engine.rs
··· 36 36 ConvertToUse, ExpandFunctionCapture, ExtractVariable, FillInMissingLabelledArgs, 37 37 GenerateDynamicDecoder, GenerateFunction, GenerateJsonEncoder, InlineVariable, 38 38 InterpolateString, LetAssertToCase, PatternMatchOnValue, RedundantTupleInCaseSubject, 39 - UseLabelShorthandSyntax, code_action_add_missing_patterns, 39 + ReplaceSpreadWithIgnoredFields, UseLabelShorthandSyntax, code_action_add_missing_patterns, 40 40 code_action_convert_qualified_constructor_to_unqualified, 41 41 code_action_convert_unqualified_constructor_to_qualified, code_action_import_module, 42 42 code_action_inexhaustive_let_to_case, ··· 385 385 actions.extend(ConvertFromUse::new(module, &lines, &params).code_actions()); 386 386 actions.extend(ConvertToUse::new(module, &lines, &params).code_actions()); 387 387 actions.extend(ExpandFunctionCapture::new(module, &lines, &params).code_actions()); 388 + actions.extend( 389 + ReplaceSpreadWithIgnoredFields::new(module, &lines, &params).code_actions(), 390 + ); 388 391 actions.extend(InterpolateString::new(module, &lines, &params).code_actions()); 389 392 actions.extend(ExtractVariable::new(module, &lines, &params).code_actions()); 390 393 actions.extend(GenerateFunction::new(module, &lines, &params).code_actions());
+85
compiler-core/src/language_server/tests/action.rs
··· 79 79 const INLINE_VARIABLE: &str = "Inline variable"; 80 80 const CONVERT_TO_PIPE: &str = "Convert to pipe"; 81 81 const INTERPOLATE_STRING: &str = "Interpolate string"; 82 + const REPLACE_SPREAD_WITH_IGNORED_FIELDS: &str = "Replace `..` with ignored fields"; 82 83 83 84 macro_rules! assert_code_action { 84 85 ($title:expr, $code:literal, $range:expr $(,)?) => { ··· 113 114 let result = actions_with_title(all_titles, $project, range); 114 115 assert_eq!(expected, result); 115 116 }; 117 + } 118 + 119 + #[test] 120 + fn replace_spread_with_ignored_labelled_fields() { 121 + assert_code_action!( 122 + REPLACE_SPREAD_WITH_IGNORED_FIELDS, 123 + r#" 124 + pub type Wibble { Wibble(Int, label1: String, label2: Int) } 125 + 126 + pub fn main() { 127 + let Wibble(_, ..) = todo 128 + }"#, 129 + find_position_of("..").to_selection() 130 + ); 131 + } 132 + 133 + #[test] 134 + fn replace_spread_with_ignored_positional_fields() { 135 + assert_code_action!( 136 + REPLACE_SPREAD_WITH_IGNORED_FIELDS, 137 + r#" 138 + pub type Wibble { Wibble(Int, label1: String, label2: Int) } 139 + 140 + pub fn main() { 141 + let Wibble(label1:, label2:, ..) = todo 142 + }"#, 143 + find_position_of("..").to_selection() 144 + ); 145 + } 146 + 147 + #[test] 148 + fn replace_spread_with_all_positional_fields() { 149 + assert_code_action!( 150 + REPLACE_SPREAD_WITH_IGNORED_FIELDS, 151 + r#" 152 + pub type Wibble { Wibble(Int, String) } 153 + 154 + pub fn main() { 155 + let Wibble(..) = todo 156 + }"#, 157 + find_position_of("..").to_selection() 158 + ); 159 + } 160 + 161 + #[test] 162 + fn replace_spread_with_ignored_mixed_fields() { 163 + assert_code_action!( 164 + REPLACE_SPREAD_WITH_IGNORED_FIELDS, 165 + r#" 166 + pub type Wibble { Wibble(Int, String, label1: String, label2: Int) } 167 + 168 + pub fn main() { 169 + let Wibble(_, label2:, ..) = todo 170 + }"#, 171 + find_position_of("..").to_selection() 172 + ); 173 + } 174 + 175 + #[test] 176 + fn replace_spread_with_all_ignored_fields() { 177 + assert_code_action!( 178 + REPLACE_SPREAD_WITH_IGNORED_FIELDS, 179 + r#" 180 + pub type Wibble { Wibble(Int, label1: String, label2: Int) } 181 + 182 + pub fn main() { 183 + let Wibble(..) = todo 184 + }"#, 185 + find_position_of("..").to_selection() 186 + ); 187 + } 188 + 189 + #[test] 190 + fn replace_spread_with_ignored_fields_never_calls_a_positional_arg_as_a_labelled_one() { 191 + assert_code_action!( 192 + REPLACE_SPREAD_WITH_IGNORED_FIELDS, 193 + r#" 194 + pub type Wibble { Wibble(Int, int: Int) } 195 + 196 + pub fn main() { 197 + let Wibble(..) = todo 198 + }"#, 199 + find_position_of("..").to_selection() 200 + ); 116 201 } 117 202 118 203 #[test]
+21
compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__action__replace_spread_with_all_ignored_fields.snap
··· 1 + --- 2 + source: compiler-core/src/language_server/tests/action.rs 3 + expression: "\npub type Wibble { Wibble(Int, label1: String, label2: Int) }\n\npub fn main() {\n let Wibble(..) = todo\n}" 4 + --- 5 + ----- BEFORE ACTION 6 + 7 + pub type Wibble { Wibble(Int, label1: String, label2: Int) } 8 + 9 + pub fn main() { 10 + let Wibble(..) = todo 11 + 12 + } 13 + 14 + 15 + ----- AFTER ACTION 16 + 17 + pub type Wibble { Wibble(Int, label1: String, label2: Int) } 18 + 19 + pub fn main() { 20 + let Wibble(int, label1:, label2:) = todo 21 + }
+21
compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__action__replace_spread_with_all_positional_fields.snap
··· 1 + --- 2 + source: compiler-core/src/language_server/tests/action.rs 3 + expression: "\npub type Wibble { Wibble(Int, String) }\n\npub fn main() {\n let Wibble(..) = todo\n}" 4 + --- 5 + ----- BEFORE ACTION 6 + 7 + pub type Wibble { Wibble(Int, String) } 8 + 9 + pub fn main() { 10 + let Wibble(..) = todo 11 + 12 + } 13 + 14 + 15 + ----- AFTER ACTION 16 + 17 + pub type Wibble { Wibble(Int, String) } 18 + 19 + pub fn main() { 20 + let Wibble(int, string) = todo 21 + }
+21
compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__action__replace_spread_with_ignored_fields_never_calls_a_positional_arg_as_a_labelled_one.snap
··· 1 + --- 2 + source: compiler-core/src/language_server/tests/action.rs 3 + expression: "\npub type Wibble { Wibble(Int, int: Int) }\n\npub fn main() {\n let Wibble(..) = todo\n}" 4 + --- 5 + ----- BEFORE ACTION 6 + 7 + pub type Wibble { Wibble(Int, int: Int) } 8 + 9 + pub fn main() { 10 + let Wibble(..) = todo 11 + 12 + } 13 + 14 + 15 + ----- AFTER ACTION 16 + 17 + pub type Wibble { Wibble(Int, int: Int) } 18 + 19 + pub fn main() { 20 + let Wibble(int_2, int:) = todo 21 + }
+21
compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__action__replace_spread_with_ignored_labelled_fields.snap
··· 1 + --- 2 + source: compiler-core/src/language_server/tests/action.rs 3 + expression: "\npub type Wibble { Wibble(Int, label1: String, label2: Int) }\n\npub fn main() {\n let Wibble(_, ..) = todo\n}" 4 + --- 5 + ----- BEFORE ACTION 6 + 7 + pub type Wibble { Wibble(Int, label1: String, label2: Int) } 8 + 9 + pub fn main() { 10 + let Wibble(_, ..) = todo 11 + 12 + } 13 + 14 + 15 + ----- AFTER ACTION 16 + 17 + pub type Wibble { Wibble(Int, label1: String, label2: Int) } 18 + 19 + pub fn main() { 20 + let Wibble(_, label1:, label2:) = todo 21 + }
+21
compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__action__replace_spread_with_ignored_mixed_fields.snap
··· 1 + --- 2 + source: compiler-core/src/language_server/tests/action.rs 3 + expression: "\npub type Wibble { Wibble(Int, String, label1: String, label2: Int) }\n\npub fn main() {\n let Wibble(_, label2:, ..) = todo\n}" 4 + --- 5 + ----- BEFORE ACTION 6 + 7 + pub type Wibble { Wibble(Int, String, label1: String, label2: Int) } 8 + 9 + pub fn main() { 10 + let Wibble(_, label2:, ..) = todo 11 + 12 + } 13 + 14 + 15 + ----- AFTER ACTION 16 + 17 + pub type Wibble { Wibble(Int, String, label1: String, label2: Int) } 18 + 19 + pub fn main() { 20 + let Wibble(_, string, label2:, label1:) = todo 21 + }
+21
compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__action__replace_spread_with_ignored_positional_fields.snap
··· 1 + --- 2 + source: compiler-core/src/language_server/tests/action.rs 3 + expression: "\npub type Wibble { Wibble(Int, label1: String, label2: Int) }\n\npub fn main() {\n let Wibble(label1:, label2:, ..) = todo\n}" 4 + --- 5 + ----- BEFORE ACTION 6 + 7 + pub type Wibble { Wibble(Int, label1: String, label2: Int) } 8 + 9 + pub fn main() { 10 + let Wibble(label1:, label2:, ..) = todo 11 + 12 + } 13 + 14 + 15 + ----- AFTER ACTION 16 + 17 + pub type Wibble { Wibble(Int, label1: String, label2: Int) } 18 + 19 + pub fn main() { 20 + let Wibble(int, label1:, label2:) = todo 21 + }