Skip to content

Commit 86b5f4e

Browse files
authored
refactor: keep if-let native instead of desugaring (#801)
1 parent 1dcb5fa commit 86b5f4e

69 files changed

Lines changed: 494 additions & 349 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

crates/emit/src/control_flow/propagation.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -380,7 +380,10 @@ impl Planner<'_> {
380380
return Some(statements);
381381
}
382382

383-
if matches!(expression, Expression::If { .. } | Expression::Match { .. }) {
383+
if matches!(
384+
expression,
385+
Expression::If { .. } | Expression::IfLet { .. } | Expression::Match { .. }
386+
) {
384387
let block = self.lower_branching_to_block(expression, &PlacePlan::Return, fx);
385388
statements.extend(block.statements);
386389
return Some(statements);

crates/emit/src/expressions/values.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,8 @@ impl Planner<'_> {
190190
| Expression::Function {
191191
params, body, ty, ..
192192
} => ValuePlan::Operand(self.emit_lambda(params, body, ty, ctx, fx)),
193-
Expression::Match { ty, .. }
193+
Expression::IfLet { ty, .. }
194+
| Expression::Match { ty, .. }
194195
| Expression::Select { ty, .. }
195196
| Expression::Block { ty, .. } => self.lower_to_operand_temp(expression, ty, fx),
196197
Expression::Return {
@@ -208,9 +209,6 @@ impl Planner<'_> {
208209
vec![self.lower_assert_statement(expression, fx)],
209210
"struct{}{}".to_string(),
210211
),
211-
Expression::IfLet { .. } => {
212-
unreachable!("IfLet should be desugared to Match before emit")
213-
}
214212
_ => unreachable!(
215213
"unexpected leaf expression in plan_operand: {:?}",
216214
expression

crates/emit/src/plan/lower.rs

Lines changed: 71 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,33 @@ use crate::plan::bodies::{
1313
use crate::plan::placement::{requires_temp_var, try_elide_tail_let};
1414
use crate::plan::values::{ValuePlan, value_plan_from_statements};
1515
use crate::utils::wrap_if_struct_literal;
16-
use syntax::ast::{BinaryOperator, Expression, Literal};
16+
use syntax::ast::{BinaryOperator, Expression, Literal, MatchArm, Pattern, TypedPattern};
1717
use syntax::types::Type;
1818

19+
fn if_let_match_arms(
20+
pattern: &Pattern,
21+
typed_pattern: &Option<TypedPattern>,
22+
consequence: &Expression,
23+
alternative: &Expression,
24+
) -> Vec<MatchArm> {
25+
vec![
26+
MatchArm {
27+
pattern: pattern.clone(),
28+
guard: None,
29+
typed_pattern: typed_pattern.clone(),
30+
expression: Box::new(consequence.clone()),
31+
},
32+
MatchArm {
33+
pattern: Pattern::WildCard {
34+
span: alternative.get_span(),
35+
},
36+
guard: None,
37+
typed_pattern: None,
38+
expression: Box::new(alternative.clone()),
39+
},
40+
]
41+
}
42+
1943
impl Planner<'_> {
2044
/// Allocate a fresh operand-temp result var and its `var V T` declaration
2145
/// as a typed setup leaf. The control-flow that assigns it follows as a
@@ -67,7 +91,7 @@ impl Planner<'_> {
6791
value_plan_from_statements(vec![declaration, LoweredStatement::If(plan)], result_var)
6892
}
6993

70-
/// Lower a value-position `match`/`select` to a fresh operand-temp
94+
/// Lower a value-position `if let`/`match`/`select` to a fresh operand-temp
7195
/// variable. Only valid for non-never result types; never-typed branches
7296
/// route through `lower_to_operand_temp` as a diverging statement.
7397
pub(crate) fn plan_branching_as_operand_temp(
@@ -309,6 +333,22 @@ impl Planner<'_> {
309333
self.build_assignment_plan(target, value, compound_operator.as_ref(), fx);
310334
directed(directive, LoweredStatement::Assign(plan))
311335
}
336+
Expression::IfLet {
337+
pattern,
338+
scrutinee,
339+
consequence,
340+
alternative,
341+
typed_pattern,
342+
..
343+
} => {
344+
let directive = self.maybe_line_directive(&expression.get_span());
345+
let arms = if_let_match_arms(pattern, typed_pattern, consequence, alternative);
346+
let body = self.lower_match_to_block(scrutinee, &arms, &PlacePlan::Statement, fx);
347+
directed(
348+
directive,
349+
LoweredStatement::Match(MatchStatementPlan { body }),
350+
)
351+
}
312352
Expression::Match { subject, arms, .. } => {
313353
let directive = self.maybe_line_directive(&expression.get_span());
314354
let body = self.lower_match_to_block(subject, arms, &PlacePlan::Statement, fx);
@@ -701,7 +741,7 @@ impl Planner<'_> {
701741
LoweredBlock { statements }
702742
}
703743

704-
/// Lower a branching expression (`if`, `match`, `select`) into a
744+
/// Lower a branching expression (`if`, `if let`, `match`, `select`) into a
705745
/// `LoweredBlock` targeting `place`. Centralises the dispatch shared by old
706746
/// emit paths that need to render a branching tail/assignment.
707747
pub(crate) fn lower_branching_to_block(
@@ -722,13 +762,24 @@ impl Planner<'_> {
722762
statements: vec![LoweredStatement::If(plan)],
723763
}
724764
}
765+
Expression::IfLet {
766+
pattern,
767+
scrutinee,
768+
consequence,
769+
alternative,
770+
typed_pattern,
771+
..
772+
} => {
773+
let arms = if_let_match_arms(pattern, typed_pattern, consequence, alternative);
774+
self.lower_match_to_block(scrutinee, &arms, place, fx)
775+
}
725776
Expression::Match { subject, arms, .. } => {
726777
self.lower_match_to_block(subject, arms, place, fx)
727778
}
728779
Expression::Select { arms, .. } => LoweredBlock {
729780
statements: vec![LoweredStatement::Select(self.lower_select(arms, place, fx))],
730781
},
731-
_ => unreachable!("lower_branching_to_block: expected if/match/select"),
782+
_ => unreachable!("lower_branching_to_block: expected if/if-let/match/select"),
732783
}
733784
}
734785

