Repository: rust-analyzer/rust-analyzer
Pull request #4670 (2020-05-31 13:03:40)

Move assists documentation into the manual


bors r+
🤖


Patch:
diff --git a/crates/ra_hir_def/src/body/lower.rs b/crates/ra_hir_def/src/body/lower.rs
index f159f80af0..905c0cf5da 100644
--- a/crates/ra_hir_def/src/body/lower.rs
+++ b/crates/ra_hir_def/src/body/lower.rs
@@ -134,7 +134,7 @@ impl ExprCollector<'_> {
         self.make_expr(expr, Err(SyntheticSyntax))
     }
     fn empty_block(&mut self) -> ExprId {
-        self.alloc_expr_desugared(Expr::Block { statements: Vec::new(), tail: None, label: None })
+        self.alloc_expr_desugared(Expr::Block { statements: Vec::new(), tail: None })
     }
     fn missing_expr(&mut self) -> ExprId {
         self.alloc_expr_desugared(Expr::Missing)
@@ -215,16 +215,7 @@ impl ExprCollector<'_> {
             ast::Expr::BlockExpr(e) => self.collect_block(e),
             ast::Expr::LoopExpr(e) => {
                 let body = self.collect_block_opt(e.loop_body());
-                self.alloc_expr(
-                    Expr::Loop {
-                        body,
-                        label: e
-                            .label()
-                            .and_then(|l| l.lifetime_token())
-                            .map(|l| Name::new_lifetime(&l)),
-                    },
-                    syntax_ptr,
-                )
+                self.alloc_expr(Expr::Loop { body }, syntax_ptr)
             }
             ast::Expr::WhileExpr(e) => {
                 let body = self.collect_block_opt(e.loop_body());
@@ -239,56 +230,25 @@ impl ExprCollector<'_> {
                             let pat = self.collect_pat(pat);
                             let match_expr = self.collect_expr_opt(condition.expr());
                             let placeholder_pat = self.missing_pat();
-                            let break_ =
-                                self.alloc_expr_desugared(Expr::Break { expr: None, label: None });
+                            let break_ = self.alloc_expr_desugared(Expr::Break { expr: None });
                             let arms = vec![
                                 MatchArm { pat, expr: body, guard: None },
                                 MatchArm { pat: placeholder_pat, expr: break_, guard: None },
                             ];
                             let match_expr =
                                 self.alloc_expr_desugared(Expr::Match { expr: match_expr, arms });
-                            return self.alloc_expr(
-                                Expr::Loop {
-                                    body: match_expr,
-                                    label: e
-                                        .label()
-                                        .and_then(|l| l.lifetime_token())
-                                        .map(|l| Name::new_lifetime(&l)),
-                                },
-                                syntax_ptr,
-                            );
+                            return self.alloc_expr(Expr::Loop { body: match_expr }, syntax_ptr);
                         }
                     },
                 };
 
-                self.alloc_expr(
-                    Expr::While {
-                        condition,
-                        body,
-                        label: e
-                            .label()
-                            .and_then(|l| l.lifetime_token())
-                            .map(|l| Name::new_lifetime(&l)),
-                    },
-                    syntax_ptr,
-                )
+                self.alloc_expr(Expr::While { condition, body }, syntax_ptr)
             }
             ast::Expr::ForExpr(e) => {
                 let iterable = self.collect_expr_opt(e.iterable());
                 let pat = self.collect_pat_opt(e.pat());
                 let body = self.collect_block_opt(e.loop_body());
-                self.alloc_expr(
-                    Expr::For {
-                        iterable,
-                        pat,
-                        body,
-                        label: e
-                            .label()
-                            .and_then(|l| l.lifetime_token())
-                            .map(|l| Name::new_lifetime(&l)),
-                    },
-                    syntax_ptr,
-                )
+                self.alloc_expr(Expr::For { iterable, pat, body }, syntax_ptr)
             }
             ast::Expr::CallExpr(e) => {
                 let callee = self.collect_expr_opt(e.expr());
@@ -341,16 +301,13 @@ impl ExprCollector<'_> {
                     .unwrap_or(Expr::Missing);
                 self.alloc_expr(path, syntax_ptr)
             }
-            ast::Expr::ContinueExpr(e) => self.alloc_expr(
-                Expr::Continue { label: e.lifetime_token().map(|l| Name::new_lifetime(&l)) },
-                syntax_ptr,
-            ),
+            ast::Expr::ContinueExpr(_e) => {
+                // FIXME: labels
+                self.alloc_expr(Expr::Continue, syntax_ptr)
+            }
             ast::Expr::BreakExpr(e) => {
                 let expr = e.expr().map(|e| self.collect_expr(e));
-                self.alloc_expr(
-                    Expr::Break { expr, label: e.lifetime_token().map(|l| Name::new_lifetime(&l)) },
-                    syntax_ptr,
-                )
+                self.alloc_expr(Expr::Break { expr }, syntax_ptr)
             }
             ast::Expr::ParenExpr(e) => {
                 let inner = self.collect_expr_opt(e.expr());
@@ -572,8 +529,7 @@ impl ExprCollector<'_> {
             })
             .collect();
         let tail = block.expr().map(|e| self.collect_expr(e));
