Skip to content

Commit 8c803d9

Browse files
committed
Update with fixes to collapsing nested cases with lists
1 parent 0f1190a commit 8c803d9

File tree

6 files changed

+166
-13
lines changed

6 files changed

+166
-13
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -432,3 +432,7 @@
432432
- Fixed a bug where the data generated for searching documentation was in the
433433
wrong format, preventing it from being used by Hexdocs search.
434434
([Surya Rose](https://github.com/GearsDatapacks))
435+
436+
- Fixed a bug where the collapse nested case would produce invalid code on a
437+
list tail pattern.
438+
([Matias Carlander](https://github.com/matiascr))

compiler-core/src/ast.rs

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3162,16 +3162,21 @@ pub struct BoundVariable {
31623162
pub enum BoundVariableName {
31633163
/// A record's labelled field introduced with the shorthand syntax.
31643164
ShorthandLabel { name: EcoString },
3165+
ListTail {
3166+
name: EcoString,
3167+
/// The location of the whole tail, from the `..` prefix until the end of the variable.
3168+
tail_prefix_location: SrcSpan,
3169+
},
31653170
/// Any other variable name.
31663171
Regular { name: EcoString },
31673172
}
31683173

31693174
impl BoundVariable {
31703175
pub fn name(&self) -> EcoString {
31713176
match &self.name {
3172-
BoundVariableName::ShorthandLabel { name } | BoundVariableName::Regular { name } => {
3173-
name.clone()
3174-
}
3177+
BoundVariableName::ShorthandLabel { name }
3178+
| BoundVariableName::ListTail { name, .. }
3179+
| BoundVariableName::Regular { name } => name.clone(),
31753180
}
31763181
}
31773182
}
@@ -3392,13 +3397,27 @@ impl TypedPattern {
33923397
});
33933398
pattern.collect_bound_variables(variables);
33943399
}
3395-
Pattern::List { elements, tail, .. } => {
3400+
Pattern::List {
3401+
elements,
3402+
tail,
3403+
type_,
3404+
..
3405+
} => {
33963406
for element in elements {
33973407
element.collect_bound_variables(variables);
33983408
}
3399-
if let Some(tail) = tail {
3400-
tail.pattern.collect_bound_variables(variables);
3401-
}
3409+
if let Some(tail) = tail
3410+
&& let Pattern::Variable { name, location, .. } = tail.pattern.to_owned()
3411+
{
3412+
variables.push(BoundVariable {
3413+
name: BoundVariableName::ListTail {
3414+
name,
3415+
tail_prefix_location: tail.location,
3416+
},
3417+
location,
3418+
type_: type_.clone(),
3419+
})
3420+
};
34023421
}
34033422
Pattern::Constructor { arguments, .. } => {
34043423
for argument in arguments {

compiler-core/src/language_server/code_action.rs

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ use ecow::{EcoString, eco_format};
2828
use im::HashMap;
2929
use itertools::Itertools;
3030
use lsp_types::{CodeAction, CodeActionKind, CodeActionParams, Position, Range, TextEdit, Url};
31+
use regex::Regex;
3132
use vec1::{Vec1, vec1};
3233

3334
use super::{
@@ -8264,22 +8265,42 @@ impl<'a> CollapseNestedCase<'a> {
82648265
let pattern_text: String = code_at(self.module, matched_pattern_span).into();
82658266
let matched_variable_span = matched_variable.location;
82668267

8267-
let pattern_with_variable = |new_content: String| {
8268+
let pattern_with_variable = |mut new_content: String| {
82688269
let mut new_pattern = pattern_text.clone();
82698270

82708271
match matched_variable {
82718272
BoundVariable {
8272-
name: BoundVariableName::Regular { .. },
8273+
name: BoundVariableName::Regular { .. } | BoundVariableName::ListTail { .. },
82738274
..
82748275
} => {
82758276
// If the variable is a regular variable we'll have to replace
82768277
// it entirely with the new pattern taking its place.
8278+
8279+
let variable_span_to_use = if let BoundVariableName::ListTail {
8280+
tail_prefix_location: prefix_and_variable_span,
8281+
..
8282+
} = matched_variable.name
8283+
{
8284+
// We need to un-nest the list when replacing the tail
8285+
let surrounding_brackets = Regex::new(r"(^\[|\]$)").expect("a valid regex");
8286+
new_content = surrounding_brackets
8287+
.replace_all(new_content.trim(), "")
8288+
.to_string();
8289+
8290+
// We need to also remove the prefix when replacing the variable
8291+
// at the tail of the list
8292+
prefix_and_variable_span
8293+
} else {
8294+
matched_variable_span
8295+
};
8296+
82778297
let variable_start_in_pattern =
8278-
matched_variable_span.start - matched_pattern_span.start;
8279-
let variable_length = matched_variable_span.end - matched_variable_span.start;
8298+
(variable_span_to_use.start - matched_pattern_span.start) as usize;
8299+
let variable_length =
8300+
(variable_span_to_use.end - variable_span_to_use.start) as usize;
8301+
82808302
let variable_end_in_pattern = variable_start_in_pattern + variable_length;
8281-
let replaced_range =
8282-
variable_start_in_pattern as usize..variable_end_in_pattern as usize;
8303+
let replaced_range = variable_start_in_pattern..variable_end_in_pattern;
82838304

82848305
new_pattern.replace_range(replaced_range, &new_content);
82858306
}

compiler-core/src/language_server/tests/action.rs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9947,6 +9947,49 @@ fn collapse_nested_case_combines_inner_and_outer_guards_and_adds_parentheses_whe
99479947
);
99489948
}
99499949

9950+
#[test]
9951+
fn collapse_nested_case_combines_list_with_unformatted_tail() {
9952+
assert_code_action!(
9953+
COLLAPSE_NESTED_CASE,
9954+
r#"pub fn main(elems: List(String)) {
9955+
case elems {
9956+
[first, .. rest_of_list] ->
9957+
case rest_of_list {
9958+
[] -> 0
9959+
["first"] -> 1
9960+
[_, "second"] -> 2
9961+
[_, "second", "third", _] -> 4
9962+
_ -> -1
9963+
}
9964+
_ -> -1
9965+
}
9966+
}"#,
9967+
find_position_of("rest").to_selection()
9968+
);
9969+
}
9970+
9971+
#[test]
9972+
fn collapse_nested_case_combines_list_with_tail() {
9973+
assert_code_action!(
9974+
COLLAPSE_NESTED_CASE,
9975+
r#"pub fn main(elems: List(List(Int))) {
9976+
case elems {
9977+
[] -> 0
9978+
[_, ..tail] ->
9979+
case tail {
9980+
[] -> -1
9981+
[[1]] -> 1
9982+
[_, [3, 4]] -> 3
9983+
[_, _, [5, 6, 7], _] -> 5
9984+
_ -> -1
9985+
}
9986+
_ -> -1
9987+
}
9988+
}"#,
9989+
find_position_of("tail").to_selection()
9990+
);
9991+
}
9992+
99509993
// https://github.com/gleam-lang/gleam/issues/3786
99519994
#[test]
99529995
fn type_variables_from_other_functions_do_not_change_annotations() {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
---
2+
source: compiler-core/src/language_server/tests/action.rs
3+
expression: "pub fn main(elems: List(List(Int))) {\n case elems {\n [] -> 0\n [_, ..tail] ->\n case tail {\n [] -> -1\n [[1]] -> 1\n [_, [3, 4]] -> 3\n [_, _, [5, 6, 7], _] -> 5\n _ -> -1\n }\n _ -> -1\n }\n}"
4+
---
5+
----- BEFORE ACTION
6+
pub fn main(elems: List(List(Int))) {
7+
case elems {
8+
[] -> 0
9+
[_, ..tail] ->
10+
11+
case tail {
12+
[] -> -1
13+
[[1]] -> 1
14+
[_, [3, 4]] -> 3
15+
[_, _, [5, 6, 7], _] -> 5
16+
_ -> -1
17+
}
18+
_ -> -1
19+
}
20+
}
21+
22+
23+
----- AFTER ACTION
24+
pub fn main(elems: List(List(Int))) {
25+
case elems {
26+
[] -> 0
27+
[_, ] -> -1
28+
[_, [1]] -> 1
29+
[_, _, [3, 4]] -> 3
30+
[_, _, _, [5, 6, 7], _] -> 5
31+
[_, _] -> -1
32+
_ -> -1
33+
}
34+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
---
2+
source: compiler-core/src/language_server/tests/action.rs
3+
expression: "pub fn main(elems: List(String)) {\n case elems {\n [first, .. rest_of_list] ->\n case rest_of_list {\n [] -> 0\n [\"first\"] -> 1\n [_, \"second\"] -> 2\n [_, \"second\", \"third\", _] -> 4\n _ -> -1\n }\n _ -> -1\n }\n}"
4+
---
5+
----- BEFORE ACTION
6+
pub fn main(elems: List(String)) {
7+
case elems {
8+
[first, .. rest_of_list] ->
9+
10+
case rest_of_list {
11+
[] -> 0
12+
["first"] -> 1
13+
[_, "second"] -> 2
14+
[_, "second", "third", _] -> 4
15+
_ -> -1
16+
}
17+
_ -> -1
18+
}
19+
}
20+
21+
22+
----- AFTER ACTION
23+
pub fn main(elems: List(String)) {
24+
case elems {
25+
[first, ] -> 0
26+
[first, "first"] -> 1
27+
[first, _, "second"] -> 2
28+
[first, _, "second", "third", _] -> 4
29+
[first, _] -> -1
30+
_ -> -1
31+
}
32+
}

0 commit comments

Comments
 (0)