@@ -795,7 +846,7 @@ impl Planner<'_> {
795846
/// Lower a single tail expression in return position to its return
796847
/// statements. Shared by branch-arm return lowering and function-body
797848
/// lowering; only leaf values and lowered-ABI returns become `RawGo`,
798-
/// `if`/`match`/`select` tails recurse structurally with a `Return` place.
849+
/// `if`/`if let`/`match`/`select` tails recurse structurally with a `Return` place.
799850
pub(crate) fn lower_return_tail(
800851
&mut self,
801852
last: &Expression,
@@ -832,6 +883,21 @@ impl Planner<'_> {
832883
self.lower_if(condition, consequence, alternative, &PlacePlan::Return, fx);
833884
statements.push(directed(directive, LoweredStatement::If(plan)));
834885
}
886+
Expression::IfLet {
887+
pattern,
888+
scrutinee,
889+
consequence,
890+
alternative,
891+
typed_pattern,
892+
..
893+
} => {
894+
if !directive.is_empty() {
895+
statements.push(LoweredStatement::RawGo(directive));
896+
}
897+
let arms = if_let_match_arms(pattern, typed_pattern, consequence, alternative);
898+
let block = self.lower_match_to_block(scrutinee, &arms, &PlacePlan::Return, fx);
899+
statements.extend(block.statements);
900+
}
835901
Expression::Match { subject, arms, .. } => {
836902
if !directive.is_empty() {
837903
statements.push(LoweredStatement::RawGo(directive));

crates/emit/src/plan/placement.rs

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -108,12 +108,12 @@ pub(crate) fn try_elide_tail_let(items: &[Expression]) -> Option<(&Expression, &
108108
if identifier != tail_name {
109109
return None;
110110
}
111-
// Only `If` and `Match` can be re-emitted at the surrounding place via
112-
// branch lowering (`lower_branching_to_block`); other shapes still stage
111+
// Only `If`, `IfLet`, and `Match` can be re-emitted at the surrounding place
112+
// via branch lowering (`lower_branching_to_block`); other shapes still stage
113113
// through temps so eliding the let would not save anything.
114114
if !matches!(
115115
value.as_ref(),
116-
Expression::If { .. } | Expression::Match { .. }
116+
Expression::If { .. } | Expression::IfLet { .. } | Expression::Match { .. }
117117
) {
118118
return None;
119119
}
@@ -152,6 +152,16 @@ pub(crate) fn expression_contains_binding(expression: &Expression, name: &str) -
152152
}
153153
}
154154
match expression {
155+
Expression::IfLet {
156+
pattern,
157+
consequence,
158+
alternative,
159+
..
160+
} => {
161+
pattern_contains_name(pattern, name)
162+
|| expression_contains_binding(consequence, name)
163+
|| expression_contains_binding(alternative, name)
164+
}
155165
Expression::Match { arms, .. } => arms
156166
.iter()
157167
.any(|arm| pattern_contains_name(&arm.pattern, name)),
@@ -475,7 +485,10 @@ impl Planner<'_> {
475485
}
476486
if matches!(
477487
last,
478-
Expression::If { .. } | Expression::Match { .. } | Expression::Select { .. }
488+
Expression::If { .. }
489+
| Expression::IfLet { .. }
490+
| Expression::Match { .. }
491+
| Expression::Select { .. }
479492
) {
480493
let place = PlacePlan::Assign {
481494
local: var,

crates/emit/src/plan/values.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,11 @@ impl Planner<'_> {
143143
}
144144
Expression::If { ty, .. } => self.plan_if_as_operand_temp(expression, ty, fx),
145145
Expression::Loop { ty, .. } => self.plan_loop_as_operand_temp(expression, ty, fx),
146-
Expression::Match { ty, .. } | Expression::Select { ty, .. } if !ty.is_never() => {
146+
Expression::IfLet { ty, .. }
147+
| Expression::Match { ty, .. }
148+
| Expression::Select { ty, .. }
149+
if !ty.is_never() =>
150+
{
147151
self.plan_branching_as_operand_temp(expression, ty, fx)
148152
}
149153
Expression::Call { ty, .. } => match self.classify_call(expression) {

crates/emit/src/statements/let_binding.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,10 @@ fn resolve_let_temp_declaration_ty(
138138
};
139139
let is_branching = matches!(
140140
value,
141-
Expression::If { .. } | Expression::Match { .. } | Expression::Select { .. }
141+
Expression::If { .. }
142+
| Expression::IfLet { .. }
143+
| Expression::Match { .. }
144+
| Expression::Select { .. }
142145
);
143146
if is_branching && let Type::Tuple(slots) = &base {
144147
Type::Tuple(planner.resolve_tuple_slot_types(slots.clone(), false))

crates/passes/src/passes/checks/pattern_analysis/mod.rs

Lines changed: 25 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,7 @@ use std::cell::RefCell;
2323
use diagnostics::{IssueKind, LocalSink, PatternIssue};
2424
use semantics::context::AnalysisContext;
2525
use semantics::store::Store;
26-
use syntax::ast::{
27-
Expression, Literal, MatchOrigin, Pattern, SelectArmPattern, Span, TypedPattern,
28-
};
26+
use syntax::ast::{Expression, Literal, Pattern, SelectArmPattern, Span, TypedPattern};
2927
use syntax::types::Type;
3028

3129
use maranget::is_useful;
@@ -171,14 +169,24 @@ pub fn check(expression: &Expression, ctx: &PatternAnalysisContext, sink: &Local
171169
check(alternative, ctx, sink);
172170
}
173171

174-
Expression::IfLet { .. } => {
175-
unreachable!("IfLet should be desugared to Match before pattern analysis")
172+
Expression::IfLet {
173+
pattern,
174+
scrutinee,
175+
consequence,
176+
alternative,
177+
typed_pattern,
178+
else_span,
179+
..
180+
} => {
181+
check(scrutinee, ctx, sink);
182+
check_if_let(pattern, typed_pattern, alternative, *else_span, ctx);
183+
check(consequence, ctx, sink);
184+
check(alternative, ctx, sink);
176185
}
177186

178187
Expression::Match {
179188
subject,
180189
arms,
181-
origin,
182190
span,
183191
..
184192
} => {
@@ -212,9 +220,7 @@ pub fn check(expression: &Expression, ctx: &PatternAnalysisContext, sink: &Local
212220
return;
213221
}
214222

215-
if let MatchOrigin::IfLet { else_span } = origin {
216-
check_desugared_if_let(arms, *else_span, ctx);
217-
} else if !check_redundancy_with_guards(arms, &mut unions, &norm_ctx, sink) {
223+
if !check_redundancy_with_guards(arms, &mut unions, &norm_ctx, sink) {
218224
return;
219225
}
220226

@@ -462,41 +468,33 @@ fn check_redundancy_with_guards(
462468
true
463469
}
464470

465-
fn check_desugared_if_let(
466-
arms: &[syntax::ast::MatchArm],
471+
fn check_if_let(
472+
pattern: &Pattern,
473+
typed_pattern: &Option<TypedPattern>,
474+
alternative: &Expression,
467475
else_span: Option<Span>,
468476
ctx: &PatternAnalysisContext,
469477
) {
470-
if arms.len() != 2 {
471-
return;
472-
}
473-
474-
let first_arm = &arms[0];
475-
let second_arm = &arms[1];
476-
477-
let Some(tp) = &first_arm.typed_pattern else {
478+
let Some(tp) = typed_pattern else {
478479
return;
479480
};
480481

481482
// Suppress lints for patterns that already have or-pattern binding errors.
482-
if ctx
483-
.or_pattern_error_spans
484-
.contains(&first_arm.pattern.get_span())
485-
{
483+
if ctx.or_pattern_error_spans.contains(&pattern.get_span()) {
486484
return;
487485
}
488486

489487
if is_pattern_irrefutable(tp, ctx.store) {
490-
ctx.add_issue(first_arm.pattern.get_span(), IssueKind::RedundantIfLet);
488+
ctx.add_issue(pattern.get_span(), IssueKind::RedundantIfLet);
491489

492490
if let Some(else_span) = else_span
493-
&& !is_trivial_expression(&second_arm.expression)
491+
&& !is_trivial_expression(alternative)
494492
{
495493
ctx.add_issue(else_span, IssueKind::UnreachableIfLetElse);
496494
}
497495
} else if let Some(else_span) = else_span
498-
&& is_trivial_expression(&second_arm.expression)
499-
&& !second_arm.expression.is_conditional()
496+
&& is_trivial_expression(alternative)
497+
&& !alternative.is_conditional()
500498
{
501499
ctx.add_issue(else_span, IssueKind::RedundantIfLetElse);
502500
}

crates/passes/src/passes/fact_producers/unused_expressions.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,11 @@ fn descend_discarded(
159159
consequence,
160160
alternative,
161161
..
162+
}
163+
| Expression::IfLet {
164+
consequence,
165+
alternative,
166+
..
162167
} => {
163168
descend_discarded(consequence, mode, module_id, store, facts);
164169
descend_discarded(alternative, mode, module_id, store, facts);

0 commit comments

Comments
 (0)