Add BreakExpr completion suggest

This commit is contained in:
A4-Tacks 2025-08-24 12:02:25 +08:00
parent 05b7cbc236
commit 8e7afeeb92
No known key found for this signature in database
GPG key ID: 86AC1F526BA06668
2 changed files with 158 additions and 1 deletions

View file

@ -736,6 +736,14 @@ fn expected_type_and_name<'db>(
});
(ty, None)
},
ast::BreakExpr(it) => {
let ty = match find_breakvalueable(&node, it.lifetime(), sema) {
Some(Either::Left(block_expr)) => sema.type_of_expr(&block_expr.into()),
Some(Either::Right(loop_expr)) => sema.type_of_expr(&loop_expr.into()),
None => None,
};
(ty.map(TypeInfo::original), None)
},
ast::ClosureExpr(it) => {
let ty = sema.type_of_expr(&it.into());
ty.and_then(|ty| ty.original.as_callable(sema.db))
@ -1855,6 +1863,35 @@ fn is_in_breakable(node: &SyntaxNode) -> BreakableKind {
.unwrap_or(BreakableKind::None)
}
fn find_breakvalueable(
node: &SyntaxNode,
label: Option<ast::Lifetime>,
sema: &Semantics<'_, RootDatabase>,
) -> Option<Either<ast::BlockExpr, ast::LoopExpr>> {
let label_eq = |value: Option<ast::Label>| match (&label, &value) {
(None, _) => true,
(Some(_), None) => false,
(Some(label), Some(found)) => {
found.lifetime().is_some_and(|found| label.syntax().text() == found.syntax().text())
}
};
sema.ancestors_with_macros(node.clone())
.take_while(|it| it.kind() != SyntaxKind::FN && it.kind() != SyntaxKind::CLOSURE_EXPR)
.find_map(|it| {
match_ast! {
match it {
ast::ForExpr(it) => label_eq(it.label()).then_some(None),
ast::WhileExpr(it) => label_eq(it.label()).then_some(None),
ast::LoopExpr(it) => label_eq(it.label()).then_some(Some(Either::Right(it))),
ast::BlockExpr(it) => it.label().and_then(|label| {
label_eq(Some(label)).then_some(Some(Either::Left(it)))
}),
_ => None,
}
}
})?
}
fn is_in_block(node: &SyntaxNode) -> bool {
node.parent()
.map(|node| ast::ExprStmt::can_cast(node.kind()) || ast::StmtList::can_cast(node.kind()))
@ -1905,7 +1942,8 @@ fn prev_special_biased_token_at_trivia(mut token: SyntaxToken) -> SyntaxToken {
| T![^=]
| T![return]
| T![break]
| T![continue] = prev.kind()
| T![continue]
| T![lifetime_ident] = prev.kind()
{
token = prev
}

View file

@ -525,3 +525,122 @@ fn foo() {
expect![[r#"ty: State, name: ?"#]],
);
}
#[test]
fn expected_type_break_expr_in_loop() {
check_expected_type_and_name(
r#"
enum State { Stop }
fn foo() {
let _x: State = loop {
{
break State::Stop;
break $0;
}
};
}
"#,
expect![[r#"ty: State, name: ?"#]],
);
check_expected_type_and_name(
r#"
enum State { Stop }
fn foo() {
let _x: State = 'a: loop {
{
break State::Stop;
break $0;
}
};
}
"#,
expect![[r#"ty: State, name: ?"#]],
);
check_expected_type_and_name(
r#"
enum State { Stop }
fn foo() {
let _x: State = 'a: loop {
while true {
break $0;
}
};
}
"#,
expect![[r#"ty: ?, name: ?"#]],
);
}
#[test]
fn expected_type_break_expr_in_labeled_loop() {
check_expected_type_and_name(
r#"
enum State { Stop }
fn foo() {
let _x: State = 'a: loop {
let _y: i32 = loop {
{
break 'a State::Stop;
break 'a $0;
}
};
};
}
"#,
expect![[r#"ty: State, name: ?"#]],
);
check_expected_type_and_name(
r#"
enum State { Stop }
fn foo() {
let _x: State = 'a: loop {
let _y: i32 = loop {
while true {
break 'a State::Stop;
break 'a $0;
}
};
};
}
"#,
expect![[r#"ty: State, name: ?"#]],
);
check_expected_type_and_name(
r#"
enum State { Stop }
fn foo() {
'a: while true {
let _x: State = loop {
break State::Stop;
break 'a $0;
};
}
}
"#,
expect![[r#"ty: ?, name: ?"#]],
);
}
#[test]
fn expected_type_break_expr_in_labeled_block() {
check_expected_type_and_name(
r#"
enum State { Stop }
fn foo() {
let _x: State = 'a: {
let _y: i32 = 'b: {
{
break 'a State::Stop;
break 'a $0;
};
};
};
}
"#,
expect![[r#"ty: State, name: ?"#]],
);
}