Skip to content

Commit 8e73ea5

Browse files
committed
Desugar try blocks
1 parent 453ae2e commit 8e73ea5

File tree

11 files changed

+215
-84
lines changed

11 files changed

+215
-84
lines changed

crates/hir-def/src/body/lower.rs

Lines changed: 74 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ use rustc_hash::FxHashMap;
1919
use smallvec::SmallVec;
2020
use syntax::{
2121
ast::{
22-
self, ArrayExprKind, AstChildren, HasArgList, HasLoopBody, HasName, LiteralKind,
22+
self, ArrayExprKind, AstChildren, BlockExpr, HasArgList, HasLoopBody, HasName, LiteralKind,
2323
SlicePatComponents,
2424
},
2525
AstNode, AstPtr, SyntaxNodePtr,
@@ -100,6 +100,7 @@ pub(super) fn lower(
100100
_c: Count::new(),
101101
},
102102
expander,
103+
current_try_block: None,
103104
is_lowering_assignee_expr: false,
104105
is_lowering_generator: false,
105106
}
@@ -113,6 +114,7 @@ struct ExprCollector<'a> {
113114
body: Body,
114115
krate: CrateId,
115116
source_map: BodySourceMap,
117+
current_try_block: Option<LabelId>,
116118
is_lowering_assignee_expr: bool,
117119
is_lowering_generator: bool,
118120
}
@@ -222,6 +224,10 @@ impl ExprCollector<'_> {
222224
self.source_map.label_map.insert(src, id);
223225
id
224226
}
227+
// FIXME: desugared labels don't have ptr, that's wrong and should be fixed somehow.
228+
fn alloc_label_desugared(&mut self, label: Label) -> LabelId {
229+
self.body.labels.alloc(label)
230+
}
225231
fn make_label(&mut self, label: Label, src: LabelSource) -> LabelId {
226232
let id = self.body.labels.alloc(label);
227233
self.source_map.label_map_back.insert(id, src);
@@ -259,13 +265,7 @@ impl ExprCollector<'_> {
259265
self.alloc_expr(Expr::Let { pat, expr }, syntax_ptr)
260266
}
261267
ast::Expr::BlockExpr(e) => match e.modifier() {
262-
Some(ast::BlockModifier::Try(_)) => {
263-
self.collect_block_(e, |id, statements, tail| Expr::TryBlock {
264-
id,
265-
statements,
266-
tail,
267-
})
268-
}
268+
Some(ast::BlockModifier::Try(_)) => self.collect_try_block(e),
269269
Some(ast::BlockModifier::Unsafe(_)) => {
270270
self.collect_block_(e, |id, statements, tail| Expr::Unsafe {
271271
id,
@@ -606,6 +606,59 @@ impl ExprCollector<'_> {
606606
})
607607
}
608608

609+
/// Desugar `try { <stmts>; <expr> }` into `'<new_label>: { <stmts>; ::std::ops::Try::from_output(<expr>) }`,
610+
/// `try { <stmts>; }` into `'<new_label>: { <stmts>; ::std::ops::Try::from_output(()) }`
611+
/// and save the `<new_label>` to use it as a break target for desugaring of the `?` operator.
612+
fn collect_try_block(&mut self, e: BlockExpr) -> ExprId {
613+
let Some(try_from_output) = LangItem::TryTraitFromOutput.path(self.db, self.krate) else {
614+
return self.alloc_expr_desugared(Expr::Missing);
615+
};
616+
let prev_try_block = self.current_try_block.take();
617+
self.current_try_block =
618+
Some(self.alloc_label_desugared(Label { name: Name::generate_new_name() }));
619+
let expr_id = self.collect_block(e);
620+
let callee = self.alloc_expr_desugared(Expr::Path(try_from_output));
621+
let Expr::Block { label, tail, .. } = &mut self.body.exprs[expr_id] else {
622+
unreachable!("It is the output of collect block");
623+
};
624+
*label = self.current_try_block;
625+
let next_tail = match *tail {
626+
Some(tail) => self.alloc_expr_desugared(Expr::Call {
627+
callee,
628+
args: Box::new([tail]),
629+
is_assignee_expr: false,
630+
}),
631+
None => {
632+
let unit = self.alloc_expr_desugared(Expr::Tuple {
633+
exprs: Box::new([]),
634+
is_assignee_expr: false,
635+
});
636+
self.alloc_expr_desugared(Expr::Call {
637+
callee,
638+
args: Box::new([unit]),
639+
is_assignee_expr: false,
640+
})
641+
}
642+
};
643+
let Expr::Block { tail, .. } = &mut self.body.exprs[expr_id] else {
644+
unreachable!("It is the output of collect block");
645+
};
646+
*tail = Some(next_tail);
647+
self.current_try_block = prev_try_block;
648+
expr_id
649+
}
650+
651+
/// Desugar `ast::TryExpr` from: `<expr>?` into:
652+
/// ```ignore (pseudo-rust)
653+
/// match Try::branch(<expr>) {
654+
/// ControlFlow::Continue(val) => val,
655+
/// ControlFlow::Break(residual) =>
656+
/// // If there is an enclosing `try {...}`:
657+
/// break 'catch_target Try::from_residual(residual),
658+
/// // Otherwise:
659+
/// return Try::from_residual(residual),
660+
/// }
661+
/// ```
609662
fn collect_try_operator(&mut self, syntax_ptr: AstPtr<ast::Expr>, e: ast::TryExpr) -> ExprId {
610663
let (try_branch, cf_continue, cf_break, try_from_residual) = 'if_chain: {
611664
if let Some(try_branch) = LangItem::TryTraitBranch.path(self.db, self.krate) {
@@ -628,7 +681,9 @@ impl ExprCollector<'_> {
628681
Expr::Call { callee: try_branch, args: Box::new([operand]), is_assignee_expr: false },
629682
syntax_ptr.clone(),
630683
);
631-
let continue_binding = self.alloc_binding(name![v1], BindingAnnotation::Unannotated);
684+
let continue_name = Name::generate_new_name();
685+
let continue_binding =
686+
self.alloc_binding(continue_name.clone(), BindingAnnotation::Unannotated);
632687
let continue_bpat =
633688
self.alloc_pat_desugared(Pat::Bind { id: continue_binding, subpat: None });
634689
self.add_definition_to_binding(continue_binding, continue_bpat);
@@ -639,9 +694,10 @@ impl ExprCollector<'_> {
639694
ellipsis: None,
640695
}),
641696
guard: None,
642-
expr: self.alloc_expr(Expr::Path(Path::from(name![v1])), syntax_ptr.clone()),
697+
expr: self.alloc_expr(Expr::Path(Path::from(continue_name)), syntax_ptr.clone()),
643698
};
644-
let break_binding = self.alloc_binding(name![v1], BindingAnnotation::Unannotated);
699+
let break_name = Name::generate_new_name();
700+
let break_binding = self.alloc_binding(break_name.clone(), BindingAnnotation::Unannotated);
645701
let break_bpat = self.alloc_pat_desugared(Pat::Bind { id: break_binding, subpat: None });
646702
self.add_definition_to_binding(break_binding, break_bpat);
647703
let break_arm = MatchArm {
@@ -652,13 +708,18 @@ impl ExprCollector<'_> {
652708
}),
653709
guard: None,
654710
expr: {
655-
let x = self.alloc_expr(Expr::Path(Path::from(name![v1])), syntax_ptr.clone());
711+
let x = self.alloc_expr(Expr::Path(Path::from(break_name)), syntax_ptr.clone());
656712
let callee = self.alloc_expr(Expr::Path(try_from_residual), syntax_ptr.clone());
657713
let result = self.alloc_expr(
658714
Expr::Call { callee, args: Box::new([x]), is_assignee_expr: false },
659715
syntax_ptr.clone(),
660716
);
661-
self.alloc_expr(Expr::Return { expr: Some(result) }, syntax_ptr.clone())
717+
if let Some(label) = self.current_try_block {
718+
let label = Some(self.body.labels[label].name.clone());
719+
self.alloc_expr(Expr::Break { expr: Some(result), label }, syntax_ptr.clone())
720+
} else {
721+
self.alloc_expr(Expr::Return { expr: Some(result) }, syntax_ptr.clone())
722+
}
662723
},
663724
};
664725
let arms = Box::new([continue_arm, break_arm]);