-        let label = block.label().and_then(|l| l.lifetime_token()).map(|t| Name::new_lifetime(&t));
-        self.alloc_expr(Expr::Block { statements, tail, label }, syntax_node_ptr)
+        self.alloc_expr(Expr::Block { statements, tail }, syntax_node_ptr)
     }
 
     fn collect_block_items(&mut self, block: &ast::BlockExpr) {
diff --git a/crates/ra_hir_def/src/body/scope.rs b/crates/ra_hir_def/src/body/scope.rs
index e48ff38f96..09e92b74e1 100644
--- a/crates/ra_hir_def/src/body/scope.rs
+++ b/crates/ra_hir_def/src/body/scope.rs
@@ -138,10 +138,10 @@ fn compute_block_scopes(
 fn compute_expr_scopes(expr: ExprId, body: &Body, scopes: &mut ExprScopes, scope: ScopeId) {
     scopes.set_scope(expr, scope);
     match &body[expr] {
-        Expr::Block { statements, tail, .. } => {
+        Expr::Block { statements, tail } => {
             compute_block_scopes(&statements, *tail, body, scopes, scope);
         }
-        Expr::For { iterable, pat, body: body_expr, .. } => {
+        Expr::For { iterable, pat, body: body_expr } => {
             compute_expr_scopes(*iterable, body, scopes, scope);
             let scope = scopes.new_scope(scope);
             scopes.add_bindings(body, scope, *pat);
diff --git a/crates/ra_hir_def/src/expr.rs b/crates/ra_hir_def/src/expr.rs
index ca49b26d15..f25c6f9580 100644
--- a/crates/ra_hir_def/src/expr.rs
+++ b/crates/ra_hir_def/src/expr.rs
@@ -52,22 +52,18 @@ pub enum Expr {
     Block {
         statements: Vec<Statement>,
         tail: Option<ExprId>,
-        label: Option<Name>,
     },
     Loop {
         body: ExprId,
-        label: Option<Name>,
     },
     While {
         condition: ExprId,
         body: ExprId,
-        label: Option<Name>,
     },
     For {
         iterable: ExprId,
         pat: PatId,
         body: ExprId,
-        label: Option<Name>,
     },
     Call {
         callee: ExprId,
@@ -83,12 +79,9 @@ pub enum Expr {
         expr: ExprId,
         arms: Vec<MatchArm>,
     },
-    Continue {
-        label: Option<Name>,
-    },
+    Continue,
     Break {
         expr: Option<ExprId>,
-        label: Option<Name>,
     },
     Return {
         expr: Option<ExprId>,
@@ -232,7 +225,7 @@ impl Expr {
                     f(*else_branch);
                 }
             }
-            Expr::Block { statements, tail, .. } => {
+            Expr::Block { statements, tail } => {
                 for stmt in statements {
                     match stmt {
                         Statement::Let { initializer, .. } => {
@@ -248,8 +241,8 @@ impl Expr {
                 }
             }
             Expr::TryBlock { body } => f(*body),
-            Expr::Loop { body, .. } => f(*body),
-            Expr::While { condition, body, .. } => {
+            Expr::Loop { body } => f(*body),
+            Expr::While { condition, body } => {
                 f(*condition);
                 f(*body);
             }
@@ -275,8 +268,8 @@ impl Expr {
                     f(arm.expr);
                 }
             }
-            Expr::Continue { .. } => {}
-            Expr::Break { expr, .. } | Expr::Return { expr } => {
+            Expr::Continue => {}
+            Expr::Break { expr } | Expr::Return { expr } => {
                 if let Some(expr) = expr {
                     f(*expr);
                 }
diff --git a/crates/ra_hir_expand/src/name.rs b/crates/ra_hir_expand/src/name.rs
index ea495cb11a..fecce224ee 100644
--- a/crates/ra_hir_expand/src/name.rs
+++ b/crates/ra_hir_expand/src/name.rs
@@ -37,11 +37,6 @@ impl Name {
         Name(Repr::TupleField(idx))
     }
 
-    pub fn new_lifetime(lt: &ra_syntax::SyntaxToken) -> Name {
-        assert!(lt.kind() == ra_syntax::SyntaxKind::LIFETIME);
-        Name(Repr::Text(lt.text().clone()))
-    }
-
     /// Shortcut to create inline plain text name
     const fn new_inline_ascii(text: &[u8]) -> Name {
         Name::new_text(SmolStr::new_inline_from_ascii(text.len(), text))
diff --git a/crates/ra_hir_ty/src/infer.rs b/crates/ra_hir_ty/src/infer.rs
index dc77e88e50..957d6e0b57 100644
--- a/crates/ra_hir_ty/src/infer.rs
+++ b/crates/ra_hir_ty/src/infer.rs
@@ -219,17 +219,6 @@ struct InferenceContext<'a> {
 struct BreakableContext {
     pub may_break: bool,
     pub break_ty: Ty,
-    pub label: Option<name::Name>,
-}
-
-fn find_breakable<'c>(
-    ctxs: &'c mut [BreakableContext],
-    label: Option<&name::Name>,
-) -> Option<&'c mut BreakableContext> {
-    match label {
-        Some(_) => ctxs.iter_mut().rev().find(|ctx| ctx.label.as_ref() == label),
-        None => ctxs.last_mut(),
-    }
 }
 
 impl<'a> InferenceContext<'a> {
diff --git a/crates/ra_hir_ty/src/infer/expr.rs b/crates/ra_hir_ty/src/infer/expr.rs
index 4a98e2debf..78084cb573 100644
--- a/crates/ra_hir_ty/src/infer/expr.rs
+++ b/crates/ra_hir_ty/src/infer/expr.rs
@@ -22,8 +22,8 @@ use crate::{
 };
 
 use super::{
-    find_breakable, BindingMode, BreakableContext, Diverges, Expectation, InferenceContext,
-    InferenceDiagnostic, TypeMismatch,
+    BindingMode, BreakableContext, Diverges, Expectation, InferenceContext, InferenceDiagnostic,
+    TypeMismatch,
 };
 
 impl<'a> InferenceContext<'a> {
@@ -86,20 +86,16 @@ impl<'a> InferenceContext<'a> {
 
                 self.coerce_merge_branch(&then_ty, &else_ty)
             }
-            Expr::Block { statements, tail, .. } => {
-                // FIXME: Breakable block inference
-                self.infer_block(statements, *tail, expected)
-            }
+            Expr::Block { statements, tail } => self.infer_block(statements, *tail, expected),
             Expr::TryBlock { body } => {
                 let _inner = self.infer_expr(*body, expected);
                 // FIXME should be std::result::Result<{inner}, _>
                 Ty::Unknown
             }
-            Expr::Loop { body, label } => {
+            Expr::Loop { body } => {
                 self.breakables.push(BreakableContext {
                     may_break: false,
                     break_ty: self.table.new_type_var(),
-                    label: label.clone(),
                 });
                 self.infer_expr(*body, &Expectation::has_type(Ty::unit()));
 
@@ -114,12 +110,8 @@ impl<'a> InferenceContext<'a> {
                     Ty::simple(TypeCtor::Never)
                 }
             }
-            Expr::While { condition, body, label } => {
-                self.breakables.push(BreakableContext {
-                    may_break: false,
-                    break_ty: Ty::Unknown,
-                    label: label.clone(),
-                });
+            Expr::While { condition, body } => {
+                self.breakables.push(BreakableContext { may_break: false, break_ty: Ty::Unknown });
                 // while let is desugared to a match loop, so this is always simple while
                 self.infer_expr(*condition, &Expectation::has_type(Ty::simple(TypeCtor::Bool)));
                 self.infer_expr(*body, &Expectation::has_type(Ty::unit()));
@@ -128,14 +120,10 @@ impl<'a> InferenceContext<'a> {
                 self.diverges = Diverges::Maybe;
                 Ty::unit()
             }
-            Expr::For { iterable, body, pat, label } => {
+            Expr::For { iterable, body, pat } => {
                 let iterable_ty = self.infer_expr(*iterable, &Expectation::none());
 
-                self.breakables.push(BreakableContext {
-                    may_break: false,
-                    break_ty: Ty::Unknown,
-                    label: label.clone(),
-                });
+                self.breakables.push(BreakableContext { may_break: false, break_ty: Ty::Unknown });
                 let pat_ty =
                     self.resolve_associated_type(iterable_ty, self.resolve_into_iter_item());
 
@@ -248,24 +236,23 @@ impl<'a> InferenceContext<'a> {
                 let resolver = resolver_for_expr(self.db.upcast(), self.owner, tgt_expr);
                 self.infer_path(&resolver, p, tgt_expr.into()).unwrap_or(Ty::Unknown)
             }
-            Expr::Continue { .. } => Ty::simple(TypeCtor::Never),
-            Expr::Break { expr, label } => {
+            Expr::Continue => Ty::simple(TypeCtor::Never),
+            Expr::Break { expr } => {
                 let val_ty = if let Some(expr) = expr {
                     self.infer_expr(*expr, &Expectation::none())
                 } else {
                     Ty::unit()
                 };
 
-                let last_ty =
-                    if let Some(ctxt) = find_breakable(&mut self.breakables, label.as_ref()) {
-                        ctxt.break_ty.clone()
-                    } else {
-                        Ty::Unknown
-                    };
+                let last_ty = if let Some(ctxt) = self.breakables.last() {
+                    ctxt.break_ty.clone()
+                } else {
+                    Ty::Unknown
+                };
 
                 let merged_type = self.coerce_merge_branch(&last_ty, &val_ty);
 
-                if let Some(ctxt) = find_breakable(&mut self.breakables, label.as_ref()) {
+                if let Some(ctxt) = self.breakables.last_mut() {
                     ctxt.break_ty = merged_type;
                     ctxt.may_break = true;
                 } else {
diff --git a/crates/ra_syntax/src/ast/generated/nodes.rs b/crates/ra_syntax/src/ast/generated/nodes.rs
index cb430ca013..255402fbce 100644
--- a/crates/ra_syntax/src/ast/generated/nodes.rs
+++ b/crates/ra_syntax/src/ast/generated/nodes.rs
@@ -1081,7 +1081,6 @@ pub struct BlockExpr {
 impl ast::AttrsOwner for BlockExpr {}
 impl ast::ModuleItemOwner for BlockExpr {}
 impl BlockExpr {
-    pub fn label(&self) -> Option<Label> { support::child(&self.syntax) }
     pub fn l_curly_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T!['{']) }
     pub fn statements(&self) -> AstChildren<Stmt> { support::children(&self.syntax) }
     pub fn expr(&self) -> Option<Expr> { support::child(&self.syntax) }
diff --git a/docs/user/assists.md b/docs/user/generated_assists.adoc
similarity index 77%
rename from docs/user/assists.md
rename to docs/user/generated_assists.adoc
index 04387e3b0e..b8cdfb1cdf 100644
--- a/docs/user/assists.md
+++ b/docs/user/generated_assists.adoc
@@ -1,18 +1,16 @@
-# Assists
-
-Cursor position or selection is signified by `┃` character.
-
-
-## `add_custom_impl`
+[discrete]
+=== `add_custom_impl`
 
 Adds impl block for derived trait.
 
+.Before
 ```rust
-// BEFORE
 #[derive(Deb┃ug, Display)]
 struct S;
+```
 
-// AFTER
+.After
+```rust
 #[derive(Display)]
 struct S;
 
@@ -21,18 +19,22 @@ impl Debug for S {
 }
 ```
 
-## `add_derive`
+
+[discrete]
+=== `add_derive`
 
 Adds a new `#[derive()]` clause to a struct or enum.
 
+.Before
 ```rust
-// BEFORE
 struct Point {
     x: u32,
     y: u32,┃
 }
+```
 
-// AFTER
+.After
+```rust
 #[derive($0)]
 struct Point {
     x: u32,
@@ -40,31 +42,39 @@ struct Point {
 }
 ```
 
-## `add_explicit_type`
+
+[discrete]
+=== `add_explicit_type`
 
 Specify type for a let binding.
 
+.Before
 ```rust
-// BEFORE
 fn main() {
     let x┃ = 92;
 }
+```
 
-// AFTER
+.After
+```rust
 fn main() {
     let x: i32 = 92;
 }
 ```
 
-## `add_from_impl_for_enum`
+
+[discrete]
+=== `add_from_impl_for_enum`
 
 Adds a From impl for an enum variant with one tuple field.
 
+.Before
 ```rust
-// BEFORE
 enum A { ┃One(u32) }
+```
 
-// AFTER
+.After
+```rust
 enum A { One(u32) }
 
 impl From<u32> for A {
@@ -74,20 +84,24 @@ impl From<u32> for A {
 }
 ```
 
-## `add_function`
+
+[discrete]
+=== `add_function`
 
 Adds a stub function with a signature matching the function under the cursor.
 
+.Before
 ```rust
-// BEFORE
 struct Baz;
 fn baz() -> Baz { Baz }
 fn foo() {
     bar┃("", baz());
 }
 
+```
 
-// AFTER
+.After
+```rust
 struct Baz;
 fn baz() -> Baz { Baz }
 fn foo() {
@@ -100,33 +114,41 @@ fn bar(arg: &str, baz: Baz) {
 
 ```
 
-## `add_hash`
+
+[discrete]
+=== `add_hash`
 
 Adds a hash to a raw string literal.
 
+.Before
 ```rust
-// BEFORE
 fn main() {
     r#"Hello,┃ World!"#;
 }
+```
 
-// AFTER
+.After
+```rust
 fn main() {
     r##"Hello, World!"##;
 }
 ```
 
-## `add_impl`
+
+[discrete]
+=== `add_impl`
 
 Adds a new inherent impl for a type.
 
+.Before
 ```rust
-// BEFORE
 struct Ctx<T: Clone> {
     data: T,┃
 }
+```
 
-// AFTER
+.After
+```rust
 struct Ctx<T: Clone> {
     data: T,
 }
@@ -136,12 +158,14 @@ impl<T: Clone> Ctx<T> {
 }
 ```
 
-## `add_impl_default_members`
+
+[discrete]
+=== `add_impl_default_members`
 
 Adds scaffold for overriding default impl members.
 
+.Before
 ```rust
-// BEFORE
 trait Trait {
     Type X;
     fn foo(&self);
@@ -153,8 +177,10 @@ impl Trait for () {
     fn foo(&self) {}┃
 
 }
+```
 
-// AFTER
+.After
+```rust
 trait Trait {
     Type X;
     fn foo(&self);
@@ -169,12 +195,14 @@ impl Trait for () {
 }
 ```
 
-## `add_impl_missing_members`
+
+[discrete]
+=== `add_impl_missing_members`
 
 Adds scaffold for required impl members.
 
+.Before
 ```rust
-// BEFORE
 trait Trait<T> {
     Type X;
     fn foo(&self) -> T;
@@ -184,8 +212,10 @@ trait Trait<T> {
 impl Trait<u32> for () {┃
 
 }
+```
 
-// AFTER
+.After
+```rust
 trait Trait<T> {
     Type X;
     fn foo(&self) -> T;
@@ -200,17 +230,21 @@ impl Trait<u32> for () {
 }
 ```
 
-## `add_new`
+
+[discrete]
+=== `add_new`
 
 Adds a new inherent impl for a type.
 
+.Before
 ```rust
-// BEFORE
 struct Ctx<T: Clone> {
      data: T,┃
 }
+```
 
-// AFTER
+.After
+```rust
 struct Ctx<T: Clone> {
      data: T,
 }
@@ -221,25 +255,31 @@ impl<T: Clone> Ctx<T> {
 
 ```
 
-## `add_turbo_fish`
+
+[discrete]
+=== `add_turbo_fish`
 
 Adds `::<_>` to a call of a generic method or function.
 
+.Before
 ```rust
-// BEFORE
 fn make<T>() -> T { todo!() }
 fn main() {
     let x = make┃();
 }
+```
 
-// AFTER
+.After
+```rust
 fn make<T>() -> T { todo!() }
 fn main() {
     let x = make::<${0:_}>();
 }
 ```
 
-## `apply_demorgan`
+
+[discrete]
+=== `apply_demorgan`
 
 Apply [De Morgan's law](https://en.wikipedia.org/wiki/De_Morgan%27s_laws).
 This transforms expressions of the form `!l || !r` into `!(l && r)`.
@@ -247,29 +287,35 @@ This also works with `&&`. This assist can only be applied with the cursor
 on either `||` or `&&`, with both operands being a negation of some kind.
 This means something of the form `!x` or `x != y`.
 
+.Before
 ```rust
-// BEFORE
 fn main() {
     if x != 4 ||┃ !y {}
 }
+```
 
-// AFTER
+.After
+```rust
 fn main() {
     if !(x == 4 && y) {}
 }
 ```
 
-## `auto_import`
+
+[discrete]
+=== `auto_import`
 
 If the name is unresolved, provides all possible imports for it.
 
+.Before
 ```rust
-// BEFORE
 fn main() {
     let map = HashMap┃::new();
 }
+```
 
-// AFTER
+.After
+```rust
 use std::collections::HashMap;
 
 fn main() {
@@ -277,12 +323,14 @@ fn main() {
 }
 ```
 
-## `change_lifetime_anon_to_named`
+
+[discrete]
+=== `change_lifetime_anon_to_named`
 
 Change an anonymous lifetime to a named lifetime.
 
+.Before
 ```rust
-// BEFORE
 impl Cursor<'_┃> {
     fn node(self) -> &SyntaxNode {
         match self {
@@ -290,8 +338,10 @@ impl Cursor<'_┃> {
         }
     }
 }
+```
 
-// AFTER
+.After
+```rust
 impl<'a> Cursor<'a> {
     fn node(self) -> &SyntaxNode {
         match self {
@@ -301,44 +351,56 @@ impl<'a> Cursor<'a> {
 }
 ```
 
-## `change_return_type_to_result`
+
+[discrete]
+=== `change_return_type_to_result`
 
 Change the function's return type to Result.
 
+.Before
 ```rust
-// BEFORE
 fn foo() -> i32┃ { 42i32 }
+```
 
-// AFTER
+.After
+```rust
 fn foo() -> Result<i32, ${0:_}> { Ok(42i32) }
 ```
 
-## `change_visibility`
+
+[discrete]
+=== `change_visibility`
 
 Adds or changes existing visibility specifier.
 
+.Before
 ```rust
-// BEFORE
 ┃fn frobnicate() {}
+```
 
-// AFTER
+.After
+```rust
 pub(crate) fn frobnicate() {}
 ```
 
-## `convert_to_guarded_return`
+
+[discrete]
+=== `convert_to_guarded_return`
 
 Replace a large conditional with a guarded return.
 
+.Before
 ```rust
-// BEFORE
 fn main() {
     ┃if cond {
         foo();
         bar();
     }
 }
+```
 
-// AFTER
+.After
+```rust
 fn main() {
     if !cond {
         return;
@@ -348,12 +410,14 @@ fn main() {
 }
 ```
 
-## `fill_match_arms`
+
+[discrete]
+=== `fill_match_arms`
 
 Adds missing clauses to a `match` expression.
 
+.Before
 ```rust
-// BEFORE
 enum Action { Move { distance: u32 }, Stop }
 
 fn handle(action: Action) {
@@ -361,8 +425,10 @@ fn handle(action: Action) {
         ┃
     }
 }
+```
 
-// AFTER
+.After
+```rust
 enum Action { Move { distance: u32 }, Stop }
 
 fn handle(action: Action) {
@@ -373,20 +439,24 @@ fn handle(action: Action) {
 }
 ```
 
-## `fix_visibility`
+
+[discrete]
+=== `fix_visibility`
 
 Makes inaccessible item public.
 
+.Before
 ```rust
-// BEFORE
 mod m {
     fn frobnicate() {}
 }
 fn main() {
     m::frobnicate┃() {}
 }
+```
 
-// AFTER
+.After
+```rust
 mod m {
     $0pub(crate) fn frobnicate() {}
 }
@@ -395,154 +465,192 @@ fn main() {
 }
 ```
 
-## `flip_binexpr`
+
+[discrete]
+=== `flip_binexpr`
 
 Flips operands of a binary expression.
 
+.Before
 ```rust
-// BEFORE
 fn main() {
     let _ = 90 +┃ 2;
 }
+```
 
-// AFTER
+.After
+```rust
 fn main() {
     let _ = 2 + 90;
 }
 ```
 
-## `flip_comma`
+
+[discrete]
+=== `flip_comma`
 
 Flips two comma-separated items.
 
+.Before
 ```rust
-// BEFORE
 fn main() {
     ((1, 2),┃ (3, 4));
 }
+```
 
-// AFTER
+.After
+```rust
 fn main() {
     ((3, 4), (1, 2));
 }
 ```
 
-## `flip_trait_bound`
+
+[discrete]
+=== `flip_trait_bound`
 
 Flips two trait bounds.
 
+.Before
 ```rust
-// BEFORE
 fn foo<T: Clone +┃ Copy>() { }
+```
 
-// AFTER
+.After
+```rust
 fn foo<T: Copy + Clone>() { }
 ```
 
-## `inline_local_variable`
+
+[discrete]
+=== `inline_local_variable`
 
 Inlines local variable.
 
+.Before
 ```rust
-// BEFORE
 fn main() {
     let x┃ = 1 + 2;
     x * 4;
 }
+```
 
-// AFTER
+.After
+```rust
 fn main() {
     (1 + 2) * 4;
 }
 ```
 
-## `introduce_variable`
+
+[discrete]
+=== `introduce_variable`
 
 Extracts subexpression into a variable.
 
+.Before
 ```rust
-// BEFORE
 fn main() {
     ┃(1 + 2)┃ * 4;
 }
+```
 
-// AFTER
+.After
+```rust
 fn main() {
     let $0var_name = (1 + 2);
     var_name * 4;
 }
 ```
 
-## `invert_if`
+
+[discrete]
+=== `invert_if`
 
 Apply invert_if
 This transforms if expressions of the form `if !x {A} else {B}` into `if x {B} else {A}`
 This also works with `!=`. This assist can only be applied with the cursor
 on `if`.
 
+.Before
 ```rust
-// BEFORE
 fn main() {
     if┃ !y { A } else { B }
 }
+```
 
-// AFTER
+.After
+```rust
 fn main() {
     if y { B } else { A }
 }
 ```
 
-## `make_raw_string`
+
+[discrete]
+=== `make_raw_string`
 
 Adds `r#` to a plain string literal.
 
+.Before
 ```rust
-// BEFORE
 fn main() {
     "Hello,┃ World!";
 }
+```
 
-// AFTER
+.After
+```rust
 fn main() {
     r#"Hello, World!"#;
 }
 ```
 
-## `make_usual_string`
+
+[discrete]
+=== `make_usual_string`
 
 Turns a raw string into a plain string.
 
+.Before
 ```rust
-// BEFORE
 fn main() {
     r#"Hello,┃ "World!""#;
 }
+```
 
-// AFTER
+.After
+```rust
 fn main() {
     "Hello, \"World!\"";
 }
 ```
 
-## `merge_imports`
+
+[discrete]
+=== `merge_imports`
 
 Merges two imports with a common prefix.
 
+.Before
 ```rust
-// BEFORE
 use std::┃fmt::Formatter;
 use std::io;
+```
 
-// AFTER
+.After
+```rust
 use std::{fmt::Formatter, io};
 ```
 
-## `merge_match_arms`
+
+[discrete]
+=== `merge_match_arms`
 
 Merges identical match arms.
 
+.Before
 ```rust
-// BEFORE
 enum Action { Move { distance: u32 }, Stop }
 
 fn handle(action: Action) {
@@ -551,8 +659,10 @@ fn handle(action: Action) {
         Action::Stop => foo(),
     }
 }
+```
 
-// AFTER
+.After
+```rust
 enum Action { Move { distance: u32 }, Stop }
 
 fn handle(action: Action) {
@@ -562,12 +672,14 @@ fn handle(action: Action) {
 }
 ```
 
-## `move_arm_cond_to_match_guard`
+
+[discrete]
+=== `move_arm_cond_to_match_guard`
 
 Moves if expression from match arm body into a guard.
 
+.Before
 ```rust
-// BEFORE
 enum Action { Move { distance: u32 }, Stop }
 
 fn handle(action: Action) {
@@ -576,8 +688,10 @@ fn handle(action: Action) {
         _ => (),
     }
 }
+```
 
-// AFTER
+.After
+```rust
 enum Action { Move { distance: u32 }, Stop }
 
 fn handle(action: Action) {
@@ -588,28 +702,34 @@ fn handle(action: Action) {
 }
 ```
 
-## `move_bounds_to_where_clause`
+
+[discrete]
+=== `move_bounds_to_where_clause`
 
 Moves inline type bounds to a where clause.
 
+.Before
 ```rust
-// BEFORE
 fn apply<T, U, ┃F: FnOnce(T) -> U>(f: F, x: T) -> U {
     f(x)
 }
+```
 
-// AFTER
+.After
+```rust
 fn apply<T, U, F>(f: F, x: T) -> U where F: FnOnce(T) -> U {
     f(x)
 }
 ```
 
-## `move_guard_to_arm_body`
+
+[discrete]
+=== `move_guard_to_arm_body`
 
 Moves match guard into match arm body.
 
+.Before
 ```rust
-// BEFORE
 enum Action { Move { distance: u32 }, Stop }
 
 fn handle(action: Action) {
@@ -618,8 +738,10 @@ fn handle(action: Action) {
         _ => (),
     }
 }
+```
 
-// AFTER
+.After
+```rust
 enum Action { Move { distance: u32 }, Stop }
 
 fn handle(action: Action) {
@@ -630,75 +752,93 @@ fn handle(action: Action) {
 }
 ```
 
-## `remove_dbg`
+
+[discrete]
+=== `remove_dbg`
 
 Removes `dbg!()` macro call.
 
+.Before
 ```rust
-// BEFORE
 fn main() {
     ┃dbg!(92);
 }
+```
 
-// AFTER
+.After
+```rust
 fn main() {
     92;
 }
 ```
 
-## `remove_hash`
+
+[discrete]
+=== `remove_hash`
 
 Removes a hash from a raw string literal.
 
+.Before
 ```rust
-// BEFORE
 fn main() {
     r#"Hello,┃ World!"#;
 }
+```
 
-// AFTER
+.After
+```rust
 fn main() {
     r"Hello, World!";
 }
 ```
 
-## `remove_mut`
+
+[discrete]
+=== `remove_mut`
 
 Removes the `mut` keyword.
 
+.Before
 ```rust
-// BEFORE
 impl Walrus {
     fn feed(&mut┃ self, amount: u32) {}
 }
+```
 
-// AFTER
+.After
+```rust
 impl Walrus {
     fn feed(&self, amount: u32) {}
 }
 ```
 
-## `reorder_fields`
+
+[discrete]
+=== `reorder_fields`
 
 Reorder the fields of record literals and record patterns in the same order as in
 the definition.
 
+.Before
 ```rust
-// BEFORE
 struct Foo {foo: i32, bar: i32};
 const test: Foo = ┃Foo {bar: 0, foo: 1}
+```
 
-// AFTER
+.After
+```rust
 struct Foo {foo: i32, bar: i32};
 const test: Foo = Foo {foo: 1, bar: 0}
 ```
 
-## `replace_if_let_with_match`
+
+[discrete]
+=== `replace_if_let_with_match`
 
 Replaces `if let` with an else branch with a `match` expression.
 
+.Before
 ```rust
-// BEFORE
 enum Action { Move { distance: u32 }, Stop }
 
 fn handle(action: Action) {
@@ -708,8 +848,10 @@ fn handle(action: Action) {
         bar()
     }
 }
+```
 
-// AFTER
+.After
+```rust
 enum Action { Move { distance: u32 }, Stop }
 
 fn handle(action: Action) {
@@ -720,20 +862,24 @@ fn handle(action: Action) {
 }
 ```
 
-## `replace_let_with_if_let`
+
+[discrete]
+=== `replace_let_with_if_let`
 
 Replaces `let` with an `if-let`.
 
+.Before
 ```rust
-// BEFORE
 
 fn main(action: Action) {
     ┃let x = compute();
 }
 
 fn compute() -> Option<i32> { None }
+```
 
-// AFTER
+.After
+```rust
 
 fn main(action: Action) {
     if let Some(x) = compute() {
@@ -743,33 +889,41 @@ fn main(action: Action) {
 fn compute() -> Option<i32> { None }
 ```
 
-## `replace_qualified_name_with_use`
+
+[discrete]
+=== `replace_qualified_name_with_use`
 
 Adds a use statement for a given fully-qualified name.
 
+.Before
 ```rust
-// BEFORE
 fn process(map: std::collections::┃HashMap<String, String>) {}
+```
 
-// AFTER
+.After
+```rust
 use std::collections::HashMap;
 
 fn process(map: HashMap<String, String>) {}
 ```
 
-## `replace_unwrap_with_match`
+
+[discrete]
+=== `replace_unwrap_with_match`
 
 Replaces `unwrap` a `match` expression. Works for Result and Option.
 
+.Before
 ```rust
-// BEFORE
 enum Result<T, E> { Ok(T), Err(E) }
 fn main() {
     let x: Result<i32, i32> = Result::Ok(92);
     let y = x.┃unwrap();
 }
+```
 
-// AFTER
+.After
+```rust
 enum Result<T, E> { Ok(T), Err(E) }
 fn main() {
     let x: Result<i32, i32> = Result::Ok(92);
@@ -780,31 +934,39 @@ fn main() {
 }
 ```
 
-## `split_import`
+
+[discrete]
+=== `split_import`
 
 Wraps the tail of import into braces.
 
+.Before
 ```rust
-// BEFORE
 use std::┃collections::HashMap;
+```
 
-// AFTER
+.After
+```rust
 use std::{collections::HashMap};
 ```
 
-## `unwrap_block`
+
+[discrete]
+=== `unwrap_block`
 
 This assist removes if...else, for, while and loop control statements to just keep the body.
 
+.Before
 ```rust
-// BEFORE
 fn foo() {
     if true {┃
         println!("foo");
     }
 }
+```
 
-// AFTER
+.After
+```rust
 fn foo() {
     println!("foo");
 }
diff --git a/docs/user/manual.adoc b/docs/user/manual.adoc
index f40139804d..27b3792135 100644
--- a/docs/user/manual.adoc
+++ b/docs/user/manual.adoc
@@ -278,5 +278,6 @@ include::./generated_features.adoc[]
 
 Assists, or code actions, are small local refactorings, available in a particular context.
 They are usually triggered by a shortcut or by clicking a light bulb icon in the editor.
+Cursor position or selection is signified by `┃` character.
 
-See [assists.md](./assists.md) for the list of available assists.
+include::./generated_assists.adoc[]
diff --git a/xtask/src/ast_src.rs b/xtask/src/ast_src.rs
index f60f0fb16c..d4621930ed 100644
--- a/xtask/src/ast_src.rs
+++ b/xtask/src/ast_src.rs
@@ -1058,7 +1058,7 @@ pub(crate) const AST_SRC: AstSrc = AstSrc {
         /// [Reference](https://doc.rust-lang.org/reference/expressions/block-expr.html)
         /// [Labels for blocks RFC](https://github.com/rust-lang/rfcs/blob/master/text/2046-label-break-value.md)
         struct BlockExpr: AttrsOwner, ModuleItemOwner {
-            Label, T!['{'], statements: [Stmt], Expr, T!['}'],
+            T!['{'], statements: [Stmt], Expr, T!['}'],
         }
 
         /// Return expression.
diff --git a/xtask/src/codegen.rs b/xtask/src/codegen.rs
index f47d54125f..f3917a244a 100644
--- a/xtask/src/codegen.rs
+++ b/xtask/src/codegen.rs
@@ -10,9 +10,12 @@ mod gen_parser_tests;
 mod gen_assists_docs;
 mod gen_feature_docs;
 
-use std::{mem, path::Path};
+use std::{
+    fmt, mem,
+    path::{Path, PathBuf},
+};
 
-use crate::{not_bash::fs2, Result};
+use crate::{not_bash::fs2, project_root, Result};
 
 pub use self::{
     gen_assists_docs::generate_assists_docs, gen_feature_docs::generate_feature_docs,
@@ -29,7 +32,6 @@ const AST_TOKENS: &str = "crates/ra_syntax/src/ast/generated/tokens.rs";
 
 const ASSISTS_DIR: &str = "crates/ra_assists/src/handlers";
 const ASSISTS_TESTS: &str = "crates/ra_assists/src/tests/generated.rs";
-const ASSISTS_DOCS: &str = "docs/user/assists.md";
 
 #[derive(Debug, PartialEq, Eq, Clone, Copy)]
 pub enum Mode {
@@ -107,3 +109,28 @@ fn do_extract_comment_blocks(text: &str, allow_blocks_with_empty_lines: bool) ->
     }
     res
 }
+
+#[derive(Debug)]
+struct Location {
+    file: PathBuf,
+}
+
+impl Location {
+    fn new(file: PathBuf) -> Self {
+        Self { file }
+    }
+}
+
+impl fmt::Display for Location {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        let path = self.file.strip_prefix(&project_root()).unwrap().display().to_string();
+        let path = path.replace('\\', "/");
+        let name = self.file.file_name().unwrap();
+        write!(
+            f,
+            "https://github.com/rust-analyzer/rust-analyzer/blob/master/{}[{}]",
+            path,
+            name.to_str().unwrap()
+        )
+    }
+}
diff --git a/xtask/src/codegen/gen_assists_docs.rs b/xtask/src/codegen/gen_assists_docs.rs
index 6ebeb8aea4..15a02d317b 100644
--- a/xtask/src/codegen/gen_assists_docs.rs
+++ b/xtask/src/codegen/gen_assists_docs.rs
@@ -1,22 +1,28 @@
 //! Generates `assists.md` documentation.
 
-use std::{fs, path::Path};
+use std::{fmt, fs, path::Path};
 
 use crate::{
-    codegen::{self, extract_comment_blocks_with_empty_lines, Mode},
+    codegen::{self, extract_comment_blocks_with_empty_lines, Location, Mode},
     project_root, rust_files, Result,
 };
 
 pub fn generate_assists_docs(mode: Mode) -> Result<()> {
     let assists = Assist::collect()?;
     generate_tests(&assists, mode)?;
-    generate_docs(&assists, mode)?;
+
+    let contents = assists.into_iter().map(|it| it.to_string()).collect::<Vec<_>>().join("\n\n");
+    let contents = contents.trim().to_string() + "\n";
+    let dst = project_root().join("docs/user/generated_assists.adoc");
+    codegen::update(&dst, &contents, mode)?;
+
     Ok(())
 }
 
 #[derive(Debug)]
 struct Assist {
     id: String,
+    location: Location,
     doc: String,
     before: String,
     after: String,
@@ -58,7 +64,8 @@ impl Assist {
                 assert_eq!(lines.next().unwrap().as_str(), "->");
                 assert_eq!(lines.next().unwrap().as_str(), "```");
                 let after = take_until(lines.by_ref(), "```");
-                acc.push(Assist { id, doc, before, after })
+                let location = Location::new(path.to_path_buf());
+                acc.push(Assist { id, location, doc, before, after })
             }
 
             fn take_until<'a>(lines: impl Iterator<Item = &'a String>, marker: &str) -> String {
@@ -76,6 +83,31 @@ impl Assist {
     }
 }
 
+impl fmt::Display for Assist {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        let before = self.before.replace("<|>", "┃"); // Unicode pseudo-graphics bar
+        let after = self.after.replace("<|>", "┃");
+        writeln!(
+            f,
+            "[discrete]\n=== `{}`
+
+{}
+
+.Before
+```rust
+{}```
+
+.After
+```rust
+{}```",
+            self.id,
+            self.doc,
+            hide_hash_comments(&before),
+            hide_hash_comments(&after)
+        )
+    }
+}
+
 fn generate_tests(assists: &[Assist], mode: Mode) -> Result<()> {
     let mut buf = String::from("use super::check_doc_test;\n");
 
@@ -103,37 +135,6 @@ r#####"
     codegen::update(&project_root().join(codegen::ASSISTS_TESTS), &buf, mode)
 }
 
-fn generate_docs(assists: &[Assist], mode: Mode) -> Result<()> {
-    let mut buf = String::from(
-        "# Assists\n\nCursor position or selection is signified by `┃` character.\n\n",
-    );
-
-    for assist in assists {
-        let before = assist.before.replace("<|>", "┃"); // Unicode pseudo-graphics bar
-        let after = assist.after.replace("<|>", "┃");
-        let docs = format!(
-            "
-## `{}`
-
-{}
-
-```rust
-// BEFORE
-{}
-// AFTER
-{}```
-",
-            assist.id,
-            assist.doc,
-            hide_hash_comments(&before),
-            hide_hash_comments(&after)
-        );
-        buf.push_str(&docs);
-    }
-
-    codegen::update(&project_root().join(codegen::ASSISTS_DOCS), &buf, mode)
-}
-
 fn hide_hash_comments(text: &str) -> String {
     text.split('\n') // want final newline
         .filter(|&it| !(it.starts_with("# ") || it == "#"))
diff --git a/xtask/src/codegen/gen_feature_docs.rs b/xtask/src/codegen/gen_feature_docs.rs
index dbe583e8e4..731e7ecf25 100644
--- a/xtask/src/codegen/gen_feature_docs.rs
+++ b/xtask/src/codegen/gen_feature_docs.rs
@@ -3,7 +3,7 @@
 use std::{fmt, fs, path::PathBuf};
 
 use crate::{
-    codegen::{self, extract_comment_blocks_with_empty_lines, Mode},
+    codegen::{self, extract_comment_blocks_with_empty_lines, Location, Mode},
     project_root, rust_files, Result,
 };
 
@@ -19,7 +19,7 @@ pub fn generate_feature_docs(mode: Mode) -> Result<()> {
 #[derive(Debug)]
 struct Feature {
     id: String,
-    path: PathBuf,
+    location: Location,
     doc: String,
 }
 
@@ -40,7 +40,7 @@ impl Feature {
                 let id = block.id;
                 assert!(is_valid_feature_name(&id), "invalid feature name: {:?}", id);
                 let doc = block.contents.join("\n");
-                acc.push(Feature { id, path: path.clone(), doc })
+                acc.push(Feature { id, location: Location::new(path.clone()), doc })
             }
 
             Ok(())
@@ -69,20 +69,6 @@ fn is_valid_feature_name(feature: &str) -> bool {
 
 impl fmt::Display for Feature {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        writeln!(f, "=== {}", self.id)?;
-        let path = self.path.strip_prefix(&project_root()).unwrap().display().to_string();
-        let path = path.replace('\\', "/");
-        let name = self.path.file_name().unwrap();
-
-        //FIXME: generate line number as well
-        writeln!(
-            f,
-            "**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/{}[{}]",
-            path,
-            name.to_str().unwrap(),
-        )?;
-
-        writeln!(f, "{}", self.doc)?;
-        Ok(())
+        writeln!(f, "=== {}\n**Source:** {}\n{}", self.id, self.location, self.doc)
     }
 }
diff --git a/xtask/src/lib.rs b/xtask/src/lib.rs
index 06043d19f8..874957885e 100644
--- a/xtask/src/lib.rs
+++ b/xtask/src/lib.rs
@@ -191,7 +191,7 @@ Release: release:{}[]
     let path = changelog_dir.join(format!("{}-changelog-{}.adoc", today, changelog_n));
     fs2::write(&path, &contents)?;
 
-    for &adoc in ["manual.adoc", "generated_features.adoc"].iter() {
+    for &adoc in ["manual.adoc", "generated_features.adoc", "generated_assists.adoc"].iter() {
         let src = project_root().join("./docs/user/").join(adoc);
         let dst = website_root.join(adoc);
         fs2::copy(src, dst)?;


Test patch:
diff --git a/crates/ra_hir_ty/src/tests/simple.rs b/crates/ra_hir_ty/src/tests/simple.rs
index 88309157b7..839491b9e2 100644
--- a/crates/ra_hir_ty/src/tests/simple.rs
+++ b/crates/ra_hir_ty/src/tests/simple.rs
@@ -1943,57 +1943,3 @@ fn test() {
     "###
     );
 }
-
-#[test]
-fn infer_labelled_break_with_val() {
-    assert_snapshot!(
-        infer(r#"
-fn foo() {
-    let _x = || 'outer: loop {
-        let inner = 'inner: loop {
-            let i = Default::default();
-            if (break 'outer i) {
-                loop { break 'inner 5i8; };
-            } else if true {
-                break 'inner 6;
-            }
-            break 7;
-        };
-        break inner < 8;
-    };
-}
-"#),
-        @r###"
-    10..336 '{     ...  }; }': ()
-    20..22 '_x': || -> bool
-    25..333 '|| 'ou...     }': || -> bool
-    28..333 ''outer...     }': bool
-    41..333 '{     ...     }': ()
-    55..60 'inner': i8
-    63..301 ''inner...     }': i8
-    76..301 '{     ...     }': ()
-    94..95 'i': bool
-    98..114 'Defaul...efault': {unknown}
-    98..116 'Defaul...ault()': bool
-    130..270 'if (br...     }': ()
-    134..148 'break 'outer i': !
-    147..148 'i': bool
-    150..209 '{     ...     }': ()
-    168..194 'loop {...5i8; }': !
-    173..194 '{ brea...5i8; }': ()
-    175..191 'break ...er 5i8': !
-    188..191 '5i8': i8
-    215..270 'if tru...     }': ()
-    218..222 'true': bool
-    223..270 '{     ...     }': ()
-    241..255 'break 'inner 6': !
-    254..255 '6': i8
-    283..290 'break 7': !
-    289..290 '7': i8
-    311..326 'break inner < 8': !
-    317..322 'inner': i8
-    317..326 'inner < 8': bool
-    325..326 '8': i8
-    "###
-    );
-}