Skip to content

Commit 5d0437b

Browse files
giacomocavalierilpil
authored andcommitted
add "Remove unreachable branches" code action
1 parent 1b675b0 commit 5d0437b

File tree

5 files changed

+189
-1
lines changed

5 files changed

+189
-1
lines changed

CHANGELOG.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,37 @@
162162

163163
### Language server
164164

165+
- The language server now offers a code action to remove all the unreachable
166+
branches in a case expression. For example:
167+
168+
```gleam
169+
pub fn main() {
170+
case find_user() {
171+
Ok(user) -> todo
172+
Ok(Admin) -> todo
173+
// ^^^^^^^^^ This branch is unreachable
174+
Ok(User) -> todo
175+
// ^^^^^^^^ This branch is unreachable
176+
Error(_) -> todo
177+
}
178+
}
179+
```
180+
181+
Hovering over one of the unreachable branches and triggering the code action
182+
would remove all the unreachable branches:
183+
184+
```gleam
185+
pub fn main() {
186+
case find_user() {
187+
Ok(user) -> todo
188+
189+
Error(_) -> todo
190+
}
191+
}
192+
```
193+
194+
([Giacomo Cavalieri](https://github.com/giacomocavalieri))
195+
165196
- The "pattern match on variable" can now be triggered on lists. For example:
166197

167198
```gleam

compiler-core/src/language_server/code_action.rs

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8106,3 +8106,93 @@ fn single_expression(expression: &TypedExpr) -> Option<&TypedExpr> {
81068106
expression => Some(expression),
81078107
}
81088108
}
8109+
8110+
/// Code action to remove `opaque` from a private type.
8111+
///
8112+
pub struct RemoveUnreachableBranches<'a> {
8113+
module: &'a Module,
8114+
params: &'a CodeActionParams,
8115+
edits: TextEdits<'a>,
8116+
/// The source location of the patterns of all the unreachable branches in
8117+
/// the current module.
8118+
///
8119+
unreachable_branches: HashSet<SrcSpan>,
8120+
branches_to_delete: Vec<SrcSpan>,
8121+
}
8122+
8123+
impl<'a> RemoveUnreachableBranches<'a> {
8124+
pub fn new(
8125+
module: &'a Module,
8126+
line_numbers: &'a LineNumbers,
8127+
params: &'a CodeActionParams,
8128+
) -> Self {
8129+
let unreachable_branches = (module.ast.type_info.warnings.iter())
8130+
.filter_map(|warning| match warning {
8131+
type_::Warning::UnreachableCasePattern { location, .. } => Some(*location),
8132+
_ => None,
8133+
})
8134+
.collect();
8135+
8136+
Self {
8137+
unreachable_branches,
8138+
module,
8139+
params,
8140+
edits: TextEdits::new(line_numbers),
8141+
branches_to_delete: vec![],
8142+
}
8143+
}
8144+
8145+
pub fn code_actions(mut self) -> Vec<CodeAction> {
8146+
self.visit_typed_module(&self.module.ast);
8147+
if self.branches_to_delete.is_empty() {
8148+
return vec![];
8149+
}
8150+
8151+
for branch in self.branches_to_delete {
8152+
self.edits.delete(branch);
8153+
}
8154+
8155+
let mut action = Vec::with_capacity(1);
8156+
CodeActionBuilder::new("Remove unreachable branches")
8157+
.kind(CodeActionKind::QUICKFIX)
8158+
.changes(self.params.text_document.uri.clone(), self.edits.edits)
8159+
.preferred(true)
8160+
.push_to(&mut action);
8161+
action
8162+
}
8163+
}
8164+
8165+
impl<'ast> ast::visit::Visit<'ast> for RemoveUnreachableBranches<'ast> {
8166+
fn visit_typed_expr_case(
8167+
&mut self,
8168+
location: &'ast SrcSpan,
8169+
type_: &'ast Arc<Type>,
8170+
subjects: &'ast [TypedExpr],
8171+
clauses: &'ast [ast::TypedClause],
8172+
compiled_case: &'ast CompiledCase,
8173+
) {
8174+
// We're showing the code action only if we're within one of the
8175+
// unreachable patterns. And the code action is going to remove all the
8176+
// unreachable patterns for this case.
8177+
let is_hovering_clause = clauses.iter().any(|clause| {
8178+
let pattern_range = self.edits.src_span_to_lsp_range(clause.pattern_location());
8179+
within(self.params.range, pattern_range)
8180+
});
8181+
if is_hovering_clause {
8182+
self.branches_to_delete = clauses
8183+
.iter()
8184+
.filter(|clause| {
8185+
self.unreachable_branches
8186+
.contains(&clause.pattern_location())
8187+
})
8188+
.map(|clause| clause.location())
8189+
.collect_vec();
8190+
return;
8191+
}
8192+
8193+
// If we're not hovering any of the branches clauses then we want to
8194+
// keep visiting the case expression as the unreachable branch might be
8195+
// in one of the nested cases.
8196+
ast::visit::visit_typed_expr_case(self, location, type_, subjects, clauses, compiled_case);
8197+
}
8198+
}

compiler-core/src/language_server/engine.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ use crate::{
1212
config::PackageConfig,
1313
io::{BeamCompiler, CommandExecutor, FileSystemReader, FileSystemWriter},
1414
language_server::{
15-
code_action::{CollapseNestedCase, RemoveBlock, RemovePrivateOpaque},
15+
code_action::{
16+
CollapseNestedCase, RemoveBlock, RemovePrivateOpaque, RemoveUnreachableBranches,
17+
},
1618
compiler::LspProjectCompiler,
1719
files::FileSystemProxy,
1820
progress::ProgressReporter,
@@ -404,6 +406,7 @@ where
404406
code_action_fix_names(&lines, &params, &this.error, &mut actions);
405407
code_action_import_module(module, &lines, &params, &this.error, &mut actions);
406408
code_action_add_missing_patterns(module, &lines, &params, &this.error, &mut actions);
409+
actions.extend(RemoveUnreachableBranches::new(module, &lines, &params).code_actions());
407410
actions.extend(CollapseNestedCase::new(module, &lines, &params).code_actions());
408411
code_action_inexhaustive_let_to_case(
409412
module,

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

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ const GENERATE_VARIANT: &str = "Generate variant";
133133
const REMOVE_BLOCK: &str = "Remove block";
134134
const REMOVE_OPAQUE_FROM_PRIVATE_TYPE: &str = "Remove opaque from private type";
135135
const COLLAPSE_NESTED_CASE: &str = "Collapse nested case";
136+
const REMOVE_UNREACHABLE_BRANCHES: &str = "Remove unreachable branches";
136137

137138
macro_rules! assert_code_action {
138139
($title:expr, $code:literal, $range:expr $(,)?) => {
@@ -9757,3 +9758,41 @@ pub fn main() {
97579758
find_position_of("subtract").to_selection()
97589759
);
97599760
}
9761+
9762+
#[test]
9763+
fn remove_unreachable_branches() {
9764+
assert_code_action!(
9765+
REMOVE_UNREACHABLE_BRANCHES,
9766+
"pub fn main(x) {
9767+
case x {
9768+
Ok(n) -> 1
9769+
Ok(_) -> 2
9770+
Error(_) -> todo
9771+
Ok(1) -> 3
9772+
}
9773+
}
9774+
",
9775+
find_position_of("Ok(1)").to_selection()
9776+
);
9777+
}
9778+
9779+
#[test]
9780+
fn remove_unreachable_branches_does_not_pop_up_if_all_branches_are_reachable() {
9781+
assert_no_code_actions!(
9782+
REMOVE_UNREACHABLE_BRANCHES,
9783+
"pub fn main(x) {
9784+
case x {
9785+
Ok(n) -> 1
9786+
Error(_) -> todo
9787+
}
9788+
9789+
case x {
9790+
Ok(n) -> todo
9791+
Ok(_) -> todo
9792+
_ -> todo
9793+
}
9794+
}
9795+
",
9796+
find_position_of("Ok(n)").to_selection()
9797+
);
9798+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
---
2+
source: compiler-core/src/language_server/tests/action.rs
3+
expression: "pub fn main(x) {\n case x {\n Ok(n) -> 1\n Ok(_) -> 2\n Error(_) -> todo\n Ok(1) -> 3\n }\n}\n"
4+
---
5+
----- BEFORE ACTION
6+
pub fn main(x) {
7+
case x {
8+
Ok(n) -> 1
9+
Ok(_) -> 2
10+
Error(_) -> todo
11+
Ok(1) -> 3
12+
13+
}
14+
}
15+
16+
17+
----- AFTER ACTION
18+
pub fn main(x) {
19+
case x {
20+
Ok(n) -> 1
21+
22+
Error(_) -> todo
23+
24+
}
25+
}

0 commit comments

Comments
 (0)