Skip to content
Draft
75 changes: 74 additions & 1 deletion compiler-core/src/language_server/code_action.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ use crate::{
strings::to_snake_case,
type_::{
self, FieldMap, ModuleValueConstructor, Type, TypeVar, TypedCallArg, ValueConstructor,
error::{ModuleSuggestion, VariableDeclaration, VariableOrigin},
error::{ModuleSuggestion, VariableDeclaration, VariableOrigin, VariableSyntax},
printer::Printer,
},
};
Expand Down Expand Up @@ -8196,3 +8196,76 @@ impl<'ast> ast::visit::Visit<'ast> for RemoveUnreachableBranches<'ast> {
ast::visit::visit_typed_expr_case(self, location, type_, subjects, clauses, compiled_case);
}
}

/// Code action builder to discard unused variables.
pub struct DiscardUnusedVariables<'a> {
module: &'a Module,
params: &'a CodeActionParams,
edits: TextEdits<'a>,
}

/// Struct used to parse each UnusedVariable warning into code editions.
struct UnusedVariable<'a> {
location: &'a SrcSpan,
origin: &'a VariableOrigin,
}

impl<'a> DiscardUnusedVariables<'a> {
pub fn new(
module: &'a Module,
line_numbers: &'a LineNumbers,
params: &'a CodeActionParams,
) -> Self {
Self {
module,
params,
edits: TextEdits::new(line_numbers),
}
}

// Parse UnusedVariable warnings into code editions.
fn gen_edits(&mut self, unused_variable: &UnusedVariable<'a>) {
match unused_variable.origin.syntax {
VariableSyntax::Variable(_) => {
self.edits
.insert(unused_variable.location.start, ("_").to_string());
}
VariableSyntax::LabelShorthand(_) => {
self.edits
.insert(unused_variable.location.end, (" _").to_string());
}
VariableSyntax::AssignmentPattern => {
self.edits.delete(SrcSpan {
start: unused_variable.location.start - 4, // Remove 'as ' before variable name
end: unused_variable.location.end,
});
}
VariableSyntax::Generated => (),
};
}

pub fn code_actions(mut self) -> Vec<CodeAction> {
self.module
.ast
.type_info
.warnings
.iter()
// get all UnusedVariable warnings and parse them as UnusedVariable struct
.filter_map(|warning| match warning {
type_::Warning::UnusedVariable { location, origin } => {
Some(UnusedVariable { location, origin })
}
_ => None,
})
//Insert to self.edits the edits needed to discard each unused variable
.for_each(|unused_variable| self.gen_edits(&unused_variable));

let mut action = Vec::with_capacity(1);
CodeActionBuilder::new("Discard unused variables")
.kind(CodeActionKind::QUICKFIX)
.changes(self.params.text_document.uri.clone(), self.edits.edits)
.preferred(true)
.push_to(&mut action);
action
}
}
5 changes: 3 additions & 2 deletions compiler-core/src/language_server/engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@ use super::{
DownloadDependencies, MakeLocker,
code_action::{
AddAnnotations, CodeActionBuilder, ConvertFromUse, ConvertToFunctionCall, ConvertToPipe,
ConvertToUse, ExpandFunctionCapture, ExtractConstant, ExtractVariable,
FillInMissingLabelledArgs, FillUnusedFields, FixBinaryOperation,
ConvertToUse, DiscardUnusedVariables, ExpandFunctionCapture, ExtractConstant,
ExtractVariable, FillInMissingLabelledArgs, FillUnusedFields, FixBinaryOperation,
FixTruncatedBitArraySegment, GenerateDynamicDecoder, GenerateFunction, GenerateJsonEncoder,
GenerateVariant, InlineVariable, InterpolateString, LetAssertToCase, PatternMatchOnValue,
RedundantTupleInCaseSubject, RemoveEchos, RemoveUnusedImports, UseLabelShorthandSyntax,
Expand Down Expand Up @@ -447,6 +447,7 @@ where
actions.extend(WrapInBlock::new(module, &lines, &params).code_actions());
actions.extend(RemoveBlock::new(module, &lines, &params).code_actions());
actions.extend(RemovePrivateOpaque::new(module, &lines, &params).code_actions());
actions.extend(DiscardUnusedVariables::new(module, &lines, &params).code_actions());
GenerateDynamicDecoder::new(module, &lines, &params, &mut actions).code_actions();
GenerateJsonEncoder::new(
module,
Expand Down
58 changes: 58 additions & 0 deletions compiler-core/src/language_server/tests/action.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ const REMOVE_BLOCK: &str = "Remove block";
const REMOVE_OPAQUE_FROM_PRIVATE_TYPE: &str = "Remove opaque from private type";
const COLLAPSE_NESTED_CASE: &str = "Collapse nested case";
const REMOVE_UNREACHABLE_BRANCHES: &str = "Remove unreachable branches";
const DISCARD_UNUSED_VARIABLE: &str = "Discard unused variables";

macro_rules! assert_code_action {
($title:expr, $code:literal, $range:expr $(,)?) => {
Expand Down Expand Up @@ -9797,6 +9798,63 @@ fn remove_unreachable_branches_does_not_pop_up_if_all_branches_are_reachable() {
);
}

#[test]
fn discard_unused_variable() {
assert_code_action!(
DISCARD_UNUSED_VARIABLE,
"pub fn main() -> Nil{
let x = 3
Nil
}",
find_position_of("x").to_selection()
);
}

#[test]
fn discard_unused_variable_shorthand() {
assert_code_action!(
DISCARD_UNUSED_VARIABLE,
"pub type MyType {
Variant1(a: Int, b: Int)
Variant2(c: Int, d: Int)
}

fn check_my_type(my_value: MyType) -> Int {
case my_value {
Variant1(b:, a:) -> a
Variant2(d:, c:) -> c
}
}

pub fn main() -> Nil {
let instance = Variant1(1, 2)

let _ = check_my_type(instance)

Nil
}",
find_position_of("b:,").to_selection()
);
}

#[test]
fn discard_unused_variable_pattern() {
assert_code_action!(
DISCARD_UNUSED_VARIABLE,
"fn inner(all: List(Int)) -> Nil {
case all {
[_, ..] as inner -> Nil
[] -> Nil
}
}

pub fn main() -> Nil {
inner([1, 2, 3])
}",
find_position_of("as inner").to_selection()
);
}

#[test]
fn add_type_annotations_public_alias_to_internal_type_aliased_module() {
let src = "
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
source: compiler-core/src/language_server/tests/action.rs
expression: "pub fn main() -> Nil{\n let x = 3\nNil\n}"
---
----- BEFORE ACTION
pub fn main() -> Nil{
let x = 3
Nil
}


----- AFTER ACTION
pub fn main() -> Nil{
let _x = 3
Nil
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
---
source: compiler-core/src/language_server/tests/action.rs
expression: "fn inner(all: List(Int)) -> Nil {\n case all {\n [_, ..] as inner -> Nil\n [] -> Nil\n }\n}\n\npub fn main() -> Nil {\n inner([1, 2, 3])\n}"
---
----- BEFORE ACTION
fn inner(all: List(Int)) -> Nil {
case all {
[_, ..] as inner -> Nil
[] -> Nil
}
}

pub fn main() -> Nil {
inner([1, 2, 3])
}


----- AFTER ACTION
fn inner(all: List(Int)) -> Nil {
case all {
[_, ..] -> Nil
[] -> Nil
}
}

pub fn main() -> Nil {
inner([1, 2, 3])
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
---
source: compiler-core/src/language_server/tests/action.rs
expression: "pub type MyType {\n Variant1(a: Int, b: Int)\n Variant2(c: Int, d: Int)\n}\n\nfn check_my_type(my_value: MyType) -> Int {\n case my_value {\n Variant1(b:, a:) -> a\n Variant2(d:, c:) -> c\n }\n}\n\npub fn main() -> Nil {\n let instance = Variant1(1, 2)\n\n let _ = check_my_type(instance)\n\n Nil\n}"
---
----- BEFORE ACTION
pub type MyType {
Variant1(a: Int, b: Int)
Variant2(c: Int, d: Int)
}

fn check_my_type(my_value: MyType) -> Int {
case my_value {
Variant1(b:, a:) -> a
Variant2(d:, c:) -> c
}
}

pub fn main() -> Nil {
let instance = Variant1(1, 2)

let _ = check_my_type(instance)

Nil
}


----- AFTER ACTION
pub type MyType {
Variant1(a: Int, b: Int)
Variant2(c: Int, d: Int)
}

fn check_my_type(my_value: MyType) -> Int {
case my_value {
Variant1(b: _, a:) -> a
Variant2(d: _, c:) -> c
}
}

pub fn main() -> Nil {
let instance = Variant1(1, 2)

let _ = check_my_type(instance)

Nil
}