diff options
43 files changed, 1109 insertions, 334 deletions
diff --git a/Cargo.lock b/Cargo.lock index 72ec68624..7fecee1b5 100644 --- a/Cargo.lock +++ b/Cargo.lock | |||
@@ -140,9 +140,9 @@ dependencies = [ | |||
140 | 140 | ||
141 | [[package]] | 141 | [[package]] |
142 | name = "cc" | 142 | name = "cc" |
143 | version = "1.0.59" | 143 | version = "1.0.60" |
144 | source = "registry+https://github.com/rust-lang/crates.io-index" | 144 | source = "registry+https://github.com/rust-lang/crates.io-index" |
145 | checksum = "66120af515773fb005778dc07c261bd201ec8ce50bd6e7144c927753fe013381" | 145 | checksum = "ef611cc68ff783f18535d77ddd080185275713d852c4f5cbb6122c462a7a825c" |
146 | 146 | ||
147 | [[package]] | 147 | [[package]] |
148 | name = "cfg" | 148 | name = "cfg" |
@@ -311,9 +311,9 @@ checksum = "9bda8e21c04aca2ae33ffc2fd8c23134f3cac46db123ba97bd9d3f3b8a4a85e1" | |||
311 | 311 | ||
312 | [[package]] | 312 | [[package]] |
313 | name = "either" | 313 | name = "either" |
314 | version = "1.6.0" | 314 | version = "1.6.1" |
315 | source = "registry+https://github.com/rust-lang/crates.io-index" | 315 | source = "registry+https://github.com/rust-lang/crates.io-index" |
316 | checksum = "cd56b59865bce947ac5958779cfa508f6c3b9497cc762b7e24a12d11ccde2c4f" | 316 | checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" |
317 | 317 | ||
318 | [[package]] | 318 | [[package]] |
319 | name = "ena" | 319 | name = "ena" |
@@ -775,9 +775,9 @@ dependencies = [ | |||
775 | 775 | ||
776 | [[package]] | 776 | [[package]] |
777 | name = "lsp-types" | 777 | name = "lsp-types" |
778 | version = "0.80.0" | 778 | version = "0.82.0" |
779 | source = "registry+https://github.com/rust-lang/crates.io-index" | 779 | source = "registry+https://github.com/rust-lang/crates.io-index" |
780 | checksum = "f4265e2715bdacbb4dad029fce525e420cd66dc0af24ff9cb996a8ab48ac92ef" | 780 | checksum = "db895abb8527cf59e3de893ab2acf52cf904faeb65e60ea6f373e11fe86464e8" |
781 | dependencies = [ | 781 | dependencies = [ |
782 | "base64", | 782 | "base64", |
783 | "bitflags", | 783 | "bitflags", |
@@ -1187,9 +1187,9 @@ dependencies = [ | |||
1187 | 1187 | ||
1188 | [[package]] | 1188 | [[package]] |
1189 | name = "rayon-core" | 1189 | name = "rayon-core" |
1190 | version = "1.8.0" | 1190 | version = "1.8.1" |
1191 | source = "registry+https://github.com/rust-lang/crates.io-index" | 1191 | source = "registry+https://github.com/rust-lang/crates.io-index" |
1192 | checksum = "91739a34c4355b5434ce54c9086c5895604a9c278586d1f1aa95e04f66b525a0" | 1192 | checksum = "e8c4fec834fb6e6d2dd5eece3c7b432a52f0ba887cf40e595190c4107edc08bf" |
1193 | dependencies = [ | 1193 | dependencies = [ |
1194 | "crossbeam-channel", | 1194 | "crossbeam-channel", |
1195 | "crossbeam-deque", | 1195 | "crossbeam-deque", |
@@ -1493,9 +1493,9 @@ version = "0.0.0" | |||
1493 | 1493 | ||
1494 | [[package]] | 1494 | [[package]] |
1495 | name = "syn" | 1495 | name = "syn" |
1496 | version = "1.0.40" | 1496 | version = "1.0.41" |
1497 | source = "registry+https://github.com/rust-lang/crates.io-index" | 1497 | source = "registry+https://github.com/rust-lang/crates.io-index" |
1498 | checksum = "963f7d3cc59b59b9325165add223142bbf1df27655d07789f109896d353d8350" | 1498 | checksum = "6690e3e9f692504b941dc6c3b188fd28df054f7fb8469ab40680df52fdcc842b" |
1499 | dependencies = [ | 1499 | dependencies = [ |
1500 | "proc-macro2", | 1500 | "proc-macro2", |
1501 | "quote", | 1501 | "quote", |
diff --git a/crates/assists/src/ast_transform.rs b/crates/assists/src/ast_transform.rs index bbcd2d488..835da3bb2 100644 --- a/crates/assists/src/ast_transform.rs +++ b/crates/assists/src/ast_transform.rs | |||
@@ -18,6 +18,34 @@ pub fn apply<'a, N: AstNode>(transformer: &dyn AstTransform<'a>, node: N) -> N { | |||
18 | .rewrite_ast(&node) | 18 | .rewrite_ast(&node) |
19 | } | 19 | } |
20 | 20 | ||
21 | /// `AstTransform` helps with applying bulk transformations to syntax nodes. | ||
22 | /// | ||
23 | /// This is mostly useful for IDE code generation. If you paste some existing | ||
24 | /// code into a new context (for example, to add method overrides to an `impl` | ||
25 | /// block), you generally want to appropriately qualify the names, and sometimes | ||
26 | /// you might want to substitute generic parameters as well: | ||
27 | /// | ||
28 | /// ``` | ||
29 | /// mod x { | ||
30 | /// pub struct A; | ||
31 | /// pub trait T<U> { fn foo(&self, _: U) -> A; } | ||
32 | /// } | ||
33 | /// | ||
34 | /// mod y { | ||
35 | /// use x::T; | ||
36 | /// | ||
37 | /// impl T<()> for () { | ||
38 | /// // If we invoke **Add Missing Members** here, we want to copy-paste `foo`. | ||
39 | /// // But we want a slightly-modified version of it: | ||
40 | /// fn foo(&self, _: ()) -> x::A {} | ||
41 | /// } | ||
42 | /// } | ||
43 | /// ``` | ||
44 | /// | ||
45 | /// So, a single `AstTransform` describes such function from `SyntaxNode` to | ||
46 | /// `SyntaxNode`. Note that the API here is a bit too high-order and high-brow. | ||
47 | /// We'd want to somehow express this concept simpler, but so far nobody got to | ||
48 | /// simplifying this! | ||
21 | pub trait AstTransform<'a> { | 49 | pub trait AstTransform<'a> { |
22 | fn get_substitution(&self, node: &syntax::SyntaxNode) -> Option<syntax::SyntaxNode>; | 50 | fn get_substitution(&self, node: &syntax::SyntaxNode) -> Option<syntax::SyntaxNode>; |
23 | 51 | ||
diff --git a/crates/assists/src/handlers/add_missing_impl_members.rs b/crates/assists/src/handlers/add_missing_impl_members.rs index 83a2ada9a..8df1d786b 100644 --- a/crates/assists/src/handlers/add_missing_impl_members.rs +++ b/crates/assists/src/handlers/add_missing_impl_members.rs | |||
@@ -111,8 +111,6 @@ fn add_missing_impl_members_inner( | |||
111 | ) -> Option<()> { | 111 | ) -> Option<()> { |
112 | let _p = profile::span("add_missing_impl_members_inner"); | 112 | let _p = profile::span("add_missing_impl_members_inner"); |
113 | let impl_def = ctx.find_node_at_offset::<ast::Impl>()?; | 113 | let impl_def = ctx.find_node_at_offset::<ast::Impl>()?; |
114 | let impl_item_list = impl_def.assoc_item_list()?; | ||
115 | |||
116 | let trait_ = resolve_target_trait(&ctx.sema, &impl_def)?; | 114 | let trait_ = resolve_target_trait(&ctx.sema, &impl_def)?; |
117 | 115 | ||
118 | let def_name = |item: &ast::AssocItem| -> Option<SmolStr> { | 116 | let def_name = |item: &ast::AssocItem| -> Option<SmolStr> { |
@@ -148,11 +146,14 @@ fn add_missing_impl_members_inner( | |||
148 | 146 | ||
149 | let target = impl_def.syntax().text_range(); | 147 | let target = impl_def.syntax().text_range(); |
150 | acc.add(AssistId(assist_id, AssistKind::QuickFix), label, target, |builder| { | 148 | acc.add(AssistId(assist_id, AssistKind::QuickFix), label, target, |builder| { |
149 | let impl_item_list = impl_def.assoc_item_list().unwrap_or(make::assoc_item_list()); | ||
150 | |||
151 | let n_existing_items = impl_item_list.assoc_items().count(); | 151 | let n_existing_items = impl_item_list.assoc_items().count(); |
152 | let source_scope = ctx.sema.scope_for_def(trait_); | 152 | let source_scope = ctx.sema.scope_for_def(trait_); |
153 | let target_scope = ctx.sema.scope(impl_item_list.syntax()); | 153 | let target_scope = ctx.sema.scope(impl_def.syntax()); |
154 | let ast_transform = QualifyPaths::new(&target_scope, &source_scope) | 154 | let ast_transform = QualifyPaths::new(&target_scope, &source_scope) |
155 | .or(SubstituteTypeParams::for_trait_impl(&source_scope, trait_, impl_def)); | 155 | .or(SubstituteTypeParams::for_trait_impl(&source_scope, trait_, impl_def.clone())); |
156 | |||
156 | let items = missing_items | 157 | let items = missing_items |
157 | .into_iter() | 158 | .into_iter() |
158 | .map(|it| ast_transform::apply(&*ast_transform, it)) | 159 | .map(|it| ast_transform::apply(&*ast_transform, it)) |
@@ -162,12 +163,14 @@ fn add_missing_impl_members_inner( | |||
162 | _ => it, | 163 | _ => it, |
163 | }) | 164 | }) |
164 | .map(|it| edit::remove_attrs_and_docs(&it)); | 165 | .map(|it| edit::remove_attrs_and_docs(&it)); |
166 | |||
165 | let new_impl_item_list = impl_item_list.append_items(items); | 167 | let new_impl_item_list = impl_item_list.append_items(items); |
166 | let first_new_item = new_impl_item_list.assoc_items().nth(n_existing_items).unwrap(); | 168 | let new_impl_def = impl_def.with_assoc_item_list(new_impl_item_list); |
169 | let first_new_item = | ||
170 | new_impl_def.assoc_item_list().unwrap().assoc_items().nth(n_existing_items).unwrap(); | ||
167 | 171 | ||
168 | let original_range = impl_item_list.syntax().text_range(); | ||
169 | match ctx.config.snippet_cap { | 172 | match ctx.config.snippet_cap { |
170 | None => builder.replace(original_range, new_impl_item_list.to_string()), | 173 | None => builder.replace(target, new_impl_def.to_string()), |
171 | Some(cap) => { | 174 | Some(cap) => { |
172 | let mut cursor = Cursor::Before(first_new_item.syntax()); | 175 | let mut cursor = Cursor::Before(first_new_item.syntax()); |
173 | let placeholder; | 176 | let placeholder; |
@@ -181,8 +184,8 @@ fn add_missing_impl_members_inner( | |||
181 | } | 184 | } |
182 | builder.replace_snippet( | 185 | builder.replace_snippet( |
183 | cap, | 186 | cap, |
184 | original_range, | 187 | target, |
185 | render_snippet(cap, new_impl_item_list.syntax(), cursor), | 188 | render_snippet(cap, new_impl_def.syntax(), cursor), |
186 | ) | 189 | ) |
187 | } | 190 | } |
188 | }; | 191 | }; |
@@ -311,6 +314,25 @@ impl Foo for S { | |||
311 | } | 314 | } |
312 | 315 | ||
313 | #[test] | 316 | #[test] |
317 | fn test_impl_def_without_braces() { | ||
318 | check_assist( | ||
319 | add_missing_impl_members, | ||
320 | r#" | ||
321 | trait Foo { fn foo(&self); } | ||
322 | struct S; | ||
323 | impl Foo for S<|>"#, | ||
324 | r#" | ||
325 | trait Foo { fn foo(&self); } | ||
326 | struct S; | ||
327 | impl Foo for S { | ||
328 | fn foo(&self) { | ||
329 | ${0:todo!()} | ||
330 | } | ||
331 | }"#, | ||
332 | ); | ||
333 | } | ||
334 | |||
335 | #[test] | ||
314 | fn fill_in_type_params_1() { | 336 | fn fill_in_type_params_1() { |
315 | check_assist( | 337 | check_assist( |
316 | add_missing_impl_members, | 338 | add_missing_impl_members, |
diff --git a/crates/hir/src/code_model.rs b/crates/hir/src/code_model.rs index 7a9747fc7..a2a166e0a 100644 --- a/crates/hir/src/code_model.rs +++ b/crates/hir/src/code_model.rs | |||
@@ -709,11 +709,23 @@ impl Function { | |||
709 | } | 709 | } |
710 | 710 | ||
711 | pub fn params(self, db: &dyn HirDatabase) -> Vec<Param> { | 711 | pub fn params(self, db: &dyn HirDatabase) -> Vec<Param> { |
712 | let resolver = self.id.resolver(db.upcast()); | ||
713 | let ctx = hir_ty::TyLoweringContext::new(db, &resolver); | ||
714 | let environment = TraitEnvironment::lower(db, &resolver); | ||
712 | db.function_data(self.id) | 715 | db.function_data(self.id) |
713 | .params | 716 | .params |
714 | .iter() | 717 | .iter() |
715 | .skip(if self.self_param(db).is_some() { 1 } else { 0 }) | 718 | .skip(if self.self_param(db).is_some() { 1 } else { 0 }) |
716 | .map(|_| Param { _ty: () }) | 719 | .map(|type_ref| { |
720 | let ty = Type { | ||
721 | krate: self.id.lookup(db.upcast()).container.module(db.upcast()).krate, | ||
722 | ty: InEnvironment { | ||
723 | value: Ty::from_hir_ext(&ctx, type_ref).0, | ||
724 | environment: environment.clone(), | ||
725 | }, | ||
726 | }; | ||
727 | Param { ty } | ||
728 | }) | ||
717 | .collect() | 729 | .collect() |
718 | } | 730 | } |
719 | 731 | ||
@@ -742,15 +754,21 @@ impl From<Mutability> for Access { | |||
742 | } | 754 | } |
743 | } | 755 | } |
744 | 756 | ||
757 | pub struct Param { | ||
758 | ty: Type, | ||
759 | } | ||
760 | |||
761 | impl Param { | ||
762 | pub fn ty(&self) -> &Type { | ||
763 | &self.ty | ||
764 | } | ||
765 | } | ||
766 | |||
745 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] | 767 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] |
746 | pub struct SelfParam { | 768 | pub struct SelfParam { |
747 | func: FunctionId, | 769 | func: FunctionId, |
748 | } | 770 | } |
749 | 771 | ||
750 | pub struct Param { | ||
751 | _ty: (), | ||
752 | } | ||
753 | |||
754 | impl SelfParam { | 772 | impl SelfParam { |
755 | pub fn access(self, db: &dyn HirDatabase) -> Access { | 773 | pub fn access(self, db: &dyn HirDatabase) -> Access { |
756 | let func_data = db.function_data(self.func); | 774 | let func_data = db.function_data(self.func); |
@@ -908,12 +926,12 @@ impl MacroDef { | |||
908 | 926 | ||
909 | /// Indicate it is a proc-macro | 927 | /// Indicate it is a proc-macro |
910 | pub fn is_proc_macro(&self) -> bool { | 928 | pub fn is_proc_macro(&self) -> bool { |
911 | matches!(self.id.kind, MacroDefKind::CustomDerive(_)) | 929 | matches!(self.id.kind, MacroDefKind::ProcMacro(_)) |
912 | } | 930 | } |
913 | 931 | ||
914 | /// Indicate it is a derive macro | 932 | /// Indicate it is a derive macro |
915 | pub fn is_derive_macro(&self) -> bool { | 933 | pub fn is_derive_macro(&self) -> bool { |
916 | matches!(self.id.kind, MacroDefKind::CustomDerive(_) | MacroDefKind::BuiltInDerive(_)) | 934 | matches!(self.id.kind, MacroDefKind::ProcMacro(_) | MacroDefKind::BuiltInDerive(_)) |
917 | } | 935 | } |
918 | } | 936 | } |
919 | 937 | ||
@@ -1276,6 +1294,14 @@ impl Type { | |||
1276 | ) | 1294 | ) |
1277 | } | 1295 | } |
1278 | 1296 | ||
1297 | pub fn remove_ref(&self) -> Option<Type> { | ||
1298 | if let Ty::Apply(ApplicationTy { ctor: TypeCtor::Ref(_), .. }) = self.ty.value { | ||
1299 | self.ty.value.substs().map(|substs| self.derived(substs[0].clone())) | ||
1300 | } else { | ||
1301 | None | ||
1302 | } | ||
1303 | } | ||
1304 | |||
1279 | pub fn is_unknown(&self) -> bool { | 1305 | pub fn is_unknown(&self) -> bool { |
1280 | matches!(self.ty.value, Ty::Unknown) | 1306 | matches!(self.ty.value, Ty::Unknown) |
1281 | } | 1307 | } |
diff --git a/crates/hir/src/semantics.rs b/crates/hir/src/semantics.rs index 0516a05b4..c61a430e1 100644 --- a/crates/hir/src/semantics.rs +++ b/crates/hir/src/semantics.rs | |||
@@ -697,6 +697,25 @@ fn find_root(node: &SyntaxNode) -> SyntaxNode { | |||
697 | node.ancestors().last().unwrap() | 697 | node.ancestors().last().unwrap() |
698 | } | 698 | } |
699 | 699 | ||
700 | /// `SemanticScope` encapsulates the notion of a scope (the set of visible | ||
701 | /// names) at a particular program point. | ||
702 | /// | ||
703 | /// It is a bit tricky, as scopes do not really exist inside the compiler. | ||
704 | /// Rather, the compiler directly computes for each reference the definition it | ||
705 | /// refers to. It might transiently compute the explicit scope map while doing | ||
706 | /// so, but, generally, this is not something left after the analysis. | ||
707 | /// | ||
708 | /// However, we do very much need explicit scopes for IDE purposes -- | ||
709 | /// completion, at its core, lists the contents of the current scope. The notion | ||
710 | /// of scope is also useful to answer questions like "what would be the meaning | ||
711 | /// of this piece of code if we inserted it into this position?". | ||
712 | /// | ||
713 | /// So `SemanticsScope` is constructed from a specific program point (a syntax | ||
714 | /// node or just a raw offset) and provides access to the set of visible names | ||
715 | /// on a somewhat best-effort basis. | ||
716 | /// | ||
717 | /// Note that if you are wondering "what does this specific existing name mean?", | ||
718 | /// you'd better use the `resolve_` family of methods. | ||
700 | #[derive(Debug)] | 719 | #[derive(Debug)] |
701 | pub struct SemanticsScope<'a> { | 720 | pub struct SemanticsScope<'a> { |
702 | pub db: &'a dyn HirDatabase, | 721 | pub db: &'a dyn HirDatabase, |
diff --git a/crates/hir_def/src/diagnostics.rs b/crates/hir_def/src/diagnostics.rs index 3e19d9117..2ec0fd3fb 100644 --- a/crates/hir_def/src/diagnostics.rs +++ b/crates/hir_def/src/diagnostics.rs | |||
@@ -28,3 +28,45 @@ impl Diagnostic for UnresolvedModule { | |||
28 | self | 28 | self |
29 | } | 29 | } |
30 | } | 30 | } |
31 | |||
32 | #[derive(Debug)] | ||
33 | pub struct UnresolvedExternCrate { | ||
34 | pub file: HirFileId, | ||
35 | pub item: AstPtr<ast::ExternCrate>, | ||
36 | } | ||
37 | |||
38 | impl Diagnostic for UnresolvedExternCrate { | ||
39 | fn code(&self) -> DiagnosticCode { | ||
40 | DiagnosticCode("unresolved-extern-crate") | ||
41 | } | ||
42 | fn message(&self) -> String { | ||
43 | "unresolved extern crate".to_string() | ||
44 | } | ||
45 | fn display_source(&self) -> InFile<SyntaxNodePtr> { | ||
46 | InFile::new(self.file, self.item.clone().into()) | ||
47 | } | ||
48 | fn as_any(&self) -> &(dyn Any + Send + 'static) { | ||
49 | self | ||
50 | } | ||
51 | } | ||
52 | |||
53 | #[derive(Debug)] | ||
54 | pub struct UnresolvedImport { | ||
55 | pub file: HirFileId, | ||
56 | pub node: AstPtr<ast::UseTree>, | ||
57 | } | ||
58 | |||
59 | impl Diagnostic for UnresolvedImport { | ||
60 | fn code(&self) -> DiagnosticCode { | ||
61 | DiagnosticCode("unresolved-import") | ||
62 | } | ||
63 | fn message(&self) -> String { | ||
64 | "unresolved import".to_string() | ||
65 | } | ||
66 | fn display_source(&self) -> InFile<SyntaxNodePtr> { | ||
67 | InFile::new(self.file, self.node.clone().into()) | ||
68 | } | ||
69 | fn as_any(&self) -> &(dyn Any + Send + 'static) { | ||
70 | self | ||
71 | } | ||
72 | } | ||
diff --git a/crates/hir_def/src/item_tree.rs b/crates/hir_def/src/item_tree.rs index 52abb8e7f..0fd91b9d0 100644 --- a/crates/hir_def/src/item_tree.rs +++ b/crates/hir_def/src/item_tree.rs | |||
@@ -291,7 +291,6 @@ pub enum AttrOwner { | |||
291 | 291 | ||
292 | Variant(Idx<Variant>), | 292 | Variant(Idx<Variant>), |
293 | Field(Idx<Field>), | 293 | Field(Idx<Field>), |
294 | // FIXME: Store variant and field attrs, and stop reparsing them in `attrs_query`. | ||
295 | } | 294 | } |
296 | 295 | ||
297 | macro_rules! from_attrs { | 296 | macro_rules! from_attrs { |
@@ -483,11 +482,16 @@ pub struct Import { | |||
483 | /// AST ID of the `use` or `extern crate` item this import was derived from. Note that many | 482 | /// AST ID of the `use` or `extern crate` item this import was derived from. Note that many |
484 | /// `Import`s can map to the same `use` item. | 483 | /// `Import`s can map to the same `use` item. |
485 | pub ast_id: FileAstId<ast::Use>, | 484 | pub ast_id: FileAstId<ast::Use>, |
485 | /// Index of this `Import` when the containing `Use` is visited via `ModPath::expand_use_item`. | ||
486 | /// | ||
487 | /// This can be used to get the `UseTree` this `Import` corresponds to and allows emitting | ||
488 | /// precise diagnostics. | ||
489 | pub index: usize, | ||
486 | } | 490 | } |
487 | 491 | ||
488 | #[derive(Debug, Clone, Eq, PartialEq)] | 492 | #[derive(Debug, Clone, Eq, PartialEq)] |
489 | pub struct ExternCrate { | 493 | pub struct ExternCrate { |
490 | pub path: ModPath, | 494 | pub name: Name, |
491 | pub alias: Option<ImportAlias>, | 495 | pub alias: Option<ImportAlias>, |
492 | pub visibility: RawVisibilityId, | 496 | pub visibility: RawVisibilityId, |
493 | /// Whether this is a `#[macro_use] extern crate ...`. | 497 | /// Whether this is a `#[macro_use] extern crate ...`. |
diff --git a/crates/hir_def/src/item_tree/lower.rs b/crates/hir_def/src/item_tree/lower.rs index d93377c3b..54814f141 100644 --- a/crates/hir_def/src/item_tree/lower.rs +++ b/crates/hir_def/src/item_tree/lower.rs | |||
@@ -483,7 +483,7 @@ impl Ctx { | |||
483 | ModPath::expand_use_item( | 483 | ModPath::expand_use_item( |
484 | InFile::new(self.file, use_item.clone()), | 484 | InFile::new(self.file, use_item.clone()), |
485 | &self.hygiene, | 485 | &self.hygiene, |
486 | |path, _tree, is_glob, alias| { | 486 | |path, _use_tree, is_glob, alias| { |
487 | imports.push(id(tree.imports.alloc(Import { | 487 | imports.push(id(tree.imports.alloc(Import { |
488 | path, | 488 | path, |
489 | alias, | 489 | alias, |
@@ -491,6 +491,7 @@ impl Ctx { | |||
491 | is_glob, | 491 | is_glob, |
492 | is_prelude, | 492 | is_prelude, |
493 | ast_id, | 493 | ast_id, |
494 | index: imports.len(), | ||
494 | }))); | 495 | }))); |
495 | }, | 496 | }, |
496 | ); | 497 | ); |
@@ -502,7 +503,7 @@ impl Ctx { | |||
502 | &mut self, | 503 | &mut self, |
503 | extern_crate: &ast::ExternCrate, | 504 | extern_crate: &ast::ExternCrate, |
504 | ) -> Option<FileItemTreeId<ExternCrate>> { | 505 | ) -> Option<FileItemTreeId<ExternCrate>> { |
505 | let path = ModPath::from_name_ref(&extern_crate.name_ref()?); | 506 | let name = extern_crate.name_ref()?.as_name(); |
506 | let alias = extern_crate.rename().map(|a| { | 507 | let alias = extern_crate.rename().map(|a| { |
507 | a.name().map(|it| it.as_name()).map_or(ImportAlias::Underscore, ImportAlias::Alias) | 508 | a.name().map(|it| it.as_name()).map_or(ImportAlias::Underscore, ImportAlias::Alias) |
508 | }); | 509 | }); |
@@ -511,7 +512,7 @@ impl Ctx { | |||
511 | // FIXME: cfg_attr | 512 | // FIXME: cfg_attr |
512 | let is_macro_use = extern_crate.has_atom_attr("macro_use"); | 513 | let is_macro_use = extern_crate.has_atom_attr("macro_use"); |
513 | 514 | ||
514 | let res = ExternCrate { path, alias, visibility, is_macro_use, ast_id }; | 515 | let res = ExternCrate { name, alias, visibility, is_macro_use, ast_id }; |
515 | Some(id(self.data().extern_crates.alloc(res))) | 516 | Some(id(self.data().extern_crates.alloc(res))) |
516 | } | 517 | } |
517 | 518 | ||
diff --git a/crates/hir_def/src/item_tree/tests.rs b/crates/hir_def/src/item_tree/tests.rs index eed3d0d6f..1a806cda5 100644 --- a/crates/hir_def/src/item_tree/tests.rs +++ b/crates/hir_def/src/item_tree/tests.rs | |||
@@ -228,11 +228,11 @@ fn smoke() { | |||
228 | 228 | ||
229 | top-level items: | 229 | top-level items: |
230 | #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("attr_on_use"))] }, input: None }]) }] | 230 | #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("attr_on_use"))] }, input: None }]) }] |
231 | Import { path: ModPath { kind: Plain, segments: [Name(Text("a"))] }, alias: None, visibility: RawVisibilityId("pub(self)"), is_glob: false, is_prelude: false, ast_id: FileAstId::<syntax::ast::generated::nodes::Use>(0) } | 231 | Import { path: ModPath { kind: Plain, segments: [Name(Text("a"))] }, alias: None, visibility: RawVisibilityId("pub(self)"), is_glob: false, is_prelude: false, ast_id: FileAstId::<syntax::ast::generated::nodes::Use>(0), index: 0 } |
232 | #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("attr_on_use"))] }, input: None }]) }] | 232 | #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("attr_on_use"))] }, input: None }]) }] |
233 | Import { path: ModPath { kind: Plain, segments: [Name(Text("b"))] }, alias: None, visibility: RawVisibilityId("pub(self)"), is_glob: true, is_prelude: false, ast_id: FileAstId::<syntax::ast::generated::nodes::Use>(0) } | 233 | Import { path: ModPath { kind: Plain, segments: [Name(Text("b"))] }, alias: None, visibility: RawVisibilityId("pub(self)"), is_glob: true, is_prelude: false, ast_id: FileAstId::<syntax::ast::generated::nodes::Use>(0), index: 1 } |
234 | #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("ext_crate"))] }, input: None }]) }] | 234 | #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("ext_crate"))] }, input: None }]) }] |
235 | ExternCrate { path: ModPath { kind: Plain, segments: [Name(Text("krate"))] }, alias: None, visibility: RawVisibilityId("pub(self)"), is_macro_use: false, ast_id: FileAstId::<syntax::ast::generated::nodes::ExternCrate>(1) } | 235 | ExternCrate { name: Name(Text("krate")), alias: None, visibility: RawVisibilityId("pub(self)"), is_macro_use: false, ast_id: FileAstId::<syntax::ast::generated::nodes::ExternCrate>(1) } |
236 | #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("on_trait"))] }, input: None }]) }] | 236 | #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("on_trait"))] }, input: None }]) }] |
237 | Trait { name: Name(Text("Tr")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(0), auto: false, items: [TypeAlias(Idx::<TypeAlias>(0)), Const(Idx::<Const>(0)), Function(Idx::<Function>(0)), Function(Idx::<Function>(1))], ast_id: FileAstId::<syntax::ast::generated::nodes::Trait>(2) } | 237 | Trait { name: Name(Text("Tr")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(0), auto: false, items: [TypeAlias(Idx::<TypeAlias>(0)), Const(Idx::<Const>(0)), Function(Idx::<Function>(0)), Function(Idx::<Function>(1))], ast_id: FileAstId::<syntax::ast::generated::nodes::Trait>(2) } |
238 | > #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("assoc_ty"))] }, input: None }]) }] | 238 | > #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("assoc_ty"))] }, input: None }]) }] |
diff --git a/crates/hir_def/src/nameres.rs b/crates/hir_def/src/nameres.rs index bf302172d..5e4d73c1f 100644 --- a/crates/hir_def/src/nameres.rs +++ b/crates/hir_def/src/nameres.rs | |||
@@ -288,31 +288,70 @@ pub enum ModuleSource { | |||
288 | 288 | ||
289 | mod diagnostics { | 289 | mod diagnostics { |
290 | use hir_expand::diagnostics::DiagnosticSink; | 290 | use hir_expand::diagnostics::DiagnosticSink; |
291 | use hir_expand::hygiene::Hygiene; | ||
292 | use hir_expand::InFile; | ||
291 | use syntax::{ast, AstPtr}; | 293 | use syntax::{ast, AstPtr}; |
292 | 294 | ||
293 | use crate::{db::DefDatabase, diagnostics::UnresolvedModule, nameres::LocalModuleId, AstId}; | 295 | use crate::path::ModPath; |
296 | use crate::{db::DefDatabase, diagnostics::*, nameres::LocalModuleId, AstId}; | ||
294 | 297 | ||
295 | #[derive(Debug, PartialEq, Eq)] | 298 | #[derive(Debug, PartialEq, Eq)] |
296 | pub(super) enum DefDiagnostic { | 299 | enum DiagnosticKind { |
297 | UnresolvedModule { | 300 | UnresolvedModule { declaration: AstId<ast::Module>, candidate: String }, |
298 | module: LocalModuleId, | 301 | |
299 | declaration: AstId<ast::Module>, | 302 | UnresolvedExternCrate { ast: AstId<ast::ExternCrate> }, |
300 | candidate: String, | 303 | |
301 | }, | 304 | UnresolvedImport { ast: AstId<ast::Use>, index: usize }, |
305 | } | ||
306 | |||
307 | #[derive(Debug, PartialEq, Eq)] | ||
308 | pub(super) struct DefDiagnostic { | ||
309 | in_module: LocalModuleId, | ||
310 | kind: DiagnosticKind, | ||
302 | } | 311 | } |
303 | 312 | ||
304 | impl DefDiagnostic { | 313 | impl DefDiagnostic { |
314 | pub(super) fn unresolved_module( | ||
315 | container: LocalModuleId, | ||
316 | declaration: AstId<ast::Module>, | ||
317 | candidate: String, | ||
318 | ) -> Self { | ||
319 | Self { | ||
320 | in_module: container, | ||
321 | kind: DiagnosticKind::UnresolvedModule { declaration, candidate }, | ||
322 | } | ||
323 | } | ||
324 | |||
325 | pub(super) fn unresolved_extern_crate( | ||
326 | container: LocalModuleId, | ||
327 | declaration: AstId<ast::ExternCrate>, | ||
328 | ) -> Self { | ||
329 | Self { | ||
330 | in_module: container, | ||
331 | kind: DiagnosticKind::UnresolvedExternCrate { ast: declaration }, | ||
332 | } | ||
333 | } | ||
334 | |||
335 | pub(super) fn unresolved_import( | ||
336 | container: LocalModuleId, | ||
337 | ast: AstId<ast::Use>, | ||
338 | index: usize, | ||
339 | ) -> Self { | ||
340 | Self { in_module: container, kind: DiagnosticKind::UnresolvedImport { ast, index } } | ||
341 | } | ||
342 | |||
305 | pub(super) fn add_to( | 343 | pub(super) fn add_to( |
306 | &self, | 344 | &self, |
307 | db: &dyn DefDatabase, | 345 | db: &dyn DefDatabase, |
308 | target_module: LocalModuleId, | 346 | target_module: LocalModuleId, |
309 | sink: &mut DiagnosticSink, | 347 | sink: &mut DiagnosticSink, |
310 | ) { | 348 | ) { |
311 | match self { | 349 | if self.in_module != target_module { |
312 | DefDiagnostic::UnresolvedModule { module, declaration, candidate } => { | 350 | return; |
313 | if *module != target_module { | 351 | } |
314 | return; | 352 | |
315 | } | 353 | match &self.kind { |
354 | DiagnosticKind::UnresolvedModule { declaration, candidate } => { | ||
316 | let decl = declaration.to_node(db.upcast()); | 355 | let decl = declaration.to_node(db.upcast()); |
317 | sink.push(UnresolvedModule { | 356 | sink.push(UnresolvedModule { |
318 | file: declaration.file_id, | 357 | file: declaration.file_id, |
@@ -320,6 +359,36 @@ mod diagnostics { | |||
320 | candidate: candidate.clone(), | 359 | candidate: candidate.clone(), |
321 | }) | 360 | }) |
322 | } | 361 | } |
362 | |||
363 | DiagnosticKind::UnresolvedExternCrate { ast } => { | ||
364 | let item = ast.to_node(db.upcast()); | ||
365 | sink.push(UnresolvedExternCrate { | ||
366 | file: ast.file_id, | ||
367 | item: AstPtr::new(&item), | ||
368 | }); | ||
369 | } | ||
370 | |||
371 | DiagnosticKind::UnresolvedImport { ast, index } => { | ||
372 | let use_item = ast.to_node(db.upcast()); | ||
373 | let hygiene = Hygiene::new(db.upcast(), ast.file_id); | ||
374 | let mut cur = 0; | ||
375 | let mut tree = None; | ||
376 | ModPath::expand_use_item( | ||
377 | InFile::new(ast.file_id, use_item), | ||
378 | &hygiene, | ||
379 | |_mod_path, use_tree, _is_glob, _alias| { | ||
380 | if cur == *index { | ||
381 | tree = Some(use_tree.clone()); | ||
382 | } | ||
383 | |||
384 | cur += 1; | ||
385 | }, | ||
386 | ); | ||
387 | |||
388 | if let Some(tree) = tree { | ||
389 | sink.push(UnresolvedImport { file: ast.file_id, node: AstPtr::new(&tree) }); | ||
390 | } | ||
391 | } | ||
323 | } | 392 | } |
324 | } | 393 | } |
325 | } | 394 | } |
diff --git a/crates/hir_def/src/nameres/collector.rs b/crates/hir_def/src/nameres/collector.rs index 3e99c8773..4c3993ff0 100644 --- a/crates/hir_def/src/nameres/collector.rs +++ b/crates/hir_def/src/nameres/collector.rs | |||
@@ -3,8 +3,11 @@ | |||
3 | //! `DefCollector::collect` contains the fixed-point iteration loop which | 3 | //! `DefCollector::collect` contains the fixed-point iteration loop which |
4 | //! resolves imports and expands macros. | 4 | //! resolves imports and expands macros. |
5 | 5 | ||
6 | use std::iter; | ||
7 | |||
6 | use base_db::{CrateId, FileId, ProcMacroId}; | 8 | use base_db::{CrateId, FileId, ProcMacroId}; |
7 | use cfg::CfgOptions; | 9 | use cfg::CfgOptions; |
10 | use hir_expand::InFile; | ||
8 | use hir_expand::{ | 11 | use hir_expand::{ |
9 | ast_id_map::FileAstId, | 12 | ast_id_map::FileAstId, |
10 | builtin_derive::find_builtin_derive, | 13 | builtin_derive::find_builtin_derive, |
@@ -14,6 +17,7 @@ use hir_expand::{ | |||
14 | HirFileId, MacroCallId, MacroDefId, MacroDefKind, | 17 | HirFileId, MacroCallId, MacroDefId, MacroDefKind, |
15 | }; | 18 | }; |
16 | use rustc_hash::FxHashMap; | 19 | use rustc_hash::FxHashMap; |
20 | use rustc_hash::FxHashSet; | ||
17 | use syntax::ast; | 21 | use syntax::ast; |
18 | use test_utils::mark; | 22 | use test_utils::mark; |
19 | 23 | ||
@@ -21,9 +25,7 @@ use crate::{ | |||
21 | attr::Attrs, | 25 | attr::Attrs, |
22 | db::DefDatabase, | 26 | db::DefDatabase, |
23 | item_scope::{ImportType, PerNsGlobImports}, | 27 | item_scope::{ImportType, PerNsGlobImports}, |
24 | item_tree::{ | 28 | item_tree::{self, ItemTree, ItemTreeId, MacroCall, Mod, ModItem, ModKind, StructDefKind}, |
25 | self, FileItemTreeId, ItemTree, ItemTreeId, MacroCall, Mod, ModItem, ModKind, StructDefKind, | ||
26 | }, | ||
27 | nameres::{ | 29 | nameres::{ |
28 | diagnostics::DefDiagnostic, mod_resolution::ModDir, path_resolution::ReachedFixedPoint, | 30 | diagnostics::DefDiagnostic, mod_resolution::ModDir, path_resolution::ReachedFixedPoint, |
29 | BuiltinShadowMode, CrateDefMap, ModuleData, ModuleOrigin, ResolveMode, | 31 | BuiltinShadowMode, CrateDefMap, ModuleData, ModuleOrigin, ResolveMode, |
@@ -112,6 +114,12 @@ impl PartialResolvedImport { | |||
112 | } | 114 | } |
113 | 115 | ||
114 | #[derive(Clone, Debug, Eq, PartialEq)] | 116 | #[derive(Clone, Debug, Eq, PartialEq)] |
117 | enum ImportSource { | ||
118 | Import(ItemTreeId<item_tree::Import>), | ||
119 | ExternCrate(ItemTreeId<item_tree::ExternCrate>), | ||
120 | } | ||
121 | |||
122 | #[derive(Clone, Debug, Eq, PartialEq)] | ||
115 | struct Import { | 123 | struct Import { |
116 | pub path: ModPath, | 124 | pub path: ModPath, |
117 | pub alias: Option<ImportAlias>, | 125 | pub alias: Option<ImportAlias>, |
@@ -120,11 +128,12 @@ struct Import { | |||
120 | pub is_prelude: bool, | 128 | pub is_prelude: bool, |
121 | pub is_extern_crate: bool, | 129 | pub is_extern_crate: bool, |
122 | pub is_macro_use: bool, | 130 | pub is_macro_use: bool, |
131 | source: ImportSource, | ||
123 | } | 132 | } |
124 | 133 | ||
125 | impl Import { | 134 | impl Import { |
126 | fn from_use(tree: &ItemTree, id: FileItemTreeId<item_tree::Import>) -> Self { | 135 | fn from_use(tree: &ItemTree, id: ItemTreeId<item_tree::Import>) -> Self { |
127 | let it = &tree[id]; | 136 | let it = &tree[id.value]; |
128 | let visibility = &tree[it.visibility]; | 137 | let visibility = &tree[it.visibility]; |
129 | Self { | 138 | Self { |
130 | path: it.path.clone(), | 139 | path: it.path.clone(), |
@@ -134,20 +143,22 @@ impl Import { | |||
134 | is_prelude: it.is_prelude, | 143 | is_prelude: it.is_prelude, |
135 | is_extern_crate: false, | 144 | is_extern_crate: false, |
136 | is_macro_use: false, | 145 | is_macro_use: false, |
146 | source: ImportSource::Import(id), | ||
137 | } | 147 | } |
138 | } | 148 | } |
139 | 149 | ||
140 | fn from_extern_crate(tree: &ItemTree, id: FileItemTreeId<item_tree::ExternCrate>) -> Self { | 150 | fn from_extern_crate(tree: &ItemTree, id: ItemTreeId<item_tree::ExternCrate>) -> Self { |
141 | let it = &tree[id]; | 151 | let it = &tree[id.value]; |
142 | let visibility = &tree[it.visibility]; | 152 | let visibility = &tree[it.visibility]; |
143 | Self { | 153 | Self { |
144 | path: it.path.clone(), | 154 | path: ModPath::from_segments(PathKind::Plain, iter::once(it.name.clone())), |
145 | alias: it.alias.clone(), | 155 | alias: it.alias.clone(), |
146 | visibility: visibility.clone(), | 156 | visibility: visibility.clone(), |
147 | is_glob: false, | 157 | is_glob: false, |
148 | is_prelude: false, | 158 | is_prelude: false, |
149 | is_extern_crate: true, | 159 | is_extern_crate: true, |
150 | is_macro_use: it.is_macro_use, | 160 | is_macro_use: it.is_macro_use, |
161 | source: ImportSource::ExternCrate(id), | ||
151 | } | 162 | } |
152 | } | 163 | } |
153 | } | 164 | } |
@@ -245,9 +256,10 @@ impl DefCollector<'_> { | |||
245 | 256 | ||
246 | let unresolved_imports = std::mem::replace(&mut self.unresolved_imports, Vec::new()); | 257 | let unresolved_imports = std::mem::replace(&mut self.unresolved_imports, Vec::new()); |
247 | // show unresolved imports in completion, etc | 258 | // show unresolved imports in completion, etc |
248 | for directive in unresolved_imports { | 259 | for directive in &unresolved_imports { |
249 | self.record_resolved_import(&directive) | 260 | self.record_resolved_import(directive) |
250 | } | 261 | } |
262 | self.unresolved_imports = unresolved_imports; | ||
251 | 263 | ||
252 | // Record proc-macros | 264 | // Record proc-macros |
253 | self.collect_proc_macro(); | 265 | self.collect_proc_macro(); |
@@ -261,7 +273,7 @@ impl DefCollector<'_> { | |||
261 | let macro_id = MacroDefId { | 273 | let macro_id = MacroDefId { |
262 | ast_id: None, | 274 | ast_id: None, |
263 | krate: Some(krate), | 275 | krate: Some(krate), |
264 | kind: MacroDefKind::CustomDerive(expander), | 276 | kind: MacroDefKind::ProcMacro(expander), |
265 | local_inner: false, | 277 | local_inner: false, |
266 | }; | 278 | }; |
267 | 279 | ||
@@ -346,20 +358,15 @@ impl DefCollector<'_> { | |||
346 | fn import_macros_from_extern_crate( | 358 | fn import_macros_from_extern_crate( |
347 | &mut self, | 359 | &mut self, |
348 | current_module_id: LocalModuleId, | 360 | current_module_id: LocalModuleId, |
349 | import: &item_tree::ExternCrate, | 361 | extern_crate: &item_tree::ExternCrate, |
350 | ) { | 362 | ) { |
351 | log::debug!( | 363 | log::debug!( |
352 | "importing macros from extern crate: {:?} ({:?})", | 364 | "importing macros from extern crate: {:?} ({:?})", |
353 | import, | 365 | extern_crate, |
354 | self.def_map.edition, | 366 | self.def_map.edition, |
355 | ); | 367 | ); |
356 | 368 | ||
357 | let res = self.def_map.resolve_name_in_extern_prelude( | 369 | let res = self.def_map.resolve_name_in_extern_prelude(&extern_crate.name); |
358 | &import | ||
359 | .path | ||
360 | .as_ident() | ||
361 | .expect("extern crate should have been desugared to one-element path"), | ||
362 | ); | ||
363 | 370 | ||
364 | if let Some(ModuleDefId::ModuleId(m)) = res.take_types() { | 371 | if let Some(ModuleDefId::ModuleId(m)) = res.take_types() { |
365 | mark::hit!(macro_rules_from_other_crates_are_visible_with_macro_use); | 372 | mark::hit!(macro_rules_from_other_crates_are_visible_with_macro_use); |
@@ -420,7 +427,11 @@ impl DefCollector<'_> { | |||
420 | .as_ident() | 427 | .as_ident() |
421 | .expect("extern crate should have been desugared to one-element path"), | 428 | .expect("extern crate should have been desugared to one-element path"), |
422 | ); | 429 | ); |
423 | PartialResolvedImport::Resolved(res) | 430 | if res.is_none() { |
431 | PartialResolvedImport::Unresolved | ||
432 | } else { | ||
433 | PartialResolvedImport::Resolved(res) | ||
434 | } | ||
424 | } else { | 435 | } else { |
425 | let res = self.def_map.resolve_path_fp_with_macro( | 436 | let res = self.def_map.resolve_path_fp_with_macro( |
426 | self.db, | 437 | self.db, |
@@ -774,7 +785,51 @@ impl DefCollector<'_> { | |||
774 | .collect(item_tree.top_level_items()); | 785 | .collect(item_tree.top_level_items()); |
775 | } | 786 | } |
776 | 787 | ||
777 | fn finish(self) -> CrateDefMap { | 788 | fn finish(mut self) -> CrateDefMap { |
789 | // Emit diagnostics for all remaining unresolved imports. | ||
790 | |||
791 | // We'd like to avoid emitting a diagnostics avalanche when some `extern crate` doesn't | ||
792 | // resolve. We first emit diagnostics for unresolved extern crates and collect the missing | ||
793 | // crate names. Then we emit diagnostics for unresolved imports, but only if the import | ||
794 | // doesn't start with an unresolved crate's name. Due to renaming and reexports, this is a | ||
795 | // heuristic, but it works in practice. | ||
796 | let mut diagnosed_extern_crates = FxHashSet::default(); | ||
797 | for directive in &self.unresolved_imports { | ||
798 | if let ImportSource::ExternCrate(krate) = directive.import.source { | ||
799 | let item_tree = self.db.item_tree(krate.file_id); | ||
800 | let extern_crate = &item_tree[krate.value]; | ||
801 | |||
802 | diagnosed_extern_crates.insert(extern_crate.name.clone()); | ||
803 | |||
804 | self.def_map.diagnostics.push(DefDiagnostic::unresolved_extern_crate( | ||
805 | directive.module_id, | ||
806 | InFile::new(krate.file_id, extern_crate.ast_id), | ||
807 | )); | ||
808 | } | ||
809 | } | ||
810 | |||
811 | for directive in &self.unresolved_imports { | ||
812 | if let ImportSource::Import(import) = &directive.import.source { | ||
813 | let item_tree = self.db.item_tree(import.file_id); | ||
814 | let import_data = &item_tree[import.value]; | ||
815 | |||
816 | match (import_data.path.segments.first(), &import_data.path.kind) { | ||
817 | (Some(krate), PathKind::Plain) | (Some(krate), PathKind::Abs) => { | ||
818 | if diagnosed_extern_crates.contains(krate) { | ||
819 | continue; | ||
820 | } | ||
821 | } | ||
822 | _ => {} | ||
823 | } | ||
824 | |||
825 | self.def_map.diagnostics.push(DefDiagnostic::unresolved_import( | ||
826 | directive.module_id, | ||
827 | InFile::new(import.file_id, import_data.ast_id), | ||
828 | import_data.index, | ||
829 | )); | ||
830 | } | ||
831 | } | ||
832 | |||
778 | self.def_map | 833 | self.def_map |
779 | } | 834 | } |
780 | } | 835 | } |
@@ -819,179 +874,184 @@ impl ModCollector<'_, '_> { | |||
819 | 874 | ||
820 | for &item in items { | 875 | for &item in items { |
821 | let attrs = self.item_tree.attrs(item.into()); | 876 | let attrs = self.item_tree.attrs(item.into()); |
822 | if self.is_cfg_enabled(attrs) { | 877 | if !self.is_cfg_enabled(attrs) { |
823 | let module = | 878 | continue; |
824 | ModuleId { krate: self.def_collector.def_map.krate, local_id: self.module_id }; | 879 | } |
825 | let container = ContainerId::ModuleId(module); | 880 | let module = |
826 | 881 | ModuleId { krate: self.def_collector.def_map.krate, local_id: self.module_id }; | |
827 | let mut def = None; | 882 | let container = ContainerId::ModuleId(module); |
828 | match item { | 883 | |
829 | ModItem::Mod(m) => self.collect_module(&self.item_tree[m], attrs), | 884 | let mut def = None; |
830 | ModItem::Import(import_id) => { | 885 | match item { |
831 | self.def_collector.unresolved_imports.push(ImportDirective { | 886 | ModItem::Mod(m) => self.collect_module(&self.item_tree[m], attrs), |
832 | module_id: self.module_id, | 887 | ModItem::Import(import_id) => { |
833 | import: Import::from_use(&self.item_tree, import_id), | 888 | self.def_collector.unresolved_imports.push(ImportDirective { |
834 | status: PartialResolvedImport::Unresolved, | 889 | module_id: self.module_id, |
835 | }) | 890 | import: Import::from_use( |
836 | } | 891 | &self.item_tree, |
837 | ModItem::ExternCrate(import_id) => { | 892 | InFile::new(self.file_id, import_id), |
838 | self.def_collector.unresolved_imports.push(ImportDirective { | 893 | ), |
839 | module_id: self.module_id, | 894 | status: PartialResolvedImport::Unresolved, |
840 | import: Import::from_extern_crate(&self.item_tree, import_id), | 895 | }) |
841 | status: PartialResolvedImport::Unresolved, | 896 | } |
842 | }) | 897 | ModItem::ExternCrate(import_id) => { |
843 | } | 898 | self.def_collector.unresolved_imports.push(ImportDirective { |
844 | ModItem::MacroCall(mac) => self.collect_macro(&self.item_tree[mac]), | 899 | module_id: self.module_id, |
845 | ModItem::Impl(imp) => { | 900 | import: Import::from_extern_crate( |
846 | let module = ModuleId { | 901 | &self.item_tree, |
847 | krate: self.def_collector.def_map.krate, | 902 | InFile::new(self.file_id, import_id), |
848 | local_id: self.module_id, | 903 | ), |
849 | }; | 904 | status: PartialResolvedImport::Unresolved, |
850 | let container = ContainerId::ModuleId(module); | 905 | }) |
851 | let impl_id = ImplLoc { container, id: ItemTreeId::new(self.file_id, imp) } | 906 | } |
852 | .intern(self.def_collector.db); | 907 | ModItem::MacroCall(mac) => self.collect_macro(&self.item_tree[mac]), |
853 | self.def_collector.def_map.modules[self.module_id] | 908 | ModItem::Impl(imp) => { |
854 | .scope | 909 | let module = ModuleId { |
855 | .define_impl(impl_id) | 910 | krate: self.def_collector.def_map.krate, |
856 | } | 911 | local_id: self.module_id, |
857 | ModItem::Function(id) => { | 912 | }; |
858 | let func = &self.item_tree[id]; | 913 | let container = ContainerId::ModuleId(module); |
859 | def = Some(DefData { | 914 | let impl_id = ImplLoc { container, id: ItemTreeId::new(self.file_id, imp) } |
860 | id: FunctionLoc { | 915 | .intern(self.def_collector.db); |
861 | container: container.into(), | 916 | self.def_collector.def_map.modules[self.module_id].scope.define_impl(impl_id) |
862 | id: ItemTreeId::new(self.file_id, id), | 917 | } |
863 | } | 918 | ModItem::Function(id) => { |
864 | .intern(self.def_collector.db) | 919 | let func = &self.item_tree[id]; |
865 | .into(), | 920 | def = Some(DefData { |
866 | name: &func.name, | 921 | id: FunctionLoc { |
867 | visibility: &self.item_tree[func.visibility], | 922 | container: container.into(), |
868 | has_constructor: false, | 923 | id: ItemTreeId::new(self.file_id, id), |
869 | }); | 924 | } |
870 | } | 925 | .intern(self.def_collector.db) |
871 | ModItem::Struct(id) => { | 926 | .into(), |
872 | let it = &self.item_tree[id]; | 927 | name: &func.name, |
873 | 928 | visibility: &self.item_tree[func.visibility], | |
874 | // FIXME: check attrs to see if this is an attribute macro invocation; | 929 | has_constructor: false, |
875 | // in which case we don't add the invocation, just a single attribute | 930 | }); |
876 | // macro invocation | 931 | } |
877 | self.collect_derives(attrs, it.ast_id.upcast()); | 932 | ModItem::Struct(id) => { |
878 | 933 | let it = &self.item_tree[id]; | |
879 | def = Some(DefData { | ||
880 | id: StructLoc { container, id: ItemTreeId::new(self.file_id, id) } | ||
881 | .intern(self.def_collector.db) | ||
882 | .into(), | ||
883 | name: &it.name, | ||
884 | visibility: &self.item_tree[it.visibility], | ||
885 | has_constructor: it.kind != StructDefKind::Record, | ||
886 | }); | ||
887 | } | ||
888 | ModItem::Union(id) => { | ||
889 | let it = &self.item_tree[id]; | ||
890 | 934 | ||
891 | // FIXME: check attrs to see if this is an attribute macro invocation; | 935 | // FIXME: check attrs to see if this is an attribute macro invocation; |
892 | // in which case we don't add the invocation, just a single attribute | 936 | // in which case we don't add the invocation, just a single attribute |
893 | // macro invocation | 937 | // macro invocation |
894 | self.collect_derives(attrs, it.ast_id.upcast()); | 938 | self.collect_derives(attrs, it.ast_id.upcast()); |
895 | 939 | ||
896 | def = Some(DefData { | 940 | def = Some(DefData { |
897 | id: UnionLoc { container, id: ItemTreeId::new(self.file_id, id) } | 941 | id: StructLoc { container, id: ItemTreeId::new(self.file_id, id) } |
898 | .intern(self.def_collector.db) | 942 | .intern(self.def_collector.db) |
899 | .into(), | 943 | .into(), |
900 | name: &it.name, | 944 | name: &it.name, |
901 | visibility: &self.item_tree[it.visibility], | 945 | visibility: &self.item_tree[it.visibility], |
902 | has_constructor: false, | 946 | has_constructor: it.kind != StructDefKind::Record, |
903 | }); | 947 | }); |
904 | } | 948 | } |
905 | ModItem::Enum(id) => { | 949 | ModItem::Union(id) => { |
906 | let it = &self.item_tree[id]; | 950 | let it = &self.item_tree[id]; |
907 | 951 | ||
908 | // FIXME: check attrs to see if this is an attribute macro invocation; | 952 | // FIXME: check attrs to see if this is an attribute macro invocation; |
909 | // in which case we don't add the invocation, just a single attribute | 953 | // in which case we don't add the invocation, just a single attribute |
910 | // macro invocation | 954 | // macro invocation |
911 | self.collect_derives(attrs, it.ast_id.upcast()); | 955 | self.collect_derives(attrs, it.ast_id.upcast()); |
912 | 956 | ||
913 | def = Some(DefData { | 957 | def = Some(DefData { |
914 | id: EnumLoc { container, id: ItemTreeId::new(self.file_id, id) } | 958 | id: UnionLoc { container, id: ItemTreeId::new(self.file_id, id) } |
915 | .intern(self.def_collector.db) | 959 | .intern(self.def_collector.db) |
916 | .into(), | 960 | .into(), |
917 | name: &it.name, | 961 | name: &it.name, |
918 | visibility: &self.item_tree[it.visibility], | 962 | visibility: &self.item_tree[it.visibility], |
919 | has_constructor: false, | 963 | has_constructor: false, |
920 | }); | 964 | }); |
921 | } | 965 | } |
922 | ModItem::Const(id) => { | 966 | ModItem::Enum(id) => { |
923 | let it = &self.item_tree[id]; | 967 | let it = &self.item_tree[id]; |
924 | |||
925 | if let Some(name) = &it.name { | ||
926 | def = Some(DefData { | ||
927 | id: ConstLoc { | ||
928 | container: container.into(), | ||
929 | id: ItemTreeId::new(self.file_id, id), | ||
930 | } | ||
931 | .intern(self.def_collector.db) | ||
932 | .into(), | ||
933 | name, | ||
934 | visibility: &self.item_tree[it.visibility], | ||
935 | has_constructor: false, | ||
936 | }); | ||
937 | } | ||
938 | } | ||
939 | ModItem::Static(id) => { | ||
940 | let it = &self.item_tree[id]; | ||
941 | 968 | ||
942 | def = Some(DefData { | 969 | // FIXME: check attrs to see if this is an attribute macro invocation; |
943 | id: StaticLoc { container, id: ItemTreeId::new(self.file_id, id) } | 970 | // in which case we don't add the invocation, just a single attribute |
944 | .intern(self.def_collector.db) | 971 | // macro invocation |
945 | .into(), | 972 | self.collect_derives(attrs, it.ast_id.upcast()); |
946 | name: &it.name, | ||
947 | visibility: &self.item_tree[it.visibility], | ||
948 | has_constructor: false, | ||
949 | }); | ||
950 | } | ||
951 | ModItem::Trait(id) => { | ||
952 | let it = &self.item_tree[id]; | ||
953 | 973 | ||
954 | def = Some(DefData { | 974 | def = Some(DefData { |
955 | id: TraitLoc { container, id: ItemTreeId::new(self.file_id, id) } | 975 | id: EnumLoc { container, id: ItemTreeId::new(self.file_id, id) } |
956 | .intern(self.def_collector.db) | 976 | .intern(self.def_collector.db) |
957 | .into(), | 977 | .into(), |
958 | name: &it.name, | 978 | name: &it.name, |
959 | visibility: &self.item_tree[it.visibility], | 979 | visibility: &self.item_tree[it.visibility], |
960 | has_constructor: false, | 980 | has_constructor: false, |
961 | }); | 981 | }); |
962 | } | 982 | } |
963 | ModItem::TypeAlias(id) => { | 983 | ModItem::Const(id) => { |
964 | let it = &self.item_tree[id]; | 984 | let it = &self.item_tree[id]; |
965 | 985 | ||
986 | if let Some(name) = &it.name { | ||
966 | def = Some(DefData { | 987 | def = Some(DefData { |
967 | id: TypeAliasLoc { | 988 | id: ConstLoc { |
968 | container: container.into(), | 989 | container: container.into(), |
969 | id: ItemTreeId::new(self.file_id, id), | 990 | id: ItemTreeId::new(self.file_id, id), |
970 | } | 991 | } |
971 | .intern(self.def_collector.db) | 992 | .intern(self.def_collector.db) |
972 | .into(), | 993 | .into(), |
973 | name: &it.name, | 994 | name, |
974 | visibility: &self.item_tree[it.visibility], | 995 | visibility: &self.item_tree[it.visibility], |
975 | has_constructor: false, | 996 | has_constructor: false, |
976 | }); | 997 | }); |
977 | } | 998 | } |
978 | } | 999 | } |
1000 | ModItem::Static(id) => { | ||
1001 | let it = &self.item_tree[id]; | ||
979 | 1002 | ||
980 | if let Some(DefData { id, name, visibility, has_constructor }) = def { | 1003 | def = Some(DefData { |
981 | self.def_collector.def_map.modules[self.module_id].scope.define_def(id); | 1004 | id: StaticLoc { container, id: ItemTreeId::new(self.file_id, id) } |
982 | let vis = self | 1005 | .intern(self.def_collector.db) |
983 | .def_collector | 1006 | .into(), |
984 | .def_map | 1007 | name: &it.name, |
985 | .resolve_visibility(self.def_collector.db, self.module_id, visibility) | 1008 | visibility: &self.item_tree[it.visibility], |
986 | .unwrap_or(Visibility::Public); | 1009 | has_constructor: false, |
987 | self.def_collector.update( | 1010 | }); |
988 | self.module_id, | 1011 | } |
989 | &[(Some(name.clone()), PerNs::from_def(id, vis, has_constructor))], | 1012 | ModItem::Trait(id) => { |
990 | vis, | 1013 | let it = &self.item_tree[id]; |
991 | ImportType::Named, | 1014 | |
992 | ) | 1015 | def = Some(DefData { |
1016 | id: TraitLoc { container, id: ItemTreeId::new(self.file_id, id) } | ||
1017 | .intern(self.def_collector.db) | ||
1018 | .into(), | ||
1019 | name: &it.name, | ||
1020 | visibility: &self.item_tree[it.visibility], | ||
1021 | has_constructor: false, | ||
1022 | }); | ||
1023 | } | ||
1024 | ModItem::TypeAlias(id) => { | ||
1025 | let it = &self.item_tree[id]; | ||
1026 | |||
1027 | def = Some(DefData { | ||
1028 | id: TypeAliasLoc { | ||
1029 | container: container.into(), | ||
1030 | id: ItemTreeId::new(self.file_id, id), | ||
1031 | } | ||
1032 | .intern(self.def_collector.db) | ||
1033 | .into(), | ||
1034 | name: &it.name, | ||
1035 | visibility: &self.item_tree[it.visibility], | ||
1036 | has_constructor: false, | ||
1037 | }); | ||
993 | } | 1038 | } |
994 | } | 1039 | } |
1040 | |||
1041 | if let Some(DefData { id, name, visibility, has_constructor }) = def { | ||
1042 | self.def_collector.def_map.modules[self.module_id].scope.define_def(id); | ||
1043 | let vis = self | ||
1044 | .def_collector | ||
1045 | .def_map | ||
1046 | .resolve_visibility(self.def_collector.db, self.module_id, visibility) | ||
1047 | .unwrap_or(Visibility::Public); | ||
1048 | self.def_collector.update( | ||
1049 | self.module_id, | ||
1050 | &[(Some(name.clone()), PerNs::from_def(id, vis, has_constructor))], | ||
1051 | vis, | ||
1052 | ImportType::Named, | ||
1053 | ) | ||
1054 | } | ||
995 | } | 1055 | } |
996 | } | 1056 | } |
997 | 1057 | ||
@@ -1051,13 +1111,11 @@ impl ModCollector<'_, '_> { | |||
1051 | self.import_all_legacy_macros(module_id); | 1111 | self.import_all_legacy_macros(module_id); |
1052 | } | 1112 | } |
1053 | } | 1113 | } |
1054 | Err(candidate) => self.def_collector.def_map.diagnostics.push( | 1114 | Err(candidate) => { |
1055 | DefDiagnostic::UnresolvedModule { | 1115 | self.def_collector.def_map.diagnostics.push( |
1056 | module: self.module_id, | 1116 | DefDiagnostic::unresolved_module(self.module_id, ast_id, candidate), |
1057 | declaration: ast_id, | 1117 | ); |
1058 | candidate, | 1118 | } |
1059 | }, | ||
1060 | ), | ||
1061 | }; | 1119 | }; |
1062 | } | 1120 | } |
1063 | } | 1121 | } |
diff --git a/crates/hir_def/src/nameres/tests.rs b/crates/hir_def/src/nameres/tests.rs index 5ca30dac9..11d84f808 100644 --- a/crates/hir_def/src/nameres/tests.rs +++ b/crates/hir_def/src/nameres/tests.rs | |||
@@ -2,6 +2,7 @@ mod globs; | |||
2 | mod incremental; | 2 | mod incremental; |
3 | mod macros; | 3 | mod macros; |
4 | mod mod_resolution; | 4 | mod mod_resolution; |
5 | mod diagnostics; | ||
5 | mod primitives; | 6 | mod primitives; |
6 | 7 | ||
7 | use std::sync::Arc; | 8 | use std::sync::Arc; |
diff --git a/crates/hir_def/src/nameres/tests/diagnostics.rs b/crates/hir_def/src/nameres/tests/diagnostics.rs new file mode 100644 index 000000000..576b813d2 --- /dev/null +++ b/crates/hir_def/src/nameres/tests/diagnostics.rs | |||
@@ -0,0 +1,131 @@ | |||
1 | use base_db::fixture::WithFixture; | ||
2 | use base_db::FileId; | ||
3 | use base_db::SourceDatabaseExt; | ||
4 | use hir_expand::db::AstDatabase; | ||
5 | use rustc_hash::FxHashMap; | ||
6 | use syntax::TextRange; | ||
7 | use syntax::TextSize; | ||
8 | |||
9 | use crate::test_db::TestDB; | ||
10 | |||
11 | fn check_diagnostics(ra_fixture: &str) { | ||
12 | let db: TestDB = TestDB::with_files(ra_fixture); | ||
13 | let annotations = db.extract_annotations(); | ||
14 | assert!(!annotations.is_empty()); | ||
15 | |||
16 | let mut actual: FxHashMap<FileId, Vec<(TextRange, String)>> = FxHashMap::default(); | ||
17 | db.diagnostics(|d| { | ||
18 | let src = d.display_source(); | ||
19 | let root = db.parse_or_expand(src.file_id).unwrap(); | ||
20 | // FIXME: macros... | ||
21 | let file_id = src.file_id.original_file(&db); | ||
22 | let range = src.value.to_node(&root).text_range(); | ||
23 | let message = d.message().to_owned(); | ||
24 | actual.entry(file_id).or_default().push((range, message)); | ||
25 | }); | ||
26 | |||
27 | for (file_id, diags) in actual.iter_mut() { | ||
28 | diags.sort_by_key(|it| it.0.start()); | ||
29 | let text = db.file_text(*file_id); | ||
30 | // For multiline spans, place them on line start | ||
31 | for (range, content) in diags { | ||
32 | if text[*range].contains('\n') { | ||
33 | *range = TextRange::new(range.start(), range.start() + TextSize::from(1)); | ||
34 | *content = format!("... {}", content); | ||
35 | } | ||
36 | } | ||
37 | } | ||
38 | |||
39 | assert_eq!(annotations, actual); | ||
40 | } | ||
41 | |||
42 | #[test] | ||
43 | fn unresolved_import() { | ||
44 | check_diagnostics( | ||
45 | r" | ||
46 | use does_exist; | ||
47 | use does_not_exist; | ||
48 | //^^^^^^^^^^^^^^ unresolved import | ||
49 | |||
50 | mod does_exist {} | ||
51 | ", | ||
52 | ); | ||
53 | } | ||
54 | |||
55 | #[test] | ||
56 | fn unresolved_import_in_use_tree() { | ||
57 | // Only the relevant part of a nested `use` item should be highlighted. | ||
58 | check_diagnostics( | ||
59 | r" | ||
60 | use does_exist::{Exists, DoesntExist}; | ||
61 | //^^^^^^^^^^^ unresolved import | ||
62 | |||
63 | use {does_not_exist::*, does_exist}; | ||
64 | //^^^^^^^^^^^^^^^^^ unresolved import | ||
65 | |||
66 | use does_not_exist::{ | ||
67 | a, | ||
68 | //^ unresolved import | ||
69 | b, | ||
70 | //^ unresolved import | ||
71 | c, | ||
72 | //^ unresolved import | ||
73 | }; | ||
74 | |||
75 | mod does_exist { | ||
76 | pub struct Exists; | ||
77 | } | ||
78 | ", | ||
79 | ); | ||
80 | } | ||
81 | |||
82 | #[test] | ||
83 | fn unresolved_extern_crate() { | ||
84 | check_diagnostics( | ||
85 | r" | ||
86 | //- /main.rs crate:main deps:core | ||
87 | extern crate core; | ||
88 | extern crate doesnotexist; | ||
89 | //^^^^^^^^^^^^^^^^^^^^^^^^^^ unresolved extern crate | ||
90 | //- /lib.rs crate:core | ||
91 | ", | ||
92 | ); | ||
93 | } | ||
94 | |||
95 | #[test] | ||
96 | fn dedup_unresolved_import_from_unresolved_crate() { | ||
97 | check_diagnostics( | ||
98 | r" | ||
99 | //- /main.rs crate:main | ||
100 | mod a { | ||
101 | extern crate doesnotexist; | ||
102 | //^^^^^^^^^^^^^^^^^^^^^^^^^^ unresolved extern crate | ||
103 | |||
104 | // Should not error, since we already errored for the missing crate. | ||
105 | use doesnotexist::{self, bla, *}; | ||
106 | |||
107 | use crate::doesnotexist; | ||
108 | //^^^^^^^^^^^^^^^^^^^ unresolved import | ||
109 | } | ||
110 | |||
111 | mod m { | ||
112 | use super::doesnotexist; | ||
113 | //^^^^^^^^^^^^^^^^^^^ unresolved import | ||
114 | } | ||
115 | ", | ||
116 | ); | ||
117 | } | ||
118 | |||
119 | #[test] | ||
120 | fn unresolved_module() { | ||
121 | check_diagnostics( | ||
122 | r" | ||
123 | //- /lib.rs | ||
124 | mod foo; | ||
125 | mod bar; | ||
126 | //^^^^^^^^ unresolved module | ||
127 | mod baz {} | ||
128 | //- /foo.rs | ||
129 | ", | ||
130 | ); | ||
131 | } | ||
diff --git a/crates/hir_def/src/nameres/tests/mod_resolution.rs b/crates/hir_def/src/nameres/tests/mod_resolution.rs index 1f619787e..f93337a6e 100644 --- a/crates/hir_def/src/nameres/tests/mod_resolution.rs +++ b/crates/hir_def/src/nameres/tests/mod_resolution.rs | |||
@@ -672,42 +672,6 @@ pub struct Baz; | |||
672 | } | 672 | } |
673 | 673 | ||
674 | #[test] | 674 | #[test] |
675 | fn unresolved_module_diagnostics() { | ||
676 | let db = TestDB::with_files( | ||
677 | r" | ||
678 | //- /lib.rs | ||
679 | mod foo; | ||
680 | mod bar; | ||
681 | mod baz {} | ||
682 | //- /foo.rs | ||
683 | ", | ||
684 | ); | ||
685 | let krate = db.test_crate(); | ||
686 | |||
687 | let crate_def_map = db.crate_def_map(krate); | ||
688 | |||
689 | expect![[r#" | ||
690 | [ | ||
691 | UnresolvedModule { | ||
692 | module: Idx::<ModuleData>(0), | ||
693 | declaration: InFile { | ||
694 | file_id: HirFileId( | ||
695 | FileId( | ||
696 | FileId( | ||
697 | 0, | ||
698 | ), | ||
699 | ), | ||
700 | ), | ||
701 | value: FileAstId::<syntax::ast::generated::nodes::Module>(1), | ||
702 | }, | ||
703 | candidate: "bar.rs", | ||
704 | }, | ||
705 | ] | ||
706 | "#]] | ||
707 | .assert_debug_eq(&crate_def_map.diagnostics); | ||
708 | } | ||
709 | |||
710 | #[test] | ||
711 | fn module_resolution_decl_inside_module_in_non_crate_root_2() { | 675 | fn module_resolution_decl_inside_module_in_non_crate_root_2() { |
712 | check( | 676 | check( |
713 | r#" | 677 | r#" |
diff --git a/crates/hir_def/src/path.rs b/crates/hir_def/src/path.rs index 99395667d..734310458 100644 --- a/crates/hir_def/src/path.rs +++ b/crates/hir_def/src/path.rs | |||
@@ -56,10 +56,6 @@ impl ModPath { | |||
56 | ModPath { kind, segments } | 56 | ModPath { kind, segments } |
57 | } | 57 | } |
58 | 58 | ||
59 | pub(crate) fn from_name_ref(name_ref: &ast::NameRef) -> ModPath { | ||
60 | name_ref.as_name().into() | ||
61 | } | ||
62 | |||
63 | /// Converts an `tt::Ident` into a single-identifier `Path`. | 59 | /// Converts an `tt::Ident` into a single-identifier `Path`. |
64 | pub(crate) fn from_tt_ident(ident: &tt::Ident) -> ModPath { | 60 | pub(crate) fn from_tt_ident(ident: &tt::Ident) -> ModPath { |
65 | ident.as_name().into() | 61 | ident.as_name().into() |
diff --git a/crates/hir_def/src/test_db.rs b/crates/hir_def/src/test_db.rs index 42a762936..fb1d3c974 100644 --- a/crates/hir_def/src/test_db.rs +++ b/crates/hir_def/src/test_db.rs | |||
@@ -5,9 +5,15 @@ use std::{ | |||
5 | sync::{Arc, Mutex}, | 5 | sync::{Arc, Mutex}, |
6 | }; | 6 | }; |
7 | 7 | ||
8 | use base_db::SourceDatabase; | ||
8 | use base_db::{salsa, CrateId, FileId, FileLoader, FileLoaderDelegate, Upcast}; | 9 | use base_db::{salsa, CrateId, FileId, FileLoader, FileLoaderDelegate, Upcast}; |
9 | use hir_expand::db::AstDatabase; | 10 | use hir_expand::db::AstDatabase; |
11 | use hir_expand::diagnostics::Diagnostic; | ||
12 | use hir_expand::diagnostics::DiagnosticSinkBuilder; | ||
13 | use rustc_hash::FxHashMap; | ||
10 | use rustc_hash::FxHashSet; | 14 | use rustc_hash::FxHashSet; |
15 | use syntax::TextRange; | ||
16 | use test_utils::extract_annotations; | ||
11 | 17 | ||
12 | use crate::db::DefDatabase; | 18 | use crate::db::DefDatabase; |
13 | 19 | ||
@@ -98,4 +104,40 @@ impl TestDB { | |||
98 | }) | 104 | }) |
99 | .collect() | 105 | .collect() |
100 | } | 106 | } |
107 | |||
108 | pub fn extract_annotations(&self) -> FxHashMap<FileId, Vec<(TextRange, String)>> { | ||
109 | let mut files = Vec::new(); | ||
110 | let crate_graph = self.crate_graph(); | ||
111 | for krate in crate_graph.iter() { | ||
112 | let crate_def_map = self.crate_def_map(krate); | ||
113 | for (module_id, _) in crate_def_map.modules.iter() { | ||
114 | let file_id = crate_def_map[module_id].origin.file_id(); | ||
115 | files.extend(file_id) | ||
116 | } | ||
117 | } | ||
118 | assert!(!files.is_empty()); | ||
119 | files | ||
120 | .into_iter() | ||
121 | .filter_map(|file_id| { | ||
122 | let text = self.file_text(file_id); | ||
123 | let annotations = extract_annotations(&text); | ||
124 | if annotations.is_empty() { | ||
125 | return None; | ||
126 | } | ||
127 | Some((file_id, annotations)) | ||
128 | }) | ||
129 | .collect() | ||
130 | } | ||
131 | |||
132 | pub fn diagnostics<F: FnMut(&dyn Diagnostic)>(&self, mut cb: F) { | ||
133 | let crate_graph = self.crate_graph(); | ||
134 | for krate in crate_graph.iter() { | ||
135 | let crate_def_map = self.crate_def_map(krate); | ||
136 | |||
137 | let mut sink = DiagnosticSinkBuilder::new().build(&mut cb); | ||
138 | for (module_id, _) in crate_def_map.modules.iter() { | ||
139 | crate_def_map.add_diagnostics(self, module_id, &mut sink); | ||
140 | } | ||
141 | } | ||
142 | } | ||
101 | } | 143 | } |
diff --git a/crates/hir_expand/src/db.rs b/crates/hir_expand/src/db.rs index 710694a34..b591130ca 100644 --- a/crates/hir_expand/src/db.rs +++ b/crates/hir_expand/src/db.rs | |||
@@ -143,7 +143,7 @@ pub(crate) fn macro_def( | |||
143 | Some(Arc::new((TokenExpander::BuiltinDerive(expander), mbe::TokenMap::default()))) | 143 | Some(Arc::new((TokenExpander::BuiltinDerive(expander), mbe::TokenMap::default()))) |
144 | } | 144 | } |
145 | MacroDefKind::BuiltInEager(_) => None, | 145 | MacroDefKind::BuiltInEager(_) => None, |
146 | MacroDefKind::CustomDerive(expander) => { | 146 | MacroDefKind::ProcMacro(expander) => { |
147 | Some(Arc::new((TokenExpander::ProcMacro(expander), mbe::TokenMap::default()))) | 147 | Some(Arc::new((TokenExpander::ProcMacro(expander), mbe::TokenMap::default()))) |
148 | } | 148 | } |
149 | } | 149 | } |
@@ -249,7 +249,7 @@ pub(crate) fn expand_proc_macro( | |||
249 | }; | 249 | }; |
250 | 250 | ||
251 | let expander = match loc.def.kind { | 251 | let expander = match loc.def.kind { |
252 | MacroDefKind::CustomDerive(expander) => expander, | 252 | MacroDefKind::ProcMacro(expander) => expander, |
253 | _ => unreachable!(), | 253 | _ => unreachable!(), |
254 | }; | 254 | }; |
255 | 255 | ||
diff --git a/crates/hir_expand/src/eager.rs b/crates/hir_expand/src/eager.rs index 10c45646f..2f37d7189 100644 --- a/crates/hir_expand/src/eager.rs +++ b/crates/hir_expand/src/eager.rs | |||
@@ -129,7 +129,7 @@ fn eager_macro_recur( | |||
129 | MacroDefKind::Declarative | 129 | MacroDefKind::Declarative |
130 | | MacroDefKind::BuiltIn(_) | 130 | | MacroDefKind::BuiltIn(_) |
131 | | MacroDefKind::BuiltInDerive(_) | 131 | | MacroDefKind::BuiltInDerive(_) |
132 | | MacroDefKind::CustomDerive(_) => { | 132 | | MacroDefKind::ProcMacro(_) => { |
133 | let expanded = lazy_expand(db, &def, curr.with_value(child.clone()), krate)?; | 133 | let expanded = lazy_expand(db, &def, curr.with_value(child.clone()), krate)?; |
134 | // replace macro inside | 134 | // replace macro inside |
135 | eager_macro_recur(db, expanded, krate, macro_resolver)? | 135 | eager_macro_recur(db, expanded, krate, macro_resolver)? |
diff --git a/crates/hir_expand/src/hygiene.rs b/crates/hir_expand/src/hygiene.rs index 845e9cbc1..d383b968d 100644 --- a/crates/hir_expand/src/hygiene.rs +++ b/crates/hir_expand/src/hygiene.rs | |||
@@ -33,7 +33,7 @@ impl Hygiene { | |||
33 | MacroDefKind::BuiltIn(_) => (None, false), | 33 | MacroDefKind::BuiltIn(_) => (None, false), |
34 | MacroDefKind::BuiltInDerive(_) => (None, false), | 34 | MacroDefKind::BuiltInDerive(_) => (None, false), |
35 | MacroDefKind::BuiltInEager(_) => (None, false), | 35 | MacroDefKind::BuiltInEager(_) => (None, false), |
36 | MacroDefKind::CustomDerive(_) => (None, false), | 36 | MacroDefKind::ProcMacro(_) => (None, false), |
37 | } | 37 | } |
38 | } | 38 | } |
39 | MacroCallId::EagerMacro(_id) => (None, false), | 39 | MacroCallId::EagerMacro(_id) => (None, false), |
diff --git a/crates/hir_expand/src/lib.rs b/crates/hir_expand/src/lib.rs index 2be15e841..17f1178ed 100644 --- a/crates/hir_expand/src/lib.rs +++ b/crates/hir_expand/src/lib.rs | |||
@@ -246,7 +246,7 @@ pub enum MacroDefKind { | |||
246 | // FIXME: maybe just Builtin and rename BuiltinFnLikeExpander to BuiltinExpander | 246 | // FIXME: maybe just Builtin and rename BuiltinFnLikeExpander to BuiltinExpander |
247 | BuiltInDerive(BuiltinDeriveExpander), | 247 | BuiltInDerive(BuiltinDeriveExpander), |
248 | BuiltInEager(EagerExpander), | 248 | BuiltInEager(EagerExpander), |
249 | CustomDerive(ProcMacroExpander), | 249 | ProcMacro(ProcMacroExpander), |
250 | } | 250 | } |
251 | 251 | ||
252 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] | 252 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] |
diff --git a/crates/ide/src/completion/completion_context.rs b/crates/ide/src/completion/completion_context.rs index 161f59c1e..671b13328 100644 --- a/crates/ide/src/completion/completion_context.rs +++ b/crates/ide/src/completion/completion_context.rs | |||
@@ -1,7 +1,7 @@ | |||
1 | //! FIXME: write short doc here | 1 | //! FIXME: write short doc here |
2 | 2 | ||
3 | use base_db::SourceDatabase; | 3 | use base_db::SourceDatabase; |
4 | use hir::{Semantics, SemanticsScope, Type}; | 4 | use hir::{Local, ScopeDef, Semantics, SemanticsScope, Type}; |
5 | use ide_db::RootDatabase; | 5 | use ide_db::RootDatabase; |
6 | use syntax::{ | 6 | use syntax::{ |
7 | algo::{find_covering_element, find_node_at_offset}, | 7 | algo::{find_covering_element, find_node_at_offset}, |
@@ -91,6 +91,7 @@ pub(crate) struct CompletionContext<'a> { | |||
91 | pub(super) impl_as_prev_sibling: bool, | 91 | pub(super) impl_as_prev_sibling: bool, |
92 | pub(super) is_match_arm: bool, | 92 | pub(super) is_match_arm: bool, |
93 | pub(super) has_item_list_or_source_file_parent: bool, | 93 | pub(super) has_item_list_or_source_file_parent: bool, |
94 | pub(super) locals: Vec<(String, Local)>, | ||
94 | } | 95 | } |
95 | 96 | ||
96 | impl<'a> CompletionContext<'a> { | 97 | impl<'a> CompletionContext<'a> { |
@@ -119,6 +120,12 @@ impl<'a> CompletionContext<'a> { | |||
119 | original_file.syntax().token_at_offset(position.offset).left_biased()?; | 120 | original_file.syntax().token_at_offset(position.offset).left_biased()?; |
120 | let token = sema.descend_into_macros(original_token.clone()); | 121 | let token = sema.descend_into_macros(original_token.clone()); |
121 | let scope = sema.scope_at_offset(&token.parent(), position.offset); | 122 | let scope = sema.scope_at_offset(&token.parent(), position.offset); |
123 | let mut locals = vec![]; | ||
124 | scope.process_all_names(&mut |name, scope| { | ||
125 | if let ScopeDef::Local(local) = scope { | ||
126 | locals.push((name.to_string(), local)); | ||
127 | } | ||
128 | }); | ||
122 | let mut ctx = CompletionContext { | 129 | let mut ctx = CompletionContext { |
123 | sema, | 130 | sema, |
124 | scope, | 131 | scope, |
@@ -167,6 +174,7 @@ impl<'a> CompletionContext<'a> { | |||
167 | if_is_prev: false, | 174 | if_is_prev: false, |
168 | is_match_arm: false, | 175 | is_match_arm: false, |
169 | has_item_list_or_source_file_parent: false, | 176 | has_item_list_or_source_file_parent: false, |
177 | locals, | ||
170 | }; | 178 | }; |
171 | 179 | ||
172 | let mut original_file = original_file.syntax().clone(); | 180 | let mut original_file = original_file.syntax().clone(); |
diff --git a/crates/ide/src/completion/presentation.rs b/crates/ide/src/completion/presentation.rs index 24c507f9b..987cbfa7a 100644 --- a/crates/ide/src/completion/presentation.rs +++ b/crates/ide/src/completion/presentation.rs | |||
@@ -191,6 +191,17 @@ impl Completions { | |||
191 | func: hir::Function, | 191 | func: hir::Function, |
192 | local_name: Option<String>, | 192 | local_name: Option<String>, |
193 | ) { | 193 | ) { |
194 | fn add_arg(arg: &str, ty: &Type, ctx: &CompletionContext) -> String { | ||
195 | if let Some(derefed_ty) = ty.remove_ref() { | ||
196 | for (name, local) in ctx.locals.iter() { | ||
197 | if name == arg && local.ty(ctx.db) == derefed_ty { | ||
198 | return (if ty.is_mutable_reference() { "&mut " } else { "&" }).to_string() | ||
199 | + &arg.to_string(); | ||
200 | } | ||
201 | } | ||
202 | } | ||
203 | arg.to_string() | ||
204 | }; | ||
194 | let name = local_name.unwrap_or_else(|| func.name(ctx.db).to_string()); | 205 | let name = local_name.unwrap_or_else(|| func.name(ctx.db).to_string()); |
195 | let ast_node = func.source(ctx.db).value; | 206 | let ast_node = func.source(ctx.db).value; |
196 | 207 | ||
@@ -205,12 +216,20 @@ impl Completions { | |||
205 | .set_deprecated(is_deprecated(func, ctx.db)) | 216 | .set_deprecated(is_deprecated(func, ctx.db)) |
206 | .detail(function_declaration(&ast_node)); | 217 | .detail(function_declaration(&ast_node)); |
207 | 218 | ||
219 | let params_ty = func.params(ctx.db); | ||
208 | let params = ast_node | 220 | let params = ast_node |
209 | .param_list() | 221 | .param_list() |
210 | .into_iter() | 222 | .into_iter() |
211 | .flat_map(|it| it.params()) | 223 | .flat_map(|it| it.params()) |
212 | .flat_map(|it| it.pat()) | 224 | .zip(params_ty) |
213 | .map(|pat| pat.to_string().trim_start_matches('_').into()) | 225 | .flat_map(|(it, param_ty)| { |
226 | if let Some(pat) = it.pat() { | ||
227 | let name = pat.to_string(); | ||
228 | let arg = name.trim_start_matches("mut ").trim_start_matches('_'); | ||
229 | return Some(add_arg(arg, param_ty.ty(), ctx)); | ||
230 | } | ||
231 | None | ||
232 | }) | ||
214 | .collect(); | 233 | .collect(); |
215 | 234 | ||
216 | builder = builder.add_call_parens(ctx, name, Params::Named(params)); | 235 | builder = builder.add_call_parens(ctx, name, Params::Named(params)); |
@@ -864,6 +883,106 @@ fn main() { foo(${1:foo}, ${2:bar}, ${3:ho_ge_})$0 } | |||
864 | } | 883 | } |
865 | 884 | ||
866 | #[test] | 885 | #[test] |
886 | fn insert_ref_when_matching_local_in_scope() { | ||
887 | check_edit( | ||
888 | "ref_arg", | ||
889 | r#" | ||
890 | struct Foo {} | ||
891 | fn ref_arg(x: &Foo) {} | ||
892 | fn main() { | ||
893 | let x = Foo {}; | ||
894 | ref_ar<|> | ||
895 | } | ||
896 | "#, | ||
897 | r#" | ||
898 | struct Foo {} | ||
899 | fn ref_arg(x: &Foo) {} | ||
900 | fn main() { | ||
901 | let x = Foo {}; | ||
902 | ref_arg(${1:&x})$0 | ||
903 | } | ||
904 | "#, | ||
905 | ); | ||
906 | } | ||
907 | |||
908 | #[test] | ||
909 | fn insert_mut_ref_when_matching_local_in_scope() { | ||
910 | check_edit( | ||
911 | "ref_arg", | ||
912 | r#" | ||
913 | struct Foo {} | ||
914 | fn ref_arg(x: &mut Foo) {} | ||
915 | fn main() { | ||
916 | let x = Foo {}; | ||
917 | ref_ar<|> | ||
918 | } | ||
919 | "#, | ||
920 | r#" | ||
921 | struct Foo {} | ||
922 | fn ref_arg(x: &mut Foo) {} | ||
923 | fn main() { | ||
924 | let x = Foo {}; | ||
925 | ref_arg(${1:&mut x})$0 | ||
926 | } | ||
927 | "#, | ||
928 | ); | ||
929 | } | ||
930 | |||
931 | #[test] | ||
932 | fn insert_ref_when_matching_local_in_scope_for_method() { | ||
933 | check_edit( | ||
934 | "apply_foo", | ||
935 | r#" | ||
936 | struct Foo {} | ||
937 | struct Bar {} | ||
938 | impl Bar { | ||
939 | fn apply_foo(&self, x: &Foo) {} | ||
940 | } | ||
941 | |||
942 | fn main() { | ||
943 | let x = Foo {}; | ||
944 | let y = Bar {}; | ||
945 | y.<|> | ||
946 | } | ||
947 | "#, | ||
948 | r#" | ||
949 | struct Foo {} | ||
950 | struct Bar {} | ||
951 | impl Bar { | ||
952 | fn apply_foo(&self, x: &Foo) {} | ||
953 | } | ||
954 | |||
955 | fn main() { | ||
956 | let x = Foo {}; | ||
957 | let y = Bar {}; | ||
958 | y.apply_foo(${1:&x})$0 | ||
959 | } | ||
960 | "#, | ||
961 | ); | ||
962 | } | ||
963 | |||
964 | #[test] | ||
965 | fn trim_mut_keyword_in_func_completion() { | ||
966 | check_edit( | ||
967 | "take_mutably", | ||
968 | r#" | ||
969 | fn take_mutably(mut x: &i32) {} | ||
970 | |||
971 | fn main() { | ||
972 | take_m<|> | ||
973 | } | ||
974 | "#, | ||
975 | r#" | ||
976 | fn take_mutably(mut x: &i32) {} | ||
977 | |||
978 | fn main() { | ||
979 | take_mutably(${1:x})$0 | ||
980 | } | ||
981 | "#, | ||
982 | ); | ||
983 | } | ||
984 | |||
985 | #[test] | ||
867 | fn inserts_parens_for_tuple_enums() { | 986 | fn inserts_parens_for_tuple_enums() { |
868 | mark::check!(inserts_parens_for_tuple_enums); | 987 | mark::check!(inserts_parens_for_tuple_enums); |
869 | check_edit( | 988 | check_edit( |
diff --git a/crates/ide/src/diagnostics.rs b/crates/ide/src/diagnostics.rs index b2b972b02..dc815a483 100644 --- a/crates/ide/src/diagnostics.rs +++ b/crates/ide/src/diagnostics.rs | |||
@@ -622,13 +622,65 @@ pub struct Foo { pub a: i32, pub b: i32 } | |||
622 | r#" | 622 | r#" |
623 | use a; | 623 | use a; |
624 | use a::{c, d::e}; | 624 | use a::{c, d::e}; |
625 | |||
626 | mod a { | ||
627 | mod c {} | ||
628 | mod d { | ||
629 | mod e {} | ||
630 | } | ||
631 | } | ||
625 | "#, | 632 | "#, |
626 | ); | 633 | ); |
627 | check_fix(r#"use {<|>b};"#, r#"use b;"#); | 634 | check_fix( |
628 | check_fix(r#"use {b<|>};"#, r#"use b;"#); | 635 | r" |
629 | check_fix(r#"use a::{c<|>};"#, r#"use a::c;"#); | 636 | mod b {} |
630 | check_fix(r#"use a::{self<|>};"#, r#"use a;"#); | 637 | use {<|>b}; |
631 | check_fix(r#"use a::{c, d::{e<|>}};"#, r#"use a::{c, d::e};"#); | 638 | ", |
639 | r" | ||
640 | mod b {} | ||
641 | use b; | ||
642 | ", | ||
643 | ); | ||
644 | check_fix( | ||
645 | r" | ||
646 | mod b {} | ||
647 | use {b<|>}; | ||
648 | ", | ||
649 | r" | ||
650 | mod b {} | ||
651 | use b; | ||
652 | ", | ||
653 | ); | ||
654 | check_fix( | ||
655 | r" | ||
656 | mod a { mod c {} } | ||
657 | use a::{c<|>}; | ||
658 | ", | ||
659 | r" | ||
660 | mod a { mod c {} } | ||
661 | use a::c; | ||
662 | ", | ||
663 | ); | ||
664 | check_fix( | ||
665 | r" | ||
666 | mod a {} | ||
667 | use a::{self<|>}; | ||
668 | ", | ||
669 | r" | ||
670 | mod a {} | ||
671 | use a; | ||
672 | ", | ||
673 | ); | ||
674 | check_fix( | ||
675 | r" | ||
676 | mod a { mod c {} mod d { mod e {} } } | ||
677 | use a::{c, d::{e<|>}}; | ||
678 | ", | ||
679 | r" | ||
680 | mod a { mod c {} mod d { mod e {} } } | ||
681 | use a::{c, d::e}; | ||
682 | ", | ||
683 | ); | ||
632 | } | 684 | } |
633 | 685 | ||
634 | #[test] | 686 | #[test] |
diff --git a/crates/project_model/src/lib.rs b/crates/project_model/src/lib.rs index 288c39e49..258f60e28 100644 --- a/crates/project_model/src/lib.rs +++ b/crates/project_model/src/lib.rs | |||
@@ -308,7 +308,13 @@ impl ProjectWorkspace { | |||
308 | .crates() | 308 | .crates() |
309 | .filter_map(|(crate_id, krate)| { | 309 | .filter_map(|(crate_id, krate)| { |
310 | let file_path = &krate.root_module; | 310 | let file_path = &krate.root_module; |
311 | let file_id = load(&file_path)?; | 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 | }; | ||
312 | 318 | ||
313 | let env = krate.env.clone().into_iter().collect(); | 319 | let env = krate.env.clone().into_iter().collect(); |
314 | let proc_macro = krate | 320 | let proc_macro = krate |
diff --git a/crates/project_model/src/project_json.rs b/crates/project_model/src/project_json.rs index 545f254aa..a6895ecdd 100644 --- a/crates/project_model/src/project_json.rs +++ b/crates/project_model/src/project_json.rs | |||
@@ -13,7 +13,7 @@ use crate::cfg_flag::CfgFlag; | |||
13 | #[derive(Clone, Debug, Eq, PartialEq)] | 13 | #[derive(Clone, Debug, Eq, PartialEq)] |
14 | pub struct ProjectJson { | 14 | pub struct ProjectJson { |
15 | pub(crate) sysroot_src: Option<AbsPathBuf>, | 15 | pub(crate) sysroot_src: Option<AbsPathBuf>, |
16 | project_root: Option<AbsPathBuf>, | 16 | project_root: AbsPathBuf, |
17 | crates: Vec<Crate>, | 17 | crates: Vec<Crate>, |
18 | } | 18 | } |
19 | 19 | ||
@@ -34,10 +34,17 @@ pub struct Crate { | |||
34 | } | 34 | } |
35 | 35 | ||
36 | impl ProjectJson { | 36 | impl ProjectJson { |
37 | /// Create a new ProjectJson instance. | ||
38 | /// | ||
39 | /// # Arguments | ||
40 | /// | ||
41 | /// * `base` - The path to the workspace root (i.e. the folder containing `rust-project.json`) | ||
42 | /// * `data` - The parsed contents of `rust-project.json`, or project json that's passed via | ||
43 | /// configuration. | ||
37 | pub fn new(base: &AbsPath, data: ProjectJsonData) -> ProjectJson { | 44 | pub fn new(base: &AbsPath, data: ProjectJsonData) -> ProjectJson { |
38 | ProjectJson { | 45 | ProjectJson { |
39 | sysroot_src: data.sysroot_src.map(|it| base.join(it)), | 46 | sysroot_src: data.sysroot_src.map(|it| base.join(it)), |
40 | project_root: base.parent().map(AbsPath::to_path_buf), | 47 | project_root: base.to_path_buf(), |
41 | crates: data | 48 | crates: data |
42 | .crates | 49 | .crates |
43 | .into_iter() | 50 | .into_iter() |
@@ -85,17 +92,17 @@ impl ProjectJson { | |||
85 | .collect::<Vec<_>>(), | 92 | .collect::<Vec<_>>(), |
86 | } | 93 | } |
87 | } | 94 | } |
95 | /// Returns the number of crates in the project. | ||
88 | pub fn n_crates(&self) -> usize { | 96 | pub fn n_crates(&self) -> usize { |
89 | self.crates.len() | 97 | self.crates.len() |
90 | } | 98 | } |
99 | /// Returns an iterator over the crates in the project. | ||
91 | pub fn crates(&self) -> impl Iterator<Item = (CrateId, &Crate)> + '_ { | 100 | pub fn crates(&self) -> impl Iterator<Item = (CrateId, &Crate)> + '_ { |
92 | self.crates.iter().enumerate().map(|(idx, krate)| (CrateId(idx as u32), krate)) | 101 | self.crates.iter().enumerate().map(|(idx, krate)| (CrateId(idx as u32), krate)) |
93 | } | 102 | } |
94 | pub fn path(&self) -> Option<&AbsPath> { | 103 | /// Returns the path to the project's root folder. |
95 | match &self.project_root { | 104 | pub fn path(&self) -> &AbsPath { |
96 | Some(p) => Some(p.as_path()), | 105 | &self.project_root |
97 | None => None, | ||
98 | } | ||
99 | } | 106 | } |
100 | } | 107 | } |
101 | 108 | ||
diff --git a/crates/rust-analyzer/Cargo.toml b/crates/rust-analyzer/Cargo.toml index 8db0b0d72..631ffc4a7 100644 --- a/crates/rust-analyzer/Cargo.toml +++ b/crates/rust-analyzer/Cargo.toml | |||
@@ -21,7 +21,7 @@ env_logger = { version = "0.7.1", default-features = false } | |||
21 | itertools = "0.9.0" | 21 | itertools = "0.9.0" |
22 | jod-thread = "0.1.0" | 22 | jod-thread = "0.1.0" |
23 | log = "0.4.8" | 23 | log = "0.4.8" |
24 | lsp-types = { version = "0.80.0", features = ["proposed"] } | 24 | lsp-types = { version = "0.82.0", features = ["proposed"] } |
25 | parking_lot = "0.11.0" | 25 | parking_lot = "0.11.0" |
26 | pico-args = "0.3.1" | 26 | pico-args = "0.3.1" |
27 | oorandom = "11.1.2" | 27 | oorandom = "11.1.2" |
diff --git a/crates/rust-analyzer/src/bin/main.rs b/crates/rust-analyzer/src/bin/main.rs index ba4402ade..97b246a32 100644 --- a/crates/rust-analyzer/src/bin/main.rs +++ b/crates/rust-analyzer/src/bin/main.rs | |||
@@ -134,6 +134,10 @@ fn run_server() -> Result<()> { | |||
134 | 134 | ||
135 | let discovered = ProjectManifest::discover_all(&workspace_roots); | 135 | let discovered = ProjectManifest::discover_all(&workspace_roots); |
136 | log::info!("discovered projects: {:?}", discovered); | 136 | log::info!("discovered projects: {:?}", discovered); |
137 | if discovered.is_empty() { | ||
138 | log::error!("failed to find any projects in {:?}", workspace_roots); | ||
139 | } | ||
140 | |||
137 | config.linked_projects = discovered.into_iter().map(LinkedProject::from).collect(); | 141 | config.linked_projects = discovered.into_iter().map(LinkedProject::from).collect(); |
138 | } | 142 | } |
139 | 143 | ||
diff --git a/crates/rust-analyzer/src/caps.rs b/crates/rust-analyzer/src/caps.rs index de4bc2813..c589afeaf 100644 --- a/crates/rust-analyzer/src/caps.rs +++ b/crates/rust-analyzer/src/caps.rs | |||
@@ -5,7 +5,7 @@ use lsp_types::{ | |||
5 | CallHierarchyServerCapability, ClientCapabilities, CodeActionKind, CodeActionOptions, | 5 | CallHierarchyServerCapability, ClientCapabilities, CodeActionKind, CodeActionOptions, |
6 | CodeActionProviderCapability, CodeLensOptions, CompletionOptions, | 6 | CodeActionProviderCapability, CodeLensOptions, CompletionOptions, |
7 | DocumentOnTypeFormattingOptions, FoldingRangeProviderCapability, HoverProviderCapability, | 7 | DocumentOnTypeFormattingOptions, FoldingRangeProviderCapability, HoverProviderCapability, |
8 | ImplementationProviderCapability, RenameOptions, RenameProviderCapability, SaveOptions, | 8 | ImplementationProviderCapability, OneOf, RenameOptions, SaveOptions, |
9 | SelectionRangeProviderCapability, SemanticTokensFullOptions, SemanticTokensLegend, | 9 | SelectionRangeProviderCapability, SemanticTokensFullOptions, SemanticTokensLegend, |
10 | SemanticTokensOptions, ServerCapabilities, SignatureHelpOptions, TextDocumentSyncCapability, | 10 | SemanticTokensOptions, ServerCapabilities, SignatureHelpOptions, TextDocumentSyncCapability, |
11 | TextDocumentSyncKind, TextDocumentSyncOptions, TypeDefinitionProviderCapability, | 11 | TextDocumentSyncKind, TextDocumentSyncOptions, TypeDefinitionProviderCapability, |
@@ -42,16 +42,16 @@ pub fn server_capabilities(client_caps: &ClientCapabilities) -> ServerCapabiliti | |||
42 | work_done_progress_options: WorkDoneProgressOptions { work_done_progress: None }, | 42 | work_done_progress_options: WorkDoneProgressOptions { work_done_progress: None }, |
43 | }), | 43 | }), |
44 | declaration_provider: None, | 44 | declaration_provider: None, |
45 | definition_provider: Some(true), | 45 | definition_provider: Some(OneOf::Left(true)), |
46 | type_definition_provider: Some(TypeDefinitionProviderCapability::Simple(true)), | 46 | type_definition_provider: Some(TypeDefinitionProviderCapability::Simple(true)), |
47 | implementation_provider: Some(ImplementationProviderCapability::Simple(true)), | 47 | implementation_provider: Some(ImplementationProviderCapability::Simple(true)), |
48 | references_provider: Some(true), | 48 | references_provider: Some(OneOf::Left(true)), |
49 | document_highlight_provider: Some(true), | 49 | document_highlight_provider: Some(OneOf::Left(true)), |
50 | document_symbol_provider: Some(true), | 50 | document_symbol_provider: Some(OneOf::Left(true)), |
51 | workspace_symbol_provider: Some(true), | 51 | workspace_symbol_provider: Some(true), |
52 | code_action_provider: Some(code_action_provider), | 52 | code_action_provider: Some(code_action_provider), |
53 | code_lens_provider: Some(CodeLensOptions { resolve_provider: Some(true) }), | 53 | code_lens_provider: Some(CodeLensOptions { resolve_provider: Some(true) }), |
54 | document_formatting_provider: Some(true), | 54 | document_formatting_provider: Some(OneOf::Left(true)), |
55 | document_range_formatting_provider: None, | 55 | document_range_formatting_provider: None, |
56 | document_on_type_formatting_provider: Some(DocumentOnTypeFormattingOptions { | 56 | document_on_type_formatting_provider: Some(DocumentOnTypeFormattingOptions { |
57 | first_trigger_character: "=".to_string(), | 57 | first_trigger_character: "=".to_string(), |
@@ -60,7 +60,7 @@ pub fn server_capabilities(client_caps: &ClientCapabilities) -> ServerCapabiliti | |||
60 | selection_range_provider: Some(SelectionRangeProviderCapability::Simple(true)), | 60 | selection_range_provider: Some(SelectionRangeProviderCapability::Simple(true)), |
61 | semantic_highlighting: None, | 61 | semantic_highlighting: None, |
62 | folding_range_provider: Some(FoldingRangeProviderCapability::Simple(true)), | 62 | folding_range_provider: Some(FoldingRangeProviderCapability::Simple(true)), |
63 | rename_provider: Some(RenameProviderCapability::Options(RenameOptions { | 63 | rename_provider: Some(OneOf::Right(RenameOptions { |
64 | prepare_provider: Some(true), | 64 | prepare_provider: Some(true), |
65 | work_done_progress_options: WorkDoneProgressOptions { work_done_progress: None }, | 65 | work_done_progress_options: WorkDoneProgressOptions { work_done_progress: None }, |
66 | })), | 66 | })), |
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index 1a74286f5..69d05aed5 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs | |||
@@ -288,7 +288,10 @@ impl Config { | |||
288 | let path = self.root_path.join(it); | 288 | let path = self.root_path.join(it); |
289 | match ProjectManifest::from_manifest_file(path) { | 289 | match ProjectManifest::from_manifest_file(path) { |
290 | Ok(it) => it.into(), | 290 | Ok(it) => it.into(), |
291 | Err(_) => continue, | 291 | Err(e) => { |
292 | log::error!("failed to load linked project: {}", e); | ||
293 | continue; | ||
294 | } | ||
292 | } | 295 | } |
293 | } | 296 | } |
294 | ManifestOrProjectJson::ProjectJson(it) => { | 297 | ManifestOrProjectJson::ProjectJson(it) => { |
diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs index 64cb4d96c..c0943a54d 100644 --- a/crates/rust-analyzer/src/handlers.rs +++ b/crates/rust-analyzer/src/handlers.rs | |||
@@ -748,10 +748,15 @@ pub(crate) fn handle_formatting( | |||
748 | } | 748 | } |
749 | } | 749 | } |
750 | 750 | ||
751 | Ok(Some(vec![lsp_types::TextEdit { | 751 | if *file == captured_stdout { |
752 | range: Range::new(Position::new(0, 0), end_position), | 752 | // The document is already formatted correctly -- no edits needed. |
753 | new_text: captured_stdout, | 753 | Ok(None) |
754 | }])) | 754 | } else { |
755 | Ok(Some(vec![lsp_types::TextEdit { | ||
756 | range: Range::new(Position::new(0, 0), end_position), | ||
757 | new_text: captured_stdout, | ||
758 | }])) | ||
759 | } | ||
755 | } | 760 | } |
756 | 761 | ||
757 | fn handle_fixes( | 762 | fn handle_fixes( |
diff --git a/crates/rust-analyzer/src/reload.rs b/crates/rust-analyzer/src/reload.rs index b819618cb..b070087a4 100644 --- a/crates/rust-analyzer/src/reload.rs +++ b/crates/rust-analyzer/src/reload.rs | |||
@@ -201,11 +201,14 @@ impl GlobalState { | |||
201 | let mut crate_graph = CrateGraph::default(); | 201 | let mut crate_graph = CrateGraph::default(); |
202 | let vfs = &mut self.vfs.write().0; | 202 | let vfs = &mut self.vfs.write().0; |
203 | let loader = &mut self.loader; | 203 | let loader = &mut self.loader; |
204 | let mem_docs = &self.mem_docs; | ||
204 | let mut load = |path: &AbsPath| { | 205 | let mut load = |path: &AbsPath| { |
205 | let contents = loader.handle.load_sync(path); | 206 | let vfs_path = vfs::VfsPath::from(path.to_path_buf()); |
206 | let path = vfs::VfsPath::from(path.to_path_buf()); | 207 | if !mem_docs.contains_key(&vfs_path) { |
207 | vfs.set_file_contents(path.clone(), contents); | 208 | let contents = loader.handle.load_sync(path); |
208 | vfs.file_id(&path) | 209 | vfs.set_file_contents(vfs_path.clone(), contents); |
210 | } | ||
211 | vfs.file_id(&vfs_path) | ||
209 | }; | 212 | }; |
210 | for ws in workspaces.iter() { | 213 | for ws in workspaces.iter() { |
211 | crate_graph.extend(ws.to_crate_graph( | 214 | crate_graph.extend(ws.to_crate_graph( |
@@ -249,7 +252,7 @@ impl GlobalState { | |||
249 | // Enable flychecks for json projects if a custom flycheck command was supplied | 252 | // Enable flychecks for json projects if a custom flycheck command was supplied |
250 | // in the workspace configuration. | 253 | // in the workspace configuration. |
251 | match config { | 254 | match config { |
252 | FlycheckConfig::CustomCommand { .. } => project.path(), | 255 | FlycheckConfig::CustomCommand { .. } => Some(project.path()), |
253 | _ => None, | 256 | _ => None, |
254 | } | 257 | } |
255 | } | 258 | } |
diff --git a/crates/rust-analyzer/src/to_proto.rs b/crates/rust-analyzer/src/to_proto.rs index dcbf837d6..59e780b7d 100644 --- a/crates/rust-analyzer/src/to_proto.rs +++ b/crates/rust-analyzer/src/to_proto.rs | |||
@@ -285,12 +285,18 @@ pub(crate) fn signature_help( | |||
285 | }) | 285 | }) |
286 | }; | 286 | }; |
287 | 287 | ||
288 | let signature = | 288 | let active_parameter = call_info.active_parameter.map(|it| it as i64); |
289 | lsp_types::SignatureInformation { label, documentation, parameters: Some(parameters) }; | 289 | |
290 | let signature = lsp_types::SignatureInformation { | ||
291 | label, | ||
292 | documentation, | ||
293 | parameters: Some(parameters), | ||
294 | active_parameter, | ||
295 | }; | ||
290 | lsp_types::SignatureHelp { | 296 | lsp_types::SignatureHelp { |
291 | signatures: vec![signature], | 297 | signatures: vec![signature], |
292 | active_signature: None, | 298 | active_signature: None, |
293 | active_parameter: call_info.active_parameter.map(|it| it as i64), | 299 | active_parameter, |
294 | } | 300 | } |
295 | } | 301 | } |
296 | 302 | ||
diff --git a/crates/rust-analyzer/tests/rust-analyzer/main.rs b/crates/rust-analyzer/tests/rust-analyzer/main.rs index 0880d0425..06726f957 100644 --- a/crates/rust-analyzer/tests/rust-analyzer/main.rs +++ b/crates/rust-analyzer/tests/rust-analyzer/main.rs | |||
@@ -260,6 +260,42 @@ pub use std::collections::HashMap; | |||
260 | } | 260 | } |
261 | 261 | ||
262 | #[test] | 262 | #[test] |
263 | fn test_format_document_unchanged() { | ||
264 | if skip_slow_tests() { | ||
265 | return; | ||
266 | } | ||
267 | |||
268 | let server = project( | ||
269 | r#" | ||
270 | //- /Cargo.toml | ||
271 | [package] | ||
272 | name = "foo" | ||
273 | version = "0.0.0" | ||
274 | |||
275 | //- /src/lib.rs | ||
276 | fn main() {} | ||
277 | "#, | ||
278 | ) | ||
279 | .wait_until_workspace_is_loaded(); | ||
280 | |||
281 | server.request::<Formatting>( | ||
282 | DocumentFormattingParams { | ||
283 | text_document: server.doc_id("src/lib.rs"), | ||
284 | options: FormattingOptions { | ||
285 | tab_size: 4, | ||
286 | insert_spaces: false, | ||
287 | insert_final_newline: None, | ||
288 | trim_final_newlines: None, | ||
289 | trim_trailing_whitespace: None, | ||
290 | properties: HashMap::new(), | ||
291 | }, | ||
292 | work_done_progress_params: WorkDoneProgressParams::default(), | ||
293 | }, | ||
294 | json!(null), | ||
295 | ); | ||
296 | } | ||
297 | |||
298 | #[test] | ||
263 | fn test_missing_module_code_action() { | 299 | fn test_missing_module_code_action() { |
264 | if skip_slow_tests() { | 300 | if skip_slow_tests() { |
265 | return; | 301 | return; |
diff --git a/crates/syntax/src/ast/edit.rs b/crates/syntax/src/ast/edit.rs index 45cf31f13..dda0a0319 100644 --- a/crates/syntax/src/ast/edit.rs +++ b/crates/syntax/src/ast/edit.rs | |||
@@ -93,6 +93,22 @@ where | |||
93 | } | 93 | } |
94 | } | 94 | } |
95 | 95 | ||
96 | impl ast::Impl { | ||
97 | #[must_use] | ||
98 | pub fn with_assoc_item_list(&self, items: ast::AssocItemList) -> ast::Impl { | ||
99 | let mut to_insert: ArrayVec<[SyntaxElement; 2]> = ArrayVec::new(); | ||
100 | if let Some(old_items) = self.assoc_item_list() { | ||
101 | let to_replace: SyntaxElement = old_items.syntax().clone().into(); | ||
102 | to_insert.push(items.syntax().clone().into()); | ||
103 | self.replace_children(single_node(to_replace), to_insert) | ||
104 | } else { | ||
105 | to_insert.push(make::tokens::single_space().into()); | ||
106 | to_insert.push(items.syntax().clone().into()); | ||
107 | self.insert_children(InsertPosition::Last, to_insert) | ||
108 | } | ||
109 | } | ||
110 | } | ||
111 | |||
96 | impl ast::AssocItemList { | 112 | impl ast::AssocItemList { |
97 | #[must_use] | 113 | #[must_use] |
98 | pub fn append_items( | 114 | pub fn append_items( |
diff --git a/crates/syntax/src/ast/make.rs b/crates/syntax/src/ast/make.rs index 6868feed9..4a0ffcbb0 100644 --- a/crates/syntax/src/ast/make.rs +++ b/crates/syntax/src/ast/make.rs | |||
@@ -21,6 +21,10 @@ pub fn ty(text: &str) -> ast::Type { | |||
21 | ast_from_text(&format!("impl {} for D {{}};", text)) | 21 | ast_from_text(&format!("impl {} for D {{}};", text)) |
22 | } | 22 | } |
23 | 23 | ||
24 | pub fn assoc_item_list() -> ast::AssocItemList { | ||
25 | ast_from_text("impl C for D {};") | ||
26 | } | ||
27 | |||
24 | pub fn path_segment(name_ref: ast::NameRef) -> ast::PathSegment { | 28 | pub fn path_segment(name_ref: ast::NameRef) -> ast::PathSegment { |
25 | ast_from_text(&format!("use {};", name_ref)) | 29 | ast_from_text(&format!("use {};", name_ref)) |
26 | } | 30 | } |
diff --git a/docs/dev/syntax.md b/docs/dev/syntax.md index c08062ef4..2eb08b7ca 100644 --- a/docs/dev/syntax.md +++ b/docs/dev/syntax.md | |||
@@ -72,7 +72,7 @@ Points of note: | |||
72 | * Trivia and non-trivia tokens are not distinguished on the type level. | 72 | * Trivia and non-trivia tokens are not distinguished on the type level. |
73 | * Each token carries its full text. | 73 | * Each token carries its full text. |
74 | * The original text can be recovered by concatenating the texts of all tokens in order. | 74 | * The original text can be recovered by concatenating the texts of all tokens in order. |
75 | * Accessing a child of particular type (for example, parameter list of a function) generally involves linerary traversing the children, looking for a specific `kind`. | 75 | * Accessing a child of particular type (for example, parameter list of a function) generally involves linearly traversing the children, looking for a specific `kind`. |
76 | * Modifying the tree is roughly `O(depth)`. | 76 | * Modifying the tree is roughly `O(depth)`. |
77 | We don't make special efforts to guarantee that the depth is not linear, but, in practice, syntax trees are branchy and shallow. | 77 | We don't make special efforts to guarantee that the depth is not linear, but, in practice, syntax trees are branchy and shallow. |
78 | * If mandatory (grammar wise) node is missing from the input, it's just missing from the tree. | 78 | * If mandatory (grammar wise) node is missing from the input, it's just missing from the tree. |
diff --git a/docs/user/manual.adoc b/docs/user/manual.adoc index b15c9ee7f..d3e6b23ae 100644 --- a/docs/user/manual.adoc +++ b/docs/user/manual.adoc | |||
@@ -44,6 +44,11 @@ https://github.com/rust-analyzer/rust-analyzer/tree/master/editors/code[in tree] | |||
44 | 44 | ||
45 | You can install the latest release of the plugin from | 45 | You can install the latest release of the plugin from |
46 | https://marketplace.visualstudio.com/items?itemName=matklad.rust-analyzer[the marketplace]. | 46 | https://marketplace.visualstudio.com/items?itemName=matklad.rust-analyzer[the marketplace]. |
47 | |||
48 | Note that the plugin may cause conflicts with the | ||
49 | https://marketplace.visualstudio.com/items?itemName=rust-lang.rust[official Rust plugin]. It is | ||
50 | recommended to disable the Rust plugin when using the rust-analyzer extension. | ||
51 | |||
47 | By default, the plugin will prompt you to download the matching version of the server as well: | 52 | By default, the plugin will prompt you to download the matching version of the server as well: |
48 | 53 | ||
49 | image::https://user-images.githubusercontent.com/9021944/75067008-17502500-54ba-11ea-835a-f92aac50e866.png[] | 54 | image::https://user-images.githubusercontent.com/9021944/75067008-17502500-54ba-11ea-835a-f92aac50e866.png[] |
diff --git a/editors/code/package.json b/editors/code/package.json index c57fbdda2..132664926 100644 --- a/editors/code/package.json +++ b/editors/code/package.json | |||
@@ -159,6 +159,11 @@ | |||
159 | "category": "Rust Analyzer" | 159 | "category": "Rust Analyzer" |
160 | }, | 160 | }, |
161 | { | 161 | { |
162 | "command": "rust-analyzer.updateGithubToken", | ||
163 | "title": "Update Github API token", | ||
164 | "category": "Rust Analyzer" | ||
165 | }, | ||
166 | { | ||
162 | "command": "rust-analyzer.onEnter", | 167 | "command": "rust-analyzer.onEnter", |
163 | "title": "Enhanced enter key", | 168 | "title": "Enhanced enter key", |
164 | "category": "Rust Analyzer" | 169 | "category": "Rust Analyzer" |
@@ -985,6 +990,10 @@ | |||
985 | "when": "inRustProject" | 990 | "when": "inRustProject" |
986 | }, | 991 | }, |
987 | { | 992 | { |
993 | "command": "rust-analyzer.updateGithubToken", | ||
994 | "when": "inRustProject" | ||
995 | }, | ||
996 | { | ||
988 | "command": "rust-analyzer.onEnter", | 997 | "command": "rust-analyzer.onEnter", |
989 | "when": "inRustProject" | 998 | "when": "inRustProject" |
990 | }, | 999 | }, |
diff --git a/editors/code/src/commands.ts b/editors/code/src/commands.ts index 4321de244..e9581a9b5 100644 --- a/editors/code/src/commands.ts +++ b/editors/code/src/commands.ts | |||
@@ -21,7 +21,7 @@ export function analyzerStatus(ctx: Ctx): Cmd { | |||
21 | provideTextDocumentContent(_uri: vscode.Uri): vscode.ProviderResult<string> { | 21 | provideTextDocumentContent(_uri: vscode.Uri): vscode.ProviderResult<string> { |
22 | if (!vscode.window.activeTextEditor) return ''; | 22 | if (!vscode.window.activeTextEditor) return ''; |
23 | 23 | ||
24 | return ctx.client.sendRequest(ra.analyzerStatus, null); | 24 | return ctx.client.sendRequest(ra.analyzerStatus); |
25 | } | 25 | } |
26 | 26 | ||
27 | get onDidChange(): vscode.Event<vscode.Uri> { | 27 | get onDidChange(): vscode.Event<vscode.Uri> { |
@@ -63,7 +63,7 @@ export function memoryUsage(ctx: Ctx): Cmd { | |||
63 | provideTextDocumentContent(_uri: vscode.Uri): vscode.ProviderResult<string> { | 63 | provideTextDocumentContent(_uri: vscode.Uri): vscode.ProviderResult<string> { |
64 | if (!vscode.window.activeTextEditor) return ''; | 64 | if (!vscode.window.activeTextEditor) return ''; |
65 | 65 | ||
66 | return ctx.client.sendRequest(ra.memoryUsage, null).then((mem: any) => { | 66 | return ctx.client.sendRequest(ra.memoryUsage).then((mem: any) => { |
67 | return 'Per-query memory usage:\n' + mem + '\n(note: database has been cleared)'; | 67 | return 'Per-query memory usage:\n' + mem + '\n(note: database has been cleared)'; |
68 | }); | 68 | }); |
69 | } | 69 | } |
@@ -372,7 +372,7 @@ export function expandMacro(ctx: Ctx): Cmd { | |||
372 | } | 372 | } |
373 | 373 | ||
374 | export function reloadWorkspace(ctx: Ctx): Cmd { | 374 | export function reloadWorkspace(ctx: Ctx): Cmd { |
375 | return async () => ctx.client.sendRequest(ra.reloadWorkspace, null); | 375 | return async () => ctx.client.sendRequest(ra.reloadWorkspace); |
376 | } | 376 | } |
377 | 377 | ||
378 | export function showReferences(ctx: Ctx): Cmd { | 378 | export function showReferences(ctx: Ctx): Cmd { |
diff --git a/editors/code/src/lsp_ext.ts b/editors/code/src/lsp_ext.ts index f280bba3d..d167041c4 100644 --- a/editors/code/src/lsp_ext.ts +++ b/editors/code/src/lsp_ext.ts | |||
@@ -4,8 +4,8 @@ | |||
4 | 4 | ||
5 | import * as lc from "vscode-languageclient"; | 5 | import * as lc from "vscode-languageclient"; |
6 | 6 | ||
7 | export const analyzerStatus = new lc.RequestType<null, string, void>("rust-analyzer/analyzerStatus"); | 7 | export const analyzerStatus = new lc.RequestType0<string, void>("rust-analyzer/analyzerStatus"); |
8 | export const memoryUsage = new lc.RequestType<null, string, void>("rust-analyzer/memoryUsage"); | 8 | export const memoryUsage = new lc.RequestType0<string, void>("rust-analyzer/memoryUsage"); |
9 | 9 | ||
10 | export type Status = "loading" | "ready" | "invalid" | "needsReload"; | 10 | export type Status = "loading" | "ready" | "invalid" | "needsReload"; |
11 | export interface StatusParams { | 11 | export interface StatusParams { |
@@ -13,7 +13,7 @@ export interface StatusParams { | |||
13 | } | 13 | } |
14 | export const status = new lc.NotificationType<StatusParams>("rust-analyzer/status"); | 14 | export const status = new lc.NotificationType<StatusParams>("rust-analyzer/status"); |
15 | 15 | ||
16 | export const reloadWorkspace = new lc.RequestType<null, null, void>("rust-analyzer/reloadWorkspace"); | 16 | export const reloadWorkspace = new lc.RequestType0<null, void>("rust-analyzer/reloadWorkspace"); |
17 | 17 | ||
18 | export interface SyntaxTreeParams { | 18 | export interface SyntaxTreeParams { |
19 | textDocument: lc.TextDocumentIdentifier; | 19 | textDocument: lc.TextDocumentIdentifier; |
diff --git a/editors/code/src/main.ts b/editors/code/src/main.ts index bd99d696a..2896d90ac 100644 --- a/editors/code/src/main.ts +++ b/editors/code/src/main.ts | |||
@@ -95,6 +95,10 @@ async function tryActivate(context: vscode.ExtensionContext) { | |||
95 | await activate(context).catch(log.error); | 95 | await activate(context).catch(log.error); |
96 | }); | 96 | }); |
97 | 97 | ||
98 | ctx.registerCommand('updateGithubToken', ctx => async () => { | ||
99 | await queryForGithubToken(new PersistentState(ctx.globalState)); | ||
100 | }); | ||
101 | |||
98 | ctx.registerCommand('analyzerStatus', commands.analyzerStatus); | 102 | ctx.registerCommand('analyzerStatus', commands.analyzerStatus); |
99 | ctx.registerCommand('memoryUsage', commands.memoryUsage); | 103 | ctx.registerCommand('memoryUsage', commands.memoryUsage); |
100 | ctx.registerCommand('reloadWorkspace', commands.reloadWorkspace); | 104 | ctx.registerCommand('reloadWorkspace', commands.reloadWorkspace); |
@@ -173,7 +177,9 @@ async function bootstrapExtension(config: Config, state: PersistentState): Promi | |||
173 | if (!shouldCheckForNewNightly) return; | 177 | if (!shouldCheckForNewNightly) return; |
174 | } | 178 | } |
175 | 179 | ||
176 | const release = await fetchRelease("nightly").catch((e) => { | 180 | const release = await downloadWithRetryDialog(state, async () => { |
181 | return await fetchRelease("nightly", state.githubToken); | ||
182 | }).catch((e) => { | ||
177 | log.error(e); | 183 | log.error(e); |
178 | if (state.releaseId === undefined) { // Show error only for the initial download | 184 | if (state.releaseId === undefined) { // Show error only for the initial download |
179 | vscode.window.showErrorMessage(`Failed to download rust-analyzer nightly ${e}`); | 185 | vscode.window.showErrorMessage(`Failed to download rust-analyzer nightly ${e}`); |
@@ -192,10 +198,14 @@ async function bootstrapExtension(config: Config, state: PersistentState): Promi | |||
192 | assert(!!artifact, `Bad release: ${JSON.stringify(release)}`); | 198 | assert(!!artifact, `Bad release: ${JSON.stringify(release)}`); |
193 | 199 | ||
194 | const dest = path.join(config.globalStoragePath, "rust-analyzer.vsix"); | 200 | const dest = path.join(config.globalStoragePath, "rust-analyzer.vsix"); |
195 | await download({ | 201 | |
196 | url: artifact.browser_download_url, | 202 | await downloadWithRetryDialog(state, async () => { |
197 | dest, | 203 | await download({ |
198 | progressTitle: "Downloading rust-analyzer extension", | 204 | url: artifact.browser_download_url, |
205 | dest, | ||
206 | progressTitle: "Downloading rust-analyzer extension", | ||
207 | overwrite: true, | ||
208 | }); | ||
199 | }); | 209 | }); |
200 | 210 | ||
201 | await vscode.commands.executeCommand("workbench.extensions.installExtension", vscode.Uri.file(dest)); | 211 | await vscode.commands.executeCommand("workbench.extensions.installExtension", vscode.Uri.file(dest)); |
@@ -308,21 +318,22 @@ async function getServer(config: Config, state: PersistentState): Promise<string | |||
308 | if (userResponse !== "Download now") return dest; | 318 | if (userResponse !== "Download now") return dest; |
309 | } | 319 | } |
310 | 320 | ||
311 | const release = await fetchRelease(config.package.releaseTag); | 321 | const releaseTag = config.package.releaseTag; |
322 | const release = await downloadWithRetryDialog(state, async () => { | ||
323 | return await fetchRelease(releaseTag, state.githubToken); | ||
324 | }); | ||
312 | const artifact = release.assets.find(artifact => artifact.name === `rust-analyzer-${platform}.gz`); | 325 | const artifact = release.assets.find(artifact => artifact.name === `rust-analyzer-${platform}.gz`); |
313 | assert(!!artifact, `Bad release: ${JSON.stringify(release)}`); | 326 | assert(!!artifact, `Bad release: ${JSON.stringify(release)}`); |
314 | 327 | ||
315 | // Unlinking the exe file before moving new one on its place should prevent ETXTBSY error. | 328 | await downloadWithRetryDialog(state, async () => { |
316 | await fs.unlink(dest).catch(err => { | 329 | await download({ |
317 | if (err.code !== "ENOENT") throw err; | 330 | url: artifact.browser_download_url, |
318 | }); | 331 | dest, |
319 | 332 | progressTitle: "Downloading rust-analyzer server", | |
320 | await download({ | 333 | gunzip: true, |
321 | url: artifact.browser_download_url, | 334 | mode: 0o755, |
322 | dest, | 335 | overwrite: true, |
323 | progressTitle: "Downloading rust-analyzer server", | 336 | }); |
324 | gunzip: true, | ||
325 | mode: 0o755 | ||
326 | }); | 337 | }); |
327 | 338 | ||
328 | // Patching executable if that's NixOS. | 339 | // Patching executable if that's NixOS. |
@@ -333,3 +344,56 @@ async function getServer(config: Config, state: PersistentState): Promise<string | |||
333 | await state.updateServerVersion(config.package.version); | 344 | await state.updateServerVersion(config.package.version); |
334 | return dest; | 345 | return dest; |
335 | } | 346 | } |
347 | |||
348 | async function downloadWithRetryDialog<T>(state: PersistentState, downloadFunc: () => Promise<T>): Promise<T> { | ||
349 | while (true) { | ||
350 | try { | ||
351 | return await downloadFunc(); | ||
352 | } catch (e) { | ||
353 | const selected = await vscode.window.showErrorMessage("Failed to download: " + e.message, {}, { | ||
354 | title: "Update Github Auth Token", | ||
355 | updateToken: true, | ||
356 | }, { | ||
357 | title: "Retry download", | ||
358 | retry: true, | ||
359 | }, { | ||
360 | title: "Dismiss", | ||
361 | }); | ||
362 | |||
363 | if (selected?.updateToken) { | ||
364 | await queryForGithubToken(state); | ||
365 | continue; | ||
366 | } else if (selected?.retry) { | ||
367 | continue; | ||
368 | } | ||
369 | throw e; | ||
370 | }; | ||
371 | } | ||
372 | } | ||
373 | |||
374 | async function queryForGithubToken(state: PersistentState): Promise<void> { | ||
375 | const githubTokenOptions: vscode.InputBoxOptions = { | ||
376 | value: state.githubToken, | ||
377 | password: true, | ||
378 | prompt: ` | ||
379 | This dialog allows to store a Github authorization token. | ||
380 | The usage of an authorization token will increase the rate | ||
381 | limit on the use of Github APIs and can thereby prevent getting | ||
382 | throttled. | ||
383 | Auth tokens can be created at https://github.com/settings/tokens`, | ||
384 | }; | ||
385 | |||
386 | const newToken = await vscode.window.showInputBox(githubTokenOptions); | ||
387 | if (newToken === undefined) { | ||
388 | // The user aborted the dialog => Do not update the stored token | ||
389 | return; | ||
390 | } | ||
391 | |||
392 | if (newToken === "") { | ||
393 | log.info("Clearing github token"); | ||
394 | await state.updateGithubToken(undefined); | ||
395 | } else { | ||
396 | log.info("Storing new github token"); | ||
397 | await state.updateGithubToken(newToken); | ||
398 | } | ||
399 | } | ||
diff --git a/editors/code/src/net.ts b/editors/code/src/net.ts index 5eba2728d..9ba17b7b5 100644 --- a/editors/code/src/net.ts +++ b/editors/code/src/net.ts | |||
@@ -18,7 +18,8 @@ const OWNER = "rust-analyzer"; | |||
18 | const REPO = "rust-analyzer"; | 18 | const REPO = "rust-analyzer"; |
19 | 19 | ||
20 | export async function fetchRelease( | 20 | export async function fetchRelease( |
21 | releaseTag: string | 21 | releaseTag: string, |
22 | githubToken: string | null | undefined, | ||
22 | ): Promise<GithubRelease> { | 23 | ): Promise<GithubRelease> { |
23 | 24 | ||
24 | const apiEndpointPath = `/repos/${OWNER}/${REPO}/releases/tags/${releaseTag}`; | 25 | const apiEndpointPath = `/repos/${OWNER}/${REPO}/releases/tags/${releaseTag}`; |
@@ -27,7 +28,12 @@ export async function fetchRelease( | |||
27 | 28 | ||
28 | log.debug("Issuing request for released artifacts metadata to", requestUrl); | 29 | log.debug("Issuing request for released artifacts metadata to", requestUrl); |
29 | 30 | ||
30 | const response = await fetch(requestUrl, { headers: { Accept: "application/vnd.github.v3+json" } }); | 31 | const headers: Record<string, string> = { Accept: "application/vnd.github.v3+json" }; |
32 | if (githubToken != null) { | ||
33 | headers.Authorization = "token " + githubToken; | ||
34 | } | ||
35 | |||
36 | const response = await fetch(requestUrl, { headers: headers }); | ||
31 | 37 | ||
32 | if (!response.ok) { | 38 | if (!response.ok) { |
33 | log.error("Error fetching artifact release info", { | 39 | log.error("Error fetching artifact release info", { |
@@ -70,6 +76,7 @@ interface DownloadOpts { | |||
70 | dest: string; | 76 | dest: string; |
71 | mode?: number; | 77 | mode?: number; |
72 | gunzip?: boolean; | 78 | gunzip?: boolean; |
79 | overwrite?: boolean; | ||
73 | } | 80 | } |
74 | 81 | ||
75 | export async function download(opts: DownloadOpts) { | 82 | export async function download(opts: DownloadOpts) { |
@@ -79,6 +86,13 @@ export async function download(opts: DownloadOpts) { | |||
79 | const randomHex = crypto.randomBytes(5).toString("hex"); | 86 | const randomHex = crypto.randomBytes(5).toString("hex"); |
80 | const tempFile = path.join(dest.dir, `${dest.name}${randomHex}`); | 87 | const tempFile = path.join(dest.dir, `${dest.name}${randomHex}`); |
81 | 88 | ||
89 | if (opts.overwrite) { | ||
90 | // Unlinking the exe file before moving new one on its place should prevent ETXTBSY error. | ||
91 | await fs.promises.unlink(opts.dest).catch(err => { | ||
92 | if (err.code !== "ENOENT") throw err; | ||
93 | }); | ||
94 | } | ||
95 | |||
82 | await vscode.window.withProgress( | 96 | await vscode.window.withProgress( |
83 | { | 97 | { |
84 | location: vscode.ProgressLocation.Notification, | 98 | location: vscode.ProgressLocation.Notification, |
diff --git a/editors/code/src/persistent_state.ts b/editors/code/src/persistent_state.ts index 5705eed81..afb652589 100644 --- a/editors/code/src/persistent_state.ts +++ b/editors/code/src/persistent_state.ts | |||
@@ -38,4 +38,15 @@ export class PersistentState { | |||
38 | async updateServerVersion(value: string | undefined) { | 38 | async updateServerVersion(value: string | undefined) { |
39 | await this.globalState.update("serverVersion", value); | 39 | await this.globalState.update("serverVersion", value); |
40 | } | 40 | } |
41 | |||
42 | /** | ||
43 | * Github authorization token. | ||
44 | * This is used for API requests against the Github API. | ||
45 | */ | ||
46 | get githubToken(): string | undefined { | ||
47 | return this.globalState.get("githubToken"); | ||
48 | } | ||
49 | async updateGithubToken(value: string | undefined) { | ||
50 | await this.globalState.update("githubToken", value); | ||
51 | } | ||
41 | } | 52 | } |