aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock1
-rw-r--r--crates/ra_assists/src/handlers/auto_import.rs588
-rw-r--r--crates/ra_assists/src/handlers/replace_qualified_name_with_use.rs29
-rw-r--r--crates/ra_hir/src/code_model.rs70
-rw-r--r--crates/ra_hir/src/lib.rs8
-rw-r--r--crates/ra_hir_expand/src/lib.rs22
-rw-r--r--crates/ra_ide/src/goto_type_definition.rs22
-rw-r--r--crates/ra_lsp_server/src/main_loop/handlers.rs2
-rw-r--r--crates/ra_parser/src/grammar/params.rs5
-rw-r--r--crates/ra_project_model/Cargo.toml2
-rw-r--r--crates/ra_project_model/src/cargo_workspace.rs11
-rw-r--r--crates/ra_project_model/src/lib.rs51
-rw-r--r--crates/ra_project_model/src/sysroot.rs14
-rw-r--r--crates/ra_syntax/src/ast.rs9
-rw-r--r--crates/ra_syntax/src/ast/extensions.rs18
-rw-r--r--crates/ra_syntax/test_data/parser/inline/ok/0157_fn_pointer_unnamed_arg.rs1
-rw-r--r--crates/ra_syntax/test_data/parser/inline/ok/0157_fn_pointer_unnamed_arg.txt26
17 files changed, 774 insertions, 105 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 45804c087..f1651edaa 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1247,6 +1247,7 @@ dependencies = [
1247name = "ra_project_model" 1247name = "ra_project_model"
1248version = "0.1.0" 1248version = "0.1.0"
1249dependencies = [ 1249dependencies = [
1250 "anyhow",
1250 "cargo_metadata", 1251 "cargo_metadata",
1251 "log", 1252 "log",
1252 "ra_arena", 1253 "ra_arena",
diff --git a/crates/ra_assists/src/handlers/auto_import.rs b/crates/ra_assists/src/handlers/auto_import.rs
index 1fb701da5..c4aea2a06 100644
--- a/crates/ra_assists/src/handlers/auto_import.rs
+++ b/crates/ra_assists/src/handlers/auto_import.rs
@@ -1,10 +1,18 @@
1use ra_ide_db::imports_locator::ImportsLocator;
2use ra_syntax::ast::{self, AstNode};
3
4use crate::{ 1use crate::{
5 assist_ctx::{Assist, AssistCtx}, 2 assist_ctx::{Assist, AssistCtx},
6 insert_use_statement, AssistId, 3 insert_use_statement, AssistId,
7}; 4};
5use hir::{
6 db::HirDatabase, AsAssocItem, AssocItemContainer, ModPath, Module, ModuleDef, PathResolution,
7 SourceAnalyzer, Trait, Type,
8};
9use ra_ide_db::{imports_locator::ImportsLocator, RootDatabase};
10use ra_prof::profile;
11use ra_syntax::{
12 ast::{self, AstNode},
13 SyntaxNode,
14};
15use rustc_hash::FxHashSet;
8use std::collections::BTreeSet; 16use std::collections::BTreeSet;
9 17
10// Assist: auto_import 18// Assist: auto_import
@@ -27,52 +35,24 @@ use std::collections::BTreeSet;
27// # pub mod std { pub mod collections { pub struct HashMap { } } } 35// # pub mod std { pub mod collections { pub struct HashMap { } } }
28// ``` 36// ```
29pub(crate) fn auto_import(ctx: AssistCtx) -> Option<Assist> { 37pub(crate) fn auto_import(ctx: AssistCtx) -> Option<Assist> {
30 let path_under_caret: ast::Path = ctx.find_node_at_offset()?; 38 let auto_import_assets = AutoImportAssets::new(&ctx)?;
31 if path_under_caret.syntax().ancestors().find_map(ast::UseItem::cast).is_some() { 39 let proposed_imports = auto_import_assets.search_for_imports(ctx.db);
32 return None;
33 }
34
35 let module = path_under_caret.syntax().ancestors().find_map(ast::Module::cast);
36 let position = match module.and_then(|it| it.item_list()) {
37 Some(item_list) => item_list.syntax().clone(),
38 None => {
39 let current_file =
40 path_under_caret.syntax().ancestors().find_map(ast::SourceFile::cast)?;
41 current_file.syntax().clone()
42 }
43 };
44 let source_analyzer = ctx.source_analyzer(&position, None);
45 let module_with_name_to_import = source_analyzer.module()?;
46
47 let name_ref_to_import =
48 path_under_caret.syntax().descendants().find_map(ast::NameRef::cast)?;
49 if source_analyzer
50 .resolve_path(ctx.db, &name_ref_to_import.syntax().ancestors().find_map(ast::Path::cast)?)
51 .is_some()
52 {
53 return None;
54 }
55
56 let name_to_import = name_ref_to_import.syntax().to_string();
57 let proposed_imports = ImportsLocator::new(ctx.db)
58 .find_imports(&name_to_import)
59 .into_iter()
60 .filter_map(|module_def| module_with_name_to_import.find_use_path(ctx.db, module_def))
61 .filter(|use_path| !use_path.segments.is_empty())
62 .take(20)
63 .collect::<BTreeSet<_>>();
64
65 if proposed_imports.is_empty() { 40 if proposed_imports.is_empty() {
66 return None; 41 return None;
67 } 42 }
68 43
69 let mut group = ctx.add_assist_group(format!("Import {}", name_to_import)); 44 let assist_group_name = if proposed_imports.len() == 1 {
45 format!("Import `{}`", proposed_imports.iter().next().unwrap())
46 } else {
47 auto_import_assets.get_import_group_message()
48 };
49 let mut group = ctx.add_assist_group(assist_group_name);
70 for import in proposed_imports { 50 for import in proposed_imports {
71 group.add_assist(AssistId("auto_import"), format!("Import `{}`", &import), |edit| { 51 group.add_assist(AssistId("auto_import"), format!("Import `{}`", &import), |edit| {
72 edit.target(path_under_caret.syntax().text_range()); 52 edit.target(auto_import_assets.syntax_under_caret.text_range());
73 insert_use_statement( 53 insert_use_statement(
74 &position, 54 &auto_import_assets.syntax_under_caret,
75 path_under_caret.syntax(), 55 &auto_import_assets.syntax_under_caret,
76 &import, 56 &import,
77 edit.text_edit_builder(), 57 edit.text_edit_builder(),
78 ); 58 );
@@ -81,11 +61,232 @@ pub(crate) fn auto_import(ctx: AssistCtx) -> Option<Assist> {
81 group.finish() 61 group.finish()
82} 62}
83 63
64struct AutoImportAssets {
65 import_candidate: ImportCandidate,
66 module_with_name_to_import: Module,
67 syntax_under_caret: SyntaxNode,
68}
69
70impl AutoImportAssets {
71 fn new(ctx: &AssistCtx) -> Option<Self> {
72 if let Some(path_under_caret) = ctx.find_node_at_offset::<ast::Path>() {
73 Self::for_regular_path(path_under_caret, &ctx)
74 } else {
75 Self::for_method_call(ctx.find_node_at_offset()?, &ctx)
76 }
77 }
78
79 fn for_method_call(method_call: ast::MethodCallExpr, ctx: &AssistCtx) -> Option<Self> {
80 let syntax_under_caret = method_call.syntax().to_owned();
81 let source_analyzer = ctx.source_analyzer(&syntax_under_caret, None);
82 let module_with_name_to_import = source_analyzer.module()?;
83 Some(Self {
84 import_candidate: ImportCandidate::for_method_call(
85 &method_call,
86 &source_analyzer,
87 ctx.db,
88 )?,
89 module_with_name_to_import,
90 syntax_under_caret,
91 })
92 }
93
94 fn for_regular_path(path_under_caret: ast::Path, ctx: &AssistCtx) -> Option<Self> {
95 let syntax_under_caret = path_under_caret.syntax().to_owned();
96 if syntax_under_caret.ancestors().find_map(ast::UseItem::cast).is_some() {
97 return None;
98 }
99
100 let source_analyzer = ctx.source_analyzer(&syntax_under_caret, None);
101 let module_with_name_to_import = source_analyzer.module()?;
102 Some(Self {
103 import_candidate: ImportCandidate::for_regular_path(
104 &path_under_caret,
105 &source_analyzer,
106 ctx.db,
107 )?,
108 module_with_name_to_import,
109 syntax_under_caret,
110 })
111 }
112
113 fn get_search_query(&self) -> &str {
114 match &self.import_candidate {
115 ImportCandidate::UnqualifiedName(name) => name,
116 ImportCandidate::QualifierStart(qualifier_start) => qualifier_start,
117 ImportCandidate::TraitAssocItem(_, trait_assoc_item_name) => trait_assoc_item_name,
118 ImportCandidate::TraitMethod(_, trait_method_name) => trait_method_name,
119 }
120 }
121
122 fn get_import_group_message(&self) -> String {
123 match &self.import_candidate {
124 ImportCandidate::UnqualifiedName(name) => format!("Import {}", name),
125 ImportCandidate::QualifierStart(qualifier_start) => {
126 format!("Import {}", qualifier_start)
127 }
128 ImportCandidate::TraitAssocItem(_, trait_assoc_item_name) => {
129 format!("Import a trait for item {}", trait_assoc_item_name)
130 }
131 ImportCandidate::TraitMethod(_, trait_method_name) => {
132 format!("Import a trait for method {}", trait_method_name)
133 }
134 }
135 }
136
137 fn search_for_imports(&self, db: &RootDatabase) -> BTreeSet<ModPath> {
138 let _p = profile("auto_import::search_for_imports");
139 let current_crate = self.module_with_name_to_import.krate();
140 ImportsLocator::new(db)
141 .find_imports(&self.get_search_query())
142 .into_iter()
143 .filter_map(|module_def| match &self.import_candidate {
144 ImportCandidate::TraitAssocItem(assoc_item_type, _) => {
145 let located_assoc_item = match module_def {
146 ModuleDef::Function(located_function) => located_function
147 .as_assoc_item(db)
148 .map(|assoc| assoc.container(db))
149 .and_then(Self::assoc_to_trait),
150 ModuleDef::Const(located_const) => located_const
151 .as_assoc_item(db)
152 .map(|assoc| assoc.container(db))
153 .and_then(Self::assoc_to_trait),
154 _ => None,
155 }?;
156
157 let mut trait_candidates = FxHashSet::default();
158 trait_candidates.insert(located_assoc_item.into());
159
160 assoc_item_type
161 .iterate_path_candidates(
162 db,
163 current_crate,
164 &trait_candidates,
165 None,
166 |_, assoc| Self::assoc_to_trait(assoc.container(db)),
167 )
168 .map(ModuleDef::from)
169 }
170 ImportCandidate::TraitMethod(function_callee, _) => {
171 let located_assoc_item =
172 if let ModuleDef::Function(located_function) = module_def {
173 located_function
174 .as_assoc_item(db)
175 .map(|assoc| assoc.container(db))
176 .and_then(Self::assoc_to_trait)
177 } else {
178 None
179 }?;
180
181 let mut trait_candidates = FxHashSet::default();
182 trait_candidates.insert(located_assoc_item.into());
183
184 function_callee
185 .iterate_method_candidates(
186 db,
187 current_crate,
188 &trait_candidates,
189 None,
190 |_, function| {
191 Self::assoc_to_trait(function.as_assoc_item(db)?.container(db))
192 },
193 )
194 .map(ModuleDef::from)
195 }
196 _ => Some(module_def),
197 })
198 .filter_map(|module_def| self.module_with_name_to_import.find_use_path(db, module_def))
199 .filter(|use_path| !use_path.segments.is_empty())
200 .take(20)
201 .collect::<BTreeSet<_>>()
202 }
203
204 fn assoc_to_trait(assoc: AssocItemContainer) -> Option<Trait> {
205 if let AssocItemContainer::Trait(extracted_trait) = assoc {
206 Some(extracted_trait)
207 } else {
208 None
209 }
210 }
211}
212
213#[derive(Debug)]
214enum ImportCandidate {
215 /// Simple name like 'HashMap'
216 UnqualifiedName(String),
217 /// First part of the qualified name.
218 /// For 'std::collections::HashMap', that will be 'std'.
219 QualifierStart(String),
220 /// A trait associated function (with no self parameter) or associated constant.
221 /// For 'test_mod::TestEnum::test_function', `Type` is the `test_mod::TestEnum` expression type
222 /// and `String` is the `test_function`
223 TraitAssocItem(Type, String),
224 /// A trait method with self parameter.
225 /// For 'test_enum.test_method()', `Type` is the `test_enum` expression type
226 /// and `String` is the `test_method`
227 TraitMethod(Type, String),
228}
229
230impl ImportCandidate {
231 fn for_method_call(
232 method_call: &ast::MethodCallExpr,
233 source_analyzer: &SourceAnalyzer,
234 db: &impl HirDatabase,
235 ) -> Option<Self> {
236 if source_analyzer.resolve_method_call(method_call).is_some() {
237 return None;
238 }
239 Some(Self::TraitMethod(
240 source_analyzer.type_of(db, &method_call.expr()?)?,
241 method_call.name_ref()?.syntax().to_string(),
242 ))
243 }
244
245 fn for_regular_path(
246 path_under_caret: &ast::Path,
247 source_analyzer: &SourceAnalyzer,
248 db: &impl HirDatabase,
249 ) -> Option<Self> {
250 if source_analyzer.resolve_path(db, path_under_caret).is_some() {
251 return None;
252 }
253
254 let segment = path_under_caret.segment()?;
255 if let Some(qualifier) = path_under_caret.qualifier() {
256 let qualifier_start = qualifier.syntax().descendants().find_map(ast::NameRef::cast)?;
257 let qualifier_start_path =
258 qualifier_start.syntax().ancestors().find_map(ast::Path::cast)?;
259 if let Some(qualifier_start_resolution) =
260 source_analyzer.resolve_path(db, &qualifier_start_path)
261 {
262 let qualifier_resolution = if qualifier_start_path == qualifier {
263 qualifier_start_resolution
264 } else {
265 source_analyzer.resolve_path(db, &qualifier)?
266 };
267 if let PathResolution::Def(ModuleDef::Adt(assoc_item_path)) = qualifier_resolution {
268 Some(ImportCandidate::TraitAssocItem(
269 assoc_item_path.ty(db),
270 segment.syntax().to_string(),
271 ))
272 } else {
273 None
274 }
275 } else {
276 Some(ImportCandidate::QualifierStart(qualifier_start.syntax().to_string()))
277 }
278 } else {
279 Some(ImportCandidate::UnqualifiedName(
280 segment.syntax().descendants().find_map(ast::NameRef::cast)?.syntax().to_string(),
281 ))
282 }
283 }
284}
285
84#[cfg(test)] 286#[cfg(test)]
85mod tests { 287mod tests {
86 use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_target};
87
88 use super::*; 288 use super::*;
289 use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_target};
89 290
90 #[test] 291 #[test]
91 fn applicable_when_found_an_import() { 292 fn applicable_when_found_an_import() {
@@ -290,4 +491,303 @@ mod tests {
290 ", 491 ",
291 ); 492 );
292 } 493 }
494
495 #[test]
496 fn not_applicable_for_imported_function() {
497 check_assist_not_applicable(
498 auto_import,
499 r"
500 pub mod test_mod {
501 pub fn test_function() {}
502 }
503
504 use test_mod::test_function;
505 fn main() {
506 test_function<|>
507 }
508 ",
509 );
510 }
511
512 #[test]
513 fn associated_struct_function() {
514 check_assist(
515 auto_import,
516 r"
517 mod test_mod {
518 pub struct TestStruct {}
519 impl TestStruct {
520 pub fn test_function() {}
521 }
522 }
523
524 fn main() {
525 TestStruct::test_function<|>
526 }
527 ",
528 r"
529 use test_mod::TestStruct;
530
531 mod test_mod {
532 pub struct TestStruct {}
533 impl TestStruct {
534 pub fn test_function() {}
535 }
536 }
537
538 fn main() {
539 TestStruct::test_function<|>
540 }
541 ",
542 );
543 }
544
545 #[test]
546 fn associated_struct_const() {
547 check_assist(
548 auto_import,
549 r"
550 mod test_mod {
551 pub struct TestStruct {}
552 impl TestStruct {
553 const TEST_CONST: u8 = 42;
554 }
555 }
556
557 fn main() {
558 TestStruct::TEST_CONST<|>
559 }
560 ",
561 r"
562 use test_mod::TestStruct;
563
564 mod test_mod {
565 pub struct TestStruct {}
566 impl TestStruct {
567 const TEST_CONST: u8 = 42;
568 }
569 }
570
571 fn main() {
572 TestStruct::TEST_CONST<|>
573 }
574 ",
575 );
576 }
577
578 #[test]
579 fn associated_trait_function() {
580 check_assist(
581 auto_import,
582 r"
583 mod test_mod {
584 pub trait TestTrait {
585 fn test_function();
586 }
587 pub struct TestStruct {}
588 impl TestTrait for TestStruct {
589 fn test_function() {}
590 }
591 }
592
593 fn main() {
594 test_mod::TestStruct::test_function<|>
595 }
596 ",
597 r"
598 use test_mod::TestTrait;
599
600 mod test_mod {
601 pub trait TestTrait {
602 fn test_function();
603 }
604 pub struct TestStruct {}
605 impl TestTrait for TestStruct {
606 fn test_function() {}
607 }
608 }
609
610 fn main() {
611 test_mod::TestStruct::test_function<|>
612 }
613 ",
614 );
615 }
616
617 #[test]
618 fn not_applicable_for_imported_trait_for_function() {
619 check_assist_not_applicable(
620 auto_import,
621 r"
622 mod test_mod {
623 pub trait TestTrait {
624 fn test_function();
625 }
626 pub trait TestTrait2 {
627 fn test_function();
628 }
629 pub enum TestEnum {
630 One,
631 Two,
632 }
633 impl TestTrait2 for TestEnum {
634 fn test_function() {}
635 }
636 impl TestTrait for TestEnum {
637 fn test_function() {}
638 }
639 }
640
641 use test_mod::TestTrait2;
642 fn main() {
643 test_mod::TestEnum::test_function<|>;
644 }
645 ",
646 )
647 }
648
649 #[test]
650 fn associated_trait_const() {
651 check_assist(
652 auto_import,
653 r"
654 mod test_mod {
655 pub trait TestTrait {
656 const TEST_CONST: u8;
657 }
658 pub struct TestStruct {}
659 impl TestTrait for TestStruct {
660 const TEST_CONST: u8 = 42;
661 }
662 }
663
664 fn main() {
665 test_mod::TestStruct::TEST_CONST<|>
666 }
667 ",
668 r"
669 use test_mod::TestTrait;
670
671 mod test_mod {
672 pub trait TestTrait {
673 const TEST_CONST: u8;
674 }
675 pub struct TestStruct {}
676 impl TestTrait for TestStruct {
677 const TEST_CONST: u8 = 42;
678 }
679 }
680
681 fn main() {
682 test_mod::TestStruct::TEST_CONST<|>
683 }
684 ",
685 );
686 }
687
688 #[test]
689 fn not_applicable_for_imported_trait_for_const() {
690 check_assist_not_applicable(
691 auto_import,
692 r"
693 mod test_mod {
694 pub trait TestTrait {
695 const TEST_CONST: u8;
696 }
697 pub trait TestTrait2 {
698 const TEST_CONST: f64;
699 }
700 pub enum TestEnum {
701 One,
702 Two,
703 }
704 impl TestTrait2 for TestEnum {
705 const TEST_CONST: f64 = 42.0;
706 }
707 impl TestTrait for TestEnum {
708 const TEST_CONST: u8 = 42;
709 }
710 }
711
712 use test_mod::TestTrait2;
713 fn main() {
714 test_mod::TestEnum::TEST_CONST<|>;
715 }
716 ",
717 )
718 }
719
720 #[test]
721 fn trait_method() {
722 check_assist(
723 auto_import,
724 r"
725 mod test_mod {
726 pub trait TestTrait {
727 fn test_method(&self);
728 }
729 pub struct TestStruct {}
730 impl TestTrait for TestStruct {
731 fn test_method(&self) {}
732 }
733 }
734
735 fn main() {
736 let test_struct = test_mod::TestStruct {};
737 test_struct.test_meth<|>od()
738 }
739 ",
740 r"
741 use test_mod::TestTrait;
742
743 mod test_mod {
744 pub trait TestTrait {
745 fn test_method(&self);
746 }
747 pub struct TestStruct {}
748 impl TestTrait for TestStruct {
749 fn test_method(&self) {}
750 }
751 }
752
753 fn main() {
754 let test_struct = test_mod::TestStruct {};
755 test_struct.test_meth<|>od()
756 }
757 ",
758 );
759 }
760
761 #[test]
762 fn not_applicable_for_imported_trait_for_method() {
763 check_assist_not_applicable(
764 auto_import,
765 r"
766 mod test_mod {
767 pub trait TestTrait {
768 fn test_method(&self);
769 }
770 pub trait TestTrait2 {
771 fn test_method(&self);
772 }
773 pub enum TestEnum {
774 One,
775 Two,
776 }
777 impl TestTrait2 for TestEnum {
778 fn test_method(&self) {}
779 }
780 impl TestTrait for TestEnum {
781 fn test_method(&self) {}
782 }
783 }
784
785 use test_mod::TestTrait2;
786 fn main() {
787 let one = test_mod::TestEnum::One;
788 one.test<|>_method();
789 }
790 ",
791 )
792 }
293} 793}
diff --git a/crates/ra_assists/src/handlers/replace_qualified_name_with_use.rs b/crates/ra_assists/src/handlers/replace_qualified_name_with_use.rs
index b70c88ec2..eac452413 100644
--- a/crates/ra_assists/src/handlers/replace_qualified_name_with_use.rs
+++ b/crates/ra_assists/src/handlers/replace_qualified_name_with_use.rs
@@ -431,7 +431,12 @@ fn best_action_for_target(
431 .find(|n| n.text_range().start() < anchor.text_range().start()) 431 .find(|n| n.text_range().start() < anchor.text_range().start())
432 .or_else(|| Some(anchor)); 432 .or_else(|| Some(anchor));
433 433
434 ImportAction::add_new_use(anchor, false) 434 let add_after_anchor = anchor
435 .clone()
436 .and_then(ast::Attr::cast)
437 .map(|attr| attr.kind() == ast::AttrKind::Inner)
438 .unwrap_or(false);
439 ImportAction::add_new_use(anchor, add_after_anchor)
435 } 440 }
436 } 441 }
437} 442}
@@ -962,4 +967,26 @@ mod foo {
962 ", 967 ",
963 ); 968 );
964 } 969 }
970
971 #[test]
972 fn inserts_imports_after_inner_attributes() {
973 check_assist(
974 replace_qualified_name_with_use,
975 "
976#![allow(dead_code)]
977
978fn main() {
979 std::fmt::Debug<|>
980}
981 ",
982 "
983#![allow(dead_code)]
984use std::fmt::Debug;
985
986fn main() {
987 Debug<|>
988}
989 ",
990 );
991 }
965} 992}
diff --git a/crates/ra_hir/src/code_model.rs b/crates/ra_hir/src/code_model.rs
index 4d9641728..a56b8ab04 100644
--- a/crates/ra_hir/src/code_model.rs
+++ b/crates/ra_hir/src/code_model.rs
@@ -10,9 +10,9 @@ use hir_def::{
10 per_ns::PerNs, 10 per_ns::PerNs,
11 resolver::HasResolver, 11 resolver::HasResolver,
12 type_ref::{Mutability, TypeRef}, 12 type_ref::{Mutability, TypeRef},
13 AdtId, ConstId, DefWithBodyId, EnumId, FunctionId, GenericDefId, HasModule, ImplId, 13 AdtId, AssocContainerId, ConstId, DefWithBodyId, EnumId, FunctionId, GenericDefId, HasModule,
14 LocalEnumVariantId, LocalModuleId, LocalStructFieldId, Lookup, ModuleId, StaticId, StructId, 14 ImplId, LocalEnumVariantId, LocalModuleId, LocalStructFieldId, Lookup, ModuleId, StaticId,
15 TraitId, TypeAliasId, TypeParamId, UnionId, 15 StructId, TraitId, TypeAliasId, TypeParamId, UnionId,
16}; 16};
17use hir_expand::{ 17use hir_expand::{
18 diagnostics::DiagnosticSink, 18 diagnostics::DiagnosticSink,
@@ -25,7 +25,10 @@ use hir_ty::{
25}; 25};
26use ra_db::{CrateId, Edition, FileId}; 26use ra_db::{CrateId, Edition, FileId};
27use ra_prof::profile; 27use ra_prof::profile;
28use ra_syntax::ast::{self, AttrsOwner}; 28use ra_syntax::{
29 ast::{self, AttrsOwner},
30 AstNode,
31};
29 32
30use crate::{ 33use crate::{
31 db::{DefDatabase, HirDatabase}, 34 db::{DefDatabase, HirDatabase},
@@ -119,7 +122,9 @@ impl_froms!(
119 BuiltinType 122 BuiltinType
120); 123);
121 124
122pub use hir_def::{attr::Attrs, item_scope::ItemInNs, visibility::Visibility, AssocItemId}; 125pub use hir_def::{
126 attr::Attrs, item_scope::ItemInNs, visibility::Visibility, AssocItemId, AssocItemLoc,
127};
123use rustc_hash::FxHashSet; 128use rustc_hash::FxHashSet;
124 129
125impl Module { 130impl Module {
@@ -639,17 +644,49 @@ pub struct MacroDef {
639 pub(crate) id: MacroDefId, 644 pub(crate) id: MacroDefId,
640} 645}
641 646
647/// Invariant: `inner.as_assoc_item(db).is_some()`
648/// We do not actively enforce this invariant.
642#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] 649#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
643pub enum AssocItem { 650pub enum AssocItem {
644 Function(Function), 651 Function(Function),
645 Const(Const), 652 Const(Const),
646 TypeAlias(TypeAlias), 653 TypeAlias(TypeAlias),
647} 654}
648// FIXME: not every function, ... is actually an assoc item. maybe we should make 655pub enum AssocItemContainer {
649// sure that you can only turn actual assoc items into AssocItems. This would 656 Trait(Trait),
650// require not implementing From, and instead having some checked way of 657 ImplBlock(ImplBlock),
651// casting them, and somehow making the constructors private, which would be annoying. 658}
652impl_froms!(AssocItem: Function, Const, TypeAlias); 659pub trait AsAssocItem {
660 fn as_assoc_item(self, db: &impl DefDatabase) -> Option<AssocItem>;
661}
662
663impl AsAssocItem for Function {
664 fn as_assoc_item(self, db: &impl DefDatabase) -> Option<AssocItem> {
665 as_assoc_item(db, AssocItem::Function, self.id)
666 }
667}
668impl AsAssocItem for Const {
669 fn as_assoc_item(self, db: &impl DefDatabase) -> Option<AssocItem> {
670 as_assoc_item(db, AssocItem::Const, self.id)
671 }
672}
673impl AsAssocItem for TypeAlias {
674 fn as_assoc_item(self, db: &impl DefDatabase) -> Option<AssocItem> {
675 as_assoc_item(db, AssocItem::TypeAlias, self.id)
676 }
677}
678fn as_assoc_item<ID, DEF, CTOR, AST>(db: &impl DefDatabase, ctor: CTOR, id: ID) -> Option<AssocItem>
679where
680 ID: Lookup<Data = AssocItemLoc<AST>>,
681 DEF: From<ID>,
682 CTOR: FnOnce(DEF) -> AssocItem,
683 AST: AstNode,
684{
685 match id.lookup(db).container {
686 AssocContainerId::TraitId(_) | AssocContainerId::ImplId(_) => Some(ctor(DEF::from(id))),
687 AssocContainerId::ContainerId(_) => None,
688 }
689}
653 690
654impl AssocItem { 691impl AssocItem {
655 pub fn module(self, db: &impl DefDatabase) -> Module { 692 pub fn module(self, db: &impl DefDatabase) -> Module {
@@ -659,6 +696,18 @@ impl AssocItem {
659 AssocItem::TypeAlias(t) => t.module(db), 696 AssocItem::TypeAlias(t) => t.module(db),
660 } 697 }
661 } 698 }
699 pub fn container(self, db: &impl DefDatabase) -> AssocItemContainer {
700 let container = match self {
701 AssocItem::Function(it) => it.id.lookup(db).container,
702 AssocItem::Const(it) => it.id.lookup(db).container,
703 AssocItem::TypeAlias(it) => it.id.lookup(db).container,
704 };
705 match container {
706 AssocContainerId::TraitId(id) => AssocItemContainer::Trait(id.into()),
707 AssocContainerId::ImplId(id) => AssocItemContainer::ImplBlock(id.into()),
708 AssocContainerId::ContainerId(_) => panic!("invalid AssocItem"),
709 }
710 }
662} 711}
663 712
664#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)] 713#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
@@ -769,6 +818,7 @@ impl TypeParam {
769 } 818 }
770} 819}
771 820
821// FIXME: rename from `ImplBlock` to `Impl`
772#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 822#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
773pub struct ImplBlock { 823pub struct ImplBlock {
774 pub(crate) id: ImplId, 824 pub(crate) id: ImplId,
diff --git a/crates/ra_hir/src/lib.rs b/crates/ra_hir/src/lib.rs
index e1c7b7a20..5cd965f7a 100644
--- a/crates/ra_hir/src/lib.rs
+++ b/crates/ra_hir/src/lib.rs
@@ -39,10 +39,10 @@ mod has_source;
39 39
40pub use crate::{ 40pub use crate::{
41 code_model::{ 41 code_model::{
42 Adt, AssocItem, AttrDef, Const, Crate, CrateDependency, DefWithBody, Docs, Enum, 42 Adt, AsAssocItem, AssocItem, AssocItemContainer, AttrDef, Const, Crate, CrateDependency,
43 EnumVariant, FieldSource, Function, GenericDef, HasAttrs, HasVisibility, ImplBlock, Local, 43 DefWithBody, Docs, Enum, EnumVariant, FieldSource, Function, GenericDef, HasAttrs,
44 MacroDef, Module, ModuleDef, ScopeDef, Static, Struct, StructField, Trait, Type, TypeAlias, 44 HasVisibility, ImplBlock, Local, MacroDef, Module, ModuleDef, ScopeDef, Static, Struct,
45 TypeParam, Union, VariantDef, 45 StructField, Trait, Type, TypeAlias, TypeParam, Union, VariantDef,
46 }, 46 },
47 has_source::HasSource, 47 has_source::HasSource,
48 source_analyzer::{PathResolution, ScopeEntryWithSyntax, SourceAnalyzer}, 48 source_analyzer::{PathResolution, ScopeEntryWithSyntax, SourceAnalyzer},
diff --git a/crates/ra_hir_expand/src/lib.rs b/crates/ra_hir_expand/src/lib.rs
index 7cf3b59a7..9506f2e1c 100644
--- a/crates/ra_hir_expand/src/lib.rs
+++ b/crates/ra_hir_expand/src/lib.rs
@@ -323,11 +323,18 @@ impl<T: Clone> InFile<&T> {
323 } 323 }
324} 324}
325 325
326impl<T> InFile<Option<T>> {
327 pub fn transpose(self) -> Option<InFile<T>> {
328 let value = self.value?;
329 Some(InFile::new(self.file_id, value))
330 }
331}
332
326impl InFile<SyntaxNode> { 333impl InFile<SyntaxNode> {
327 pub fn ancestors_with_macros<'a>( 334 pub fn ancestors_with_macros(
328 self, 335 self,
329 db: &'a impl crate::db::AstDatabase, 336 db: &impl crate::db::AstDatabase,
330 ) -> impl Iterator<Item = InFile<SyntaxNode>> + 'a { 337 ) -> impl Iterator<Item = InFile<SyntaxNode>> + '_ {
331 std::iter::successors(Some(self), move |node| match node.value.parent() { 338 std::iter::successors(Some(self), move |node| match node.value.parent() {
332 Some(parent) => Some(node.with_value(parent)), 339 Some(parent) => Some(node.with_value(parent)),
333 None => { 340 None => {
@@ -338,6 +345,15 @@ impl InFile<SyntaxNode> {
338 } 345 }
339} 346}
340 347
348impl InFile<SyntaxToken> {
349 pub fn ancestors_with_macros(
350 self,
351 db: &impl crate::db::AstDatabase,
352 ) -> impl Iterator<Item = InFile<SyntaxNode>> + '_ {
353 self.map(|it| it.parent()).ancestors_with_macros(db)
354 }
355}
356
341impl<N: AstNode> InFile<N> { 357impl<N: AstNode> InFile<N> {
342 pub fn descendants<T: AstNode>(self) -> impl Iterator<Item = InFile<T>> { 358 pub fn descendants<T: AstNode>(self) -> impl Iterator<Item = InFile<T>> {
343 self.value.syntax().descendants().filter_map(T::cast).map(move |n| self.with_value(n)) 359 self.value.syntax().descendants().filter_map(T::cast).map(move |n| self.with_value(n))
diff --git a/crates/ra_ide/src/goto_type_definition.rs b/crates/ra_ide/src/goto_type_definition.rs
index 11ad6d137..69940fc36 100644
--- a/crates/ra_ide/src/goto_type_definition.rs
+++ b/crates/ra_ide/src/goto_type_definition.rs
@@ -16,24 +16,16 @@ pub(crate) fn goto_type_definition(
16 let token = pick_best(file.token_at_offset(position.offset))?; 16 let token = pick_best(file.token_at_offset(position.offset))?;
17 let token = descend_into_macros(db, position.file_id, token); 17 let token = descend_into_macros(db, position.file_id, token);
18 18
19 let node = token.value.ancestors().find_map(|token| { 19 let node = token
20 token 20 .value
21 .ancestors() 21 .ancestors()
22 .find(|n| ast::Expr::cast(n.clone()).is_some() || ast::Pat::cast(n.clone()).is_some()) 22 .find(|n| ast::Expr::cast(n.clone()).is_some() || ast::Pat::cast(n.clone()).is_some())?;
23 })?;
24 23
25 let analyzer = hir::SourceAnalyzer::new(db, token.with_value(&node), None); 24 let analyzer = hir::SourceAnalyzer::new(db, token.with_value(&node), None);
26 25
27 let ty: hir::Type = if let Some(ty) = 26 let ty: hir::Type = ast::Expr::cast(node.clone())
28 ast::Expr::cast(node.clone()).and_then(|e| analyzer.type_of(db, &e)) 27 .and_then(|e| analyzer.type_of(db, &e))
29 { 28 .or_else(|| ast::Pat::cast(node.clone()).and_then(|p| analyzer.type_of_pat(db, &p)))?;
30 ty
31 } else if let Some(ty) = ast::Pat::cast(node.clone()).and_then(|p| analyzer.type_of_pat(db, &p))
32 {
33 ty
34 } else {
35 return None;
36 };
37 29
38 let adt_def = ty.autoderef(db).find_map(|ty| ty.as_adt())?; 30 let adt_def = ty.autoderef(db).find_map(|ty| ty.as_adt())?;
39 31
diff --git a/crates/ra_lsp_server/src/main_loop/handlers.rs b/crates/ra_lsp_server/src/main_loop/handlers.rs
index 59c86bbfa..2e598fdcd 100644
--- a/crates/ra_lsp_server/src/main_loop/handlers.rs
+++ b/crates/ra_lsp_server/src/main_loop/handlers.rs
@@ -758,7 +758,7 @@ pub fn handle_code_lens(
758 // Gather runnables 758 // Gather runnables
759 for runnable in world.analysis().runnables(file_id)? { 759 for runnable in world.analysis().runnables(file_id)? {
760 let title = match &runnable.kind { 760 let title = match &runnable.kind {
761 RunnableKind::Test { .. } | RunnableKind::TestMod { .. } => "▶️Run Test", 761 RunnableKind::Test { .. } | RunnableKind::TestMod { .. } => "▶️\u{fe0e}Run Test",
762 RunnableKind::Bench { .. } => "Run Bench", 762 RunnableKind::Bench { .. } => "Run Bench",
763 RunnableKind::Bin => "Run", 763 RunnableKind::Bin => "Run",
764 } 764 }
diff --git a/crates/ra_parser/src/grammar/params.rs b/crates/ra_parser/src/grammar/params.rs
index ed4f93347..272661b1d 100644
--- a/crates/ra_parser/src/grammar/params.rs
+++ b/crates/ra_parser/src/grammar/params.rs
@@ -114,8 +114,11 @@ fn value_parameter(p: &mut Parser, flavor: Flavor) {
114 // test fn_pointer_param_ident_path 114 // test fn_pointer_param_ident_path
115 // type Foo = fn(Bar::Baz); 115 // type Foo = fn(Bar::Baz);
116 // type Qux = fn(baz: Bar::Baz); 116 // type Qux = fn(baz: Bar::Baz);
117
118 // test fn_pointer_unnamed_arg
119 // type Foo = fn(_: bar);
117 Flavor::FnPointer => { 120 Flavor::FnPointer => {
118 if p.at(IDENT) && p.nth(1) == T![:] && !p.nth_at(1, T![::]) { 121 if (p.at(IDENT) || p.at(UNDERSCORE)) && p.nth(1) == T![:] && !p.nth_at(1, T![::]) {
119 patterns::pattern_single(p); 122 patterns::pattern_single(p);
120 types::ascription(p); 123 types::ascription(p);
121 } else { 124 } else {
diff --git a/crates/ra_project_model/Cargo.toml b/crates/ra_project_model/Cargo.toml
index 69edc3c66..653d5bd14 100644
--- a/crates/ra_project_model/Cargo.toml
+++ b/crates/ra_project_model/Cargo.toml
@@ -19,3 +19,5 @@ ra_cfg = { path = "../ra_cfg" }
19 19
20serde = { version = "1.0.89", features = ["derive"] } 20serde = { version = "1.0.89", features = ["derive"] }
21serde_json = "1.0.39" 21serde_json = "1.0.39"
22
23anyhow = "1.0.26"
diff --git a/crates/ra_project_model/src/cargo_workspace.rs b/crates/ra_project_model/src/cargo_workspace.rs
index 60cb8c1eb..22d226a74 100644
--- a/crates/ra_project_model/src/cargo_workspace.rs
+++ b/crates/ra_project_model/src/cargo_workspace.rs
@@ -2,14 +2,13 @@
2 2
3use std::path::{Path, PathBuf}; 3use std::path::{Path, PathBuf};
4 4
5use anyhow::{Context, Result};
5use cargo_metadata::{CargoOpt, MetadataCommand}; 6use cargo_metadata::{CargoOpt, MetadataCommand};
6use ra_arena::{impl_arena_id, Arena, RawId}; 7use ra_arena::{impl_arena_id, Arena, RawId};
7use ra_db::Edition; 8use ra_db::Edition;
8use rustc_hash::FxHashMap; 9use rustc_hash::FxHashMap;
9use serde::Deserialize; 10use serde::Deserialize;
10 11
11use crate::Result;
12
13/// `CargoWorkspace` represents the logical structure of, well, a Cargo 12/// `CargoWorkspace` represents the logical structure of, well, a Cargo
14/// workspace. It pretty closely mirrors `cargo metadata` output. 13/// workspace. It pretty closely mirrors `cargo metadata` output.
15/// 14///
@@ -171,7 +170,9 @@ impl CargoWorkspace {
171 if let Some(parent) = cargo_toml.parent() { 170 if let Some(parent) = cargo_toml.parent() {
172 meta.current_dir(parent); 171 meta.current_dir(parent);
173 } 172 }
174 let meta = meta.exec().map_err(|e| format!("cargo metadata failed: {}", e))?; 173 let meta = meta.exec().with_context(|| {
174 format!("Failed to run `cargo metadata --manifest-path {}`", cargo_toml.display())
175 })?;
175 let mut pkg_by_id = FxHashMap::default(); 176 let mut pkg_by_id = FxHashMap::default();
176 let mut packages = Arena::default(); 177 let mut packages = Arena::default();
177 let mut targets = Arena::default(); 178 let mut targets = Arena::default();
@@ -181,7 +182,9 @@ impl CargoWorkspace {
181 for meta_pkg in meta.packages { 182 for meta_pkg in meta.packages {
182 let cargo_metadata::Package { id, edition, name, manifest_path, .. } = meta_pkg; 183 let cargo_metadata::Package { id, edition, name, manifest_path, .. } = meta_pkg;
183 let is_member = ws_members.contains(&id); 184 let is_member = ws_members.contains(&id);
184 let edition = edition.parse::<Edition>()?; 185 let edition = edition
186 .parse::<Edition>()
187 .with_context(|| format!("Failed to parse edition {}", edition))?;
185 let pkg = packages.alloc(PackageData { 188 let pkg = packages.alloc(PackageData {
186 name, 189 name,
187 manifest: manifest_path, 190 manifest: manifest_path,
diff --git a/crates/ra_project_model/src/lib.rs b/crates/ra_project_model/src/lib.rs
index bc1d15406..fef405b7f 100644
--- a/crates/ra_project_model/src/lib.rs
+++ b/crates/ra_project_model/src/lib.rs
@@ -12,6 +12,7 @@ use std::{
12 process::Command, 12 process::Command,
13}; 13};
14 14
15use anyhow::{bail, Context, Result};
15use ra_cfg::CfgOptions; 16use ra_cfg::CfgOptions;
16use ra_db::{CrateGraph, CrateId, CrateName, Edition, Env, FileId}; 17use ra_db::{CrateGraph, CrateId, CrateName, Edition, Env, FileId};
17use rustc_hash::FxHashMap; 18use rustc_hash::FxHashMap;
@@ -23,8 +24,6 @@ pub use crate::{
23 sysroot::Sysroot, 24 sysroot::Sysroot,
24}; 25};
25 26
26pub type Result<T> = ::std::result::Result<T, Box<dyn Error + Send + Sync>>;
27
28#[derive(Clone, PartialEq, Eq, Hash, Debug)] 27#[derive(Clone, PartialEq, Eq, Hash, Debug)]
29pub struct CargoTomlNotFoundError(pub PathBuf); 28pub struct CargoTomlNotFoundError(pub PathBuf);
30 29
@@ -81,15 +80,36 @@ impl ProjectWorkspace {
81 ) -> Result<ProjectWorkspace> { 80 ) -> Result<ProjectWorkspace> {
82 match find_rust_project_json(path) { 81 match find_rust_project_json(path) {
83 Some(json_path) => { 82 Some(json_path) => {
84 let file = File::open(json_path)?; 83 let file = File::open(&json_path)
84 .with_context(|| format!("Failed to open json file {}", json_path.display()))?;
85 let reader = BufReader::new(file); 85 let reader = BufReader::new(file);
86 Ok(ProjectWorkspace::Json { project: from_reader(reader)? }) 86 Ok(ProjectWorkspace::Json {
87 project: from_reader(reader).with_context(|| {
88 format!("Failed to deserialize json file {}", json_path.display())
89 })?,
90 })
87 } 91 }
88 None => { 92 None => {
89 let cargo_toml = find_cargo_toml(path)?; 93 let cargo_toml = find_cargo_toml(path).with_context(|| {
90 let cargo = CargoWorkspace::from_cargo_metadata(&cargo_toml, cargo_features)?; 94 format!("Failed to find Cargo.toml for path {}", path.display())
91 let sysroot = 95 })?;
92 if with_sysroot { Sysroot::discover(&cargo_toml)? } else { Sysroot::default() }; 96 let cargo = CargoWorkspace::from_cargo_metadata(&cargo_toml, cargo_features)
97 .with_context(|| {
98 format!(
99 "Failed to read Cargo metadata from Cargo.toml file {}",
100 cargo_toml.display()
101 )
102 })?;
103 let sysroot = if with_sysroot {
104 Sysroot::discover(&cargo_toml).with_context(|| {
105 format!(
106 "Failed to find sysroot for Cargo.toml file {}",
107 cargo_toml.display()
108 )
109 })?
110 } else {
111 Sysroot::default()
112 };
93 Ok(ProjectWorkspace::Cargo { cargo, sysroot }) 113 Ok(ProjectWorkspace::Cargo { cargo, sysroot })
94 } 114 }
95 } 115 }
@@ -403,11 +423,20 @@ pub fn get_rustc_cfg_options() -> CfgOptions {
403 } 423 }
404 } 424 }
405 425
406 match (|| -> Result<_> { 426 match (|| -> Result<String> {
407 // `cfg(test)` and `cfg(debug_assertion)` are handled outside, so we suppress them here. 427 // `cfg(test)` and `cfg(debug_assertion)` are handled outside, so we suppress them here.
408 let output = Command::new("rustc").args(&["--print", "cfg", "-O"]).output()?; 428 let output = Command::new("rustc")
429 .args(&["--print", "cfg", "-O"])
430 .output()
431 .context("Failed to get output from rustc --print cfg -O")?;
409 if !output.status.success() { 432 if !output.status.success() {
410 Err("failed to get rustc cfgs")?; 433 bail!(
434 "rustc --print cfg -O exited with exit code ({})",
435 output
436 .status
437 .code()
438 .map_or(String::from("no exit code"), |code| format!("{}", code))
439 );
411 } 440 }
412 Ok(String::from_utf8(output.stdout)?) 441 Ok(String::from_utf8(output.stdout)?)
413 })() { 442 })() {
diff --git a/crates/ra_project_model/src/sysroot.rs b/crates/ra_project_model/src/sysroot.rs
index a23265fc0..7b9cc899c 100644
--- a/crates/ra_project_model/src/sysroot.rs
+++ b/crates/ra_project_model/src/sysroot.rs
@@ -1,5 +1,6 @@
1//! FIXME: write short doc here 1//! FIXME: write short doc here
2 2
3use anyhow::{anyhow, bail, Context, Result};
3use std::{ 4use std::{
4 env, 5 env,
5 path::{Path, PathBuf}, 6 path::{Path, PathBuf},
@@ -8,8 +9,6 @@ use std::{
8 9
9use ra_arena::{impl_arena_id, Arena, RawId}; 10use ra_arena::{impl_arena_id, Arena, RawId};
10 11
11use crate::Result;
12
13#[derive(Default, Debug, Clone)] 12#[derive(Default, Debug, Clone)]
14pub struct Sysroot { 13pub struct Sysroot {
15 crates: Arena<SysrootCrate, SysrootCrateData>, 14 crates: Arena<SysrootCrate, SysrootCrateData>,
@@ -51,7 +50,7 @@ impl Sysroot {
51 let src = try_find_src_path(cargo_toml)?; 50 let src = try_find_src_path(cargo_toml)?;
52 51
53 if !src.exists() { 52 if !src.exists() {
54 Err(format!( 53 Err(anyhow!(
55 "can't load standard library from sysroot\n\ 54 "can't load standard library from sysroot\n\
56 {}\n\ 55 {}\n\
57 (discovered via `rustc --print sysroot`)\n\ 56 (discovered via `rustc --print sysroot`)\n\
@@ -100,9 +99,14 @@ fn try_find_src_path(cargo_toml: &Path) -> Result<PathBuf> {
100 .current_dir(cargo_toml.parent().unwrap()) 99 .current_dir(cargo_toml.parent().unwrap())
101 .args(&["--print", "sysroot"]) 100 .args(&["--print", "sysroot"])
102 .output() 101 .output()
103 .map_err(|e| format!("rustc --print sysroot failed: {}", e))?; 102 .context("rustc --print sysroot failed")?;
104 if !rustc_output.status.success() { 103 if !rustc_output.status.success() {
105 Err("failed to locate sysroot")?; 104 match rustc_output.status.code() {
105 Some(code) => {
106 bail!("failed to locate sysroot: rustc --print sysroot exited with code {}", code)
107 }
108 None => bail!("failed to locate sysroot: rustc --print sysroot terminated by signal"),
109 };
106 } 110 }
107 let stdout = String::from_utf8(rustc_output.stdout)?; 111 let stdout = String::from_utf8(rustc_output.stdout)?;
108 let sysroot_path = Path::new(stdout.trim()); 112 let sysroot_path = Path::new(stdout.trim());
diff --git a/crates/ra_syntax/src/ast.rs b/crates/ra_syntax/src/ast.rs
index d3e8888bd..9cc7930f7 100644
--- a/crates/ra_syntax/src/ast.rs
+++ b/crates/ra_syntax/src/ast.rs
@@ -18,8 +18,8 @@ use crate::{
18pub use self::{ 18pub use self::{
19 expr_extensions::{ArrayExprKind, BinOp, ElseBranch, LiteralKind, PrefixOp, RangeOp}, 19 expr_extensions::{ArrayExprKind, BinOp, ElseBranch, LiteralKind, PrefixOp, RangeOp},
20 extensions::{ 20 extensions::{
21 FieldKind, PathSegmentKind, SelfParamKind, SlicePatComponents, StructKind, TypeBoundKind, 21 AttrKind, FieldKind, PathSegmentKind, SelfParamKind, SlicePatComponents, StructKind,
22 VisibilityKind, 22 TypeBoundKind, VisibilityKind,
23 }, 23 },
24 generated::*, 24 generated::*,
25 tokens::*, 25 tokens::*,
@@ -217,10 +217,7 @@ fn test_doc_comment_multi_line_block_strips_suffix() {
217#[test] 217#[test]
218fn test_comments_preserve_trailing_whitespace() { 218fn test_comments_preserve_trailing_whitespace() {
219 let file = SourceFile::parse( 219 let file = SourceFile::parse(
220 r#" 220 "\n/// Representation of a Realm. \n/// In the specification these are called Realm Records.\nstruct Realm {}",
221/// Representation of a Realm.
222/// In the specification these are called Realm Records.
223struct Realm {}"#,
224 ) 221 )
225 .ok() 222 .ok()
226 .unwrap(); 223 .unwrap();
diff --git a/crates/ra_syntax/src/ast/extensions.rs b/crates/ra_syntax/src/ast/extensions.rs
index 7dcf084de..44de4af89 100644
--- a/crates/ra_syntax/src/ast/extensions.rs
+++ b/crates/ra_syntax/src/ast/extensions.rs
@@ -37,6 +37,12 @@ fn text_of_first_token(node: &SyntaxNode) -> &SmolStr {
37 node.green().children().next().and_then(|it| it.into_token()).unwrap().text() 37 node.green().children().next().and_then(|it| it.into_token()).unwrap().text()
38} 38}
39 39
40#[derive(Debug, Clone, PartialEq, Eq)]
41pub enum AttrKind {
42 Inner,
43 Outer,
44}
45
40impl ast::Attr { 46impl ast::Attr {
41 pub fn as_simple_atom(&self) -> Option<SmolStr> { 47 pub fn as_simple_atom(&self) -> Option<SmolStr> {
42 match self.input() { 48 match self.input() {
@@ -71,6 +77,18 @@ impl ast::Attr {
71 _ => None, 77 _ => None,
72 } 78 }
73 } 79 }
80
81 pub fn kind(&self) -> AttrKind {
82 let first_token = self.syntax().first_token();
83 let first_token_kind = first_token.as_ref().map(SyntaxToken::kind);
84 let second_token_kind =
85 first_token.and_then(|token| token.next_token()).as_ref().map(SyntaxToken::kind);
86
87 match (first_token_kind, second_token_kind) {
88 (Some(SyntaxKind::POUND), Some(SyntaxKind::EXCL)) => AttrKind::Inner,
89 _ => AttrKind::Outer,
90 }
91 }
74} 92}
75 93
76#[derive(Debug, Clone, PartialEq, Eq)] 94#[derive(Debug, Clone, PartialEq, Eq)]
diff --git a/crates/ra_syntax/test_data/parser/inline/ok/0157_fn_pointer_unnamed_arg.rs b/crates/ra_syntax/test_data/parser/inline/ok/0157_fn_pointer_unnamed_arg.rs
new file mode 100644
index 000000000..1ebbe5b03
--- /dev/null
+++ b/crates/ra_syntax/test_data/parser/inline/ok/0157_fn_pointer_unnamed_arg.rs
@@ -0,0 +1 @@
type Foo = fn(_: bar);
diff --git a/crates/ra_syntax/test_data/parser/inline/ok/0157_fn_pointer_unnamed_arg.txt b/crates/ra_syntax/test_data/parser/inline/ok/0157_fn_pointer_unnamed_arg.txt
new file mode 100644
index 000000000..52d8f21a4
--- /dev/null
+++ b/crates/ra_syntax/test_data/parser/inline/ok/0157_fn_pointer_unnamed_arg.txt
@@ -0,0 +1,26 @@
1SOURCE_FILE@[0; 23)
2 TYPE_ALIAS_DEF@[0; 22)
3 TYPE_KW@[0; 4) "type"
4 WHITESPACE@[4; 5) " "
5 NAME@[5; 8)
6 IDENT@[5; 8) "Foo"
7 WHITESPACE@[8; 9) " "
8 EQ@[9; 10) "="
9 WHITESPACE@[10; 11) " "
10 FN_POINTER_TYPE@[11; 21)
11 FN_KW@[11; 13) "fn"
12 PARAM_LIST@[13; 21)
13 L_PAREN@[13; 14) "("
14 PARAM@[14; 20)
15 PLACEHOLDER_PAT@[14; 15)
16 UNDERSCORE@[14; 15) "_"
17 COLON@[15; 16) ":"
18 WHITESPACE@[16; 17) " "
19 PATH_TYPE@[17; 20)
20 PATH@[17; 20)
21 PATH_SEGMENT@[17; 20)
22 NAME_REF@[17; 20)
23 IDENT@[17; 20) "bar"
24 R_PAREN@[20; 21) ")"
25 SEMI@[21; 22) ";"
26 WHITESPACE@[22; 23) "\n"