crates/hir-def/src/body/pretty.rs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -420,9 +420,6 @@ impl<'a> Printer<'a> {
420420
Expr::Unsafe { id: _, statements, tail } => {
421421
self.print_block(Some("unsafe "), statements, tail);
422422
}
423-
Expr::TryBlock { id: _, statements, tail } => {
424-
self.print_block(Some("try "), statements, tail);
425-
}
426423
Expr::Async { id: _, statements, tail } => {
427424
self.print_block(Some("async "), statements, tail);
428425
}

crates/hir-def/src/body/scope.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -202,8 +202,7 @@ fn compute_expr_scopes(expr: ExprId, body: &Body, scopes: &mut ExprScopes, scope
202202
}
203203
Expr::Unsafe { id, statements, tail }
204204
| Expr::Async { id, statements, tail }
205-
| Expr::Const { id, statements, tail }
206-
| Expr::TryBlock { id, statements, tail } => {
205+
| Expr::Const { id, statements, tail } => {
207206
let mut scope = scopes.new_block_scope(*scope, *id, None);
208207
// Overwrite the old scope for the block expr, so that every block scope can be found
209208
// via the block itself (important for blocks that only contain items, no expressions).

crates/hir-def/src/expr.rs

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -122,11 +122,6 @@ pub enum Expr {
122122
tail: Option<ExprId>,
123123
label: Option<LabelId>,
124124
},
125-
TryBlock {
126-
id: BlockId,
127-
statements: Box<[Statement]>,
128-
tail: Option<ExprId>,
129-
},
130125
Async {
131126
id: BlockId,
132127
statements: Box<[Statement]>,
@@ -310,7 +305,6 @@ impl Expr {
310305
f(*expr);
311306
}
312307
Expr::Block { statements, tail, .. }
313-
| Expr::TryBlock { statements, tail, .. }
314308
| Expr::Unsafe { statements, tail, .. }
315309
| Expr::Async { statements, tail, .. }
316310
| Expr::Const { statements, tail, .. } => {

crates/hir-expand/src/name.rs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ impl Name {
7878
Self::new_text(lt.text().into())
7979
}
8080

81-
/// Shortcut to create inline plain text name
81+
/// Shortcut to create inline plain text name. Panics if `text.len() > 22`
8282
const fn new_inline(text: &str) -> Name {
8383
Name::new_text(SmolStr::new_inline(text))
8484
}
@@ -112,6 +112,18 @@ impl Name {
112112
Name::new_inline("[missing name]")
113113
}
114114

115+
/// Generates a new name which is only equal to itself, by incrementing a counter. Due
116+
/// its implementation, it should not be used in things that salsa considers, like
117+
/// type names or field names, and it should be only used in names of local variables
118+
/// and labels and similar things.
119+
pub fn generate_new_name() -> Name {
120+
use std::sync::atomic::{AtomicUsize, Ordering};
121+
static CNT: AtomicUsize = AtomicUsize::new(0);
122+
let c = CNT.fetch_add(1, Ordering::Relaxed);
123+
// FIXME: Currently a `__RA_generated_name` in user code will break our analysis
124+
Name::new_text(format!("__RA_geneated_name_{c}").into())
125+
}
126+
115127
/// Returns the tuple index this name represents if it is a tuple field.
116128
pub fn as_tuple_index(&self) -> Option<usize> {
117129
match self.0 {

crates/hir-ty/src/consteval/tests.rs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -522,6 +522,42 @@ fn loops() {
522522
"#,
523523
4,
524524
);
525+
check_number(
526+
r#"
527+
const GOAL: u8 = {
528+
let mut x = 0;
529+
loop {
530+
x = x + 1;
531+
if x == 5 {
532+
break x + 2;
533+
}
534+
}
535+
};
536+
"#,
537+
7,
538+
);
539+
check_number(
540+
r#"
541+
const GOAL: u8 = {
542+
'a: loop {
543+
let x = 'b: loop {
544+
let x = 'c: loop {
545+
let x = 'd: loop {
546+
let x = 'e: loop {
547+
break 'd 1;
548+
};
549+
break 2 + x;
550+
};
551+
break 3 + x;
552+
};
553+
break 'a 4 + x;
554+
};
555+
break 5 + x;
556+
}
557+
};
558+
"#,
559+
8,
560+
);
525561
}
526562

527563
#[test]
@@ -1019,6 +1055,24 @@ fn try_operator() {
10191055
);
10201056
}
10211057

