diff options
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 | |||
10 | generated_assists.adoc | 10 | generated_assists.adoc |
11 | generated_features.adoc | 11 | generated_features.adoc |
12 | generated_diagnostic.adoc | 12 | generated_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 = [ | |||
253 | name = "completion" | 253 | name = "completion" |
254 | version = "0.0.0" | 254 | version = "0.0.0" |
255 | dependencies = [ | 255 | dependencies = [ |
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}; | |||
5 | use ide_db::{defs::Definition, search::Reference, RootDatabase}; | 5 | use ide_db::{defs::Definition, search::Reference, RootDatabase}; |
6 | use rustc_hash::{FxHashMap, FxHashSet}; | 6 | use rustc_hash::{FxHashMap, FxHashSet}; |
7 | use syntax::{ | 7 | use 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 | ||
14 | use crate::{ | 13 | use crate::{ |
@@ -130,17 +129,21 @@ fn existing_definition(db: &RootDatabase, variant_name: &ast::Name, variant: &En | |||
130 | fn insert_import( | 129 | fn 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 | ||
322 | mod my_mod { | 329 | mod 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#" | ||
360 | enum E { | ||
361 | <|>V { i: i32, j: i32 } | ||
362 | } | ||
363 | |||
364 | fn f() { | ||
365 | let e = E::V { i: 9, j: 2 }; | ||
366 | } | ||
367 | "#, | ||
368 | r#" | ||
369 | struct V{ pub i: i32, pub j: i32 } | ||
370 | |||
371 | enum E { | ||
372 | V(V) | ||
373 | } | ||
374 | |||
375 | fn 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 { | |||
372 | mod foo; | 406 | mod foo; |
373 | 407 | ||
374 | //- /foo.rs | 408 | //- /foo.rs |
375 | use V; | 409 | use crate::{E, V}; |
376 | |||
377 | use crate::E; | ||
378 | fn f() { | 410 | fn 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 | ||
404 | struct V{ pub i: i32, pub j: i32 } | 436 | struct V{ pub i: i32, pub j: i32 } |
405 | 437 | ||
406 | enum E { | 438 | enum E { |
@@ -408,10 +440,42 @@ enum E { | |||
408 | } | 440 | } |
409 | mod foo; | 441 | mod foo; |
410 | 442 | ||
443 | //- /foo.rs | ||
444 | use crate::{E, V}; | ||
445 | fn 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#" | ||
457 | enum A { <|>One { a: u32, b: u32 } } | ||
458 | |||
459 | struct B(A); | ||
460 | |||
461 | fn foo() { | ||
462 | let _ = B(A::One { a: 1, b: 2 }); | ||
463 | } | ||
464 | "#, | ||
465 | r#" | ||
466 | struct One{ pub a: u32, pub b: u32 } | ||
467 | |||
468 | enum A { One(One) } | ||
469 | |||
470 | struct B(A); | ||
471 | |||
472 | fn 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#" | ||
126 | mod bar { pub fn foo(x: i32, <|>y: i32) { x; } } | ||
127 | fn b() { bar::foo(9, 2) } | ||
128 | "#, | ||
129 | r#" | ||
130 | mod bar { pub fn foo(x: i32) { x; } } | ||
131 | fn 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#" | ||
141 | pub fn foo<T>(x: T, <|>y: i32) { x; } | ||
142 | fn b() { foo::<i32>(9, 2) } | ||
143 | "#, | ||
144 | r#" | ||
145 | pub fn foo<T>(x: T) { x; } | ||
146 | fn 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#" | ||
156 | pub fn foo<T>(x: i32, <|>y: T) { x; } | ||
157 | fn b() { foo::<i32>(9, 2) } | ||
158 | fn b2() { foo(9, 2) } | ||
159 | "#, | ||
160 | r#" | ||
161 | pub fn foo<T>(x: i32) { x; } | ||
162 | fn b() { foo::<i32>(9) } | ||
163 | fn 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 | ||
186 | fn foo(x: i32, <|>y: i32) { x; } | ||
187 | |||
188 | mod foo; | ||
189 | |||
190 | //- /foo.rs | ||
191 | use super::foo; | ||
192 | |||
193 | fn bar() { | ||
194 | let _ = foo(1, 2); | ||
195 | } | ||
196 | "#, | ||
197 | r#" | ||
198 | //- /main.rs | ||
199 | fn foo(x: i32) { x; } | ||
200 | |||
201 | mod foo; | ||
202 | |||
203 | //- /foo.rs | ||
204 | use super::foo; | ||
205 | |||
206 | fn 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 | ||
260 | pub mod default { | ||
261 | pub trait Default { | ||
262 | fn default() -> Self; | ||
263 | } | ||
264 | } | ||
265 | |||
260 | pub mod iter { | 266 | pub 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 | ||
329 | pub mod prelude { | 335 | pub 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] |
333 | pub use prelude::*; | 339 | pub 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 | }; |
14 | use test_utils::mark; | 14 | use 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 | ||
81 | fn is_inner_attribute(node: SyntaxNode) -> bool { | ||
82 | ast::Attr::cast(node).map(|attr| attr.kind()) == Some(ast::AttrKind::Inner) | ||
83 | } | ||
84 | |||
85 | fn 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. |
88 | pub(crate) fn insert_use<'a>( | 91 | pub(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] | ||
833 | fn main() {}", | 837 | fn main() {}", |
834 | r"#![allow(unused_imports)] | 838 | r"#![allow(unused_imports)] |
835 | 839 | ||
836 | use foo::bar; | 840 | #![no_std] |
837 | 841 | ||
842 | use foo::bar; | ||
838 | fn main() {}", | 843 | fn 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 | |||
854 | use 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. */ | ||
865 | fn 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 | |||
870 | use foo::bar::Baz; | ||
871 | fn 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 | ||
886 | fn 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 | |||
895 | use foo::bar::Baz; | ||
896 | fn 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" | |||
14 | log = "0.4.8" | 14 | log = "0.4.8" |
15 | rustc-hash = "1.1.0" | 15 | rustc-hash = "1.1.0" |
16 | 16 | ||
17 | assists = { path = "../assists", version = "0.0.0" } | ||
17 | stdx = { path = "../stdx", version = "0.0.0" } | 18 | stdx = { path = "../stdx", version = "0.0.0" } |
18 | syntax = { path = "../syntax", version = "0.0.0" } | 19 | syntax = { path = "../syntax", version = "0.0.0" } |
19 | text_edit = { path = "../text_edit", version = "0.0.0" } | 20 | text_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. |
2 | use crate::{CompletionContext, Completions}; | 2 | use assists::utils::FamousDefs; |
3 | use syntax::ast::Expr; | ||
4 | |||
5 | use crate::{ | ||
6 | item::CompletionKind, CompletionContext, CompletionItem, CompletionItemKind, Completions, | ||
7 | }; | ||
3 | 8 | ||
4 | pub(crate) fn complete_record(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> { | 9 | pub(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)] |
20 | mod tests { | 47 | mod 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#" | ||
69 | struct S { foo: u32, bar: usize } | ||
70 | |||
71 | impl core::default::Default for S { | ||
72 | fn default() -> Self { | ||
73 | S { | ||
74 | foo: 0, | ||
75 | bar: 0, | ||
76 | } | ||
77 | } | ||
78 | } | ||
79 | |||
80 | fn 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#" | ||
107 | struct S { foo: u32, bar: usize } | ||
108 | |||
109 | fn 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 | ||
69 | pub type Package = Idx<PackageData>; | 76 | pub type Package = Idx<PackageData>; |
@@ -137,27 +144,27 @@ impl PackageData { | |||
137 | impl CargoWorkspace { | 144 | impl 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; | |||
4 | mod project_json; | 4 | mod project_json; |
5 | mod sysroot; | 5 | mod sysroot; |
6 | mod cfg_flag; | 6 | mod cfg_flag; |
7 | mod workspace; | ||
7 | 8 | ||
8 | use std::{ | 9 | use 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 | ||
15 | use anyhow::{bail, Context, Result}; | 15 | use anyhow::{bail, Context, Result}; |
16 | use base_db::{CrateDisplayName, CrateGraph, CrateId, CrateName, Edition, Env, FileId}; | ||
17 | use cfg::CfgOptions; | ||
18 | use paths::{AbsPath, AbsPathBuf}; | 16 | use paths::{AbsPath, AbsPathBuf}; |
19 | use rustc_hash::{FxHashMap, FxHashSet}; | 17 | use rustc_hash::FxHashSet; |
20 | |||
21 | use crate::cfg_flag::CfgFlag; | ||
22 | 18 | ||
23 | pub use crate::{ | 19 | pub 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 | ||
29 | pub use proc_macro_api::ProcMacroClient; | 26 | pub use proc_macro_api::ProcMacroClient; |
30 | 27 | ||
31 | #[derive(Clone, Eq, PartialEq)] | ||
32 | pub 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 | |||
39 | impl 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)] | ||
63 | pub 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)] |
71 | pub enum ProjectManifest { | 29 | pub enum ProjectManifest { |
72 | ProjectJson(AbsPathBuf), | 30 | ProjectJson(AbsPathBuf), |
@@ -153,376 +111,6 @@ impl ProjectManifest { | |||
153 | } | 111 | } |
154 | } | 112 | } |
155 | 113 | ||
156 | impl 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 | |||
498 | fn 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 | |||
526 | fn utf8_stdout(mut cmd: Command) -> Result<String> { | 114 | fn 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 | |||
540 | fn 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 | |||
5 | use std::{fmt, fs, path::Component, process::Command}; | ||
6 | |||
7 | use anyhow::{Context, Result}; | ||
8 | use base_db::{CrateDisplayName, CrateGraph, CrateId, CrateName, Edition, Env, FileId}; | ||
9 | use cfg::CfgOptions; | ||
10 | use paths::{AbsPath, AbsPathBuf}; | ||
11 | use proc_macro_api::ProcMacroClient; | ||
12 | use rustc_hash::{FxHashMap, FxHashSet}; | ||
13 | |||
14 | use 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)] | ||
23 | pub 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)] | ||
31 | pub 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 | |||
38 | impl 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 | |||
62 | impl 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 | |||
469 | fn 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 | } | ||
515 | fn 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 | |||
564 | fn 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 | ||
10 | use std::{ffi::OsString, path::PathBuf}; | 10 | use std::{convert::TryFrom, ffi::OsString, path::PathBuf}; |
11 | 11 | ||
12 | use flycheck::FlycheckConfig; | 12 | use flycheck::FlycheckConfig; |
13 | use hir::PrefixKind; | 13 | use 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 | ||
1325 | pub(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, ¶ms.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 | |||
1325 | fn implementation_title(count: usize) -> String { | 1347 | fn 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 | |||
358 | pub enum OpenCargoToml {} | ||
359 | |||
360 | impl 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")] | ||
368 | pub 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 | }; |
14 | use lsp_types::{ProgressParams, ProgressParamsValue}; | 14 | use lsp_types::{ProgressParams, ProgressParamsValue}; |
15 | use project_model::ProjectManifest; | 15 | use project_model::{CargoConfig, ProjectManifest}; |
16 | use rust_analyzer::{ | 16 | use 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 | <!--- |
2 | lsp_ext.rs hash: 4f86fb54e4b2870e | 2 | lsp_ext.rs hash: 9d5daed5b25dc4f6 |
3 | 3 | ||
4 | If you need to change the above hash to make the test pass, please check if you | 4 | If you need to change the above hash to make the test pass, please check if you |
5 | need to adjust this doc as well and ping this issue: | 5 | need 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 | |||
545 | This 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 | ||
191 | export 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 | |||
191 | export function ssr(ctx: Ctx): Cmd { | 212 | export 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 | ||
116 | export const openDocs = new lc.RequestType<lc.TextDocumentPositionParams, string | void, void>('experimental/externalDocs'); | 116 | export const openDocs = new lc.RequestType<lc.TextDocumentPositionParams, string | void, void>('experimental/externalDocs'); |
117 | |||
118 | export const openCargoToml = new lc.RequestType<OpenCargoTomlParams, lc.Location, void>("experimental/openCargoToml"); | ||
119 | |||
120 | export 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); |