diff options
Diffstat (limited to 'crates')
25 files changed, 716 insertions, 609 deletions
diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index 5da6a0340..eb7865c84 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs | |||
@@ -852,6 +852,7 @@ impl Function { | |||
852 | }) | 852 | }) |
853 | .collect() | 853 | .collect() |
854 | } | 854 | } |
855 | |||
855 | pub fn method_params(self, db: &dyn HirDatabase) -> Option<Vec<Param>> { | 856 | pub fn method_params(self, db: &dyn HirDatabase) -> Option<Vec<Param>> { |
856 | if self.self_param(db).is_none() { | 857 | if self.self_param(db).is_none() { |
857 | return None; | 858 | return None; |
@@ -909,7 +910,7 @@ impl From<hir_ty::Mutability> for Access { | |||
909 | } | 910 | } |
910 | } | 911 | } |
911 | 912 | ||
912 | #[derive(Debug)] | 913 | #[derive(Clone, Debug)] |
913 | pub struct Param { | 914 | pub struct Param { |
914 | func: Function, | 915 | func: Function, |
915 | /// The index in parameter list, including self parameter. | 916 | /// The index in parameter list, including self parameter. |
@@ -922,13 +923,25 @@ impl Param { | |||
922 | &self.ty | 923 | &self.ty |
923 | } | 924 | } |
924 | 925 | ||
926 | pub fn as_local(&self, db: &dyn HirDatabase) -> Local { | ||
927 | let parent = DefWithBodyId::FunctionId(self.func.into()); | ||
928 | let body = db.body(parent); | ||
929 | Local { parent, pat_id: body.params[self.idx] } | ||
930 | } | ||
931 | |||
925 | pub fn pattern_source(&self, db: &dyn HirDatabase) -> Option<ast::Pat> { | 932 | pub fn pattern_source(&self, db: &dyn HirDatabase) -> Option<ast::Pat> { |
926 | let params = self.func.source(db)?.value.param_list()?; | 933 | self.source(db).and_then(|p| p.value.pat()) |
934 | } | ||
935 | |||
936 | pub fn source(&self, db: &dyn HirDatabase) -> Option<InFile<ast::Param>> { | ||
937 | let InFile { file_id, value } = self.func.source(db)?; | ||
938 | let params = value.param_list()?; | ||
927 | if params.self_param().is_some() { | 939 | if params.self_param().is_some() { |
928 | params.params().nth(self.idx.checked_sub(1)?)?.pat() | 940 | params.params().nth(self.idx.checked_sub(1)?) |
929 | } else { | 941 | } else { |
930 | params.params().nth(self.idx)?.pat() | 942 | params.params().nth(self.idx) |
931 | } | 943 | } |
944 | .map(|value| InFile { file_id, value }) | ||
932 | } | 945 | } |
933 | } | 946 | } |
934 | 947 | ||
@@ -960,6 +973,14 @@ impl SelfParam { | |||
960 | Access::Owned => "self", | 973 | Access::Owned => "self", |
961 | } | 974 | } |
962 | } | 975 | } |
976 | |||
977 | pub fn source(&self, db: &dyn HirDatabase) -> Option<InFile<ast::SelfParam>> { | ||
978 | let InFile { file_id, value } = Function::from(self.func).source(db)?; | ||
979 | value | ||
980 | .param_list() | ||
981 | .and_then(|params| params.self_param()) | ||
982 | .map(|value| InFile { file_id, value }) | ||
983 | } | ||
963 | } | 984 | } |
964 | 985 | ||
965 | impl HasVisibility for Function { | 986 | impl HasVisibility for Function { |
@@ -1335,6 +1356,13 @@ impl Local { | |||
1335 | } | 1356 | } |
1336 | } | 1357 | } |
1337 | 1358 | ||
1359 | pub fn as_self_param(self, db: &dyn HirDatabase) -> Option<SelfParam> { | ||
1360 | match self.parent { | ||
1361 | DefWithBodyId::FunctionId(func) if self.is_self(db) => Some(SelfParam { func }), | ||
1362 | _ => None, | ||
1363 | } | ||
1364 | } | ||
1365 | |||
1338 | // FIXME: why is this an option? It shouldn't be? | 1366 | // FIXME: why is this an option? It shouldn't be? |
1339 | pub fn name(self, db: &dyn HirDatabase) -> Option<Name> { | 1367 | pub fn name(self, db: &dyn HirDatabase) -> Option<Name> { |
1340 | let body = db.body(self.parent); | 1368 | let body = db.body(self.parent); |
diff --git a/crates/hir_def/src/nameres/collector.rs b/crates/hir_def/src/nameres/collector.rs index 28b73c3a1..d8fabe49b 100644 --- a/crates/hir_def/src/nameres/collector.rs +++ b/crates/hir_def/src/nameres/collector.rs | |||
@@ -91,7 +91,6 @@ pub(super) fn collect_defs( | |||
91 | resolved_imports: Vec::new(), | 91 | resolved_imports: Vec::new(), |
92 | 92 | ||
93 | unexpanded_macros: Vec::new(), | 93 | unexpanded_macros: Vec::new(), |
94 | unexpanded_attribute_macros: Vec::new(), | ||
95 | mod_dirs: FxHashMap::default(), | 94 | mod_dirs: FxHashMap::default(), |
96 | cfg_options, | 95 | cfg_options, |
97 | proc_macros, | 96 | proc_macros, |
@@ -202,15 +201,14 @@ struct ImportDirective { | |||
202 | #[derive(Clone, Debug, Eq, PartialEq)] | 201 | #[derive(Clone, Debug, Eq, PartialEq)] |
203 | struct MacroDirective { | 202 | struct MacroDirective { |
204 | module_id: LocalModuleId, | 203 | module_id: LocalModuleId, |
205 | ast_id: AstIdWithPath<ast::MacroCall>, | ||
206 | legacy: Option<MacroCallId>, | ||
207 | depth: usize, | 204 | depth: usize, |
205 | kind: MacroDirectiveKind, | ||
208 | } | 206 | } |
209 | 207 | ||
210 | #[derive(Clone, Debug, Eq, PartialEq)] | 208 | #[derive(Clone, Debug, Eq, PartialEq)] |
211 | struct DeriveDirective { | 209 | enum MacroDirectiveKind { |
212 | module_id: LocalModuleId, | 210 | FnLike { ast_id: AstIdWithPath<ast::MacroCall>, legacy: Option<MacroCallId> }, |
213 | ast_id: AstIdWithPath<ast::Item>, | 211 | Derive { ast_id: AstIdWithPath<ast::Item> }, |
214 | } | 212 | } |
215 | 213 | ||
216 | struct DefData<'a> { | 214 | struct DefData<'a> { |
@@ -228,7 +226,6 @@ struct DefCollector<'a> { | |||
228 | unresolved_imports: Vec<ImportDirective>, | 226 | unresolved_imports: Vec<ImportDirective>, |
229 | resolved_imports: Vec<ImportDirective>, | 227 | resolved_imports: Vec<ImportDirective>, |
230 | unexpanded_macros: Vec<MacroDirective>, | 228 | unexpanded_macros: Vec<MacroDirective>, |
231 | unexpanded_attribute_macros: Vec<DeriveDirective>, | ||
232 | mod_dirs: FxHashMap<LocalModuleId, ModDir>, | 229 | mod_dirs: FxHashMap<LocalModuleId, ModDir>, |
233 | cfg_options: &'a CfgOptions, | 230 | cfg_options: &'a CfgOptions, |
234 | /// List of procedural macros defined by this crate. This is read from the dynamic library | 231 | /// List of procedural macros defined by this crate. This is read from the dynamic library |
@@ -782,60 +779,58 @@ impl DefCollector<'_> { | |||
782 | 779 | ||
783 | fn resolve_macros(&mut self) -> ReachedFixedPoint { | 780 | fn resolve_macros(&mut self) -> ReachedFixedPoint { |
784 | let mut macros = std::mem::replace(&mut self.unexpanded_macros, Vec::new()); | 781 | let mut macros = std::mem::replace(&mut self.unexpanded_macros, Vec::new()); |
785 | let mut attribute_macros = | ||
786 | std::mem::replace(&mut self.unexpanded_attribute_macros, Vec::new()); | ||
787 | let mut resolved = Vec::new(); | 782 | let mut resolved = Vec::new(); |
788 | let mut res = ReachedFixedPoint::Yes; | 783 | let mut res = ReachedFixedPoint::Yes; |
789 | macros.retain(|directive| { | 784 | macros.retain(|directive| { |
790 | if let Some(call_id) = directive.legacy { | 785 | match &directive.kind { |
791 | res = ReachedFixedPoint::No; | 786 | MacroDirectiveKind::FnLike { ast_id, legacy } => { |
792 | resolved.push((directive.module_id, call_id, directive.depth)); | 787 | if let Some(call_id) = legacy { |
793 | return false; | 788 | res = ReachedFixedPoint::No; |
794 | } | 789 | resolved.push((directive.module_id, *call_id, directive.depth)); |
790 | return false; | ||
791 | } | ||
795 | 792 | ||
796 | match macro_call_as_call_id( | 793 | match macro_call_as_call_id( |
797 | &directive.ast_id, | 794 | ast_id, |
798 | self.db, | ||
799 | self.def_map.krate, | ||
800 | |path| { | ||
801 | let resolved_res = self.def_map.resolve_path_fp_with_macro( | ||
802 | self.db, | 795 | self.db, |
803 | ResolveMode::Other, | 796 | self.def_map.krate, |
804 | directive.module_id, | 797 | |path| { |
805 | &path, | 798 | let resolved_res = self.def_map.resolve_path_fp_with_macro( |
806 | BuiltinShadowMode::Module, | 799 | self.db, |
807 | ); | 800 | ResolveMode::Other, |
808 | resolved_res.resolved_def.take_macros() | 801 | directive.module_id, |
809 | }, | 802 | &path, |
810 | &mut |_err| (), | 803 | BuiltinShadowMode::Module, |
811 | ) { | 804 | ); |
812 | Ok(Ok(call_id)) => { | 805 | resolved_res.resolved_def.take_macros() |
813 | resolved.push((directive.module_id, call_id, directive.depth)); | 806 | }, |
814 | res = ReachedFixedPoint::No; | 807 | &mut |_err| (), |
815 | return false; | 808 | ) { |
809 | Ok(Ok(call_id)) => { | ||
810 | resolved.push((directive.module_id, call_id, directive.depth)); | ||
811 | res = ReachedFixedPoint::No; | ||
812 | return false; | ||
813 | } | ||
814 | Err(UnresolvedMacro) | Ok(Err(_)) => {} | ||
815 | } | ||
816 | } | 816 | } |
817 | Err(UnresolvedMacro) | Ok(Err(_)) => {} | 817 | MacroDirectiveKind::Derive { ast_id } => { |
818 | } | 818 | match derive_macro_as_call_id(ast_id, self.db, self.def_map.krate, |path| { |
819 | 819 | self.resolve_derive_macro(directive.module_id, &path) | |
820 | true | 820 | }) { |
821 | }); | 821 | Ok(call_id) => { |
822 | attribute_macros.retain(|directive| { | 822 | resolved.push((directive.module_id, call_id, 0)); |
823 | match derive_macro_as_call_id(&directive.ast_id, self.db, self.def_map.krate, |path| { | 823 | res = ReachedFixedPoint::No; |
824 | self.resolve_derive_macro(&directive, &path) | 824 | return false; |
825 | }) { | 825 | } |
826 | Ok(call_id) => { | 826 | Err(UnresolvedMacro) => (), |
827 | resolved.push((directive.module_id, call_id, 0)); | 827 | } |
828 | res = ReachedFixedPoint::No; | ||
829 | return false; | ||
830 | } | 828 | } |
831 | Err(UnresolvedMacro) => (), | ||
832 | } | 829 | } |
833 | 830 | ||
834 | true | 831 | true |
835 | }); | 832 | }); |
836 | |||
837 | self.unexpanded_macros = macros; | 833 | self.unexpanded_macros = macros; |
838 | self.unexpanded_attribute_macros = attribute_macros; | ||
839 | 834 | ||
840 | for (module_id, macro_call_id, depth) in resolved { | 835 | for (module_id, macro_call_id, depth) in resolved { |
841 | self.collect_macro_expansion(module_id, macro_call_id, depth); | 836 | self.collect_macro_expansion(module_id, macro_call_id, depth); |
@@ -844,15 +839,11 @@ impl DefCollector<'_> { | |||
844 | res | 839 | res |
845 | } | 840 | } |
846 | 841 | ||
847 | fn resolve_derive_macro( | 842 | fn resolve_derive_macro(&self, module: LocalModuleId, path: &ModPath) -> Option<MacroDefId> { |
848 | &self, | ||
849 | directive: &DeriveDirective, | ||
850 | path: &ModPath, | ||
851 | ) -> Option<MacroDefId> { | ||
852 | let resolved_res = self.def_map.resolve_path_fp_with_macro( | 843 | let resolved_res = self.def_map.resolve_path_fp_with_macro( |
853 | self.db, | 844 | self.db, |
854 | ResolveMode::Other, | 845 | ResolveMode::Other, |
855 | directive.module_id, | 846 | module, |
856 | &path, | 847 | &path, |
857 | BuiltinShadowMode::Module, | 848 | BuiltinShadowMode::Module, |
858 | ); | 849 | ); |
@@ -912,33 +903,35 @@ impl DefCollector<'_> { | |||
912 | // Emit diagnostics for all remaining unexpanded macros. | 903 | // Emit diagnostics for all remaining unexpanded macros. |
913 | 904 | ||
914 | for directive in &self.unexpanded_macros { | 905 | for directive in &self.unexpanded_macros { |
915 | let mut error = None; | 906 | match &directive.kind { |
916 | match macro_call_as_call_id( | 907 | MacroDirectiveKind::FnLike { ast_id, .. } => match macro_call_as_call_id( |
917 | &directive.ast_id, | 908 | ast_id, |
918 | self.db, | 909 | self.db, |
919 | self.def_map.krate, | 910 | self.def_map.krate, |
920 | |path| { | 911 | |path| { |
921 | let resolved_res = self.def_map.resolve_path_fp_with_macro( | 912 | let resolved_res = self.def_map.resolve_path_fp_with_macro( |
922 | self.db, | 913 | self.db, |
923 | ResolveMode::Other, | 914 | ResolveMode::Other, |
924 | directive.module_id, | 915 | directive.module_id, |
925 | &path, | 916 | &path, |
926 | BuiltinShadowMode::Module, | 917 | BuiltinShadowMode::Module, |
927 | ); | 918 | ); |
928 | resolved_res.resolved_def.take_macros() | 919 | resolved_res.resolved_def.take_macros() |
929 | }, | 920 | }, |
930 | &mut |e| { | 921 | &mut |_| (), |
931 | error.get_or_insert(e); | 922 | ) { |
923 | Ok(_) => (), | ||
924 | Err(UnresolvedMacro) => { | ||
925 | self.def_map.diagnostics.push(DefDiagnostic::unresolved_macro_call( | ||
926 | directive.module_id, | ||
927 | ast_id.ast_id, | ||
928 | )); | ||
929 | } | ||
932 | }, | 930 | }, |
933 | ) { | 931 | MacroDirectiveKind::Derive { .. } => { |
934 | Ok(_) => (), | 932 | // FIXME: we might want to diagnose this too |
935 | Err(UnresolvedMacro) => { | ||
936 | self.def_map.diagnostics.push(DefDiagnostic::unresolved_macro_call( | ||
937 | directive.module_id, | ||
938 | directive.ast_id.ast_id, | ||
939 | )); | ||
940 | } | 933 | } |
941 | }; | 934 | } |
942 | } | 935 | } |
943 | 936 | ||
944 | // Emit diagnostics for all remaining unresolved imports. | 937 | // Emit diagnostics for all remaining unresolved imports. |
@@ -1380,9 +1373,11 @@ impl ModCollector<'_, '_> { | |||
1380 | Some(derive_macros) => { | 1373 | Some(derive_macros) => { |
1381 | for path in derive_macros { | 1374 | for path in derive_macros { |
1382 | let ast_id = AstIdWithPath::new(self.file_id, ast_id, path); | 1375 | let ast_id = AstIdWithPath::new(self.file_id, ast_id, path); |
1383 | self.def_collector | 1376 | self.def_collector.unexpanded_macros.push(MacroDirective { |
1384 | .unexpanded_attribute_macros | 1377 | module_id: self.module_id, |
1385 | .push(DeriveDirective { module_id: self.module_id, ast_id }); | 1378 | depth: self.macro_depth + 1, |
1379 | kind: MacroDirectiveKind::Derive { ast_id }, | ||
1380 | }); | ||
1386 | } | 1381 | } |
1387 | } | 1382 | } |
1388 | None => { | 1383 | None => { |
@@ -1497,9 +1492,8 @@ impl ModCollector<'_, '_> { | |||
1497 | 1492 | ||
1498 | self.def_collector.unexpanded_macros.push(MacroDirective { | 1493 | self.def_collector.unexpanded_macros.push(MacroDirective { |
1499 | module_id: self.module_id, | 1494 | module_id: self.module_id, |
1500 | ast_id, | ||
1501 | legacy: None, | ||
1502 | depth: self.macro_depth + 1, | 1495 | depth: self.macro_depth + 1, |
1496 | kind: MacroDirectiveKind::FnLike { ast_id, legacy: None }, | ||
1503 | }); | 1497 | }); |
1504 | } | 1498 | } |
1505 | 1499 | ||
@@ -1542,7 +1536,6 @@ mod tests { | |||
1542 | unresolved_imports: Vec::new(), | 1536 | unresolved_imports: Vec::new(), |
1543 | resolved_imports: Vec::new(), | 1537 | resolved_imports: Vec::new(), |
1544 | unexpanded_macros: Vec::new(), | 1538 | unexpanded_macros: Vec::new(), |
1545 | unexpanded_attribute_macros: Vec::new(), | ||
1546 | mod_dirs: FxHashMap::default(), | 1539 | mod_dirs: FxHashMap::default(), |
1547 | cfg_options: &CfgOptions::default(), | 1540 | cfg_options: &CfgOptions::default(), |
1548 | proc_macros: Default::default(), | 1541 | proc_macros: Default::default(), |
diff --git a/crates/ide/src/annotations.rs b/crates/ide/src/annotations.rs index 72492f826..64bc926f1 100644 --- a/crates/ide/src/annotations.rs +++ b/crates/ide/src/annotations.rs | |||
@@ -1,5 +1,5 @@ | |||
1 | use either::Either; | 1 | use either::Either; |
2 | use hir::{HasSource, Semantics}; | 2 | use hir::{HasSource, InFile, Semantics}; |
3 | use ide_db::{ | 3 | use ide_db::{ |
4 | base_db::{FileId, FilePosition, FileRange}, | 4 | base_db::{FileId, FilePosition, FileRange}, |
5 | helpers::visit_file_defs, | 5 | helpers::visit_file_defs, |
@@ -80,19 +80,19 @@ pub(crate) fn annotations( | |||
80 | Either::Left(def) => { | 80 | Either::Left(def) => { |
81 | let node = match def { | 81 | let node = match def { |
82 | hir::ModuleDef::Const(konst) => { | 82 | hir::ModuleDef::Const(konst) => { |
83 | konst.source(db).and_then(|node| range_and_position_of(&node.value)) | 83 | konst.source(db).and_then(|node| range_and_position_of(&node, file_id)) |
84 | } | 84 | } |
85 | hir::ModuleDef::Trait(trait_) => { | 85 | hir::ModuleDef::Trait(trait_) => { |
86 | trait_.source(db).and_then(|node| range_and_position_of(&node.value)) | 86 | trait_.source(db).and_then(|node| range_and_position_of(&node, file_id)) |
87 | } | 87 | } |
88 | hir::ModuleDef::Adt(hir::Adt::Struct(strukt)) => { | 88 | hir::ModuleDef::Adt(hir::Adt::Struct(strukt)) => { |
89 | strukt.source(db).and_then(|node| range_and_position_of(&node.value)) | 89 | strukt.source(db).and_then(|node| range_and_position_of(&node, file_id)) |
90 | } | 90 | } |
91 | hir::ModuleDef::Adt(hir::Adt::Enum(enum_)) => { | 91 | hir::ModuleDef::Adt(hir::Adt::Enum(enum_)) => { |
92 | enum_.source(db).and_then(|node| range_and_position_of(&node.value)) | 92 | enum_.source(db).and_then(|node| range_and_position_of(&node, file_id)) |
93 | } | 93 | } |
94 | hir::ModuleDef::Adt(hir::Adt::Union(union)) => { | 94 | hir::ModuleDef::Adt(hir::Adt::Union(union)) => { |
95 | union.source(db).and_then(|node| range_and_position_of(&node.value)) | 95 | union.source(db).and_then(|node| range_and_position_of(&node, file_id)) |
96 | } | 96 | } |
97 | _ => None, | 97 | _ => None, |
98 | }; | 98 | }; |
@@ -120,8 +120,19 @@ pub(crate) fn annotations( | |||
120 | }); | 120 | }); |
121 | } | 121 | } |
122 | 122 | ||
123 | fn range_and_position_of(node: &dyn NameOwner) -> Option<(TextSize, TextRange)> { | 123 | fn range_and_position_of<T: NameOwner>( |
124 | Some((node.name()?.syntax().text_range().start(), node.syntax().text_range())) | 124 | node: &InFile<T>, |
125 | file_id: FileId, | ||
126 | ) -> Option<(TextSize, TextRange)> { | ||
127 | if node.file_id != file_id.into() { | ||
128 | // Node is outside the file we are adding annotations to (e.g. macros). | ||
129 | None | ||
130 | } else { | ||
131 | Some(( | ||
132 | node.value.name()?.syntax().text_range().start(), | ||
133 | node.value.syntax().text_range(), | ||
134 | )) | ||
135 | } | ||
125 | } | 136 | } |
126 | } | 137 | } |
127 | Either::Right(_) => (), | 138 | Either::Right(_) => (), |
@@ -967,4 +978,23 @@ struct Foo; | |||
967 | "#]], | 978 | "#]], |
968 | ); | 979 | ); |
969 | } | 980 | } |
981 | |||
982 | #[test] | ||
983 | fn test_no_annotations_macro_struct_def() { | ||
984 | check( | ||
985 | r#" | ||
986 | //- /lib.rs | ||
987 | macro_rules! m { | ||
988 | () => { | ||
989 | struct A {} | ||
990 | }; | ||
991 | } | ||
992 | |||
993 | m!(); | ||
994 | "#, | ||
995 | expect![[r#" | ||
996 | [] | ||
997 | "#]], | ||
998 | ); | ||
999 | } | ||
970 | } | 1000 | } |
diff --git a/crates/ide/src/call_hierarchy.rs b/crates/ide/src/call_hierarchy.rs index 96021f677..5cd186565 100644 --- a/crates/ide/src/call_hierarchy.rs +++ b/crates/ide/src/call_hierarchy.rs | |||
@@ -50,16 +50,16 @@ pub(crate) fn incoming_calls(db: &RootDatabase, position: FilePosition) -> Optio | |||
50 | for (file_id, references) in refs.references { | 50 | for (file_id, references) in refs.references { |
51 | let file = sema.parse(file_id); | 51 | let file = sema.parse(file_id); |
52 | let file = file.syntax(); | 52 | let file = file.syntax(); |
53 | for (r_range, _) in references { | 53 | for (relative_range, token) in references |
54 | let token = file.token_at_offset(r_range.start()).next()?; | 54 | .into_iter() |
55 | .filter_map(|(range, _)| Some(range).zip(file.token_at_offset(range.start()).next())) | ||
56 | { | ||
55 | let token = sema.descend_into_macros(token); | 57 | let token = sema.descend_into_macros(token); |
56 | // This target is the containing function | 58 | // This target is the containing function |
57 | if let Some(nav) = token.ancestors().find_map(|node| { | 59 | if let Some(nav) = token.ancestors().find_map(|node| { |
58 | let fn_ = ast::Fn::cast(node)?; | 60 | let def = ast::Fn::cast(node).and_then(|fn_| sema.to_def(&fn_))?; |
59 | let def = sema.to_def(&fn_)?; | ||
60 | def.try_to_nav(sema.db) | 61 | def.try_to_nav(sema.db) |
61 | }) { | 62 | }) { |
62 | let relative_range = r_range; | ||
63 | calls.add(&nav, relative_range); | 63 | calls.add(&nav, relative_range); |
64 | } | 64 | } |
65 | } | 65 | } |
@@ -87,7 +87,6 @@ pub(crate) fn outgoing_calls(db: &RootDatabase, position: FilePosition) -> Optio | |||
87 | let name_ref = call_node.name_ref()?; | 87 | let name_ref = call_node.name_ref()?; |
88 | let func_target = match call_node { | 88 | let func_target = match call_node { |
89 | FnCallNode::CallExpr(expr) => { | 89 | FnCallNode::CallExpr(expr) => { |
90 | //FIXME: Type::as_callable is broken | ||
91 | let callable = sema.type_of_expr(&expr.expr()?)?.as_callable(db)?; | 90 | let callable = sema.type_of_expr(&expr.expr()?)?.as_callable(db)?; |
92 | match callable.kind() { | 91 | match callable.kind() { |
93 | hir::CallableKind::Function(it) => it.try_to_nav(db), | 92 | hir::CallableKind::Function(it) => it.try_to_nav(db), |
diff --git a/crates/ide/src/doc_links.rs b/crates/ide/src/doc_links.rs index c7c1f4fee..0cee741ac 100644 --- a/crates/ide/src/doc_links.rs +++ b/crates/ide/src/doc_links.rs | |||
@@ -1,4 +1,4 @@ | |||
1 | //! Resolves and rewrites links in markdown documentation. | 1 | //! Extracts, resolves and rewrites links and intra-doc links in markdown documentation. |
2 | 2 | ||
3 | use std::{convert::TryFrom, iter::once, ops::Range}; | 3 | use std::{convert::TryFrom, iter::once, ops::Range}; |
4 | 4 | ||
@@ -15,7 +15,10 @@ use ide_db::{ | |||
15 | defs::{Definition, NameClass, NameRefClass}, | 15 | defs::{Definition, NameClass, NameRefClass}, |
16 | RootDatabase, | 16 | RootDatabase, |
17 | }; | 17 | }; |
18 | use syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset, T}; | 18 | use syntax::{ |
19 | ast, match_ast, AstNode, AstToken, SyntaxKind::*, SyntaxNode, SyntaxToken, TextRange, TextSize, | ||
20 | TokenAtOffset, T, | ||
21 | }; | ||
19 | 22 | ||
20 | use crate::{FilePosition, Semantics}; | 23 | use crate::{FilePosition, Semantics}; |
21 | 24 | ||
@@ -60,29 +63,6 @@ pub(crate) fn rewrite_links(db: &RootDatabase, markdown: &str, definition: &Defi | |||
60 | out | 63 | out |
61 | } | 64 | } |
62 | 65 | ||
63 | pub(crate) fn extract_definitions_from_markdown( | ||
64 | markdown: &str, | ||
65 | ) -> Vec<(String, Option<hir::Namespace>, Range<usize>)> { | ||
66 | let mut res = vec![]; | ||
67 | let mut cb = |link: BrokenLink| { | ||
68 | // These allocations are actually unnecessary but the lifetimes on BrokenLinkCallback are wrong | ||
69 | // this is fixed in the repo but not on the crates.io release yet | ||
70 | Some(( | ||
71 | /*url*/ link.reference.to_owned().into(), | ||
72 | /*title*/ link.reference.to_owned().into(), | ||
73 | )) | ||
74 | }; | ||
75 | let doc = Parser::new_with_broken_link_callback(markdown, Options::empty(), Some(&mut cb)); | ||
76 | for (event, range) in doc.into_offset_iter() { | ||
77 | if let Event::Start(Tag::Link(_, target, title)) = event { | ||
78 | let link = if target.is_empty() { title } else { target }; | ||
79 | let (link, ns) = parse_link(&link); | ||
80 | res.push((link.to_string(), ns, range)); | ||
81 | } | ||
82 | } | ||
83 | res | ||
84 | } | ||
85 | |||
86 | /// Remove all links in markdown documentation. | 66 | /// Remove all links in markdown documentation. |
87 | pub(crate) fn remove_links(markdown: &str) -> String { | 67 | pub(crate) fn remove_links(markdown: &str) -> String { |
88 | let mut drop_link = false; | 68 | let mut drop_link = false; |
@@ -118,6 +98,105 @@ pub(crate) fn remove_links(markdown: &str) -> String { | |||
118 | out | 98 | out |
119 | } | 99 | } |
120 | 100 | ||
101 | pub(crate) fn extract_definitions_from_markdown( | ||
102 | markdown: &str, | ||
103 | ) -> Vec<(Range<usize>, String, Option<hir::Namespace>)> { | ||
104 | let mut res = vec![]; | ||
105 | let mut cb = |link: BrokenLink| { | ||
106 | // These allocations are actually unnecessary but the lifetimes on BrokenLinkCallback are wrong | ||
107 | // this is fixed in the repo but not on the crates.io release yet | ||
108 | Some(( | ||
109 | /*url*/ link.reference.to_owned().into(), | ||
110 | /*title*/ link.reference.to_owned().into(), | ||
111 | )) | ||
112 | }; | ||
113 | let doc = Parser::new_with_broken_link_callback(markdown, Options::empty(), Some(&mut cb)); | ||
114 | for (event, range) in doc.into_offset_iter() { | ||
115 | if let Event::Start(Tag::Link(_, target, title)) = event { | ||
116 | let link = if target.is_empty() { title } else { target }; | ||
117 | let (link, ns) = parse_intra_doc_link(&link); | ||
118 | res.push((range, link.to_string(), ns)); | ||
119 | } | ||
120 | } | ||
121 | res | ||
122 | } | ||
123 | |||
124 | /// Extracts a link from a comment at the given position returning the spanning range, link and | ||
125 | /// optionally it's namespace. | ||
126 | pub(crate) fn extract_positioned_link_from_comment( | ||
127 | position: TextSize, | ||
128 | comment: &ast::Comment, | ||
129 | ) -> Option<(TextRange, String, Option<hir::Namespace>)> { | ||
130 | let doc_comment = comment.doc_comment()?; | ||
131 | let comment_start = | ||
132 | comment.syntax().text_range().start() + TextSize::from(comment.prefix().len() as u32); | ||
133 | let def_links = extract_definitions_from_markdown(doc_comment); | ||
134 | let (range, def_link, ns) = | ||
135 | def_links.into_iter().find_map(|(Range { start, end }, def_link, ns)| { | ||
136 | let range = TextRange::at( | ||
137 | comment_start + TextSize::from(start as u32), | ||
138 | TextSize::from((end - start) as u32), | ||
139 | ); | ||
140 | range.contains(position).then(|| (range, def_link, ns)) | ||
141 | })?; | ||
142 | Some((range, def_link, ns)) | ||
143 | } | ||
144 | |||
145 | /// Turns a syntax node into it's [`Definition`] if it can hold docs. | ||
146 | pub(crate) fn doc_owner_to_def( | ||
147 | sema: &Semantics<RootDatabase>, | ||
148 | item: &SyntaxNode, | ||
149 | ) -> Option<Definition> { | ||
150 | let res: hir::ModuleDef = match_ast! { | ||
151 | match item { | ||
152 | ast::SourceFile(_it) => sema.scope(item).module()?.into(), | ||
153 | ast::Fn(it) => sema.to_def(&it)?.into(), | ||
154 | ast::Struct(it) => sema.to_def(&it)?.into(), | ||
155 | ast::Enum(it) => sema.to_def(&it)?.into(), | ||
156 | ast::Union(it) => sema.to_def(&it)?.into(), | ||
157 | ast::Trait(it) => sema.to_def(&it)?.into(), | ||
158 | ast::Const(it) => sema.to_def(&it)?.into(), | ||
159 | ast::Static(it) => sema.to_def(&it)?.into(), | ||
160 | ast::TypeAlias(it) => sema.to_def(&it)?.into(), | ||
161 | ast::Variant(it) => sema.to_def(&it)?.into(), | ||
162 | ast::Trait(it) => sema.to_def(&it)?.into(), | ||
163 | ast::Impl(it) => return sema.to_def(&it).map(Definition::SelfType), | ||
164 | ast::MacroRules(it) => return sema.to_def(&it).map(Definition::Macro), | ||
165 | ast::TupleField(it) => return sema.to_def(&it).map(Definition::Field), | ||
166 | ast::RecordField(it) => return sema.to_def(&it).map(Definition::Field), | ||
167 | _ => return None, | ||
168 | } | ||
169 | }; | ||
170 | Some(Definition::ModuleDef(res)) | ||
171 | } | ||
172 | |||
173 | pub(crate) fn resolve_doc_path_for_def( | ||
174 | db: &dyn HirDatabase, | ||
175 | def: Definition, | ||
176 | link: &str, | ||
177 | ns: Option<hir::Namespace>, | ||
178 | ) -> Option<hir::ModuleDef> { | ||
179 | match def { | ||
180 | Definition::ModuleDef(def) => match def { | ||
181 | ModuleDef::Module(it) => it.resolve_doc_path(db, &link, ns), | ||
182 | ModuleDef::Function(it) => it.resolve_doc_path(db, &link, ns), | ||
183 | ModuleDef::Adt(it) => it.resolve_doc_path(db, &link, ns), | ||
184 | ModuleDef::Variant(it) => it.resolve_doc_path(db, &link, ns), | ||
185 | ModuleDef::Const(it) => it.resolve_doc_path(db, &link, ns), | ||
186 | ModuleDef::Static(it) => it.resolve_doc_path(db, &link, ns), | ||
187 | ModuleDef::Trait(it) => it.resolve_doc_path(db, &link, ns), | ||
188 | ModuleDef::TypeAlias(it) => it.resolve_doc_path(db, &link, ns), | ||
189 | ModuleDef::BuiltinType(_) => None, | ||
190 | }, | ||
191 | Definition::Macro(it) => it.resolve_doc_path(db, &link, ns), | ||
192 | Definition::Field(it) => it.resolve_doc_path(db, &link, ns), | ||
193 | Definition::SelfType(_) | ||
194 | | Definition::Local(_) | ||
195 | | Definition::GenericParam(_) | ||
196 | | Definition::Label(_) => None, | ||
197 | } | ||
198 | } | ||
199 | |||
121 | // FIXME: | 200 | // FIXME: |
122 | // BUG: For Option::Some | 201 | // BUG: For Option::Some |
123 | // Returns https://doc.rust-lang.org/nightly/core/prelude/v1/enum.Option.html#variant.Some | 202 | // Returns https://doc.rust-lang.org/nightly/core/prelude/v1/enum.Option.html#variant.Some |
@@ -197,26 +276,8 @@ fn rewrite_intra_doc_link( | |||
197 | title: &str, | 276 | title: &str, |
198 | ) -> Option<(String, String)> { | 277 | ) -> Option<(String, String)> { |
199 | let link = if target.is_empty() { title } else { target }; | 278 | let link = if target.is_empty() { title } else { target }; |
200 | let (link, ns) = parse_link(link); | 279 | let (link, ns) = parse_intra_doc_link(link); |
201 | let resolved = match def { | 280 | let resolved = resolve_doc_path_for_def(db, def, link, ns)?; |
202 | Definition::ModuleDef(def) => match def { | ||
203 | ModuleDef::Module(it) => it.resolve_doc_path(db, link, ns), | ||
204 | ModuleDef::Function(it) => it.resolve_doc_path(db, link, ns), | ||
205 | ModuleDef::Adt(it) => it.resolve_doc_path(db, link, ns), | ||
206 | ModuleDef::Variant(it) => it.resolve_doc_path(db, link, ns), | ||
207 | ModuleDef::Const(it) => it.resolve_doc_path(db, link, ns), | ||
208 | ModuleDef::Static(it) => it.resolve_doc_path(db, link, ns), | ||
209 | ModuleDef::Trait(it) => it.resolve_doc_path(db, link, ns), | ||
210 | ModuleDef::TypeAlias(it) => it.resolve_doc_path(db, link, ns), | ||
211 | ModuleDef::BuiltinType(_) => return None, | ||
212 | }, | ||
213 | Definition::Macro(it) => it.resolve_doc_path(db, link, ns), | ||
214 | Definition::Field(it) => it.resolve_doc_path(db, link, ns), | ||
215 | Definition::SelfType(_) | ||
216 | | Definition::Local(_) | ||
217 | | Definition::GenericParam(_) | ||
218 | | Definition::Label(_) => return None, | ||
219 | }?; | ||
220 | let krate = resolved.module(db)?.krate(); | 281 | let krate = resolved.module(db)?.krate(); |
221 | let canonical_path = resolved.canonical_path(db)?; | 282 | let canonical_path = resolved.canonical_path(db)?; |
222 | let mut new_url = get_doc_url(db, &krate)? | 283 | let mut new_url = get_doc_url(db, &krate)? |
@@ -228,24 +289,23 @@ fn rewrite_intra_doc_link( | |||
228 | .ok()?; | 289 | .ok()?; |
229 | 290 | ||
230 | if let ModuleDef::Trait(t) = resolved { | 291 | if let ModuleDef::Trait(t) = resolved { |
231 | let items = t.items(db); | 292 | if let Some(assoc_item) = t.items(db).into_iter().find_map(|assoc_item| { |
232 | if let Some(field_or_assoc_item) = items.iter().find_map(|assoc_item| { | ||
233 | if let Some(name) = assoc_item.name(db) { | 293 | if let Some(name) = assoc_item.name(db) { |
234 | if *link == format!("{}::{}", canonical_path, name) { | 294 | if *link == format!("{}::{}", canonical_path, name) { |
235 | return Some(FieldOrAssocItem::AssocItem(*assoc_item)); | 295 | return Some(assoc_item); |
236 | } | 296 | } |
237 | } | 297 | } |
238 | None | 298 | None |
239 | }) { | 299 | }) { |
240 | if let Some(fragment) = get_symbol_fragment(db, &field_or_assoc_item) { | 300 | if let Some(fragment) = |
301 | get_symbol_fragment(db, &FieldOrAssocItem::AssocItem(assoc_item)) | ||
302 | { | ||
241 | new_url = new_url.join(&fragment).ok()?; | 303 | new_url = new_url.join(&fragment).ok()?; |
242 | } | 304 | } |
243 | }; | 305 | }; |
244 | } | 306 | } |
245 | 307 | ||
246 | let new_target = new_url.into_string(); | 308 | Some((new_url.into_string(), strip_prefixes_suffixes(title).to_string())) |
247 | let new_title = strip_prefixes_suffixes(title); | ||
248 | Some((new_target, new_title.to_string())) | ||
249 | } | 309 | } |
250 | 310 | ||
251 | /// Try to resolve path to local documentation via path-based links (i.e. `../gateway/struct.Shard.html`). | 311 | /// Try to resolve path to local documentation via path-based links (i.e. `../gateway/struct.Shard.html`). |
@@ -322,73 +382,61 @@ fn map_links<'e>( | |||
322 | }) | 382 | }) |
323 | } | 383 | } |
324 | 384 | ||
325 | fn parse_link(s: &str) -> (&str, Option<hir::Namespace>) { | 385 | const TYPES: ([&str; 9], [&str; 0]) = |
326 | let path = strip_prefixes_suffixes(s); | 386 | (["type", "struct", "enum", "mod", "trait", "union", "module", "prim", "primitive"], []); |
327 | let ns = ns_from_intra_spec(s); | 387 | const VALUES: ([&str; 8], [&str; 1]) = |
328 | (path, ns) | ||
329 | } | ||
330 | |||
331 | /// Strip prefixes, suffixes, and inline code marks from the given string. | ||
332 | fn strip_prefixes_suffixes(mut s: &str) -> &str { | ||
333 | s = s.trim_matches('`'); | ||
334 | |||
335 | [ | ||
336 | (TYPES.0.iter(), TYPES.1.iter()), | ||
337 | (VALUES.0.iter(), VALUES.1.iter()), | ||
338 | (MACROS.0.iter(), MACROS.1.iter()), | ||
339 | ] | ||
340 | .iter() | ||
341 | .for_each(|(prefixes, suffixes)| { | ||
342 | prefixes.clone().for_each(|prefix| s = s.trim_start_matches(*prefix)); | ||
343 | suffixes.clone().for_each(|suffix| s = s.trim_end_matches(*suffix)); | ||
344 | }); | ||
345 | s.trim_start_matches('@').trim() | ||
346 | } | ||
347 | |||
348 | static TYPES: ([&str; 7], [&str; 0]) = | ||
349 | (["type", "struct", "enum", "mod", "trait", "union", "module"], []); | ||
350 | static VALUES: ([&str; 8], [&str; 1]) = | ||
351 | (["value", "function", "fn", "method", "const", "static", "mod", "module"], ["()"]); | 388 | (["value", "function", "fn", "method", "const", "static", "mod", "module"], ["()"]); |
352 | static MACROS: ([&str; 1], [&str; 1]) = (["macro"], ["!"]); | 389 | const MACROS: ([&str; 2], [&str; 1]) = (["macro", "derive"], ["!"]); |
353 | 390 | ||
354 | /// Extract the specified namespace from an intra-doc-link if one exists. | 391 | /// Extract the specified namespace from an intra-doc-link if one exists. |
355 | /// | 392 | /// |
356 | /// # Examples | 393 | /// # Examples |
357 | /// | 394 | /// |
358 | /// * `struct MyStruct` -> `Namespace::Types` | 395 | /// * `struct MyStruct` -> ("MyStruct", `Namespace::Types`) |
359 | /// * `panic!` -> `Namespace::Macros` | 396 | /// * `panic!` -> ("panic", `Namespace::Macros`) |
360 | /// * `fn@from_intra_spec` -> `Namespace::Values` | 397 | /// * `fn@from_intra_spec` -> ("from_intra_spec", `Namespace::Values`) |
361 | fn ns_from_intra_spec(s: &str) -> Option<hir::Namespace> { | 398 | fn parse_intra_doc_link(s: &str) -> (&str, Option<hir::Namespace>) { |
399 | let s = s.trim_matches('`'); | ||
400 | |||
362 | [ | 401 | [ |
363 | (hir::Namespace::Types, (TYPES.0.iter(), TYPES.1.iter())), | 402 | (hir::Namespace::Types, (TYPES.0.iter(), TYPES.1.iter())), |
364 | (hir::Namespace::Values, (VALUES.0.iter(), VALUES.1.iter())), | 403 | (hir::Namespace::Values, (VALUES.0.iter(), VALUES.1.iter())), |
365 | (hir::Namespace::Macros, (MACROS.0.iter(), MACROS.1.iter())), | 404 | (hir::Namespace::Macros, (MACROS.0.iter(), MACROS.1.iter())), |
366 | ] | 405 | ] |
367 | .iter() | 406 | .iter() |
368 | .filter(|(_ns, (prefixes, suffixes))| { | 407 | .cloned() |
369 | prefixes | 408 | .find_map(|(ns, (mut prefixes, mut suffixes))| { |
370 | .clone() | 409 | if let Some(prefix) = prefixes.find(|&&prefix| { |
371 | .map(|prefix| { | 410 | s.starts_with(prefix) |
372 | s.starts_with(*prefix) | 411 | && s.chars().nth(prefix.len()).map_or(false, |c| c == '@' || c == ' ') |
373 | && s.chars() | 412 | }) { |
374 | .nth(prefix.len() + 1) | 413 | Some((&s[prefix.len() + 1..], ns)) |
375 | .map(|c| c == '@' || c == ' ') | 414 | } else { |
376 | .unwrap_or(false) | 415 | suffixes.find_map(|&suffix| s.strip_suffix(suffix).zip(Some(ns))) |
377 | }) | 416 | } |
378 | .any(|cond| cond) | 417 | }) |
379 | || suffixes | 418 | .map_or((s, None), |(s, ns)| (s, Some(ns))) |
380 | .clone() | 419 | } |
381 | .map(|suffix| { | 420 | |
382 | s.starts_with(*suffix) | 421 | fn strip_prefixes_suffixes(s: &str) -> &str { |
383 | && s.chars() | 422 | [ |
384 | .nth(suffix.len() + 1) | 423 | (TYPES.0.iter(), TYPES.1.iter()), |
385 | .map(|c| c == '@' || c == ' ') | 424 | (VALUES.0.iter(), VALUES.1.iter()), |
386 | .unwrap_or(false) | 425 | (MACROS.0.iter(), MACROS.1.iter()), |
387 | }) | 426 | ] |
388 | .any(|cond| cond) | 427 | .iter() |
428 | .cloned() | ||
429 | .find_map(|(mut prefixes, mut suffixes)| { | ||
430 | if let Some(prefix) = prefixes.find(|&&prefix| { | ||
431 | s.starts_with(prefix) | ||
432 | && s.chars().nth(prefix.len()).map_or(false, |c| c == '@' || c == ' ') | ||
433 | }) { | ||
434 | Some(&s[prefix.len() + 1..]) | ||
435 | } else { | ||
436 | suffixes.find_map(|&suffix| s.strip_suffix(suffix)) | ||
437 | } | ||
389 | }) | 438 | }) |
390 | .map(|(ns, (_, _))| *ns) | 439 | .unwrap_or(s) |
391 | .next() | ||
392 | } | 440 | } |
393 | 441 | ||
394 | /// Get the root URL for the documentation of a crate. | 442 | /// Get the root URL for the documentation of a crate. |
diff --git a/crates/ide/src/goto_definition.rs b/crates/ide/src/goto_definition.rs index 473d48c2f..a2c97061f 100644 --- a/crates/ide/src/goto_definition.rs +++ b/crates/ide/src/goto_definition.rs | |||
@@ -1,18 +1,14 @@ | |||
1 | use std::ops::Range; | ||
2 | |||
3 | use either::Either; | 1 | use either::Either; |
4 | use hir::{HasAttrs, ModuleDef, Semantics}; | 2 | use hir::Semantics; |
5 | use ide_db::{ | 3 | use ide_db::{ |
6 | defs::{Definition, NameClass, NameRefClass}, | 4 | defs::{NameClass, NameRefClass}, |
7 | RootDatabase, | 5 | RootDatabase, |
8 | }; | 6 | }; |
9 | use syntax::{ | 7 | use syntax::{ast, match_ast, AstNode, AstToken, SyntaxKind::*, SyntaxToken, TokenAtOffset, T}; |
10 | ast, match_ast, AstNode, AstToken, SyntaxKind::*, SyntaxToken, TextRange, TextSize, | ||
11 | TokenAtOffset, T, | ||
12 | }; | ||
13 | 8 | ||
14 | use crate::{ | 9 | use crate::{ |
15 | display::TryToNav, doc_links::extract_definitions_from_markdown, runnables::doc_owner_to_def, | 10 | display::TryToNav, |
11 | doc_links::{doc_owner_to_def, extract_positioned_link_from_comment, resolve_doc_path_for_def}, | ||
16 | FilePosition, NavigationTarget, RangeInfo, | 12 | FilePosition, NavigationTarget, RangeInfo, |
17 | }; | 13 | }; |
18 | 14 | ||
@@ -35,7 +31,9 @@ pub(crate) fn goto_definition( | |||
35 | let token = sema.descend_into_macros(original_token.clone()); | 31 | let token = sema.descend_into_macros(original_token.clone()); |
36 | let parent = token.parent()?; | 32 | let parent = token.parent()?; |
37 | if let Some(comment) = ast::Comment::cast(token) { | 33 | if let Some(comment) = ast::Comment::cast(token) { |
38 | let nav = def_for_doc_comment(&sema, position, &comment)?.try_to_nav(db)?; | 34 | let (_, link, ns) = extract_positioned_link_from_comment(position.offset, &comment)?; |
35 | let def = doc_owner_to_def(&sema, &parent)?; | ||
36 | let nav = resolve_doc_path_for_def(db, def, &link, ns)?.try_to_nav(db)?; | ||
39 | return Some(RangeInfo::new(original_token.text_range(), vec![nav])); | 37 | return Some(RangeInfo::new(original_token.text_range(), vec![nav])); |
40 | } | 38 | } |
41 | 39 | ||
@@ -61,54 +59,6 @@ pub(crate) fn goto_definition( | |||
61 | Some(RangeInfo::new(original_token.text_range(), nav.into_iter().collect())) | 59 | Some(RangeInfo::new(original_token.text_range(), nav.into_iter().collect())) |
62 | } | 60 | } |
63 | 61 | ||
64 | fn def_for_doc_comment( | ||
65 | sema: &Semantics<RootDatabase>, | ||
66 | position: FilePosition, | ||
67 | doc_comment: &ast::Comment, | ||
68 | ) -> Option<hir::ModuleDef> { | ||
69 | let parent = doc_comment.syntax().parent()?; | ||
70 | let (link, ns) = extract_positioned_link_from_comment(position, doc_comment)?; | ||
71 | |||
72 | let def = doc_owner_to_def(sema, parent)?; | ||
73 | match def { | ||
74 | Definition::ModuleDef(def) => match def { | ||
75 | ModuleDef::Module(it) => it.resolve_doc_path(sema.db, &link, ns), | ||
76 | ModuleDef::Function(it) => it.resolve_doc_path(sema.db, &link, ns), | ||
77 | ModuleDef::Adt(it) => it.resolve_doc_path(sema.db, &link, ns), | ||
78 | ModuleDef::Variant(it) => it.resolve_doc_path(sema.db, &link, ns), | ||
79 | ModuleDef::Const(it) => it.resolve_doc_path(sema.db, &link, ns), | ||
80 | ModuleDef::Static(it) => it.resolve_doc_path(sema.db, &link, ns), | ||
81 | ModuleDef::Trait(it) => it.resolve_doc_path(sema.db, &link, ns), | ||
82 | ModuleDef::TypeAlias(it) => it.resolve_doc_path(sema.db, &link, ns), | ||
83 | ModuleDef::BuiltinType(_) => return None, | ||
84 | }, | ||
85 | Definition::Macro(it) => it.resolve_doc_path(sema.db, &link, ns), | ||
86 | Definition::Field(it) => it.resolve_doc_path(sema.db, &link, ns), | ||
87 | Definition::SelfType(_) | ||
88 | | Definition::Local(_) | ||
89 | | Definition::GenericParam(_) | ||
90 | | Definition::Label(_) => return None, | ||
91 | } | ||
92 | } | ||
93 | |||
94 | fn extract_positioned_link_from_comment( | ||
95 | position: FilePosition, | ||
96 | comment: &ast::Comment, | ||
97 | ) -> Option<(String, Option<hir::Namespace>)> { | ||
98 | let doc_comment = comment.doc_comment()?; | ||
99 | let comment_start = | ||
100 | comment.syntax().text_range().start() + TextSize::from(comment.prefix().len() as u32); | ||
101 | let def_links = extract_definitions_from_markdown(doc_comment); | ||
102 | let (def_link, ns, _) = def_links.into_iter().find(|&(_, _, Range { start, end })| { | ||
103 | TextRange::at( | ||
104 | comment_start + TextSize::from(start as u32), | ||
105 | TextSize::from((end - start) as u32), | ||
106 | ) | ||
107 | .contains(position.offset) | ||
108 | })?; | ||
109 | Some((def_link, ns)) | ||
110 | } | ||
111 | |||
112 | fn pick_best(tokens: TokenAtOffset<SyntaxToken>) -> Option<SyntaxToken> { | 62 | fn pick_best(tokens: TokenAtOffset<SyntaxToken>) -> Option<SyntaxToken> { |
113 | return tokens.max_by_key(priority); | 63 | return tokens.max_by_key(priority); |
114 | fn priority(n: &SyntaxToken) -> usize { | 64 | fn priority(n: &SyntaxToken) -> usize { |
diff --git a/crates/ide/src/hover.rs b/crates/ide/src/hover.rs index a3fb17c0a..c43089476 100644 --- a/crates/ide/src/hover.rs +++ b/crates/ide/src/hover.rs | |||
@@ -11,11 +11,14 @@ use ide_db::{ | |||
11 | }; | 11 | }; |
12 | use itertools::Itertools; | 12 | use itertools::Itertools; |
13 | use stdx::format_to; | 13 | use stdx::format_to; |
14 | use syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset, T}; | 14 | use syntax::{ast, match_ast, AstNode, AstToken, SyntaxKind::*, SyntaxToken, TokenAtOffset, T}; |
15 | 15 | ||
16 | use crate::{ | 16 | use crate::{ |
17 | display::{macro_label, TryToNav}, | 17 | display::{macro_label, TryToNav}, |
18 | doc_links::{remove_links, rewrite_links}, | 18 | doc_links::{ |
19 | doc_owner_to_def, extract_positioned_link_from_comment, remove_links, | ||
20 | resolve_doc_path_for_def, rewrite_links, | ||
21 | }, | ||
19 | markdown_remove::remove_markdown, | 22 | markdown_remove::remove_markdown, |
20 | markup::Markup, | 23 | markup::Markup, |
21 | runnables::{runnable_fn, runnable_mod}, | 24 | runnables::{runnable_fn, runnable_mod}, |
@@ -93,20 +96,35 @@ pub(crate) fn hover( | |||
93 | let mut res = HoverResult::default(); | 96 | let mut res = HoverResult::default(); |
94 | 97 | ||
95 | let node = token.parent()?; | 98 | let node = token.parent()?; |
99 | let mut range = None; | ||
96 | let definition = match_ast! { | 100 | let definition = match_ast! { |
97 | match node { | 101 | match node { |
98 | // we don't use NameClass::referenced_or_defined here as we do not want to resolve | 102 | // we don't use NameClass::referenced_or_defined here as we do not want to resolve |
99 | // field pattern shorthands to their definition | 103 | // field pattern shorthands to their definition |
100 | ast::Name(name) => NameClass::classify(&sema, &name).and_then(|class| match class { | 104 | ast::Name(name) => NameClass::classify(&sema, &name).and_then(|class| match class { |
101 | NameClass::ConstReference(def) => Some(def), | 105 | NameClass::ConstReference(def) => Some(def), |
102 | def => def.defined(sema.db), | 106 | def => def.defined(db), |
103 | }), | 107 | }), |
104 | ast::NameRef(name_ref) => NameRefClass::classify(&sema, &name_ref).map(|d| d.referenced(sema.db)), | 108 | ast::NameRef(name_ref) => { |
105 | ast::Lifetime(lifetime) => NameClass::classify_lifetime(&sema, &lifetime) | 109 | NameRefClass::classify(&sema, &name_ref).map(|d| d.referenced(db)) |
106 | .map_or_else(|| NameRefClass::classify_lifetime(&sema, &lifetime).map(|d| d.referenced(sema.db)), |d| d.defined(sema.db)), | 110 | }, |
107 | _ => None, | 111 | ast::Lifetime(lifetime) => NameClass::classify_lifetime(&sema, &lifetime).map_or_else( |
112 | || NameRefClass::classify_lifetime(&sema, &lifetime).map(|d| d.referenced(db)), | ||
113 | |d| d.defined(db), | ||
114 | ), | ||
115 | |||
116 | _ => ast::Comment::cast(token.clone()) | ||
117 | .and_then(|comment| { | ||
118 | let (idl_range, link, ns) = | ||
119 | extract_positioned_link_from_comment(position.offset, &comment)?; | ||
120 | range = Some(idl_range); | ||
121 | let def = doc_owner_to_def(&sema, &node)?; | ||
122 | resolve_doc_path_for_def(db, def, &link, ns) | ||
123 | }) | ||
124 | .map(Definition::ModuleDef), | ||
108 | } | 125 | } |
109 | }; | 126 | }; |
127 | |||
110 | if let Some(definition) = definition { | 128 | if let Some(definition) = definition { |
111 | let famous_defs = match &definition { | 129 | let famous_defs = match &definition { |
112 | Definition::ModuleDef(ModuleDef::BuiltinType(_)) => { | 130 | Definition::ModuleDef(ModuleDef::BuiltinType(_)) => { |
@@ -128,15 +146,16 @@ pub(crate) fn hover( | |||
128 | res.actions.push(action); | 146 | res.actions.push(action); |
129 | } | 147 | } |
130 | 148 | ||
131 | let range = sema.original_range(&node).range; | 149 | let range = range.unwrap_or_else(|| sema.original_range(&node).range); |
132 | return Some(RangeInfo::new(range, res)); | 150 | return Some(RangeInfo::new(range, res)); |
133 | } | 151 | } |
134 | } | 152 | } |
135 | 153 | ||
136 | if token.kind() == syntax::SyntaxKind::COMMENT { | 154 | if token.kind() == syntax::SyntaxKind::COMMENT { |
137 | // don't highlight the entire parent node on comment hover | 155 | cov_mark::hit!(no_highlight_on_comment_hover); |
138 | return None; | 156 | return None; |
139 | } | 157 | } |
158 | |||
140 | if let res @ Some(_) = hover_for_keyword(&sema, links_in_hover, markdown, &token) { | 159 | if let res @ Some(_) = hover_for_keyword(&sema, links_in_hover, markdown, &token) { |
141 | return res; | 160 | return res; |
142 | } | 161 | } |
@@ -3483,6 +3502,7 @@ fn foo$0() {} | |||
3483 | 3502 | ||
3484 | #[test] | 3503 | #[test] |
3485 | fn hover_comments_dont_highlight_parent() { | 3504 | fn hover_comments_dont_highlight_parent() { |
3505 | cov_mark::check!(no_highlight_on_comment_hover); | ||
3486 | check_hover_no_result( | 3506 | check_hover_no_result( |
3487 | r#" | 3507 | r#" |
3488 | fn no_hover() { | 3508 | fn no_hover() { |
@@ -3755,4 +3775,29 @@ fn main() { | |||
3755 | "#]], | 3775 | "#]], |
3756 | ) | 3776 | ) |
3757 | } | 3777 | } |
3778 | |||
3779 | #[test] | ||
3780 | fn hover_intra_doc_links() { | ||
3781 | check( | ||
3782 | r#" | ||
3783 | /// This is the [`foo`](foo$0) function. | ||
3784 | fn foo() {} | ||
3785 | "#, | ||
3786 | expect![[r#" | ||
3787 | *[`foo`](foo)* | ||
3788 | |||
3789 | ```rust | ||
3790 | test | ||
3791 | ``` | ||
3792 | |||
3793 | ```rust | ||
3794 | fn foo() | ||
3795 | ``` | ||
3796 | |||
3797 | --- | ||
3798 | |||
3799 | This is the [`foo`](https://docs.rs/test/*/test/fn.foo.html) function. | ||
3800 | "#]], | ||
3801 | ); | ||
3802 | } | ||
3758 | } | 3803 | } |
diff --git a/crates/ide/src/references/rename.rs b/crates/ide/src/references/rename.rs index 5340b638a..26d6dc9c9 100644 --- a/crates/ide/src/references/rename.rs +++ b/crates/ide/src/references/rename.rs | |||
@@ -1,8 +1,11 @@ | |||
1 | //! FIXME: write short doc here | 1 | //! Renaming functionality |
2 | //! | ||
3 | //! All reference and file rename requests go through here where the corresponding [`SourceChange`]s | ||
4 | //! will be calculated. | ||
2 | use std::fmt::{self, Display}; | 5 | use std::fmt::{self, Display}; |
3 | 6 | ||
4 | use either::Either; | 7 | use either::Either; |
5 | use hir::{HasSource, InFile, Module, ModuleDef, ModuleSource, Semantics}; | 8 | use hir::{AsAssocItem, InFile, Module, ModuleDef, ModuleSource, Semantics}; |
6 | use ide_db::{ | 9 | use ide_db::{ |
7 | base_db::{AnchoredPathBuf, FileId}, | 10 | base_db::{AnchoredPathBuf, FileId}, |
8 | defs::{Definition, NameClass, NameRefClass}, | 11 | defs::{Definition, NameClass, NameRefClass}, |
@@ -196,7 +199,7 @@ fn rename_mod( | |||
196 | file_id, | 199 | file_id, |
197 | TextEdit::replace(name.syntax().text_range(), new_name.to_string()), | 200 | TextEdit::replace(name.syntax().text_range(), new_name.to_string()), |
198 | ), | 201 | ), |
199 | _ => unreachable!(), | 202 | _ => never!("Module source node is missing a name"), |
200 | } | 203 | } |
201 | } | 204 | } |
202 | let def = Definition::ModuleDef(ModuleDef::Module(module)); | 205 | let def = Definition::ModuleDef(ModuleDef::Module(module)); |
@@ -216,40 +219,44 @@ fn rename_reference( | |||
216 | ) -> RenameResult<SourceChange> { | 219 | ) -> RenameResult<SourceChange> { |
217 | let ident_kind = check_identifier(new_name)?; | 220 | let ident_kind = check_identifier(new_name)?; |
218 | 221 | ||
219 | let def_is_lbl_or_lt = matches!( | 222 | if matches!( |
220 | def, | 223 | def, // is target a lifetime? |
221 | Definition::GenericParam(hir::GenericParam::LifetimeParam(_)) | Definition::Label(_) | 224 | Definition::GenericParam(hir::GenericParam::LifetimeParam(_)) | Definition::Label(_) |
222 | ); | 225 | ) { |
223 | match (ident_kind, def) { | 226 | match ident_kind { |
224 | (IdentifierKind::ToSelf, _) | 227 | IdentifierKind::Ident | IdentifierKind::ToSelf | IdentifierKind::Underscore => { |
225 | | (IdentifierKind::Underscore, _) | 228 | cov_mark::hit!(rename_not_a_lifetime_ident_ref); |
226 | | (IdentifierKind::Ident, _) | 229 | bail!("Invalid name `{}`: not a lifetime identifier", new_name); |
227 | if def_is_lbl_or_lt => | 230 | } |
228 | { | 231 | IdentifierKind::Lifetime => cov_mark::hit!(rename_lifetime), |
229 | cov_mark::hit!(rename_not_a_lifetime_ident_ref); | ||
230 | bail!("Invalid name `{}`: not a lifetime identifier", new_name) | ||
231 | } | ||
232 | (IdentifierKind::Lifetime, _) if def_is_lbl_or_lt => cov_mark::hit!(rename_lifetime), | ||
233 | (IdentifierKind::Lifetime, _) => { | ||
234 | cov_mark::hit!(rename_not_an_ident_ref); | ||
235 | bail!("Invalid name `{}`: not an identifier", new_name) | ||
236 | } | ||
237 | (IdentifierKind::ToSelf, Definition::Local(local)) if local.is_self(sema.db) => { | ||
238 | // no-op | ||
239 | cov_mark::hit!(rename_self_to_self); | ||
240 | return Ok(SourceChange::default()); | ||
241 | } | ||
242 | (ident_kind, Definition::Local(local)) if local.is_self(sema.db) => { | ||
243 | cov_mark::hit!(rename_self_to_param); | ||
244 | return rename_self_to_param(sema, local, new_name, ident_kind); | ||
245 | } | ||
246 | (IdentifierKind::ToSelf, Definition::Local(local)) => { | ||
247 | cov_mark::hit!(rename_to_self); | ||
248 | return rename_to_self(sema, local); | ||
249 | } | 232 | } |
250 | (IdentifierKind::ToSelf, _) => bail!("Invalid name `{}`: not an identifier", new_name), | 233 | } else { |
251 | (IdentifierKind::Ident, _) | (IdentifierKind::Underscore, _) => { | 234 | match (ident_kind, def) { |
252 | cov_mark::hit!(rename_ident) | 235 | (IdentifierKind::Lifetime, _) => { |
236 | cov_mark::hit!(rename_not_an_ident_ref); | ||
237 | bail!("Invalid name `{}`: not an identifier", new_name); | ||
238 | } | ||
239 | (IdentifierKind::ToSelf, Definition::Local(local)) => { | ||
240 | if local.is_self(sema.db) { | ||
241 | // no-op | ||
242 | cov_mark::hit!(rename_self_to_self); | ||
243 | return Ok(SourceChange::default()); | ||
244 | } else { | ||
245 | cov_mark::hit!(rename_to_self); | ||
246 | return rename_to_self(sema, local); | ||
247 | } | ||
248 | } | ||
249 | (ident_kind, Definition::Local(local)) => { | ||
250 | if let Some(self_param) = local.as_self_param(sema.db) { | ||
251 | cov_mark::hit!(rename_self_to_param); | ||
252 | return rename_self_to_param(sema, local, self_param, new_name, ident_kind); | ||
253 | } else { | ||
254 | cov_mark::hit!(rename_local); | ||
255 | } | ||
256 | } | ||
257 | (IdentifierKind::ToSelf, _) => bail!("Invalid name `{}`: not an identifier", new_name), | ||
258 | (IdentifierKind::Ident, _) => cov_mark::hit!(rename_non_local), | ||
259 | (IdentifierKind::Underscore, _) => (), | ||
253 | } | 260 | } |
254 | } | 261 | } |
255 | 262 | ||
@@ -275,46 +282,32 @@ fn rename_to_self(sema: &Semantics<RootDatabase>, local: hir::Local) -> RenameRe | |||
275 | 282 | ||
276 | let fn_def = match local.parent(sema.db) { | 283 | let fn_def = match local.parent(sema.db) { |
277 | hir::DefWithBody::Function(func) => func, | 284 | hir::DefWithBody::Function(func) => func, |
278 | _ => bail!("Cannot rename non-param local to self"), | 285 | _ => bail!("Cannot rename local to self outside of function"), |
279 | }; | 286 | }; |
280 | 287 | ||
281 | // FIXME: reimplement this on the hir instead | 288 | if let Some(_) = fn_def.self_param(sema.db) { |
282 | // as of the time of this writing params in hir don't keep their names | ||
283 | let fn_ast = fn_def | ||
284 | .source(sema.db) | ||
285 | .ok_or_else(|| format_err!("Cannot rename non-param local to self"))? | ||
286 | .value; | ||
287 | |||
288 | let first_param_range = fn_ast | ||
289 | .param_list() | ||
290 | .and_then(|p| p.params().next()) | ||
291 | .ok_or_else(|| format_err!("Method has no parameters"))? | ||
292 | .syntax() | ||
293 | .text_range(); | ||
294 | let InFile { file_id, value: local_source } = local.source(sema.db); | ||
295 | match local_source { | ||
296 | either::Either::Left(pat) | ||
297 | if !first_param_range.contains_range(pat.syntax().text_range()) => | ||
298 | { | ||
299 | bail!("Only the first parameter can be self"); | ||
300 | } | ||
301 | _ => (), | ||
302 | } | ||
303 | |||
304 | let impl_block = fn_ast | ||
305 | .syntax() | ||
306 | .ancestors() | ||
307 | .find_map(|node| ast::Impl::cast(node)) | ||
308 | .and_then(|def| sema.to_def(&def)) | ||
309 | .ok_or_else(|| format_err!("No impl block found for function"))?; | ||
310 | if fn_def.self_param(sema.db).is_some() { | ||
311 | bail!("Method already has a self parameter"); | 289 | bail!("Method already has a self parameter"); |
312 | } | 290 | } |
313 | 291 | ||
314 | let params = fn_def.assoc_fn_params(sema.db); | 292 | let params = fn_def.assoc_fn_params(sema.db); |
315 | let first_param = params.first().ok_or_else(|| format_err!("Method has no parameters"))?; | 293 | let first_param = params |
294 | .first() | ||
295 | .ok_or_else(|| format_err!("Cannot rename local to self unless it is a parameter"))?; | ||
296 | if first_param.as_local(sema.db) != local { | ||
297 | bail!("Only the first parameter may be renamed to self"); | ||
298 | } | ||
299 | |||
300 | let assoc_item = fn_def | ||
301 | .as_assoc_item(sema.db) | ||
302 | .ok_or_else(|| format_err!("Cannot rename parameter to self for free function"))?; | ||
303 | let impl_ = match assoc_item.container(sema.db) { | ||
304 | hir::AssocItemContainer::Trait(_) => { | ||
305 | bail!("Cannot rename parameter to self for trait functions"); | ||
306 | } | ||
307 | hir::AssocItemContainer::Impl(impl_) => impl_, | ||
308 | }; | ||
316 | let first_param_ty = first_param.ty(); | 309 | let first_param_ty = first_param.ty(); |
317 | let impl_ty = impl_block.target_ty(sema.db); | 310 | let impl_ty = impl_.target_ty(sema.db); |
318 | let (ty, self_param) = if impl_ty.remove_ref().is_some() { | 311 | let (ty, self_param) = if impl_ty.remove_ref().is_some() { |
319 | // if the impl is a ref to the type we can just match the `&T` with self directly | 312 | // if the impl is a ref to the type we can just match the `&T` with self directly |
320 | (first_param_ty.clone(), "self") | 313 | (first_param_ty.clone(), "self") |
@@ -328,6 +321,9 @@ fn rename_to_self(sema: &Semantics<RootDatabase>, local: hir::Local) -> RenameRe | |||
328 | bail!("Parameter type differs from impl block type"); | 321 | bail!("Parameter type differs from impl block type"); |
329 | } | 322 | } |
330 | 323 | ||
324 | let InFile { file_id, value: param_source } = | ||
325 | first_param.source(sema.db).ok_or_else(|| format_err!("No source for parameter found"))?; | ||
326 | |||
331 | let def = Definition::Local(local); | 327 | let def = Definition::Local(local); |
332 | let usages = def.usages(sema).all(); | 328 | let usages = def.usages(sema).all(); |
333 | let mut source_change = SourceChange::default(); | 329 | let mut source_change = SourceChange::default(); |
@@ -336,25 +332,20 @@ fn rename_to_self(sema: &Semantics<RootDatabase>, local: hir::Local) -> RenameRe | |||
336 | })); | 332 | })); |
337 | source_change.insert_source_edit( | 333 | source_change.insert_source_edit( |
338 | file_id.original_file(sema.db), | 334 | file_id.original_file(sema.db), |
339 | TextEdit::replace(first_param_range, String::from(self_param)), | 335 | TextEdit::replace(param_source.syntax().text_range(), String::from(self_param)), |
340 | ); | 336 | ); |
341 | |||
342 | Ok(source_change) | 337 | Ok(source_change) |
343 | } | 338 | } |
344 | 339 | ||
345 | fn rename_self_to_param( | 340 | fn rename_self_to_param( |
346 | sema: &Semantics<RootDatabase>, | 341 | sema: &Semantics<RootDatabase>, |
347 | local: hir::Local, | 342 | local: hir::Local, |
343 | self_param: hir::SelfParam, | ||
348 | new_name: &str, | 344 | new_name: &str, |
349 | identifier_kind: IdentifierKind, | 345 | identifier_kind: IdentifierKind, |
350 | ) -> RenameResult<SourceChange> { | 346 | ) -> RenameResult<SourceChange> { |
351 | let (file_id, self_param) = match local.source(sema.db) { | 347 | let InFile { file_id, value: self_param } = |
352 | InFile { file_id, value: Either::Right(self_param) } => (file_id, self_param), | 348 | self_param.source(sema.db).ok_or_else(|| format_err!("cannot find function source"))?; |
353 | _ => { | ||
354 | never!(true, "rename_self_to_param invoked on a non-self local"); | ||
355 | bail!("rename_self_to_param invoked on a non-self local"); | ||
356 | } | ||
357 | }; | ||
358 | 349 | ||
359 | let def = Definition::Local(local); | 350 | let def = Definition::Local(local); |
360 | let usages = def.usages(sema).all(); | 351 | let usages = def.usages(sema).all(); |
@@ -710,7 +701,7 @@ foo!(Foo$0);", | |||
710 | 701 | ||
711 | #[test] | 702 | #[test] |
712 | fn test_rename_for_local() { | 703 | fn test_rename_for_local() { |
713 | cov_mark::check!(rename_ident); | 704 | cov_mark::check!(rename_local); |
714 | check( | 705 | check( |
715 | "k", | 706 | "k", |
716 | r#" | 707 | r#" |
@@ -1251,6 +1242,7 @@ pub mod foo$0; | |||
1251 | 1242 | ||
1252 | #[test] | 1243 | #[test] |
1253 | fn test_enum_variant_from_module_1() { | 1244 | fn test_enum_variant_from_module_1() { |
1245 | cov_mark::check!(rename_non_local); | ||
1254 | check( | 1246 | check( |
1255 | "Baz", | 1247 | "Baz", |
1256 | r#" | 1248 | r#" |
@@ -1361,7 +1353,7 @@ fn f(foo$0: &mut Foo) -> i32 { | |||
1361 | foo.i | 1353 | foo.i |
1362 | } | 1354 | } |
1363 | "#, | 1355 | "#, |
1364 | "error: No impl block found for function", | 1356 | "error: Cannot rename parameter to self for free function", |
1365 | ); | 1357 | ); |
1366 | check( | 1358 | check( |
1367 | "self", | 1359 | "self", |
@@ -1391,7 +1383,7 @@ impl Foo { | |||
1391 | } | 1383 | } |
1392 | } | 1384 | } |
1393 | "#, | 1385 | "#, |
1394 | "error: Only the first parameter can be self", | 1386 | "error: Only the first parameter may be renamed to self", |
1395 | ); | 1387 | ); |
1396 | } | 1388 | } |
1397 | 1389 | ||
diff --git a/crates/ide/src/runnables.rs b/crates/ide/src/runnables.rs index bea020b06..5b488e2c5 100644 --- a/crates/ide/src/runnables.rs +++ b/crates/ide/src/runnables.rs | |||
@@ -7,17 +7,13 @@ use hir::{AsAssocItem, HasAttrs, HasSource, HirDisplay, Semantics}; | |||
7 | use ide_assists::utils::test_related_attribute; | 7 | use ide_assists::utils::test_related_attribute; |
8 | use ide_db::{ | 8 | use ide_db::{ |
9 | base_db::{FilePosition, FileRange}, | 9 | base_db::{FilePosition, FileRange}, |
10 | defs::Definition, | ||
11 | helpers::visit_file_defs, | 10 | helpers::visit_file_defs, |
12 | search::SearchScope, | 11 | search::SearchScope, |
13 | RootDatabase, SymbolKind, | 12 | RootDatabase, SymbolKind, |
14 | }; | 13 | }; |
15 | use itertools::Itertools; | 14 | use itertools::Itertools; |
16 | use rustc_hash::FxHashSet; | 15 | use rustc_hash::FxHashSet; |
17 | use syntax::{ | 16 | use syntax::ast::{self, AstNode, AttrsOwner}; |
18 | ast::{self, AstNode, AttrsOwner}, | ||
19 | match_ast, SyntaxNode, | ||
20 | }; | ||
21 | 17 | ||
22 | use crate::{ | 18 | use crate::{ |
23 | display::{ToNav, TryToNav}, | 19 | display::{ToNav, TryToNav}, |
@@ -271,28 +267,6 @@ pub(crate) fn runnable_mod(sema: &Semantics<RootDatabase>, def: hir::Module) -> | |||
271 | Some(Runnable { nav, kind: RunnableKind::TestMod { path }, cfg }) | 267 | Some(Runnable { nav, kind: RunnableKind::TestMod { path }, cfg }) |
272 | } | 268 | } |
273 | 269 | ||
274 | // FIXME: figure out a proper API here. | ||
275 | pub(crate) fn doc_owner_to_def( | ||
276 | sema: &Semantics<RootDatabase>, | ||
277 | item: SyntaxNode, | ||
278 | ) -> Option<Definition> { | ||
279 | let res: hir::ModuleDef = match_ast! { | ||
280 | match item { | ||
281 | ast::SourceFile(_it) => sema.scope(&item).module()?.into(), | ||
282 | ast::Fn(it) => sema.to_def(&it)?.into(), | ||
283 | ast::Struct(it) => sema.to_def(&it)?.into(), | ||
284 | ast::Enum(it) => sema.to_def(&it)?.into(), | ||
285 | ast::Union(it) => sema.to_def(&it)?.into(), | ||
286 | ast::Trait(it) => sema.to_def(&it)?.into(), | ||
287 | ast::Const(it) => sema.to_def(&it)?.into(), | ||
288 | ast::Static(it) => sema.to_def(&it)?.into(), | ||
289 | ast::TypeAlias(it) => sema.to_def(&it)?.into(), | ||
290 | _ => return None, | ||
291 | } | ||
292 | }; | ||
293 | Some(Definition::ModuleDef(res)) | ||
294 | } | ||
295 | |||
296 | fn module_def_doctest(sema: &Semantics<RootDatabase>, def: hir::ModuleDef) -> Option<Runnable> { | 270 | fn module_def_doctest(sema: &Semantics<RootDatabase>, def: hir::ModuleDef) -> Option<Runnable> { |
297 | let attrs = match def { | 271 | let attrs = match def { |
298 | hir::ModuleDef::Module(it) => it.attrs(sema.db), | 272 | hir::ModuleDef::Module(it) => it.attrs(sema.db), |
diff --git a/crates/ide/src/syntax_highlighting/inject.rs b/crates/ide/src/syntax_highlighting/inject.rs index 8e0940184..38bf49348 100644 --- a/crates/ide/src/syntax_highlighting/inject.rs +++ b/crates/ide/src/syntax_highlighting/inject.rs | |||
@@ -190,10 +190,10 @@ pub(super) fn doc_comment( | |||
190 | intra_doc_links.extend( | 190 | intra_doc_links.extend( |
191 | extract_definitions_from_markdown(line) | 191 | extract_definitions_from_markdown(line) |
192 | .into_iter() | 192 | .into_iter() |
193 | .filter_map(|(link, ns, range)| { | 193 | .filter_map(|(range, link, ns)| { |
194 | validate_intra_doc_link(sema.db, &def, &link, ns).zip(Some(range)) | 194 | Some(range).zip(validate_intra_doc_link(sema.db, &def, &link, ns)) |
195 | }) | 195 | }) |
196 | .map(|(def, Range { start, end })| { | 196 | .map(|(Range { start, end }, def)| { |
197 | ( | 197 | ( |
198 | def, | 198 | def, |
199 | TextRange::at( | 199 | TextRange::at( |
diff --git a/crates/ide_assists/src/assist_context.rs b/crates/ide_assists/src/assist_context.rs index bba6c08e0..1482d37f8 100644 --- a/crates/ide_assists/src/assist_context.rs +++ b/crates/ide_assists/src/assist_context.rs | |||
@@ -14,8 +14,8 @@ use ide_db::{ | |||
14 | }; | 14 | }; |
15 | use syntax::{ | 15 | use syntax::{ |
16 | algo::{self, find_node_at_offset, SyntaxRewriter}, | 16 | algo::{self, find_node_at_offset, SyntaxRewriter}, |
17 | AstNode, AstToken, SourceFile, SyntaxElement, SyntaxKind, SyntaxToken, TextRange, TextSize, | 17 | AstNode, AstToken, SourceFile, SyntaxElement, SyntaxKind, SyntaxNode, SyntaxNodePtr, |
18 | TokenAtOffset, | 18 | SyntaxToken, TextRange, TextSize, TokenAtOffset, |
19 | }; | 19 | }; |
20 | use text_edit::{TextEdit, TextEditBuilder}; | 20 | use text_edit::{TextEdit, TextEditBuilder}; |
21 | 21 | ||
@@ -180,11 +180,19 @@ pub(crate) struct AssistBuilder { | |||
180 | edit: TextEditBuilder, | 180 | edit: TextEditBuilder, |
181 | file_id: FileId, | 181 | file_id: FileId, |
182 | source_change: SourceChange, | 182 | source_change: SourceChange, |
183 | |||
184 | /// Maps the original, immutable `SyntaxNode` to a `clone_for_update` twin. | ||
185 | mutated_tree: Option<(SyntaxNode, SyntaxNode)>, | ||
183 | } | 186 | } |
184 | 187 | ||
185 | impl AssistBuilder { | 188 | impl AssistBuilder { |
186 | pub(crate) fn new(file_id: FileId) -> AssistBuilder { | 189 | pub(crate) fn new(file_id: FileId) -> AssistBuilder { |
187 | AssistBuilder { edit: TextEdit::builder(), file_id, source_change: SourceChange::default() } | 190 | AssistBuilder { |
191 | edit: TextEdit::builder(), | ||
192 | file_id, | ||
193 | source_change: SourceChange::default(), | ||
194 | mutated_tree: None, | ||
195 | } | ||
188 | } | 196 | } |
189 | 197 | ||
190 | pub(crate) fn edit_file(&mut self, file_id: FileId) { | 198 | pub(crate) fn edit_file(&mut self, file_id: FileId) { |
@@ -193,12 +201,42 @@ impl AssistBuilder { | |||
193 | } | 201 | } |
194 | 202 | ||
195 | fn commit(&mut self) { | 203 | fn commit(&mut self) { |
204 | if let Some((old, new)) = self.mutated_tree.take() { | ||
205 | algo::diff(&old, &new).into_text_edit(&mut self.edit) | ||
206 | } | ||
207 | |||
196 | let edit = mem::take(&mut self.edit).finish(); | 208 | let edit = mem::take(&mut self.edit).finish(); |
197 | if !edit.is_empty() { | 209 | if !edit.is_empty() { |
198 | self.source_change.insert_source_edit(self.file_id, edit); | 210 | self.source_change.insert_source_edit(self.file_id, edit); |
199 | } | 211 | } |
200 | } | 212 | } |
201 | 213 | ||
214 | pub(crate) fn make_ast_mut<N: AstNode>(&mut self, node: N) -> N { | ||
215 | N::cast(self.make_mut(node.syntax().clone())).unwrap() | ||
216 | } | ||
217 | /// Returns a copy of the `node`, suitable for mutation. | ||
218 | /// | ||
219 | /// Syntax trees in rust-analyzer are typically immutable, and mutating | ||
220 | /// operations panic at runtime. However, it is possible to make a copy of | ||
221 | /// the tree and mutate the copy freely. Mutation is based on interior | ||
222 | /// mutability, and different nodes in the same tree see the same mutations. | ||
223 | /// | ||
224 | /// The typical pattern for an assist is to find specific nodes in the read | ||
225 | /// phase, and then get their mutable couterparts using `make_mut` in the | ||
226 | /// mutable state. | ||
227 | pub(crate) fn make_mut(&mut self, node: SyntaxNode) -> SyntaxNode { | ||
228 | let root = &self | ||
229 | .mutated_tree | ||
230 | .get_or_insert_with(|| { | ||
231 | let immutable = node.ancestors().last().unwrap(); | ||
232 | let mutable = immutable.clone_for_update(); | ||
233 | (immutable, mutable) | ||
234 | }) | ||
235 | .1; | ||
236 | let ptr = SyntaxNodePtr::new(&&node); | ||
237 | ptr.to_node(root) | ||
238 | } | ||
239 | |||
202 | /// Remove specified `range` of text. | 240 | /// Remove specified `range` of text. |
203 | pub(crate) fn delete(&mut self, range: TextRange) { | 241 | pub(crate) fn delete(&mut self, range: TextRange) { |
204 | self.edit.delete(range) | 242 | self.edit.delete(range) |
diff --git a/crates/ide_assists/src/handlers/add_explicit_type.rs b/crates/ide_assists/src/handlers/add_explicit_type.rs index cb1548cef..62db31952 100644 --- a/crates/ide_assists/src/handlers/add_explicit_type.rs +++ b/crates/ide_assists/src/handlers/add_explicit_type.rs | |||
@@ -34,26 +34,33 @@ pub(crate) fn add_explicit_type(acc: &mut Assists, ctx: &AssistContext) -> Optio | |||
34 | // The binding must have a name | 34 | // The binding must have a name |
35 | let name = pat.name()?; | 35 | let name = pat.name()?; |
36 | let name_range = name.syntax().text_range(); | 36 | let name_range = name.syntax().text_range(); |
37 | let stmt_range = let_stmt.syntax().text_range(); | 37 | |
38 | let eq_range = let_stmt.eq_token()?.text_range(); | ||
39 | // Assist should only be applicable if cursor is between 'let' and '=' | 38 | // Assist should only be applicable if cursor is between 'let' and '=' |
40 | let let_range = TextRange::new(stmt_range.start(), eq_range.start()); | 39 | let cursor_in_range = { |
41 | let cursor_in_range = let_range.contains_range(ctx.frange.range); | 40 | let stmt_range = let_stmt.syntax().text_range(); |
41 | let eq_range = let_stmt.eq_token()?.text_range(); | ||
42 | let let_range = TextRange::new(stmt_range.start(), eq_range.start()); | ||
43 | let_range.contains_range(ctx.frange.range) | ||
44 | }; | ||
42 | if !cursor_in_range { | 45 | if !cursor_in_range { |
46 | cov_mark::hit!(add_explicit_type_not_applicable_if_cursor_after_equals); | ||
43 | return None; | 47 | return None; |
44 | } | 48 | } |
49 | |||
45 | // Assist not applicable if the type has already been specified | 50 | // Assist not applicable if the type has already been specified |
46 | // and it has no placeholders | 51 | // and it has no placeholders |
47 | let ascribed_ty = let_stmt.ty(); | 52 | let ascribed_ty = let_stmt.ty(); |
48 | if let Some(ty) = &ascribed_ty { | 53 | if let Some(ty) = &ascribed_ty { |
49 | if ty.syntax().descendants().find_map(ast::InferType::cast).is_none() { | 54 | if ty.syntax().descendants().find_map(ast::InferType::cast).is_none() { |
55 | cov_mark::hit!(add_explicit_type_not_applicable_if_ty_already_specified); | ||
50 | return None; | 56 | return None; |
51 | } | 57 | } |
52 | } | 58 | } |
59 | |||
53 | // Infer type | 60 | // Infer type |
54 | let ty = ctx.sema.type_of_expr(&expr)?; | 61 | let ty = ctx.sema.type_of_expr(&expr)?; |
55 | |||
56 | if ty.contains_unknown() || ty.is_closure() { | 62 | if ty.contains_unknown() || ty.is_closure() { |
63 | cov_mark::hit!(add_explicit_type_not_applicable_if_ty_not_inferred); | ||
57 | return None; | 64 | return None; |
58 | } | 65 | } |
59 | 66 | ||
@@ -81,17 +88,25 @@ mod tests { | |||
81 | 88 | ||
82 | #[test] | 89 | #[test] |
83 | fn add_explicit_type_target() { | 90 | fn add_explicit_type_target() { |
84 | check_assist_target(add_explicit_type, "fn f() { let a$0 = 1; }", "a"); | 91 | check_assist_target(add_explicit_type, r#"fn f() { let a$0 = 1; }"#, "a"); |
85 | } | 92 | } |
86 | 93 | ||
87 | #[test] | 94 | #[test] |
88 | fn add_explicit_type_works_for_simple_expr() { | 95 | fn add_explicit_type_works_for_simple_expr() { |
89 | check_assist(add_explicit_type, "fn f() { let a$0 = 1; }", "fn f() { let a: i32 = 1; }"); | 96 | check_assist( |
97 | add_explicit_type, | ||
98 | r#"fn f() { let a$0 = 1; }"#, | ||
99 | r#"fn f() { let a: i32 = 1; }"#, | ||
100 | ); | ||
90 | } | 101 | } |
91 | 102 | ||
92 | #[test] | 103 | #[test] |
93 | fn add_explicit_type_works_for_underscore() { | 104 | fn add_explicit_type_works_for_underscore() { |
94 | check_assist(add_explicit_type, "fn f() { let a$0: _ = 1; }", "fn f() { let a: i32 = 1; }"); | 105 | check_assist( |
106 | add_explicit_type, | ||
107 | r#"fn f() { let a$0: _ = 1; }"#, | ||
108 | r#"fn f() { let a: i32 = 1; }"#, | ||
109 | ); | ||
95 | } | 110 | } |
96 | 111 | ||
97 | #[test] | 112 | #[test] |
@@ -99,23 +114,19 @@ mod tests { | |||
99 | check_assist( | 114 | check_assist( |
100 | add_explicit_type, | 115 | add_explicit_type, |
101 | r#" | 116 | r#" |
102 | enum Option<T> { | 117 | enum Option<T> { Some(T), None } |
103 | Some(T), | ||
104 | None | ||
105 | } | ||
106 | 118 | ||
107 | fn f() { | 119 | fn f() { |
108 | let a$0: Option<_> = Option::Some(1); | 120 | let a$0: Option<_> = Option::Some(1); |
109 | }"#, | 121 | } |
122 | "#, | ||
110 | r#" | 123 | r#" |
111 | enum Option<T> { | 124 | enum Option<T> { Some(T), None } |
112 | Some(T), | ||
113 | None | ||
114 | } | ||
115 | 125 | ||
116 | fn f() { | 126 | fn f() { |
117 | let a: Option<i32> = Option::Some(1); | 127 | let a: Option<i32> = Option::Some(1); |
118 | }"#, | 128 | } |
129 | "#, | ||
119 | ); | 130 | ); |
120 | } | 131 | } |
121 | 132 | ||
@@ -139,24 +150,30 @@ mod tests { | |||
139 | 150 | ||
140 | #[test] | 151 | #[test] |
141 | fn add_explicit_type_not_applicable_if_ty_not_inferred() { | 152 | fn add_explicit_type_not_applicable_if_ty_not_inferred() { |
142 | check_assist_not_applicable(add_explicit_type, "fn f() { let a$0 = None; }"); | 153 | cov_mark::check!(add_explicit_type_not_applicable_if_ty_not_inferred); |
154 | check_assist_not_applicable(add_explicit_type, r#"fn f() { let a$0 = None; }"#); | ||
143 | } | 155 | } |
144 | 156 | ||
145 | #[test] | 157 | #[test] |
146 | fn add_explicit_type_not_applicable_if_ty_already_specified() { | 158 | fn add_explicit_type_not_applicable_if_ty_already_specified() { |
147 | check_assist_not_applicable(add_explicit_type, "fn f() { let a$0: i32 = 1; }"); | 159 | cov_mark::check!(add_explicit_type_not_applicable_if_ty_already_specified); |
160 | check_assist_not_applicable(add_explicit_type, r#"fn f() { let a$0: i32 = 1; }"#); | ||
148 | } | 161 | } |
149 | 162 | ||
150 | #[test] | 163 | #[test] |
151 | fn add_explicit_type_not_applicable_if_specified_ty_is_tuple() { | 164 | fn add_explicit_type_not_applicable_if_specified_ty_is_tuple() { |
152 | check_assist_not_applicable(add_explicit_type, "fn f() { let a$0: (i32, i32) = (3, 4); }"); | 165 | check_assist_not_applicable( |
166 | add_explicit_type, | ||
167 | r#"fn f() { let a$0: (i32, i32) = (3, 4); }"#, | ||
168 | ); | ||
153 | } | 169 | } |
154 | 170 | ||
155 | #[test] | 171 | #[test] |
156 | fn add_explicit_type_not_applicable_if_cursor_after_equals() { | 172 | fn add_explicit_type_not_applicable_if_cursor_after_equals() { |
173 | cov_mark::check!(add_explicit_type_not_applicable_if_cursor_after_equals); | ||
157 | check_assist_not_applicable( | 174 | check_assist_not_applicable( |
158 | add_explicit_type, | 175 | add_explicit_type, |
159 | "fn f() {let a =$0 match 1 {2 => 3, 3 => 5};}", | 176 | r#"fn f() {let a =$0 match 1 {2 => 3, 3 => 5};}"#, |
160 | ) | 177 | ) |
161 | } | 178 | } |
162 | 179 | ||
@@ -164,7 +181,7 @@ mod tests { | |||
164 | fn add_explicit_type_not_applicable_if_cursor_before_let() { | 181 | fn add_explicit_type_not_applicable_if_cursor_before_let() { |
165 | check_assist_not_applicable( | 182 | check_assist_not_applicable( |
166 | add_explicit_type, | 183 | add_explicit_type, |
167 | "fn f() $0{let a = match 1 {2 => 3, 3 => 5};}", | 184 | r#"fn f() $0{let a = match 1 {2 => 3, 3 => 5};}"#, |
168 | ) | 185 | ) |
169 | } | 186 | } |
170 | 187 | ||
@@ -176,7 +193,8 @@ mod tests { | |||
176 | fn main() { | 193 | fn main() { |
177 | let multiply_by_two$0 = |i| i * 3; | 194 | let multiply_by_two$0 = |i| i * 3; |
178 | let six = multiply_by_two(2); | 195 | let six = multiply_by_two(2); |
179 | }"#, | 196 | } |
197 | "#, | ||
180 | ) | 198 | ) |
181 | } | 199 | } |
182 | 200 | ||
@@ -185,23 +203,19 @@ fn main() { | |||
185 | check_assist( | 203 | check_assist( |
186 | add_explicit_type, | 204 | add_explicit_type, |
187 | r#" | 205 | r#" |
188 | struct Test<K, T = u8> { | 206 | struct Test<K, T = u8> { k: K, t: T } |
189 | k: K, | ||
190 | t: T, | ||
191 | } | ||
192 | 207 | ||
193 | fn main() { | 208 | fn main() { |
194 | let test$0 = Test { t: 23u8, k: 33 }; | 209 | let test$0 = Test { t: 23u8, k: 33 }; |
195 | }"#, | ||
196 | r#" | ||
197 | struct Test<K, T = u8> { | ||
198 | k: K, | ||
199 | t: T, | ||
200 | } | 210 | } |
211 | "#, | ||
212 | r#" | ||
213 | struct Test<K, T = u8> { k: K, t: T } | ||
201 | 214 | ||
202 | fn main() { | 215 | fn main() { |
203 | let test: Test<i32> = Test { t: 23u8, k: 33 }; | 216 | let test: Test<i32> = Test { t: 23u8, k: 33 }; |
204 | }"#, | 217 | } |
218 | "#, | ||
205 | ); | 219 | ); |
206 | } | 220 | } |
207 | } | 221 | } |
diff --git a/crates/ide_assists/src/handlers/add_lifetime_to_type.rs b/crates/ide_assists/src/handlers/add_lifetime_to_type.rs index 844928754..7030d0f97 100644 --- a/crates/ide_assists/src/handlers/add_lifetime_to_type.rs +++ b/crates/ide_assists/src/handlers/add_lifetime_to_type.rs | |||
@@ -1,5 +1,4 @@ | |||
1 | use ast::FieldList; | 1 | use syntax::ast::{self, AstNode, GenericParamsOwner, NameOwner}; |
2 | use syntax::ast::{self, AstNode, GenericParamsOwner, NameOwner, RefType, Type}; | ||
3 | 2 | ||
4 | use crate::{AssistContext, AssistId, AssistKind, Assists}; | 3 | use crate::{AssistContext, AssistId, AssistKind, Assists}; |
5 | 4 | ||
@@ -65,8 +64,8 @@ pub(crate) fn add_lifetime_to_type(acc: &mut Assists, ctx: &AssistContext) -> Op | |||
65 | ) | 64 | ) |
66 | } | 65 | } |
67 | 66 | ||
68 | fn fetch_borrowed_types(node: &ast::Adt) -> Option<Vec<RefType>> { | 67 | fn fetch_borrowed_types(node: &ast::Adt) -> Option<Vec<ast::RefType>> { |
69 | let ref_types: Vec<RefType> = match node { | 68 | let ref_types: Vec<ast::RefType> = match node { |
70 | ast::Adt::Enum(enum_) => { | 69 | ast::Adt::Enum(enum_) => { |
71 | let variant_list = enum_.variant_list()?; | 70 | let variant_list = enum_.variant_list()?; |
72 | variant_list | 71 | variant_list |
@@ -88,7 +87,7 @@ fn fetch_borrowed_types(node: &ast::Adt) -> Option<Vec<RefType>> { | |||
88 | record_field_list | 87 | record_field_list |
89 | .fields() | 88 | .fields() |
90 | .filter_map(|r_field| { | 89 | .filter_map(|r_field| { |
91 | if let Type::RefType(ref_type) = r_field.ty()? { | 90 | if let ast::Type::RefType(ref_type) = r_field.ty()? { |
92 | if ref_type.lifetime().is_none() { | 91 | if ref_type.lifetime().is_none() { |
93 | return Some(ref_type); | 92 | return Some(ref_type); |
94 | } | 93 | } |
@@ -107,12 +106,12 @@ fn fetch_borrowed_types(node: &ast::Adt) -> Option<Vec<RefType>> { | |||
107 | } | 106 | } |
108 | } | 107 | } |
109 | 108 | ||
110 | fn find_ref_types_from_field_list(field_list: &FieldList) -> Option<Vec<RefType>> { | 109 | fn find_ref_types_from_field_list(field_list: &ast::FieldList) -> Option<Vec<ast::RefType>> { |
111 | let ref_types: Vec<RefType> = match field_list { | 110 | let ref_types: Vec<ast::RefType> = match field_list { |
112 | ast::FieldList::RecordFieldList(record_list) => record_list | 111 | ast::FieldList::RecordFieldList(record_list) => record_list |
113 | .fields() | 112 | .fields() |
114 | .filter_map(|f| { | 113 | .filter_map(|f| { |
115 | if let Type::RefType(ref_type) = f.ty()? { | 114 | if let ast::Type::RefType(ref_type) = f.ty()? { |
116 | if ref_type.lifetime().is_none() { | 115 | if ref_type.lifetime().is_none() { |
117 | return Some(ref_type); | 116 | return Some(ref_type); |
118 | } | 117 | } |
@@ -124,7 +123,7 @@ fn find_ref_types_from_field_list(field_list: &FieldList) -> Option<Vec<RefType> | |||
124 | ast::FieldList::TupleFieldList(tuple_field_list) => tuple_field_list | 123 | ast::FieldList::TupleFieldList(tuple_field_list) => tuple_field_list |
125 | .fields() | 124 | .fields() |
126 | .filter_map(|f| { | 125 | .filter_map(|f| { |
127 | if let Type::RefType(ref_type) = f.ty()? { | 126 | if let ast::Type::RefType(ref_type) = f.ty()? { |
128 | if ref_type.lifetime().is_none() { | 127 | if ref_type.lifetime().is_none() { |
129 | return Some(ref_type); | 128 | return Some(ref_type); |
130 | } | 129 | } |
@@ -152,76 +151,79 @@ mod tests { | |||
152 | fn add_lifetime_to_struct() { | 151 | fn add_lifetime_to_struct() { |
153 | check_assist( | 152 | check_assist( |
154 | add_lifetime_to_type, | 153 | add_lifetime_to_type, |
155 | "struct Foo { a: &$0i32 }", | 154 | r#"struct Foo { a: &$0i32 }"#, |
156 | "struct Foo<'a> { a: &'a i32 }", | 155 | r#"struct Foo<'a> { a: &'a i32 }"#, |
157 | ); | 156 | ); |
158 | 157 | ||
159 | check_assist( | 158 | check_assist( |
160 | add_lifetime_to_type, | 159 | add_lifetime_to_type, |
161 | "struct Foo { a: &$0i32, b: &usize }", | 160 | r#"struct Foo { a: &$0i32, b: &usize }"#, |
162 | "struct Foo<'a> { a: &'a i32, b: &'a usize }", | 161 | r#"struct Foo<'a> { a: &'a i32, b: &'a usize }"#, |
163 | ); | 162 | ); |
164 | 163 | ||
165 | check_assist( | 164 | check_assist( |
166 | add_lifetime_to_type, | 165 | add_lifetime_to_type, |
167 | "struct Foo { a: &$0i32, b: usize }", | 166 | r#"struct Foo { a: &$0i32, b: usize }"#, |
168 | "struct Foo<'a> { a: &'a i32, b: usize }", | 167 | r#"struct Foo<'a> { a: &'a i32, b: usize }"#, |
169 | ); | 168 | ); |
170 | 169 | ||
171 | check_assist( | 170 | check_assist( |
172 | add_lifetime_to_type, | 171 | add_lifetime_to_type, |
173 | "struct Foo<T> { a: &$0T, b: usize }", | 172 | r#"struct Foo<T> { a: &$0T, b: usize }"#, |
174 | "struct Foo<'a, T> { a: &'a T, b: usize }", | 173 | r#"struct Foo<'a, T> { a: &'a T, b: usize }"#, |
175 | ); | 174 | ); |
176 | 175 | ||
177 | check_assist_not_applicable(add_lifetime_to_type, "struct Foo<'a> { a: &$0'a i32 }"); | 176 | check_assist_not_applicable(add_lifetime_to_type, r#"struct Foo<'a> { a: &$0'a i32 }"#); |
178 | check_assist_not_applicable(add_lifetime_to_type, "struct Foo { a: &'a$0 i32 }"); | 177 | check_assist_not_applicable(add_lifetime_to_type, r#"struct Foo { a: &'a$0 i32 }"#); |
179 | } | 178 | } |
180 | 179 | ||
181 | #[test] | 180 | #[test] |
182 | fn add_lifetime_to_enum() { | 181 | fn add_lifetime_to_enum() { |
183 | check_assist( | 182 | check_assist( |
184 | add_lifetime_to_type, | 183 | add_lifetime_to_type, |
185 | "enum Foo { Bar { a: i32 }, Other, Tuple(u32, &$0u32)}", | 184 | r#"enum Foo { Bar { a: i32 }, Other, Tuple(u32, &$0u32)}"#, |
186 | "enum Foo<'a> { Bar { a: i32 }, Other, Tuple(u32, &'a u32)}", | 185 | r#"enum Foo<'a> { Bar { a: i32 }, Other, Tuple(u32, &'a u32)}"#, |
187 | ); | 186 | ); |
188 | 187 | ||
189 | check_assist( | 188 | check_assist( |
190 | add_lifetime_to_type, | 189 | add_lifetime_to_type, |
191 | "enum Foo { Bar { a: &$0i32 }}", | 190 | r#"enum Foo { Bar { a: &$0i32 }}"#, |
192 | "enum Foo<'a> { Bar { a: &'a i32 }}", | 191 | r#"enum Foo<'a> { Bar { a: &'a i32 }}"#, |
193 | ); | 192 | ); |
194 | 193 | ||
195 | check_assist( | 194 | check_assist( |
196 | add_lifetime_to_type, | 195 | add_lifetime_to_type, |
197 | "enum Foo<T> { Bar { a: &$0i32, b: &T }}", | 196 | r#"enum Foo<T> { Bar { a: &$0i32, b: &T }}"#, |
198 | "enum Foo<'a, T> { Bar { a: &'a i32, b: &'a T }}", | 197 | r#"enum Foo<'a, T> { Bar { a: &'a i32, b: &'a T }}"#, |
199 | ); | 198 | ); |
200 | 199 | ||
201 | check_assist_not_applicable(add_lifetime_to_type, "enum Foo<'a> { Bar { a: &$0'a i32 }}"); | 200 | check_assist_not_applicable( |
202 | check_assist_not_applicable(add_lifetime_to_type, "enum Foo { Bar, $0Misc }"); | 201 | add_lifetime_to_type, |
202 | r#"enum Foo<'a> { Bar { a: &$0'a i32 }}"#, | ||
203 | ); | ||
204 | check_assist_not_applicable(add_lifetime_to_type, r#"enum Foo { Bar, $0Misc }"#); | ||
203 | } | 205 | } |
204 | 206 | ||
205 | #[test] | 207 | #[test] |
206 | fn add_lifetime_to_union() { | 208 | fn add_lifetime_to_union() { |
207 | check_assist( | 209 | check_assist( |
208 | add_lifetime_to_type, | 210 | add_lifetime_to_type, |
209 | "union Foo { a: &$0i32 }", | 211 | r#"union Foo { a: &$0i32 }"#, |
210 | "union Foo<'a> { a: &'a i32 }", | 212 | r#"union Foo<'a> { a: &'a i32 }"#, |
211 | ); | 213 | ); |
212 | 214 | ||
213 | check_assist( | 215 | check_assist( |
214 | add_lifetime_to_type, | 216 | add_lifetime_to_type, |
215 | "union Foo { a: &$0i32, b: &usize }", | 217 | r#"union Foo { a: &$0i32, b: &usize }"#, |
216 | "union Foo<'a> { a: &'a i32, b: &'a usize }", | 218 | r#"union Foo<'a> { a: &'a i32, b: &'a usize }"#, |
217 | ); | 219 | ); |
218 | 220 | ||
219 | check_assist( | 221 | check_assist( |
220 | add_lifetime_to_type, | 222 | add_lifetime_to_type, |
221 | "union Foo<T> { a: &$0T, b: usize }", | 223 | r#"union Foo<T> { a: &$0T, b: usize }"#, |
222 | "union Foo<'a, T> { a: &'a T, b: usize }", | 224 | r#"union Foo<'a, T> { a: &'a T, b: usize }"#, |
223 | ); | 225 | ); |
224 | 226 | ||
225 | check_assist_not_applicable(add_lifetime_to_type, "struct Foo<'a> { a: &'a $0i32 }"); | 227 | check_assist_not_applicable(add_lifetime_to_type, r#"struct Foo<'a> { a: &'a $0i32 }"#); |
226 | } | 228 | } |
227 | } | 229 | } |
diff --git a/crates/ide_assists/src/handlers/add_missing_impl_members.rs b/crates/ide_assists/src/handlers/add_missing_impl_members.rs index 63cea754d..0148635f9 100644 --- a/crates/ide_assists/src/handlers/add_missing_impl_members.rs +++ b/crates/ide_assists/src/handlers/add_missing_impl_members.rs | |||
@@ -3,9 +3,9 @@ use syntax::ast::{self, AstNode}; | |||
3 | 3 | ||
4 | use crate::{ | 4 | use crate::{ |
5 | assist_context::{AssistContext, Assists}, | 5 | assist_context::{AssistContext, Assists}, |
6 | utils::add_trait_assoc_items_to_impl, | 6 | utils::{ |
7 | utils::DefaultMethods, | 7 | add_trait_assoc_items_to_impl, filter_assoc_items, render_snippet, Cursor, DefaultMethods, |
8 | utils::{filter_assoc_items, render_snippet, Cursor}, | 8 | }, |
9 | AssistId, AssistKind, | 9 | AssistId, AssistKind, |
10 | }; | 10 | }; |
11 | 11 | ||
diff --git a/crates/ide_assists/src/handlers/convert_comment_block.rs b/crates/ide_assists/src/handlers/convert_comment_block.rs index 9dc3ee28f..d202a85f9 100644 --- a/crates/ide_assists/src/handlers/convert_comment_block.rs +++ b/crates/ide_assists/src/handlers/convert_comment_block.rs | |||
@@ -1,13 +1,6 @@ | |||
1 | use itertools::Itertools; | 1 | use itertools::Itertools; |
2 | use syntax::{ | 2 | use syntax::{ |
3 | ast::{ | 3 | ast::{self, edit::IndentLevel, Comment, CommentKind, CommentShape, Whitespace}, |
4 | self, | ||
5 | edit::IndentLevel, | ||
6 | Comment, CommentKind, | ||
7 | CommentPlacement::{Inner, Outer}, | ||
8 | CommentShape::{self, Block, Line}, | ||
9 | Whitespace, | ||
10 | }, | ||
11 | AstToken, Direction, SyntaxElement, TextRange, | 4 | AstToken, Direction, SyntaxElement, TextRange, |
12 | }; | 5 | }; |
13 | 6 | ||
@@ -29,21 +22,18 @@ use crate::{AssistContext, AssistId, AssistKind, Assists}; | |||
29 | /// */ | 22 | /// */ |
30 | /// ``` | 23 | /// ``` |
31 | pub(crate) fn convert_comment_block(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | 24 | pub(crate) fn convert_comment_block(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { |
32 | if let Some(comment) = ctx.find_token_at_offset::<ast::Comment>() { | 25 | let comment = ctx.find_token_at_offset::<ast::Comment>()?; |
33 | // Only allow comments which are alone on their line | 26 | // Only allow comments which are alone on their line |
34 | if let Some(prev) = comment.syntax().prev_token() { | 27 | if let Some(prev) = comment.syntax().prev_token() { |
35 | if Whitespace::cast(prev).filter(|w| w.text().contains('\n')).is_none() { | 28 | if Whitespace::cast(prev).filter(|w| w.text().contains('\n')).is_none() { |
36 | return None; | 29 | return None; |
37 | } | ||
38 | } | 30 | } |
39 | |||
40 | return match comment.kind().shape { | ||
41 | ast::CommentShape::Block => block_to_line(acc, comment), | ||
42 | ast::CommentShape::Line => line_to_block(acc, comment), | ||
43 | }; | ||
44 | } | 31 | } |
45 | 32 | ||
46 | return None; | 33 | match comment.kind().shape { |
34 | ast::CommentShape::Block => block_to_line(acc, comment), | ||
35 | ast::CommentShape::Line => line_to_block(acc, comment), | ||
36 | } | ||
47 | } | 37 | } |
48 | 38 | ||
49 | fn block_to_line(acc: &mut Assists, comment: ast::Comment) -> Option<()> { | 39 | fn block_to_line(acc: &mut Assists, comment: ast::Comment) -> Option<()> { |
@@ -55,8 +45,7 @@ fn block_to_line(acc: &mut Assists, comment: ast::Comment) -> Option<()> { | |||
55 | target, | 45 | target, |
56 | |edit| { | 46 | |edit| { |
57 | let indentation = IndentLevel::from_token(comment.syntax()); | 47 | let indentation = IndentLevel::from_token(comment.syntax()); |
58 | let line_prefix = | 48 | let line_prefix = CommentKind { shape: CommentShape::Line, ..comment.kind() }.prefix(); |
59 | comment_kind_prefix(CommentKind { shape: CommentShape::Line, ..comment.kind() }); | ||
60 | 49 | ||
61 | let text = comment.text(); | 50 | let text = comment.text(); |
62 | let text = &text[comment.prefix().len()..(text.len() - "*/".len())].trim(); | 51 | let text = &text[comment.prefix().len()..(text.len() - "*/".len())].trim(); |
@@ -105,7 +94,7 @@ fn line_to_block(acc: &mut Assists, comment: ast::Comment) -> Option<()> { | |||
105 | comments.into_iter().map(|c| line_comment_text(indentation, c)).join("\n"); | 94 | comments.into_iter().map(|c| line_comment_text(indentation, c)).join("\n"); |
106 | 95 | ||
107 | let block_prefix = | 96 | let block_prefix = |
108 | comment_kind_prefix(CommentKind { shape: CommentShape::Block, ..comment.kind() }); | 97 | CommentKind { shape: CommentShape::Block, ..comment.kind() }.prefix(); |
109 | 98 | ||
110 | let output = | 99 | let output = |
111 | format!("{}\n{}\n{}*/", block_prefix, block_comment_body, indentation.to_string()); | 100 | format!("{}\n{}\n{}*/", block_prefix, block_comment_body, indentation.to_string()); |
@@ -182,17 +171,6 @@ fn line_comment_text(indentation: IndentLevel, comm: ast::Comment) -> String { | |||
182 | } | 171 | } |
183 | } | 172 | } |
184 | 173 | ||
185 | fn comment_kind_prefix(ck: ast::CommentKind) -> &'static str { | ||
186 | match (ck.shape, ck.doc) { | ||
187 | (Line, Some(Inner)) => "//!", | ||
188 | (Line, Some(Outer)) => "///", | ||
189 | (Line, None) => "//", | ||
190 | (Block, Some(Inner)) => "/*!", | ||
191 | (Block, Some(Outer)) => "/**", | ||
192 | (Block, None) => "/*", | ||
193 | } | ||
194 | } | ||
195 | |||
196 | #[cfg(test)] | 174 | #[cfg(test)] |
197 | mod tests { | 175 | mod tests { |
198 | use crate::tests::{check_assist, check_assist_not_applicable}; | 176 | use crate::tests::{check_assist, check_assist_not_applicable}; |
diff --git a/crates/ide_assists/src/handlers/merge_imports.rs b/crates/ide_assists/src/handlers/merge_imports.rs index cfc472a32..8e0794218 100644 --- a/crates/ide_assists/src/handlers/merge_imports.rs +++ b/crates/ide_assists/src/handlers/merge_imports.rs | |||
@@ -21,12 +21,6 @@ use crate::{ | |||
21 | // ``` | 21 | // ``` |
22 | pub(crate) fn merge_imports(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | 22 | pub(crate) fn merge_imports(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { |
23 | let tree: ast::UseTree = ctx.find_node_at_offset()?; | 23 | let tree: ast::UseTree = ctx.find_node_at_offset()?; |
24 | let original_parent = tree.syntax().ancestors().last()?; | ||
25 | |||
26 | let tree = tree.clone_for_update(); | ||
27 | let new_parent = tree.syntax().ancestors().last()?; | ||
28 | |||
29 | let mut offset = ctx.offset(); | ||
30 | 24 | ||
31 | let mut imports = None; | 25 | let mut imports = None; |
32 | let mut uses = None; | 26 | let mut uses = None; |
@@ -53,22 +47,20 @@ pub(crate) fn merge_imports(acc: &mut Assists, ctx: &AssistContext) -> Option<() | |||
53 | target, | 47 | target, |
54 | |builder| { | 48 | |builder| { |
55 | if let Some((to_replace, replacement, to_remove)) = imports { | 49 | if let Some((to_replace, replacement, to_remove)) = imports { |
56 | if to_remove.syntax().text_range().end() < offset { | 50 | let to_replace = builder.make_ast_mut(to_replace); |
57 | offset -= to_remove.syntax().text_range().len(); | 51 | let to_remove = builder.make_ast_mut(to_remove); |
58 | } | 52 | |
59 | ted::replace(to_replace.syntax().clone(), replacement.syntax().clone()); | 53 | ted::replace(to_replace.syntax(), replacement.syntax()); |
60 | to_remove.remove(); | 54 | to_remove.remove(); |
61 | } | 55 | } |
62 | 56 | ||
63 | if let Some((to_replace, replacement, to_remove)) = uses { | 57 | if let Some((to_replace, replacement, to_remove)) = uses { |
64 | if to_remove.syntax().text_range().end() < offset { | 58 | let to_replace = builder.make_ast_mut(to_replace); |
65 | offset -= to_remove.syntax().text_range().len(); | 59 | let to_remove = builder.make_ast_mut(to_remove); |
66 | } | 60 | |
67 | ted::replace(to_replace.syntax().clone(), replacement.syntax().clone()); | 61 | ted::replace(to_replace.syntax(), replacement.syntax()); |
68 | to_remove.remove() | 62 | to_remove.remove() |
69 | } | 63 | } |
70 | |||
71 | builder.replace(original_parent.text_range(), new_parent.to_string()) | ||
72 | }, | 64 | }, |
73 | ) | 65 | ) |
74 | } | 66 | } |
diff --git a/crates/ide_assists/src/handlers/move_bounds.rs b/crates/ide_assists/src/handlers/move_bounds.rs index b5dec8014..011a28d44 100644 --- a/crates/ide_assists/src/handlers/move_bounds.rs +++ b/crates/ide_assists/src/handlers/move_bounds.rs | |||
@@ -21,7 +21,7 @@ use crate::{AssistContext, AssistId, AssistKind, Assists}; | |||
21 | // } | 21 | // } |
22 | // ``` | 22 | // ``` |
23 | pub(crate) fn move_bounds_to_where_clause(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | 23 | pub(crate) fn move_bounds_to_where_clause(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { |
24 | let type_param_list = ctx.find_node_at_offset::<ast::GenericParamList>()?.clone_for_update(); | 24 | let type_param_list = ctx.find_node_at_offset::<ast::GenericParamList>()?; |
25 | 25 | ||
26 | let mut type_params = type_param_list.type_params(); | 26 | let mut type_params = type_param_list.type_params(); |
27 | if type_params.all(|p| p.type_bound_list().is_none()) { | 27 | if type_params.all(|p| p.type_bound_list().is_none()) { |
@@ -29,7 +29,6 @@ pub(crate) fn move_bounds_to_where_clause(acc: &mut Assists, ctx: &AssistContext | |||
29 | } | 29 | } |
30 | 30 | ||
31 | let parent = type_param_list.syntax().parent()?; | 31 | let parent = type_param_list.syntax().parent()?; |
32 | let original_parent_range = parent.text_range(); | ||
33 | 32 | ||
34 | let target = type_param_list.syntax().text_range(); | 33 | let target = type_param_list.syntax().text_range(); |
35 | acc.add( | 34 | acc.add( |
@@ -37,6 +36,9 @@ pub(crate) fn move_bounds_to_where_clause(acc: &mut Assists, ctx: &AssistContext | |||
37 | "Move to where clause", | 36 | "Move to where clause", |
38 | target, | 37 | target, |
39 | |edit| { | 38 | |edit| { |
39 | let type_param_list = edit.make_ast_mut(type_param_list); | ||
40 | let parent = edit.make_mut(parent); | ||
41 | |||
40 | let where_clause: ast::WhereClause = match_ast! { | 42 | let where_clause: ast::WhereClause = match_ast! { |
41 | match parent { | 43 | match parent { |
42 | ast::Fn(it) => it.get_or_create_where_clause(), | 44 | ast::Fn(it) => it.get_or_create_where_clause(), |
@@ -56,8 +58,6 @@ pub(crate) fn move_bounds_to_where_clause(acc: &mut Assists, ctx: &AssistContext | |||
56 | tbl.remove() | 58 | tbl.remove() |
57 | } | 59 | } |
58 | } | 60 | } |
59 | |||
60 | edit.replace(original_parent_range, parent.to_string()) | ||
61 | }, | 61 | }, |
62 | ) | 62 | ) |
63 | } | 63 | } |
diff --git a/crates/ide_assists/src/handlers/reorder_fields.rs b/crates/ide_assists/src/handlers/reorder_fields.rs index 794c89323..383ca6c47 100644 --- a/crates/ide_assists/src/handlers/reorder_fields.rs +++ b/crates/ide_assists/src/handlers/reorder_fields.rs | |||
@@ -1,9 +1,6 @@ | |||
1 | use itertools::Itertools; | ||
2 | use rustc_hash::FxHashMap; | 1 | use rustc_hash::FxHashMap; |
3 | 2 | ||
4 | use hir::{Adt, ModuleDef, PathResolution, Semantics, Struct}; | 3 | use syntax::{algo, ast, match_ast, AstNode, SyntaxKind::*, SyntaxNode}; |
5 | use ide_db::RootDatabase; | ||
6 | use syntax::{algo, ast, match_ast, AstNode, SyntaxKind, SyntaxKind::*, SyntaxNode}; | ||
7 | 4 | ||
8 | use crate::{AssistContext, AssistId, AssistKind, Assists}; | 5 | use crate::{AssistContext, AssistId, AssistKind, Assists}; |
9 | 6 | ||
@@ -23,26 +20,39 @@ use crate::{AssistContext, AssistId, AssistKind, Assists}; | |||
23 | // ``` | 20 | // ``` |
24 | // | 21 | // |
25 | pub(crate) fn reorder_fields(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | 22 | pub(crate) fn reorder_fields(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { |
26 | reorder::<ast::RecordExpr>(acc, ctx).or_else(|| reorder::<ast::RecordPat>(acc, ctx)) | 23 | let record = ctx |
27 | } | 24 | .find_node_at_offset::<ast::RecordExpr>() |
25 | .map(|it| it.syntax().clone()) | ||
26 | .or_else(|| ctx.find_node_at_offset::<ast::RecordPat>().map(|it| it.syntax().clone()))?; | ||
28 | 27 | ||
29 | fn reorder<R: AstNode>(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | 28 | let path = record.children().find_map(ast::Path::cast)?; |
30 | let record = ctx.find_node_at_offset::<R>()?; | ||
31 | let path = record.syntax().children().find_map(ast::Path::cast)?; | ||
32 | 29 | ||
33 | let ranks = compute_fields_ranks(&path, &ctx)?; | 30 | let ranks = compute_fields_ranks(&path, &ctx)?; |
34 | 31 | ||
35 | let fields = get_fields(&record.syntax()); | 32 | let fields: Vec<SyntaxNode> = { |
36 | let sorted_fields = sorted_by_rank(&fields, |node| { | 33 | let field_kind = match record.kind() { |
37 | *ranks.get(&get_field_name(node)).unwrap_or(&usize::max_value()) | 34 | RECORD_EXPR => RECORD_EXPR_FIELD, |
38 | }); | 35 | RECORD_PAT => RECORD_PAT_FIELD, |
36 | _ => { | ||
37 | stdx::never!(); | ||
38 | return None; | ||
39 | } | ||
40 | }; | ||
41 | record.children().flat_map(|n| n.children()).filter(|n| n.kind() == field_kind).collect() | ||
42 | }; | ||
43 | |||
44 | let sorted_fields = { | ||
45 | let mut fields = fields.clone(); | ||
46 | fields.sort_by_key(|node| *ranks.get(&get_field_name(node)).unwrap_or(&usize::max_value())); | ||
47 | fields | ||
48 | }; | ||
39 | 49 | ||
40 | if sorted_fields == fields { | 50 | if sorted_fields == fields { |
41 | cov_mark::hit!(reorder_sorted_fields); | 51 | cov_mark::hit!(reorder_sorted_fields); |
42 | return None; | 52 | return None; |
43 | } | 53 | } |
44 | 54 | ||
45 | let target = record.syntax().text_range(); | 55 | let target = record.text_range(); |
46 | acc.add( | 56 | acc.add( |
47 | AssistId("reorder_fields", AssistKind::RefactorRewrite), | 57 | AssistId("reorder_fields", AssistKind::RefactorRewrite), |
48 | "Reorder record fields", | 58 | "Reorder record fields", |
@@ -57,14 +67,6 @@ fn reorder<R: AstNode>(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | |||
57 | ) | 67 | ) |
58 | } | 68 | } |
59 | 69 | ||
60 | fn get_fields_kind(node: &SyntaxNode) -> Vec<SyntaxKind> { | ||
61 | match node.kind() { | ||
62 | RECORD_EXPR => vec![RECORD_EXPR_FIELD], | ||
63 | RECORD_PAT => vec![RECORD_PAT_FIELD, IDENT_PAT], | ||
64 | _ => vec![], | ||
65 | } | ||
66 | } | ||
67 | |||
68 | fn get_field_name(node: &SyntaxNode) -> String { | 70 | fn get_field_name(node: &SyntaxNode) -> String { |
69 | let res = match_ast! { | 71 | let res = match_ast! { |
70 | match node { | 72 | match node { |
@@ -76,34 +78,20 @@ fn get_field_name(node: &SyntaxNode) -> String { | |||
76 | res.unwrap_or_default() | 78 | res.unwrap_or_default() |
77 | } | 79 | } |
78 | 80 | ||
79 | fn get_fields(record: &SyntaxNode) -> Vec<SyntaxNode> { | 81 | fn compute_fields_ranks(path: &ast::Path, ctx: &AssistContext) -> Option<FxHashMap<String, usize>> { |
80 | let kinds = get_fields_kind(record); | 82 | let strukt = match ctx.sema.resolve_path(path) { |
81 | record.children().flat_map(|n| n.children()).filter(|n| kinds.contains(&n.kind())).collect() | 83 | Some(hir::PathResolution::Def(hir::ModuleDef::Adt(hir::Adt::Struct(it)))) => it, |
82 | } | 84 | _ => return None, |
83 | 85 | }; | |
84 | fn sorted_by_rank( | ||
85 | fields: &[SyntaxNode], | ||
86 | get_rank: impl Fn(&SyntaxNode) -> usize, | ||
87 | ) -> Vec<SyntaxNode> { | ||
88 | fields.iter().cloned().sorted_by_key(get_rank).collect() | ||
89 | } | ||
90 | 86 | ||
91 | fn struct_definition(path: &ast::Path, sema: &Semantics<RootDatabase>) -> Option<Struct> { | 87 | let res = strukt |
92 | match sema.resolve_path(path) { | 88 | .fields(ctx.db()) |
93 | Some(PathResolution::Def(ModuleDef::Adt(Adt::Struct(s)))) => Some(s), | 89 | .iter() |
94 | _ => None, | 90 | .enumerate() |
95 | } | 91 | .map(|(idx, field)| (field.name(ctx.db()).to_string(), idx)) |
96 | } | 92 | .collect(); |
97 | 93 | ||
98 | fn compute_fields_ranks(path: &ast::Path, ctx: &AssistContext) -> Option<FxHashMap<String, usize>> { | 94 | Some(res) |
99 | Some( | ||
100 | struct_definition(path, &ctx.sema)? | ||
101 | .fields(ctx.db()) | ||
102 | .iter() | ||
103 | .enumerate() | ||
104 | .map(|(idx, field)| (field.name(ctx.db()).to_string(), idx)) | ||
105 | .collect(), | ||
106 | ) | ||
107 | } | 95 | } |
108 | 96 | ||
109 | #[cfg(test)] | 97 | #[cfg(test)] |
@@ -118,11 +106,7 @@ mod tests { | |||
118 | check_assist_not_applicable( | 106 | check_assist_not_applicable( |
119 | reorder_fields, | 107 | reorder_fields, |
120 | r#" | 108 | r#" |
121 | struct Foo { | 109 | struct Foo { foo: i32, bar: i32 } |
122 | foo: i32, | ||
123 | bar: i32, | ||
124 | } | ||
125 | |||
126 | const test: Foo = $0Foo { foo: 0, bar: 0 }; | 110 | const test: Foo = $0Foo { foo: 0, bar: 0 }; |
127 | "#, | 111 | "#, |
128 | ) | 112 | ) |
@@ -133,8 +117,8 @@ const test: Foo = $0Foo { foo: 0, bar: 0 }; | |||
133 | check_assist_not_applicable( | 117 | check_assist_not_applicable( |
134 | reorder_fields, | 118 | reorder_fields, |
135 | r#" | 119 | r#" |
136 | struct Foo {}; | 120 | struct Foo {} |
137 | const test: Foo = $0Foo {} | 121 | const test: Foo = $0Foo {}; |
138 | "#, | 122 | "#, |
139 | ) | 123 | ) |
140 | } | 124 | } |
@@ -144,12 +128,12 @@ const test: Foo = $0Foo {} | |||
144 | check_assist( | 128 | check_assist( |
145 | reorder_fields, | 129 | reorder_fields, |
146 | r#" | 130 | r#" |
147 | struct Foo {foo: i32, bar: i32}; | 131 | struct Foo { foo: i32, bar: i32 } |
148 | const test: Foo = $0Foo {bar: 0, foo: 1} | 132 | const test: Foo = $0Foo { bar: 0, foo: 1 }; |
149 | "#, | 133 | "#, |
150 | r#" | 134 | r#" |
151 | struct Foo {foo: i32, bar: i32}; | 135 | struct Foo { foo: i32, bar: i32 } |
152 | const test: Foo = Foo {foo: 1, bar: 0} | 136 | const test: Foo = Foo { foo: 1, bar: 0 }; |
153 | "#, | 137 | "#, |
154 | ) | 138 | ) |
155 | } | 139 | } |
@@ -186,10 +170,7 @@ fn f(f: Foo) -> { | |||
186 | check_assist( | 170 | check_assist( |
187 | reorder_fields, | 171 | reorder_fields, |
188 | r#" | 172 | r#" |
189 | struct Foo { | 173 | struct Foo { foo: String, bar: String } |
190 | foo: String, | ||
191 | bar: String, | ||
192 | } | ||
193 | 174 | ||
194 | impl Foo { | 175 | impl Foo { |
195 | fn new() -> Foo { | 176 | fn new() -> Foo { |
@@ -203,10 +184,7 @@ impl Foo { | |||
203 | } | 184 | } |
204 | "#, | 185 | "#, |
205 | r#" | 186 | r#" |
206 | struct Foo { | 187 | struct Foo { foo: String, bar: String } |
207 | foo: String, | ||
208 | bar: String, | ||
209 | } | ||
210 | 188 | ||
211 | impl Foo { | 189 | impl Foo { |
212 | fn new() -> Foo { | 190 | fn new() -> Foo { |
diff --git a/crates/proc_macro_api/src/msg.rs b/crates/proc_macro_api/src/msg.rs index 970f165ed..f525df152 100644 --- a/crates/proc_macro_api/src/msg.rs +++ b/crates/proc_macro_api/src/msg.rs | |||
@@ -55,8 +55,8 @@ pub enum ErrorCode { | |||
55 | } | 55 | } |
56 | 56 | ||
57 | pub trait Message: Serialize + DeserializeOwned { | 57 | pub trait Message: Serialize + DeserializeOwned { |
58 | fn read(inp: &mut impl BufRead) -> io::Result<Option<Self>> { | 58 | fn read(inp: &mut impl BufRead, buf: &mut String) -> io::Result<Option<Self>> { |
59 | Ok(match read_json(inp)? { | 59 | Ok(match read_json(inp, buf)? { |
60 | None => None, | 60 | None => None, |
61 | Some(text) => { | 61 | Some(text) => { |
62 | let mut deserializer = serde_json::Deserializer::from_str(&text); | 62 | let mut deserializer = serde_json::Deserializer::from_str(&text); |
@@ -76,14 +76,29 @@ pub trait Message: Serialize + DeserializeOwned { | |||
76 | impl Message for Request {} | 76 | impl Message for Request {} |
77 | impl Message for Response {} | 77 | impl Message for Response {} |
78 | 78 | ||
79 | fn read_json(inp: &mut impl BufRead) -> io::Result<Option<String>> { | 79 | fn read_json<'a>( |
80 | let mut buf = String::new(); | 80 | inp: &mut impl BufRead, |
81 | inp.read_line(&mut buf)?; | 81 | mut buf: &'a mut String, |
82 | buf.pop(); // Remove trailing '\n' | 82 | ) -> io::Result<Option<&'a String>> { |
83 | Ok(match buf.len() { | 83 | loop { |
84 | 0 => None, | 84 | buf.clear(); |
85 | _ => Some(buf), | 85 | |
86 | }) | 86 | inp.read_line(&mut buf)?; |
87 | buf.pop(); // Remove trailing '\n' | ||
88 | |||
89 | if buf.is_empty() { | ||
90 | return Ok(None); | ||
91 | } | ||
92 | |||
93 | // Some ill behaved macro try to use stdout for debugging | ||
94 | // We ignore it here | ||
95 | if !buf.starts_with("{") { | ||
96 | log::error!("proc-macro tried to print : {}", buf); | ||
97 | continue; | ||
98 | } | ||
99 | |||
100 | return Ok(Some(buf)); | ||
101 | } | ||
87 | } | 102 | } |
88 | 103 | ||
89 | fn write_json(out: &mut impl Write, msg: &str) -> io::Result<()> { | 104 | fn write_json(out: &mut impl Write, msg: &str) -> io::Result<()> { |
diff --git a/crates/proc_macro_api/src/process.rs b/crates/proc_macro_api/src/process.rs index 30bb1b687..99d05aef3 100644 --- a/crates/proc_macro_api/src/process.rs +++ b/crates/proc_macro_api/src/process.rs | |||
@@ -90,8 +90,10 @@ impl ProcMacroProcessSrv { | |||
90 | fn client_loop(task_rx: Receiver<Task>, mut process: Process) { | 90 | fn client_loop(task_rx: Receiver<Task>, mut process: Process) { |
91 | let (mut stdin, mut stdout) = process.stdio().expect("couldn't access child stdio"); | 91 | let (mut stdin, mut stdout) = process.stdio().expect("couldn't access child stdio"); |
92 | 92 | ||
93 | let mut buf = String::new(); | ||
94 | |||
93 | for Task { req, result_tx } in task_rx { | 95 | for Task { req, result_tx } in task_rx { |
94 | match send_request(&mut stdin, &mut stdout, req) { | 96 | match send_request(&mut stdin, &mut stdout, req, &mut buf) { |
95 | Ok(res) => result_tx.send(res).unwrap(), | 97 | Ok(res) => result_tx.send(res).unwrap(), |
96 | Err(err) => { | 98 | Err(err) => { |
97 | log::error!( | 99 | log::error!( |
@@ -152,7 +154,8 @@ fn send_request( | |||
152 | mut writer: &mut impl Write, | 154 | mut writer: &mut impl Write, |
153 | mut reader: &mut impl BufRead, | 155 | mut reader: &mut impl BufRead, |
154 | req: Request, | 156 | req: Request, |
157 | buf: &mut String, | ||
155 | ) -> io::Result<Option<Response>> { | 158 | ) -> io::Result<Option<Response>> { |
156 | req.write(&mut writer)?; | 159 | req.write(&mut writer)?; |
157 | Response::read(&mut reader) | 160 | Response::read(&mut reader, buf) |
158 | } | 161 | } |
diff --git a/crates/proc_macro_api/src/rpc.rs b/crates/proc_macro_api/src/rpc.rs index 9a68e2cc5..8f7270afe 100644 --- a/crates/proc_macro_api/src/rpc.rs +++ b/crates/proc_macro_api/src/rpc.rs | |||
@@ -77,7 +77,11 @@ struct TokenIdDef(u32); | |||
77 | #[derive(Serialize, Deserialize)] | 77 | #[derive(Serialize, Deserialize)] |
78 | #[serde(remote = "Delimiter")] | 78 | #[serde(remote = "Delimiter")] |
79 | struct DelimiterDef { | 79 | struct DelimiterDef { |
80 | #[serde(with = "TokenIdDef")] | 80 | #[serde( |
81 | with = "TokenIdDef", | ||
82 | default = "tt::TokenId::unspecified", | ||
83 | skip_serializing_if = "token_id_def::skip_if" | ||
84 | )] | ||
81 | id: TokenId, | 85 | id: TokenId, |
82 | #[serde(with = "DelimiterKindDef")] | 86 | #[serde(with = "DelimiterKindDef")] |
83 | kind: DelimiterKind, | 87 | kind: DelimiterKind, |
@@ -116,7 +120,11 @@ enum LeafDef { | |||
116 | #[serde(remote = "Literal")] | 120 | #[serde(remote = "Literal")] |
117 | struct LiteralDef { | 121 | struct LiteralDef { |
118 | text: SmolStr, | 122 | text: SmolStr, |
119 | #[serde(with = "TokenIdDef")] | 123 | #[serde( |
124 | with = "TokenIdDef", | ||
125 | default = "tt::TokenId::unspecified", | ||
126 | skip_serializing_if = "token_id_def::skip_if" | ||
127 | )] | ||
120 | id: TokenId, | 128 | id: TokenId, |
121 | } | 129 | } |
122 | 130 | ||
@@ -126,7 +134,11 @@ struct PunctDef { | |||
126 | char: char, | 134 | char: char, |
127 | #[serde(with = "SpacingDef")] | 135 | #[serde(with = "SpacingDef")] |
128 | spacing: Spacing, | 136 | spacing: Spacing, |
129 | #[serde(with = "TokenIdDef")] | 137 | #[serde( |
138 | with = "TokenIdDef", | ||
139 | default = "tt::TokenId::unspecified", | ||
140 | skip_serializing_if = "token_id_def::skip_if" | ||
141 | )] | ||
130 | id: TokenId, | 142 | id: TokenId, |
131 | } | 143 | } |
132 | 144 | ||
@@ -141,10 +153,20 @@ enum SpacingDef { | |||
141 | #[serde(remote = "Ident")] | 153 | #[serde(remote = "Ident")] |
142 | struct IdentDef { | 154 | struct IdentDef { |
143 | text: SmolStr, | 155 | text: SmolStr, |
144 | #[serde(with = "TokenIdDef")] | 156 | #[serde( |
157 | with = "TokenIdDef", | ||
158 | default = "tt::TokenId::unspecified", | ||
159 | skip_serializing_if = "token_id_def::skip_if" | ||
160 | )] | ||
145 | id: TokenId, | 161 | id: TokenId, |
146 | } | 162 | } |
147 | 163 | ||
164 | mod token_id_def { | ||
165 | pub(super) fn skip_if(value: &tt::TokenId) -> bool { | ||
166 | *value == tt::TokenId::unspecified() | ||
167 | } | ||
168 | } | ||
169 | |||
148 | mod opt_delimiter_def { | 170 | mod opt_delimiter_def { |
149 | use super::{Delimiter, DelimiterDef}; | 171 | use super::{Delimiter, DelimiterDef}; |
150 | use serde::{Deserialize, Deserializer, Serialize, Serializer}; | 172 | use serde::{Deserialize, Deserializer, Serialize, Serializer}; |
diff --git a/crates/proc_macro_srv/src/cli.rs b/crates/proc_macro_srv/src/cli.rs index d428b9567..bc48f1c43 100644 --- a/crates/proc_macro_srv/src/cli.rs +++ b/crates/proc_macro_srv/src/cli.rs | |||
@@ -6,8 +6,9 @@ use std::io; | |||
6 | 6 | ||
7 | pub fn run() -> io::Result<()> { | 7 | pub fn run() -> io::Result<()> { |
8 | let mut srv = ProcMacroSrv::default(); | 8 | let mut srv = ProcMacroSrv::default(); |
9 | let mut buf = String::new(); | ||
9 | 10 | ||
10 | while let Some(req) = read_request()? { | 11 | while let Some(req) = read_request(&mut buf)? { |
11 | let res = match req { | 12 | let res = match req { |
12 | msg::Request::ListMacro(task) => srv.list_macros(&task).map(msg::Response::ListMacro), | 13 | msg::Request::ListMacro(task) => srv.list_macros(&task).map(msg::Response::ListMacro), |
13 | msg::Request::ExpansionMacro(task) => { | 14 | msg::Request::ExpansionMacro(task) => { |
@@ -30,8 +31,8 @@ pub fn run() -> io::Result<()> { | |||
30 | Ok(()) | 31 | Ok(()) |
31 | } | 32 | } |
32 | 33 | ||
33 | fn read_request() -> io::Result<Option<msg::Request>> { | 34 | fn read_request(buf: &mut String) -> io::Result<Option<msg::Request>> { |
34 | msg::Request::read(&mut io::stdin().lock()) | 35 | msg::Request::read(&mut io::stdin().lock(), buf) |
35 | } | 36 | } |
36 | 37 | ||
37 | fn write_response(msg: msg::Response) -> io::Result<()> { | 38 | fn write_response(msg: msg::Response) -> io::Result<()> { |
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index 5c88c3a9b..cda272fd4 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs | |||
@@ -131,8 +131,8 @@ config_data! { | |||
131 | 131 | ||
132 | /// Whether to show inlay type hints for method chains. | 132 | /// Whether to show inlay type hints for method chains. |
133 | inlayHints_chainingHints: bool = "true", | 133 | inlayHints_chainingHints: bool = "true", |
134 | /// Maximum length for inlay hints. Default is unlimited. | 134 | /// Maximum length for inlay hints. Set to null to have an unlimited length. |
135 | inlayHints_maxLength: Option<usize> = "null", | 135 | inlayHints_maxLength: Option<usize> = "25", |
136 | /// Whether to show function parameter name inlay hints at the call | 136 | /// Whether to show function parameter name inlay hints at the call |
137 | /// site. | 137 | /// site. |
138 | inlayHints_parameterHints: bool = "true", | 138 | inlayHints_parameterHints: bool = "true", |
diff --git a/crates/rust-analyzer/tests/rust-analyzer/main.rs b/crates/rust-analyzer/tests/rust-analyzer/main.rs index 19516de7b..4442cbff6 100644 --- a/crates/rust-analyzer/tests/rust-analyzer/main.rs +++ b/crates/rust-analyzer/tests/rust-analyzer/main.rs | |||
@@ -712,6 +712,10 @@ pub fn foo(_input: TokenStream) -> TokenStream { | |||
712 | // We hard code the output here for preventing to use any deps | 712 | // We hard code the output here for preventing to use any deps |
713 | let mut res = TokenStream::new(); | 713 | let mut res = TokenStream::new(); |
714 | 714 | ||
715 | // ill behaved proc-macro will use the stdout | ||
716 | // we should ignore it | ||
717 | println!("I am bad guy"); | ||
718 | |||
715 | // impl Bar for Foo { fn bar() {} } | 719 | // impl Bar for Foo { fn bar() {} } |
716 | let mut tokens = vec![t!("impl"), t!("Bar"), t!("for"), t!("Foo")]; | 720 | let mut tokens = vec![t!("impl"), t!("Bar"), t!("for"), t!("Foo")]; |
717 | let mut fn_stream = TokenStream::new(); | 721 | let mut fn_stream = TokenStream::new(); |
diff --git a/crates/syntax/src/ast/token_ext.rs b/crates/syntax/src/ast/token_ext.rs index 090282d28..29d25a58a 100644 --- a/crates/syntax/src/ast/token_ext.rs +++ b/crates/syntax/src/ast/token_ext.rs | |||
@@ -102,8 +102,9 @@ impl CommentKind { | |||
102 | kind | 102 | kind |
103 | } | 103 | } |
104 | 104 | ||
105 | fn prefix(&self) -> &'static str { | 105 | pub fn prefix(&self) -> &'static str { |
106 | let &(prefix, _) = CommentKind::BY_PREFIX.iter().find(|(_, kind)| kind == self).unwrap(); | 106 | let &(prefix, _) = |
107 | CommentKind::BY_PREFIX.iter().rev().find(|(_, kind)| kind == self).unwrap(); | ||
107 | prefix | 108 | prefix |
108 | } | 109 | } |
109 | } | 110 | } |