1058+
#[test]
1059+
fn try_block() {
1060+
check_number(
1061+
r#"
1062+
//- minicore: option, try
1063+
const fn g(x: Option<i32>, y: Option<i32>) -> i32 {
1064+
let r = try { x? * y? };
1065+
match r {
1066+
Some(k) => k,
1067+
None => 5,
1068+
}
1069+
}
1070+
const GOAL: i32 = g(Some(10), Some(20)) + g(Some(30), None) + g(None, Some(40)) + g(None, None);
1071+
"#,
1072+
215,
1073+
);
1074+
}
1075+
10221076
#[test]
10231077
fn or_pattern() {
10241078
check_number(

crates/hir-ty/src/infer.rs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1025,10 +1025,6 @@ impl<'a> InferenceContext<'a> {
10251025
self.resolve_lang_item(lang)?.as_trait()
10261026
}
10271027

1028-
fn resolve_ops_try_output(&self) -> Option<TypeAliasId> {
1029-
self.resolve_output_on(self.resolve_lang_trait(LangItem::Try)?)
1030-
}
1031-
10321028
fn resolve_ops_neg_output(&self) -> Option<TypeAliasId> {
10331029
self.resolve_output_on(self.resolve_lang_trait(LangItem::Neg)?)
10341030
}

crates/hir-ty/src/infer/expr.rs

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -159,26 +159,6 @@ impl<'a> InferenceContext<'a> {
159159
})
160160
.1
161161
}
162-
Expr::TryBlock { id: _, statements, tail } => {
163-
// The type that is returned from the try block
164-
let try_ty = self.table.new_type_var();
165-
if let Some(ty) = expected.only_has_type(&mut self.table) {
166-
self.unify(&try_ty, &ty);
167-
}
168-
169-
// The ok-ish type that is expected from the last expression
170-
let ok_ty =
171-
self.resolve_associated_type(try_ty.clone(), self.resolve_ops_try_output());
172-
173-
self.infer_block(
174-
tgt_expr,
175-
statements,
176-
*tail,
177-
None,
178-
&Expectation::has_type(ok_ty.clone()),
179-
);
180-
try_ty
181-
}
182162
Expr::Async { id: _, statements, tail } => {
183163
let ret_ty = self.table.new_type_var();
184164
let prev_diverges = mem::replace(&mut self.diverges, Diverges::Maybe);

crates/hir-ty/src/infer/mutability.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@ impl<'a> InferenceContext<'a> {
4444
}
4545
Expr::Let { pat, expr } => self.infer_mut_expr(*expr, self.pat_bound_mutability(*pat)),
4646
Expr::Block { id: _, statements, tail, label: _ }
47-
| Expr::TryBlock { id: _, statements, tail }
4847
| Expr::Async { id: _, statements, tail }
4948
| Expr::Const { id: _, statements, tail }
5049
| Expr::Unsafe { id: _, statements, tail } => {

0 commit comments

Comments
 (0)