Fork of daniellemaywood.uk/gleam — Wasm codegen work
2

Configure Feed

Select the types of activity you want to include in your feed.

add "Remove unreachable branches" code action

author
Giacomo Cavalieri
committer
Louis Pilfold
date (Sep 8, 2025, 12:30 PM +0100) commit 5d0437bb parent 1b675b02 change-id vtxxmlyy
+189 -1
+31
CHANGELOG.md
··· 162 162 163 163 ### Language server 164 164 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 + 165 196 - The "pattern match on variable" can now be triggered on lists. For example: 166 197 167 198 ```gleam
+90
compiler-core/src/language_server/code_action.rs
··· 8106 8106 expression => Some(expression), 8107 8107 } 8108 8108 } 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 + }
+4 -1
compiler-core/src/language_server/engine.rs
··· 12 12 config::PackageConfig, 13 13 io::{BeamCompiler, CommandExecutor, FileSystemReader, FileSystemWriter}, 14 14 language_server::{ 15 - code_action::{CollapseNestedCase, RemoveBlock, RemovePrivateOpaque}, 15 + code_action::{ 16 + CollapseNestedCase, RemoveBlock, RemovePrivateOpaque, RemoveUnreachableBranches, 17 + }, 16 18 compiler::LspProjectCompiler, 17 19 files::FileSystemProxy, 18 20 progress::ProgressReporter, ··· 404 406 code_action_fix_names(&lines, &params, &this.error, &mut actions); 405 407 code_action_import_module(module, &lines, &params, &this.error, &mut actions); 406 408 code_action_add_missing_patterns(module, &lines, &params, &this.error, &mut actions); 409 + actions.extend(RemoveUnreachableBranches::new(module, &lines, &params).code_actions()); 407 410 actions.extend(CollapseNestedCase::new(module, &lines, &params).code_actions()); 408 411 code_action_inexhaustive_let_to_case( 409 412 module,
+39
compiler-core/src/language_server/tests/action.rs
··· 133 133 const REMOVE_BLOCK: &str = "Remove block"; 134 134 const REMOVE_OPAQUE_FROM_PRIVATE_TYPE: &str = "Remove opaque from private type"; 135 135 const COLLAPSE_NESTED_CASE: &str = "Collapse nested case"; 136 + const REMOVE_UNREACHABLE_BRANCHES: &str = "Remove unreachable branches"; 136 137 137 138 macro_rules! assert_code_action { 138 139 ($title:expr, $code:literal, $range:expr $(,)?) => { ··· 9757 9758 find_position_of("subtract").to_selection() 9758 9759 ); 9759 9760 } 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 + }
+25
compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__action__remove_unreachable_branches.snap
··· 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 + }