Skip to content

Commit 7025900

Browse files
GearsDatapackslpil
authored andcommitted
Comment and Changelog
1 parent c8a7834 commit 7025900

File tree

2 files changed

+191
-8
lines changed

2 files changed

+191
-8
lines changed

CHANGELOG.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -662,6 +662,48 @@
662662
single `let assert` expression is selected.
663663
([Giacomo Cavalieri](https://github.com/giacomocavalieri))
664664

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+
665707
### Formatter
666708

667709
- The formatter now removes needless multiple negations that are safe to remove.

compiler-core/src/language_server/code_action.rs

Lines changed: 149 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8413,17 +8413,57 @@ impl<'ast> ast::visit::Visit<'ast> for AddOmittedLabels<'ast> {
84138413
}
84148414

84158415
/// 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+
///
84168447
pub struct ExtractFunction<'a> {
84178448
module: &'a Module,
84188449
params: &'a CodeActionParams,
84198450
edits: TextEdits<'a>,
8420-
extract: Option<ExtractedFunction<'a>>,
8451+
function: Option<ExtractedFunction<'a>>,
84218452
function_end_position: Option<u32>,
84228453
}
84238454

8455+
/// Information about a section of code we are extracting as a function.
84248456
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.
84258460
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.
84268464
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`
84278467
value: ExtractedValue<'a>,
84288468
}
84298469

@@ -8460,12 +8500,14 @@ impl<'a> ExtractFunction<'a> {
84608500
module,
84618501
params,
84628502
edits: TextEdits::new(line_numbers),
8463-
extract: None,
8503+
function: None,
84648504
function_end_position: None,
84658505
}
84668506
}
84678507

84688508
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.
84698511
if self.params.range.start == self.params.range.end {
84708512
return Vec::new();
84718513
}
@@ -8476,9 +8518,11 @@ impl<'a> ExtractFunction<'a> {
84768518
return Vec::new();
84778519
};
84788520

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 {
84808523
return Vec::new();
84818524
};
8525+
84828526
match extracted.value {
84838527
ExtractedValue::Expression(expression) => {
84848528
self.extract_expression(expression, extracted.parameters, end)
@@ -8500,6 +8544,8 @@ impl<'a> ExtractFunction<'a> {
85008544
action
85018545
}
85028546

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.
85038549
fn function_name(&self) -> EcoString {
85048550
if !self.module.ast.type_info.values.contains_key("function") {
85058551
return "function".into();
@@ -8575,6 +8621,11 @@ impl<'a> ExtractFunction<'a> {
85758621
let name = self.function_name();
85768622
let arguments = parameters.iter().map(|(name, _)| name).join(", ");
85778623
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.
85788629
self.edits.replace(expression.location(), call);
85798630

85808631
let mut printer = Printer::new(&self.module.ast.names);
@@ -8605,6 +8656,64 @@ impl<'a> ExtractFunction<'a> {
86058656

86068657
let returns_anything = !returned_variables.is_empty();
86078658

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.
86088717
let (return_type, return_value) = match returned_variables.as_slice() {
86098718
[] => (type_::nil(), "Nil".into()),
86108719
[(name, type_)] => (type_.clone(), name.clone()),
@@ -8624,6 +8733,8 @@ impl<'a> ExtractFunction<'a> {
86248733
let name = self.function_name();
86258734
let arguments = parameters.iter().map(|(name, _)| name).join(", ");
86268735

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.
86278738
let call = if returns_anything {
86288739
format!("let {return_value} = {name}({arguments})")
86298740
} else {
@@ -8650,23 +8761,34 @@ impl<'a> ExtractFunction<'a> {
86508761
self.edits.insert(function_end, function);
86518762
}
86528763

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.
86538771
fn register_referenced_variable(
86548772
&mut self,
86558773
name: &EcoString,
86568774
type_: &Arc<Type>,
86578775
location: SrcSpan,
86588776
definition_location: SrcSpan,
86598777
) {
8660-
let Some(extracted) = &mut self.extract else {
8778+
let Some(extracted) = &mut self.function else {
86618779
return;
86628780
};
86638781

86648782
let extracted_location = extracted.location();
86658783

8784+
// If a variable defined outside the extracted code is referenced inside
8785+
// it, we need to add it to the list of parameters.
86668786
let variables = if extracted_location.contains_span(location)
86678787
&& !extracted_location.contains_span(definition_location)
86688788
{
86698789
&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.
86708792
} else if extracted_location.contains_span(definition_location)
86718793
&& !extracted_location.contains_span(location)
86728794
{
@@ -8675,6 +8797,13 @@ impl<'a> ExtractFunction<'a> {
86758797
return;
86768798
};
86778799

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.
86788807
if variables.iter().any(|(variable, _)| variable == name) {
86798808
return;
86808809
}
@@ -8695,11 +8824,17 @@ impl<'ast> ast::visit::Visit<'ast> for ExtractFunction<'ast> {
86958824
}
86968825

86978826
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() {
86998833
let range = self.edits.src_span_to_lsp_range(expression.location());
87008834

8835+
// If this expression is fully selected, we mark it as being extracted.
87018836
if within(range, self.params.range) {
8702-
self.extract = Some(ExtractedFunction::new(ExtractedValue::Expression(
8837+
self.function = Some(ExtractedFunction::new(ExtractedValue::Expression(
87038838
expression,
87048839
)));
87058840
}
@@ -8710,16 +8845,22 @@ impl<'ast> ast::visit::Visit<'ast> for ExtractFunction<'ast> {
87108845
fn visit_typed_statement(&mut self, statement: &'ast TypedStatement) {
87118846
let range = self.edits.src_span_to_lsp_range(statement.location());
87128847
if within(range, self.params.range) {
8713-
match &mut self.extract {
8848+
match &mut self.function {
87148849
None => {
8715-
self.extract = Some(ExtractedFunction::new(ExtractedValue::Statements(
8850+
self.function = Some(ExtractedFunction::new(ExtractedValue::Statements(
87168851
statement.location(),
87178852
)));
87188853
}
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.
87198857
Some(ExtractedFunction {
87208858
value: ExtractedValue::Expression(_),
87218859
..
87228860
}) => {}
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.
87238864
Some(ExtractedFunction {
87248865
value: ExtractedValue::Statements(location),
87258866
..

0 commit comments

Comments
 (0)