aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock7
-rw-r--r--Cargo.toml1
-rw-r--r--crates/hir_def/src/attr.rs144
-rw-r--r--crates/hir_def/src/body/lower.rs24
-rw-r--r--crates/hir_def/src/body/scope.rs2
-rw-r--r--crates/hir_def/src/expr.rs4
-rw-r--r--crates/hir_def/src/lib.rs6
-rw-r--r--crates/hir_def/src/nameres/collector.rs4
-rw-r--r--crates/hir_expand/src/builtin_derive.rs4
-rw-r--r--crates/hir_expand/src/db.rs18
-rw-r--r--crates/hir_expand/src/input.rs94
-rw-r--r--crates/hir_expand/src/lib.rs23
-rw-r--r--crates/hir_expand/src/proc_macro.rs102
-rw-r--r--crates/hir_ty/src/diagnostics/expr.rs2
-rw-r--r--crates/hir_ty/src/infer/expr.rs2
-rw-r--r--crates/hir_ty/src/tests/regression.rs49
-rw-r--r--crates/ide/Cargo.toml1
-rw-r--r--crates/ide/src/lib.rs6
-rw-r--r--crates/ide/src/view_crate_graph.rs90
-rw-r--r--crates/ide_assists/src/handlers/merge_imports.rs4
-rw-r--r--crates/ide_assists/src/handlers/pull_assignment_up.rs50
-rw-r--r--crates/ide_assists/src/tests.rs2
-rw-r--r--crates/ide_completion/src/test_utils.rs2
-rw-r--r--crates/ide_db/src/helpers/insert_use/tests.rs84
-rw-r--r--crates/ide_db/src/helpers/merge_imports.rs14
-rw-r--r--crates/project_model/src/cargo_workspace.rs34
-rw-r--r--crates/project_model/src/workspace.rs39
-rw-r--r--crates/rust-analyzer/src/cargo_target_spec.rs2
-rw-r--r--crates/rust-analyzer/src/config.rs20
-rw-r--r--crates/rust-analyzer/src/handlers.rs22
-rw-r--r--crates/rust-analyzer/src/integrated_benchmarks.rs4
-rw-r--r--crates/rust-analyzer/src/lsp_ext.rs8
-rw-r--r--crates/rust-analyzer/src/main_loop.rs1
-rw-r--r--docs/dev/lsp-extensions.md12
-rw-r--r--docs/user/generated_config.adoc2
-rw-r--r--editors/code/package.json17
-rw-r--r--editors/code/src/commands.ts8
-rw-r--r--editors/code/src/lsp_ext.ts2
-rw-r--r--editors/code/src/main.ts1
39 files changed, 623 insertions, 288 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 0e1234b72..f9c34547e 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -320,6 +320,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
320checksum = "fc4b29f4b9bb94bf267d57269fd0706d343a160937108e9619fe380645428abb" 320checksum = "fc4b29f4b9bb94bf267d57269fd0706d343a160937108e9619fe380645428abb"
321 321
322[[package]] 322[[package]]
323name = "dot"
324version = "0.1.4"
325source = "registry+https://github.com/rust-lang/crates.io-index"
326checksum = "a74b6c4d4a1cff5f454164363c16b72fa12463ca6b31f4b5f2035a65fa3d5906"
327
328[[package]]
323name = "drop_bomb" 329name = "drop_bomb"
324version = "0.1.5" 330version = "0.1.5"
325source = "registry+https://github.com/rust-lang/crates.io-index" 331source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -588,6 +594,7 @@ version = "0.0.0"
588dependencies = [ 594dependencies = [
589 "cfg", 595 "cfg",
590 "cov-mark", 596 "cov-mark",
597 "dot",
591 "either", 598 "either",
592 "expect-test", 599 "expect-test",
593 "hir", 600 "hir",
diff --git a/Cargo.toml b/Cargo.toml
index cf3013c08..498cf7d62 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,4 +1,5 @@
1[workspace] 1[workspace]
2resolver = "2"
2members = ["xtask/", "lib/*", "crates/*"] 3members = ["xtask/", "lib/*", "crates/*"]
3 4
4[profile.dev] 5[profile.dev]
diff --git a/crates/hir_def/src/attr.rs b/crates/hir_def/src/attr.rs
index a2479016e..aadd4e44a 100644
--- a/crates/hir_def/src/attr.rs
+++ b/crates/hir_def/src/attr.rs
@@ -9,7 +9,7 @@ use std::{
9use base_db::CrateId; 9use base_db::CrateId;
10use cfg::{CfgExpr, CfgOptions}; 10use cfg::{CfgExpr, CfgOptions};
11use either::Either; 11use either::Either;
12use hir_expand::{hygiene::Hygiene, name::AsName, AstId, AttrId, InFile}; 12use hir_expand::{hygiene::Hygiene, name::AsName, AstId, InFile};
13use itertools::Itertools; 13use itertools::Itertools;
14use la_arena::ArenaMap; 14use la_arena::ArenaMap;
15use mbe::ast_to_token_tree; 15use mbe::ast_to_token_tree;
@@ -101,17 +101,13 @@ impl RawAttrs {
101 hygiene: &Hygiene, 101 hygiene: &Hygiene,
102 ) -> Self { 102 ) -> Self {
103 let entries = collect_attrs(owner) 103 let entries = collect_attrs(owner)
104 .enumerate() 104 .flat_map(|(id, attr)| match attr {
105 .flat_map(|(i, attr)| { 105 Either::Left(attr) => Attr::from_src(db, attr, hygiene, id),
106 let index = AttrId(i as u32); 106 Either::Right(comment) => comment.doc_comment().map(|doc| Attr {
107 match attr { 107 id,
108 Either::Left(attr) => Attr::from_src(db, attr, hygiene, index), 108 input: Some(AttrInput::Literal(SmolStr::new(doc))),
109 Either::Right(comment) => comment.doc_comment().map(|doc| Attr { 109 path: Interned::new(ModPath::from(hir_expand::name!(doc))),
110 id: index, 110 }),
111 input: Some(AttrInput::Literal(SmolStr::new(doc))),
112 path: Interned::new(ModPath::from(hir_expand::name!(doc))),
113 }),
114 }
115 }) 111 })
116 .collect::<Arc<_>>(); 112 .collect::<Arc<_>>();
117 113
@@ -124,6 +120,7 @@ impl RawAttrs {
124 } 120 }
125 121
126 pub(crate) fn merge(&self, other: Self) -> Self { 122 pub(crate) fn merge(&self, other: Self) -> Self {
123 // FIXME: This needs to fixup `AttrId`s
127 match (&self.entries, &other.entries) { 124 match (&self.entries, &other.entries) {
128 (None, None) => Self::EMPTY, 125 (None, None) => Self::EMPTY,
129 (Some(entries), None) | (None, Some(entries)) => { 126 (Some(entries), None) | (None, Some(entries)) => {
@@ -375,39 +372,26 @@ impl AttrsWithOwner {
375 372
376 let def_map = module.def_map(db); 373 let def_map = module.def_map(db);
377 let mod_data = &def_map[module.local_id]; 374 let mod_data = &def_map[module.local_id];
378 let attrs = match mod_data.declaration_source(db) { 375 match mod_data.declaration_source(db) {
379 Some(it) => { 376 Some(it) => {
380 let mut attrs: Vec<_> = collect_attrs(&it.value as &dyn ast::AttrsOwner) 377 let mut map = AttrSourceMap::new(InFile::new(it.file_id, &it.value));
381 .map(|attr| InFile::new(it.file_id, attr))
382 .collect();
383 if let InFile { file_id, value: ModuleSource::SourceFile(file) } = 378 if let InFile { file_id, value: ModuleSource::SourceFile(file) } =
384 mod_data.definition_source(db) 379 mod_data.definition_source(db)
385 { 380 {
386 attrs.extend( 381 map.merge(AttrSourceMap::new(InFile::new(file_id, &file)));
387 collect_attrs(&file as &dyn ast::AttrsOwner)
388 .map(|attr| InFile::new(file_id, attr)),
389 )
390 } 382 }
391 attrs 383 return map;
392 } 384 }
393 None => { 385 None => {
394 let InFile { file_id, value } = mod_data.definition_source(db); 386 let InFile { file_id, value } = mod_data.definition_source(db);
395 match &value { 387 let attrs_owner = match &value {
396 ModuleSource::SourceFile(file) => { 388 ModuleSource::SourceFile(file) => file as &dyn ast::AttrsOwner,
397 collect_attrs(file as &dyn ast::AttrsOwner) 389 ModuleSource::Module(module) => module as &dyn ast::AttrsOwner,
398 } 390 ModuleSource::BlockExpr(block) => block as &dyn ast::AttrsOwner,
399 ModuleSource::Module(module) => { 391 };
400 collect_attrs(module as &dyn ast::AttrsOwner) 392 return AttrSourceMap::new(InFile::new(file_id, attrs_owner));
401 }
402 ModuleSource::BlockExpr(block) => {
403 collect_attrs(block as &dyn ast::AttrsOwner)
404 }
405 }
406 .map(|attr| InFile::new(file_id, attr))
407 .collect()
408 } 393 }
409 }; 394 }
410 return AttrSourceMap { attrs };
411 } 395 }
412 AttrDefId::FieldId(id) => { 396 AttrDefId::FieldId(id) => {
413 let map = db.fields_attrs_source_map(id.parent); 397 let map = db.fields_attrs_source_map(id.parent);
@@ -462,11 +446,7 @@ impl AttrsWithOwner {
462 }, 446 },
463 }; 447 };
464 448
465 AttrSourceMap { 449 AttrSourceMap::new(owner.as_ref().map(|node| node as &dyn AttrsOwner))
466 attrs: collect_attrs(&owner.value)
467 .map(|attr| InFile::new(owner.file_id, attr))
468 .collect(),
469 }
470 } 450 }
471 451
472 pub fn docs_with_rangemap( 452 pub fn docs_with_rangemap(
@@ -518,7 +498,7 @@ impl AttrsWithOwner {
518 if buf.is_empty() { 498 if buf.is_empty() {
519 None 499 None
520 } else { 500 } else {
521 Some((Documentation(buf), DocsRangeMap { mapping, source: self.source_map(db).attrs })) 501 Some((Documentation(buf), DocsRangeMap { mapping, source_map: self.source_map(db) }))
522 } 502 }
523 } 503 }
524} 504}
@@ -559,27 +539,59 @@ fn inner_attributes(
559} 539}
560 540
561pub struct AttrSourceMap { 541pub struct AttrSourceMap {
562 attrs: Vec<InFile<Either<ast::Attr, ast::Comment>>>, 542 attrs: Vec<InFile<ast::Attr>>,
543 doc_comments: Vec<InFile<ast::Comment>>,
563} 544}
564 545
565impl AttrSourceMap { 546impl AttrSourceMap {
547 fn new(owner: InFile<&dyn ast::AttrsOwner>) -> Self {
548 let mut attrs = Vec::new();
549 let mut doc_comments = Vec::new();
550 for (_, attr) in collect_attrs(owner.value) {
551 match attr {
552 Either::Left(attr) => attrs.push(owner.with_value(attr)),
553 Either::Right(comment) => doc_comments.push(owner.with_value(comment)),
554 }
555 }
556
557 Self { attrs, doc_comments }
558 }
559
560 fn merge(&mut self, other: Self) {
561 self.attrs.extend(other.attrs);
562 self.doc_comments.extend(other.doc_comments);
563 }
564
566 /// Maps the lowered `Attr` back to its original syntax node. 565 /// Maps the lowered `Attr` back to its original syntax node.
567 /// 566 ///
568 /// `attr` must come from the `owner` used for AttrSourceMap 567 /// `attr` must come from the `owner` used for AttrSourceMap
569 /// 568 ///
570 /// Note that the returned syntax node might be a `#[cfg_attr]`, or a doc comment, instead of 569 /// Note that the returned syntax node might be a `#[cfg_attr]`, or a doc comment, instead of
571 /// the attribute represented by `Attr`. 570 /// the attribute represented by `Attr`.
572 pub fn source_of(&self, attr: &Attr) -> InFile<&Either<ast::Attr, ast::Comment>> { 571 pub fn source_of(&self, attr: &Attr) -> InFile<Either<ast::Attr, ast::Comment>> {
573 self.attrs 572 self.source_of_id(attr.id)
574 .get(attr.id.0 as usize) 573 }
575 .unwrap_or_else(|| panic!("cannot find `Attr` at index {:?}", attr.id)) 574
576 .as_ref() 575 fn source_of_id(&self, id: AttrId) -> InFile<Either<ast::Attr, ast::Comment>> {
576 if id.is_doc_comment {
577 self.doc_comments
578 .get(id.ast_index as usize)
579 .unwrap_or_else(|| panic!("cannot find doc comment at index {:?}", id))
580 .clone()
581 .map(|attr| Either::Right(attr))
582 } else {
583 self.attrs
584 .get(id.ast_index as usize)
585 .unwrap_or_else(|| panic!("cannot find `Attr` at index {:?}", id))
586 .clone()
587 .map(|attr| Either::Left(attr))
588 }
577 } 589 }
578} 590}
579 591
580/// A struct to map text ranges from [`Documentation`] back to TextRanges in the syntax tree. 592/// A struct to map text ranges from [`Documentation`] back to TextRanges in the syntax tree.
581pub struct DocsRangeMap { 593pub struct DocsRangeMap {
582 source: Vec<InFile<Either<ast::Attr, ast::Comment>>>, 594 source_map: AttrSourceMap,
583 // (docstring-line-range, attr_index, attr-string-range) 595 // (docstring-line-range, attr_index, attr-string-range)
584 // a mapping from the text range of a line of the [`Documentation`] to the attribute index and 596 // a mapping from the text range of a line of the [`Documentation`] to the attribute index and
585 // the original (untrimmed) syntax doc line 597 // the original (untrimmed) syntax doc line
@@ -596,7 +608,7 @@ impl DocsRangeMap {
596 608
597 let relative_range = range - line_docs_range.start(); 609 let relative_range = range - line_docs_range.start();
598 610
599 let &InFile { file_id, value: ref source } = &self.source[idx.0 as usize]; 611 let &InFile { file_id, value: ref source } = &self.source_map.source_of_id(idx);
600 match source { 612 match source {
601 Either::Left(_) => None, // FIXME, figure out a nice way to handle doc attributes here 613 Either::Left(_) => None, // FIXME, figure out a nice way to handle doc attributes here
602 // as well as for whats done in syntax highlight doc injection 614 // as well as for whats done in syntax highlight doc injection
@@ -615,6 +627,12 @@ impl DocsRangeMap {
615 } 627 }
616} 628}
617 629
630#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
631pub(crate) struct AttrId {
632 is_doc_comment: bool,
633 pub(crate) ast_index: u32,
634}
635
618#[derive(Debug, Clone, PartialEq, Eq)] 636#[derive(Debug, Clone, PartialEq, Eq)]
619pub struct Attr { 637pub struct Attr {
620 pub(crate) id: AttrId, 638 pub(crate) id: AttrId,
@@ -749,22 +767,32 @@ fn attrs_from_item_tree<N: ItemTreeNode>(id: ItemTreeId<N>, db: &dyn DefDatabase
749 767
750fn collect_attrs( 768fn collect_attrs(
751 owner: &dyn ast::AttrsOwner, 769 owner: &dyn ast::AttrsOwner,
752) -> impl Iterator<Item = Either<ast::Attr, ast::Comment>> { 770) -> impl Iterator<Item = (AttrId, Either<ast::Attr, ast::Comment>)> {
753 let (inner_attrs, inner_docs) = inner_attributes(owner.syntax()) 771 let (inner_attrs, inner_docs) = inner_attributes(owner.syntax())
754 .map_or((None, None), |(attrs, docs)| (Some(attrs), Some(docs))); 772 .map_or((None, None), |(attrs, docs)| (Some(attrs), Some(docs)));
755 773
756 let outer_attrs = owner.attrs().filter(|attr| attr.kind().is_outer()); 774 let outer_attrs = owner.attrs().filter(|attr| attr.kind().is_outer());
757 let attrs = outer_attrs 775 let attrs =
758 .chain(inner_attrs.into_iter().flatten()) 776 outer_attrs.chain(inner_attrs.into_iter().flatten()).enumerate().map(|(idx, attr)| {
759 .map(|attr| (attr.syntax().text_range().start(), Either::Left(attr))); 777 (
778 AttrId { ast_index: idx as u32, is_doc_comment: false },
779 attr.syntax().text_range().start(),
780 Either::Left(attr),
781 )
782 });
760 783
761 let outer_docs = 784 let outer_docs =
762 ast::CommentIter::from_syntax_node(owner.syntax()).filter(ast::Comment::is_outer); 785 ast::CommentIter::from_syntax_node(owner.syntax()).filter(ast::Comment::is_outer);
763 let docs = outer_docs 786 let docs =
764 .chain(inner_docs.into_iter().flatten()) 787 outer_docs.chain(inner_docs.into_iter().flatten()).enumerate().map(|(idx, docs_text)| {
765 .map(|docs_text| (docs_text.syntax().text_range().start(), Either::Right(docs_text))); 788 (
789 AttrId { ast_index: idx as u32, is_doc_comment: true },
790 docs_text.syntax().text_range().start(),
791 Either::Right(docs_text),
792 )
793 });
766 // sort here by syntax node offset because the source can have doc attributes and doc strings be interleaved 794 // sort here by syntax node offset because the source can have doc attributes and doc strings be interleaved
767 docs.chain(attrs).sorted_by_key(|&(offset, _)| offset).map(|(_, attr)| attr) 795 docs.chain(attrs).sorted_by_key(|&(_, offset, _)| offset).map(|(id, _, attr)| (id, attr))
768} 796}
769 797
770pub(crate) fn variants_attrs_source_map( 798pub(crate) fn variants_attrs_source_map(
diff --git a/crates/hir_def/src/body/lower.rs b/crates/hir_def/src/body/lower.rs
index 75dc19c11..9f278d35b 100644
--- a/crates/hir_def/src/body/lower.rs
+++ b/crates/hir_def/src/body/lower.rs
@@ -205,7 +205,7 @@ impl ExprCollector<'_> {
205 self.maybe_collect_expr(expr).unwrap_or_else(|| self.missing_expr()) 205 self.maybe_collect_expr(expr).unwrap_or_else(|| self.missing_expr())
206 } 206 }
207 207
208 /// Returns `None` if the expression is `#[cfg]`d out. 208 /// Returns `None` if and only if the expression is `#[cfg]`d out.
209 fn maybe_collect_expr(&mut self, expr: ast::Expr) -> Option<ExprId> { 209 fn maybe_collect_expr(&mut self, expr: ast::Expr) -> Option<ExprId> {
210 let syntax_ptr = AstPtr::new(&expr); 210 let syntax_ptr = AstPtr::new(&expr);
211 self.check_cfg(&expr)?; 211 self.check_cfg(&expr)?;
@@ -668,7 +668,7 @@ impl ExprCollector<'_> {
668 if self.check_cfg(&stmt).is_none() { 668 if self.check_cfg(&stmt).is_none() {
669 return; 669 return;
670 } 670 }
671 671 let has_semi = stmt.semicolon_token().is_some();
672 // Note that macro could be expended to multiple statements 672 // Note that macro could be expended to multiple statements
673 if let Some(ast::Expr::MacroCall(m)) = stmt.expr() { 673 if let Some(ast::Expr::MacroCall(m)) = stmt.expr() {
674 let macro_ptr = AstPtr::new(&m); 674 let macro_ptr = AstPtr::new(&m);
@@ -685,18 +685,19 @@ impl ExprCollector<'_> {
685 statements.statements().for_each(|stmt| this.collect_stmt(stmt)); 685 statements.statements().for_each(|stmt| this.collect_stmt(stmt));
686 if let Some(expr) = statements.expr() { 686 if let Some(expr) = statements.expr() {
687 let expr = this.collect_expr(expr); 687 let expr = this.collect_expr(expr);
688 this.statements_in_scope.push(Statement::Expr(expr)); 688 this.statements_in_scope
689 .push(Statement::Expr { expr, has_semi });
689 } 690 }
690 } 691 }
691 None => { 692 None => {
692 let expr = this.alloc_expr(Expr::Missing, syntax_ptr.clone()); 693 let expr = this.alloc_expr(Expr::Missing, syntax_ptr.clone());
693 this.statements_in_scope.push(Statement::Expr(expr)); 694 this.statements_in_scope.push(Statement::Expr { expr, has_semi });
694 } 695 }
695 }, 696 },
696 ); 697 );
697 } else { 698 } else {
698 let expr = self.collect_expr_opt(stmt.expr()); 699 let expr = self.collect_expr_opt(stmt.expr());
699 self.statements_in_scope.push(Statement::Expr(expr)); 700 self.statements_in_scope.push(Statement::Expr { expr, has_semi });
700 } 701 }
701 } 702 }
702 ast::Stmt::Item(item) => { 703 ast::Stmt::Item(item) => {
@@ -725,8 +726,17 @@ impl ExprCollector<'_> {
725 let prev_statements = std::mem::take(&mut self.statements_in_scope); 726 let prev_statements = std::mem::take(&mut self.statements_in_scope);
726 727
727 block.statements().for_each(|s| self.collect_stmt(s)); 728 block.statements().for_each(|s| self.collect_stmt(s));
728 729 block.tail_expr().and_then(|e| {
729 let tail = block.tail_expr().map(|e| self.collect_expr(e)); 730 let expr = self.maybe_collect_expr(e)?;
731 Some(self.statements_in_scope.push(Statement::Expr { expr, has_semi: false }))
732 });
733
734 let mut tail = None;
735 if let Some(Statement::Expr { expr, has_semi: false }) = self.statements_in_scope.last() {
736 tail = Some(*expr);
737 self.statements_in_scope.pop();
738 }
739 let tail = tail;
730 let statements = std::mem::replace(&mut self.statements_in_scope, prev_statements); 740 let statements = std::mem::replace(&mut self.statements_in_scope, prev_statements);
731 let syntax_node_ptr = AstPtr::new(&block.into()); 741 let syntax_node_ptr = AstPtr::new(&block.into());
732 let expr_id = self.alloc_expr( 742 let expr_id = self.alloc_expr(
diff --git a/crates/hir_def/src/body/scope.rs b/crates/hir_def/src/body/scope.rs
index bd7005ca6..6764de3a7 100644
--- a/crates/hir_def/src/body/scope.rs
+++ b/crates/hir_def/src/body/scope.rs
@@ -157,7 +157,7 @@ fn compute_block_scopes(
157 scope = scopes.new_scope(scope); 157 scope = scopes.new_scope(scope);
158 scopes.add_bindings(body, scope, *pat); 158 scopes.add_bindings(body, scope, *pat);
159 } 159 }
160 Statement::Expr(expr) => { 160 Statement::Expr { expr, .. } => {
161 scopes.set_scope(*expr, scope); 161 scopes.set_scope(*expr, scope);
162 compute_expr_scopes(*expr, body, scopes, scope); 162 compute_expr_scopes(*expr, body, scopes, scope);
163 } 163 }
diff --git a/crates/hir_def/src/expr.rs b/crates/hir_def/src/expr.rs
index b4ad984bd..0c3b41080 100644
--- a/crates/hir_def/src/expr.rs
+++ b/crates/hir_def/src/expr.rs
@@ -242,7 +242,7 @@ pub struct RecordLitField {
242#[derive(Debug, Clone, Eq, PartialEq)] 242#[derive(Debug, Clone, Eq, PartialEq)]
243pub enum Statement { 243pub enum Statement {
244 Let { pat: PatId, type_ref: Option<Interned<TypeRef>>, initializer: Option<ExprId> }, 244 Let { pat: PatId, type_ref: Option<Interned<TypeRef>>, initializer: Option<ExprId> },
245 Expr(ExprId), 245 Expr { expr: ExprId, has_semi: bool },
246} 246}
247 247
248impl Expr { 248impl Expr {
@@ -265,7 +265,7 @@ impl Expr {
265 f(*expr); 265 f(*expr);
266 } 266 }
267 } 267 }
268 Statement::Expr(e) => f(*e), 268 Statement::Expr { expr: expression, .. } => f(*expression),
269 } 269 }
270 } 270 }
271 if let Some(expr) = tail { 271 if let Some(expr) = tail {
diff --git a/crates/hir_def/src/lib.rs b/crates/hir_def/src/lib.rs
index e96ca953f..a82ea5957 100644
--- a/crates/hir_def/src/lib.rs
+++ b/crates/hir_def/src/lib.rs
@@ -62,14 +62,14 @@ use hir_expand::{
62 ast_id_map::FileAstId, 62 ast_id_map::FileAstId,
63 eager::{expand_eager_macro, ErrorEmitted, ErrorSink}, 63 eager::{expand_eager_macro, ErrorEmitted, ErrorSink},
64 hygiene::Hygiene, 64 hygiene::Hygiene,
65 AstId, AttrId, FragmentKind, HirFileId, InFile, MacroCallId, MacroCallKind, MacroDefId, 65 AstId, FragmentKind, HirFileId, InFile, MacroCallId, MacroCallKind, MacroDefId, MacroDefKind,
66 MacroDefKind,
67}; 66};
68use la_arena::Idx; 67use la_arena::Idx;
69use nameres::DefMap; 68use nameres::DefMap;
70use path::ModPath; 69use path::ModPath;
71use syntax::ast; 70use syntax::ast;
72 71
72use crate::attr::AttrId;
73use crate::builtin_type::BuiltinType; 73use crate::builtin_type::BuiltinType;
74use item_tree::{ 74use item_tree::{
75 Const, Enum, Function, Impl, ItemTreeId, ItemTreeNode, ModItem, Static, Struct, Trait, 75 Const, Enum, Function, Impl, ItemTreeId, ItemTreeNode, ModItem, Static, Struct, Trait,
@@ -753,7 +753,7 @@ fn derive_macro_as_call_id(
753 MacroCallKind::Derive { 753 MacroCallKind::Derive {
754 ast_id: item_attr.ast_id, 754 ast_id: item_attr.ast_id,
755 derive_name: last_segment.to_string(), 755 derive_name: last_segment.to_string(),
756 derive_attr, 756 derive_attr_index: derive_attr.ast_index,
757 }, 757 },
758 ) 758 )
759 .into(); 759 .into();
diff --git a/crates/hir_def/src/nameres/collector.rs b/crates/hir_def/src/nameres/collector.rs
index e89136ed1..adfb78c94 100644
--- a/crates/hir_def/src/nameres/collector.rs
+++ b/crates/hir_def/src/nameres/collector.rs
@@ -13,14 +13,14 @@ use hir_expand::{
13 builtin_macro::find_builtin_macro, 13 builtin_macro::find_builtin_macro,
14 name::{AsName, Name}, 14 name::{AsName, Name},
15 proc_macro::ProcMacroExpander, 15 proc_macro::ProcMacroExpander,
16 AttrId, FragmentKind, HirFileId, MacroCallId, MacroCallKind, MacroDefId, MacroDefKind, 16 FragmentKind, HirFileId, MacroCallId, MacroCallKind, MacroDefId, MacroDefKind,
17}; 17};
18use hir_expand::{InFile, MacroCallLoc}; 18use hir_expand::{InFile, MacroCallLoc};
19use rustc_hash::{FxHashMap, FxHashSet}; 19use rustc_hash::{FxHashMap, FxHashSet};
20use syntax::ast; 20use syntax::ast;
21 21
22use crate::{ 22use crate::{
23 attr::Attrs, 23 attr::{AttrId, Attrs},
24 db::DefDatabase, 24 db::DefDatabase,
25 derive_macro_as_call_id, 25 derive_macro_as_call_id,
26 intern::Interned, 26 intern::Interned,
diff --git a/crates/hir_expand/src/builtin_derive.rs b/crates/hir_expand/src/builtin_derive.rs
index 537c03028..b6a6d602f 100644
--- a/crates/hir_expand/src/builtin_derive.rs
+++ b/crates/hir_expand/src/builtin_derive.rs
@@ -269,7 +269,7 @@ mod tests {
269 use expect_test::{expect, Expect}; 269 use expect_test::{expect, Expect};
270 use name::AsName; 270 use name::AsName;
271 271
272 use crate::{test_db::TestDB, AstId, AttrId, MacroCallId, MacroCallKind, MacroCallLoc}; 272 use crate::{test_db::TestDB, AstId, MacroCallId, MacroCallKind, MacroCallLoc};
273 273
274 use super::*; 274 use super::*;
275 275
@@ -320,7 +320,7 @@ $0
320 kind: MacroCallKind::Derive { 320 kind: MacroCallKind::Derive {
321 ast_id, 321 ast_id,
322 derive_name: name.to_string(), 322 derive_name: name.to_string(),
323 derive_attr: AttrId(0), 323 derive_attr_index: 0,
324 }, 324 },
325 }; 325 };
326 326
diff --git a/crates/hir_expand/src/db.rs b/crates/hir_expand/src/db.rs
index 6647e57e7..c43d382ad 100644
--- a/crates/hir_expand/src/db.rs
+++ b/crates/hir_expand/src/db.rs
@@ -12,9 +12,9 @@ use syntax::{
12}; 12};
13 13
14use crate::{ 14use crate::{
15 ast_id_map::AstIdMap, hygiene::HygieneFrame, BuiltinDeriveExpander, BuiltinFnLikeExpander, 15 ast_id_map::AstIdMap, hygiene::HygieneFrame, input::process_macro_input, BuiltinDeriveExpander,
16 EagerCallLoc, EagerMacroId, HirFileId, HirFileIdRepr, LazyMacroId, MacroCallId, MacroCallLoc, 16 BuiltinFnLikeExpander, EagerCallLoc, EagerMacroId, HirFileId, HirFileIdRepr, LazyMacroId,
17 MacroDefId, MacroDefKind, MacroFile, ProcMacroExpander, 17 MacroCallId, MacroCallLoc, MacroDefId, MacroDefKind, MacroFile, ProcMacroExpander,
18}; 18};
19 19
20/// Total limit on the number of tokens produced by any macro invocation. 20/// Total limit on the number of tokens produced by any macro invocation.
@@ -267,7 +267,16 @@ fn parse_macro_expansion(
267 267
268fn macro_arg(db: &dyn AstDatabase, id: MacroCallId) -> Option<Arc<(tt::Subtree, mbe::TokenMap)>> { 268fn macro_arg(db: &dyn AstDatabase, id: MacroCallId) -> Option<Arc<(tt::Subtree, mbe::TokenMap)>> {
269 let arg = db.macro_arg_text(id)?; 269 let arg = db.macro_arg_text(id)?;
270 let (tt, tmap) = mbe::syntax_node_to_token_tree(&SyntaxNode::new_root(arg)); 270 let (mut tt, tmap) = mbe::syntax_node_to_token_tree(&SyntaxNode::new_root(arg));
271
272 if let MacroCallId::LazyMacro(id) = id {
273 let loc: MacroCallLoc = db.lookup_intern_macro(id);
274 if loc.def.is_proc_macro() {
275 // proc macros expect their inputs without parentheses, MBEs expect it with them included
276 tt.delimiter = None;
277 }
278 }
279
271 Some(Arc::new((tt, tmap))) 280 Some(Arc::new((tt, tmap)))
272} 281}
273 282
@@ -281,6 +290,7 @@ fn macro_arg_text(db: &dyn AstDatabase, id: MacroCallId) -> Option<GreenNode> {
281 }; 290 };
282 let loc = db.lookup_intern_macro(id); 291 let loc = db.lookup_intern_macro(id);
283 let arg = loc.kind.arg(db)?; 292 let arg = loc.kind.arg(db)?;
293 let arg = process_macro_input(db, arg, id);
284 Some(arg.green().into()) 294 Some(arg.green().into())
285} 295}
286 296
diff --git a/crates/hir_expand/src/input.rs b/crates/hir_expand/src/input.rs
new file mode 100644
index 000000000..112216859
--- /dev/null
+++ b/crates/hir_expand/src/input.rs
@@ -0,0 +1,94 @@
1//! Macro input conditioning.
2
3use syntax::{
4 ast::{self, AttrsOwner},
5 AstNode, SyntaxNode,
6};
7
8use crate::{
9 db::AstDatabase,
10 name::{name, AsName},
11 LazyMacroId, MacroCallKind, MacroCallLoc,
12};
13
14pub(crate) fn process_macro_input(
15 db: &dyn AstDatabase,
16 node: SyntaxNode,
17 id: LazyMacroId,
18) -> SyntaxNode {
19 let loc: MacroCallLoc = db.lookup_intern_macro(id);
20
21 match loc.kind {
22 MacroCallKind::FnLike { .. } => node,
23 MacroCallKind::Derive { derive_attr_index, .. } => {
24 let item = match ast::Item::cast(node.clone()) {
25 Some(item) => item,
26 None => return node,
27 };
28
29 remove_derives_up_to(item, derive_attr_index as usize).syntax().clone()
30 }
31 }
32}
33
34/// Removes `#[derive]` attributes from `item`, up to `attr_index`.
35fn remove_derives_up_to(item: ast::Item, attr_index: usize) -> ast::Item {
36 let item = item.clone_for_update();
37 for attr in item.attrs().take(attr_index + 1) {
38 if let Some(name) =
39 attr.path().and_then(|path| path.as_single_segment()).and_then(|seg| seg.name_ref())
40 {
41 if name.as_name() == name![derive] {
42 attr.syntax().detach();
43 }
44 }
45 }
46 item
47}
48
49#[cfg(test)]
50mod tests {
51 use base_db::fixture::WithFixture;
52 use base_db::SourceDatabase;
53 use expect_test::{expect, Expect};
54
55 use crate::test_db::TestDB;
56
57 use super::*;
58
59 fn test_remove_derives_up_to(attr: usize, ra_fixture: &str, expect: Expect) {
60 let (db, file_id) = TestDB::with_single_file(&ra_fixture);
61 let parsed = db.parse(file_id);
62
63 let mut items: Vec<_> =
64 parsed.syntax_node().descendants().filter_map(ast::Item::cast).collect();
65 assert_eq!(items.len(), 1);
66
67 let item = remove_derives_up_to(items.pop().unwrap(), attr);
68 expect.assert_eq(&item.to_string());
69 }
70
71 #[test]
72 fn remove_derive() {
73 test_remove_derives_up_to(
74 2,
75 r#"
76#[allow(unused)]
77#[derive(Copy)]
78#[derive(Hello)]
79#[derive(Clone)]
80struct A {
81 bar: u32
82}
83 "#,
84 expect![[r#"
85#[allow(unused)]
86
87
88#[derive(Clone)]
89struct A {
90 bar: u32
91}"#]],
92 );
93 }
94}
diff --git a/crates/hir_expand/src/lib.rs b/crates/hir_expand/src/lib.rs
index 80ab3aeee..88cb16ca4 100644
--- a/crates/hir_expand/src/lib.rs
+++ b/crates/hir_expand/src/lib.rs
@@ -14,6 +14,7 @@ pub mod builtin_macro;
14pub mod proc_macro; 14pub mod proc_macro;
15pub mod quote; 15pub mod quote;
16pub mod eager; 16pub mod eager;
17mod input;
17 18
18use either::Either; 19use either::Either;
19 20
@@ -271,6 +272,10 @@ impl MacroDefId {
271 }; 272 };
272 Either::Left(*id) 273 Either::Left(*id)
273 } 274 }
275
276 pub fn is_proc_macro(&self) -> bool {
277 matches!(self.kind, MacroDefKind::ProcMacro(..))
278 }
274} 279}
275 280
276#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 281#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
@@ -292,13 +297,21 @@ pub struct MacroCallLoc {
292 297
293#[derive(Debug, Clone, PartialEq, Eq, Hash)] 298#[derive(Debug, Clone, PartialEq, Eq, Hash)]
294pub enum MacroCallKind { 299pub enum MacroCallKind {
295 FnLike { ast_id: AstId<ast::MacroCall>, fragment: FragmentKind }, 300 FnLike {
296 Derive { ast_id: AstId<ast::Item>, derive_name: String, derive_attr: AttrId }, 301 ast_id: AstId<ast::MacroCall>,
302 fragment: FragmentKind,
303 },
304 Derive {
305 ast_id: AstId<ast::Item>,
306 derive_name: String,
307 /// Syntactical index of the invoking `#[derive]` attribute.
308 ///
309 /// Outer attributes are counted first, then inner attributes. This does not support
310 /// out-of-line modules, which may have attributes spread across 2 files!
311 derive_attr_index: u32,
312 },
297} 313}
298 314
299#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
300pub struct AttrId(pub u32);
301
302impl MacroCallKind { 315impl MacroCallKind {
303 fn file_id(&self) -> HirFileId { 316 fn file_id(&self) -> HirFileId {
304 match self { 317 match self {
diff --git a/crates/hir_expand/src/proc_macro.rs b/crates/hir_expand/src/proc_macro.rs
index 75e950816..d5643393a 100644
--- a/crates/hir_expand/src/proc_macro.rs
+++ b/crates/hir_expand/src/proc_macro.rs
@@ -2,7 +2,6 @@
2 2
3use crate::db::AstDatabase; 3use crate::db::AstDatabase;
4use base_db::{CrateId, ProcMacroId}; 4use base_db::{CrateId, ProcMacroId};
5use tt::buffer::{Cursor, TokenBuffer};
6 5
7#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] 6#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
8pub struct ProcMacroExpander { 7pub struct ProcMacroExpander {
@@ -44,9 +43,6 @@ impl ProcMacroExpander {
44 .clone() 43 .clone()
45 .ok_or_else(|| err!("No derive macro found."))?; 44 .ok_or_else(|| err!("No derive macro found."))?;
46 45
47 let tt = remove_derive_attrs(tt)
48 .ok_or_else(|| err!("Fail to remove derive for custom derive"))?;
49
50 // Proc macros have access to the environment variables of the invoking crate. 46 // Proc macros have access to the environment variables of the invoking crate.
51 let env = &krate_graph[calling_crate].env; 47 let env = &krate_graph[calling_crate].env;
52 48
@@ -56,101 +52,3 @@ impl ProcMacroExpander {
56 } 52 }
57 } 53 }
58} 54}
59
60fn eat_punct(cursor: &mut Cursor, c: char) -> bool {
61 if let Some(tt::buffer::TokenTreeRef::Leaf(tt::Leaf::Punct(punct), _)) = cursor.token_tree() {
62 if punct.char == c {
63 *cursor = cursor.bump();
64 return true;
65 }
66 }
67 false
68}
69
70fn eat_subtree(cursor: &mut Cursor, kind: tt::DelimiterKind) -> bool {
71 if let Some(tt::buffer::TokenTreeRef::Subtree(subtree, _)) = cursor.token_tree() {
72 if Some(kind) == subtree.delimiter_kind() {
73 *cursor = cursor.bump_subtree();
74 return true;
75 }
76 }
77 false
78}
79
80fn eat_ident(cursor: &mut Cursor, t: &str) -> bool {
81 if let Some(tt::buffer::TokenTreeRef::Leaf(tt::Leaf::Ident(ident), _)) = cursor.token_tree() {
82 if t == ident.text.as_str() {
83 *cursor = cursor.bump();
84 return true;
85 }
86 }
87 false
88}
89
90fn remove_derive_attrs(tt: &tt::Subtree) -> Option<tt::Subtree> {
91 let buffer = TokenBuffer::from_tokens(&tt.token_trees);
92 let mut p = buffer.begin();
93 let mut result = tt::Subtree::default();
94
95 while !p.eof() {
96 let curr = p;
97
98 if eat_punct(&mut p, '#') {
99 eat_punct(&mut p, '!');
100 let parent = p;
101 if eat_subtree(&mut p, tt::DelimiterKind::Bracket) {
102 if eat_ident(&mut p, "derive") {
103 p = parent.bump();
104 continue;
105 }
106 }
107 }
108
109 result.token_trees.push(curr.token_tree()?.cloned());
110 p = curr.bump();
111 }
112
113 Some(result)
114}
115
116#[cfg(test)]
117mod tests {
118 use super::*;
119 use test_utils::assert_eq_text;
120
121 #[test]
122 fn test_remove_derive_attrs() {
123 let tt = mbe::parse_to_token_tree(
124 r#"
125 #[allow(unused)]
126 #[derive(Copy)]
127 #[derive(Hello)]
128 struct A {
129 bar: u32
130 }
131"#,
132 )
133 .unwrap()
134 .0;
135 let result = format!("{:#?}", remove_derive_attrs(&tt).unwrap());
136
137 assert_eq_text!(
138 r#"
139SUBTREE $
140 PUNCH # [alone] 0
141 SUBTREE [] 1
142 IDENT allow 2
143 SUBTREE () 3
144 IDENT unused 4
145 IDENT struct 15
146 IDENT A 16
147 SUBTREE {} 17
148 IDENT bar 18
149 PUNCH : [alone] 19
150 IDENT u32 20
151"#
152 .trim(),
153 &result
154 );
155 }
156}
diff --git a/crates/hir_ty/src/diagnostics/expr.rs b/crates/hir_ty/src/diagnostics/expr.rs
index 79602c3dd..47709c1e8 100644
--- a/crates/hir_ty/src/diagnostics/expr.rs
+++ b/crates/hir_ty/src/diagnostics/expr.rs
@@ -83,7 +83,7 @@ impl<'a, 'b> ExprValidator<'a, 'b> {
83 if let Expr::Block { statements, tail, .. } = body_expr { 83 if let Expr::Block { statements, tail, .. } = body_expr {
84 if let Some(t) = tail { 84 if let Some(t) = tail {
85 self.validate_results_in_tail_expr(body.body_expr, *t, db); 85 self.validate_results_in_tail_expr(body.body_expr, *t, db);
86 } else if let Some(Statement::Expr(id)) = statements.last() { 86 } else if let Some(Statement::Expr { expr: id, .. }) = statements.last() {
87 self.validate_missing_tail_expr(body.body_expr, *id, db); 87 self.validate_missing_tail_expr(body.body_expr, *id, db);
88 } 88 }
89 } 89 }
diff --git a/crates/hir_ty/src/infer/expr.rs b/crates/hir_ty/src/infer/expr.rs
index 50497eecb..9476e6297 100644
--- a/crates/hir_ty/src/infer/expr.rs
+++ b/crates/hir_ty/src/infer/expr.rs
@@ -809,7 +809,7 @@ impl<'a> InferenceContext<'a> {
809 let ty = self.resolve_ty_as_possible(ty); 809 let ty = self.resolve_ty_as_possible(ty);
810 self.infer_pat(*pat, &ty, BindingMode::default()); 810 self.infer_pat(*pat, &ty, BindingMode::default());
811 } 811 }
812 Statement::Expr(expr) => { 812 Statement::Expr { expr, .. } => {
813 self.infer_expr(*expr, &Expectation::none()); 813 self.infer_expr(*expr, &Expectation::none());
814 } 814 }
815 } 815 }
diff --git a/crates/hir_ty/src/tests/regression.rs b/crates/hir_ty/src/tests/regression.rs
index d14f5c9bb..e23bd4da9 100644
--- a/crates/hir_ty/src/tests/regression.rs
+++ b/crates/hir_ty/src/tests/regression.rs
@@ -1050,3 +1050,52 @@ fn test() {
1050 "#]], 1050 "#]],
1051 ); 1051 );
1052} 1052}
1053
1054#[test]
1055fn cfg_tail() {
1056 // https://github.com/rust-analyzer/rust-analyzer/issues/8378
1057 check_infer(
1058 r#"
1059 fn fake_tail(){
1060 { "first" }
1061 #[cfg(never)] 9
1062 }
1063 fn multiple_fake(){
1064 { "fake" }
1065 { "fake" }
1066 { "second" }
1067 #[cfg(never)] { 11 }
1068 #[cfg(never)] 12;
1069 #[cfg(never)] 13
1070 }
1071 fn no_normal_tail(){
1072 { "third" }
1073 #[cfg(never)] 14;
1074 #[cfg(never)] 15;
1075 }
1076 fn no_actual_tail(){
1077 { "fourth" };
1078 #[cfg(never)] 14;
1079 #[cfg(never)] 15
1080 }
1081 "#,
1082 expect![[r#"
1083 14..53 '{ ...)] 9 }': &str
1084 20..31 '{ "first" }': &str
1085 22..29 '"first"': &str
1086 72..190 '{ ...] 13 }': &str
1087 78..88 '{ "fake" }': &str
1088 80..86 '"fake"': &str
1089 93..103 '{ "fake" }': &str
1090 95..101 '"fake"': &str
1091 108..120 '{ "second" }': &str
1092 110..118 '"second"': &str
1093 210..273 '{ ... 15; }': &str
1094 216..227 '{ "third" }': &str
1095 218..225 '"third"': &str
1096 293..357 '{ ...] 15 }': ()
1097 299..311 '{ "fourth" }': &str
1098 301..309 '"fourth"': &str
1099 "#]],
1100 )
1101}
diff --git a/crates/ide/Cargo.toml b/crates/ide/Cargo.toml
index f04bcf531..88f3d09d3 100644
--- a/crates/ide/Cargo.toml
+++ b/crates/ide/Cargo.toml
@@ -20,6 +20,7 @@ oorandom = "11.1.2"
20pulldown-cmark-to-cmark = "6.0.0" 20pulldown-cmark-to-cmark = "6.0.0"
21pulldown-cmark = { version = "0.8.0", default-features = false } 21pulldown-cmark = { version = "0.8.0", default-features = false }
22url = "2.1.1" 22url = "2.1.1"
23dot = "0.1.4"
23 24
24stdx = { path = "../stdx", version = "0.0.0" } 25stdx = { path = "../stdx", version = "0.0.0" }
25syntax = { path = "../syntax", version = "0.0.0" } 26syntax = { path = "../syntax", version = "0.0.0" }
diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs
index 8e5b72044..db08547d1 100644
--- a/crates/ide/src/lib.rs
+++ b/crates/ide/src/lib.rs
@@ -49,6 +49,7 @@ mod syntax_tree;
49mod typing; 49mod typing;
50mod markdown_remove; 50mod markdown_remove;
51mod doc_links; 51mod doc_links;
52mod view_crate_graph;
52 53
53use std::sync::Arc; 54use std::sync::Arc;
54 55
@@ -287,6 +288,11 @@ impl Analysis {
287 self.with_db(|db| view_hir::view_hir(&db, position)) 288 self.with_db(|db| view_hir::view_hir(&db, position))
288 } 289 }
289 290
291 /// Renders the crate graph to GraphViz "dot" syntax.
292 pub fn view_crate_graph(&self) -> Cancelable<Result<String, String>> {
293 self.with_db(|db| view_crate_graph::view_crate_graph(&db))
294 }
295
290 pub fn expand_macro(&self, position: FilePosition) -> Cancelable<Option<ExpandedMacro>> { 296 pub fn expand_macro(&self, position: FilePosition) -> Cancelable<Option<ExpandedMacro>> {
291 self.with_db(|db| expand_macro::expand_macro(db, position)) 297 self.with_db(|db| expand_macro::expand_macro(db, position))
292 } 298 }
diff --git a/crates/ide/src/view_crate_graph.rs b/crates/ide/src/view_crate_graph.rs
new file mode 100644
index 000000000..df6cc8aed
--- /dev/null
+++ b/crates/ide/src/view_crate_graph.rs
@@ -0,0 +1,90 @@
1use std::sync::Arc;
2
3use dot::{Id, LabelText};
4use ide_db::{
5 base_db::{CrateGraph, CrateId, Dependency, SourceDatabase, SourceDatabaseExt},
6 RootDatabase,
7};
8use rustc_hash::FxHashSet;
9
10// Feature: View Crate Graph
11//
12// Renders the currently loaded crate graph as an SVG graphic. Requires the `dot` tool, which
13// is part of graphviz, to be installed.
14//
15// Only workspace crates are included, no crates.io dependencies or sysroot crates.
16//
17// |===
18// | Editor | Action Name
19//
20// | VS Code | **Rust Analyzer: View Crate Graph**
21// |===
22pub(crate) fn view_crate_graph(db: &RootDatabase) -> Result<String, String> {
23 let crate_graph = db.crate_graph();
24 let crates_to_render = crate_graph
25 .iter()
26 .filter(|krate| {
27 // Only render workspace crates
28 let root_id = db.file_source_root(crate_graph[*krate].root_file_id);
29 !db.source_root(root_id).is_library
30 })
31 .collect();
32 let graph = DotCrateGraph { graph: crate_graph, crates_to_render };
33
34 let mut dot = Vec::new();
35 dot::render(&graph, &mut dot).unwrap();
36 Ok(String::from_utf8(dot).unwrap())
37}
38
39struct DotCrateGraph {
40 graph: Arc<CrateGraph>,
41 crates_to_render: FxHashSet<CrateId>,
42}
43
44type Edge<'a> = (CrateId, &'a Dependency);
45
46impl<'a> dot::GraphWalk<'a, CrateId, Edge<'a>> for DotCrateGraph {
47 fn nodes(&'a self) -> dot::Nodes<'a, CrateId> {
48 self.crates_to_render.iter().copied().collect()
49 }
50
51 fn edges(&'a self) -> dot::Edges<'a, Edge<'a>> {
52 self.crates_to_render
53 .iter()
54 .flat_map(|krate| {
55 self.graph[*krate]
56 .dependencies
57 .iter()
58 .filter(|dep| self.crates_to_render.contains(&dep.crate_id))
59 .map(move |dep| (*krate, dep))
60 })
61 .collect()
62 }
63
64 fn source(&'a self, edge: &Edge<'a>) -> CrateId {
65 edge.0
66 }
67
68 fn target(&'a self, edge: &Edge<'a>) -> CrateId {
69 edge.1.crate_id
70 }
71}
72
73impl<'a> dot::Labeller<'a, CrateId, Edge<'a>> for DotCrateGraph {
74 fn graph_id(&'a self) -> Id<'a> {
75 Id::new("rust_analyzer_crate_graph").unwrap()
76 }
77
78 fn node_id(&'a self, n: &CrateId) -> Id<'a> {
79 Id::new(format!("_{}", n.0)).unwrap()
80 }
81
82 fn node_shape(&'a self, _node: &CrateId) -> Option<LabelText<'a>> {
83 Some(LabelText::LabelStr("box".into()))
84 }
85
86 fn node_label(&'a self, n: &CrateId) -> LabelText<'a> {
87 let name = self.graph[*n].display_name.as_ref().map_or("(unnamed crate)", |name| &*name);
88 LabelText::LabelStr(name.into())
89 }
90}
diff --git a/crates/ide_assists/src/handlers/merge_imports.rs b/crates/ide_assists/src/handlers/merge_imports.rs
index add7b8e37..3cd090737 100644
--- a/crates/ide_assists/src/handlers/merge_imports.rs
+++ b/crates/ide_assists/src/handlers/merge_imports.rs
@@ -27,14 +27,14 @@ pub(crate) fn merge_imports(acc: &mut Assists, ctx: &AssistContext) -> Option<()
27 if let Some(use_item) = tree.syntax().parent().and_then(ast::Use::cast) { 27 if let Some(use_item) = tree.syntax().parent().and_then(ast::Use::cast) {
28 let (merged, to_remove) = 28 let (merged, to_remove) =
29 next_prev().filter_map(|dir| neighbor(&use_item, dir)).find_map(|use_item2| { 29 next_prev().filter_map(|dir| neighbor(&use_item, dir)).find_map(|use_item2| {
30 try_merge_imports(&use_item, &use_item2, MergeBehavior::Full).zip(Some(use_item2)) 30 try_merge_imports(&use_item, &use_item2, MergeBehavior::Crate).zip(Some(use_item2))
31 })?; 31 })?;
32 32
33 imports = Some((use_item, merged, to_remove)); 33 imports = Some((use_item, merged, to_remove));
34 } else { 34 } else {
35 let (merged, to_remove) = 35 let (merged, to_remove) =
36 next_prev().filter_map(|dir| neighbor(&tree, dir)).find_map(|use_tree| { 36 next_prev().filter_map(|dir| neighbor(&tree, dir)).find_map(|use_tree| {
37 try_merge_trees(&tree, &use_tree, MergeBehavior::Full).zip(Some(use_tree)) 37 try_merge_trees(&tree, &use_tree, MergeBehavior::Crate).zip(Some(use_tree))
38 })?; 38 })?;
39 39
40 uses = Some((tree.clone(), merged, to_remove)) 40 uses = Some((tree.clone(), merged, to_remove))
diff --git a/crates/ide_assists/src/handlers/pull_assignment_up.rs b/crates/ide_assists/src/handlers/pull_assignment_up.rs
index 28d14b9c3..3128faa68 100644
--- a/crates/ide_assists/src/handlers/pull_assignment_up.rs
+++ b/crates/ide_assists/src/handlers/pull_assignment_up.rs
@@ -60,6 +60,12 @@ pub(crate) fn pull_assignment_up(acc: &mut Assists, ctx: &AssistContext) -> Opti
60 return None; 60 return None;
61 }; 61 };
62 62
63 if let Some(parent) = tgt.syntax().parent() {
64 if matches!(parent.kind(), syntax::SyntaxKind::BIN_EXPR | syntax::SyntaxKind::LET_STMT) {
65 return None;
66 }
67 }
68
63 acc.add( 69 acc.add(
64 AssistId("pull_assignment_up", AssistKind::RefactorExtract), 70 AssistId("pull_assignment_up", AssistKind::RefactorExtract),
65 "Pull assignment up", 71 "Pull assignment up",
@@ -74,7 +80,13 @@ pub(crate) fn pull_assignment_up(acc: &mut Assists, ctx: &AssistContext) -> Opti
74 let tgt = edit.make_ast_mut(tgt); 80 let tgt = edit.make_ast_mut(tgt);
75 81
76 for (stmt, rhs) in assignments { 82 for (stmt, rhs) in assignments {
77 ted::replace(stmt.syntax(), rhs.syntax()); 83 let mut stmt = stmt.syntax().clone();
84 if let Some(parent) = stmt.parent() {
85 if ast::ExprStmt::cast(parent.clone()).is_some() {
86 stmt = parent.clone();
87 }
88 }
89 ted::replace(stmt, rhs.syntax());
78 } 90 }
79 let assign_expr = make::expr_assignment(collector.common_lhs, tgt.clone()); 91 let assign_expr = make::expr_assignment(collector.common_lhs, tgt.clone());
80 let assign_stmt = make::expr_stmt(assign_expr); 92 let assign_stmt = make::expr_stmt(assign_expr);
@@ -87,7 +99,7 @@ pub(crate) fn pull_assignment_up(acc: &mut Assists, ctx: &AssistContext) -> Opti
87struct AssignmentsCollector<'a> { 99struct AssignmentsCollector<'a> {
88 sema: &'a hir::Semantics<'a, ide_db::RootDatabase>, 100 sema: &'a hir::Semantics<'a, ide_db::RootDatabase>,
89 common_lhs: ast::Expr, 101 common_lhs: ast::Expr,
90 assignments: Vec<(ast::ExprStmt, ast::Expr)>, 102 assignments: Vec<(ast::BinExpr, ast::Expr)>,
91} 103}
92 104
93impl<'a> AssignmentsCollector<'a> { 105impl<'a> AssignmentsCollector<'a> {
@@ -95,6 +107,7 @@ impl<'a> AssignmentsCollector<'a> {
95 for arm in match_expr.match_arm_list()?.arms() { 107 for arm in match_expr.match_arm_list()?.arms() {
96 match arm.expr()? { 108 match arm.expr()? {
97 ast::Expr::BlockExpr(block) => self.collect_block(&block)?, 109 ast::Expr::BlockExpr(block) => self.collect_block(&block)?,
110 ast::Expr::BinExpr(expr) => self.collect_expr(&expr)?,
98 _ => return None, 111 _ => return None,
99 } 112 }
100 } 113 }
@@ -114,24 +127,30 @@ impl<'a> AssignmentsCollector<'a> {
114 } 127 }
115 } 128 }
116 fn collect_block(&mut self, block: &ast::BlockExpr) -> Option<()> { 129 fn collect_block(&mut self, block: &ast::BlockExpr) -> Option<()> {
117 if block.tail_expr().is_some() { 130 let last_expr = block.tail_expr().or_else(|| {
118 return None; 131 if let ast::Stmt::ExprStmt(stmt) = block.statements().last()? {
119 } 132 stmt.expr()
120 133 } else {
121 let last_stmt = block.statements().last()?; 134 None
122 if let ast::Stmt::ExprStmt(stmt) = last_stmt {
123 if let ast::Expr::BinExpr(expr) = stmt.expr()? {
124 if expr.op_kind()? == ast::BinOp::Assignment
125 && is_equivalent(self.sema, &expr.lhs()?, &self.common_lhs)
126 {
127 self.assignments.push((stmt, expr.rhs()?));
128 return Some(());
129 }
130 } 135 }
136 })?;
137
138 if let ast::Expr::BinExpr(expr) = last_expr {
139 return self.collect_expr(&expr);
131 } 140 }
132 141
133 None 142 None
134 } 143 }
144
145 fn collect_expr(&mut self, expr: &ast::BinExpr) -> Option<()> {
146 if expr.op_kind()? == ast::BinOp::Assignment
147 && is_equivalent(self.sema, &expr.lhs()?, &self.common_lhs)
148 {
149 self.assignments.push((expr.clone(), expr.rhs()?));
150 return Some(());
151 }
152 None
153 }
135} 154}
136 155
137fn is_equivalent( 156fn is_equivalent(
@@ -241,7 +260,6 @@ fn foo() {
241 } 260 }
242 261
243 #[test] 262 #[test]
244 #[ignore]
245 fn test_pull_assignment_up_assignment_expressions() { 263 fn test_pull_assignment_up_assignment_expressions() {
246 check_assist( 264 check_assist(
247 pull_assignment_up, 265 pull_assignment_up,
diff --git a/crates/ide_assists/src/tests.rs b/crates/ide_assists/src/tests.rs
index 9c2847998..0d3969c36 100644
--- a/crates/ide_assists/src/tests.rs
+++ b/crates/ide_assists/src/tests.rs
@@ -21,7 +21,7 @@ pub(crate) const TEST_CONFIG: AssistConfig = AssistConfig {
21 snippet_cap: SnippetCap::new(true), 21 snippet_cap: SnippetCap::new(true),
22 allowed: None, 22 allowed: None,
23 insert_use: InsertUseConfig { 23 insert_use: InsertUseConfig {
24 merge: Some(MergeBehavior::Full), 24 merge: Some(MergeBehavior::Crate),
25 prefix_kind: hir::PrefixKind::Plain, 25 prefix_kind: hir::PrefixKind::Plain,
26 group: true, 26 group: true,
27 }, 27 },
diff --git a/crates/ide_completion/src/test_utils.rs b/crates/ide_completion/src/test_utils.rs
index c9857ec5f..939fb2d47 100644
--- a/crates/ide_completion/src/test_utils.rs
+++ b/crates/ide_completion/src/test_utils.rs
@@ -20,7 +20,7 @@ pub(crate) const TEST_CONFIG: CompletionConfig = CompletionConfig {
20 add_call_argument_snippets: true, 20 add_call_argument_snippets: true,
21 snippet_cap: SnippetCap::new(true), 21 snippet_cap: SnippetCap::new(true),
22 insert_use: InsertUseConfig { 22 insert_use: InsertUseConfig {
23 merge: Some(MergeBehavior::Full), 23 merge: Some(MergeBehavior::Crate),
24 prefix_kind: PrefixKind::Plain, 24 prefix_kind: PrefixKind::Plain,
25 group: true, 25 group: true,
26 }, 26 },
diff --git a/crates/ide_db/src/helpers/insert_use/tests.rs b/crates/ide_db/src/helpers/insert_use/tests.rs
index 048c213e2..248227d29 100644
--- a/crates/ide_db/src/helpers/insert_use/tests.rs
+++ b/crates/ide_db/src/helpers/insert_use/tests.rs
@@ -44,7 +44,7 @@ fn insert_not_group_empty() {
44 44
45#[test] 45#[test]
46fn insert_existing() { 46fn insert_existing() {
47 check_full("std::fs", "use std::fs;", "use std::fs;") 47 check_crate("std::fs", "use std::fs;", "use std::fs;")
48} 48}
49 49
50#[test] 50#[test]
@@ -249,7 +249,7 @@ use self::fmt;",
249 249
250#[test] 250#[test]
251fn insert_no_imports() { 251fn insert_no_imports() {
252 check_full( 252 check_crate(
253 "foo::bar", 253 "foo::bar",
254 "fn main() {}", 254 "fn main() {}",
255 r"use foo::bar; 255 r"use foo::bar;
@@ -263,7 +263,7 @@ fn insert_empty_file() {
263 cov_mark::check!(insert_group_empty_file); 263 cov_mark::check!(insert_group_empty_file);
264 // empty files will get two trailing newlines 264 // empty files will get two trailing newlines
265 // this is due to the test case insert_no_imports above 265 // this is due to the test case insert_no_imports above
266 check_full( 266 check_crate(
267 "foo::bar", 267 "foo::bar",
268 "", 268 "",
269 r"use foo::bar; 269 r"use foo::bar;
@@ -290,7 +290,7 @@ fn insert_empty_module() {
290#[test] 290#[test]
291fn insert_after_inner_attr() { 291fn insert_after_inner_attr() {
292 cov_mark::check!(insert_group_empty_inner_attr); 292 cov_mark::check!(insert_group_empty_inner_attr);
293 check_full( 293 check_crate(
294 "foo::bar", 294 "foo::bar",
295 r"#![allow(unused_imports)]", 295 r"#![allow(unused_imports)]",
296 r"#![allow(unused_imports)] 296 r"#![allow(unused_imports)]
@@ -301,7 +301,7 @@ use foo::bar;",
301 301
302#[test] 302#[test]
303fn insert_after_inner_attr2() { 303fn insert_after_inner_attr2() {
304 check_full( 304 check_crate(
305 "foo::bar", 305 "foo::bar",
306 r"#![allow(unused_imports)] 306 r"#![allow(unused_imports)]
307 307
@@ -371,12 +371,12 @@ fn main() {}"#,
371 371
372#[test] 372#[test]
373fn merge_groups() { 373fn merge_groups() {
374 check_last("std::io", r"use std::fmt;", r"use std::{fmt, io};") 374 check_module("std::io", r"use std::fmt;", r"use std::{fmt, io};")
375} 375}
376 376
377#[test] 377#[test]
378fn merge_groups_last() { 378fn merge_groups_last() {
379 check_last( 379 check_module(
380 "std::io", 380 "std::io",
381 r"use std::fmt::{Result, Display};", 381 r"use std::fmt::{Result, Display};",
382 r"use std::fmt::{Result, Display}; 382 r"use std::fmt::{Result, Display};
@@ -386,12 +386,12 @@ use std::io;",
386 386
387#[test] 387#[test]
388fn merge_last_into_self() { 388fn merge_last_into_self() {
389 check_last("foo::bar::baz", r"use foo::bar;", r"use foo::bar::{self, baz};"); 389 check_module("foo::bar::baz", r"use foo::bar;", r"use foo::bar::{self, baz};");
390} 390}
391 391
392#[test] 392#[test]
393fn merge_groups_full() { 393fn merge_groups_full() {
394 check_full( 394 check_crate(
395 "std::io", 395 "std::io",
396 r"use std::fmt::{Result, Display};", 396 r"use std::fmt::{Result, Display};",
397 r"use std::{fmt::{Result, Display}, io};", 397 r"use std::{fmt::{Result, Display}, io};",
@@ -400,17 +400,21 @@ fn merge_groups_full() {
400 400
401#[test] 401#[test]
402fn merge_groups_long_full() { 402fn merge_groups_long_full() {
403 check_full("std::foo::bar::Baz", r"use std::foo::bar::Qux;", r"use std::foo::bar::{Baz, Qux};") 403 check_crate("std::foo::bar::Baz", r"use std::foo::bar::Qux;", r"use std::foo::bar::{Baz, Qux};")
404} 404}
405 405
406#[test] 406#[test]
407fn merge_groups_long_last() { 407fn merge_groups_long_last() {
408 check_last("std::foo::bar::Baz", r"use std::foo::bar::Qux;", r"use std::foo::bar::{Baz, Qux};") 408 check_module(
409 "std::foo::bar::Baz",
410 r"use std::foo::bar::Qux;",
411 r"use std::foo::bar::{Baz, Qux};",
412 )
409} 413}
410 414
411#[test] 415#[test]
412fn merge_groups_long_full_list() { 416fn merge_groups_long_full_list() {
413 check_full( 417 check_crate(
414 "std::foo::bar::Baz", 418 "std::foo::bar::Baz",
415 r"use std::foo::bar::{Qux, Quux};", 419 r"use std::foo::bar::{Qux, Quux};",
416 r"use std::foo::bar::{Baz, Quux, Qux};", 420 r"use std::foo::bar::{Baz, Quux, Qux};",
@@ -419,7 +423,7 @@ fn merge_groups_long_full_list() {
419 423
420#[test] 424#[test]
421fn merge_groups_long_last_list() { 425fn merge_groups_long_last_list() {
422 check_last( 426 check_module(
423 "std::foo::bar::Baz", 427 "std::foo::bar::Baz",
424 r"use std::foo::bar::{Qux, Quux};", 428 r"use std::foo::bar::{Qux, Quux};",
425 r"use std::foo::bar::{Baz, Quux, Qux};", 429 r"use std::foo::bar::{Baz, Quux, Qux};",
@@ -428,7 +432,7 @@ fn merge_groups_long_last_list() {
428 432
429#[test] 433#[test]
430fn merge_groups_long_full_nested() { 434fn merge_groups_long_full_nested() {
431 check_full( 435 check_crate(
432 "std::foo::bar::Baz", 436 "std::foo::bar::Baz",
433 r"use std::foo::bar::{Qux, quux::{Fez, Fizz}};", 437 r"use std::foo::bar::{Qux, quux::{Fez, Fizz}};",
434 r"use std::foo::bar::{Baz, Qux, quux::{Fez, Fizz}};", 438 r"use std::foo::bar::{Baz, Qux, quux::{Fez, Fizz}};",
@@ -437,7 +441,7 @@ fn merge_groups_long_full_nested() {
437 441
438#[test] 442#[test]
439fn merge_groups_long_last_nested() { 443fn merge_groups_long_last_nested() {
440 check_last( 444 check_module(
441 "std::foo::bar::Baz", 445 "std::foo::bar::Baz",
442 r"use std::foo::bar::{Qux, quux::{Fez, Fizz}};", 446 r"use std::foo::bar::{Qux, quux::{Fez, Fizz}};",
443 r"use std::foo::bar::Baz; 447 r"use std::foo::bar::Baz;
@@ -447,7 +451,7 @@ use std::foo::bar::{Qux, quux::{Fez, Fizz}};",
447 451
448#[test] 452#[test]
449fn merge_groups_full_nested_deep() { 453fn merge_groups_full_nested_deep() {
450 check_full( 454 check_crate(
451 "std::foo::bar::quux::Baz", 455 "std::foo::bar::quux::Baz",
452 r"use std::foo::bar::{Qux, quux::{Fez, Fizz}};", 456 r"use std::foo::bar::{Qux, quux::{Fez, Fizz}};",
453 r"use std::foo::bar::{Qux, quux::{Baz, Fez, Fizz}};", 457 r"use std::foo::bar::{Qux, quux::{Baz, Fez, Fizz}};",
@@ -456,7 +460,7 @@ fn merge_groups_full_nested_deep() {
456 460
457#[test] 461#[test]
458fn merge_groups_full_nested_long() { 462fn merge_groups_full_nested_long() {
459 check_full( 463 check_crate(
460 "std::foo::bar::Baz", 464 "std::foo::bar::Baz",
461 r"use std::{foo::bar::Qux};", 465 r"use std::{foo::bar::Qux};",
462 r"use std::{foo::bar::{Baz, Qux}};", 466 r"use std::{foo::bar::{Baz, Qux}};",
@@ -465,7 +469,7 @@ fn merge_groups_full_nested_long() {
465 469
466#[test] 470#[test]
467fn merge_groups_last_nested_long() { 471fn merge_groups_last_nested_long() {
468 check_full( 472 check_crate(
469 "std::foo::bar::Baz", 473 "std::foo::bar::Baz",
470 r"use std::{foo::bar::Qux};", 474 r"use std::{foo::bar::Qux};",
471 r"use std::{foo::bar::{Baz, Qux}};", 475 r"use std::{foo::bar::{Baz, Qux}};",
@@ -474,7 +478,7 @@ fn merge_groups_last_nested_long() {
474 478
475#[test] 479#[test]
476fn merge_groups_skip_pub() { 480fn merge_groups_skip_pub() {
477 check_full( 481 check_crate(
478 "std::io", 482 "std::io",
479 r"pub use std::fmt::{Result, Display};", 483 r"pub use std::fmt::{Result, Display};",
480 r"pub use std::fmt::{Result, Display}; 484 r"pub use std::fmt::{Result, Display};
@@ -484,7 +488,7 @@ use std::io;",
484 488
485#[test] 489#[test]
486fn merge_groups_skip_pub_crate() { 490fn merge_groups_skip_pub_crate() {
487 check_full( 491 check_crate(
488 "std::io", 492 "std::io",
489 r"pub(crate) use std::fmt::{Result, Display};", 493 r"pub(crate) use std::fmt::{Result, Display};",
490 r"pub(crate) use std::fmt::{Result, Display}; 494 r"pub(crate) use std::fmt::{Result, Display};
@@ -494,7 +498,7 @@ use std::io;",
494 498
495#[test] 499#[test]
496fn merge_groups_skip_attributed() { 500fn merge_groups_skip_attributed() {
497 check_full( 501 check_crate(
498 "std::io", 502 "std::io",
499 r#" 503 r#"
500#[cfg(feature = "gated")] use std::fmt::{Result, Display}; 504#[cfg(feature = "gated")] use std::fmt::{Result, Display};
@@ -509,7 +513,7 @@ use std::io;
509#[test] 513#[test]
510#[ignore] // FIXME: Support this 514#[ignore] // FIXME: Support this
511fn split_out_merge() { 515fn split_out_merge() {
512 check_last( 516 check_module(
513 "std::fmt::Result", 517 "std::fmt::Result",
514 r"use std::{fmt, io};", 518 r"use std::{fmt, io};",
515 r"use std::fmt::{self, Result}; 519 r"use std::fmt::{self, Result};
@@ -519,29 +523,33 @@ use std::io;",
519 523
520#[test] 524#[test]
521fn merge_into_module_import() { 525fn merge_into_module_import() {
522 check_full("std::fmt::Result", r"use std::{fmt, io};", r"use std::{fmt::{self, Result}, io};") 526 check_crate("std::fmt::Result", r"use std::{fmt, io};", r"use std::{fmt::{self, Result}, io};")
523} 527}
524 528
525#[test] 529#[test]
526fn merge_groups_self() { 530fn merge_groups_self() {
527 check_full("std::fmt::Debug", r"use std::fmt;", r"use std::fmt::{self, Debug};") 531 check_crate("std::fmt::Debug", r"use std::fmt;", r"use std::fmt::{self, Debug};")
528} 532}
529 533
530#[test] 534#[test]
531fn merge_mod_into_glob() { 535fn merge_mod_into_glob() {
532 check_full("token::TokenKind", r"use token::TokenKind::*;", r"use token::TokenKind::{*, self};") 536 check_crate(
537 "token::TokenKind",
538 r"use token::TokenKind::*;",
539 r"use token::TokenKind::{*, self};",
540 )
533 // FIXME: have it emit `use token::TokenKind::{self, *}`? 541 // FIXME: have it emit `use token::TokenKind::{self, *}`?
534} 542}
535 543
536#[test] 544#[test]
537fn merge_self_glob() { 545fn merge_self_glob() {
538 check_full("self", r"use self::*;", r"use self::{*, self};") 546 check_crate("self", r"use self::*;", r"use self::{*, self};")
539 // FIXME: have it emit `use {self, *}`? 547 // FIXME: have it emit `use {self, *}`?
540} 548}
541 549
542#[test] 550#[test]
543fn merge_glob_nested() { 551fn merge_glob_nested() {
544 check_full( 552 check_crate(
545 "foo::bar::quux::Fez", 553 "foo::bar::quux::Fez",
546 r"use foo::bar::{Baz, quux::*};", 554 r"use foo::bar::{Baz, quux::*};",
547 r"use foo::bar::{Baz, quux::{self::*, Fez}};", 555 r"use foo::bar::{Baz, quux::{self::*, Fez}};",
@@ -550,7 +558,7 @@ fn merge_glob_nested() {
550 558
551#[test] 559#[test]
552fn merge_nested_considers_first_segments() { 560fn merge_nested_considers_first_segments() {
553 check_full( 561 check_crate(
554 "hir_ty::display::write_bounds_like_dyn_trait", 562 "hir_ty::display::write_bounds_like_dyn_trait",
555 r"use hir_ty::{autoderef, display::{HirDisplayError, HirFormatter}, method_resolution};", 563 r"use hir_ty::{autoderef, display::{HirDisplayError, HirFormatter}, method_resolution};",
556 r"use hir_ty::{autoderef, display::{HirDisplayError, HirFormatter, write_bounds_like_dyn_trait}, method_resolution};", 564 r"use hir_ty::{autoderef, display::{HirDisplayError, HirFormatter, write_bounds_like_dyn_trait}, method_resolution};",
@@ -559,7 +567,7 @@ fn merge_nested_considers_first_segments() {
559 567
560#[test] 568#[test]
561fn skip_merge_last_too_long() { 569fn skip_merge_last_too_long() {
562 check_last( 570 check_module(
563 "foo::bar", 571 "foo::bar",
564 r"use foo::bar::baz::Qux;", 572 r"use foo::bar::baz::Qux;",
565 r"use foo::bar; 573 r"use foo::bar;
@@ -569,7 +577,7 @@ use foo::bar::baz::Qux;",
569 577
570#[test] 578#[test]
571fn skip_merge_last_too_long2() { 579fn skip_merge_last_too_long2() {
572 check_last( 580 check_module(
573 "foo::bar::baz::Qux", 581 "foo::bar::baz::Qux",
574 r"use foo::bar;", 582 r"use foo::bar;",
575 r"use foo::bar; 583 r"use foo::bar;
@@ -592,7 +600,7 @@ fn merge_last_fail() {
592 check_merge_only_fail( 600 check_merge_only_fail(
593 r"use foo::bar::{baz::{Qux, Fez}};", 601 r"use foo::bar::{baz::{Qux, Fez}};",
594 r"use foo::bar::{baaz::{Quux, Feez}};", 602 r"use foo::bar::{baaz::{Quux, Feez}};",
595 MergeBehavior::Last, 603 MergeBehavior::Module,
596 ); 604 );
597} 605}
598 606
@@ -601,7 +609,7 @@ fn merge_last_fail1() {
601 check_merge_only_fail( 609 check_merge_only_fail(
602 r"use foo::bar::{baz::{Qux, Fez}};", 610 r"use foo::bar::{baz::{Qux, Fez}};",
603 r"use foo::bar::baaz::{Quux, Feez};", 611 r"use foo::bar::baaz::{Quux, Feez};",
604 MergeBehavior::Last, 612 MergeBehavior::Module,
605 ); 613 );
606} 614}
607 615
@@ -610,7 +618,7 @@ fn merge_last_fail2() {
610 check_merge_only_fail( 618 check_merge_only_fail(
611 r"use foo::bar::baz::{Qux, Fez};", 619 r"use foo::bar::baz::{Qux, Fez};",
612 r"use foo::bar::{baaz::{Quux, Feez}};", 620 r"use foo::bar::{baaz::{Quux, Feez}};",
613 MergeBehavior::Last, 621 MergeBehavior::Module,
614 ); 622 );
615} 623}
616 624
@@ -619,7 +627,7 @@ fn merge_last_fail3() {
619 check_merge_only_fail( 627 check_merge_only_fail(
620 r"use foo::bar::baz::{Qux, Fez};", 628 r"use foo::bar::baz::{Qux, Fez};",
621 r"use foo::bar::baaz::{Quux, Feez};", 629 r"use foo::bar::baaz::{Quux, Feez};",
622 MergeBehavior::Last, 630 MergeBehavior::Module,
623 ); 631 );
624} 632}
625 633
@@ -648,12 +656,12 @@ fn check(
648 assert_eq_text!(ra_fixture_after, &result); 656 assert_eq_text!(ra_fixture_after, &result);
649} 657}
650 658
651fn check_full(path: &str, ra_fixture_before: &str, ra_fixture_after: &str) { 659fn check_crate(path: &str, ra_fixture_before: &str, ra_fixture_after: &str) {
652 check(path, ra_fixture_before, ra_fixture_after, Some(MergeBehavior::Full), false, true) 660 check(path, ra_fixture_before, ra_fixture_after, Some(MergeBehavior::Crate), false, true)
653} 661}
654 662
655fn check_last(path: &str, ra_fixture_before: &str, ra_fixture_after: &str) { 663fn check_module(path: &str, ra_fixture_before: &str, ra_fixture_after: &str) {
656 check(path, ra_fixture_before, ra_fixture_after, Some(MergeBehavior::Last), false, true) 664 check(path, ra_fixture_before, ra_fixture_after, Some(MergeBehavior::Module), false, true)
657} 665}
658 666
659fn check_none(path: &str, ra_fixture_before: &str, ra_fixture_after: &str) { 667fn check_none(path: &str, ra_fixture_before: &str, ra_fixture_after: &str) {
diff --git a/crates/ide_db/src/helpers/merge_imports.rs b/crates/ide_db/src/helpers/merge_imports.rs
index 148297279..af2a51a4d 100644
--- a/crates/ide_db/src/helpers/merge_imports.rs
+++ b/crates/ide_db/src/helpers/merge_imports.rs
@@ -9,19 +9,19 @@ use syntax::ast::{
9/// What type of merges are allowed. 9/// What type of merges are allowed.
10#[derive(Copy, Clone, Debug, PartialEq, Eq)] 10#[derive(Copy, Clone, Debug, PartialEq, Eq)]
11pub enum MergeBehavior { 11pub enum MergeBehavior {
12 /// Merge everything together creating deeply nested imports. 12 /// Merge imports from the same crate into a single use statement.
13 Full, 13 Crate,
14 /// Only merge the last import level, doesn't allow import nesting. 14 /// Merge imports from the same module into a single use statement.
15 Last, 15 Module,
16} 16}
17 17
18impl MergeBehavior { 18impl MergeBehavior {
19 #[inline] 19 #[inline]
20 fn is_tree_allowed(&self, tree: &ast::UseTree) -> bool { 20 fn is_tree_allowed(&self, tree: &ast::UseTree) -> bool {
21 match self { 21 match self {
22 MergeBehavior::Full => true, 22 MergeBehavior::Crate => true,
23 // only simple single segment paths are allowed 23 // only simple single segment paths are allowed
24 MergeBehavior::Last => { 24 MergeBehavior::Module => {
25 tree.use_tree_list().is_none() && tree.path().map(path_len) <= Some(1) 25 tree.use_tree_list().is_none() && tree.path().map(path_len) <= Some(1)
26 } 26 }
27 } 27 }
@@ -153,7 +153,7 @@ fn recursive_merge(
153 } 153 }
154 } 154 }
155 Err(_) 155 Err(_)
156 if merge == MergeBehavior::Last 156 if merge == MergeBehavior::Module
157 && use_trees.len() > 0 157 && use_trees.len() > 0
158 && rhs_t.use_tree_list().is_some() => 158 && rhs_t.use_tree_list().is_some() =>
159 { 159 {
diff --git a/crates/project_model/src/cargo_workspace.rs b/crates/project_model/src/cargo_workspace.rs
index b18699b77..4a4996cf4 100644
--- a/crates/project_model/src/cargo_workspace.rs
+++ b/crates/project_model/src/cargo_workspace.rs
@@ -119,6 +119,32 @@ pub struct RustAnalyzerPackageMetaData {
119pub struct PackageDependency { 119pub struct PackageDependency {
120 pub pkg: Package, 120 pub pkg: Package,
121 pub name: String, 121 pub name: String,
122 pub kind: DepKind,
123}
124
125#[derive(Debug, Clone, Eq, PartialEq)]
126pub enum DepKind {
127 /// Available to the library, binary, and dev targets in the package (but not the build script).
128 Normal,
129 /// Available only to test and bench targets (and the library target, when built with `cfg(test)`).
130 Dev,
131 /// Available only to the build script target.
132 Build,
133}
134
135impl DepKind {
136 fn new(list: &[cargo_metadata::DepKindInfo]) -> Self {
137 for info in list {
138 match info.kind {
139 cargo_metadata::DependencyKind::Normal => return Self::Normal,
140 cargo_metadata::DependencyKind::Development => return Self::Dev,
141 cargo_metadata::DependencyKind::Build => return Self::Build,
142 cargo_metadata::DependencyKind::Unknown => continue,
143 }
144 }
145
146 Self::Normal
147 }
122} 148}
123 149
124/// Information associated with a package's target 150/// Information associated with a package's target
@@ -144,6 +170,7 @@ pub enum TargetKind {
144 Example, 170 Example,
145 Test, 171 Test,
146 Bench, 172 Bench,
173 BuildScript,
147 Other, 174 Other,
148} 175}
149 176
@@ -155,6 +182,7 @@ impl TargetKind {
155 "test" => TargetKind::Test, 182 "test" => TargetKind::Test,
156 "bench" => TargetKind::Bench, 183 "bench" => TargetKind::Bench,
157 "example" => TargetKind::Example, 184 "example" => TargetKind::Example,
185 "custom-build" => TargetKind::BuildScript,
158 "proc-macro" => TargetKind::Lib, 186 "proc-macro" => TargetKind::Lib,
159 _ if kind.contains("lib") => TargetKind::Lib, 187 _ if kind.contains("lib") => TargetKind::Lib,
160 _ => continue, 188 _ => continue,
@@ -301,7 +329,11 @@ impl CargoWorkspace {
301 continue; 329 continue;
302 } 330 }
303 }; 331 };
304 let dep = PackageDependency { name: dep_node.name, pkg }; 332 let dep = PackageDependency {
333 name: dep_node.name,
334 pkg,
335 kind: DepKind::new(&dep_node.dep_kinds),
336 };
305 packages[source].dependencies.push(dep); 337 packages[source].dependencies.push(dep);
306 } 338 }
307 packages[source].active_features.extend(node.features); 339 packages[source].active_features.extend(node.features);
diff --git a/crates/project_model/src/workspace.rs b/crates/project_model/src/workspace.rs
index 84c702fdf..607e62ea5 100644
--- a/crates/project_model/src/workspace.rs
+++ b/crates/project_model/src/workspace.rs
@@ -6,6 +6,7 @@ use std::{collections::VecDeque, fmt, fs, path::Path, process::Command};
6 6
7use anyhow::{Context, Result}; 7use anyhow::{Context, Result};
8use base_db::{CrateDisplayName, CrateGraph, CrateId, CrateName, Edition, Env, FileId, ProcMacro}; 8use base_db::{CrateDisplayName, CrateGraph, CrateId, CrateName, Edition, Env, FileId, ProcMacro};
9use cargo_workspace::DepKind;
9use cfg::CfgOptions; 10use cfg::CfgOptions;
10use paths::{AbsPath, AbsPathBuf}; 11use paths::{AbsPath, AbsPathBuf};
11use proc_macro_api::ProcMacroClient; 12use proc_macro_api::ProcMacroClient;
@@ -390,6 +391,7 @@ fn cargo_to_crate_graph(
390 &cfg_options, 391 &cfg_options,
391 proc_macro_loader, 392 proc_macro_loader,
392 file_id, 393 file_id,
394 &cargo[tgt].name,
393 ); 395 );
394 if cargo[tgt].kind == TargetKind::Lib { 396 if cargo[tgt].kind == TargetKind::Lib {
395 lib_tgt = Some((crate_id, cargo[tgt].name.clone())); 397 lib_tgt = Some((crate_id, cargo[tgt].name.clone()));
@@ -406,23 +408,25 @@ fn cargo_to_crate_graph(
406 } 408 }
407 } 409 }
408 410
409 pkg_crates.entry(pkg).or_insert_with(Vec::new).push(crate_id); 411 pkg_crates.entry(pkg).or_insert_with(Vec::new).push((crate_id, cargo[tgt].kind));
410 } 412 }
411 } 413 }
412 414
413 // Set deps to the core, std and to the lib target of the current package 415 // Set deps to the core, std and to the lib target of the current package
414 for &from in pkg_crates.get(&pkg).into_iter().flatten() { 416 for (from, kind) in pkg_crates.get(&pkg).into_iter().flatten() {
415 if let Some((to, name)) = lib_tgt.clone() { 417 if let Some((to, name)) = lib_tgt.clone() {
416 if to != from { 418 if to != *from && *kind != TargetKind::BuildScript {
419 // (build script can not depend on its library target)
420
417 // For root projects with dashes in their name, 421 // For root projects with dashes in their name,
418 // cargo metadata does not do any normalization, 422 // cargo metadata does not do any normalization,
419 // so we do it ourselves currently 423 // so we do it ourselves currently
420 let name = CrateName::normalize_dashes(&name); 424 let name = CrateName::normalize_dashes(&name);
421 add_dep(&mut crate_graph, from, name, to); 425 add_dep(&mut crate_graph, *from, name, to);
422 } 426 }
423 } 427 }
424 for (name, krate) in public_deps.iter() { 428 for (name, krate) in public_deps.iter() {
425 add_dep(&mut crate_graph, from, name.clone(), *krate); 429 add_dep(&mut crate_graph, *from, name.clone(), *krate);
426 } 430 }
427 } 431 }
428 } 432 }
@@ -433,8 +437,17 @@ fn cargo_to_crate_graph(
433 for dep in cargo[pkg].dependencies.iter() { 437 for dep in cargo[pkg].dependencies.iter() {
434 let name = CrateName::new(&dep.name).unwrap(); 438 let name = CrateName::new(&dep.name).unwrap();
435 if let Some(&to) = pkg_to_lib_crate.get(&dep.pkg) { 439 if let Some(&to) = pkg_to_lib_crate.get(&dep.pkg) {
436 for &from in pkg_crates.get(&pkg).into_iter().flatten() { 440 for (from, kind) in pkg_crates.get(&pkg).into_iter().flatten() {
437 add_dep(&mut crate_graph, from, name.clone(), to) 441 if dep.kind == DepKind::Build && *kind != TargetKind::BuildScript {
442 // Only build scripts may depend on build dependencies.
443 continue;
444 }
445 if dep.kind != DepKind::Build && *kind == TargetKind::BuildScript {
446 // Build scripts may only depend on build dependencies.
447 continue;
448 }
449
450 add_dep(&mut crate_graph, *from, name.clone(), to)
438 } 451 }
439 } 452 }
440 } 453 }
@@ -471,7 +484,7 @@ fn handle_rustc_crates(
471 pkg_to_lib_crate: &mut FxHashMap<la_arena::Idx<crate::PackageData>, CrateId>, 484 pkg_to_lib_crate: &mut FxHashMap<la_arena::Idx<crate::PackageData>, CrateId>,
472 public_deps: &[(CrateName, CrateId)], 485 public_deps: &[(CrateName, CrateId)],
473 cargo: &CargoWorkspace, 486 cargo: &CargoWorkspace,
474 pkg_crates: &FxHashMap<la_arena::Idx<crate::PackageData>, Vec<CrateId>>, 487 pkg_crates: &FxHashMap<la_arena::Idx<crate::PackageData>, Vec<(CrateId, TargetKind)>>,
475) { 488) {
476 let mut rustc_pkg_crates = FxHashMap::default(); 489 let mut rustc_pkg_crates = FxHashMap::default();
477 // The root package of the rustc-dev component is rustc_driver, so we match that 490 // The root package of the rustc-dev component is rustc_driver, so we match that
@@ -505,6 +518,7 @@ fn handle_rustc_crates(
505 &cfg_options, 518 &cfg_options,
506 proc_macro_loader, 519 proc_macro_loader,
507 file_id, 520 file_id,
521 &rustc_workspace[tgt].name,
508 ); 522 );
509 pkg_to_lib_crate.insert(pkg, crate_id); 523 pkg_to_lib_crate.insert(pkg, crate_id);
510 // Add dependencies on core / std / alloc for this crate 524 // Add dependencies on core / std / alloc for this crate
@@ -539,13 +553,13 @@ fn handle_rustc_crates(
539 if !package.metadata.rustc_private { 553 if !package.metadata.rustc_private {
540 continue; 554 continue;
541 } 555 }
542 for &from in pkg_crates.get(&pkg).into_iter().flatten() { 556 for (from, _) in pkg_crates.get(&pkg).into_iter().flatten() {
543 // Avoid creating duplicate dependencies 557 // Avoid creating duplicate dependencies
544 // This avoids the situation where `from` depends on e.g. `arrayvec`, but 558 // This avoids the situation where `from` depends on e.g. `arrayvec`, but
545 // `rust_analyzer` thinks that it should use the one from the `rustcSource` 559 // `rust_analyzer` thinks that it should use the one from the `rustcSource`
546 // instead of the one from `crates.io` 560 // instead of the one from `crates.io`
547 if !crate_graph[from].dependencies.iter().any(|d| d.name == name) { 561 if !crate_graph[*from].dependencies.iter().any(|d| d.name == name) {
548 add_dep(crate_graph, from, name.clone(), to); 562 add_dep(crate_graph, *from, name.clone(), to);
549 } 563 }
550 } 564 }
551 } 565 }
@@ -560,6 +574,7 @@ fn add_target_crate_root(
560 cfg_options: &CfgOptions, 574 cfg_options: &CfgOptions,
561 proc_macro_loader: &dyn Fn(&Path) -> Vec<ProcMacro>, 575 proc_macro_loader: &dyn Fn(&Path) -> Vec<ProcMacro>,
562 file_id: FileId, 576 file_id: FileId,
577 cargo_name: &str,
563) -> CrateId { 578) -> CrateId {
564 let edition = pkg.edition; 579 let edition = pkg.edition;
565 let cfg_options = { 580 let cfg_options = {
@@ -586,7 +601,7 @@ fn add_target_crate_root(
586 .map(|it| proc_macro_loader(&it)) 601 .map(|it| proc_macro_loader(&it))
587 .unwrap_or_default(); 602 .unwrap_or_default();
588 603
589 let display_name = CrateDisplayName::from_canonical_name(pkg.name.clone()); 604 let display_name = CrateDisplayName::from_canonical_name(cargo_name.to_string());
590 let crate_id = crate_graph.add_crate_root( 605 let crate_id = crate_graph.add_crate_root(
591 file_id, 606 file_id,
592 edition, 607 edition,
diff --git a/crates/rust-analyzer/src/cargo_target_spec.rs b/crates/rust-analyzer/src/cargo_target_spec.rs
index 909c21532..f4cd43448 100644
--- a/crates/rust-analyzer/src/cargo_target_spec.rs
+++ b/crates/rust-analyzer/src/cargo_target_spec.rs
@@ -159,7 +159,7 @@ impl CargoTargetSpec {
159 TargetKind::Lib => { 159 TargetKind::Lib => {
160 buf.push("--lib".to_string()); 160 buf.push("--lib".to_string());
161 } 161 }
162 TargetKind::Other => (), 162 TargetKind::Other | TargetKind::BuildScript => (),
163 } 163 }
164 } 164 }
165} 165}
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs
index 8879a9161..d83670bda 100644
--- a/crates/rust-analyzer/src/config.rs
+++ b/crates/rust-analyzer/src/config.rs
@@ -36,7 +36,7 @@ config_data! {
36 struct ConfigData { 36 struct ConfigData {
37 /// The strategy to use when inserting new imports or merging imports. 37 /// The strategy to use when inserting new imports or merging imports.
38 assist_importMergeBehavior | 38 assist_importMergeBehavior |
39 assist_importMergeBehaviour: MergeBehaviorDef = "\"full\"", 39 assist_importMergeBehaviour: MergeBehaviorDef = "\"crate\"",
40 /// The path structure for newly inserted paths to use. 40 /// The path structure for newly inserted paths to use.
41 assist_importPrefix: ImportPrefixDef = "\"plain\"", 41 assist_importPrefix: ImportPrefixDef = "\"plain\"",
42 /// Group inserted imports by the [following order](https://rust-analyzer.github.io/manual.html#auto-import). Groups are separated by newlines. 42 /// Group inserted imports by the [following order](https://rust-analyzer.github.io/manual.html#auto-import). Groups are separated by newlines.
@@ -604,8 +604,8 @@ impl Config {
604 InsertUseConfig { 604 InsertUseConfig {
605 merge: match self.data.assist_importMergeBehavior { 605 merge: match self.data.assist_importMergeBehavior {
606 MergeBehaviorDef::None => None, 606 MergeBehaviorDef::None => None,
607 MergeBehaviorDef::Full => Some(MergeBehavior::Full), 607 MergeBehaviorDef::Crate => Some(MergeBehavior::Crate),
608 MergeBehaviorDef::Last => Some(MergeBehavior::Last), 608 MergeBehaviorDef::Module => Some(MergeBehavior::Module),
609 }, 609 },
610 prefix_kind: match self.data.assist_importPrefix { 610 prefix_kind: match self.data.assist_importPrefix {
611 ImportPrefixDef::Plain => PrefixKind::Plain, 611 ImportPrefixDef::Plain => PrefixKind::Plain,
@@ -709,8 +709,10 @@ enum ManifestOrProjectJson {
709#[serde(rename_all = "snake_case")] 709#[serde(rename_all = "snake_case")]
710enum MergeBehaviorDef { 710enum MergeBehaviorDef {
711 None, 711 None,
712 Full, 712 #[serde(alias = "full")]
713 Last, 713 Crate,
714 #[serde(alias = "last")]
715 Module,
714} 716}
715 717
716#[derive(Deserialize, Debug, Clone)] 718#[derive(Deserialize, Debug, Clone)]
@@ -867,11 +869,11 @@ fn field_props(field: &str, ty: &str, doc: &[&str], default: &str) -> serde_json
867 }, 869 },
868 "MergeBehaviorDef" => set! { 870 "MergeBehaviorDef" => set! {
869 "type": "string", 871 "type": "string",
870 "enum": ["none", "full", "last"], 872 "enum": ["none", "crate", "module"],
871 "enumDescriptions": [ 873 "enumDescriptions": [
872 "No merging", 874 "Do not merge imports at all.",
873 "Merge all layers of the import trees", 875 "Merge imports from the same crate into a single `use` statement.",
874 "Only merge the last layer of the import trees" 876 "Merge imports from the same module into a single `use` statement."
875 ], 877 ],
876 }, 878 },
877 "ImportPrefixDef" => set! { 879 "ImportPrefixDef" => set! {
diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs
index f6e40f872..551013aa9 100644
--- a/crates/rust-analyzer/src/handlers.rs
+++ b/crates/rust-analyzer/src/handlers.rs
@@ -3,8 +3,8 @@
3//! `ide` crate. 3//! `ide` crate.
4 4
5use std::{ 5use std::{
6 io::Write as _, 6 io::{Read, Write as _},
7 process::{self, Stdio}, 7 process::{self, Command, Stdio},
8}; 8};
9 9
10use ide::{ 10use ide::{
@@ -117,6 +117,24 @@ pub(crate) fn handle_view_hir(
117 Ok(res) 117 Ok(res)
118} 118}
119 119
120pub(crate) fn handle_view_crate_graph(snap: GlobalStateSnapshot, (): ()) -> Result<String> {
121 let _p = profile::span("handle_view_crate_graph");
122 let dot = snap.analysis.view_crate_graph()??;
123
124 // We shell out to `dot` to render to SVG, as there does not seem to be a pure-Rust renderer.
125 let child = Command::new("dot")
126 .arg("-Tsvg")
127 .stdin(Stdio::piped())
128 .stdout(Stdio::piped())
129 .spawn()
130 .map_err(|err| format!("failed to spawn `dot`: {}", err))?;
131 child.stdin.unwrap().write_all(dot.as_bytes())?;
132
133 let mut svg = String::new();
134 child.stdout.unwrap().read_to_string(&mut svg)?;
135 Ok(svg)
136}
137
120pub(crate) fn handle_expand_macro( 138pub(crate) fn handle_expand_macro(
121 snap: GlobalStateSnapshot, 139 snap: GlobalStateSnapshot,
122 params: lsp_ext::ExpandMacroParams, 140 params: lsp_ext::ExpandMacroParams,
diff --git a/crates/rust-analyzer/src/integrated_benchmarks.rs b/crates/rust-analyzer/src/integrated_benchmarks.rs
index 3dcbe397a..56de9681c 100644
--- a/crates/rust-analyzer/src/integrated_benchmarks.rs
+++ b/crates/rust-analyzer/src/integrated_benchmarks.rs
@@ -133,7 +133,7 @@ fn integrated_completion_benchmark() {
133 add_call_argument_snippets: true, 133 add_call_argument_snippets: true,
134 snippet_cap: SnippetCap::new(true), 134 snippet_cap: SnippetCap::new(true),
135 insert_use: InsertUseConfig { 135 insert_use: InsertUseConfig {
136 merge: Some(MergeBehavior::Full), 136 merge: Some(MergeBehavior::Crate),
137 prefix_kind: hir::PrefixKind::ByCrate, 137 prefix_kind: hir::PrefixKind::ByCrate,
138 group: true, 138 group: true,
139 }, 139 },
@@ -166,7 +166,7 @@ fn integrated_completion_benchmark() {
166 add_call_argument_snippets: true, 166 add_call_argument_snippets: true,
167 snippet_cap: SnippetCap::new(true), 167 snippet_cap: SnippetCap::new(true),
168 insert_use: InsertUseConfig { 168 insert_use: InsertUseConfig {
169 merge: Some(MergeBehavior::Full), 169 merge: Some(MergeBehavior::Crate),
170 prefix_kind: hir::PrefixKind::ByCrate, 170 prefix_kind: hir::PrefixKind::ByCrate,
171 group: true, 171 group: true,
172 }, 172 },
diff --git a/crates/rust-analyzer/src/lsp_ext.rs b/crates/rust-analyzer/src/lsp_ext.rs
index b8835a534..3bd098058 100644
--- a/crates/rust-analyzer/src/lsp_ext.rs
+++ b/crates/rust-analyzer/src/lsp_ext.rs
@@ -61,6 +61,14 @@ impl Request for ViewHir {
61 const METHOD: &'static str = "rust-analyzer/viewHir"; 61 const METHOD: &'static str = "rust-analyzer/viewHir";
62} 62}
63 63
64pub enum ViewCrateGraph {}
65
66impl Request for ViewCrateGraph {
67 type Params = ();
68 type Result = String;
69 const METHOD: &'static str = "rust-analyzer/viewCrateGraph";
70}
71
64pub enum ExpandMacro {} 72pub enum ExpandMacro {}
65 73
66impl Request for ExpandMacro { 74impl Request for ExpandMacro {
diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs
index ce7ece559..c7bd7eee1 100644
--- a/crates/rust-analyzer/src/main_loop.rs
+++ b/crates/rust-analyzer/src/main_loop.rs
@@ -513,6 +513,7 @@ impl GlobalState {
513 .on::<lsp_ext::AnalyzerStatus>(handlers::handle_analyzer_status) 513 .on::<lsp_ext::AnalyzerStatus>(handlers::handle_analyzer_status)
514 .on::<lsp_ext::SyntaxTree>(handlers::handle_syntax_tree) 514 .on::<lsp_ext::SyntaxTree>(handlers::handle_syntax_tree)
515 .on::<lsp_ext::ViewHir>(handlers::handle_view_hir) 515 .on::<lsp_ext::ViewHir>(handlers::handle_view_hir)
516 .on::<lsp_ext::ViewCrateGraph>(handlers::handle_view_crate_graph)
516 .on::<lsp_ext::ExpandMacro>(handlers::handle_expand_macro) 517 .on::<lsp_ext::ExpandMacro>(handlers::handle_expand_macro)
517 .on::<lsp_ext::ParentModule>(handlers::handle_parent_module) 518 .on::<lsp_ext::ParentModule>(handlers::handle_parent_module)
518 .on::<lsp_ext::Runnables>(handlers::handle_runnables) 519 .on::<lsp_ext::Runnables>(handlers::handle_runnables)
diff --git a/docs/dev/lsp-extensions.md b/docs/dev/lsp-extensions.md
index f0f981802..8fcd72d5d 100644
--- a/docs/dev/lsp-extensions.md
+++ b/docs/dev/lsp-extensions.md
@@ -1,5 +1,5 @@
1<!--- 1<!---
2lsp_ext.rs hash: 28a9d5a24b7ca396 2lsp_ext.rs hash: 6e57fc1b345b00e9
3 3
4If you need to change the above hash to make the test pass, please check if you 4If you need to change the above hash to make the test pass, please check if you
5need to adjust this doc as well and ping this issue: 5need to adjust this doc as well and ping this issue:
@@ -486,6 +486,16 @@ Primarily for debugging, but very useful for all people working on rust-analyzer
486Returns a textual representation of the HIR of the function containing the cursor. 486Returns a textual representation of the HIR of the function containing the cursor.
487For debugging or when working on rust-analyzer itself. 487For debugging or when working on rust-analyzer itself.
488 488
489## View Crate Graph
490
491**Method:** `rust-analyzer/viewCrateGraph`
492
493**Request:** `null`
494
495**Response:** `string`
496
497Renders rust-analyzer's crate graph as an SVG image.
498
489## Expand Macro 499## Expand Macro
490 500
491**Method:** `rust-analyzer/expandMacro` 501**Method:** `rust-analyzer/expandMacro`
diff --git a/docs/user/generated_config.adoc b/docs/user/generated_config.adoc
index e28423e99..f70558200 100644
--- a/docs/user/generated_config.adoc
+++ b/docs/user/generated_config.adoc
@@ -1,4 +1,4 @@
1[[rust-analyzer.assist.importMergeBehavior]]rust-analyzer.assist.importMergeBehavior (default: `"full"`):: 1[[rust-analyzer.assist.importMergeBehavior]]rust-analyzer.assist.importMergeBehavior (default: `"crate"`)::
2+ 2+
3-- 3--
4The strategy to use when inserting new imports or merging imports. 4The strategy to use when inserting new imports or merging imports.
diff --git a/editors/code/package.json b/editors/code/package.json
index 0cc265aa4..0f38a1673 100644
--- a/editors/code/package.json
+++ b/editors/code/package.json
@@ -110,6 +110,11 @@
110 "category": "Rust Analyzer" 110 "category": "Rust Analyzer"
111 }, 111 },
112 { 112 {
113 "command": "rust-analyzer.viewCrateGraph",
114 "title": "View Crate Graph",
115 "category": "Rust Analyzer"
116 },
117 {
113 "command": "rust-analyzer.expandMacro", 118 "command": "rust-analyzer.expandMacro",
114 "title": "Expand macro recursively", 119 "title": "Expand macro recursively",
115 "category": "Rust Analyzer" 120 "category": "Rust Analyzer"
@@ -382,17 +387,17 @@
382 "$generated-start": false, 387 "$generated-start": false,
383 "rust-analyzer.assist.importMergeBehavior": { 388 "rust-analyzer.assist.importMergeBehavior": {
384 "markdownDescription": "The strategy to use when inserting new imports or merging imports.", 389 "markdownDescription": "The strategy to use when inserting new imports or merging imports.",
385 "default": "full", 390 "default": "crate",
386 "type": "string", 391 "type": "string",
387 "enum": [ 392 "enum": [
388 "none", 393 "none",
389 "full", 394 "crate",
390 "last" 395 "module"
391 ], 396 ],
392 "enumDescriptions": [ 397 "enumDescriptions": [
393 "No merging", 398 "Do not merge imports at all.",
394 "Merge all layers of the import trees", 399 "Merge imports from the same crate into a single `use` statement.",
395 "Only merge the last layer of the import trees" 400 "Merge imports from the same module into a single `use` statement."
396 ] 401 ]
397 }, 402 },
398 "rust-analyzer.assist.importPrefix": { 403 "rust-analyzer.assist.importPrefix": {
diff --git a/editors/code/src/commands.ts b/editors/code/src/commands.ts
index 4092435db..8ab259af2 100644
--- a/editors/code/src/commands.ts
+++ b/editors/code/src/commands.ts
@@ -429,6 +429,14 @@ export function viewHir(ctx: Ctx): Cmd {
429 }; 429 };
430} 430}
431 431
432export function viewCrateGraph(ctx: Ctx): Cmd {
433 return async () => {
434 const panel = vscode.window.createWebviewPanel("rust-analyzer.crate-graph", "rust-analyzer crate graph", vscode.ViewColumn.Two);
435 const svg = await ctx.client.sendRequest(ra.viewCrateGraph);
436 panel.webview.html = svg;
437 };
438}
439
432// Opens the virtual file that will show the syntax tree 440// Opens the virtual file that will show the syntax tree
433// 441//
434// The contents of the file come from the `TextDocumentContentProvider` 442// The contents of the file come from the `TextDocumentContentProvider`
diff --git a/editors/code/src/lsp_ext.ts b/editors/code/src/lsp_ext.ts
index f78de894b..aa745a65c 100644
--- a/editors/code/src/lsp_ext.ts
+++ b/editors/code/src/lsp_ext.ts
@@ -27,6 +27,8 @@ export const syntaxTree = new lc.RequestType<SyntaxTreeParams, string, void>("ru
27 27
28export const viewHir = new lc.RequestType<lc.TextDocumentPositionParams, string, void>("rust-analyzer/viewHir"); 28export const viewHir = new lc.RequestType<lc.TextDocumentPositionParams, string, void>("rust-analyzer/viewHir");
29 29
30export const viewCrateGraph = new lc.RequestType0<string, void>("rust-analyzer/viewCrateGraph");
31
30export interface ExpandMacroParams { 32export interface ExpandMacroParams {
31 textDocument: lc.TextDocumentIdentifier; 33 textDocument: lc.TextDocumentIdentifier;
32 position: lc.Position; 34 position: lc.Position;
diff --git a/editors/code/src/main.ts b/editors/code/src/main.ts
index 643fb643f..516322d03 100644
--- a/editors/code/src/main.ts
+++ b/editors/code/src/main.ts
@@ -106,6 +106,7 @@ async function tryActivate(context: vscode.ExtensionContext) {
106 ctx.registerCommand('parentModule', commands.parentModule); 106 ctx.registerCommand('parentModule', commands.parentModule);
107 ctx.registerCommand('syntaxTree', commands.syntaxTree); 107 ctx.registerCommand('syntaxTree', commands.syntaxTree);
108 ctx.registerCommand('viewHir', commands.viewHir); 108 ctx.registerCommand('viewHir', commands.viewHir);
109 ctx.registerCommand('viewCrateGraph', commands.viewCrateGraph);
109 ctx.registerCommand('expandMacro', commands.expandMacro); 110 ctx.registerCommand('expandMacro', commands.expandMacro);
110 ctx.registerCommand('run', commands.run); 111 ctx.registerCommand('run', commands.run);
111 ctx.registerCommand('copyRunCommandLine', commands.copyRunCommandLine); 112 ctx.registerCommand('copyRunCommandLine', commands.copyRunCommandLine);