Skip to content

Commit a0ed87f

Browse files
bors[bot]Veykril
andauthored
Merge #8127
8127: Add label completion r=Veykril a=Veykril Co-authored-by: Lukas Wirth <[email protected]>
2 parents 787bd3c + 64957ac commit a0ed87f

File tree

8 files changed

+184
-18
lines changed

8 files changed

+184
-18
lines changed

crates/hir/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2199,6 +2199,7 @@ pub enum ScopeDef {
21992199
ImplSelfType(Impl),
22002200
AdtSelfType(Adt),
22012201
Local(Local),
2202+
Label(Label),
22022203
Unknown,
22032204
}
22042205

crates/hir/src/semantics.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -839,6 +839,10 @@ impl<'a> SemanticsScope<'a> {
839839
let parent = resolver.body_owner().unwrap();
840840
ScopeDef::Local(Local { parent, pat_id })
841841
}
842+
resolver::ScopeDef::Label(label_id) => {
843+
let parent = resolver.body_owner().unwrap();
844+
ScopeDef::Label(Label { parent, label_id })
845+
}
842846
};
843847
f(name, def)
844848
})

crates/hir_def/src/body/scope.rs

Lines changed: 47 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use rustc_hash::FxHashMap;
88
use crate::{
99
body::Body,
1010
db::DefDatabase,
11-
expr::{Expr, ExprId, Pat, PatId, Statement},
11+
expr::{Expr, ExprId, LabelId, Pat, PatId, Statement},
1212
BlockId, DefWithBodyId,
1313
};
1414

@@ -40,6 +40,7 @@ impl ScopeEntry {
4040
pub struct ScopeData {
4141
parent: Option<ScopeId>,
4242
block: Option<BlockId>,
43+
label: Option<(LabelId, Name)>,
4344
entries: Vec<ScopeEntry>,
4445
}
4546

@@ -67,6 +68,11 @@ impl ExprScopes {
6768
self.scopes[scope].block
6869
}
6970

71+
/// If `scope` refers to a labeled expression scope, returns the corresponding `Label`.
72+
pub fn label(&self, scope: ScopeId) -> Option<(LabelId, Name)> {
73+
self.scopes[scope].label.clone()
74+
}
75+
7076
pub fn scope_chain(&self, scope: Option<ScopeId>) -> impl Iterator<Item = ScopeId> + '_ {
7177
std::iter::successors(scope, move |&scope| self.scopes[scope].parent)
7278
}
@@ -85,15 +91,34 @@ impl ExprScopes {
8591
}
8692

8793
fn root_scope(&mut self) -> ScopeId {
88-
self.scopes.alloc(ScopeData { parent: None, block: None, entries: vec![] })
94+
self.scopes.alloc(ScopeData { parent: None, block: None, label: None, entries: vec![] })
8995
}
9096

9197
fn new_scope(&mut self, parent: ScopeId) -> ScopeId {
92-
self.scopes.alloc(ScopeData { parent: Some(parent), block: None, entries: vec![] })
98+
self.scopes.alloc(ScopeData {
99+
parent: Some(parent),
100+
block: None,
101+
label: None,
102+
entries: vec![],
103+
})
93104
}
94105

