From cbd325707bc44c08f60ee3312af815cfb96ee86a Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Sun, 21 Mar 2021 00:59:45 +0100 Subject: Track labels in scopes --- crates/hir/src/lib.rs | 1 + crates/hir/src/semantics.rs | 4 +++ crates/hir_def/src/body/scope.rs | 57 +++++++++++++++++++++++++++++++++------- crates/hir_def/src/resolver.rs | 6 ++++- 4 files changed, 57 insertions(+), 11 deletions(-) diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index 30e577671..e34be7e42 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs @@ -2199,6 +2199,7 @@ pub enum ScopeDef { ImplSelfType(Impl), AdtSelfType(Adt), Local(Local), + Label(Label), Unknown, } diff --git a/crates/hir/src/semantics.rs b/crates/hir/src/semantics.rs index 15651bb22..1198e3f0b 100644 --- a/crates/hir/src/semantics.rs +++ b/crates/hir/src/semantics.rs @@ -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) }) diff --git a/crates/hir_def/src/body/scope.rs b/crates/hir_def/src/body/scope.rs index 1bbb54fc6..7f0d8f915 100644 --- a/crates/hir_def/src/body/scope.rs +++ b/crates/hir_def/src/body/scope.rs @@ -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, block: Option, + label: Option<(LabelId, Name)>, entries: Vec, } @@ -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) -> impl Iterator + '_ { 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 } => { + compute_expr_scopes(*condition, body, scopes, scope); + let scope = scopes.new_labeled_scope(scope, make_label(label)); + 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); diff --git a/crates/hir_def/src/resolver.rs b/crates/hir_def/src/resolver.rs index 42736171e..4a2d1c087 100644 --- a/crates/hir_def/src/resolver.rs +++ b/crates/hir_def/src/resolver.rs @@ -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())); }); -- cgit v1.2.3 From 62a4677dbc6cf1c90e4558c3c73fef201a0d1080 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Sun, 21 Mar 2021 01:00:09 +0100 Subject: Add label completion --- crates/ide_completion/src/completions/lifetime.rs | 77 ++++++++++++++++++++++- crates/ide_completion/src/context.rs | 28 +++++++-- crates/ide_completion/src/lib.rs | 1 + crates/ide_completion/src/render.rs | 1 + 4 files changed, 100 insertions(+), 7 deletions(-) diff --git a/crates/ide_completion/src/completions/lifetime.rs b/crates/ide_completion/src/completions/lifetime.rs index 74eb23360..07be28e9c 100644 --- a/crates/ide_completion/src/completions/lifetime.rs +++ b/crates/ide_completion/src/completions/lifetime.rs @@ -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,67 @@ 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 + "#]], + ); + } } diff --git a/crates/ide_completion/src/context.rs b/crates/ide_completion/src/context.rs index 4c2b31084..6cb7e5264 100644 --- a/crates/ide_completion/src/context.rs +++ b/crates/ide_completion/src/context.rs @@ -53,6 +53,7 @@ pub(crate) struct CompletionContext<'a> { /// FIXME: `ActiveParameter` is string-based, which is very very wrong pub(super) active_parameter: Option, 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,26 @@ 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; + } + + if parent.kind() != syntax::SyntaxKind::LABEL { + 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, + } + } + } } } diff --git a/crates/ide_completion/src/lib.rs b/crates/ide_completion/src/lib.rs index 7a0eb6a96..995970fca 100644 --- a/crates/ide_completion/src/lib.rs +++ b/crates/ide_completion/src/lib.rs @@ -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) } diff --git a/crates/ide_completion/src/render.rs b/crates/ide_completion/src/render.rs index 2b6e9ebd1..23e00aa47 100644 --- a/crates/ide_completion/src/render.rs +++ b/crates/ide_completion/src/render.rs @@ -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) } -- cgit v1.2.3 From 64957acb5f359763395a54e314d1f5d5cfc6ccf3 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Sun, 21 Mar 2021 01:10:59 +0100 Subject: Fix incorrect scoping in while expressions --- crates/hir_def/src/body/scope.rs | 2 +- crates/ide_completion/src/completions/lifetime.rs | 29 +++++++++++++++++++++++ crates/ide_completion/src/context.rs | 24 +++++++++---------- 3 files changed, 41 insertions(+), 14 deletions(-) diff --git a/crates/hir_def/src/body/scope.rs b/crates/hir_def/src/body/scope.rs index 7f0d8f915..bd7005ca6 100644 --- a/crates/hir_def/src/body/scope.rs +++ b/crates/hir_def/src/body/scope.rs @@ -188,8 +188,8 @@ fn compute_expr_scopes(expr: ExprId, body: &Body, scopes: &mut ExprScopes, scope compute_expr_scopes(*body_expr, body, scopes, scope); } Expr::While { condition, body: body_expr, label } => { - compute_expr_scopes(*condition, body, scopes, scope); 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 } => { diff --git a/crates/ide_completion/src/completions/lifetime.rs b/crates/ide_completion/src/completions/lifetime.rs index 07be28e9c..628c1fb9b 100644 --- a/crates/ide_completion/src/completions/lifetime.rs +++ b/crates/ide_completion/src/completions/lifetime.rs @@ -253,4 +253,33 @@ fn foo() { "#]], ); } + + #[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 + "#]], + ); + } } diff --git a/crates/ide_completion/src/context.rs b/crates/ide_completion/src/context.rs index 6cb7e5264..67e2d6f6c 100644 --- a/crates/ide_completion/src/context.rs +++ b/crates/ide_completion/src/context.rs @@ -475,19 +475,17 @@ impl<'a> CompletionContext<'a> { return; } - if parent.kind() != syntax::SyntaxKind::LABEL { - 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, - } + 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, } } } -- cgit v1.2.3