Merge #8127
8127: Add label completion r=Veykril a=Veykril Co-authored-by: Lukas Wirth <lukastw97@gmail.com>
This commit is contained in:
commit
a0ed87ff56
@ -2199,6 +2199,7 @@ pub enum ScopeDef {
|
||||
ImplSelfType(Impl),
|
||||
AdtSelfType(Adt),
|
||||
Local(Local),
|
||||
Label(Label),
|
||||
Unknown,
|
||||
}
|
||||
|
||||
|
@ -839,6 +839,10 @@ impl<'a> SemanticsScope<'a> {
|
||||
let parent = resolver.body_owner().unwrap();
|
||||
ScopeDef::Local(Local { parent, pat_id })
|
||||
}
|
||||
resolver::ScopeDef::Label(label_id) => {
|
||||
let parent = resolver.body_owner().unwrap();
|
||||
ScopeDef::Label(Label { parent, label_id })
|
||||
}
|
||||
};
|
||||
f(name, def)
|
||||
})
|
||||
|
@ -8,7 +8,7 @@ use rustc_hash::FxHashMap;
|
||||
use crate::{
|
||||
body::Body,
|
||||
db::DefDatabase,
|
||||
expr::{Expr, ExprId, Pat, PatId, Statement},
|
||||
expr::{Expr, ExprId, LabelId, Pat, PatId, Statement},
|
||||
BlockId, DefWithBodyId,
|
||||
};
|
||||
|
||||
@ -40,6 +40,7 @@ impl ScopeEntry {
|
||||
pub struct ScopeData {
|
||||
parent: Option<ScopeId>,
|
||||
block: Option<BlockId>,
|
||||
label: Option<(LabelId, Name)>,
|
||||
entries: Vec<ScopeEntry>,
|
||||
}
|
||||
|
||||
@ -67,6 +68,11 @@ impl ExprScopes {
|
||||
self.scopes[scope].block
|
||||
}
|
||||
|
||||
/// If `scope` refers to a labeled expression scope, returns the corresponding `Label`.
|
||||
pub fn label(&self, scope: ScopeId) -> Option<(LabelId, Name)> {
|
||||
self.scopes[scope].label.clone()
|
||||
}
|
||||
|
||||
pub fn scope_chain(&self, scope: Option<ScopeId>) -> impl Iterator<Item = ScopeId> + '_ {
|
||||
std::iter::successors(scope, move |&scope| self.scopes[scope].parent)
|
||||
}
|
||||
@ -85,15 +91,34 @@ impl ExprScopes {
|
||||
}
|
||||
|
||||
fn root_scope(&mut self) -> ScopeId {
|
||||
self.scopes.alloc(ScopeData { parent: None, block: None, entries: vec![] })
|
||||
self.scopes.alloc(ScopeData { parent: None, block: None, label: None, entries: vec![] })
|
||||
}
|
||||
|
||||
fn new_scope(&mut self, parent: ScopeId) -> ScopeId {
|
||||
self.scopes.alloc(ScopeData { parent: Some(parent), block: None, entries: vec![] })
|
||||
self.scopes.alloc(ScopeData {
|
||||
parent: Some(parent),
|
||||
block: None,
|
||||
label: None,
|
||||
entries: vec![],
|
||||
})
|
||||
}
|
||||
|
||||
fn new_block_scope(&mut self, parent: ScopeId, block: BlockId) -> ScopeId {
|
||||
self.scopes.alloc(ScopeData { parent: Some(parent), block: Some(block), entries: vec![] })
|
||||
fn new_labeled_scope(&mut self, parent: ScopeId, label: Option<(LabelId, Name)>) -> ScopeId {
|
||||
self.scopes.alloc(ScopeData { parent: Some(parent), block: None, label, entries: vec![] })
|
||||
}
|
||||
|
||||
fn new_block_scope(
|
||||
&mut self,
|
||||
parent: ScopeId,
|
||||
block: BlockId,
|
||||
label: Option<(LabelId, Name)>,
|
||||
) -> ScopeId {
|
||||
self.scopes.alloc(ScopeData {
|
||||
parent: Some(parent),
|
||||
block: Some(block),
|
||||
label,
|
||||
entries: vec![],
|
||||
})
|
||||
}
|
||||
|
||||
fn add_bindings(&mut self, body: &Body, scope: ScopeId, pat: PatId) {
|
||||
@ -144,21 +169,33 @@ fn compute_block_scopes(
|
||||
}
|
||||
|
||||
fn compute_expr_scopes(expr: ExprId, body: &Body, scopes: &mut ExprScopes, scope: ScopeId) {
|
||||
let make_label =
|
||||
|label: &Option<_>| label.map(|label| (label, body.labels[label].name.clone()));
|
||||
|
||||
scopes.set_scope(expr, scope);
|
||||
match &body[expr] {
|
||||
Expr::Block { statements, tail, id, .. } => {
|
||||
let scope = scopes.new_block_scope(scope, *id);
|
||||
Expr::Block { statements, tail, id, label } => {
|
||||
let scope = scopes.new_block_scope(scope, *id, make_label(label));
|
||||
// Overwrite the old scope for the block expr, so that every block scope can be found
|
||||
// via the block itself (important for blocks that only contain items, no expressions).
|
||||
scopes.set_scope(expr, scope);
|
||||
compute_block_scopes(&statements, *tail, body, scopes, scope);
|
||||
compute_block_scopes(statements, *tail, body, scopes, scope);
|
||||
}
|
||||
Expr::For { iterable, pat, body: body_expr, .. } => {
|
||||
Expr::For { iterable, pat, body: body_expr, label } => {
|
||||
compute_expr_scopes(*iterable, body, scopes, scope);
|
||||
let scope = scopes.new_scope(scope);
|
||||
let scope = scopes.new_labeled_scope(scope, make_label(label));
|
||||
scopes.add_bindings(body, scope, *pat);
|
||||
compute_expr_scopes(*body_expr, body, scopes, scope);
|
||||
}
|
||||
Expr::While { condition, body: body_expr, label } => {
|
||||
let scope = scopes.new_labeled_scope(scope, make_label(label));
|
||||
compute_expr_scopes(*condition, body, scopes, scope);
|
||||
compute_expr_scopes(*body_expr, body, scopes, scope);
|
||||
}
|
||||
Expr::Loop { body: body_expr, label } => {
|
||||
let scope = scopes.new_labeled_scope(scope, make_label(label));
|
||||
compute_expr_scopes(*body_expr, body, scopes, scope);
|
||||
}
|
||||
Expr::Lambda { args, body: body_expr, .. } => {
|
||||
let scope = scopes.new_scope(scope);
|
||||
scopes.add_params_bindings(body, scope, &args);
|
||||
|
@ -12,7 +12,7 @@ use crate::{
|
||||
body::scope::{ExprScopes, ScopeId},
|
||||
builtin_type::BuiltinType,
|
||||
db::DefDatabase,
|
||||
expr::{ExprId, PatId},
|
||||
expr::{ExprId, LabelId, PatId},
|
||||
generics::GenericParams,
|
||||
item_scope::{BuiltinShadowMode, BUILTIN_SCOPE},
|
||||
nameres::DefMap,
|
||||
@ -409,6 +409,7 @@ pub enum ScopeDef {
|
||||
AdtSelfType(AdtId),
|
||||
GenericParam(GenericParamId),
|
||||
Local(PatId),
|
||||
Label(LabelId),
|
||||
}
|
||||
|
||||
impl Scope {
|
||||
@ -470,6 +471,9 @@ impl Scope {
|
||||
f(name![Self], ScopeDef::AdtSelfType(*i));
|
||||
}
|
||||
Scope::ExprScope(scope) => {
|
||||
if let Some((label, name)) = scope.expr_scopes.label(scope.scope_id) {
|
||||
f(name.clone(), ScopeDef::Label(label))
|
||||
}
|
||||
scope.expr_scopes.entries(scope.scope_id).iter().for_each(|e| {
|
||||
f(e.name().clone(), ScopeDef::Local(e.pat()));
|
||||
});
|
||||
|
@ -1,4 +1,4 @@
|
||||
//! Completes lifetimes.
|
||||
//! Completes lifetimes and labels.
|
||||
use hir::ScopeDef;
|
||||
|
||||
use crate::{completions::Completions, context::CompletionContext};
|
||||
@ -29,6 +29,18 @@ pub(crate) fn complete_lifetime(acc: &mut Completions, ctx: &CompletionContext)
|
||||
}
|
||||
}
|
||||
|
||||
/// Completes labels.
|
||||
pub(crate) fn complete_label(acc: &mut Completions, ctx: &CompletionContext) {
|
||||
if !ctx.is_label_ref {
|
||||
return;
|
||||
}
|
||||
ctx.scope.process_all_names(&mut |name, res| {
|
||||
if let ScopeDef::Label(_) = res {
|
||||
acc.add_resolution(ctx, name.to_string(), &res);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use expect_test::{expect, Expect};
|
||||
@ -178,4 +190,96 @@ fn foo<'footime, 'lifetime: 'a$0>() {}
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn complete_label_in_loop() {
|
||||
check(
|
||||
r#"
|
||||
fn foo() {
|
||||
'foop: loop {
|
||||
break '$0
|
||||
}
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
lb 'foop
|
||||
"#]],
|
||||
);
|
||||
check(
|
||||
r#"
|
||||
fn foo() {
|
||||
'foop: loop {
|
||||
continue '$0
|
||||
}
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
lb 'foop
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn complete_label_in_block_nested() {
|
||||
check(
|
||||
r#"
|
||||
fn foo() {
|
||||
'foop: {
|
||||
'baap: {
|
||||
break '$0
|
||||
}
|
||||
}
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
lb 'baap
|
||||
lb 'foop
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn complete_label_in_loop_with_value() {
|
||||
check(
|
||||
r#"
|
||||
fn foo() {
|
||||
'foop: loop {
|
||||
break '$0 i32;
|
||||
}
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
lb 'foop
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn complete_label_in_while_cond() {
|
||||
check(
|
||||
r#"
|
||||
fn foo() {
|
||||
'outer: while { 'inner: loop { break '$0 } } {}
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
lb 'inner
|
||||
lb 'outer
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn complete_label_in_for_iterable() {
|
||||
check(
|
||||
r#"
|
||||
fn foo() {
|
||||
'outer: for _ in [{ 'inner: loop { break '$0 } }] {}
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
lb 'inner
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -53,6 +53,7 @@ pub(crate) struct CompletionContext<'a> {
|
||||
/// FIXME: `ActiveParameter` is string-based, which is very very wrong
|
||||
pub(super) active_parameter: Option<ActiveParameter>,
|
||||
pub(super) is_param: bool,
|
||||
pub(super) is_label_ref: bool,
|
||||
/// If a name-binding or reference to a const in a pattern.
|
||||
/// Irrefutable patterns (like let) are excluded.
|
||||
pub(super) is_pat_binding_or_const: bool,
|
||||
@ -155,6 +156,7 @@ impl<'a> CompletionContext<'a> {
|
||||
record_field_syntax: None,
|
||||
impl_def: None,
|
||||
active_parameter: ActiveParameter::at(db, position),
|
||||
is_label_ref: false,
|
||||
is_param: false,
|
||||
is_pat_binding_or_const: false,
|
||||
is_irrefutable_pat_binding: false,
|
||||
@ -468,12 +470,24 @@ impl<'a> CompletionContext<'a> {
|
||||
) {
|
||||
self.lifetime_syntax =
|
||||
find_node_at_offset(original_file, lifetime.syntax().text_range().start());
|
||||
if lifetime.syntax().parent().map_or(false, |p| p.kind() != syntax::SyntaxKind::ERROR) {
|
||||
self.lifetime_allowed = true;
|
||||
}
|
||||
if let Some(_) = lifetime.syntax().parent().and_then(ast::LifetimeParam::cast) {
|
||||
self.lifetime_param_syntax =
|
||||
self.sema.find_node_at_offset_with_macros(original_file, offset);
|
||||
if let Some(parent) = lifetime.syntax().parent() {
|
||||
if parent.kind() == syntax::SyntaxKind::ERROR {
|
||||
return;
|
||||
}
|
||||
|
||||
match_ast! {
|
||||
match parent {
|
||||
ast::LifetimeParam(_it) => {
|
||||
self.lifetime_allowed = true;
|
||||
self.lifetime_param_syntax =
|
||||
self.sema.find_node_at_offset_with_macros(original_file, offset);
|
||||
},
|
||||
ast::BreakExpr(_it) => self.is_label_ref = true,
|
||||
ast::ContinueExpr(_it) => self.is_label_ref = true,
|
||||
ast::Label(_it) => (),
|
||||
_ => self.lifetime_allowed = true,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -131,6 +131,7 @@ pub fn completions(
|
||||
completions::mod_::complete_mod(&mut acc, &ctx);
|
||||
completions::flyimport::import_on_the_fly(&mut acc, &ctx);
|
||||
completions::lifetime::complete_lifetime(&mut acc, &ctx);
|
||||
completions::lifetime::complete_label(&mut acc, &ctx);
|
||||
|
||||
Some(acc)
|
||||
}
|
||||
|
@ -219,6 +219,7 @@ impl<'a> Render<'a> {
|
||||
hir::GenericParam::ConstParam(_) => SymbolKind::ConstParam,
|
||||
}),
|
||||
ScopeDef::Local(..) => CompletionItemKind::SymbolKind(SymbolKind::Local),
|
||||
ScopeDef::Label(..) => CompletionItemKind::SymbolKind(SymbolKind::Label),
|
||||
ScopeDef::AdtSelfType(..) | ScopeDef::ImplSelfType(..) => {
|
||||
CompletionItemKind::SymbolKind(SymbolKind::SelfParam)
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user