+42
CHANGELOG.md
+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
+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
..