this repo has no description

Comment and Changelog

authored by gearsco.de and committed by Louis Pilfold 70259009 c8a78349

Changed files
+191 -8
compiler-core
src
language_server
+42
CHANGELOG.md
··· 662 662 single `let assert` expression is selected. 663 663 ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) 664 664 665 + - The language server now offers an "Extract function" code action to extract a 666 + selected piece of code into a separate function. For example: 667 + 668 + ```gleam 669 + const head_byte_count = 256 670 + 671 + pub fn get_head_of_file() { 672 + let assert Ok(contents) = read_file() 673 + 674 + case contents { 675 + //^ Select from here 676 + <<head:bytes-size(head_byte_count), _:bits>> -> Ok(head) 677 + _ -> Error(Nil) 678 + } 679 + //^ Until here 680 + } 681 + ``` 682 + 683 + Would become: 684 + 685 + ```gleam 686 + const head_byte_count = 256 687 + 688 + pub fn get_head_of_file() { 689 + let assert Ok(contents) = read_file() 690 + 691 + function(contents) 692 + } 693 + 694 + fn function(contents: BitArray) -> Result(BitArray, Nil) { 695 + case contents { 696 + <<head:bytes-size(head_byte_count), _:bits>> -> Ok(head) 697 + _ -> Error(Nil) 698 + } 699 + } 700 + ``` 701 + 702 + You can then use language server renaming to choose an appropriate name for 703 + the new function. 704 + 705 + ([Surya Rose](https://github.com/GearsDatapacks)) 706 + 665 707 ### Formatter 666 708 667 709 - The formatter now removes needless multiple negations that are safe to remove.
+149 -8
compiler-core/src/language_server/code_action.rs
··· 8413 8413 } 8414 8414 8415 8415 /// Code action to extract selected code into a separate function. 8416 + /// If a user selected a portion of code in a function, we offer a code action 8417 + /// to extract it into a new one. This can either be a single expression, such 8418 + /// as in the following example: 8419 + /// 8420 + /// ```gleam 8421 + /// pub fn main() { 8422 + /// let value = { 8423 + /// // ^ User selects from here 8424 + /// ... 8425 + /// } 8426 + /// //^ Until here 8427 + /// } 8428 + /// ``` 8429 + /// 8430 + /// Here, we would extract the selected block expression. It could also be a 8431 + /// series of statements. For example: 8432 + /// 8433 + /// ```gleam 8434 + /// pub fn main() { 8435 + /// let a = 1 8436 + /// //^ User selects from here 8437 + /// let b = 2 8438 + /// let c = a + b 8439 + /// // ^ Until here 8440 + /// 8441 + /// do_more_things(c) 8442 + /// } 8443 + /// ``` 8444 + /// 8445 + /// Here, we want to extract the statements inside the user's selection. 8446 + /// 8416 8447 pub struct ExtractFunction<'a> { 8417 8448 module: &'a Module, 8418 8449 params: &'a CodeActionParams, 8419 8450 edits: TextEdits<'a>, 8420 - extract: Option<ExtractedFunction<'a>>, 8451 + function: Option<ExtractedFunction<'a>>, 8421 8452 function_end_position: Option<u32>, 8422 8453 } 8423 8454 8455 + /// Information about a section of code we are extracting as a function. 8424 8456 struct ExtractedFunction<'a> { 8457 + /// A list of parameters which need to be passed to the extracted function. 8458 + /// These are any variables used in the extracted code, which are defined 8459 + /// outside of the extracted code. 8425 8460 parameters: Vec<(EcoString, Arc<Type>)>, 8461 + /// A list of values which need to be returned from the extracted function. 8462 + /// These are the variables defined in the extracted code which are used 8463 + /// outside of the extracted section. 8426 8464 returned_variables: Vec<(EcoString, Arc<Type>)>, 8465 + /// The piece of code to be extracted. This is either a single expression or 8466 + /// a list of statements, as explained in the documentation of `ExtractFunction` 8427 8467 value: ExtractedValue<'a>, 8428 8468 } 8429 8469 ··· 8460 8500 module, 8461 8501 params, 8462 8502 edits: TextEdits::new(line_numbers), 8463 - extract: None, 8503 + function: None, 8464 8504 function_end_position: None, 8465 8505 } 8466 8506 } 8467 8507 8468 8508 pub fn code_actions(mut self) -> Vec<CodeAction> { 8509 + // If no code is selected, then there is no function to extract and we 8510 + // can return no code actions. 8469 8511 if self.params.range.start == self.params.range.end { 8470 8512 return Vec::new(); 8471 8513 } ··· 8476 8518 return Vec::new(); 8477 8519 }; 8478 8520 8479 - let Some(extracted) = self.extract.take() else { 8521 + // If nothing was found in the selected range, there is no code action. 8522 + let Some(extracted) = self.function.take() else { 8480 8523 return Vec::new(); 8481 8524 }; 8525 + 8482 8526 match extracted.value { 8483 8527 ExtractedValue::Expression(expression) => { 8484 8528 self.extract_expression(expression, extracted.parameters, end) ··· 8500 8544 action 8501 8545 } 8502 8546 8547 + /// Choose a suitable name for an extracted function to make sure it doesn't 8548 + /// clash with existing functions defined in the module and cause an error. 8503 8549 fn function_name(&self) -> EcoString { 8504 8550 if !self.module.ast.type_info.values.contains_key("function") { 8505 8551 return "function".into(); ··· 8575 8621 let name = self.function_name(); 8576 8622 let arguments = parameters.iter().map(|(name, _)| name).join(", "); 8577 8623 let call = format!("{name}({arguments})"); 8624 + 8625 + // Since we are only extracting a single expression, we can just replace 8626 + // it with the call and preserve all other semantics; only one value can 8627 + // be returned from the expression, unlike when extracting multiple 8628 + // statements. 8578 8629 self.edits.replace(expression.location(), call); 8579 8630 8580 8631 let mut printer = Printer::new(&self.module.ast.names); ··· 8605 8656 8606 8657 let returns_anything = !returned_variables.is_empty(); 8607 8658 8659 + // Here, we decide what value to return from the function. There are 8660 + // three cases: 8661 + // The first is when the extracted code is purely for side-effects, and 8662 + // does not produce any values which are needed outside of the extracted 8663 + // code. For example: 8664 + // 8665 + // ```gleam 8666 + // pub fn main() { 8667 + // let message = "Something important" 8668 + // //^ Select from here 8669 + // io.println("Something important") 8670 + // io.println("Something else which is repeated") 8671 + // // ^ Until here 8672 + // 8673 + // do_final_thing() 8674 + // } 8675 + // ``` 8676 + // 8677 + // It doesn't make sense to return any values from this function, since 8678 + // no values from the extract code are used afterwards, so we simply 8679 + // return `Nil`. 8680 + // 8681 + // The next is when we need just a single value defined in the extracted 8682 + // function, such as in this piece of code: 8683 + // 8684 + // ```gleam 8685 + // pub fn main() { 8686 + // let a = 10 8687 + // //^ Select from here 8688 + // let b = 20 8689 + // let c = a + b 8690 + // // ^ Until here 8691 + // 8692 + // echo c 8693 + // } 8694 + // ``` 8695 + // 8696 + // Here, we can just return the single value, `c`. 8697 + // 8698 + // The last situation is when we need multiple defined values, such as 8699 + // in the following code: 8700 + // 8701 + // ```gleam 8702 + // pub fn main() { 8703 + // let a = 10 8704 + // //^ Select from here 8705 + // let b = 20 8706 + // let c = a + b 8707 + // // ^ Until here 8708 + // 8709 + // echo a 8710 + // echo b 8711 + // echo c 8712 + // } 8713 + // ``` 8714 + // 8715 + // In this case, we must return a tuple containing `a`, `b` and `c` in 8716 + // order for the calling function to have access to the correct values. 8608 8717 let (return_type, return_value) = match returned_variables.as_slice() { 8609 8718 [] => (type_::nil(), "Nil".into()), 8610 8719 [(name, type_)] => (type_.clone(), name.clone()), ··· 8624 8733 let name = self.function_name(); 8625 8734 let arguments = parameters.iter().map(|(name, _)| name).join(", "); 8626 8735 8736 + // If any values are returned from the extracted function, we need to 8737 + // bind them so that they are accessible in the current scope. 8627 8738 let call = if returns_anything { 8628 8739 format!("let {return_value} = {name}({arguments})") 8629 8740 } else { ··· 8650 8761 self.edits.insert(function_end, function); 8651 8762 } 8652 8763 8764 + /// When a variable is referenced, we need to decide if we need to do anything 8765 + /// to ensure that the reference is still valid after extracting a function. 8766 + /// If the variable is defined outside the extracted function, but used inside 8767 + /// it, then we need to add it as a parameter of the function. Similarly, if 8768 + /// a variable is defined inside the extracted code, but used outside of it, 8769 + /// we need to ensure that value is returned from the function so that it is 8770 + /// accessible. 8653 8771 fn register_referenced_variable( 8654 8772 &mut self, 8655 8773 name: &EcoString, ··· 8657 8775 location: SrcSpan, 8658 8776 definition_location: SrcSpan, 8659 8777 ) { 8660 - let Some(extracted) = &mut self.extract else { 8778 + let Some(extracted) = &mut self.function else { 8661 8779 return; 8662 8780 }; 8663 8781 8664 8782 let extracted_location = extracted.location(); 8665 8783 8784 + // If a variable defined outside the extracted code is referenced inside 8785 + // it, we need to add it to the list of parameters. 8666 8786 let variables = if extracted_location.contains_span(location) 8667 8787 && !extracted_location.contains_span(definition_location) 8668 8788 { 8669 8789 &mut extracted.parameters 8790 + // If a variable defined inside the extracted code is referenced outside 8791 + // it, then we need to ensure that it is returned from the function. 8670 8792 } else if extracted_location.contains_span(definition_location) 8671 8793 && !extracted_location.contains_span(location) 8672 8794 { ··· 8675 8797 return; 8676 8798 }; 8677 8799 8800 + // If the variable has already been tracked, no need to register it again. 8801 + // We use a `Vec` here rather than a `HashMap` because we want to ensure 8802 + // the order of arguments is consistent; in this case it will be determined 8803 + // by the order the variables are used. This isn't always desired, but it's 8804 + // better than random order, and makes it easier to write tests too. 8805 + // The cost of iterating the list here is minimal; it is unlikely that 8806 + // a given function will ever have more than 10 or so parameters. 8678 8807 if variables.iter().any(|(variable, _)| variable == name) { 8679 8808 return; 8680 8809 } ··· 8695 8824 } 8696 8825 8697 8826 fn visit_typed_expr(&mut self, expression: &'ast TypedExpr) { 8698 - if self.extract.is_none() { 8827 + // If we have already determined what code we want to extract, we don't 8828 + // want to extract this instead. This expression would be inside the 8829 + // piece of code we already are going to extract, leading to us 8830 + // extracting just a single literal in any selection, which is of course 8831 + // not desired. 8832 + if self.function.is_none() { 8699 8833 let range = self.edits.src_span_to_lsp_range(expression.location()); 8700 8834 8835 + // If this expression is fully selected, we mark it as being extracted. 8701 8836 if within(range, self.params.range) { 8702 - self.extract = Some(ExtractedFunction::new(ExtractedValue::Expression( 8837 + self.function = Some(ExtractedFunction::new(ExtractedValue::Expression( 8703 8838 expression, 8704 8839 ))); 8705 8840 } ··· 8710 8845 fn visit_typed_statement(&mut self, statement: &'ast TypedStatement) { 8711 8846 let range = self.edits.src_span_to_lsp_range(statement.location()); 8712 8847 if within(range, self.params.range) { 8713 - match &mut self.extract { 8848 + match &mut self.function { 8714 8849 None => { 8715 - self.extract = Some(ExtractedFunction::new(ExtractedValue::Statements( 8850 + self.function = Some(ExtractedFunction::new(ExtractedValue::Statements( 8716 8851 statement.location(), 8717 8852 ))); 8718 8853 } 8854 + // If we have already chosen an expression to extract, that means 8855 + // that this statement is within the already extracted expression, 8856 + // so we don't want to extract this instead. 8719 8857 Some(ExtractedFunction { 8720 8858 value: ExtractedValue::Expression(_), 8721 8859 .. 8722 8860 }) => {} 8861 + // If we are selecting multiple statements, this statement should 8862 + // be included within list, so we merge th spans to ensure it is 8863 + // included. 8723 8864 Some(ExtractedFunction { 8724 8865 value: ExtractedValue::Statements(location), 8725 8866 ..