Repository: rust-analyzer/rust-analyzer
Pull request #8127 (2021-03-21 00:02:45)

Add label completion


Patch:
diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs
index 30e5776712..e34be7e42f 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 15651bb222..1198e3f0be 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 1bbb54fc6d..bd7005ca69 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<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);
diff --git a/crates/hir_def/src/resolver.rs b/crates/hir_def/src/resolver.rs
index 42736171eb..4a2d1c0875 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()));
                 });
diff --git a/crates/ide_completion/src/completions/lifetime.rs b/crates/ide_completion/src/completions/lifetime.rs
index 74eb233602..628c1fb9b6 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,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
+            "#]],
+        );
+    }
 }
diff --git a/crates/ide_completion/src/context.rs b/crates/ide_completion/src/context.rs
index 4c2b31084d..67e2d6f6c2 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<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,
+                }
+            }
         }
     }
 
diff --git a/crates/ide_completion/src/lib.rs b/crates/ide_completion/src/lib.rs
index 7a0eb6a966..995970fcab 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 2b6e9ebd1f..23e00aa471 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)
             }
diff --git a/crates/mbe/src/expander/matcher.rs b/crates/mbe/src/expander/matcher.rs
index 1682b21b08..b6782b4ba8 100644
--- a/crates/mbe/src/expander/matcher.rs
+++ b/crates/mbe/src/expander/matcher.rs
@@ -762,7 +762,7 @@ impl<'a> TtIter<'a> {
     fn expect_separator(&mut self, separator: &Separator, idx: usize) -> bool {
         let mut fork = self.clone();
         let ok = match separator {
-            Separator::Ident(lhs) if idx == 0 => match fork.expect_ident_or_underscore() {
+            Separator::Ident(lhs) if idx == 0 => match fork.expect_ident() {
                 Ok(rhs) => rhs.text == lhs.text,
                 _ => false,
             },
@@ -852,7 +852,7 @@ impl<'a> TtIter<'a> {
         if punct.char != '\'' {
             return Err(());
         }
-        let ident = self.expect_ident_or_underscore()?;
+        let ident = self.expect_ident()?;
 
         Ok(tt::Subtree {
             delimiter: None,
diff --git a/crates/mbe/src/parser.rs b/crates/mbe/src/parser.rs
index c88387653d..7b5b8ec164 100644
--- a/crates/mbe/src/parser.rs
+++ b/crates/mbe/src/parser.rs
@@ -177,8 +177,16 @@ fn next_op<'a>(first: &tt::TokenTree, src: &mut TtIter<'a>, mode: Mode) -> Resul
                     Op::Repeat { tokens: MetaTemplate(tokens), separator, kind }
                 }
                 tt::TokenTree::Leaf(leaf) => match leaf {
-                    tt::Leaf::Punct(_) => {
-                        return Err(ParseError::Expected("ident".to_string()));
+                    tt::Leaf::Punct(punct) => {
+                        static UNDERSCORE: SmolStr = SmolStr::new_inline("_");
+
+                        if punct.char != '_' {
+                            return Err(ParseError::Expected("_".to_string()));
+                        }
+                        let name = UNDERSCORE.clone();
+                        let kind = eat_fragment_kind(src, mode)?;
+                        let id = punct.id;
+                        Op::Var { name, kind, id }
                     }
                     tt::Leaf::Ident(ident) if ident.text == "crate" => {
                         // We simply produce identifier `$crate` here. And it will be resolved when lowering ast to Path.
diff --git a/crates/mbe/src/subtree_source.rs b/crates/mbe/src/subtree_source.rs
index a05cab0f37..d7433bd353 100644
--- a/crates/mbe/src/subtree_source.rs
+++ b/crates/mbe/src/subtree_source.rs
@@ -150,7 +150,6 @@ fn convert_ident(ident: &tt::Ident) -> TtToken {
     let kind = match ident.text.as_ref() {
         "true" => T![true],
         "false" => T![false],
-        "_" => UNDERSCORE,
         i if i.starts_with('\'') => LIFETIME_IDENT,
         _ => SyntaxKind::from_keyword(ident.text.as_str()).unwrap_or(IDENT),
     };
diff --git a/crates/mbe/src/syntax_bridge.rs b/crates/mbe/src/syntax_bridge.rs
index 8bba3d3d52..85163c4b32 100644
--- a/crates/mbe/src/syntax_bridge.rs
+++ b/crates/mbe/src/syntax_bridge.rs
@@ -350,7 +350,7 @@ trait TokenConvertor {
             return;
         }
 
-        result.push(if k.is_punct() && k != UNDERSCORE {
+        result.push(if k.is_punct() {
             assert_eq!(range.len(), TextSize::of('.'));
             let delim = match k {
                 T!['('] => Some((tt::DelimiterKind::Parenthesis, T![')'])),
@@ -395,9 +395,7 @@ trait TokenConvertor {
                     {
                         tt::Spacing::Alone
                     }
-                    Some(next) if next.kind().is_punct() && next.kind() != UNDERSCORE => {
-                        tt::Spacing::Joint
-                    }
+                    Some(next) if next.kind().is_punct() => tt::Spacing::Joint,
                     _ => tt::Spacing::Alone,
                 };
                 let char = match token.to_char() {
@@ -417,7 +415,6 @@ trait TokenConvertor {
             let leaf: tt::Leaf = match k {
                 T![true] | T![false] => make_leaf!(Ident),
                 IDENT => make_leaf!(Ident),
-                UNDERSCORE => make_leaf!(Ident),
                 k if k.is_keyword() => make_leaf!(Ident),
                 k if k.is_literal() => make_leaf!(Literal),
                 LIFETIME_IDENT => {
diff --git a/crates/mbe/src/tt_iter.rs b/crates/mbe/src/tt_iter.rs
index 319a40f2aa..a362d31fcf 100644
--- a/crates/mbe/src/tt_iter.rs
+++ b/crates/mbe/src/tt_iter.rs
@@ -49,13 +49,6 @@ impl<'a> TtIter<'a> {
     }
 
     pub(crate) fn expect_ident(&mut self) -> Result<&'a tt::Ident, ()> {
-        match self.expect_leaf()? {
-            tt::Leaf::Ident(it) if it.text != "_" => Ok(it),
-            _ => Err(()),
-        }
-    }
-
-    pub(crate) fn expect_ident_or_underscore(&mut self) -> Result<&'a tt::Ident, ()> {
         match self.expect_leaf()? {
             tt::Leaf::Ident(it) => Ok(it),
             _ => Err(()),
diff --git a/crates/proc_macro_srv/src/rustc_server.rs b/crates/proc_macro_srv/src/rustc_server.rs
index c147484c0e..ceefd187d2 100644
--- a/crates/proc_macro_srv/src/rustc_server.rs
+++ b/crates/proc_macro_srv/src/rustc_server.rs
@@ -805,14 +805,5 @@ mod tests {
         let t2 = TokenStream::from_str("(a);").unwrap();
         assert_eq!(t2.token_trees.len(), 2);
         assert_eq!(t2.token_trees[0], subtree_paren_a);
-
-        let underscore = TokenStream::from_str("_").unwrap();
-        assert_eq!(
-            underscore.token_trees[0],
-            tt::TokenTree::Leaf(tt::Leaf::Ident(tt::Ident {
-                text: "_".into(),
-                id: tt::TokenId::unspecified(),
-            }))
-        );
     }
 }


Test patch:
diff --git a/crates/mbe/src/tests/expand.rs b/crates/mbe/src/tests/expand.rs
index 2cce62781a..9dd8ff75bc 100644
--- a/crates/mbe/src/tests/expand.rs
+++ b/crates/mbe/src/tests/expand.rs
@@ -1079,12 +1079,6 @@ macro_rules! q {
     .assert_expand_items(r#"q![_]"#, r#"0"#);
 }
 
-#[test]
-fn test_underscore_lifetime() {
-    parse_macro(r#"macro_rules! q { ($a:lifetime) => {0}; }"#)
-        .assert_expand_items(r#"q!['_]"#, r#"0"#);
-}
-
 #[test]
 fn test_vertical_bar_with_pat() {
     parse_macro(
diff --git a/crates/mbe/src/tests/rule.rs b/crates/mbe/src/tests/rule.rs
index bf48112b3a..07277966d4 100644
--- a/crates/mbe/src/tests/rule.rs
+++ b/crates/mbe/src/tests/rule.rs
@@ -12,9 +12,6 @@ fn test_valid_arms() {
     }
 
     check("($i:ident) => ()");
-    check("($(x),*) => ()");
-    check("($(x)_*) => ()");
-    check("($(x)i*) => ()");
     check("($($i:ident)*) => ($_)");
     check("($($true:ident)*) => ($true)");
     check("($($false:ident)*) => ($false)");
@@ -35,7 +32,6 @@ fn test_invalid_arms() {
 
     check("($i) => ($i)", ParseError::UnexpectedToken("bad fragment specifier 1".into()));
     check("($i:) => ($i)", ParseError::UnexpectedToken("bad fragment specifier 1".into()));
-    check("($i:_) => ()", ParseError::UnexpectedToken("bad fragment specifier 1".into()));
 }
 
 fn parse_macro_arm(arm_definition: &str) -> Result<crate::MacroRules, ParseError> {