Skip to content

Commit 9689d4f

Browse files
giacomocavalierilpil
authored andcommitted
properly deal with label shorthand
1 parent 4625f3f commit 9689d4f

6 files changed

+241
-38
lines changed

compiler-core/src/ast.rs

Lines changed: 65 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1530,7 +1530,18 @@ where
15301530
{
15311531
#[must_use]
15321532
pub fn uses_label_shorthand(&self) -> bool {
1533-
self.label.is_some() && self.location == self.value.location()
1533+
self.label_shorthand_name().is_some()
1534+
}
1535+
1536+
/// If the call arg is defined using a label shorthand, this will return the
1537+
/// label name.
1538+
///
1539+
fn label_shorthand_name(&self) -> Option<EcoString> {
1540+
if !self.is_implicit() && self.location == self.value.location() {
1541+
self.label.clone()
1542+
} else {
1543+
None
1544+
}
15341545
}
15351546
}
15361547

@@ -2533,6 +2544,32 @@ impl<A> Pattern<A> {
25332544
}
25342545
}
25352546

2547+
/// A variable bound inside a pattern.
2548+
#[derive(Debug, Clone)]
2549+
pub enum BoundVariable {
2550+
/// A record's labelled field introduced with the shorthand syntax.
2551+
ShorthandLabel { name: EcoString, location: SrcSpan },
2552+
/// Any other variable.
2553+
Regular { name: EcoString, location: SrcSpan },
2554+
}
2555+
2556+
impl BoundVariable {
2557+
pub fn name(&self) -> EcoString {
2558+
match self {
2559+
BoundVariable::ShorthandLabel { name, .. } | BoundVariable::Regular { name, .. } => {
2560+
name.clone()
2561+
}
2562+
}
2563+
}
2564+
2565+
pub fn location(&self) -> SrcSpan {
2566+
match self {
2567+
BoundVariable::ShorthandLabel { location, .. }
2568+
| BoundVariable::Regular { location, .. } => *location,
2569+
}
2570+
}
2571+
}
2572+
25362573
impl TypedPattern {
25372574
pub fn definition_location(&self) -> Option<DefinitionLocation> {
25382575
match self {
@@ -2707,28 +2744,34 @@ impl TypedPattern {
27072744
}
27082745
}
27092746

2710-
pub fn bound_variables(&self) -> Vec<(EcoString, SrcSpan)> {
2747+
pub fn bound_variables(&self) -> Vec<BoundVariable> {
27112748
let mut variables = Vec::new();
27122749
self.collect_bound_variables(&mut variables);
27132750
variables
27142751
}
27152752

2716-
fn collect_bound_variables(&self, variables: &mut Vec<(EcoString, SrcSpan)>) {
2753+
fn collect_bound_variables(&self, variables: &mut Vec<BoundVariable>) {
27172754
match self {
27182755
Pattern::Int { .. }
27192756
| Pattern::Float { .. }
27202757
| Pattern::String { .. }
27212758
| Pattern::Discard { .. }
27222759
| Pattern::Invalid { .. } => {}
27232760

2724-
Pattern::Variable { name, location, .. } => variables.push((name.clone(), *location)),
2761+
Pattern::Variable { name, location, .. } => variables.push(BoundVariable::Regular {
2762+
name: name.clone(),
2763+
location: *location,
2764+
}),
27252765
Pattern::BitArraySize { .. } => {}
27262766
Pattern::Assign {
27272767
name,
27282768
pattern,
27292769
location,
27302770
} => {
2731-
variables.push((name.clone(), *location));
2771+
variables.push(BoundVariable::Regular {
2772+
name: name.clone(),
2773+
location: *location,
2774+
});
27322775
pattern.collect_bound_variables(variables);
27332776
}
27342777
Pattern::List { elements, tail, .. } => {
@@ -2741,7 +2784,14 @@ impl TypedPattern {
27412784
}
27422785
Pattern::Constructor { arguments, .. } => {
27432786
for argument in arguments {
2744-
argument.value.collect_bound_variables(variables);
2787+
if let Some(name) = argument.label_shorthand_name() {
2788+
variables.push(BoundVariable::ShorthandLabel {
2789+
name,
2790+
location: argument.location,
2791+
})
2792+
} else {
2793+
argument.value.collect_bound_variables(variables);
2794+
}
27452795
}
27462796
}
27472797
Pattern::Tuple { elements, .. } => {
@@ -2760,11 +2810,17 @@ impl TypedPattern {
27602810
right_location,
27612811
..
27622812
} => {
2763-
if let Some(assignment) = left_side_assignment {
2764-
variables.push(assignment.clone());
2813+
if let Some((name, location)) = left_side_assignment {
2814+
variables.push(BoundVariable::Regular {
2815+
name: name.clone(),
2816+
location: *location,
2817+
});
27652818
}
27662819
match right_side_assignment {
2767-
AssignName::Variable(name) => variables.push((name.clone(), *right_location)),
2820+
AssignName::Variable(name) => variables.push(BoundVariable::Regular {
2821+
name: name.clone(),
2822+
location: *right_location,
2823+
}),
27682824
AssignName::Discard(_) => {}
27692825
}
27702826
}

compiler-core/src/javascript/decision.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -702,10 +702,10 @@ pub fn let_<'a>(
702702
// We must generate this after we generate the code for the decision tree
703703
// itself as we might be re-binding variables which are used in the checks
704704
// to determine whether the pattern matches or not.
705-
let beginning_assignments = pattern.bound_variables().into_iter().map(|(variable, _)| {
705+
let beginning_assignments = pattern.bound_variables().into_iter().map(|bound_variable| {
706706
docvec![
707707
"let ",
708-
expression_generator.local_var(&variable),
708+
expression_generator.local_var(&bound_variable.name()),
709709
";",
710710
line()
711711
]

compiler-core/src/language_server/code_action.rs

Lines changed: 42 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@ use std::{collections::HashSet, iter, sync::Arc};
33
use crate::{
44
Error, STDLIB_PACKAGE_NAME, analyse,
55
ast::{
6-
self, ArgNames, AssignName, AssignmentKind, BitArraySegmentTruncation, CallArg, CustomType,
7-
FunctionLiteralKind, ImplicitCallArgOrigin, Import, PIPE_PRECEDENCE, Pattern,
8-
PatternUnusedArguments, PipelineAssignmentKind, RecordConstructor, SrcSpan, TodoKind,
9-
TypedArg, TypedAssignment, TypedClauseGuard, TypedExpr, TypedModuleConstant, TypedPattern,
10-
TypedPipelineAssignment, TypedRecordConstructor, TypedStatement, TypedUse,
6+
self, ArgNames, AssignName, AssignmentKind, BitArraySegmentTruncation, BoundVariable,
7+
CallArg, CustomType, FunctionLiteralKind, ImplicitCallArgOrigin, Import, PIPE_PRECEDENCE,
8+
Pattern, PatternUnusedArguments, PipelineAssignmentKind, RecordConstructor, SrcSpan,
9+
TodoKind, TypedArg, TypedAssignment, TypedClauseGuard, TypedExpr, TypedModuleConstant,
10+
TypedPattern, TypedPipelineAssignment, TypedRecordConstructor, TypedStatement, TypedUse,
1111
visit::Visit as _,
1212
},
1313
build::{Located, Module},
@@ -7687,27 +7687,22 @@ struct Collapsed<'a> {
76877687
///
76887688
outer_guard: &'a Option<TypedClauseGuard>,
76897689

7690-
/// In this example it's `username`.
7691-
///
7692-
matched_variable_name: EcoString,
7693-
7694-
/// The span covering the definition of the pattern variable being matched
7695-
/// on:
7690+
/// The pattern variable being matched on:
76967691
///
76977692
/// ```gleam
76987693
/// case something {
76997694
/// User(username: _, NotAdmin) -> "Stranger!!"
77007695
/// User(username:, Admin) if wibble ->
77017696
/// ┬───────
7702-
/// ╰─ `matched_variable_span`
7697+
/// ╰─ `matched_variable`
77037698
/// case username {
77047699
/// "Joe" -> "Hello, Joe!"
77057700
/// _ -> "I don't know you, " <> username
77067701
/// }
77077702
/// }
77087703
/// ```
77097704
///
7710-
matched_variable_span: SrcSpan,
7705+
matched_variable: BoundVariable,
77117706

77127707
/// The span covering the entire pattern that is bringing the matched
77137708
/// variable in scope:
@@ -7756,8 +7751,7 @@ impl<'a> CollapseNestedCase<'a> {
77567751
let Some(Collapsed {
77577752
outer_clause_span,
77587753
outer_guard,
7759-
ref matched_variable_name,
7760-
matched_variable_span,
7754+
ref matched_variable,
77617755
matched_pattern_span,
77627756
inner_clauses,
77637757
}) = self.collapsed
@@ -7794,15 +7788,37 @@ impl<'a> CollapseNestedCase<'a> {
77947788
// can't simply write `Ok(2 | 3)` but we have to write `Ok(2) | Ok(3)`!
77957789

77967790
let pattern_text: String = self.code_at(matched_pattern_span).into();
7797-
7798-
let variable_start_in_pattern = matched_variable_span.start - matched_pattern_span.start;
7799-
let variable_length = matched_variable_span.end - matched_variable_span.start;
7800-
let variable_end_in_pattern = variable_start_in_pattern + variable_length;
7801-
let variable_range = variable_start_in_pattern as usize..variable_end_in_pattern as usize;
7791+
let matched_variable_span = matched_variable.location();
78027792

78037793
let pattern_with_variable = |new_content: String| {
78047794
let mut new_pattern = pattern_text.clone();
7805-
new_pattern.replace_range(variable_range.clone(), &new_content);
7795+
7796+
match matched_variable {
7797+
BoundVariable::Regular { .. } => {
7798+
// If the variable is a regular variable we'll have to replace
7799+
// it entirely with the new pattern taking its place.
7800+
let variable_start_in_pattern =
7801+
matched_variable_span.start - matched_pattern_span.start;
7802+
let variable_length = matched_variable_span.end - matched_variable_span.start;
7803+
let variable_end_in_pattern = variable_start_in_pattern + variable_length;
7804+
let replaced_range =
7805+
variable_start_in_pattern as usize..variable_end_in_pattern as usize;
7806+
7807+
new_pattern.replace_range(replaced_range, &new_content);
7808+
}
7809+
7810+
BoundVariable::ShorthandLabel { .. } => {
7811+
// But if it's introduced using the shorthand syntax we can't
7812+
// just replace it's location with the new pattern: we would be
7813+
// removing the label!!
7814+
// So we instead insert the pattern right after the label.
7815+
new_pattern.insert_str(
7816+
(matched_variable_span.end - matched_pattern_span.start) as usize,
7817+
&format!(" {new_content}"),
7818+
);
7819+
}
7820+
}
7821+
78067822
new_pattern
78077823
};
78087824

@@ -7813,7 +7829,7 @@ impl<'a> CollapseNestedCase<'a> {
78137829
// everything together with ` | `.
78147830

78157831
let references_to_matched_variable =
7816-
FindVariableReferences::new(matched_variable_span, matched_variable_name.clone())
7832+
FindVariableReferences::new(matched_variable_span, matched_variable.name())
78177833
.find(&clause.then);
78187834

78197835
let new_patterns = iter::once(&clause.pattern)
@@ -7827,7 +7843,7 @@ impl<'a> CollapseNestedCase<'a> {
78277843

78287844
let mut pattern_code = self.code_at(pattern_location).to_string();
78297845
if !references_to_matched_variable.is_empty() {
7830-
pattern_code = format!("{pattern_code} as {matched_variable_name}");
7846+
pattern_code = format!("{pattern_code} as {}", matched_variable.name());
78317847
};
78327848
pattern_with_variable(pattern_code)
78337849
})
@@ -7927,10 +7943,10 @@ impl<'a> CollapseNestedCase<'a> {
79277943

79287944
// That variable must be one the variables we brought into scope in this
79297945
// branch.
7930-
let (variable_name, variable_location) = pattern
7946+
let variable = pattern
79317947
.iter()
79327948
.flat_map(|pattern| pattern.bound_variables())
7933-
.find(|(variable, _)| variable == name)?;
7949+
.find(|variable| variable.name() == *name)?;
79347950

79357951
// There's one last condition to trigger the code action: we must
79367952
// actually be with the cursor over the pattern or the nested case
@@ -7956,8 +7972,7 @@ impl<'a> CollapseNestedCase<'a> {
79567972
Some(Collapsed {
79577973
outer_clause_span: *location,
79587974
outer_guard: guard,
7959-
matched_variable_name: variable_name,
7960-
matched_variable_span: variable_location,
7975+
matched_variable: variable,
79617976
matched_pattern_span: pattern_location,
79627977
inner_clauses: clauses,
79637978
})

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

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9290,6 +9290,58 @@ pub type Wibble {
92909290
);
92919291
}
92929292

9293+
#[test]
9294+
fn collapse_nested_case_does_not_remove_labels() {
9295+
assert_code_action!(
9296+
COLLAPSE_NESTED_CASE,
9297+
"pub fn main(x) {
9298+
case x {
9299+
Wibble(field2:, field: wibble) ->
9300+
case wibble {
9301+
1 -> 2
9302+
2 -> 4
9303+
_ -> -1
9304+
}
9305+
9306+
Wobble -> todo
9307+
}
9308+
}
9309+
9310+
pub type Wibble {
9311+
Wibble(field: Int, field2: String)
9312+
Wobble
9313+
}
9314+
",
9315+
find_position_of("field").to_selection()
9316+
);
9317+
}
9318+
9319+
#[test]
9320+
fn collapse_nested_case_does_not_remove_labels_with_shorthand_syntax() {
9321+
assert_code_action!(
9322+
COLLAPSE_NESTED_CASE,
9323+
"pub fn main(x) {
9324+
case x {
9325+
Wibble(field2:, field:) ->
9326+
case field {
9327+
1 -> 2
9328+
2 -> 4
9329+
_ -> -1
9330+
}
9331+
9332+
Wobble -> todo
9333+
}
9334+
}
9335+
9336+
pub type Wibble {
9337+
Wibble(field: Int, field2: String)
9338+
Wobble
9339+
}
9340+
",
9341+
find_position_of("field").to_selection()
9342+
);
9343+
}
9344+
92939345
#[test]
92949346
fn collapse_nested_case_works_with_alternative_patterns() {
92959347
assert_code_action!(
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
---
2+
source: compiler-core/src/language_server/tests/action.rs
3+
expression: "pub fn main(x) {\n case x {\n Wibble(field2:, field: wibble) ->\n case wibble {\n 1 -> 2\n 2 -> 4\n _ -> -1\n }\n\n Wobble -> todo\n }\n}\n\npub type Wibble {\n Wibble(field: Int, field2: String)\n Wobble\n}\n"
4+
---
5+
----- BEFORE ACTION
6+
pub fn main(x) {
7+
case x {
8+
Wibble(field2:, field: wibble) ->
9+
10+
case wibble {
11+
1 -> 2
12+
2 -> 4
13+
_ -> -1
14+
}
15+
16+
Wobble -> todo
17+
}
18+
}
19+
20+
pub type Wibble {
21+
Wibble(field: Int, field2: String)
22+
Wobble
23+
}
24+
25+
26+
----- AFTER ACTION
27+
pub fn main(x) {
28+
case x {
29+
Wibble(field2:, field: 1) -> 2
30+
Wibble(field2:, field: 2) -> 4
31+
Wibble(field2:, field: _) -> -1
32+
33+
Wobble -> todo
34+
}
35+
}
36+
37+
pub type Wibble {
38+
Wibble(field: Int, field2: String)
39+
Wobble
40+
}

0 commit comments

Comments
 (0)