aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
Diffstat (limited to 'crates')
-rw-r--r--crates/assists/src/handlers/extract_struct_from_enum_variant.rs120
-rw-r--r--crates/assists/src/handlers/remove_unused_param.rs83
-rw-r--r--crates/assists/src/utils.rs12
-rw-r--r--crates/assists/src/utils/insert_use.rs96
-rw-r--r--crates/completion/Cargo.toml1
-rw-r--r--crates/completion/src/completions/record.rs108
-rw-r--r--crates/project_model/src/cargo_workspace.rs23
-rw-r--r--crates/project_model/src/lib.rs469
-rw-r--r--crates/project_model/src/workspace.rs590
-rw-r--r--crates/rust-analyzer/src/cli/load_cargo.rs1
-rw-r--r--crates/rust-analyzer/src/config.rs31
-rw-r--r--crates/rust-analyzer/src/handlers.rs22
-rw-r--r--crates/rust-analyzer/src/lsp_ext.rs14
-rw-r--r--crates/rust-analyzer/src/main_loop.rs1
-rw-r--r--crates/rust-analyzer/src/reload.rs11
-rw-r--r--crates/rust-analyzer/tests/rust-analyzer/support.rs8
16 files changed, 1042 insertions, 548 deletions
diff --git a/crates/assists/src/handlers/extract_struct_from_enum_variant.rs b/crates/assists/src/handlers/extract_struct_from_enum_variant.rs
index 84662d832..067afabf2 100644
--- a/crates/assists/src/handlers/extract_struct_from_enum_variant.rs
+++ b/crates/assists/src/handlers/extract_struct_from_enum_variant.rs
@@ -5,10 +5,9 @@ use hir::{AsName, EnumVariant, Module, ModuleDef, Name};
5use ide_db::{defs::Definition, search::Reference, RootDatabase}; 5use ide_db::{defs::Definition, search::Reference, RootDatabase};
6use rustc_hash::{FxHashMap, FxHashSet}; 6use rustc_hash::{FxHashMap, FxHashSet};
7use syntax::{ 7use syntax::{
8 algo::find_node_at_offset, 8 algo::{find_node_at_offset, SyntaxRewriter},
9 algo::SyntaxRewriter, 9 ast::{self, edit::IndentLevel, make, AstNode, NameOwner, VisibilityOwner},
10 ast::{self, edit::IndentLevel, make, ArgListOwner, AstNode, NameOwner, VisibilityOwner}, 10 SourceFile, SyntaxElement, SyntaxNode, T,
11 SourceFile, SyntaxElement,
12}; 11};
13 12
14use crate::{ 13use crate::{
@@ -130,17 +129,21 @@ fn existing_definition(db: &RootDatabase, variant_name: &ast::Name, variant: &En
130fn insert_import( 129fn insert_import(
131 ctx: &AssistContext, 130 ctx: &AssistContext,
132 rewriter: &mut SyntaxRewriter, 131 rewriter: &mut SyntaxRewriter,
133 path: &ast::PathExpr, 132 scope_node: &SyntaxNode,
134 module: &Module, 133 module: &Module,
135 enum_module_def: &ModuleDef, 134 enum_module_def: &ModuleDef,
136 variant_hir_name: &Name, 135 variant_hir_name: &Name,
137) -> Option<()> { 136) -> Option<()> {
138 let db = ctx.db(); 137 let db = ctx.db();
139 let mod_path = module.find_use_path(db, enum_module_def.clone()); 138 let mod_path = module.find_use_path_prefixed(
139 db,
140 enum_module_def.clone(),
141 ctx.config.insert_use.prefix_kind,
142 );
140 if let Some(mut mod_path) = mod_path { 143 if let Some(mut mod_path) = mod_path {
141 mod_path.segments.pop(); 144 mod_path.segments.pop();
142 mod_path.segments.push(variant_hir_name.clone()); 145 mod_path.segments.push(variant_hir_name.clone());
143 let scope = ImportScope::find_insert_use_container(path.syntax(), ctx)?; 146 let scope = ImportScope::find_insert_use_container(scope_node, ctx)?;
144 147
145 *rewriter += insert_use(&scope, mod_path_to_ast(&mod_path), ctx.config.insert_use.merge); 148 *rewriter += insert_use(&scope, mod_path_to_ast(&mod_path), ctx.config.insert_use.merge);
146 } 149 }
@@ -204,27 +207,31 @@ fn update_reference(
204 variant_hir_name: &Name, 207 variant_hir_name: &Name,
205 visited_modules_set: &mut FxHashSet<Module>, 208 visited_modules_set: &mut FxHashSet<Module>,
206) -> Option<()> { 209) -> Option<()> {
207 let path_expr: ast::PathExpr = find_node_at_offset::<ast::PathExpr>( 210 let offset = reference.file_range.range.start();
208 source_file.syntax(), 211 let (segment, expr) = if let Some(path_expr) =
209 reference.file_range.range.start(), 212 find_node_at_offset::<ast::PathExpr>(source_file.syntax(), offset)
210 )?; 213 {
211 let call = path_expr.syntax().parent().and_then(ast::CallExpr::cast)?; 214 // tuple variant
212 let list = call.arg_list()?; 215 (path_expr.path()?.segment()?, path_expr.syntax().parent()?.clone())
213 let segment = path_expr.path()?.segment()?; 216 } else if let Some(record_expr) =
214 let module = ctx.sema.scope(&path_expr.syntax()).module()?; 217 find_node_at_offset::<ast::RecordExpr>(source_file.syntax(), offset)
218 {
219 // record variant
220 (record_expr.path()?.segment()?, record_expr.syntax().clone())
221 } else {
222 return None;
223 };
224
225 let module = ctx.sema.scope(&expr).module()?;
215 if !visited_modules_set.contains(&module) { 226 if !visited_modules_set.contains(&module) {
216 if insert_import(ctx, rewriter, &path_expr, &module, enum_module_def, variant_hir_name) 227 if insert_import(ctx, rewriter, &expr, &module, enum_module_def, variant_hir_name).is_some()
217 .is_some()
218 { 228 {
219 visited_modules_set.insert(module); 229 visited_modules_set.insert(module);
220 } 230 }
221 } 231 }
222 232 rewriter.insert_after(segment.syntax(), &make::token(T!['(']));
223 let lparen = syntax::SyntaxElement::from(list.l_paren_token()?); 233 rewriter.insert_after(segment.syntax(), segment.syntax());
224 let rparen = syntax::SyntaxElement::from(list.r_paren_token()?); 234 rewriter.insert_after(&expr, &make::token(T![')']));
225 rewriter.insert_after(&lparen, segment.syntax());
226 rewriter.insert_after(&lparen, &lparen);
227 rewriter.insert_before(&rparen, &rparen);
228 Some(()) 235 Some(())
229} 236}
230 237
@@ -320,7 +327,7 @@ fn another_fn() {
320 r#"use my_mod::my_other_mod::MyField; 327 r#"use my_mod::my_other_mod::MyField;
321 328
322mod my_mod { 329mod my_mod {
323 use my_other_mod::MyField; 330 use self::my_other_mod::MyField;
324 331
325 fn another_fn() { 332 fn another_fn() {
326 let m = my_other_mod::MyEnum::MyField(MyField(1, 1)); 333 let m = my_other_mod::MyEnum::MyField(MyField(1, 1));
@@ -346,6 +353,33 @@ fn another_fn() {
346 } 353 }
347 354
348 #[test] 355 #[test]
356 fn extract_record_fix_references() {
357 check_assist(
358 extract_struct_from_enum_variant,
359 r#"
360enum E {
361 <|>V { i: i32, j: i32 }
362}
363
364fn f() {
365 let e = E::V { i: 9, j: 2 };
366}
367"#,
368 r#"
369struct V{ pub i: i32, pub j: i32 }
370
371enum E {
372 V(V)
373}
374
375fn f() {
376 let e = E::V(V { i: 9, j: 2 });
377}
378"#,
379 )
380 }
381
382 #[test]
349 fn test_several_files() { 383 fn test_several_files() {
350 check_assist( 384 check_assist(
351 extract_struct_from_enum_variant, 385 extract_struct_from_enum_variant,
@@ -372,9 +406,7 @@ enum E {
372mod foo; 406mod foo;
373 407
374//- /foo.rs 408//- /foo.rs
375use V; 409use crate::{E, V};
376
377use crate::E;
378fn f() { 410fn f() {
379 let e = E::V(V(9, 2)); 411 let e = E::V(V(9, 2));
380} 412}
@@ -384,7 +416,6 @@ fn f() {
384 416
385 #[test] 417 #[test]
386 fn test_several_files_record() { 418 fn test_several_files_record() {
387 // FIXME: this should fix the usage as well!
388 check_assist( 419 check_assist(
389 extract_struct_from_enum_variant, 420 extract_struct_from_enum_variant,
390 r#" 421 r#"
@@ -401,6 +432,7 @@ fn f() {
401} 432}
402"#, 433"#,
403 r#" 434 r#"
435//- /main.rs
404struct V{ pub i: i32, pub j: i32 } 436struct V{ pub i: i32, pub j: i32 }
405 437
406enum E { 438enum E {
@@ -408,10 +440,42 @@ enum E {
408} 440}
409mod foo; 441mod foo;
410 442
443//- /foo.rs
444use crate::{E, V};
445fn f() {
446 let e = E::V(V { i: 9, j: 2 });
447}
411"#, 448"#,
412 ) 449 )
413 } 450 }
414 451
452 #[test]
453 fn test_extract_struct_record_nested_call_exp() {
454 check_assist(
455 extract_struct_from_enum_variant,
456 r#"
457enum A { <|>One { a: u32, b: u32 } }
458
459struct B(A);
460
461fn foo() {
462 let _ = B(A::One { a: 1, b: 2 });
463}
464"#,
465 r#"
466struct One{ pub a: u32, pub b: u32 }
467
468enum A { One(One) }
469
470struct B(A);
471
472fn foo() {
473 let _ = B(A::One(One { a: 1, b: 2 }));
474}
475"#,
476 );
477 }
478
415 fn check_not_applicable(ra_fixture: &str) { 479 fn check_not_applicable(ra_fixture: &str) {
416 let fixture = 480 let fixture =
417 format!("//- /main.rs crate:main deps:core\n{}\n{}", ra_fixture, FamousDefs::FIXTURE); 481 format!("//- /main.rs crate:main deps:core\n{}\n{}", ra_fixture, FamousDefs::FIXTURE);
diff --git a/crates/assists/src/handlers/remove_unused_param.rs b/crates/assists/src/handlers/remove_unused_param.rs
index 5fccca54b..1ff5e92b0 100644
--- a/crates/assists/src/handlers/remove_unused_param.rs
+++ b/crates/assists/src/handlers/remove_unused_param.rs
@@ -73,7 +73,8 @@ fn process_usage(
73 let source_file = ctx.sema.parse(usage.file_range.file_id); 73 let source_file = ctx.sema.parse(usage.file_range.file_id);
74 let call_expr: ast::CallExpr = 74 let call_expr: ast::CallExpr =
75 find_node_at_range(source_file.syntax(), usage.file_range.range)?; 75 find_node_at_range(source_file.syntax(), usage.file_range.range)?;
76 if call_expr.expr()?.syntax().text_range() != usage.file_range.range { 76 let call_expr_range = call_expr.expr()?.syntax().text_range();
77 if !call_expr_range.contains_range(usage.file_range.range) {
77 return None; 78 return None;
78 } 79 }
79 let arg = call_expr.arg_list()?.args().nth(arg_to_remove)?; 80 let arg = call_expr.arg_list()?.args().nth(arg_to_remove)?;
@@ -118,6 +119,53 @@ fn b() { foo(9, ) }
118 } 119 }
119 120
120 #[test] 121 #[test]
122 fn remove_unused_qualified_call() {
123 check_assist(
124 remove_unused_param,
125 r#"
126mod bar { pub fn foo(x: i32, <|>y: i32) { x; } }
127fn b() { bar::foo(9, 2) }
128"#,
129 r#"
130mod bar { pub fn foo(x: i32) { x; } }
131fn b() { bar::foo(9) }
132"#,
133 );
134 }
135
136 #[test]
137 fn remove_unused_turbofished_func() {
138 check_assist(
139 remove_unused_param,
140 r#"
141pub fn foo<T>(x: T, <|>y: i32) { x; }
142fn b() { foo::<i32>(9, 2) }
143"#,
144 r#"
145pub fn foo<T>(x: T) { x; }
146fn b() { foo::<i32>(9) }
147"#,
148 );
149 }
150
151 #[test]
152 fn remove_unused_generic_unused_param_func() {
153 check_assist(
154 remove_unused_param,
155 r#"
156pub fn foo<T>(x: i32, <|>y: T) { x; }
157fn b() { foo::<i32>(9, 2) }
158fn b2() { foo(9, 2) }
159"#,
160 r#"
161pub fn foo<T>(x: i32) { x; }
162fn b() { foo::<i32>(9) }
163fn b2() { foo(9) }
164"#,
165 );
166 }
167
168 #[test]
121 fn keep_used() { 169 fn keep_used() {
122 mark::check!(keep_used); 170 mark::check!(keep_used);
123 check_assist_not_applicable( 171 check_assist_not_applicable(
@@ -128,4 +176,37 @@ fn main() { foo(9, 2) }
128"#, 176"#,
129 ); 177 );
130 } 178 }
179
180 #[test]
181 fn remove_across_files() {
182 check_assist(
183 remove_unused_param,
184 r#"
185//- /main.rs
186fn foo(x: i32, <|>y: i32) { x; }
187
188mod foo;
189
190//- /foo.rs
191use super::foo;
192
193fn bar() {
194 let _ = foo(1, 2);
195}
196"#,
197 r#"
198//- /main.rs
199fn foo(x: i32) { x; }
200
201mod foo;
202
203//- /foo.rs
204use super::foo;
205
206fn bar() {
207 let _ = foo(1);
208}
209"#,
210 )
211 }
131} 212}
diff --git a/crates/assists/src/utils.rs b/crates/assists/src/utils.rs
index 7071fe96b..7bd338e99 100644
--- a/crates/assists/src/utils.rs
+++ b/crates/assists/src/utils.rs
@@ -257,6 +257,12 @@ pub mod convert {
257 } 257 }
258} 258}
259 259
260pub mod default {
261 pub trait Default {
262 fn default() -> Self;
263 }
264}
265
260pub mod iter { 266pub mod iter {
261 pub use self::traits::{collect::IntoIterator, iterator::Iterator}; 267 pub use self::traits::{collect::IntoIterator, iterator::Iterator};
262 mod traits { 268 mod traits {
@@ -327,7 +333,7 @@ pub mod option {
327} 333}
328 334
329pub mod prelude { 335pub mod prelude {
330 pub use crate::{convert::From, iter::{IntoIterator, Iterator}, option::Option::{self, *}}; 336 pub use crate::{convert::From, iter::{IntoIterator, Iterator}, option::Option::{self, *}, default::Default};
331} 337}
332#[prelude_import] 338#[prelude_import]
333pub use prelude::*; 339pub use prelude::*;
@@ -345,6 +351,10 @@ pub use prelude::*;
345 self.find_enum("core:option:Option") 351 self.find_enum("core:option:Option")
346 } 352 }
347 353
354 pub fn core_default_Default(&self) -> Option<Trait> {
355 self.find_trait("core:default:Default")
356 }
357
348 pub fn core_iter_Iterator(&self) -> Option<Trait> { 358 pub fn core_iter_Iterator(&self) -> Option<Trait> {
349 self.find_trait("core:iter:traits:iterator:Iterator") 359 self.find_trait("core:iter:traits:iterator:Iterator")
350 } 360 }
diff --git a/crates/assists/src/utils/insert_use.rs b/crates/assists/src/utils/insert_use.rs
index 84a0dffdd..af3fc96b6 100644
--- a/crates/assists/src/utils/insert_use.rs
+++ b/crates/assists/src/utils/insert_use.rs
@@ -9,7 +9,7 @@ use syntax::{
9 edit::{AstNodeEdit, IndentLevel}, 9 edit::{AstNodeEdit, IndentLevel},
10 make, AstNode, PathSegmentKind, VisibilityOwner, 10 make, AstNode, PathSegmentKind, VisibilityOwner,
11 }, 11 },
12 InsertPosition, SyntaxElement, SyntaxNode, 12 AstToken, InsertPosition, NodeOrToken, SyntaxElement, SyntaxNode, SyntaxToken,
13}; 13};
14use test_utils::mark; 14use test_utils::mark;
15 15
@@ -63,27 +63,30 @@ impl ImportScope {
63 } 63 }
64 } 64 }
65 65
66 fn insert_pos_after_inner_attribute(&self) -> (InsertPosition<SyntaxElement>, AddBlankLine) { 66 fn insert_pos_after_last_inner_element(&self) -> (InsertPosition<SyntaxElement>, AddBlankLine) {
67 // check if the scope has inner attributes, we dont want to insert in front of them 67 self.as_syntax_node()
68 match self 68 .children_with_tokens()
69 .as_syntax_node() 69 .filter(|child| match child {
70 .children() 70 NodeOrToken::Node(node) => is_inner_attribute(node.clone()),
71 // no flat_map here cause we want to short circuit the iterator 71 NodeOrToken::Token(token) => is_inner_comment(token.clone()),
72 .map(ast::Attr::cast)
73 .take_while(|attr| {
74 attr.as_ref().map(|attr| attr.kind() == ast::AttrKind::Inner).unwrap_or(false)
75 }) 72 })
76 .last() 73 .last()
77 .flatten() 74 .map(|last_inner_element| {
78 { 75 (InsertPosition::After(last_inner_element.into()), AddBlankLine::BeforeTwice)
79 Some(attr) => { 76 })
80 (InsertPosition::After(attr.syntax().clone().into()), AddBlankLine::BeforeTwice) 77 .unwrap_or_else(|| self.first_insert_pos())
81 }
82 None => self.first_insert_pos(),
83 }
84 } 78 }
85} 79}
86 80
81fn is_inner_attribute(node: SyntaxNode) -> bool {
82 ast::Attr::cast(node).map(|attr| attr.kind()) == Some(ast::AttrKind::Inner)
83}
84
85fn is_inner_comment(token: SyntaxToken) -> bool {
86 ast::Comment::cast(token).and_then(|comment| comment.kind().doc)
87 == Some(ast::CommentPlacement::Inner)
88}
89
87/// Insert an import path into the given file/node. A `merge` value of none indicates that no import merging is allowed to occur. 90/// Insert an import path into the given file/node. A `merge` value of none indicates that no import merging is allowed to occur.
88pub(crate) fn insert_use<'a>( 91pub(crate) fn insert_use<'a>(
89 scope: &ImportScope, 92 scope: &ImportScope,
@@ -558,7 +561,7 @@ fn find_insert_position(
558 (InsertPosition::After(node.into()), AddBlankLine::BeforeTwice) 561 (InsertPosition::After(node.into()), AddBlankLine::BeforeTwice)
559 } 562 }
560 // there are no imports in this file at all 563 // there are no imports in this file at all
561 None => scope.insert_pos_after_inner_attribute(), 564 None => scope.insert_pos_after_last_inner_element(),
562 }, 565 },
563 } 566 }
564 } 567 }
@@ -830,12 +833,67 @@ use foo::bar;",
830 "foo::bar", 833 "foo::bar",
831 r"#![allow(unused_imports)] 834 r"#![allow(unused_imports)]
832 835
836#![no_std]
833fn main() {}", 837fn main() {}",
834 r"#![allow(unused_imports)] 838 r"#![allow(unused_imports)]
835 839
836use foo::bar; 840#![no_std]
837 841
842use foo::bar;
838fn main() {}", 843fn main() {}",
844 );
845 }
846
847 #[test]
848 fn inserts_after_single_line_inner_comments() {
849 check_none(
850 "foo::bar::Baz",
851 "//! Single line inner comments do not allow any code before them.",
852 r#"//! Single line inner comments do not allow any code before them.
853
854use foo::bar::Baz;"#,
855 );
856 }
857
858 #[test]
859 fn inserts_after_multiline_inner_comments() {
860 check_none(
861 "foo::bar::Baz",
862 r#"/*! Multiline inner comments do not allow any code before them. */
863
864/*! Still an inner comment, cannot place any code before. */
865fn main() {}"#,
866 r#"/*! Multiline inner comments do not allow any code before them. */
867
868/*! Still an inner comment, cannot place any code before. */
869
870use foo::bar::Baz;
871fn main() {}"#,
872 )
873 }
874
875 #[test]
876 fn inserts_after_all_inner_items() {
877 check_none(
878 "foo::bar::Baz",
879 r#"#![allow(unused_imports)]
880/*! Multiline line comment 2 */
881
882
883//! Single line comment 1
884#![no_std]
885//! Single line comment 2
886fn main() {}"#,
887 r#"#![allow(unused_imports)]
888/*! Multiline line comment 2 */
889
890
891//! Single line comment 1
892#![no_std]
893//! Single line comment 2
894
895use foo::bar::Baz;
896fn main() {}"#,
839 ) 897 )
840 } 898 }
841 899
diff --git a/crates/completion/Cargo.toml b/crates/completion/Cargo.toml
index b79ee33f7..3015ec9e0 100644
--- a/crates/completion/Cargo.toml
+++ b/crates/completion/Cargo.toml
@@ -14,6 +14,7 @@ itertools = "0.9.0"
14log = "0.4.8" 14log = "0.4.8"
15rustc-hash = "1.1.0" 15rustc-hash = "1.1.0"
16 16
17assists = { path = "../assists", version = "0.0.0" }
17stdx = { path = "../stdx", version = "0.0.0" } 18stdx = { path = "../stdx", version = "0.0.0" }
18syntax = { path = "../syntax", version = "0.0.0" } 19syntax = { path = "../syntax", version = "0.0.0" }
19text_edit = { path = "../text_edit", version = "0.0.0" } 20text_edit = { path = "../text_edit", version = "0.0.0" }
diff --git a/crates/completion/src/completions/record.rs b/crates/completion/src/completions/record.rs
index 0f611084b..2049b9d09 100644
--- a/crates/completion/src/completions/record.rs
+++ b/crates/completion/src/completions/record.rs
@@ -1,16 +1,43 @@
1//! Complete fields in record literals and patterns. 1//! Complete fields in record literals and patterns.
2use crate::{CompletionContext, Completions}; 2use assists::utils::FamousDefs;
3use syntax::ast::Expr;
4
5use crate::{
6 item::CompletionKind, CompletionContext, CompletionItem, CompletionItemKind, Completions,
7};
3 8
4pub(crate) fn complete_record(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> { 9pub(crate) fn complete_record(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> {
5 let missing_fields = match (ctx.record_pat_syntax.as_ref(), ctx.record_lit_syntax.as_ref()) { 10 let missing_fields = match (ctx.record_pat_syntax.as_ref(), ctx.record_lit_syntax.as_ref()) {
6 (None, None) => return None, 11 (None, None) => return None,
7 (Some(_), Some(_)) => unreachable!("A record cannot be both a literal and a pattern"), 12 (Some(_), Some(_)) => unreachable!("A record cannot be both a literal and a pattern"),
8 (Some(record_pat), _) => ctx.sema.record_pattern_missing_fields(record_pat), 13 (Some(record_pat), _) => ctx.sema.record_pattern_missing_fields(record_pat),
9 (_, Some(record_lit)) => ctx.sema.record_literal_missing_fields(record_lit), 14 (_, Some(record_lit)) => {
15 let ty = ctx.sema.type_of_expr(&Expr::RecordExpr(record_lit.clone()));
16 let default_trait = FamousDefs(&ctx.sema, ctx.krate).core_default_Default();
17 let impl_default_trait = default_trait
18 .and_then(|default_trait| ty.map(|ty| ty.impls_trait(ctx.db, default_trait, &[])))
19 .unwrap_or(false);
20
21 let missing_fields = ctx.sema.record_literal_missing_fields(record_lit);
22 if impl_default_trait && !missing_fields.is_empty() {
23 acc.add(
24 CompletionItem::new(
25 CompletionKind::Snippet,
26 ctx.source_range(),
27 "..Default::default()",
28 )
29 .insert_text("..Default::default()")
30 .kind(CompletionItemKind::Field)
31 .build(),
32 );
33 }
34
35 missing_fields
36 }
10 }; 37 };
11 38
12 for (field, ty) in missing_fields { 39 for (field, ty) in missing_fields {
13 acc.add_field(ctx, field, &ty) 40 acc.add_field(ctx, field, &ty);
14 } 41 }
15 42
16 Some(()) 43 Some(())
@@ -18,6 +45,7 @@ pub(crate) fn complete_record(acc: &mut Completions, ctx: &CompletionContext) ->
18 45
19#[cfg(test)] 46#[cfg(test)]
20mod tests { 47mod tests {
48 use assists::utils::FamousDefs;
21 use expect_test::{expect, Expect}; 49 use expect_test::{expect, Expect};
22 50
23 use crate::{test_utils::completion_list, CompletionKind}; 51 use crate::{test_utils::completion_list, CompletionKind};
@@ -27,6 +55,80 @@ mod tests {
27 expect.assert_eq(&actual); 55 expect.assert_eq(&actual);
28 } 56 }
29 57
58 fn check_snippet(ra_fixture: &str, expect: Expect) {
59 let actual = completion_list(
60 &format!("//- /main.rs crate:main deps:core\n{}\n{}", ra_fixture, FamousDefs::FIXTURE),
61 CompletionKind::Snippet,
62 );
63 expect.assert_eq(&actual);
64 }
65
66 #[test]
67 fn test_record_literal_field_default() {
68 let test_code = r#"
69struct S { foo: u32, bar: usize }
70
71impl core::default::Default for S {
72 fn default() -> Self {
73 S {
74 foo: 0,
75 bar: 0,
76 }
77 }
78}
79
80fn process(f: S) {
81 let other = S {
82 foo: 5,
83 .<|>
84 };
85}
86"#;
87 check(
88 test_code,
89 expect![[r#"
90 fd bar usize
91 "#]],
92 );
93
94 check_snippet(
95 test_code,
96 expect![[r#"
97 fd ..Default::default()
98 sn pd
99 sn ppd
100 "#]],
101 );
102 }
103
104 #[test]
105 fn test_record_literal_field_without_default() {
106 let test_code = r#"
107struct S { foo: u32, bar: usize }
108
109fn process(f: S) {
110 let other = S {
111 foo: 5,
112 .<|>
113 };
114}
115"#;
116 check(
117 test_code,
118 expect![[r#"
119 fd bar usize
120 "#]],
121 );
122
123 check_snippet(
124 test_code,
125 expect![[r#"
126 sn pd
127 sn ppd
128 "#]],
129 );
130 }
131
30 #[test] 132 #[test]
31 fn test_record_pattern_field() { 133 fn test_record_pattern_field() {
32 check( 134 check(
diff --git a/crates/project_model/src/cargo_workspace.rs b/crates/project_model/src/cargo_workspace.rs
index d5f6a4025..540b57ae4 100644
--- a/crates/project_model/src/cargo_workspace.rs
+++ b/crates/project_model/src/cargo_workspace.rs
@@ -64,6 +64,13 @@ pub struct CargoConfig {
64 64
65 /// rustc target 65 /// rustc target
66 pub target: Option<String>, 66 pub target: Option<String>,
67
68 /// Don't load sysroot crates (`std`, `core` & friends). Might be useful
69 /// when debugging isolated issues.
70 pub no_sysroot: bool,
71
72 /// rustc private crate source
73 pub rustc_source: Option<AbsPathBuf>,
67} 74}
68 75
69pub type Package = Idx<PackageData>; 76pub type Package = Idx<PackageData>;
@@ -137,27 +144,27 @@ impl PackageData {
137impl CargoWorkspace { 144impl CargoWorkspace {
138 pub fn from_cargo_metadata( 145 pub fn from_cargo_metadata(
139 cargo_toml: &AbsPath, 146 cargo_toml: &AbsPath,
140 cargo_features: &CargoConfig, 147 config: &CargoConfig,
141 ) -> Result<CargoWorkspace> { 148 ) -> Result<CargoWorkspace> {
142 let mut meta = MetadataCommand::new(); 149 let mut meta = MetadataCommand::new();
143 meta.cargo_path(toolchain::cargo()); 150 meta.cargo_path(toolchain::cargo());
144 meta.manifest_path(cargo_toml.to_path_buf()); 151 meta.manifest_path(cargo_toml.to_path_buf());
145 if cargo_features.all_features { 152 if config.all_features {
146 meta.features(CargoOpt::AllFeatures); 153 meta.features(CargoOpt::AllFeatures);
147 } else { 154 } else {
148 if cargo_features.no_default_features { 155 if config.no_default_features {
149 // FIXME: `NoDefaultFeatures` is mutual exclusive with `SomeFeatures` 156 // FIXME: `NoDefaultFeatures` is mutual exclusive with `SomeFeatures`
150 // https://github.com/oli-obk/cargo_metadata/issues/79 157 // https://github.com/oli-obk/cargo_metadata/issues/79
151 meta.features(CargoOpt::NoDefaultFeatures); 158 meta.features(CargoOpt::NoDefaultFeatures);
152 } 159 }
153 if !cargo_features.features.is_empty() { 160 if !config.features.is_empty() {
154 meta.features(CargoOpt::SomeFeatures(cargo_features.features.clone())); 161 meta.features(CargoOpt::SomeFeatures(config.features.clone()));
155 } 162 }
156 } 163 }
157 if let Some(parent) = cargo_toml.parent() { 164 if let Some(parent) = cargo_toml.parent() {
158 meta.current_dir(parent.to_path_buf()); 165 meta.current_dir(parent.to_path_buf());
159 } 166 }
160 if let Some(target) = cargo_features.target.as_ref() { 167 if let Some(target) = config.target.as_ref() {
161 meta.other_options(vec![String::from("--filter-platform"), target.clone()]); 168 meta.other_options(vec![String::from("--filter-platform"), target.clone()]);
162 } 169 }
163 let mut meta = meta.exec().with_context(|| { 170 let mut meta = meta.exec().with_context(|| {
@@ -167,8 +174,8 @@ impl CargoWorkspace {
167 let mut out_dir_by_id = FxHashMap::default(); 174 let mut out_dir_by_id = FxHashMap::default();
168 let mut cfgs = FxHashMap::default(); 175 let mut cfgs = FxHashMap::default();
169 let mut proc_macro_dylib_paths = FxHashMap::default(); 176 let mut proc_macro_dylib_paths = FxHashMap::default();
170 if cargo_features.load_out_dirs_from_check { 177 if config.load_out_dirs_from_check {
171 let resources = load_extern_resources(cargo_toml, cargo_features)?; 178 let resources = load_extern_resources(cargo_toml, config)?;
172 out_dir_by_id = resources.out_dirs; 179 out_dir_by_id = resources.out_dirs;
173 cfgs = resources.cfgs; 180 cfgs = resources.cfgs;
174 proc_macro_dylib_paths = resources.proc_dylib_paths; 181 proc_macro_dylib_paths = resources.proc_dylib_paths;
diff --git a/crates/project_model/src/lib.rs b/crates/project_model/src/lib.rs
index e92cfea59..24aa9b8fa 100644
--- a/crates/project_model/src/lib.rs
+++ b/crates/project_model/src/lib.rs
@@ -4,69 +4,27 @@ mod cargo_workspace;
4mod project_json; 4mod project_json;
5mod sysroot; 5mod sysroot;
6mod cfg_flag; 6mod cfg_flag;
7mod workspace;
7 8
8use std::{ 9use std::{
9 fmt, 10 fs::{read_dir, ReadDir},
10 fs::{self, read_dir, ReadDir},
11 io, 11 io,
12 process::Command, 12 process::Command,
13}; 13};
14 14
15use anyhow::{bail, Context, Result}; 15use anyhow::{bail, Context, Result};
16use base_db::{CrateDisplayName, CrateGraph, CrateId, CrateName, Edition, Env, FileId};
17use cfg::CfgOptions;
18use paths::{AbsPath, AbsPathBuf}; 16use paths::{AbsPath, AbsPathBuf};
19use rustc_hash::{FxHashMap, FxHashSet}; 17use rustc_hash::FxHashSet;
20
21use crate::cfg_flag::CfgFlag;
22 18
23pub use crate::{ 19pub use crate::{
24 cargo_workspace::{CargoConfig, CargoWorkspace, Package, Target, TargetKind}, 20 cargo_workspace::{CargoConfig, CargoWorkspace, Package, Target, TargetKind},
25 project_json::{ProjectJson, ProjectJsonData}, 21 project_json::{ProjectJson, ProjectJsonData},
26 sysroot::Sysroot, 22 sysroot::Sysroot,
23 workspace::{PackageRoot, ProjectWorkspace},
27}; 24};
28 25
29pub use proc_macro_api::ProcMacroClient; 26pub use proc_macro_api::ProcMacroClient;
30 27
31#[derive(Clone, Eq, PartialEq)]
32pub enum ProjectWorkspace {
33 /// Project workspace was discovered by running `cargo metadata` and `rustc --print sysroot`.
34 Cargo { cargo: CargoWorkspace, sysroot: Sysroot },
35 /// Project workspace was manually specified using a `rust-project.json` file.
36 Json { project: ProjectJson, sysroot: Option<Sysroot> },
37}
38
39impl fmt::Debug for ProjectWorkspace {
40 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
41 match self {
42 ProjectWorkspace::Cargo { cargo, sysroot } => f
43 .debug_struct("Cargo")
44 .field("n_packages", &cargo.packages().len())
45 .field("n_sysroot_crates", &sysroot.crates().len())
46 .finish(),
47 ProjectWorkspace::Json { project, sysroot } => {
48 let mut debug_struct = f.debug_struct("Json");
49 debug_struct.field("n_crates", &project.n_crates());
50 if let Some(sysroot) = sysroot {
51 debug_struct.field("n_sysroot_crates", &sysroot.crates().len());
52 }
53 debug_struct.finish()
54 }
55 }
56 }
57}
58
59/// `PackageRoot` describes a package root folder.
60/// Which may be an external dependency, or a member of
61/// the current workspace.
62#[derive(Debug, Clone, Eq, PartialEq, Hash)]
63pub struct PackageRoot {
64 /// Is a member of the current workspace
65 pub is_member: bool,
66 pub include: Vec<AbsPathBuf>,
67 pub exclude: Vec<AbsPathBuf>,
68}
69
70#[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)] 28#[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)]
71pub enum ProjectManifest { 29pub enum ProjectManifest {
72 ProjectJson(AbsPathBuf), 30 ProjectJson(AbsPathBuf),
@@ -153,376 +111,6 @@ impl ProjectManifest {
153 } 111 }
154} 112}
155 113
156impl ProjectWorkspace {
157 pub fn load(
158 manifest: ProjectManifest,
159 cargo_config: &CargoConfig,
160 with_sysroot: bool,
161 ) -> Result<ProjectWorkspace> {
162 let res = match manifest {
163 ProjectManifest::ProjectJson(project_json) => {
164 let file = fs::read_to_string(&project_json).with_context(|| {
165 format!("Failed to read json file {}", project_json.display())
166 })?;
167 let data = serde_json::from_str(&file).with_context(|| {
168 format!("Failed to deserialize json file {}", project_json.display())
169 })?;
170 let project_location = project_json.parent().unwrap().to_path_buf();
171 let project = ProjectJson::new(&project_location, data);
172 let sysroot = match &project.sysroot_src {
173 Some(path) => Some(Sysroot::load(path)?),
174 None => None,
175 };
176 ProjectWorkspace::Json { project, sysroot }
177 }
178 ProjectManifest::CargoToml(cargo_toml) => {
179 let cargo_version = utf8_stdout({
180 let mut cmd = Command::new(toolchain::cargo());
181 cmd.arg("--version");
182 cmd
183 })?;
184
185 let cargo = CargoWorkspace::from_cargo_metadata(&cargo_toml, cargo_config)
186 .with_context(|| {
187 format!(
188 "Failed to read Cargo metadata from Cargo.toml file {}, {}",
189 cargo_toml.display(),
190 cargo_version
191 )
192 })?;
193 let sysroot = if with_sysroot {
194 Sysroot::discover(&cargo_toml).with_context(|| {
195 format!(
196 "Failed to find sysroot for Cargo.toml file {}. Is rust-src installed?",
197 cargo_toml.display()
198 )
199 })?
200 } else {
201 Sysroot::default()
202 };
203 ProjectWorkspace::Cargo { cargo, sysroot }
204 }
205 };
206
207 Ok(res)
208 }
209
210 pub fn load_inline(project_json: ProjectJson) -> Result<ProjectWorkspace> {
211 let sysroot = match &project_json.sysroot_src {
212 Some(path) => Some(Sysroot::load(path)?),
213 None => None,
214 };
215
216 Ok(ProjectWorkspace::Json { project: project_json, sysroot })
217 }
218
219 /// Returns the roots for the current `ProjectWorkspace`
220 /// The return type contains the path and whether or not
221 /// the root is a member of the current workspace
222 pub fn to_roots(&self) -> Vec<PackageRoot> {
223 match self {
224 ProjectWorkspace::Json { project, sysroot } => project
225 .crates()
226 .map(|(_, krate)| PackageRoot {
227 is_member: krate.is_workspace_member,
228 include: krate.include.clone(),
229 exclude: krate.exclude.clone(),
230 })
231 .collect::<FxHashSet<_>>()
232 .into_iter()
233 .chain(sysroot.as_ref().into_iter().flat_map(|sysroot| {
234 sysroot.crates().map(move |krate| PackageRoot {
235 is_member: false,
236 include: vec![sysroot[krate].root_dir().to_path_buf()],
237 exclude: Vec::new(),
238 })
239 }))
240 .collect::<Vec<_>>(),
241 ProjectWorkspace::Cargo { cargo, sysroot } => cargo
242 .packages()
243 .map(|pkg| {
244 let is_member = cargo[pkg].is_member;
245 let pkg_root = cargo[pkg].root().to_path_buf();
246
247 let mut include = vec![pkg_root.clone()];
248 include.extend(cargo[pkg].out_dir.clone());
249
250 let mut exclude = vec![pkg_root.join(".git")];
251 if is_member {
252 exclude.push(pkg_root.join("target"));
253 } else {
254 exclude.push(pkg_root.join("tests"));
255 exclude.push(pkg_root.join("examples"));
256 exclude.push(pkg_root.join("benches"));
257 }
258 PackageRoot { is_member, include, exclude }
259 })
260 .chain(sysroot.crates().map(|krate| PackageRoot {
261 is_member: false,
262 include: vec![sysroot[krate].root_dir().to_path_buf()],
263 exclude: Vec::new(),
264 }))
265 .collect(),
266 }
267 }
268
269 pub fn proc_macro_dylib_paths(&self) -> Vec<AbsPathBuf> {
270 match self {
271 ProjectWorkspace::Json { project, sysroot: _ } => project
272 .crates()
273 .filter_map(|(_, krate)| krate.proc_macro_dylib_path.as_ref())
274 .cloned()
275 .collect(),
276 ProjectWorkspace::Cargo { cargo, sysroot: _sysroot } => cargo
277 .packages()
278 .filter_map(|pkg| cargo[pkg].proc_macro_dylib_path.as_ref())
279 .cloned()
280 .collect(),
281 }
282 }
283
284 pub fn n_packages(&self) -> usize {
285 match self {
286 ProjectWorkspace::Json { project, .. } => project.n_crates(),
287 ProjectWorkspace::Cargo { cargo, sysroot } => {
288 cargo.packages().len() + sysroot.crates().len()
289 }
290 }
291 }
292
293 pub fn to_crate_graph(
294 &self,
295 target: Option<&str>,
296 proc_macro_client: &ProcMacroClient,
297 load: &mut dyn FnMut(&AbsPath) -> Option<FileId>,
298 ) -> CrateGraph {
299 let mut crate_graph = CrateGraph::default();
300 match self {
301 ProjectWorkspace::Json { project, sysroot } => {
302 let sysroot_dps = sysroot
303 .as_ref()
304 .map(|sysroot| sysroot_to_crate_graph(&mut crate_graph, sysroot, target, load));
305
306 let mut cfg_cache: FxHashMap<Option<&str>, Vec<CfgFlag>> = FxHashMap::default();
307 let crates: FxHashMap<_, _> = project
308 .crates()
309 .filter_map(|(crate_id, krate)| {
310 let file_path = &krate.root_module;
311 let file_id = match load(&file_path) {
312 Some(id) => id,
313 None => {
314 log::error!("failed to load crate root {}", file_path.display());
315 return None;
316 }
317 };
318
319 let env = krate.env.clone().into_iter().collect();
320 let proc_macro = krate
321 .proc_macro_dylib_path
322 .clone()
323 .map(|it| proc_macro_client.by_dylib_path(&it));
324
325 let target = krate.target.as_deref().or(target);
326 let target_cfgs = cfg_cache
327 .entry(target)
328 .or_insert_with(|| get_rustc_cfg_options(target));
329
330 let mut cfg_options = CfgOptions::default();
331 cfg_options.extend(target_cfgs.iter().chain(krate.cfg.iter()).cloned());
332
333 Some((
334 crate_id,
335 crate_graph.add_crate_root(
336 file_id,
337 krate.edition,
338 krate.display_name.clone(),
339 cfg_options,
340 env,
341 proc_macro.unwrap_or_default(),
342 ),
343 ))
344 })
345 .collect();
346
347 for (from, krate) in project.crates() {
348 if let Some(&from) = crates.get(&from) {
349 if let Some((public_deps, _proc_macro)) = &sysroot_dps {
350 for (name, to) in public_deps.iter() {
351 if let Err(_) = crate_graph.add_dep(from, name.clone(), *to) {
352 log::error!("cyclic dependency on {} for {:?}", name, from)
353 }
354 }
355 }
356
357 for dep in &krate.deps {
358 let to_crate_id = dep.crate_id;
359 if let Some(&to) = crates.get(&to_crate_id) {
360 if let Err(_) = crate_graph.add_dep(from, dep.name.clone(), to) {
361 log::error!("cyclic dependency {:?} -> {:?}", from, to);
362 }
363 }
364 }
365 }
366 }
367 }
368 ProjectWorkspace::Cargo { cargo, sysroot } => {
369 let (public_deps, libproc_macro) =
370 sysroot_to_crate_graph(&mut crate_graph, sysroot, target, load);
371
372 let mut cfg_options = CfgOptions::default();
373 cfg_options.extend(get_rustc_cfg_options(target));
374
375 let mut pkg_to_lib_crate = FxHashMap::default();
376 let mut pkg_crates = FxHashMap::default();
377
378 // Add test cfg for non-sysroot crates
379 cfg_options.insert_atom("test".into());
380 cfg_options.insert_atom("debug_assertions".into());
381
382 // Next, create crates for each package, target pair
383 for pkg in cargo.packages() {
384 let mut lib_tgt = None;
385 for &tgt in cargo[pkg].targets.iter() {
386 let root = cargo[tgt].root.as_path();
387 if let Some(file_id) = load(root) {
388 let edition = cargo[pkg].edition;
389 let cfg_options = {
390 let mut opts = cfg_options.clone();
391 for feature in cargo[pkg].features.iter() {
392 opts.insert_key_value("feature".into(), feature.into());
393 }
394 opts.extend(cargo[pkg].cfgs.iter().cloned());
395 opts
396 };
397 let mut env = Env::default();
398 if let Some(out_dir) = &cargo[pkg].out_dir {
399 // NOTE: cargo and rustc seem to hide non-UTF-8 strings from env! and option_env!()
400 if let Some(out_dir) = out_dir.to_str().map(|s| s.to_owned()) {
401 env.set("OUT_DIR", out_dir);
402 }
403 }
404 let proc_macro = cargo[pkg]
405 .proc_macro_dylib_path
406 .as_ref()
407 .map(|it| proc_macro_client.by_dylib_path(&it))
408 .unwrap_or_default();
409
410 let display_name =
411 CrateDisplayName::from_canonical_name(cargo[pkg].name.clone());
412 let crate_id = crate_graph.add_crate_root(
413 file_id,
414 edition,
415 Some(display_name),
416 cfg_options,
417 env,
418 proc_macro.clone(),
419 );
420 if cargo[tgt].kind == TargetKind::Lib {
421 lib_tgt = Some((crate_id, cargo[tgt].name.clone()));
422 pkg_to_lib_crate.insert(pkg, crate_id);
423 }
424 if cargo[tgt].is_proc_macro {
425 if let Some(proc_macro) = libproc_macro {
426 if let Err(_) = crate_graph.add_dep(
427 crate_id,
428 CrateName::new("proc_macro").unwrap(),
429 proc_macro,
430 ) {
431 log::error!(
432 "cyclic dependency on proc_macro for {}",
433 &cargo[pkg].name
434 )
435 }
436 }
437 }
438
439 pkg_crates.entry(pkg).or_insert_with(Vec::new).push(crate_id);
440 }
441 }
442
443 // Set deps to the core, std and to the lib target of the current package
444 for &from in pkg_crates.get(&pkg).into_iter().flatten() {
445 if let Some((to, name)) = lib_tgt.clone() {
446 // For root projects with dashes in their name,
447 // cargo metadata does not do any normalization,
448 // so we do it ourselves currently
449 let name = CrateName::normalize_dashes(&name);
450 if to != from && crate_graph.add_dep(from, name, to).is_err() {
451 log::error!(
452 "cyclic dependency between targets of {}",
453 &cargo[pkg].name
454 )
455 }
456 }
457 for (name, krate) in public_deps.iter() {
458 if let Err(_) = crate_graph.add_dep(from, name.clone(), *krate) {
459 log::error!(
460 "cyclic dependency on {} for {}",
461 name,
462 &cargo[pkg].name
463 )
464 }
465 }
466 }
467 }
468
469 // Now add a dep edge from all targets of upstream to the lib
470 // target of downstream.
471 for pkg in cargo.packages() {
472 for dep in cargo[pkg].dependencies.iter() {
473 let name = CrateName::new(&dep.name).unwrap();
474 if let Some(&to) = pkg_to_lib_crate.get(&dep.pkg) {
475 for &from in pkg_crates.get(&pkg).into_iter().flatten() {
476 if let Err(_) = crate_graph.add_dep(from, name.clone(), to) {
477 log::error!(
478 "cyclic dependency {} -> {}",
479 &cargo[pkg].name,
480 &cargo[dep.pkg].name
481 )
482 }
483 }
484 }
485 }
486 }
487 }
488 }
489 if crate_graph.patch_cfg_if() {
490 log::debug!("Patched std to depend on cfg-if")
491 } else {
492 log::debug!("Did not patch std to depend on cfg-if")
493 }
494 crate_graph
495 }
496}
497
498fn get_rustc_cfg_options(target: Option<&str>) -> Vec<CfgFlag> {
499 let mut res = Vec::new();
500
501 // Some nightly-only cfgs, which are required for stdlib
502 res.push(CfgFlag::Atom("target_thread_local".into()));
503 for &ty in ["8", "16", "32", "64", "cas", "ptr"].iter() {
504 for &key in ["target_has_atomic", "target_has_atomic_load_store"].iter() {
505 res.push(CfgFlag::KeyValue { key: key.to_string(), value: ty.into() });
506 }
507 }
508
509 let rustc_cfgs = {
510 let mut cmd = Command::new(toolchain::rustc());
511 cmd.args(&["--print", "cfg", "-O"]);
512 if let Some(target) = target {
513 cmd.args(&["--target", target]);
514 }
515 utf8_stdout(cmd)
516 };
517
518 match rustc_cfgs {
519 Ok(rustc_cfgs) => res.extend(rustc_cfgs.lines().map(|it| it.parse().unwrap())),
520 Err(e) => log::error!("failed to get rustc cfgs: {:#}", e),
521 }
522
523 res
524}
525
526fn utf8_stdout(mut cmd: Command) -> Result<String> { 114fn utf8_stdout(mut cmd: Command) -> Result<String> {
527 let output = cmd.output().with_context(|| format!("{:?} failed", cmd))?; 115 let output = cmd.output().with_context(|| format!("{:?} failed", cmd))?;
528 if !output.status.success() { 116 if !output.status.success() {
@@ -536,52 +124,3 @@ fn utf8_stdout(mut cmd: Command) -> Result<String> {
536 let stdout = String::from_utf8(output.stdout)?; 124 let stdout = String::from_utf8(output.stdout)?;
537 Ok(stdout.trim().to_string()) 125 Ok(stdout.trim().to_string())
538} 126}
539
540fn sysroot_to_crate_graph(
541 crate_graph: &mut CrateGraph,
542 sysroot: &Sysroot,
543 target: Option<&str>,
544 load: &mut dyn FnMut(&AbsPath) -> Option<FileId>,
545) -> (Vec<(CrateName, CrateId)>, Option<CrateId>) {
546 let mut cfg_options = CfgOptions::default();
547 cfg_options.extend(get_rustc_cfg_options(target));
548 let sysroot_crates: FxHashMap<_, _> = sysroot
549 .crates()
550 .filter_map(|krate| {
551 let file_id = load(&sysroot[krate].root)?;
552
553 let env = Env::default();
554 let proc_macro = vec![];
555 let name = CrateName::new(&sysroot[krate].name)
556 .expect("Sysroot crates' names do not contain dashes");
557 let crate_id = crate_graph.add_crate_root(
558 file_id,
559 Edition::Edition2018,
560 Some(name.into()),
561 cfg_options.clone(),
562 env,
563 proc_macro,
564 );
565 Some((krate, crate_id))
566 })
567 .collect();
568
569 for from in sysroot.crates() {
570 for &to in sysroot[from].deps.iter() {
571 let name = CrateName::new(&sysroot[to].name).unwrap();
572 if let (Some(&from), Some(&to)) = (sysroot_crates.get(&from), sysroot_crates.get(&to)) {
573 if let Err(_) = crate_graph.add_dep(from, name, to) {
574 log::error!("cyclic dependency between sysroot crates")
575 }
576 }
577 }
578 }
579
580 let public_deps = sysroot
581 .public_deps()
582 .map(|(name, idx)| (CrateName::new(name).unwrap(), sysroot_crates[&idx]))
583 .collect::<Vec<_>>();
584
585 let libproc_macro = sysroot.proc_macro().and_then(|it| sysroot_crates.get(&it).copied());
586 (public_deps, libproc_macro)
587}
diff --git a/crates/project_model/src/workspace.rs b/crates/project_model/src/workspace.rs
new file mode 100644
index 000000000..9ebb0a811
--- /dev/null
+++ b/crates/project_model/src/workspace.rs
@@ -0,0 +1,590 @@
1//! Handles lowering of build-system specific workspace information (`cargo
2//! metadata` or `rust-project.json`) into representation stored in the salsa
3//! database -- `CrateGraph`.
4
5use std::{fmt, fs, path::Component, process::Command};
6
7use anyhow::{Context, Result};
8use base_db::{CrateDisplayName, CrateGraph, CrateId, CrateName, Edition, Env, FileId};
9use cfg::CfgOptions;
10use paths::{AbsPath, AbsPathBuf};
11use proc_macro_api::ProcMacroClient;
12use rustc_hash::{FxHashMap, FxHashSet};
13
14use crate::{
15 cargo_workspace, cfg_flag::CfgFlag, utf8_stdout, CargoConfig, CargoWorkspace, ProjectJson,
16 ProjectManifest, Sysroot, TargetKind,
17};
18
19/// `PackageRoot` describes a package root folder.
20/// Which may be an external dependency, or a member of
21/// the current workspace.
22#[derive(Debug, Clone, Eq, PartialEq, Hash)]
23pub struct PackageRoot {
24 /// Is a member of the current workspace
25 pub is_member: bool,
26 pub include: Vec<AbsPathBuf>,
27 pub exclude: Vec<AbsPathBuf>,
28}
29
30#[derive(Clone, Eq, PartialEq)]
31pub enum ProjectWorkspace {
32 /// Project workspace was discovered by running `cargo metadata` and `rustc --print sysroot`.
33 Cargo { cargo: CargoWorkspace, sysroot: Sysroot, rustc: Option<CargoWorkspace> },
34 /// Project workspace was manually specified using a `rust-project.json` file.
35 Json { project: ProjectJson, sysroot: Option<Sysroot> },
36}
37
38impl fmt::Debug for ProjectWorkspace {
39 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
40 match self {
41 ProjectWorkspace::Cargo { cargo, sysroot, rustc } => f
42 .debug_struct("Cargo")
43 .field("n_packages", &cargo.packages().len())
44 .field("n_sysroot_crates", &sysroot.crates().len())
45 .field(
46 "n_rustc_compiler_crates",
47 &rustc.as_ref().map_or(0, |rc| rc.packages().len()),
48 )
49 .finish(),
50 ProjectWorkspace::Json { project, sysroot } => {
51 let mut debug_struct = f.debug_struct("Json");
52 debug_struct.field("n_crates", &project.n_crates());
53 if let Some(sysroot) = sysroot {
54 debug_struct.field("n_sysroot_crates", &sysroot.crates().len());
55 }
56 debug_struct.finish()
57 }
58 }
59 }
60}
61
62impl ProjectWorkspace {
63 pub fn load(manifest: ProjectManifest, config: &CargoConfig) -> Result<ProjectWorkspace> {
64 let res = match manifest {
65 ProjectManifest::ProjectJson(project_json) => {
66 let file = fs::read_to_string(&project_json).with_context(|| {
67 format!("Failed to read json file {}", project_json.display())
68 })?;
69 let data = serde_json::from_str(&file).with_context(|| {
70 format!("Failed to deserialize json file {}", project_json.display())
71 })?;
72 let project_location = project_json.parent().unwrap().to_path_buf();
73 let project = ProjectJson::new(&project_location, data);
74 let sysroot = match &project.sysroot_src {
75 Some(path) => Some(Sysroot::load(path)?),
76 None => None,
77 };
78 ProjectWorkspace::Json { project, sysroot }
79 }
80 ProjectManifest::CargoToml(cargo_toml) => {
81 let cargo_version = utf8_stdout({
82 let mut cmd = Command::new(toolchain::cargo());
83 cmd.arg("--version");
84 cmd
85 })?;
86
87 let cargo = CargoWorkspace::from_cargo_metadata(&cargo_toml, config).with_context(
88 || {
89 format!(
90 "Failed to read Cargo metadata from Cargo.toml file {}, {}",
91 cargo_toml.display(),
92 cargo_version
93 )
94 },
95 )?;
96 let sysroot = if config.no_sysroot {
97 Sysroot::default()
98 } else {
99 Sysroot::discover(&cargo_toml).with_context(|| {
100 format!(
101 "Failed to find sysroot for Cargo.toml file {}. Is rust-src installed?",
102 cargo_toml.display()
103 )
104 })?
105 };
106
107 let rustc = if let Some(rustc_dir) = &config.rustc_source {
108 Some(CargoWorkspace::from_cargo_metadata(&rustc_dir, config).with_context(
109 || format!("Failed to read Cargo metadata for Rust sources"),
110 )?)
111 } else {
112 None
113 };
114
115 ProjectWorkspace::Cargo { cargo, sysroot, rustc }
116 }
117 };
118
119 Ok(res)
120 }
121
122 pub fn load_inline(project_json: ProjectJson) -> Result<ProjectWorkspace> {
123 let sysroot = match &project_json.sysroot_src {
124 Some(path) => Some(Sysroot::load(path)?),
125 None => None,
126 };
127
128 Ok(ProjectWorkspace::Json { project: project_json, sysroot })
129 }
130
131 /// Returns the roots for the current `ProjectWorkspace`
132 /// The return type contains the path and whether or not
133 /// the root is a member of the current workspace
134 pub fn to_roots(&self) -> Vec<PackageRoot> {
135 match self {
136 ProjectWorkspace::Json { project, sysroot } => project
137 .crates()
138 .map(|(_, krate)| PackageRoot {
139 is_member: krate.is_workspace_member,
140 include: krate.include.clone(),
141 exclude: krate.exclude.clone(),
142 })
143 .collect::<FxHashSet<_>>()
144 .into_iter()
145 .chain(sysroot.as_ref().into_iter().flat_map(|sysroot| {
146 sysroot.crates().map(move |krate| PackageRoot {
147 is_member: false,
148 include: vec![sysroot[krate].root_dir().to_path_buf()],
149 exclude: Vec::new(),
150 })
151 }))
152 .collect::<Vec<_>>(),
153 ProjectWorkspace::Cargo { cargo, sysroot, rustc } => {
154 let roots = cargo
155 .packages()
156 .map(|pkg| {
157 let is_member = cargo[pkg].is_member;
158 let pkg_root = cargo[pkg].root().to_path_buf();
159
160 let mut include = vec![pkg_root.clone()];
161 include.extend(cargo[pkg].out_dir.clone());
162
163 let mut exclude = vec![pkg_root.join(".git")];
164 if is_member {
165 exclude.push(pkg_root.join("target"));
166 } else {
167 exclude.push(pkg_root.join("tests"));
168 exclude.push(pkg_root.join("examples"));
169 exclude.push(pkg_root.join("benches"));
170 }
171 PackageRoot { is_member, include, exclude }
172 })
173 .chain(sysroot.crates().map(|krate| PackageRoot {
174 is_member: false,
175 include: vec![sysroot[krate].root_dir().to_path_buf()],
176 exclude: Vec::new(),
177 }));
178 if let Some(rustc_packages) = rustc {
179 roots
180 .chain(rustc_packages.packages().map(|krate| PackageRoot {
181 is_member: false,
182 include: vec![rustc_packages[krate].root().to_path_buf()],
183 exclude: Vec::new(),
184 }))
185 .collect()
186 } else {
187 roots.collect()
188 }
189 }
190 }
191 }
192
193 pub fn n_packages(&self) -> usize {
194 match self {
195 ProjectWorkspace::Json { project, .. } => project.n_crates(),
196 ProjectWorkspace::Cargo { cargo, sysroot, rustc } => {
197 let rustc_package_len = rustc.as_ref().map_or(0, |rc| rc.packages().len());
198 cargo.packages().len() + sysroot.crates().len() + rustc_package_len
199 }
200 }
201 }
202
203 pub fn to_crate_graph(
204 &self,
205 target: Option<&str>,
206 proc_macro_client: &ProcMacroClient,
207 load: &mut dyn FnMut(&AbsPath) -> Option<FileId>,
208 ) -> CrateGraph {
209 let mut crate_graph = CrateGraph::default();
210 match self {
211 ProjectWorkspace::Json { project, sysroot } => {
212 let sysroot_dps = sysroot
213 .as_ref()
214 .map(|sysroot| sysroot_to_crate_graph(&mut crate_graph, sysroot, target, load));
215
216 let mut cfg_cache: FxHashMap<Option<&str>, Vec<CfgFlag>> = FxHashMap::default();
217 let crates: FxHashMap<_, _> = project
218 .crates()
219 .filter_map(|(crate_id, krate)| {
220 let file_path = &krate.root_module;
221 let file_id = match load(&file_path) {
222 Some(id) => id,
223 None => {
224 log::error!("failed to load crate root {}", file_path.display());
225 return None;
226 }
227 };
228
229 let env = krate.env.clone().into_iter().collect();
230 let proc_macro = krate
231 .proc_macro_dylib_path
232 .clone()
233 .map(|it| proc_macro_client.by_dylib_path(&it));
234
235 let target = krate.target.as_deref().or(target);
236 let target_cfgs = cfg_cache
237 .entry(target)
238 .or_insert_with(|| get_rustc_cfg_options(target));
239
240 let mut cfg_options = CfgOptions::default();
241 cfg_options.extend(target_cfgs.iter().chain(krate.cfg.iter()).cloned());
242
243 Some((
244 crate_id,
245 crate_graph.add_crate_root(
246 file_id,
247 krate.edition,
248 krate.display_name.clone(),
249 cfg_options,
250 env,
251 proc_macro.unwrap_or_default(),
252 ),
253 ))
254 })
255 .collect();
256
257 for (from, krate) in project.crates() {
258 if let Some(&from) = crates.get(&from) {
259 if let Some((public_deps, _proc_macro)) = &sysroot_dps {
260 for (name, to) in public_deps.iter() {
261 if let Err(_) = crate_graph.add_dep(from, name.clone(), *to) {
262 log::error!("cyclic dependency on {} for {:?}", name, from)
263 }
264 }
265 }
266
267 for dep in &krate.deps {
268 let to_crate_id = dep.crate_id;
269 if let Some(&to) = crates.get(&to_crate_id) {
270 if let Err(_) = crate_graph.add_dep(from, dep.name.clone(), to) {
271 log::error!("cyclic dependency {:?} -> {:?}", from, to);
272 }
273 }
274 }
275 }
276 }
277 }
278 ProjectWorkspace::Cargo { cargo, sysroot, rustc } => {
279 let (public_deps, libproc_macro) =
280 sysroot_to_crate_graph(&mut crate_graph, sysroot, target, load);
281
282 let mut cfg_options = CfgOptions::default();
283 cfg_options.extend(get_rustc_cfg_options(target));
284
285 let mut pkg_to_lib_crate = FxHashMap::default();
286
287 // Add test cfg for non-sysroot crates
288 cfg_options.insert_atom("test".into());
289 cfg_options.insert_atom("debug_assertions".into());
290
291 let mut pkg_crates = FxHashMap::default();
292
293 // Next, create crates for each package, target pair
294 for pkg in cargo.packages() {
295 let mut lib_tgt = None;
296 for &tgt in cargo[pkg].targets.iter() {
297 if let Some(crate_id) = add_target_crate_root(
298 &mut crate_graph,
299 &cargo[pkg],
300 &cargo[tgt],
301 &cfg_options,
302 proc_macro_client,
303 load,
304 ) {
305 if cargo[tgt].kind == TargetKind::Lib {
306 lib_tgt = Some((crate_id, cargo[tgt].name.clone()));
307 pkg_to_lib_crate.insert(pkg, crate_id);
308 }
309 if cargo[tgt].is_proc_macro {
310 if let Some(proc_macro) = libproc_macro {
311 if let Err(_) = crate_graph.add_dep(
312 crate_id,
313 CrateName::new("proc_macro").unwrap(),
314 proc_macro,
315 ) {
316 log::error!(
317 "cyclic dependency on proc_macro for {}",
318 &cargo[pkg].name
319 )
320 }
321 }
322 }
323
324 pkg_crates.entry(pkg).or_insert_with(Vec::new).push(crate_id);
325 }
326 }
327
328 // Set deps to the core, std and to the lib target of the current package
329 for &from in pkg_crates.get(&pkg).into_iter().flatten() {
330 if let Some((to, name)) = lib_tgt.clone() {
331 // For root projects with dashes in their name,
332 // cargo metadata does not do any normalization,
333 // so we do it ourselves currently
334 let name = CrateName::normalize_dashes(&name);
335 if to != from && crate_graph.add_dep(from, name, to).is_err() {
336 log::error!(
337 "cyclic dependency between targets of {}",
338 &cargo[pkg].name
339 )
340 }
341 }
342 for (name, krate) in public_deps.iter() {
343 if let Err(_) = crate_graph.add_dep(from, name.clone(), *krate) {
344 log::error!(
345 "cyclic dependency on {} for {}",
346 name,
347 &cargo[pkg].name
348 )
349 }
350 }
351 }
352 }
353
354 // Now add a dep edge from all targets of upstream to the lib
355 // target of downstream.
356 for pkg in cargo.packages() {
357 for dep in cargo[pkg].dependencies.iter() {
358 let name = CrateName::new(&dep.name).unwrap();
359 if let Some(&to) = pkg_to_lib_crate.get(&dep.pkg) {
360 for &from in pkg_crates.get(&pkg).into_iter().flatten() {
361 if let Err(_) = crate_graph.add_dep(from, name.clone(), to) {
362 log::error!(
363 "cyclic dependency {} -> {}",
364 &cargo[pkg].name,
365 &cargo[dep.pkg].name
366 )
367 }
368 }
369 }
370 }
371 }
372
373 let mut rustc_pkg_crates = FxHashMap::default();
374
375 // If the user provided a path to rustc sources, we add all the rustc_private crates
376 // and create dependencies on them for the crates in the current workspace
377 if let Some(rustc_workspace) = rustc {
378 for pkg in rustc_workspace.packages() {
379 for &tgt in rustc_workspace[pkg].targets.iter() {
380 if rustc_workspace[tgt].kind != TargetKind::Lib {
381 continue;
382 }
383 // Exclude alloc / core / std
384 if rustc_workspace[tgt]
385 .root
386 .components()
387 .any(|c| c == Component::Normal("library".as_ref()))
388 {
389 continue;
390 }
391
392 if let Some(crate_id) = add_target_crate_root(
393 &mut crate_graph,
394 &rustc_workspace[pkg],
395 &rustc_workspace[tgt],
396 &cfg_options,
397 proc_macro_client,
398 load,
399 ) {
400 pkg_to_lib_crate.insert(pkg, crate_id);
401 // Add dependencies on the core / std / alloc for rustc
402 for (name, krate) in public_deps.iter() {
403 if let Err(_) =
404 crate_graph.add_dep(crate_id, name.clone(), *krate)
405 {
406 log::error!(
407 "cyclic dependency on {} for {}",
408 name,
409 &cargo[pkg].name
410 )
411 }
412 }
413 rustc_pkg_crates.entry(pkg).or_insert_with(Vec::new).push(crate_id);
414 }
415 }
416 }
417 // Now add a dep edge from all targets of upstream to the lib
418 // target of downstream.
419 for pkg in rustc_workspace.packages() {
420 for dep in rustc_workspace[pkg].dependencies.iter() {
421 let name = CrateName::new(&dep.name).unwrap();
422 if let Some(&to) = pkg_to_lib_crate.get(&dep.pkg) {
423 for &from in rustc_pkg_crates.get(&pkg).into_iter().flatten() {
424 if let Err(_) = crate_graph.add_dep(from, name.clone(), to) {
425 log::error!(
426 "cyclic dependency {} -> {}",
427 &rustc_workspace[pkg].name,
428 &rustc_workspace[dep.pkg].name
429 )
430 }
431 }
432 }
433 }
434 }
435
436 // Add dependencies for all the crates of the current workspace to rustc_private libraries
437 for dep in rustc_workspace.packages() {
438 let name = CrateName::normalize_dashes(&rustc_workspace[dep].name);
439
440 if let Some(&to) = pkg_to_lib_crate.get(&dep) {
441 for pkg in cargo.packages() {
442 if !cargo[pkg].is_member {
443 continue;
444 }
445 for &from in pkg_crates.get(&pkg).into_iter().flatten() {
446 if let Err(_) = crate_graph.add_dep(from, name.clone(), to) {
447 log::error!(
448 "cyclic dependency {} -> {}",
449 &cargo[pkg].name,
450 &rustc_workspace[dep].name
451 )
452 }
453 }
454 }
455 }
456 }
457 }
458 }
459 }
460 if crate_graph.patch_cfg_if() {
461 log::debug!("Patched std to depend on cfg-if")
462 } else {
463 log::debug!("Did not patch std to depend on cfg-if")
464 }
465 crate_graph
466 }
467}
468
469fn add_target_crate_root(
470 crate_graph: &mut CrateGraph,
471 pkg: &cargo_workspace::PackageData,
472 tgt: &cargo_workspace::TargetData,
473 cfg_options: &CfgOptions,
474 proc_macro_client: &ProcMacroClient,
475 load: &mut dyn FnMut(&AbsPath) -> Option<FileId>,
476) -> Option<CrateId> {
477 let root = tgt.root.as_path();
478 if let Some(file_id) = load(root) {
479 let edition = pkg.edition;
480 let cfg_options = {
481 let mut opts = cfg_options.clone();
482 for feature in pkg.features.iter() {
483 opts.insert_key_value("feature".into(), feature.into());
484 }
485 opts.extend(pkg.cfgs.iter().cloned());
486 opts
487 };
488 let mut env = Env::default();
489 if let Some(out_dir) = &pkg.out_dir {
490 // NOTE: cargo and rustc seem to hide non-UTF-8 strings from env! and option_env!()
491 if let Some(out_dir) = out_dir.to_str().map(|s| s.to_owned()) {
492 env.set("OUT_DIR", out_dir);
493 }
494 }
495 let proc_macro = pkg
496 .proc_macro_dylib_path
497 .as_ref()
498 .map(|it| proc_macro_client.by_dylib_path(&it))
499 .unwrap_or_default();
500
501 let display_name = CrateDisplayName::from_canonical_name(pkg.name.clone());
502 let crate_id = crate_graph.add_crate_root(
503 file_id,
504 edition,
505 Some(display_name),
506 cfg_options,
507 env,
508 proc_macro.clone(),
509 );
510
511 return Some(crate_id);
512 }
513 None
514}
515fn sysroot_to_crate_graph(
516 crate_graph: &mut CrateGraph,
517 sysroot: &Sysroot,
518 target: Option<&str>,
519 load: &mut dyn FnMut(&AbsPath) -> Option<FileId>,
520) -> (Vec<(CrateName, CrateId)>, Option<CrateId>) {
521 let mut cfg_options = CfgOptions::default();
522 cfg_options.extend(get_rustc_cfg_options(target));
523 let sysroot_crates: FxHashMap<_, _> = sysroot
524 .crates()
525 .filter_map(|krate| {
526 let file_id = load(&sysroot[krate].root)?;
527
528 let env = Env::default();
529 let proc_macro = vec![];
530 let name = CrateName::new(&sysroot[krate].name)
531 .expect("Sysroot crates' names do not contain dashes");
532 let crate_id = crate_graph.add_crate_root(
533 file_id,
534 Edition::Edition2018,
535 Some(name.into()),
536 cfg_options.clone(),
537 env,
538 proc_macro,
539 );
540 Some((krate, crate_id))
541 })
542 .collect();
543
544 for from in sysroot.crates() {
545 for &to in sysroot[from].deps.iter() {
546 let name = CrateName::new(&sysroot[to].name).unwrap();
547 if let (Some(&from), Some(&to)) = (sysroot_crates.get(&from), sysroot_crates.get(&to)) {
548 if let Err(_) = crate_graph.add_dep(from, name, to) {
549 log::error!("cyclic dependency between sysroot crates")
550 }
551 }
552 }
553 }
554
555 let public_deps = sysroot
556 .public_deps()
557 .map(|(name, idx)| (CrateName::new(name).unwrap(), sysroot_crates[&idx]))
558 .collect::<Vec<_>>();
559
560 let libproc_macro = sysroot.proc_macro().and_then(|it| sysroot_crates.get(&it).copied());
561 (public_deps, libproc_macro)
562}
563
564fn get_rustc_cfg_options(target: Option<&str>) -> Vec<CfgFlag> {
565 let mut res = Vec::new();
566
567 // Some nightly-only cfgs, which are required for stdlib
568 res.push(CfgFlag::Atom("target_thread_local".into()));
569 for &ty in ["8", "16", "32", "64", "cas", "ptr"].iter() {
570 for &key in ["target_has_atomic", "target_has_atomic_load_store"].iter() {
571 res.push(CfgFlag::KeyValue { key: key.to_string(), value: ty.into() });
572 }
573 }
574
575 let rustc_cfgs = {
576 let mut cmd = Command::new(toolchain::rustc());
577 cmd.args(&["--print", "cfg", "-O"]);
578 if let Some(target) = target {
579 cmd.args(&["--target", target]);
580 }
581 utf8_stdout(cmd)
582 };
583
584 match rustc_cfgs {
585 Ok(rustc_cfgs) => res.extend(rustc_cfgs.lines().map(|it| it.parse().unwrap())),
586 Err(e) => log::error!("failed to get rustc cfgs: {:#}", e),
587 }
588
589 res
590}
diff --git a/crates/rust-analyzer/src/cli/load_cargo.rs b/crates/rust-analyzer/src/cli/load_cargo.rs
index ab1e2ab92..76526c66c 100644
--- a/crates/rust-analyzer/src/cli/load_cargo.rs
+++ b/crates/rust-analyzer/src/cli/load_cargo.rs
@@ -21,7 +21,6 @@ pub fn load_cargo(
21 let ws = ProjectWorkspace::load( 21 let ws = ProjectWorkspace::load(
22 root, 22 root,
23 &CargoConfig { load_out_dirs_from_check, ..Default::default() }, 23 &CargoConfig { load_out_dirs_from_check, ..Default::default() },
24 true,
25 )?; 24 )?;
26 25
27 let (sender, receiver) = unbounded(); 26 let (sender, receiver) = unbounded();
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs
index b4c738272..d16796590 100644
--- a/crates/rust-analyzer/src/config.rs
+++ b/crates/rust-analyzer/src/config.rs
@@ -7,7 +7,7 @@
7//! configure the server itself, feature flags are passed into analysis, and 7//! configure the server itself, feature flags are passed into analysis, and
8//! tweak things like automatic insertion of `()` in completions. 8//! tweak things like automatic insertion of `()` in completions.
9 9
10use std::{ffi::OsString, path::PathBuf}; 10use std::{convert::TryFrom, ffi::OsString, path::PathBuf};
11 11
12use flycheck::FlycheckConfig; 12use flycheck::FlycheckConfig;
13use hir::PrefixKind; 13use hir::PrefixKind;
@@ -49,7 +49,6 @@ pub struct Config {
49 pub hover: HoverConfig, 49 pub hover: HoverConfig,
50 pub semantic_tokens_refresh: bool, 50 pub semantic_tokens_refresh: bool,
51 51
52 pub with_sysroot: bool,
53 pub linked_projects: Vec<LinkedProject>, 52 pub linked_projects: Vec<LinkedProject>,
54 pub root_path: AbsPathBuf, 53 pub root_path: AbsPathBuf,
55} 54}
@@ -155,7 +154,6 @@ impl Config {
155 Config { 154 Config {
156 client_caps: ClientCapsConfig::default(), 155 client_caps: ClientCapsConfig::default(),
157 156
158 with_sysroot: true,
159 publish_diagnostics: true, 157 publish_diagnostics: true,
160 diagnostics: DiagnosticsConfig::default(), 158 diagnostics: DiagnosticsConfig::default(),
161 diagnostics_map: DiagnosticsMapConfig::default(), 159 diagnostics_map: DiagnosticsMapConfig::default(),
@@ -209,7 +207,6 @@ impl Config {
209 207
210 let data = ConfigData::from_json(json); 208 let data = ConfigData::from_json(json);
211 209
212 self.with_sysroot = data.withSysroot;
213 self.publish_diagnostics = data.diagnostics_enable; 210 self.publish_diagnostics = data.diagnostics_enable;
214 self.diagnostics = DiagnosticsConfig { 211 self.diagnostics = DiagnosticsConfig {
215 disable_experimental: !data.diagnostics_enableExperimental, 212 disable_experimental: !data.diagnostics_enableExperimental,
@@ -227,12 +224,26 @@ impl Config {
227 self.notifications = 224 self.notifications =
228 NotificationsConfig { cargo_toml_not_found: data.notifications_cargoTomlNotFound }; 225 NotificationsConfig { cargo_toml_not_found: data.notifications_cargoTomlNotFound };
229 self.cargo_autoreload = data.cargo_autoreload; 226 self.cargo_autoreload = data.cargo_autoreload;
227
228 let rustc_source = if let Some(rustc_source) = data.rustcSource {
229 let rustpath: PathBuf = rustc_source.into();
230 AbsPathBuf::try_from(rustpath)
231 .map_err(|_| {
232 log::error!("rustc source directory must be an absolute path");
233 })
234 .ok()
235 } else {
236 None
237 };
238
230 self.cargo = CargoConfig { 239 self.cargo = CargoConfig {
231 no_default_features: data.cargo_noDefaultFeatures, 240 no_default_features: data.cargo_noDefaultFeatures,
232 all_features: data.cargo_allFeatures, 241 all_features: data.cargo_allFeatures,
233 features: data.cargo_features.clone(), 242 features: data.cargo_features.clone(),
234 load_out_dirs_from_check: data.cargo_loadOutDirsFromCheck, 243 load_out_dirs_from_check: data.cargo_loadOutDirsFromCheck,
235 target: data.cargo_target.clone(), 244 target: data.cargo_target.clone(),
245 rustc_source: rustc_source,
246 no_sysroot: data.cargo_noSysroot,
236 }; 247 };
237 self.runnables = RunnablesConfig { 248 self.runnables = RunnablesConfig {
238 override_cargo: data.runnables_overrideCargo, 249 override_cargo: data.runnables_overrideCargo,
@@ -385,13 +396,10 @@ impl Config {
385 } 396 }
386 397
387 if let Some(code_action) = &doc_caps.code_action { 398 if let Some(code_action) = &doc_caps.code_action {
388 match (code_action.data_support, &code_action.resolve_support) { 399 if let Some(resolve_support) = &code_action.resolve_support {
389 (Some(true), Some(resolve_support)) => { 400 if resolve_support.properties.iter().any(|it| it == "edit") {
390 if resolve_support.properties.iter().any(|it| it == "edit") { 401 self.client_caps.code_action_resolve = true;
391 self.client_caps.code_action_resolve = true;
392 }
393 } 402 }
394 _ => (),
395 } 403 }
396 } 404 }
397 } 405 }
@@ -482,6 +490,7 @@ config_data! {
482 cargo_loadOutDirsFromCheck: bool = false, 490 cargo_loadOutDirsFromCheck: bool = false,
483 cargo_noDefaultFeatures: bool = false, 491 cargo_noDefaultFeatures: bool = false,
484 cargo_target: Option<String> = None, 492 cargo_target: Option<String> = None,
493 cargo_noSysroot: bool = false,
485 494
486 checkOnSave_enable: bool = true, 495 checkOnSave_enable: bool = true,
487 checkOnSave_allFeatures: Option<bool> = None, 496 checkOnSave_allFeatures: Option<bool> = None,
@@ -534,6 +543,6 @@ config_data! {
534 rustfmt_extraArgs: Vec<String> = Vec::new(), 543 rustfmt_extraArgs: Vec<String> = Vec::new(),
535 rustfmt_overrideCommand: Option<Vec<String>> = None, 544 rustfmt_overrideCommand: Option<Vec<String>> = None,
536 545
537 withSysroot: bool = true, 546 rustcSource : Option<String> = None,
538 } 547 }
539} 548}
diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs
index 95659b0db..782797e85 100644
--- a/crates/rust-analyzer/src/handlers.rs
+++ b/crates/rust-analyzer/src/handlers.rs
@@ -1322,6 +1322,28 @@ pub(crate) fn handle_open_docs(
1322 Ok(remote.and_then(|remote| Url::parse(&remote).ok())) 1322 Ok(remote.and_then(|remote| Url::parse(&remote).ok()))
1323} 1323}
1324 1324
1325pub(crate) fn handle_open_cargo_toml(
1326 snap: GlobalStateSnapshot,
1327 params: lsp_ext::OpenCargoTomlParams,
1328) -> Result<Option<lsp_types::GotoDefinitionResponse>> {
1329 let _p = profile::span("handle_open_cargo_toml");
1330 let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
1331 let maybe_cargo_spec = CargoTargetSpec::for_file(&snap, file_id)?;
1332 if maybe_cargo_spec.is_none() {
1333 return Ok(None);
1334 }
1335
1336 let cargo_spec = maybe_cargo_spec.unwrap();
1337 let cargo_toml_path = cargo_spec.workspace_root.join("Cargo.toml");
1338 if !cargo_toml_path.exists() {
1339 return Ok(None);
1340 }
1341 let cargo_toml_url = to_proto::url_from_abs_path(&cargo_toml_path);
1342 let cargo_toml_location = Location::new(cargo_toml_url, Range::default());
1343 let res = lsp_types::GotoDefinitionResponse::from(cargo_toml_location);
1344 Ok(Some(res))
1345}
1346
1325fn implementation_title(count: usize) -> String { 1347fn implementation_title(count: usize) -> String {
1326 if count == 1 { 1348 if count == 1 {
1327 "1 implementation".into() 1349 "1 implementation".into()
diff --git a/crates/rust-analyzer/src/lsp_ext.rs b/crates/rust-analyzer/src/lsp_ext.rs
index a7c3028e4..a5c65fa3e 100644
--- a/crates/rust-analyzer/src/lsp_ext.rs
+++ b/crates/rust-analyzer/src/lsp_ext.rs
@@ -354,3 +354,17 @@ impl Request for ExternalDocs {
354 type Result = Option<lsp_types::Url>; 354 type Result = Option<lsp_types::Url>;
355 const METHOD: &'static str = "experimental/externalDocs"; 355 const METHOD: &'static str = "experimental/externalDocs";
356} 356}
357
358pub enum OpenCargoToml {}
359
360impl Request for OpenCargoToml {
361 type Params = OpenCargoTomlParams;
362 type Result = Option<lsp_types::GotoDefinitionResponse>;
363 const METHOD: &'static str = "experimental/openCargoToml";
364}
365
366#[derive(Serialize, Deserialize, Debug)]
367#[serde(rename_all = "camelCase")]
368pub struct OpenCargoTomlParams {
369 pub text_document: TextDocumentIdentifier,
370}
diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs
index 6e6cac42e..68a53bbcb 100644
--- a/crates/rust-analyzer/src/main_loop.rs
+++ b/crates/rust-analyzer/src/main_loop.rs
@@ -438,6 +438,7 @@ impl GlobalState {
438 .on::<lsp_ext::CodeActionResolveRequest>(handlers::handle_code_action_resolve) 438 .on::<lsp_ext::CodeActionResolveRequest>(handlers::handle_code_action_resolve)
439 .on::<lsp_ext::HoverRequest>(handlers::handle_hover) 439 .on::<lsp_ext::HoverRequest>(handlers::handle_hover)
440 .on::<lsp_ext::ExternalDocs>(handlers::handle_open_docs) 440 .on::<lsp_ext::ExternalDocs>(handlers::handle_open_docs)
441 .on::<lsp_ext::OpenCargoToml>(handlers::handle_open_cargo_toml)
441 .on::<lsp_types::request::OnTypeFormatting>(handlers::handle_on_type_formatting) 442 .on::<lsp_types::request::OnTypeFormatting>(handlers::handle_on_type_formatting)
442 .on::<lsp_types::request::DocumentSymbolRequest>(handlers::handle_document_symbol) 443 .on::<lsp_types::request::DocumentSymbolRequest>(handlers::handle_document_symbol)
443 .on::<lsp_types::request::WorkspaceSymbol>(handlers::handle_workspace_symbol) 444 .on::<lsp_types::request::WorkspaceSymbol>(handlers::handle_workspace_symbol)
diff --git a/crates/rust-analyzer/src/reload.rs b/crates/rust-analyzer/src/reload.rs
index 0eabd51bd..fa6e09f42 100644
--- a/crates/rust-analyzer/src/reload.rs
+++ b/crates/rust-analyzer/src/reload.rs
@@ -96,17 +96,12 @@ impl GlobalState {
96 self.task_pool.handle.spawn({ 96 self.task_pool.handle.spawn({
97 let linked_projects = self.config.linked_projects.clone(); 97 let linked_projects = self.config.linked_projects.clone();
98 let cargo_config = self.config.cargo.clone(); 98 let cargo_config = self.config.cargo.clone();
99 let with_sysroot = self.config.with_sysroot.clone();
100 move || { 99 move || {
101 let workspaces = linked_projects 100 let workspaces = linked_projects
102 .iter() 101 .iter()
103 .map(|project| match project { 102 .map(|project| match project {
104 LinkedProject::ProjectManifest(manifest) => { 103 LinkedProject::ProjectManifest(manifest) => {
105 project_model::ProjectWorkspace::load( 104 project_model::ProjectWorkspace::load(manifest.clone(), &cargo_config)
106 manifest.clone(),
107 &cargo_config,
108 with_sysroot,
109 )
110 } 105 }
111 LinkedProject::InlineJsonProject(it) => { 106 LinkedProject::InlineJsonProject(it) => {
112 project_model::ProjectWorkspace::load_inline(it.clone()) 107 project_model::ProjectWorkspace::load_inline(it.clone())
@@ -246,7 +241,9 @@ impl GlobalState {
246 .iter() 241 .iter()
247 .enumerate() 242 .enumerate()
248 .filter_map(|(id, w)| match w { 243 .filter_map(|(id, w)| match w {
249 ProjectWorkspace::Cargo { cargo, sysroot: _ } => Some((id, cargo.workspace_root())), 244 ProjectWorkspace::Cargo { cargo, sysroot: _, rustc: _ } => {
245 Some((id, cargo.workspace_root()))
246 }
250 ProjectWorkspace::Json { project, .. } => { 247 ProjectWorkspace::Json { project, .. } => {
251 // Enable flychecks for json projects if a custom flycheck command was supplied 248 // Enable flychecks for json projects if a custom flycheck command was supplied
252 // in the workspace configuration. 249 // in the workspace configuration.
diff --git a/crates/rust-analyzer/tests/rust-analyzer/support.rs b/crates/rust-analyzer/tests/rust-analyzer/support.rs
index fe9362bc0..b210b98f0 100644
--- a/crates/rust-analyzer/tests/rust-analyzer/support.rs
+++ b/crates/rust-analyzer/tests/rust-analyzer/support.rs
@@ -12,7 +12,7 @@ use lsp_types::{
12 notification::Exit, request::Shutdown, TextDocumentIdentifier, Url, WorkDoneProgress, 12 notification::Exit, request::Shutdown, TextDocumentIdentifier, Url, WorkDoneProgress,
13}; 13};
14use lsp_types::{ProgressParams, ProgressParamsValue}; 14use lsp_types::{ProgressParams, ProgressParamsValue};
15use project_model::ProjectManifest; 15use project_model::{CargoConfig, ProjectManifest};
16use rust_analyzer::{ 16use rust_analyzer::{
17 config::{ClientCapsConfig, Config, FilesConfig, FilesWatcher, LinkedProject}, 17 config::{ClientCapsConfig, Config, FilesConfig, FilesWatcher, LinkedProject},
18 main_loop, 18 main_loop,
@@ -47,8 +47,8 @@ impl<'a> Project<'a> {
47 self 47 self
48 } 48 }
49 49
50 pub(crate) fn with_sysroot(mut self, sysroot: bool) -> Project<'a> { 50 pub(crate) fn with_sysroot(mut self, yes: bool) -> Project<'a> {
51 self.with_sysroot = sysroot; 51 self.with_sysroot = yes;
52 self 52 self
53 } 53 }
54 54
@@ -90,7 +90,7 @@ impl<'a> Project<'a> {
90 work_done_progress: true, 90 work_done_progress: true,
91 ..Default::default() 91 ..Default::default()
92 }, 92 },
93 with_sysroot: self.with_sysroot, 93 cargo: CargoConfig { no_sysroot: !self.with_sysroot, ..Default::default() },
94 linked_projects, 94 linked_projects,
95 files: FilesConfig { watcher: FilesWatcher::Client, exclude: Vec::new() }, 95 files: FilesConfig { watcher: FilesWatcher::Client, exclude: Vec::new() },
96 ..Config::new(tmp_dir_path) 96 ..Config::new(tmp_dir_path)