Repository: rust-analyzer/rust-analyzer (rust)

Issue:
No completion for enum variants through type alias
```rust
enum Enum {
    VariantA,
    VariantB
}

type Type = Enum;

fn main() {
    let x = Type::<|>;
}
```

We should get completions for the variants, but currently don't. Very annoying in hir_ty currently, since we have so many type aliases for Chalk enums there :sweat_smile: Type inference works otherwise for e.g. `Type::VariantA`, so this seems to be a completion problem (well, the actual problem is that a lot of this name resolution logic happens inside of type inference and can't be reused by completion right now).

Pull request description:
Complete enum variants through type aliases
Fixes #8730
bors r+

Patch:
diff --git a/crates/hir_def/src/attr.rs b/crates/hir_def/src/attr.rs
index 0171d8a92a..d9294d93af 100644
--- a/crates/hir_def/src/attr.rs
+++ b/crates/hir_def/src/attr.rs
@@ -484,10 +484,10 @@ impl AttrsWithOwner {
         let mut buf = String::new();
         let mut mapping = Vec::new();
         for (doc, idx) in docs {
+            // str::lines doesn't yield anything for the empty string
             if !doc.is_empty() {
-                let mut base_offset = 0;
-                for raw_line in doc.split('\n') {
-                    let line = raw_line.trim_end();
+                for line in doc.split('\n') {
+                    let line = line.trim_end();
                     let line_len = line.len();
                     let (offset, line) = match line.char_indices().nth(indent) {
                         Some((offset, _)) => (offset, &line[offset..]),
@@ -498,13 +498,9 @@ impl AttrsWithOwner {
                     mapping.push((
                         TextRange::new(buf_offset.try_into().ok()?, buf.len().try_into().ok()?),
                         idx,
-                        TextRange::at(
-                            (base_offset + offset).try_into().ok()?,
-                            line_len.try_into().ok()?,
-                        ),
+                        TextRange::new(offset.try_into().ok()?, line_len.try_into().ok()?),
                     ));
                     buf.push('\n');
-                    base_offset += raw_line.len() + 1;
                 }
             } else {
                 buf.push('\n');
diff --git a/crates/hir_expand/src/db.rs b/crates/hir_expand/src/db.rs
index 1389e30db6..1e4b0cc194 100644
--- a/crates/hir_expand/src/db.rs
+++ b/crates/hir_expand/src/db.rs
@@ -3,14 +3,14 @@
 use std::sync::Arc;
 
 use base_db::{salsa, SourceDatabase};
-use mbe::{ExpandError, ExpandResult};
+use mbe::{ExpandError, ExpandResult, MacroDef, MacroRules};
 use parser::FragmentKind;
 use syntax::{
     algo::diff,
-    ast::{self, NameOwner},
+    ast::{MacroStmts, NameOwner},
     AstNode, GreenNode, Parse,
     SyntaxKind::*,
-    SyntaxNode, SyntaxToken,
+    SyntaxNode,
 };
 
 use crate::{
@@ -27,20 +27,15 @@ const TOKEN_LIMIT: usize = 524288;
 
 #[derive(Debug, Clone, Eq, PartialEq)]
 pub enum TokenExpander {
-    /// Old-style `macro_rules`.
     MacroRules(mbe::MacroRules),
-    /// AKA macros 2.0.
     MacroDef(mbe::MacroDef),
-    /// Stuff like `line!` and `file!`.
     Builtin(BuiltinFnLikeExpander),
-    /// `derive(Copy)` and such.
     BuiltinDerive(BuiltinDeriveExpander),
-    /// The thing we love the most here in rust-analyzer -- procedural macros.
     ProcMacro(ProcMacroExpander),
 }
 
 impl TokenExpander {
-    fn expand(
+    pub fn expand(
         &self,
         db: &dyn AstDatabase,
         id: LazyMacroId,
@@ -61,7 +56,7 @@ impl TokenExpander {
         }
     }
 
-    pub(crate) fn map_id_down(&self, id: tt::TokenId) -> tt::TokenId {
+    pub fn map_id_down(&self, id: tt::TokenId) -> tt::TokenId {
         match self {
             TokenExpander::MacroRules(it) => it.map_id_down(id),
             TokenExpander::MacroDef(it) => it.map_id_down(id),
@@ -71,7 +66,7 @@ impl TokenExpander {
         }
     }
 
-    pub(crate) fn map_id_up(&self, id: tt::TokenId) -> (tt::TokenId, mbe::Origin) {
+    pub fn map_id_up(&self, id: tt::TokenId) -> (tt::TokenId, mbe::Origin) {
         match self {
             TokenExpander::MacroRules(it) => it.map_id_up(id),
             TokenExpander::MacroDef(it) => it.map_id_up(id),
@@ -120,9 +115,9 @@ pub trait AstDatabase: SourceDatabase {
 pub fn expand_hypothetical(
     db: &dyn AstDatabase,
     actual_macro_call: MacroCallId,
-    hypothetical_args: &ast::TokenTree,
-    token_to_map: SyntaxToken,
-) -> Option<(SyntaxNode, SyntaxToken)> {
+    hypothetical_args: &syntax::ast::TokenTree,
+    token_to_map: syntax::SyntaxToken,
+) -> Option<(SyntaxNode, syntax::SyntaxToken)> {
     let macro_file = MacroFile { macro_call_id: actual_macro_call };
     let (tt, tmap_1) = mbe::syntax_node_to_token_tree(hypothetical_args.syntax());
     let range =
@@ -146,10 +141,10 @@ fn ast_id_map(db: &dyn AstDatabase, file_id: HirFileId) -> Arc<AstIdMap> {
 fn macro_def(db: &dyn AstDatabase, id: MacroDefId) -> Option<Arc<(TokenExpander, mbe::TokenMap)>> {
     match id.kind {
         MacroDefKind::Declarative(ast_id) => match ast_id.to_node(db) {
-            ast::Macro::MacroRules(macro_rules) => {
+            syntax::ast::Macro::MacroRules(macro_rules) => {
                 let arg = macro_rules.token_tree()?;
                 let (tt, tmap) = mbe::ast_to_token_tree(&arg);
-                let rules = match mbe::MacroRules::parse(&tt) {
+                let rules = match MacroRules::parse(&tt) {
                     Ok(it) => it,
                     Err(err) => {
                         let name = macro_rules.name().map(|n| n.to_string()).unwrap_or_default();
@@ -159,10 +154,10 @@ fn macro_def(db: &dyn AstDatabase, id: MacroDefId) -> Option<Arc<(TokenExpander,
                 };
                 Some(Arc::new((TokenExpander::MacroRules(rules), tmap)))
             }
-            ast::Macro::MacroDef(macro_def) => {
+            syntax::ast::Macro::MacroDef(macro_def) => {
                 let arg = macro_def.body()?;
                 let (tt, tmap) = mbe::ast_to_token_tree(&arg);
-                let rules = match mbe::MacroDef::parse(&tt) {
+                let rules = match MacroDef::parse(&tt) {
                     Ok(it) => it,
                     Err(err) => {
                         let name = macro_def.name().map(|n| n.to_string()).unwrap_or_default();
@@ -408,7 +403,7 @@ fn is_self_replicating(from: &SyntaxNode, to: &SyntaxNode) -> bool {
     if diff(from, to).is_empty() {
         return true;
     }
-    if let Some(stmts) = ast::MacroStmts::cast(from.clone()) {
+    if let Some(stmts) = MacroStmts::cast(from.clone()) {
         if stmts.statements().any(|stmt| diff(stmt.syntax(), to).is_empty()) {
             return true;
         }
diff --git a/crates/ide_completion/src/completions/qualified_path.rs b/crates/ide_completion/src/completions/qualified_path.rs
index d8f23d1eba..d2ebba65ff 100644
--- a/crates/ide_completion/src/completions/qualified_path.rs
+++ b/crates/ide_completion/src/completions/qualified_path.rs
@@ -52,13 +52,17 @@ pub(crate) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionCon
         | PathResolution::Def(def @ hir::ModuleDef::TypeAlias(_))
         | PathResolution::Def(def @ hir::ModuleDef::BuiltinType(_)) => {
             if let hir::ModuleDef::Adt(Adt::Enum(e)) = def {
-                for variant in e.variants(ctx.db) {
-                    acc.add_enum_variant(ctx, variant, None);
-                }
+                add_enum_variants(ctx, acc, e);
             }
             let ty = match def {
                 hir::ModuleDef::Adt(adt) => adt.ty(ctx.db),
-                hir::ModuleDef::TypeAlias(a) => a.ty(ctx.db),
+                hir::ModuleDef::TypeAlias(a) => {
+                    let ty = a.ty(ctx.db);
+                    if let Some(Adt::Enum(e)) = ty.as_adt() {
+                        add_enum_variants(ctx, acc, e);
+                    }
+                    ty
+                }
                 hir::ModuleDef::BuiltinType(builtin) => {
                     let module = match ctx.scope.module() {
                         Some(it) => it,
@@ -122,9 +126,7 @@ pub(crate) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionCon
                 };
 
                 if let Some(Adt::Enum(e)) = ty.as_adt() {
-                    for variant in e.variants(ctx.db) {
-                        acc.add_enum_variant(ctx, variant, None);
-                    }
+                    add_enum_variants(ctx, acc, e);
                 }
 
                 let traits_in_scope = ctx.scope.traits_in_scope();
@@ -151,6 +153,12 @@ pub(crate) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionCon
     }
 }
 
+fn add_enum_variants(ctx: &CompletionContext, acc: &mut Completions, e: hir::Enum) {
+    for variant in e.variants(ctx.db) {
+        acc.add_enum_variant(ctx, variant, None);
+    }
+}
+
 #[cfg(test)]
 mod tests {
     use expect_test::{expect, Expect};
@@ -782,4 +790,22 @@ impl u8 {
             "#]],
         );
     }
+
+    #[test]
+    fn completes_through_alias() {
+        check(
+            r#"
+enum Foo {
+    Bar
+}
+type Foo2 = Foo;
+fn main() {
+    Foo2::$0
+}
+"#,
+            expect![[r#"
+                ev Bar ()
+            "#]],
+        );
+    }
 }
diff --git a/crates/rust-analyzer/src/benchmarks.rs b/crates/rust-analyzer/src/benchmarks.rs
new file mode 100644
index 0000000000..bdd94b1c4e
--- /dev/null
+++ b/crates/rust-analyzer/src/benchmarks.rs
@@ -0,0 +1,74 @@
+//! Fully integrated benchmarks for rust-analyzer, which load real cargo
+//! projects.
+//!
+//! The benchmark here is used to debug specific performance regressions. If you
+//! notice that, eg, completion is slow in some specific case, you can  modify
+//! code here exercise this specific completion, and thus have a fast
+//! edit/compile/test cycle.
+//!
+//! Note that "Rust Analyzer: Run" action does not allow running a single test
+//! in release mode in VS Code. There's however "Rust Analyzer: Copy Run Command Line"
+//! which you can use to paste the command in terminal and add `--release` manually.
+
+use std::sync::Arc;
+
+use ide::Change;
+use test_utils::project_root;
+use vfs::{AbsPathBuf, VfsPath};
+
+use crate::cli::load_cargo::{load_workspace_at, LoadCargoConfig};
+
+#[test]
+fn benchmark_integrated_highlighting() {
+    // Don't run slow benchmark by default
+    if true {
+        return;
+    }
+
+    // Load rust-analyzer itself.
+    let workspace_to_load = project_root();
+    let file = "./crates/ide_db/src/apply_change.rs";
+
+    let cargo_config = Default::default();
+    let load_cargo_config = LoadCargoConfig {
+        load_out_dirs_from_check: true,
+        wrap_rustc: false,
+        with_proc_macro: false,
+    };
+
+    let (mut host, vfs, _proc_macro) = {
+        let _it = stdx::timeit("workspace loading");
+        load_workspace_at(&workspace_to_load, &cargo_config, &load_cargo_config, &|_| {}).unwrap()
+    };
+
+    let file_id = {
+        let file = workspace_to_load.join(file);
+        let path = VfsPath::from(AbsPathBuf::assert(file));
+        vfs.file_id(&path).unwrap_or_else(|| panic!("can't find virtual file for {}", path))
+    };
+
+    {
+        let _it = stdx::timeit("initial");
+        let analysis = host.analysis();
+        analysis.highlight_as_html(file_id, false).unwrap();
+    }
+
+    profile::init_from("*>100");
+    // let _s = profile::heartbeat_span();
+
+    {
+        let _it = stdx::timeit("change");
+        let mut text = host.analysis().file_text(file_id).unwrap().to_string();
+        text.push_str("\npub fn _dummy() {}\n");
+        let mut change = Change::new();
+        change.change_file(file_id, Some(Arc::new(text)));
+        host.apply_change(change);
+    }
+
+    {
+        let _it = stdx::timeit("after change");
+        let _span = profile::cpu_span();
+        let analysis = host.analysis();
+        analysis.highlight_as_html(file_id, false).unwrap();
+    }
+}
diff --git a/crates/rust-analyzer/src/integrated_benchmarks.rs b/crates/rust-analyzer/src/integrated_benchmarks.rs
deleted file mode 100644
index 3dcbe397ad..0000000000
--- a/crates/rust-analyzer/src/integrated_benchmarks.rs
+++ /dev/null
@@ -1,184 +0,0 @@
-//! Fully integrated benchmarks for rust-analyzer, which load real cargo
-//! projects.
-//!
-//! The benchmark here is used to debug specific performance regressions. If you
-//! notice that, eg, completion is slow in some specific case, you can  modify
-//! code here exercise this specific completion, and thus have a fast
-//! edit/compile/test cycle.
-//!
-//! Note that "Rust Analyzer: Run" action does not allow running a single test
-//! in release mode in VS Code. There's however "Rust Analyzer: Copy Run Command Line"
-//! which you can use to paste the command in terminal and add `--release` manually.
-
-use std::{convert::TryFrom, sync::Arc};
-
-use ide::{Change, CompletionConfig, FilePosition, TextSize};
-use ide_db::helpers::{insert_use::InsertUseConfig, merge_imports::MergeBehavior, SnippetCap};
-use test_utils::project_root;
-use vfs::{AbsPathBuf, VfsPath};
-
-use crate::cli::load_cargo::{load_workspace_at, LoadCargoConfig};
-
-#[test]
-fn integrated_highlighting_benchmark() {
-    if std::env::var("RUN_SLOW_BENCHES").is_err() {
-        return;
-    }
-
-    // Load rust-analyzer itself.
-    let workspace_to_load = project_root();
-    let file = "./crates/ide_db/src/apply_change.rs";
-
-    let cargo_config = Default::default();
-    let load_cargo_config = LoadCargoConfig {
-        load_out_dirs_from_check: true,
-        wrap_rustc: false,
-        with_proc_macro: false,
-    };
-
-    let (mut host, vfs, _proc_macro) = {
-        let _it = stdx::timeit("workspace loading");
-        load_workspace_at(&workspace_to_load, &cargo_config, &load_cargo_config, &|_| {}).unwrap()
-    };
-
-    let file_id = {
-        let file = workspace_to_load.join(file);
-        let path = VfsPath::from(AbsPathBuf::assert(file));
-        vfs.file_id(&path).unwrap_or_else(|| panic!("can't find virtual file for {}", path))
-    };
-
-    {
-        let _it = stdx::timeit("initial");
-        let analysis = host.analysis();
-        analysis.highlight_as_html(file_id, false).unwrap();
-    }
-
-    profile::init_from("*>100");
-    // let _s = profile::heartbeat_span();
-
-    {
-        let _it = stdx::timeit("change");
-        let mut text = host.analysis().file_text(file_id).unwrap().to_string();
-        text.push_str("\npub fn _dummy() {}\n");
-        let mut change = Change::new();
-        change.change_file(file_id, Some(Arc::new(text)));
-        host.apply_change(change);
-    }
-
-    {
-        let _it = stdx::timeit("after change");
-        let _span = profile::cpu_span();
-        let analysis = host.analysis();
-        analysis.highlight_as_html(file_id, false).unwrap();
-    }
-}
-
-#[test]
-fn integrated_completion_benchmark() {
-    if std::env::var("RUN_SLOW_BENCHES").is_err() {
-        return;
-    }
-
-    // Load rust-analyzer itself.
-    let workspace_to_load = project_root();
-    let file = "./crates/hir/src/lib.rs";
-
-    let cargo_config = Default::default();
-    let load_cargo_config = LoadCargoConfig {
-        load_out_dirs_from_check: true,
-        wrap_rustc: false,
-        with_proc_macro: false,
-    };
-
-    let (mut host, vfs, _proc_macro) = {
-        let _it = stdx::timeit("workspace loading");
-        load_workspace_at(&workspace_to_load, &cargo_config, &load_cargo_config, &|_| {}).unwrap()
-    };
-
-    let file_id = {
-        let file = workspace_to_load.join(file);
-        let path = VfsPath::from(AbsPathBuf::assert(file));
-        vfs.file_id(&path).unwrap_or_else(|| panic!("can't find virtual file for {}", path))
-    };
-
-    {
-        let _it = stdx::timeit("initial");
-        let analysis = host.analysis();
-        analysis.highlight_as_html(file_id, false).unwrap();
-    }
-
-    profile::init_from("*>5");
-    // let _s = profile::heartbeat_span();
-
-    let completion_offset = {
-        let _it = stdx::timeit("change");
-        let mut text = host.analysis().file_text(file_id).unwrap().to_string();
-        let completion_offset =
-            patch(&mut text, "db.struct_data(self.id)", "sel;\ndb.struct_data(self.id)")
-                + "sel".len();
-        let mut change = Change::new();
-        change.change_file(file_id, Some(Arc::new(text)));
-        host.apply_change(change);
-        completion_offset
-    };
-
-    {
-        let _it = stdx::timeit("unqualified path completion");
-        let _span = profile::cpu_span();
-        let analysis = host.analysis();
-        let config = CompletionConfig {
-            enable_postfix_completions: true,
-            enable_imports_on_the_fly: true,
-            add_call_parenthesis: true,
-            add_call_argument_snippets: true,
-            snippet_cap: SnippetCap::new(true),
-            insert_use: InsertUseConfig {
-                merge: Some(MergeBehavior::Full),
-                prefix_kind: hir::PrefixKind::ByCrate,
-                group: true,
-            },
-        };
-        let position =
-            FilePosition { file_id, offset: TextSize::try_from(completion_offset).unwrap() };
-        analysis.completions(&config, position).unwrap();
-    }
-
-    let completion_offset = {
-        let _it = stdx::timeit("change");
-        let mut text = host.analysis().file_text(file_id).unwrap().to_string();
-        let completion_offset =
-            patch(&mut text, "sel;\ndb.struct_data(self.id)", "self.;\ndb.struct_data(self.id)")
-                + "self.".len();
-        let mut change = Change::new();
-        change.change_file(file_id, Some(Arc::new(text)));
-        host.apply_change(change);
-        completion_offset
-    };
-
-    {
-        let _it = stdx::timeit("dot completion");
-        let _span = profile::cpu_span();
-        let analysis = host.analysis();
-        let config = CompletionConfig {
-            enable_postfix_completions: true,
-            enable_imports_on_the_fly: true,
-            add_call_parenthesis: true,
-            add_call_argument_snippets: true,
-            snippet_cap: SnippetCap::new(true),
-            insert_use: InsertUseConfig {
-                merge: Some(MergeBehavior::Full),
-                prefix_kind: hir::PrefixKind::ByCrate,
-                group: true,
-            },
-        };
-        let position =
-            FilePosition { file_id, offset: TextSize::try_from(completion_offset).unwrap() };
-        analysis.completions(&config, position).unwrap();
-    }
-}
-
-fn patch(what: &mut String, from: &str, to: &str) -> usize {
-    let idx = what.find(from).unwrap();
-    *what = what.replacen(from, to, 1);
-    idx
-}
diff --git a/crates/rust-analyzer/src/lib.rs b/crates/rust-analyzer/src/lib.rs
index da7e24becd..d9a5030a0f 100644
--- a/crates/rust-analyzer/src/lib.rs
+++ b/crates/rust-analyzer/src/lib.rs
@@ -40,7 +40,7 @@ pub mod lsp_ext;
 pub mod config;
 
 #[cfg(test)]
-mod integrated_benchmarks;
+mod benchmarks;
 
 use serde::de::DeserializeOwned;
 use std::fmt;


Test patch:
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html b/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html
index 8d83ba2065..638f42c2f0 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html
@@ -142,7 +142,6 @@ It is beyond me why you'd use these when you got ///
 ```rust
 </span><span class="keyword injected">let</span><span class="none injected"> </span><span class="punctuation injected">_</span><span class="none injected"> </span><span class="operator injected">=</span><span class="none injected"> </span><span class="function injected">example</span><span class="parenthesis injected">(</span><span class="operator injected">&</span><span class="bracket injected">[</span><span class="numeric_literal injected">1</span><span class="comma injected">,</span><span class="none injected"> </span><span class="numeric_literal injected">2</span><span class="comma injected">,</span><span class="none injected"> </span><span class="numeric_literal injected">3</span><span class="bracket injected">]</span><span class="parenthesis injected">)</span><span class="semicolon injected">;</span><span class="comment documentation">
 ```
-</span><span class="function documentation injected intra_doc_link">[`block_comments2`]</span><span class="comment documentation"> tests these with indentation
  */</span>
 <span class="keyword">pub</span> <span class="keyword">fn</span> <span class="function declaration">block_comments</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span>
 
@@ -151,6 +150,5 @@ It is beyond me why you'd use these when you got ///
     ```rust
 </span><span class="comment documentation"> </span><span class="none injected">   </span><span class="keyword injected">let</span><span class="none injected"> </span><span class="punctuation injected">_</span><span class="none injected"> </span><span class="operator injected">=</span><span class="none injected"> </span><span class="function injected">example</span><span class="parenthesis injected">(</span><span class="operator injected">&</span><span class="bracket injected">[</span><span class="numeric_literal injected">1</span><span class="comma injected">,</span><span class="none injected"> </span><span class="numeric_literal injected">2</span><span class="comma injected">,</span><span class="none injected"> </span><span class="numeric_literal injected">3</span><span class="bracket injected">]</span><span class="parenthesis injected">)</span><span class="semicolon injected">;</span><span class="comment documentation">
     ```
-    </span><span class="function documentation injected intra_doc_link">[`block_comments`]</span><span class="comment documentation"> tests these without indentation
 */</span>
 <span class="keyword">pub</span> <span class="keyword">fn</span> <span class="function declaration">block_comments2</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span></code></pre>
\ No newline at end of file
diff --git a/crates/ide/src/syntax_highlighting/tests.rs b/crates/ide/src/syntax_highlighting/tests.rs
index b6e952b088..17cc6334b9 100644
--- a/crates/ide/src/syntax_highlighting/tests.rs
+++ b/crates/ide/src/syntax_highlighting/tests.rs
@@ -618,7 +618,6 @@ It is beyond me why you'd use these when you got ///
 ```rust
 let _ = example(&[1, 2, 3]);
 ```
-[`block_comments2`] tests these with indentation
  */
 pub fn block_comments() {}
 
@@ -627,7 +626,6 @@ pub fn block_comments() {}
     ```rust
     let _ = example(&[1, 2, 3]);
     ```
-    [`block_comments`] tests these without indentation
 */
 pub fn block_comments2() {}
 "#