···34803480pub enum AssignmentKind<Expression> {
34813481 /// let x = ...
34823482 Let,
34833483- /// This is a let assignment generated by the compiler for intermediate variables
34843484- /// needed by record updates and `use`.
34833483+ /// This is a let assignment generated by the compiler for intermediate
34843484+ /// variables needed by record updates and `use`.
34853485 /// Like a regular `Let` assignment this can never fail.
34863486 ///
34873487 Generated,
···45644564///
45654565#[derive(Debug, Clone, PartialEq, Eq)]
45664566pub struct TypedPipelineAssignment {
45674567+ /// This is the location of the corresponding pipeline step.
45684568+ ///
45694569+ /// Take this pipeline:
45704570+ ///
45714571+ /// ```gleam
45724572+ /// wibble |> wobble |> woo
45734573+ /// ```
45744574+ ///
45754575+ /// It's made of two steps and a final expression:
45764576+ ///
45774577+ /// ```gleam
45784578+ /// let step_0 = wibble
45794579+ /// let step_1 = wobble(step_0)
45804580+ /// woo(step_1)
45814581+ /// ```
45824582+ ///
45834583+ /// The locations of each step would be the following:
45844584+ ///
45854585+ /// ```gleam
45864586+ /// wibble |> wobble |> woo
45874587+ /// ^^^^^^ location of first step
45884588+ /// ^^^^^^ location of second step
45894589+ /// ```
45904590+ ///
45674591 pub location: SrcSpan,
45684592 pub name: EcoString,
45694593 pub value: Box<TypedExpr>,
···3131use lsp_types::{CodeAction, CodeActionKind, CodeActionParams, Position, Range, TextEdit, Url};
3232use vec1::{Vec1, vec1};
33333434+use crate::engine::{completely_within, position_within};
3535+3436use super::{
3537 TextEdits,
3638 compiler::LspProjectCompiler,
···97169718 /// a statement is the last in a block or function, we need to track that
97179719 /// manually.
97189720 last_statement_location: Option<SrcSpan>,
97219721+ /// When visiting a pipeline step, this will hold the type of the value
97229722+ /// returned by the previous step (if any!)
97239723+ previous_pipeline_assignment_type: Option<Arc<Type>>,
97199724}
9720972597219726/// Information about a section of code we are extracting as a function.
97279727+#[derive(Debug)]
97229728struct ExtractedFunction<'a> {
97239729 /// A list of parameters which need to be passed to the extracted function.
97249730 /// These are any variables used in the extracted code, which are defined
···97459751 fn location(&self) -> SrcSpan {
97469752 match &self.value {
97479753 ExtractedValue::Expression(expression) => expression.location(),
97489748- ExtractedValue::Statements { location, .. } => *location,
97549754+ ExtractedValue::Statements { location, .. }
97559755+ | ExtractedValue::PipelineSteps { location, .. } => *location,
97569756+ }
97579757+ }
97589758+97599759+ /// If the extracted function is a series of pipeline steps, this adds to it
97609760+ /// the given pipeline step, otherwise leaving it unchanged.
97619761+ /// If the extracted function was indeed a pipeline, this will return `true`,
97629762+ /// otherwise it returns `false`.
97639763+ ///
97649764+ fn try_add_pipeline_step(&mut self, step_type: Arc<Type>, step_location: SrcSpan) {
97659765+ if let ExtractedFunction {
97669766+ value:
97679767+ ExtractedValue::PipelineSteps {
97689768+ location,
97699769+ before_first: _,
97709770+ return_type,
97719771+ },
97729772+ ..
97739773+ } = self
97749774+ {
97759775+ // If we're extracting this pipeline and the final step is included
97769776+ // in the selection we want to add it to the extracted steps
97779777+ *return_type = step_type;
97789778+ *location = location.merge(&step_location);
97499779 }
97509780 }
97519781}
···97579787 location: SrcSpan,
97589788 position: StatementPosition,
97599789 },
97909790+ PipelineSteps {
97919791+ location: SrcSpan,
97929792+ /// The type of the value produced by the pipeline steps that will be
97939793+ /// piped into the extracted function. Could be none if the steps we're
97949794+ /// extracting include the first step, in that case there would be
97959795+ /// nothing that is fed into it.
97969796+ before_first: Option<Arc<Type>>,
97979797+ /// The type returned by the extracted steps.
97989798+ return_type: Arc<Type>,
97999799+ },
98009800+}
98019801+98029802+impl ExtractedValue<'_> {
98039803+ fn location(&self) -> SrcSpan {
98049804+ match self {
98059805+ ExtractedValue::Expression(typed_expr) => typed_expr.location(),
98069806+ ExtractedValue::Statements { location, .. }
98079807+ | ExtractedValue::PipelineSteps { location, .. } => *location,
98089808+ }
98099809+ }
97609810}
9761981197629812/// When we are extracting multiple statements, there are two possible cases:
···98199869 function: None,
98209870 function_end_position: None,
98219871 last_statement_location: None,
98729872+ previous_pipeline_assignment_type: None,
98229873 }
98239874 }
98249875···98419892 };
9842989398439894 match extracted.value {
98449844- // If we extract a block, it isn't very helpful to have the body of the
98459845- // extracted function just be a single block expression, so instead we
98469846- // extract the statements inside the block. For example, the following
98479847- // code:
98959895+ // If we extract a block, it isn't very helpful to have the body of
98969896+ // the extracted function just be a single block expression, so
98979897+ // instead we extract the statements inside the block. For example,
98989898+ // the following code:
98489899 //
98499900 // ```gleam
98509901 // pub fn main() {
···997310024 type_,
997410025 extracted.parameters,
997510026 end,
1002710027+ ),
1002810028+ ExtractedValue::PipelineSteps {
1002910029+ location,
1003010030+ before_first,
1003110031+ return_type,
1003210032+ } => self.extract_pipeline_steps(
1003310033+ location,
1003410034+ end,
1003510035+ extracted.parameters,
1003610036+ before_first,
1003710037+ return_type,
997610038 ),
997710039 }
997810040···1034610408 self.edits.insert(function_end, function);
1034710409 }
10348104101041110411+ fn extract_pipeline_steps(
1041210412+ &mut self,
1041310413+ location: SrcSpan,
1041410414+ function_end: u32,
1041510415+ parameters: Vec<(EcoString, Arc<Type>)>,
1041610416+ before_first: Option<Arc<Type>>,
1041710417+ return_type: Arc<Type>,
1041810418+ ) {
1041910419+ let name = self.function_name();
1042010420+ let code = code_at(self.module, location);
1042110421+ let arguments = parameters.iter().map(|(name, _)| name.clone()).join(", ");
1042210422+ let replacement = match before_first {
1042310423+ Some(_) if parameters.is_empty() => format!("{name}"),
1042410424+ Some(_) | None => format!("{name}({arguments})"),
1042510425+ };
1042610426+ self.edits.replace(location, replacement);
1042710427+1042810428+ // When extracting something out of the middle of a pipeline the
1042910429+ // function we produce will produce a single value as output but could
1043010430+ // take multiple values as input:
1043110431+ //
1043210432+ // ```gleam
1043310433+ // wibble
1043410434+ // |> wobble(a) // extracting this
1043510435+ // |> woo(b) //
1043610436+ // |> something
1043710437+ // ```
1043810438+ //
1043910439+ // It will take the type returned by `wibble`, `a`, and `b` as input,
1044010440+ // and produce the value returned by `woo` as output:
1044110441+ //
1044210442+ // ```gleam
1044310443+ // wibble
1044410444+ // |> function(a, b)
1044510445+ // |> something
1044610446+ // ```
1044710447+ //
1044810448+ // If the steps extracted are at the beginning of the pipeline, then it
1044910449+ // won't take that additional argument!
1045010450+ //
1045110451+ // ```gleam
1045210452+ // wibble // extracting these
1045310453+ // |> wobble(a) //
1045410454+ // |> woo(b) //
1045510455+ // |> something
1045610456+ // ```
1045710457+ //
1045810458+ // Becomes:
1045910459+ //
1046010460+ // ```gleam
1046110461+ // function(a, b)
1046210462+ // |> something
1046310463+ // ```
1046410464+1046510465+ let mut type_printer = Printer::new(&self.module.ast.names);
1046610466+ let return_type = type_printer.print_type(&return_type);
1046710467+ let first_argument = before_first.map(|type_of_first_argument| {
1046810468+ let mut generator = NameGenerator::new();
1046910469+ for (name, _) in parameters.iter() {
1047010470+ generator.add_used_name(name.clone());
1047110471+ }
1047210472+ let first_argument_name = generator.generate_name_from_type(&type_of_first_argument);
1047310473+ (first_argument_name, type_of_first_argument.clone())
1047410474+ });
1047510475+ let parameters = first_argument
1047610476+ .clone()
1047710477+ .into_iter()
1047810478+ .chain(parameters)
1047910479+ .map(|(name, type_)| eco_format!("{name}: {}", type_printer.print_type(&type_)))
1048010480+ .join(", ");
1048110481+1048210482+ let code = if let Some((first_argument_name, _)) = first_argument {
1048310483+ format!("{first_argument_name}\n |> {code}")
1048410484+ } else {
1048510485+ format!("{}", code.trim_start_matches("|>"))
1048610486+ };
1048710487+ let function = format!(
1048810488+ "\n\nfn {name}({parameters}) -> {return_type} {{
1048910489+ {code}
1049010490+}}"
1049110491+ );
1049210492+1049310493+ self.edits.insert(function_end, function);
1049410494+ }
1049510495+1034910496 /// When a variable is referenced, we need to decide if we need to do anything
1035010497 /// to ensure that the reference is still valid after extracting a function.
1035110498 /// If the variable is defined outside the extracted function, but used inside
···1039610543 variables.push((name.clone(), type_.clone()));
1039710544 }
10398105451039910399- fn can_extract(&self, location: SrcSpan) -> bool {
1040010400- let expression_range = self.edits.src_span_to_lsp_range(location);
1054610546+ fn can_extract_expression(&self, expression: &TypedExpr) -> bool {
1054710547+ let expression_range = self.edits.src_span_to_lsp_range(expression.location());
1040110548 let selected_range = self.params.range;
10402105491040310550 // If the selected range doesn't touch the expression at all, then there
···1040610553 return false;
1040710554 }
10408105551040910409- // Determine whether the selected range falls completely within the
1041010410- // expression. For example:
1041110411- // ```gleam
1041210412- // pub fn main() {
1041310413- // let something = {
1041410414- // let a = 1
1041510415- // let b = 2
1041610416- // let c = a + b
1041710417- // //^ The user has selected from here
1041810418- // let d = a * b
1041910419- // c / d
1042010420- // // ^ Until here
1042110421- // }
1042210422- // }
1042310423- // ```
1042410424- //
1042510425- // Here, the selected range does overlap with the `let something`
1042610426- // statement; but we don't want to extract that whole statement! The
1042710427- // user only wanted to extract the statements inside the block. So if
1042810428- // the selected range falls completely within the expression, we ignore
1042910429- // it and traverse the tree further until we find exactly what the user
1043010430- // selected.
1043110431- //
1043210432- let selected_within_expression = selected_range.start > expression_range.start
1043310433- && selected_range.start < expression_range.end
1043410434- && selected_range.end > expression_range.start
1043510435- && selected_range.end < expression_range.end;
1055610556+ match expression {
1055710557+ TypedExpr::Pipeline {
1055810558+ first_value,
1055910559+ finally,
1056010560+ ..
1056110561+ } => {
1056210562+ // We can extract a pipeline as a whole only if the selection
1056310563+ // spans all of its steps!
1056410564+ let first_step = self.edits.src_span_to_lsp_range(first_value.location);
1056510565+ let last_step = self.edits.src_span_to_lsp_range(finally.location());
1056610566+ position_within(selected_range.start, first_step)
1056710567+ && position_within(selected_range.end, last_step)
1056810568+ }
1056910569+1057010570+ TypedExpr::Int { .. }
1057110571+ | TypedExpr::Float { .. }
1057210572+ | TypedExpr::String { .. }
1057310573+ | TypedExpr::Block { .. }
1057410574+ | TypedExpr::Var { .. }
1057510575+ | TypedExpr::Fn { .. }
1057610576+ | TypedExpr::List { .. }
1057710577+ | TypedExpr::Call { .. }
1057810578+ | TypedExpr::BinOp { .. }
1057910579+ | TypedExpr::Case { .. }
1058010580+ | TypedExpr::RecordAccess { .. }
1058110581+ | TypedExpr::PositionalAccess { .. }
1058210582+ | TypedExpr::ModuleSelect { .. }
1058310583+ | TypedExpr::Tuple { .. }
1058410584+ | TypedExpr::TupleIndex { .. }
1058510585+ | TypedExpr::Todo { .. }
1058610586+ | TypedExpr::Panic { .. }
1058710587+ | TypedExpr::Echo { .. }
1058810588+ | TypedExpr::BitArray { .. }
1058910589+ | TypedExpr::RecordUpdate { .. }
1059010590+ | TypedExpr::NegateBool { .. }
1059110591+ | TypedExpr::NegateInt { .. }
1059210592+ | TypedExpr::Invalid { .. } => !completely_within(selected_range, expression_range),
1059310593+ }
1059410594+ }
10436105951043710437- // If the selected range is completely within the expression, we don't
1043810438- // want to extract it.
1043910439- !selected_within_expression
1059610596+ fn can_extract_statement(&self, statement: &TypedStatement) -> bool {
1059710597+ match statement {
1059810598+ ast::Statement::Expression(expression) => self.can_extract_expression(expression),
1059910599+1060010600+ // Determine whether the selected range falls completely within the
1060110601+ // expression. For example:
1060210602+ // ```gleam
1060310603+ // pub fn main() {
1060410604+ // let something = {
1060510605+ // let a = 1
1060610606+ // let b = 2
1060710607+ // let c = a + b
1060810608+ // //^ The user has selected from here
1060910609+ // let d = a * b
1061010610+ // c / d
1061110611+ // // ^ Until here
1061210612+ // }
1061310613+ // }
1061410614+ // ```
1061510615+ //
1061610616+ // Here, the selected range does overlap with the `let something`
1061710617+ // statement; but we don't want to extract that whole statement! The
1061810618+ // user only wanted to extract the statements inside the block. So if
1061910619+ // the selected range falls completely within the expression, we ignore
1062010620+ // it and traverse the tree further until we find exactly what the user
1062110621+ // selected.
1062210622+ //
1062310623+ // If the selected range is completely within the expression, we don't
1062410624+ // want to extract it.
1062510625+ ast::Statement::Assignment(_) | ast::Statement::Use(_) | ast::Statement::Assert(_) => {
1062610626+ let statement_range = self.edits.src_span_to_lsp_range(statement.location());
1062710627+ let selected_range = self.params.range;
1062810628+1062910629+ // If the selected range doesn't touch the statement at all, then there
1063010630+ // is no reason to extract it.
1063110631+ if !overlaps(statement_range, selected_range) {
1063210632+ return false;
1063310633+ }
1063410634+ !completely_within(selected_range, statement_range)
1063510635+ }
1063610636+ }
1044010637 }
1044110638}
1044210639···1047310670 // not desired.
1047410671 if self.function.is_none() {
1047510672 // If this expression is fully selected, we mark it as being extracted.
1047610476- if self.can_extract(expression.location()) {
1067310673+ if self.can_extract_expression(expression) {
1047710674 self.function = Some(ExtractedFunction::new(ExtractedValue::Expression(
1047810675 expression,
1047910676 )));
···1048510682 fn visit_typed_statement(&mut self, statement: &'ast TypedStatement) {
1048610683 let statement_location = statement.location();
10487106841048810488- if self.can_extract(statement_location) {
1068510685+ if self.can_extract_statement(statement) {
1048910686 let is_in_tail_position =
1049010687 self.last_statement_location
1049110688 .is_some_and(|last_statement_location| {
···10505107021050610703 match &mut self.function {
1050710704 None => {
1050810508- self.function = Some(ExtractedFunction::new(ExtractedValue::Statements {
1050910509- location: statement_location,
1051010510- position,
1051110511- }));
1070510705+ self.function = match statement {
1070610706+ TypedStatement::Expression(TypedExpr::Pipeline { .. }) => None,
1070710707+ TypedStatement::Assert(_)
1070810708+ | TypedStatement::Assignment(_)
1070910709+ | TypedStatement::Expression(_)
1071010710+ | TypedStatement::Use(_) => {
1071110711+ Some(ExtractedFunction::new(ExtractedValue::Statements {
1071210712+ location: statement_location,
1071310713+ position,
1071410714+ }))
1071510715+ }
1071610716+ }
1051210717 }
1071810718+1051310719 // If we have already chosen an expression to extract, that means
1051410720 // that this statement is within the already extracted expression,
1051510721 // so we don't want to extract this instead.
···1051810724 ..
1051910725 }) => {}
1052010726 // If we are selecting multiple statements, this statement should
1052110521- // be included within list, so we merge the spans to ensure it
1052210522- // is included.
1072710727+ // be included within that list, so we merge the spans to ensure
1072810728+ // it is included.
1052310729 Some(ExtractedFunction {
1052410730 value:
1052510731 ExtractedValue::Statements {
···1053110737 *location = location.merge(&statement_location);
1053210738 *extracted_position = position;
1053310739 }
1074010740+ Some(ExtractedFunction {
1074110741+ value: value @ ExtractedValue::PipelineSteps { .. },
1074210742+ ..
1074310743+ }) => {
1074410744+ // If we were extracting a pipeline, but end up selecting
1074510745+ // some statement that is not part of it, then we go back to
1074610746+ // selecting a batch of statements.
1074710747+ *value = ExtractedValue::Statements {
1074810748+ location: value.location(),
1074910749+ position,
1075010750+ }
1075110751+ }
1053410752 }
1053510753 }
1053610754 ast::visit::visit_typed_statement(self, statement);
1075510755+ }
1075610756+1075710757+ fn visit_typed_expr_pipeline(
1075810758+ &mut self,
1075910759+ _location: &'ast SrcSpan,
1076010760+ first_value: &'ast TypedPipelineAssignment,
1076110761+ assignments: &'ast [(TypedPipelineAssignment, PipelineAssignmentKind)],
1076210762+ finally: &'ast TypedExpr,
1076310763+ _finally_kind: &'ast PipelineAssignmentKind,
1076410764+ ) {
1076510765+ self.previous_pipeline_assignment_type = None;
1076610766+ self.visit_typed_pipeline_assignment(first_value);
1076710767+1076810768+ self.previous_pipeline_assignment_type = Some(first_value.type_());
1076910769+ for (assignment, _kind) in assignments {
1077010770+ self.visit_typed_pipeline_assignment(assignment);
1077110771+ self.previous_pipeline_assignment_type = Some(assignment.type_());
1077210772+ }
1077310773+1077410774+ // If we're selecting a pipeline and the selection ends on its final step
1077510775+ // we want to include that as well into the extracted bit.
1077610776+ let final_step_range = self.edits.src_span_to_lsp_range(finally.location());
1077710777+ if let Some(extracted_function) = &mut self.function
1077810778+ && position_within(self.params.range.end, final_step_range)
1077910779+ {
1078010780+ extracted_function.try_add_pipeline_step(finally.type_(), finally.location());
1078110781+ };
1078210782+1078310783+ self.visit_typed_expr(finally);
1078410784+ self.previous_pipeline_assignment_type = None;
1078510785+ }
1078610786+1078710787+ fn visit_typed_pipeline_assignment(&mut self, assignment: &'ast TypedPipelineAssignment) {
1078810788+ // In order to be extracted, a pipeline step must be overlapping with
1078910789+ // the cursor selection!
1079010790+ let assignment_range = self.edits.src_span_to_lsp_range(assignment.location);
1079110791+ if !overlaps(self.params.range, assignment_range) {
1079210792+ return;
1079310793+ }
1079410794+1079510795+ match &mut self.function {
1079610796+ None => {
1079710797+ self.function = Some(ExtractedFunction::new(ExtractedValue::PipelineSteps {
1079810798+ location: assignment.location,
1079910799+ before_first: self.previous_pipeline_assignment_type.clone(),
1080010800+ return_type: assignment.type_(),
1080110801+ }));
1080210802+ }
1080310803+ Some(extracted_function) => {
1080410804+ extracted_function.try_add_pipeline_step(assignment.type_(), assignment.location)
1080510805+ }
1080610806+ }
1080710807+ ast::visit::visit_typed_pipeline_assignment(self, assignment);
1053710808 }
10538108091053910810 fn visit_typed_expr_var(
···16891689 }
16901690}
1691169116921692-// Returns true if any part of either range overlaps with the other.
16921692+/// Returns true if any part of either range overlaps with the other.
16931693pub fn overlaps(a: Range, b: Range) -> bool {
16941694 position_within(a.start, b)
16951695 || position_within(a.end, b)
···16971697 || position_within(b.end, a)
16981698}
1699169917001700-// Returns true if a range is contained within another.
17001700+/// Returns true if the first range is within the second range.
17011701+/// The ranges might touch on their extremes and still be considered one within
17021702+/// the other!
17031703+///
17041704+/// `within(a, b)` is true in all of these cases:
17051705+///
17061706+/// - ```txt
17071707+/// |------| b
17081708+/// |---| a
17091709+/// ```
17101710+/// - ```txt
17111711+/// |------| b
17121712+/// |---| a
17131713+/// ```
17141714+/// - ```txt
17151715+/// |------| b
17161716+/// |---| a
17171717+/// ```
17181718+/// - ```txt
17191719+/// |------| b
17201720+/// |------| a
17211721+/// ```
17221722+///
17011723pub fn within(a: Range, b: Range) -> bool {
17021724 position_within(a.start, b) && position_within(a.end, b)
17031725}
1704172617271727+/// Returns true if the first range is completely within the second range.
17281728+/// The range cannot have any extreme in common to be considered completely
17291729+/// within another.
17301730+///
17311731+/// `completely_within(a, b)` is true this case:
17321732+///
17331733+/// - ```txt
17341734+/// |------| b
17351735+/// |---| a
17361736+/// ```
17371737+///
17381738+/// And `completely_within(a, b)` is false in all these cases:
17391739+///
17401740+/// - ```txt
17411741+/// |------| b
17421742+/// |---| a
17431743+/// ```
17441744+/// - ```txt
17451745+/// |------| b
17461746+/// |---| a
17471747+/// ```
17481748+/// - ```txt
17491749+/// |------| b
17501750+/// |------| a
17511751+/// ```
17521752+///
17531753+pub fn completely_within(a: Range, b: Range) -> bool {
17541754+ a.start > b.start && a.start < b.end && a.end > b.start && a.end < b.end
17551755+}
17561756+17051757// Returns true if a position is within a range.
17061706-fn position_within(position: Position, range: Range) -> bool {
17581758+pub fn position_within(position: Position, range: Range) -> bool {
17071759 position >= range.start && position <= range.end
17081760}
17091761