aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--Cargo.lock1
-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
-rw-r--r--docs/dev/lsp-extensions.md29
-rw-r--r--editors/code/package.json27
-rw-r--r--editors/code/src/commands.ts21
-rw-r--r--editors/code/src/lsp_ext.ts6
-rw-r--r--editors/code/src/main.ts1
23 files changed, 1121 insertions, 555 deletions
diff --git a/.gitignore b/.gitignore
index b205bf3fb..7e097c015 100644
--- a/.gitignore
+++ b/.gitignore
@@ -10,3 +10,4 @@ crates/*/target
10generated_assists.adoc 10generated_assists.adoc
11generated_features.adoc 11generated_features.adoc
12generated_diagnostic.adoc 12generated_diagnostic.adoc
13.DS_Store
diff --git a/Cargo.lock b/Cargo.lock
index 494011068..715a80978 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -253,6 +253,7 @@ dependencies = [
253name = "completion" 253name = "completion"
254version = "0.0.0" 254version = "0.0.0"
255dependencies = [ 255dependencies = [
256 "assists",
256 "base_db", 257 "base_db",
257 "expect-test", 258 "expect-test",
258 "hir", 259 "hir",
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)
diff --git a/docs/dev/lsp-extensions.md b/docs/dev/lsp-extensions.md
index 77d4e0ec9..db9727bee 100644
--- a/docs/dev/lsp-extensions.md
+++ b/docs/dev/lsp-extensions.md
@@ -1,8 +1,8 @@
1<!--- 1<!---
2lsp_ext.rs hash: 4f86fb54e4b2870e 2lsp_ext.rs hash: 9d5daed5b25dc4f6
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:
6 6
7 https://github.com/rust-analyzer/rust-analyzer/issues/4604 7 https://github.com/rust-analyzer/rust-analyzer/issues/4604
8 8
@@ -537,3 +537,28 @@ Such actions on the client side are appended to a hover bottom as command links:
537 +-----------------------------+ 537 +-----------------------------+
538 ... 538 ...
539``` 539```
540
541## Open Cargo.toml
542
543**Issue:** https://github.com/rust-analyzer/rust-analyzer/issues/6462
544
545This request is sent from client to server to open the current project's Cargo.toml
546
547**Method:** `experimental/openCargoToml`
548
549**Request:** `OpenCargoTomlParams`
550
551**Response:** `Location | null`
552
553
554### Example
555
556```rust
557// Cargo.toml
558[package]
559// src/main.rs
560
561/* cursor here*/
562```
563
564`experimental/openCargoToml` returns a single `Link` to the start of the `[package]` keyword.
diff --git a/editors/code/package.json b/editors/code/package.json
index eccafccdd..220d44abc 100644
--- a/editors/code/package.json
+++ b/editors/code/package.json
@@ -187,6 +187,11 @@
187 "command": "rust-analyzer.openDocs", 187 "command": "rust-analyzer.openDocs",
188 "title": "Open docs under cursor", 188 "title": "Open docs under cursor",
189 "category": "Rust Analyzer" 189 "category": "Rust Analyzer"
190 },
191 {
192 "command": "rust-analyzer.openCargoToml",
193 "title": "Open Cargo.toml",
194 "category": "Rust Analyzer"
190 } 195 }
191 ], 196 ],
192 "keybindings": [ 197 "keybindings": [
@@ -278,6 +283,11 @@
278 "default": null, 283 "default": null,
279 "description": "Specify the compilation target" 284 "description": "Specify the compilation target"
280 }, 285 },
286 "rust-analyzer.noSysroot": {
287 "markdownDescription": "Internal config for debugging, disables loading of sysroot crates",
288 "type": "boolean",
289 "default": false
290 },
281 "rust-analyzer.rustfmt.extraArgs": { 291 "rust-analyzer.rustfmt.extraArgs": {
282 "type": "array", 292 "type": "array",
283 "items": { 293 "items": {
@@ -600,11 +610,6 @@
600 }, 610 },
601 "default": null 611 "default": null
602 }, 612 },
603 "rust-analyzer.withSysroot": {
604 "markdownDescription": "Internal config for debugging, disables loading of sysroot crates",
605 "type": "boolean",
606 "default": true
607 },
608 "rust-analyzer.diagnostics.enable": { 613 "rust-analyzer.diagnostics.enable": {
609 "type": "boolean", 614 "type": "boolean",
610 "default": true, 615 "default": true,
@@ -687,6 +692,14 @@
687 }, 692 },
688 "default": [], 693 "default": [],
689 "description": "Additional arguments to be passed to cargo for runnables such as tests or binaries.\nFor example, it may be '--release'" 694 "description": "Additional arguments to be passed to cargo for runnables such as tests or binaries.\nFor example, it may be '--release'"
695 },
696 "rust-analyzer.rustcSource": {
697 "type": [
698 "null",
699 "string"
700 ],
701 "default": null,
702 "description": "Path to the rust compiler sources, for usage in rustc_private projects."
690 } 703 }
691 } 704 }
692 }, 705 },
@@ -1057,6 +1070,10 @@
1057 { 1070 {
1058 "command": "rust-analyzer.openDocs", 1071 "command": "rust-analyzer.openDocs",
1059 "when": "inRustProject" 1072 "when": "inRustProject"
1073 },
1074 {
1075 "command": "rust-analyzer.openCargoToml",
1076 "when": "inRustProject"
1060 } 1077 }
1061 ] 1078 ]
1062 } 1079 }
diff --git a/editors/code/src/commands.ts b/editors/code/src/commands.ts
index cf34622c3..92bc4d7f7 100644
--- a/editors/code/src/commands.ts
+++ b/editors/code/src/commands.ts
@@ -188,6 +188,27 @@ export function parentModule(ctx: Ctx): Cmd {
188 }; 188 };
189} 189}
190 190
191export function openCargoToml(ctx: Ctx): Cmd {
192 return async () => {
193 const editor = ctx.activeRustEditor;
194 const client = ctx.client;
195 if (!editor || !client) return;
196
197 const response = await client.sendRequest(ra.openCargoToml, {
198 textDocument: ctx.client.code2ProtocolConverter.asTextDocumentIdentifier(editor.document),
199 });
200 if (!response) return;
201
202 const uri = client.protocol2CodeConverter.asUri(response.uri);
203 const range = client.protocol2CodeConverter.asRange(response.range);
204
205 const doc = await vscode.workspace.openTextDocument(uri);
206 const e = await vscode.window.showTextDocument(doc);
207 e.selection = new vscode.Selection(range.start, range.start);
208 e.revealRange(range, vscode.TextEditorRevealType.InCenter);
209 };
210}
211
191export function ssr(ctx: Ctx): Cmd { 212export function ssr(ctx: Ctx): Cmd {
192 return async () => { 213 return async () => {
193 const editor = vscode.window.activeTextEditor; 214 const editor = vscode.window.activeTextEditor;
diff --git a/editors/code/src/lsp_ext.ts b/editors/code/src/lsp_ext.ts
index d320c3cd7..5e877ce65 100644
--- a/editors/code/src/lsp_ext.ts
+++ b/editors/code/src/lsp_ext.ts
@@ -114,3 +114,9 @@ export interface CommandLinkGroup {
114} 114}
115 115
116export const openDocs = new lc.RequestType<lc.TextDocumentPositionParams, string | void, void>('experimental/externalDocs'); 116export const openDocs = new lc.RequestType<lc.TextDocumentPositionParams, string | void, void>('experimental/externalDocs');
117
118export const openCargoToml = new lc.RequestType<OpenCargoTomlParams, lc.Location, void>("experimental/openCargoToml");
119
120export interface OpenCargoTomlParams {
121 textDocument: lc.TextDocumentIdentifier;
122}
diff --git a/editors/code/src/main.ts b/editors/code/src/main.ts
index 09543e348..2f3dde8ac 100644
--- a/editors/code/src/main.ts
+++ b/editors/code/src/main.ts
@@ -111,6 +111,7 @@ async function tryActivate(context: vscode.ExtensionContext) {
111 ctx.registerCommand('debug', commands.debug); 111 ctx.registerCommand('debug', commands.debug);
112 ctx.registerCommand('newDebugConfig', commands.newDebugConfig); 112 ctx.registerCommand('newDebugConfig', commands.newDebugConfig);
113 ctx.registerCommand('openDocs', commands.openDocs); 113 ctx.registerCommand('openDocs', commands.openDocs);
114 ctx.registerCommand('openCargoToml', commands.openCargoToml);
114 115
115 defaultOnEnter.dispose(); 116 defaultOnEnter.dispose();
116 ctx.registerCommand('onEnter', commands.onEnter); 117 ctx.registerCommand('onEnter', commands.onEnter);