Skip to content

Commit b383eef

Browse files
committed
Update with fixes to collapsing nested cases with lists
1 parent 0f0eb0c commit b383eef

7 files changed

+160
-12
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -341,3 +341,7 @@
341341
- Fixed a bug where the compiler would generate invalid Erlang and TypeScript
342342
code for unused opaque types referencing private types.
343343
([Surya Rose](https://github.com/GearsDatapacks))
344+
345+
- Fixed a bug where the collapse nested case would produce invalid code on a
346+
list tail pattern.
347+
([Matias Carlander](https://github.com/matiascr))

compiler-core/src/ast.rs

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3155,22 +3155,30 @@ impl TypedPattern {
31553155
pub enum BoundVariable {
31563156
/// A record's labelled field introduced with the shorthand syntax.
31573157
ShorthandLabel { name: EcoString, location: SrcSpan },
3158+
/// The variable as the tail of a list.
3159+
ListTail {
3160+
name: EcoString,
3161+
location: SrcSpan,
3162+
/// The location of the whole tail, from the `..` prefix until the end of the variable.
3163+
location_including_prefix: SrcSpan,
3164+
},
31583165
/// Any other variable.
31593166
Regular { name: EcoString, location: SrcSpan },
31603167
}
31613168

31623169
impl BoundVariable {
31633170
pub fn name(&self) -> EcoString {
31643171
match self {
3165-
BoundVariable::ShorthandLabel { name, .. } | BoundVariable::Regular { name, .. } => {
3166-
name.clone()
3167-
}
3172+
BoundVariable::ShorthandLabel { name, .. }
3173+
| BoundVariable::ListTail { name, .. }
3174+
| BoundVariable::Regular { name, .. } => name.clone(),
31683175
}
31693176
}
31703177

31713178
pub fn location(&self) -> SrcSpan {
31723179
match self {
31733180
BoundVariable::ShorthandLabel { location, .. }
3181+
| BoundVariable::ListTail { location, .. }
31743182
| BoundVariable::Regular { location, .. } => *location,
31753183
}
31763184
}
@@ -3389,9 +3397,15 @@ impl TypedPattern {
33893397
for element in elements {
33903398
element.collect_bound_variables(variables);
33913399
}
3392-
if let Some(tail) = tail {
3393-
tail.pattern.collect_bound_variables(variables);
3394-
}
3400+
if let Some(tail) = tail
3401+
&& let Pattern::Variable { name, location, .. } = tail.pattern.to_owned()
3402+
{
3403+
variables.push(BoundVariable::ListTail {
3404+
name,
3405+
location,
3406+
location_including_prefix: tail.location,
3407+
})
3408+
};
33953409
}
33963410
Pattern::Constructor { arguments, .. } => {
33973411
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
@@ -27,6 +27,7 @@ use ecow::{EcoString, eco_format};
2727
use im::HashMap;
2828
use itertools::Itertools;
2929
use lsp_types::{CodeAction, CodeActionKind, CodeActionParams, Position, Range, TextEdit, Url};
30+
use regex::Regex;
3031
use vec1::{Vec1, vec1};
3132

3233
use super::{
@@ -8122,19 +8123,39 @@ impl<'a> CollapseNestedCase<'a> {
81228123
let pattern_text: String = code_at(self.module, matched_pattern_span).into();
81238124
let matched_variable_span = matched_variable.location();
81248125

8125-
let pattern_with_variable = |new_content: String| {
8126+
let pattern_with_variable = |mut new_content: String| {
81268127
let mut new_pattern = pattern_text.clone();
81278128

81288129
match matched_variable {
8129-
BoundVariable::Regular { .. } => {
8130+
BoundVariable::Regular { .. } | BoundVariable::ListTail { .. } => {
81308131
// If the variable is a regular variable we'll have to replace
81318132
// it entirely with the new pattern taking its place.
8133+
8134+
let variable_span_to_use = if let BoundVariable::ListTail {
8135+
location_including_prefix: prefix_and_variable_span,
8136+
..
8137+
} = matched_variable
8138+
{
8139+
// We need to un-nest the list when replacing the tail
8140+
let surrounding_brackets = Regex::new(r"(^\[|\]$)").expect("a valid regex");
8141+
new_content = surrounding_brackets
8142+
.replace_all(new_content.trim(), "")
8143+
.to_string();
8144+
8145+
// We need to also remove the prefix when replacing the variable
8146+
// at the tail of the list
8147+
prefix_and_variable_span
8148+
} else {
8149+
&matched_variable_span
8150+
};
8151+
81328152
let variable_start_in_pattern =
8133-
matched_variable_span.start - matched_pattern_span.start;
8134-
let variable_length = matched_variable_span.end - matched_variable_span.start;
8153+
(variable_span_to_use.start - matched_pattern_span.start) as usize;
8154+
let variable_length =
8155+
(variable_span_to_use.end - variable_span_to_use.start) as usize;
8156+
81358157
let variable_end_in_pattern = variable_start_in_pattern + variable_length;
8136-
let replaced_range =
8137-
variable_start_in_pattern as usize..variable_end_in_pattern as usize;
8158+
let replaced_range = variable_start_in_pattern..variable_end_in_pattern;
81388159

81398160
new_pattern.replace_range(replaced_range, &new_content);
81408161
}

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

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9922,6 +9922,49 @@ fn collapse_nested_case_combines_inner_and_outer_guards_and_adds_parentheses_whe
99229922
);
99239923
}
99249924

9925+
#[test]
9926+
fn collapse_nested_case_combines_list_with_unformatted_tail() {
9927+
assert_code_action!(
9928+
COLLAPSE_NESTED_CASE,
9929+
r#"pub fn main(elems: List(String)) {
9930+
case elems {
9931+
[first, .. rest_of_list] ->
9932+
case rest_of_list {
9933+
[] -> 0
9934+
["first"] -> 1
9935+
[_, "second"] -> 2
9936+
[_, "second", "third", _] -> 4
9937+
_ -> -1
9938+
}
9939+
_ -> -1
9940+
}
9941+
}"#,
9942+
find_position_of("rest").to_selection()
9943+
);
9944+
}
9945+
9946+
#[test]
9947+
fn collapse_nested_case_combines_list_with_tail() {
9948+
assert_code_action!(
9949+
COLLAPSE_NESTED_CASE,
9950+
r#"pub fn main(elems: List(List(Int))) {
9951+
case elems {
9952+
[] -> 0
9953+
[_, ..tail] ->
9954+
case tail {
9955+
[] -> -1
9956+
[[1]] -> 1
9957+
[_, [3, 4]] -> 3
9958+
[_, _, [5, 6, 7], _] -> 5
9959+
_ -> -1
9960+
}
9961+
_ -> -1
9962+
}
9963+
}"#,
9964+
find_position_of("tail").to_selection()
9965+
);
9966+
}
9967+
99259968
// https://github.com/gleam-lang/gleam/issues/3786
99269969
#[test]
99279970
fn type_variables_from_other_functions_do_not_change_annotations() {

compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__action__collapse_nested_case_combines_inner_and_lists.snap

Whitespace-only changes.
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)