95-
fn new_block_scope(&mut self, parent: ScopeId, block: BlockId) -> ScopeId {
96-
self.scopes.alloc(ScopeData { parent: Some(parent), block: Some(block), entries: vec![] })
106+
fn new_labeled_scope(&mut self, parent: ScopeId, label: Option<(LabelId, Name)>) -> ScopeId {
107+
self.scopes.alloc(ScopeData { parent: Some(parent), block: None, label, entries: vec![] })
108+
}
109+
110+
fn new_block_scope(
111+
&mut self,
112+
parent: ScopeId,
113+
block: BlockId,
114+
label: Option<(LabelId, Name)>,
115+
) -> ScopeId {
116+
self.scopes.alloc(ScopeData {
117+
parent: Some(parent),
118+
block: Some(block),
119+
label,
120+
entries: vec![],
121+
})
97122
}
98123

99124
fn add_bindings(&mut self, body: &Body, scope: ScopeId, pat: PatId) {
@@ -144,21 +169,33 @@ fn compute_block_scopes(
144169
}
145170

146171
fn compute_expr_scopes(expr: ExprId, body: &Body, scopes: &mut ExprScopes, scope: ScopeId) {
172+
let make_label =
173+
|label: &Option<_>| label.map(|label| (label, body.labels[label].name.clone()));
174+
147175
scopes.set_scope(expr, scope);
148176
match &body[expr] {
149-
Expr::Block { statements, tail, id, .. } => {
150-
let scope = scopes.new_block_scope(scope, *id);
177+
Expr::Block { statements, tail, id, label } => {
178+
let scope = scopes.new_block_scope(scope, *id, make_label(label));
151179
// Overwrite the old scope for the block expr, so that every block scope can be found
152180
// via the block itself (important for blocks that only contain items, no expressions).
153181
scopes.set_scope(expr, scope);
154-
compute_block_scopes(&statements, *tail, body, scopes, scope);
182+
compute_block_scopes(statements, *tail, body, scopes, scope);
155183
}
156-
Expr::For { iterable, pat, body: body_expr, .. } => {
184+
Expr::For { iterable, pat, body: body_expr, label } => {
157185
compute_expr_scopes(*iterable, body, scopes, scope);
158-
let scope = scopes.new_scope(scope);
186+
let scope = scopes.new_labeled_scope(scope, make_label(label));
159187
scopes.add_bindings(body, scope, *pat);
160188
compute_expr_scopes(*body_expr, body, scopes, scope);
161189
}
190+
Expr::While { condition, body: body_expr, label } => {
191+
let scope = scopes.new_labeled_scope(scope, make_label(label));
192+
compute_expr_scopes(*condition, body, scopes, scope);
193+
compute_expr_scopes(*body_expr, body, scopes, scope);
194+
}
195+
Expr::Loop { body: body_expr, label } => {
196+
let scope = scopes.new_labeled_scope(scope, make_label(label));
197+
compute_expr_scopes(*body_expr, body, scopes, scope);
198+
}
162199
Expr::Lambda { args, body: body_expr, .. } => {
163200
let scope = scopes.new_scope(scope);
164201
scopes.add_params_bindings(body, scope, &args);

crates/hir_def/src/resolver.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use crate::{
1212
body::scope::{ExprScopes, ScopeId},
1313
builtin_type::BuiltinType,
1414
db::DefDatabase,
15-
expr::{ExprId, PatId},
15+
expr::{ExprId, LabelId, PatId},
1616
generics::GenericParams,
1717
item_scope::{BuiltinShadowMode, BUILTIN_SCOPE},
1818
nameres::DefMap,
@@ -409,6 +409,7 @@ pub enum ScopeDef {
409409
AdtSelfType(AdtId),
410410
GenericParam(GenericParamId),
411411
Local(PatId),
412+
Label(LabelId),
412413
}
413414

414415
impl Scope {
@@ -470,6 +471,9 @@ impl Scope {
470471
f(name![Self], ScopeDef::AdtSelfType(*i));
471472
}
472473
Scope::ExprScope(scope) => {
474+
if let Some((label, name)) = scope.expr_scopes.label(scope.scope_id) {
475+
f(name.clone(), ScopeDef::Label(label))
476+
}
473477
scope.expr_scopes.entries(scope.scope_id).iter().for_each(|e| {
474478
f(e.name().clone(), ScopeDef::Local(e.pat()));
475479
});

crates/ide_completion/src/completions/lifetime.rs

Lines changed: 105 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//! Completes lifetimes.
1+
//! Completes lifetimes and labels.
22
use hir::ScopeDef;
33

44
use crate::{completions::Completions, context::CompletionContext};
@@ -29,6 +29,18 @@ pub(crate) fn complete_lifetime(acc: &mut Completions, ctx: &CompletionContext)
2929
}
3030
}
3131

32+
/// Completes labels.
33+
pub(crate) fn complete_label(acc: &mut Completions, ctx: &CompletionContext) {
34+
if !ctx.is_label_ref {
35+
return;
36+
}
37+
ctx.scope.process_all_names(&mut |name, res| {
38+
if let ScopeDef::Label(_) = res {
39+
acc.add_resolution(ctx, name.to_string(), &res);
40+
}
41+
});
42+
}
43+
3244
#[cfg(test)]
3345
mod tests {
3446
use expect_test::{expect, Expect};
@@ -178,4 +190,96 @@ fn foo<'footime, 'lifetime: 'a$0>() {}
178190
"#]],
179191
);
180192
}
193+
194+
#[test]
195+
fn complete_label_in_loop() {
196+
check(
197+
r#"
198+
fn foo() {
199+
'foop: loop {
200+
break '$0
201+
}
202+
}
203+
"#,
204+
expect![[r#"
205+
lb 'foop
206+
"#]],
207+
);
208+
check(
209+
r#"
210+
fn foo() {
211+
'foop: loop {
212+
continue '$0
213+
}
214+
}
215+
"#,
216+
expect![[r#"
217+
lb 'foop
218+
"#]],
219+
);
220+
}
221+
222+
#[test]
223+
fn complete_label_in_block_nested() {
224+
check(
225+
r#"
226+
fn foo() {
227+
'foop: {
228+
'baap: {
229+
break '$0
230+
}
231+
}
232+
}
233+
"#,
234+
expect![[r#"
235+
lb 'baap
236+
lb 'foop
237+
"#]],
238+
);
239+
}
240+
241+
#[test]
242+
fn complete_label_in_loop_with_value() {
243+
check(
244+
r#"
245+
fn foo() {
246+
'foop: loop {
247+
break '$0 i32;
248+
}
249+
}
250+
"#,
251+
expect![[r#"
252+
lb 'foop
253+
"#]],
254+
);
255+
}
256+
257+
#[test]
258+
fn complete_label_in_while_cond() {
259+
check(
260+
r#"
261+
fn foo() {
262+
'outer: while { 'inner: loop { break '$0 } } {}
263+
}
264+
"#,
265+
expect![[r#"
266+
lb 'inner
267+
lb 'outer
268+
"#]],
269+
);
270+
}
271+
272+
#[test]
273+
fn complete_label_in_for_iterable() {
274+
check(
275+
r#"
276+
fn foo() {
277+
'outer: for _ in [{ 'inner: loop { break '$0 } }] {}
278+
}
279+
"#,
280+
expect![[r#"
281+
lb 'inner
282+
"#]],
283+
);
284+
}
181285
}

crates/ide_completion/src/context.rs

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ pub(crate) struct CompletionContext<'a> {
5353
/// FIXME: `ActiveParameter` is string-based, which is very very wrong
5454
pub(super) active_parameter: Option<ActiveParameter>,
5555
pub(super) is_param: bool,
56+
pub(super) is_label_ref: bool,
5657
/// If a name-binding or reference to a const in a pattern.
5758
/// Irrefutable patterns (like let) are excluded.
5859
pub(super) is_pat_binding_or_const: bool,
@@ -155,6 +156,7 @@ impl<'a> CompletionContext<'a> {
155156
record_field_syntax: None,
156157
impl_def: None,
157158
active_parameter: ActiveParameter::at(db, position),
159+
is_label_ref: false,
158160
is_param: false,
159161
is_pat_binding_or_const: false,
160162
is_irrefutable_pat_binding: false,
@@ -468,12 +470,24 @@ impl<'a> CompletionContext<'a> {
468470
) {
469471
self.lifetime_syntax =
470472
find_node_at_offset(original_file, lifetime.syntax().text_range().start());
471-
if lifetime.syntax().parent().map_or(false, |p| p.kind() != syntax::SyntaxKind::ERROR) {
472-
self.lifetime_allowed = true;
473-
}
474-
if let Some(_) = lifetime.syntax().parent().and_then(ast::LifetimeParam::cast) {
475-
self.lifetime_param_syntax =
476-
self.sema.find_node_at_offset_with_macros(original_file, offset);
473+
if let Some(parent) = lifetime.syntax().parent() {
474+
if parent.kind() == syntax::SyntaxKind::ERROR {
475+
return;
476+
}
477+
478+
match_ast! {
479+
match parent {
480+
ast::LifetimeParam(_it) => {
481+
self.lifetime_allowed = true;
482+
self.lifetime_param_syntax =
483+
self.sema.find_node_at_offset_with_macros(original_file, offset);
484+
},
485+
ast::BreakExpr(_it) => self.is_label_ref = true,
486+
ast::ContinueExpr(_it) => self.is_label_ref = true,
487+
ast::Label(_it) => (),
488+
_ => self.lifetime_allowed = true,
489+
}
490+
}
477491
}
478492
}
479493

crates/ide_completion/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ pub fn completions(
131131
completions::mod_::complete_mod(&mut acc, &ctx);
132132
completions::flyimport::import_on_the_fly(&mut acc, &ctx);
133133
completions::lifetime::complete_lifetime(&mut acc, &ctx);
134+
completions::lifetime::complete_label(&mut acc, &ctx);
134135

135136
Some(acc)
136137
}

crates/ide_completion/src/render.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,7 @@ impl<'a> Render<'a> {
219219
hir::GenericParam::ConstParam(_) => SymbolKind::ConstParam,
220220
}),
221221
ScopeDef::Local(..) => CompletionItemKind::SymbolKind(SymbolKind::Local),
222+
ScopeDef::Label(..) => CompletionItemKind::SymbolKind(SymbolKind::Label),
222223
ScopeDef::AdtSelfType(..) | ScopeDef::ImplSelfType(..) => {
223224
CompletionItemKind::SymbolKind(SymbolKind::SelfParam)
224225
}

0 commit comments

Comments
 (0)