mirror of
https://github.com/rust-lang/rust-analyzer.git
synced 2025-10-03 15:15:24 +00:00
Desugar try blocks
This commit is contained in:
parent
453ae2e00e
commit
8e73ea5253
11 changed files with 215 additions and 84 deletions
|
@ -522,6 +522,42 @@ fn loops() {
|
|||
"#,
|
||||
4,
|
||||
);
|
||||
check_number(
|
||||
r#"
|
||||
const GOAL: u8 = {
|
||||
let mut x = 0;
|
||||
loop {
|
||||
x = x + 1;
|
||||
if x == 5 {
|
||||
break x + 2;
|
||||
}
|
||||
}
|
||||
};
|
||||
"#,
|
||||
7,
|
||||
);
|
||||
check_number(
|
||||
r#"
|
||||
const GOAL: u8 = {
|
||||
'a: loop {
|
||||
let x = 'b: loop {
|
||||
let x = 'c: loop {
|
||||
let x = 'd: loop {
|
||||
let x = 'e: loop {
|
||||
break 'd 1;
|
||||
};
|
||||
break 2 + x;
|
||||
};
|
||||
break 3 + x;
|
||||
};
|
||||
break 'a 4 + x;
|
||||
};
|
||||
break 5 + x;
|
||||
}
|
||||
};
|
||||
"#,
|
||||
8,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -1019,6 +1055,24 @@ fn try_operator() {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn try_block() {
|
||||
check_number(
|
||||
r#"
|
||||
//- minicore: option, try
|
||||
const fn g(x: Option<i32>, y: Option<i32>) -> i32 {
|
||||
let r = try { x? * y? };
|
||||
match r {
|
||||
Some(k) => k,
|
||||
None => 5,
|
||||
}
|
||||
}
|
||||
const GOAL: i32 = g(Some(10), Some(20)) + g(Some(30), None) + g(None, Some(40)) + g(None, None);
|
||||
"#,
|
||||
215,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn or_pattern() {
|
||||
check_number(
|
||||
|
|
|
@ -1025,10 +1025,6 @@ impl<'a> InferenceContext<'a> {
|
|||
self.resolve_lang_item(lang)?.as_trait()
|
||||
}
|
||||
|
||||
fn resolve_ops_try_output(&self) -> Option<TypeAliasId> {
|
||||
self.resolve_output_on(self.resolve_lang_trait(LangItem::Try)?)
|
||||
}
|
||||
|
||||
fn resolve_ops_neg_output(&self) -> Option<TypeAliasId> {
|
||||
self.resolve_output_on(self.resolve_lang_trait(LangItem::Neg)?)
|
||||
}
|
||||
|
|
|
@ -159,26 +159,6 @@ impl<'a> InferenceContext<'a> {
|
|||
})
|
||||
.1
|
||||
}
|
||||
Expr::TryBlock { id: _, statements, tail } => {
|
||||
// The type that is returned from the try block
|
||||
let try_ty = self.table.new_type_var();
|
||||
if let Some(ty) = expected.only_has_type(&mut self.table) {
|
||||
self.unify(&try_ty, &ty);
|
||||
}
|
||||
|
||||
// The ok-ish type that is expected from the last expression
|
||||
let ok_ty =
|
||||
self.resolve_associated_type(try_ty.clone(), self.resolve_ops_try_output());
|
||||
|
||||
self.infer_block(
|
||||
tgt_expr,
|
||||
statements,
|
||||
*tail,
|
||||
None,
|
||||
&Expectation::has_type(ok_ty.clone()),
|
||||
);
|
||||
try_ty
|
||||
}
|
||||
Expr::Async { id: _, statements, tail } => {
|
||||
let ret_ty = self.table.new_type_var();
|
||||
let prev_diverges = mem::replace(&mut self.diverges, Diverges::Maybe);
|
||||
|
|
|
@ -44,7 +44,6 @@ impl<'a> InferenceContext<'a> {
|
|||
}
|
||||
Expr::Let { pat, expr } => self.infer_mut_expr(*expr, self.pat_bound_mutability(*pat)),
|
||||
Expr::Block { id: _, statements, tail, label: _ }
|
||||
| Expr::TryBlock { id: _, statements, tail }
|
||||
| Expr::Async { id: _, statements, tail }
|
||||
| Expr::Const { id: _, statements, tail }
|
||||
| Expr::Unsafe { id: _, statements, tail } => {
|
||||
|
|
|
@ -18,6 +18,7 @@ use hir_def::{
|
|||
};
|
||||
use hir_expand::name::Name;
|
||||
use la_arena::ArenaMap;
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
use crate::{
|
||||
consteval::ConstEvalError, db::HirDatabase, display::HirDisplay, infer::TypeMismatch,
|
||||
|
@ -32,17 +33,21 @@ mod pattern_matching;
|
|||
|
||||
use pattern_matching::AdtPatternShape;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[derive(Debug, Clone)]
|
||||
struct LoopBlocks {
|
||||
begin: BasicBlockId,
|
||||
/// `None` for loops that are not terminating
|
||||
end: Option<BasicBlockId>,
|
||||
place: Place,
|
||||
}
|
||||
|
||||
struct MirLowerCtx<'a> {
|
||||
result: MirBody,
|
||||
owner: DefWithBodyId,
|
||||
current_loop_blocks: Option<LoopBlocks>,
|
||||
// FIXME: we should resolve labels in HIR lowering and always work with label id here, not
|
||||
// with raw names.
|
||||
labeled_loop_blocks: FxHashMap<Name, LoopBlocks>,
|
||||
discr_temp: Option<Place>,
|
||||
db: &'a dyn HirDatabase,
|
||||
body: &'a Body,
|
||||
|
@ -72,6 +77,7 @@ pub enum MirLowerError {
|
|||
ImplementationError(&'static str),
|
||||
LangItemNotFound(LangItem),
|
||||
MutatingRvalue,
|
||||
UnresolvedLabel,
|
||||
}
|
||||
|
||||
macro_rules! not_supported {
|
||||
|
@ -375,19 +381,29 @@ impl MirLowerCtx<'_> {
|
|||
Ok(self.merge_blocks(Some(then_target), else_target))
|
||||
}
|
||||
Expr::Unsafe { id: _, statements, tail } => {
|
||||
self.lower_block_to_place(None, statements, current, *tail, place)
|
||||
self.lower_block_to_place(statements, current, *tail, place)
|
||||
}
|
||||
Expr::Block { id: _, statements, tail, label } => {
|
||||
self.lower_block_to_place(*label, statements, current, *tail, place)
|
||||
if let Some(label) = label {
|
||||
self.lower_loop(current, place.clone(), Some(*label), |this, begin| {
|
||||
if let Some(block) = this.lower_block_to_place(statements, begin, *tail, place)? {
|
||||
let end = this.current_loop_end()?;
|
||||
this.set_goto(block, end);
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
} else {
|
||||
self.lower_block_to_place(statements, current, *tail, place)
|
||||
}
|
||||
}
|
||||
Expr::Loop { body, label } => self.lower_loop(current, *label, |this, begin| {
|
||||
Expr::Loop { body, label } => self.lower_loop(current, place, *label, |this, begin| {
|
||||
if let Some((_, block)) = this.lower_expr_as_place(begin, *body, true)? {
|
||||
this.set_goto(block, begin);
|
||||
}
|
||||
Ok(())
|
||||
}),
|
||||
Expr::While { condition, body, label } => {
|
||||
self.lower_loop(current, *label, |this, begin| {
|
||||
self.lower_loop(current, place, *label, |this, begin| {
|
||||
let Some((discr, to_switch)) = this.lower_expr_to_some_operand(*condition, begin)? else {
|
||||
return Ok(());
|
||||
};
|
||||
|
@ -438,7 +454,7 @@ impl MirLowerCtx<'_> {
|
|||
return Ok(None);
|
||||
};
|
||||
self.push_assignment(current, ref_mut_iterator_place.clone(), Rvalue::Ref(BorrowKind::Mut { allow_two_phase_borrow: false }, iterator_place), expr_id.into());
|
||||
self.lower_loop(current, label, |this, begin| {
|
||||
self.lower_loop(current, place, label, |this, begin| {
|
||||
let Some(current) = this.lower_call(iter_next_fn_op, vec![Operand::Copy(ref_mut_iterator_place)], option_item_place.clone(), begin, false)?
|
||||
else {
|
||||
return Ok(());
|
||||
|
@ -558,24 +574,28 @@ impl MirLowerCtx<'_> {
|
|||
Some(_) => not_supported!("continue with label"),
|
||||
None => {
|
||||
let loop_data =
|
||||
self.current_loop_blocks.ok_or(MirLowerError::ContinueWithoutLoop)?;
|
||||
self.current_loop_blocks.as_ref().ok_or(MirLowerError::ContinueWithoutLoop)?;
|
||||
self.set_goto(current, loop_data.begin);
|
||||
Ok(None)
|
||||
}
|
||||
},
|
||||
Expr::Break { expr, label } => {
|
||||
if expr.is_some() {
|
||||
not_supported!("break with value");
|
||||
}
|
||||
match label {
|
||||
Some(_) => not_supported!("break with label"),
|
||||
None => {
|
||||
let end =
|
||||
self.current_loop_end()?;
|
||||
self.set_goto(current, end);
|
||||
Ok(None)
|
||||
}
|
||||
if let Some(expr) = expr {
|
||||
let loop_data = match label {
|
||||
Some(l) => self.labeled_loop_blocks.get(l).ok_or(MirLowerError::UnresolvedLabel)?,
|
||||
None => self.current_loop_blocks.as_ref().ok_or(MirLowerError::BreakWithoutLoop)?,
|
||||
};
|
||||
let Some(c) = self.lower_expr_to_place(*expr, loop_data.place.clone(), current)? else {
|
||||
return Ok(None);
|
||||
};
|
||||
current = c;
|
||||
}
|
||||
let end = match label {
|
||||
Some(l) => self.labeled_loop_blocks.get(l).ok_or(MirLowerError::UnresolvedLabel)?.end.expect("We always generate end for labeled loops"),
|
||||
None => self.current_loop_end()?,
|
||||
};
|
||||
self.set_goto(current, end);
|
||||
Ok(None)
|
||||
}
|
||||
Expr::Return { expr } => {
|
||||
if let Some(expr) = expr {
|
||||
|
@ -668,7 +688,6 @@ impl MirLowerCtx<'_> {
|
|||
}
|
||||
Expr::Await { .. } => not_supported!("await"),
|
||||
Expr::Yeet { .. } => not_supported!("yeet"),
|
||||
Expr::TryBlock { .. } => not_supported!("try block"),
|
||||
Expr::Async { .. } => not_supported!("async block"),
|
||||
Expr::Const { .. } => not_supported!("anonymous const block"),
|
||||
Expr::Cast { expr, type_ref: _ } => {
|
||||
|
@ -1085,19 +1104,34 @@ impl MirLowerCtx<'_> {
|
|||
fn lower_loop(
|
||||
&mut self,
|
||||
prev_block: BasicBlockId,
|
||||
place: Place,
|
||||
label: Option<LabelId>,
|
||||
f: impl FnOnce(&mut MirLowerCtx<'_>, BasicBlockId) -> Result<()>,
|
||||
) -> Result<Option<BasicBlockId>> {
|
||||
if label.is_some() {
|
||||
not_supported!("loop with label");
|
||||
}
|
||||
let begin = self.new_basic_block();
|
||||
let prev =
|
||||
mem::replace(&mut self.current_loop_blocks, Some(LoopBlocks { begin, end: None }));
|
||||
let prev = mem::replace(
|
||||
&mut self.current_loop_blocks,
|
||||
Some(LoopBlocks { begin, end: None, place }),
|
||||
);
|
||||
let prev_label = if let Some(label) = label {
|
||||
// We should generate the end now, to make sure that it wouldn't change later. It is
|
||||
// bad as we may emit end (unneccessary unreachable block) for unterminating loop, but
|
||||
// it should not affect correctness.
|
||||
self.current_loop_end()?;
|
||||
self.labeled_loop_blocks.insert(
|
||||
self.body.labels[label].name.clone(),
|
||||
self.current_loop_blocks.as_ref().unwrap().clone(),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
self.set_goto(prev_block, begin);
|
||||
f(self, begin)?;
|
||||
let my = mem::replace(&mut self.current_loop_blocks, prev)
|
||||
.ok_or(MirLowerError::ImplementationError("current_loop_blocks is corrupt"))?;
|
||||
if let Some(prev) = prev_label {
|
||||
self.labeled_loop_blocks.insert(self.body.labels[label.unwrap()].name.clone(), prev);
|
||||
}
|
||||
Ok(my.end)
|
||||
}
|
||||
|
||||
|
@ -1185,15 +1219,11 @@ impl MirLowerCtx<'_> {
|
|||
|
||||
fn lower_block_to_place(
|
||||
&mut self,
|
||||
label: Option<LabelId>,
|
||||
statements: &[hir_def::expr::Statement],
|
||||
mut current: BasicBlockId,
|
||||
tail: Option<ExprId>,
|
||||
place: Place,
|
||||
) -> Result<Option<Idx<BasicBlock>>> {
|
||||
if label.is_some() {
|
||||
not_supported!("block with label");
|
||||
}
|
||||
for statement in statements.iter() {
|
||||
match statement {
|
||||
hir_def::expr::Statement::Let { pat, initializer, else_branch, type_ref: _ } => {
|
||||
|
@ -1355,6 +1385,7 @@ pub fn lower_to_mir(
|
|||
body,
|
||||
owner,
|
||||
current_loop_blocks: None,
|
||||
labeled_loop_blocks: Default::default(),
|
||||
discr_temp: None,
|
||||
};
|
||||
let mut current = start_block;
|
||||
|
|
|
@ -206,19 +206,27 @@ fn test() {
|
|||
fn infer_try_trait() {
|
||||
check_types(
|
||||
r#"
|
||||
//- minicore: try, result
|
||||
//- minicore: try, result, from
|
||||
fn test() {
|
||||
let r: Result<i32, u64> = Result::Ok(1);
|
||||
let v = r?;
|
||||
v;
|
||||
} //^ i32
|
||||
|
||||
impl<O, E> core::ops::Try for Result<O, E> {
|
||||
type Output = O;
|
||||
type Error = Result<core::convert::Infallible, E>;
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
impl<T, E, F: From<E>> core::ops::FromResidual<Result<core::convert::Infallible, E>> for Result<T, F> {}
|
||||
#[test]
|
||||
fn infer_try_block() {
|
||||
// FIXME: We should test more cases, but it currently doesn't work, since
|
||||
// our labeled block type inference is broken.
|
||||
check_types(
|
||||
r#"
|
||||
//- minicore: try, option
|
||||
fn test() {
|
||||
let x: Option<_> = try { Some(2)?; };
|
||||
//^ Option<()>
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue