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/extract_struct_from_enum_variant.rs135
-rw-r--r--crates/ide_assists/src/handlers/pull_assignment_up.rs50
-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/handlers.rs22
-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--editors/code/package.json5
-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
32 files changed, 672 insertions, 230 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 a7e720f88..2178ffd07 100644
--- a/crates/hir_ty/src/infer/expr.rs
+++ b/crates/hir_ty/src/infer/expr.rs
@@ -823,7 +823,7 @@ impl<'a> InferenceContext<'a> {
823 let ty = self.resolve_ty_as_possible(ty); 823 let ty = self.resolve_ty_as_possible(ty);
824 self.infer_pat(*pat, &ty, BindingMode::default()); 824 self.infer_pat(*pat, &ty, BindingMode::default());
825 } 825 }
826 Statement::Expr(expr) => { 826 Statement::Expr { expr, .. } => {
827 self.infer_expr(*expr, &Expectation::none()); 827 self.infer_expr(*expr, &Expectation::none());
828 } 828 }
829 } 829 }
diff --git a/crates/hir_ty/src/tests/regression.rs b/crates/hir_ty/src/tests/regression.rs
index 769809edf..431861712 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/extract_struct_from_enum_variant.rs b/crates/ide_assists/src/handlers/extract_struct_from_enum_variant.rs
index 66f274fa7..8e2178391 100644
--- a/crates/ide_assists/src/handlers/extract_struct_from_enum_variant.rs
+++ b/crates/ide_assists/src/handlers/extract_struct_from_enum_variant.rs
@@ -151,20 +151,37 @@ fn create_struct_def(
151 field_list: &Either<ast::RecordFieldList, ast::TupleFieldList>, 151 field_list: &Either<ast::RecordFieldList, ast::TupleFieldList>,
152 visibility: Option<ast::Visibility>, 152 visibility: Option<ast::Visibility>,
153) -> ast::Struct { 153) -> ast::Struct {
154 let pub_vis = Some(make::visibility_pub()); 154 let pub_vis = make::visibility_pub();
155
156 let insert_pub = |node: &'_ SyntaxNode| {
157 let pub_vis = pub_vis.clone_for_update();
158 ted::insert(ted::Position::before(node), pub_vis.syntax());
159 };
160
161 // for fields without any existing visibility, use pub visibility
155 let field_list = match field_list { 162 let field_list = match field_list {
156 Either::Left(field_list) => { 163 Either::Left(field_list) => {
157 make::record_field_list(field_list.fields().flat_map(|field| { 164 let field_list = field_list.clone_for_update();
158 Some(make::record_field(pub_vis.clone(), field.name()?, field.ty()?)) 165
159 })) 166 field_list
160 .into() 167 .fields()
168 .filter(|field| field.visibility().is_none())
169 .filter_map(|field| field.name())
170 .for_each(|it| insert_pub(it.syntax()));
171
172 field_list.into()
161 } 173 }
162 Either::Right(field_list) => make::tuple_field_list( 174 Either::Right(field_list) => {
175 let field_list = field_list.clone_for_update();
176
163 field_list 177 field_list
164 .fields() 178 .fields()
165 .flat_map(|field| Some(make::tuple_field(pub_vis.clone(), field.ty()?))), 179 .filter(|field| field.visibility().is_none())
166 ) 180 .filter_map(|field| field.ty())
167 .into(), 181 .for_each(|it| insert_pub(it.syntax()));
182
183 field_list.into()
184 }
168 }; 185 };
169 186
170 make::struct_(visibility, variant_name, None, field_list).clone_for_update() 187 make::struct_(visibility, variant_name, None, field_list).clone_for_update()
@@ -295,6 +312,106 @@ enum A { One(One) }"#,
295 } 312 }
296 313
297 #[test] 314 #[test]
315 fn test_extract_struct_keep_comments_and_attrs_one_field_named() {
316 check_assist(
317 extract_struct_from_enum_variant,
318 r#"
319enum A {
320 $0One {
321 // leading comment
322 /// doc comment
323 #[an_attr]
324 foo: u32
325 // trailing comment
326 }
327}"#,
328 r#"
329struct One{
330 // leading comment
331 /// doc comment
332 #[an_attr]
333 pub foo: u32
334 // trailing comment
335 }
336
337enum A {
338 One(One)
339}"#,
340 );
341 }
342
343 #[test]
344 fn test_extract_struct_keep_comments_and_attrs_several_fields_named() {
345 check_assist(
346 extract_struct_from_enum_variant,
347 r#"
348enum A {
349 $0One {
350 // comment
351 /// doc
352 #[attr]
353 foo: u32,
354 // comment
355 #[attr]
356 /// doc
357 bar: u32
358 }
359}"#,
360 r#"
361struct One{
362 // comment
363 /// doc
364 #[attr]
365 pub foo: u32,
366 // comment
367 #[attr]
368 /// doc
369 pub bar: u32
370 }
371
372enum A {
373 One(One)
374}"#,
375 );
376 }
377
378 #[test]
379 fn test_extract_struct_keep_comments_and_attrs_several_fields_tuple() {
380 check_assist(
381 extract_struct_from_enum_variant,
382 "enum A { $0One(/* comment */ #[attr] u32, /* another */ u32 /* tail */) }",
383 r#"
384struct One(/* comment */ #[attr] pub u32, /* another */ pub u32 /* tail */);
385
386enum A { One(One) }"#,
387 );
388 }
389
390 #[test]
391 fn test_extract_struct_keep_existing_visibility_named() {
392 check_assist(
393 extract_struct_from_enum_variant,
394 "enum A { $0One{ pub a: u32, pub(crate) b: u32, pub(super) c: u32, d: u32 } }",
395 r#"
396struct One{ pub a: u32, pub(crate) b: u32, pub(super) c: u32, pub d: u32 }
397
398enum A { One(One) }"#,
399 );
400 }
401
402 #[test]
403 fn test_extract_struct_keep_existing_visibility_tuple() {
404 check_assist(
405 extract_struct_from_enum_variant,
406 "enum A { $0One(pub u32, pub(crate) u32, pub(super) u32, u32) }",
407 r#"
408struct One(pub u32, pub(crate) u32, pub(super) u32, pub u32);
409
410enum A { One(One) }"#,
411 );
412 }
413
414 #[test]
298 fn test_extract_enum_variant_name_value_namespace() { 415 fn test_extract_enum_variant_name_value_namespace() {
299 check_assist( 416 check_assist(
300 extract_struct_from_enum_variant, 417 extract_struct_from_enum_variant,
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/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/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/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/editors/code/package.json b/editors/code/package.json
index f35d30898..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"
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);