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

Configure Feed

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

Add code action to generate missing type definitions

When an unknown type is referenced in the source, the language server
now offers a "Generate type" code action that inserts a type stub
before the containing definition. For example:

pub fn wibble(arg: Wibble) { todo }

generates:

type Wibble

pub fn wibble(arg: Wibble) { todo }

Parameterised types use the original names from the call site:

Wibble(some, generics) → pub type Wibble(some, generics)

Types used in public contexts are generated as pub.

Closes #5389

+359 -2
+18
CHANGELOG.md
··· 115 115 116 116 ([Surya Rose](https://github.com/GearsDatapacks)) 117 117 118 + - The language server now offers a code action to generate a missing type 119 + definition when an unknown type is referenced. For example, if `Wibble` 120 + is not defined: 121 + 122 + ```gleam 123 + pub fn wibble(argument: Wibble) { todo } 124 + ``` 125 + 126 + The code action will generate: 127 + 128 + ```gleam 129 + pub type Wibble 130 + 131 + pub fn wibble(argument: Wibble) { todo } 132 + ``` 133 + 134 + ([Daniele Scaratti](https://github.com/lupodevelop)) 135 + 118 136 ### Formatter 119 137 120 138 ### Bug fixes
+1
compiler-core/src/error.rs
··· 3344 3344 location, 3345 3345 name, 3346 3346 hint, 3347 + .. 3347 3348 } => { 3348 3349 let label_text = match hint { 3349 3350 UnknownTypeHint::AlternativeTypes(types) => did_you_mean(name, types),
+3
compiler-core/src/type_/error.rs
··· 178 178 location: SrcSpan, 179 179 name: EcoString, 180 180 hint: UnknownTypeHint, 181 + parameter_names: Vec<EcoString>, 181 182 }, 182 183 183 184 /// This happens when someone writes `module_name.` with no type name after ··· 1574 1575 error: UnknownTypeConstructorError, 1575 1576 location: &SrcSpan, 1576 1577 module_location: Option<SrcSpan>, 1578 + parameter_names: Vec<EcoString>, 1577 1579 ) -> Error { 1578 1580 match error { 1579 1581 UnknownTypeConstructorError::Type { name, hint } => Error::UnknownType { 1580 1582 location: *location, 1581 1583 name, 1582 1584 hint, 1585 + parameter_names, 1583 1586 }, 1584 1587 1585 1588 UnknownTypeConstructorError::Module { name, suggestions } => Error::UnknownModule {
+54
compiler-core/src/type_/hydrator.rs
··· 170 170 } = environment 171 171 .get_type_constructor(&module, name) 172 172 .map_err(|error| { 173 + let parameter_names = generate_parameter_names(arguments); 173 174 convert_get_type_constructor_error( 174 175 error, 175 176 location, 176 177 module.as_ref().map(|(_, location)| *location), 178 + parameter_names, 177 179 ) 178 180 })? 179 181 .clone(); ··· 320 322 name: name.clone(), 321 323 location: *location, 322 324 hint, 325 + parameter_names: vec![], 323 326 }) 324 327 } 325 328 } ··· 374 377 pub type_: Arc<Type>, 375 378 pub usage_count: usize, 376 379 } 380 + 381 + /// Names for the parameters of an unknown type, used to suggest a definition. 382 + /// Type variables keep their name; other parameters get a generated one. 383 + fn generate_parameter_names(arguments: &[TypeAst]) -> Vec<EcoString> { 384 + // Reserve the type variable names so generated ones don't collide with them. 385 + let mut used: HashSet<EcoString> = arguments 386 + .iter() 387 + .filter_map(|argument| match argument { 388 + TypeAst::Var(TypeAstVar { name, .. }) => Some(name.clone()), 389 + TypeAst::Constructor(_) | TypeAst::Fn(_) | TypeAst::Tuple(_) | TypeAst::Hole(_) => None, 390 + }) 391 + .collect(); 392 + 393 + let mut next = 0; 394 + arguments 395 + .iter() 396 + .map(|argument| match argument { 397 + TypeAst::Var(TypeAstVar { name, .. }) => name.clone(), 398 + TypeAst::Constructor(_) | TypeAst::Fn(_) | TypeAst::Tuple(_) | TypeAst::Hole(_) => { 399 + loop { 400 + let candidate = type_parameter_name(next); 401 + next += 1; 402 + if used.insert(candidate.clone()) { 403 + break candidate; 404 + } 405 + } 406 + } 407 + }) 408 + .collect() 409 + } 410 + 411 + /// `0 -> "a"`, `25 -> "z"`, `26 -> "aa"`, ... 412 + fn type_parameter_name(index: usize) -> EcoString { 413 + let alphabet_length = 26; 414 + let char_offset = 97; 415 + let mut chars = vec![]; 416 + let mut rest = index; 417 + 418 + loop { 419 + let n = rest % alphabet_length; 420 + rest /= alphabet_length; 421 + chars.push((n as u8 + char_offset) as char); 422 + 423 + if rest == 0 { 424 + break; 425 + } 426 + rest -= 1; 427 + } 428 + 429 + chars.into_iter().rev().collect() 430 + }
+96
language-server/src/code_action.rs
··· 1282 1282 } 1283 1283 } 1284 1284 1285 + pub fn code_action_generate_type( 1286 + module: &Module, 1287 + line_numbers: &LineNumbers, 1288 + params: &CodeActionParams, 1289 + error: &Option<Error>, 1290 + actions: &mut Vec<CodeAction>, 1291 + ) { 1292 + let uri = &params.text_document.uri; 1293 + let Some(errors) = type_errors_for_module(error, module) else { 1294 + return; 1295 + }; 1296 + 1297 + for error in errors { 1298 + let type_::Error::UnknownType { 1299 + location, 1300 + name, 1301 + parameter_names, 1302 + .. 1303 + } = error 1304 + else { 1305 + continue; 1306 + }; 1307 + 1308 + let range = src_span_to_lsp_range(*location, line_numbers); 1309 + if !within(params.range, range) { 1310 + continue; 1311 + } 1312 + 1313 + // Insert the new type stub before the top-level definition that 1314 + // contains the error, so it appears close to where it is used. 1315 + let (insert_at, is_public) = 1316 + definition_start_and_publicity_containing(&module.ast.definitions, location.start) 1317 + .unwrap_or((module.code.len() as u32, false)); 1318 + let insert_range = src_span_to_lsp_range( 1319 + SrcSpan { 1320 + start: insert_at, 1321 + end: insert_at, 1322 + }, 1323 + line_numbers, 1324 + ); 1325 + 1326 + let pub_prefix = if is_public { "pub " } else { "" }; 1327 + let new_text = if parameter_names.is_empty() { 1328 + format!("{pub_prefix}type {name}\n\n") 1329 + } else { 1330 + let parameters = parameter_names.join(", "); 1331 + format!("{pub_prefix}type {name}({parameters})\n\n") 1332 + }; 1333 + 1334 + let edit = TextEdit { 1335 + range: insert_range, 1336 + new_text, 1337 + }; 1338 + 1339 + CodeActionBuilder::new("Generate type") 1340 + .kind(CodeActionKind::QuickFix) 1341 + .changes(uri.clone(), vec![edit]) 1342 + .preferred(true) 1343 + .push_to(actions); 1344 + } 1345 + } 1346 + 1347 + /// Returns the source offset and publicity of the top-level definition that 1348 + /// contains `position`, so the caller can insert code before it. 1349 + fn definition_start_and_publicity_containing( 1350 + definitions: &TypedDefinitions, 1351 + position: u32, 1352 + ) -> Option<(u32, bool)> { 1353 + let functions = definitions 1354 + .functions 1355 + .iter() 1356 + .map(|function| (function.full_location(), function.publicity.is_public())); 1357 + let custom_types = definitions.custom_types.iter().map(|custom_type| { 1358 + ( 1359 + custom_type.full_location(), 1360 + custom_type.publicity.is_public(), 1361 + ) 1362 + }); 1363 + let type_aliases = definitions 1364 + .type_aliases 1365 + .iter() 1366 + .map(|type_alias| (type_alias.location, type_alias.publicity.is_public())); 1367 + let constants = definitions 1368 + .constants 1369 + .iter() 1370 + .map(|constant| (constant.location, constant.publicity.is_public())); 1371 + 1372 + functions 1373 + .chain(custom_types) 1374 + .chain(type_aliases) 1375 + .chain(constants) 1376 + .filter(|(span, _)| span.start <= position && position <= span.end) 1377 + .map(|(span, is_public)| (span.start, is_public)) 1378 + .next() 1379 + } 1380 + 1285 1381 fn suggest_imports( 1286 1382 location: SrcSpan, 1287 1383 importable_modules: &[ModuleSuggestion],
+3 -2
language-server/src/engine.rs
··· 57 57 RemoveEchos, RemovePrivateOpaque, RemoveUnreachableCaseClauses, RemoveUnusedImports, 58 58 UnwrapAnonymousFunction, UseLabelShorthandSyntax, WrapInAnonymousFunction, WrapInBlock, 59 59 code_action_add_missing_patterns, code_action_convert_qualified_constructor_to_unqualified, 60 - code_action_convert_unqualified_constructor_to_qualified, code_action_import_module, 61 - code_action_inexhaustive_let_to_case, 60 + code_action_convert_unqualified_constructor_to_qualified, code_action_generate_type, 61 + code_action_import_module, code_action_inexhaustive_let_to_case, 62 62 }, 63 63 compiler::LspProjectCompiler, 64 64 completer::Completer, ··· 443 443 actions.extend(RemoveUnusedImports::new(module, &lines, &params).code_actions()); 444 444 code_action_fix_names(module, &lines, &params, &this.error, &mut actions); 445 445 code_action_import_module(module, &lines, &params, &this.error, &mut actions); 446 + code_action_generate_type(module, &lines, &params, &this.error, &mut actions); 446 447 code_action_add_missing_patterns(module, &lines, &params, &this.error, &mut actions); 447 448 actions 448 449 .extend(RemoveUnreachableCaseClauses::new(module, &lines, &params).code_actions());
+84
language-server/src/tests/action.rs
··· 185 185 const PATTERN_MATCH_ON_VARIABLE: &str = "Pattern match on variable"; 186 186 const PATTERN_MATCH_ON_VALUE: &str = "Pattern match on value"; 187 187 const GENERATE_FUNCTION: &str = "Generate function"; 188 + const GENERATE_TYPE: &str = "Generate type"; 188 189 const CONVERT_TO_FUNCTION_CALL: &str = "Convert to function call"; 189 190 const INLINE_VARIABLE: &str = "Inline variable"; 190 191 const CONVERT_TO_PIPE: &str = "Convert to pipe"; ··· 8770 8771 } 8771 8772 ", 8772 8773 find_position_of("wibble").to_selection() 8774 + ); 8775 + } 8776 + 8777 + #[test] 8778 + fn generate_type_works_for_unknown_type() { 8779 + assert_code_action!( 8780 + GENERATE_TYPE, 8781 + " 8782 + pub fn main() { 8783 + let x: Wobble = todo 8784 + } 8785 + ", 8786 + find_position_of("Wobble").to_selection() 8787 + ); 8788 + } 8789 + 8790 + #[test] 8791 + fn generate_type_works_for_unknown_type_with_parameters() { 8792 + assert_code_action!( 8793 + GENERATE_TYPE, 8794 + " 8795 + type Wibble { 8796 + Wibble(Wobble(Int, String)) 8797 + } 8798 + ", 8799 + find_position_of("Wobble").to_selection() 8800 + ); 8801 + } 8802 + 8803 + #[test] 8804 + fn generate_type_works_for_unknown_type_in_argument_annotation() { 8805 + assert_code_action!( 8806 + GENERATE_TYPE, 8807 + " 8808 + pub fn wibble(argument: Wibble(some, generics)) { todo } 8809 + ", 8810 + find_position_of("Wibble").to_selection() 8811 + ); 8812 + } 8813 + 8814 + #[test] 8815 + fn generate_type_works_for_unknown_type_in_private_function() { 8816 + assert_code_action!( 8817 + GENERATE_TYPE, 8818 + " 8819 + fn wibble(argument: Wobble) { todo } 8820 + ", 8821 + find_position_of("Wobble").to_selection() 8822 + ); 8823 + } 8824 + 8825 + #[test] 8826 + fn generate_type_generates_non_colliding_parameter_names() { 8827 + assert_code_action!( 8828 + GENERATE_TYPE, 8829 + " 8830 + pub fn wibble(argument: Wibble(b, Int)) { todo } 8831 + ", 8832 + find_position_of("Wibble").to_selection() 8833 + ); 8834 + } 8835 + 8836 + #[test] 8837 + fn generate_type_generates_names_for_more_than_26_parameters() { 8838 + assert_code_action!( 8839 + GENERATE_TYPE, 8840 + " 8841 + pub fn wibble(argument: Wibble(Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int)) { todo } 8842 + ", 8843 + find_position_of("Wibble").to_selection() 8844 + ); 8845 + } 8846 + 8847 + #[test] 8848 + fn generate_type_not_offered_when_cursor_is_elsewhere() { 8849 + assert_no_code_actions!( 8850 + GENERATE_TYPE, 8851 + " 8852 + pub fn main() { 8853 + let x: Wobble = todo 8854 + } 8855 + ", 8856 + find_position_of("main").to_selection() 8773 8857 ); 8774 8858 } 8775 8859
+15
language-server/src/tests/snapshots/gleam_language_server__tests__action__generate_type_generates_names_for_more_than_26_parameters.snap
··· 1 + --- 2 + source: language-server/src/tests/action.rs 3 + expression: "\npub fn wibble(argument: Wibble(Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int)) { todo }\n" 4 + --- 5 + ----- BEFORE ACTION 6 + 7 + pub fn wibble(argument: Wibble(Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int)) { todo } 8 + 9 + 10 + 11 + ----- AFTER ACTION 12 + 13 + pub type Wibble(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, aa, ab, ac, ad) 14 + 15 + pub fn wibble(argument: Wibble(Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int)) { todo }
+15
language-server/src/tests/snapshots/gleam_language_server__tests__action__generate_type_generates_non_colliding_parameter_names.snap
··· 1 + --- 2 + source: language-server/src/tests/action.rs 3 + expression: "\npub fn wibble(argument: Wibble(b, Int)) { todo }\n" 4 + --- 5 + ----- BEFORE ACTION 6 + 7 + pub fn wibble(argument: Wibble(b, Int)) { todo } 8 + 9 + 10 + 11 + ----- AFTER ACTION 12 + 13 + pub type Wibble(b, a) 14 + 15 + pub fn wibble(argument: Wibble(b, Int)) { todo }
+20
language-server/src/tests/snapshots/gleam_language_server__tests__action__generate_type_works_for_unknown_type.snap
··· 1 + --- 2 + source: language-server/src/tests/action.rs 3 + expression: "\npub fn main() {\n let x: Wobble = todo\n}\n" 4 + snapshot_kind: text 5 + --- 6 + ----- BEFORE ACTION 7 + 8 + pub fn main() { 9 + let x: Wobble = todo 10 + 11 + } 12 + 13 + 14 + ----- AFTER ACTION 15 + 16 + pub type Wobble 17 + 18 + pub fn main() { 19 + let x: Wobble = todo 20 + }
+15
language-server/src/tests/snapshots/gleam_language_server__tests__action__generate_type_works_for_unknown_type_in_argument_annotation.snap
··· 1 + --- 2 + source: language-server/src/tests/action.rs 3 + expression: "\npub fn wibble(argument: Wibble(some, generics)) { todo }\n" 4 + --- 5 + ----- BEFORE ACTION 6 + 7 + pub fn wibble(argument: Wibble(some, generics)) { todo } 8 + 9 + 10 + 11 + ----- AFTER ACTION 12 + 13 + pub type Wibble(some, generics) 14 + 15 + pub fn wibble(argument: Wibble(some, generics)) { todo }
+15
language-server/src/tests/snapshots/gleam_language_server__tests__action__generate_type_works_for_unknown_type_in_private_function.snap
··· 1 + --- 2 + source: language-server/src/tests/action.rs 3 + expression: "\nfn wibble(argument: Wobble) { todo }\n" 4 + --- 5 + ----- BEFORE ACTION 6 + 7 + fn wibble(argument: Wobble) { todo } 8 + 9 + 10 + 11 + ----- AFTER ACTION 12 + 13 + type Wobble 14 + 15 + fn wibble(argument: Wobble) { todo }
+20
language-server/src/tests/snapshots/gleam_language_server__tests__action__generate_type_works_for_unknown_type_with_parameters.snap
··· 1 + --- 2 + source: language-server/src/tests/action.rs 3 + expression: "\ntype Wibble {\n Wibble(Wobble(Int, String))\n}\n" 4 + snapshot_kind: text 5 + --- 6 + ----- BEFORE ACTION 7 + 8 + type Wibble { 9 + Wibble(Wobble(Int, String)) 10 + 11 + } 12 + 13 + 14 + ----- AFTER ACTION 15 + 16 + type Wobble(a, b) 17 + 18 + type Wibble { 19 + Wibble(Wobble(Int, String)) 20 + }