@@ -8413,17 +8413,57 @@ impl<'ast> ast::visit::Visit<'ast> for AddOmittedLabels<'ast> {
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,12 +8500,14 @@ impl<'a> ExtractFunction<'a> {
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,9 +8518,11 @@ impl<'a> ExtractFunction<'a> {
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,6 +8544,8 @@ impl<'a> ExtractFunction<'a> {
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,6 +8621,11 @@ impl<'a> ExtractFunction<'a> {
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,6 +8656,64 @@ impl<'a> ExtractFunction<'a> {
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,6 +8733,8 @@ impl<'a> ExtractFunction<'a> {
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,23 +8761,34 @@ impl<'a> ExtractFunction<'a> {
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 ,
8656
8774
type_ : & Arc < Type > ,
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,6 +8797,13 @@ impl<'a> ExtractFunction<'a> {
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,11 +8824,17 @@ impl<'ast> ast::visit::Visit<'ast> for ExtractFunction<'ast> {
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,16 +8845,22 @@ impl<'ast> ast::visit::Visit<'ast> for ExtractFunction<'ast> {
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
..
0 commit comments