diff options
27 files changed, 871 insertions, 723 deletions
diff --git a/Cargo.lock b/Cargo.lock index 19ee689cb..7f666ca61 100644 --- a/Cargo.lock +++ b/Cargo.lock | |||
@@ -35,9 +35,9 @@ dependencies = [ | |||
35 | 35 | ||
36 | [[package]] | 36 | [[package]] |
37 | name = "anyhow" | 37 | name = "anyhow" |
38 | version = "1.0.38" | 38 | version = "1.0.39" |
39 | source = "registry+https://github.com/rust-lang/crates.io-index" | 39 | source = "registry+https://github.com/rust-lang/crates.io-index" |
40 | checksum = "afddf7f520a80dbf76e6f50a35bca42a2331ef227a28b3b6dc5c2e2338d114b1" | 40 | checksum = "81cddc5f91628367664cc7c69714ff08deee8a3efc54623011c772544d7b2767" |
41 | 41 | ||
42 | [[package]] | 42 | [[package]] |
43 | name = "anymap" | 43 | name = "anymap" |
@@ -111,9 +111,9 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" | |||
111 | 111 | ||
112 | [[package]] | 112 | [[package]] |
113 | name = "camino" | 113 | name = "camino" |
114 | version = "1.0.2" | 114 | version = "1.0.4" |
115 | source = "registry+https://github.com/rust-lang/crates.io-index" | 115 | source = "registry+https://github.com/rust-lang/crates.io-index" |
116 | checksum = "cd065703998b183ed0b348a22555691373a9345a1431141e5778b48bb17e4703" | 116 | checksum = "d4648c6d00a709aa069a236adcaae4f605a6241c72bf5bee79331a4b625921a9" |
117 | dependencies = [ | 117 | dependencies = [ |
118 | "serde", | 118 | "serde", |
119 | ] | 119 | ] |
@@ -794,9 +794,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" | |||
794 | 794 | ||
795 | [[package]] | 795 | [[package]] |
796 | name = "libc" | 796 | name = "libc" |
797 | version = "0.2.88" | 797 | version = "0.2.91" |
798 | source = "registry+https://github.com/rust-lang/crates.io-index" | 798 | source = "registry+https://github.com/rust-lang/crates.io-index" |
799 | checksum = "03b07a082330a35e43f63177cc01689da34fbffa0105e1246cf0311472cac73a" | 799 | checksum = "8916b1f6ca17130ec6568feccee27c156ad12037880833a3b842a823236502e7" |
800 | 800 | ||
801 | [[package]] | 801 | [[package]] |
802 | name = "libloading" | 802 | name = "libloading" |
@@ -946,9 +946,9 @@ dependencies = [ | |||
946 | 946 | ||
947 | [[package]] | 947 | [[package]] |
948 | name = "mio" | 948 | name = "mio" |
949 | version = "0.7.9" | 949 | version = "0.7.11" |
950 | source = "registry+https://github.com/rust-lang/crates.io-index" | 950 | source = "registry+https://github.com/rust-lang/crates.io-index" |
951 | checksum = "a5dede4e2065b3842b8b0af444119f3aa331cc7cc2dd20388bfb0f5d5a38823a" | 951 | checksum = "cf80d3e903b34e0bd7282b218398aec54e082c840d9baf8339e0080a0c542956" |
952 | dependencies = [ | 952 | dependencies = [ |
953 | "libc", | 953 | "libc", |
954 | "log", | 954 | "log", |
@@ -959,11 +959,10 @@ dependencies = [ | |||
959 | 959 | ||
960 | [[package]] | 960 | [[package]] |
961 | name = "miow" | 961 | name = "miow" |
962 | version = "0.3.6" | 962 | version = "0.3.7" |
963 | source = "registry+https://github.com/rust-lang/crates.io-index" | 963 | source = "registry+https://github.com/rust-lang/crates.io-index" |
964 | checksum = "5a33c1b55807fbed163481b5ba66db4b2fa6cde694a5027be10fb724206c5897" | 964 | checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" |
965 | dependencies = [ | 965 | dependencies = [ |
966 | "socket2", | ||
967 | "winapi", | 966 | "winapi", |
968 | ] | 967 | ] |
969 | 968 | ||
@@ -1299,9 +1298,9 @@ dependencies = [ | |||
1299 | 1298 | ||
1300 | [[package]] | 1299 | [[package]] |
1301 | name = "regex" | 1300 | name = "regex" |
1302 | version = "1.4.3" | 1301 | version = "1.4.5" |
1303 | source = "registry+https://github.com/rust-lang/crates.io-index" | 1302 | source = "registry+https://github.com/rust-lang/crates.io-index" |
1304 | checksum = "d9251239e129e16308e70d853559389de218ac275b515068abc96829d05b948a" | 1303 | checksum = "957056ecddbeba1b26965114e191d2e8589ce74db242b6ea25fc4062427a5c19" |
1305 | dependencies = [ | 1304 | dependencies = [ |
1306 | "regex-syntax", | 1305 | "regex-syntax", |
1307 | ] | 1306 | ] |
@@ -1318,9 +1317,9 @@ dependencies = [ | |||
1318 | 1317 | ||
1319 | [[package]] | 1318 | [[package]] |
1320 | name = "regex-syntax" | 1319 | name = "regex-syntax" |
1321 | version = "0.6.22" | 1320 | version = "0.6.23" |
1322 | source = "registry+https://github.com/rust-lang/crates.io-index" | 1321 | source = "registry+https://github.com/rust-lang/crates.io-index" |
1323 | checksum = "b5eb417147ba9860a96cfe72a0b93bf88fee1744b5636ec99ab20c1aa9376581" | 1322 | checksum = "24d5f089152e60f62d28b835fbff2cd2e8dc0baf1ac13343bef92ab7eed84548" |
1324 | 1323 | ||
1325 | [[package]] | 1324 | [[package]] |
1326 | name = "rowan" | 1325 | name = "rowan" |
@@ -1485,18 +1484,18 @@ dependencies = [ | |||
1485 | 1484 | ||
1486 | [[package]] | 1485 | [[package]] |
1487 | name = "serde" | 1486 | name = "serde" |
1488 | version = "1.0.124" | 1487 | version = "1.0.125" |
1489 | source = "registry+https://github.com/rust-lang/crates.io-index" | 1488 | source = "registry+https://github.com/rust-lang/crates.io-index" |
1490 | checksum = "bd761ff957cb2a45fbb9ab3da6512de9de55872866160b23c25f1a841e99d29f" | 1489 | checksum = "558dc50e1a5a5fa7112ca2ce4effcb321b0300c0d4ccf0776a9f60cd89031171" |
1491 | dependencies = [ | 1490 | dependencies = [ |
1492 | "serde_derive", | 1491 | "serde_derive", |
1493 | ] | 1492 | ] |
1494 | 1493 | ||
1495 | [[package]] | 1494 | [[package]] |
1496 | name = "serde_derive" | 1495 | name = "serde_derive" |
1497 | version = "1.0.124" | 1496 | version = "1.0.125" |
1498 | source = "registry+https://github.com/rust-lang/crates.io-index" | 1497 | source = "registry+https://github.com/rust-lang/crates.io-index" |
1499 | checksum = "1800f7693e94e186f5e25a28291ae1570da908aff7d97a095dec1e56ff99069b" | 1498 | checksum = "b093b7a2bb58203b5da3056c05b4ec1fed827dcfdb37347a8841695263b3d06d" |
1500 | dependencies = [ | 1499 | dependencies = [ |
1501 | "proc-macro2", | 1500 | "proc-macro2", |
1502 | "quote", | 1501 | "quote", |
@@ -1566,17 +1565,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | |||
1566 | checksum = "dc725476a1398f0480d56cd0ad381f6f32acf2642704456f8f59a35df464b59a" | 1565 | checksum = "dc725476a1398f0480d56cd0ad381f6f32acf2642704456f8f59a35df464b59a" |
1567 | 1566 | ||
1568 | [[package]] | 1567 | [[package]] |
1569 | name = "socket2" | ||
1570 | version = "0.3.19" | ||
1571 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
1572 | checksum = "122e570113d28d773067fab24266b66753f6ea915758651696b6e35e49f88d6e" | ||
1573 | dependencies = [ | ||
1574 | "cfg-if", | ||
1575 | "libc", | ||
1576 | "winapi", | ||
1577 | ] | ||
1578 | |||
1579 | [[package]] | ||
1580 | name = "stdx" | 1568 | name = "stdx" |
1581 | version = "0.0.0" | 1569 | version = "0.0.0" |
1582 | dependencies = [ | 1570 | dependencies = [ |
@@ -1586,9 +1574,9 @@ dependencies = [ | |||
1586 | 1574 | ||
1587 | [[package]] | 1575 | [[package]] |
1588 | name = "syn" | 1576 | name = "syn" |
1589 | version = "1.0.63" | 1577 | version = "1.0.64" |
1590 | source = "registry+https://github.com/rust-lang/crates.io-index" | 1578 | source = "registry+https://github.com/rust-lang/crates.io-index" |
1591 | checksum = "8fd9bc7ccc2688b3344c2f48b9b546648b25ce0b20fc717ee7fa7981a8ca9717" | 1579 | checksum = "3fd9d1e9976102a03c542daa2eff1b43f9d72306342f3f8b3ed5fb8908195d6f" |
1592 | dependencies = [ | 1580 | dependencies = [ |
1593 | "proc-macro2", | 1581 | "proc-macro2", |
1594 | "quote", | 1582 | "quote", |
@@ -1718,9 +1706,9 @@ dependencies = [ | |||
1718 | 1706 | ||
1719 | [[package]] | 1707 | [[package]] |
1720 | name = "tracing-attributes" | 1708 | name = "tracing-attributes" |
1721 | version = "0.1.13" | 1709 | version = "0.1.15" |
1722 | source = "registry+https://github.com/rust-lang/crates.io-index" | 1710 | source = "registry+https://github.com/rust-lang/crates.io-index" |
1723 | checksum = "a8a9bd1db7706f2373a190b0d067146caa39350c486f3d455b0e33b431f94c07" | 1711 | checksum = "c42e6fa53307c8a17e4ccd4dc81cf5ec38db9209f59b222210375b54ee40d1e2" |
1724 | dependencies = [ | 1712 | dependencies = [ |
1725 | "proc-macro2", | 1713 | "proc-macro2", |
1726 | "quote", | 1714 | "quote", |
@@ -1759,9 +1747,9 @@ dependencies = [ | |||
1759 | 1747 | ||
1760 | [[package]] | 1748 | [[package]] |
1761 | name = "tracing-subscriber" | 1749 | name = "tracing-subscriber" |
1762 | version = "0.2.16" | 1750 | version = "0.2.17" |
1763 | source = "registry+https://github.com/rust-lang/crates.io-index" | 1751 | source = "registry+https://github.com/rust-lang/crates.io-index" |
1764 | checksum = "8ab8966ac3ca27126141f7999361cc97dd6fb4b71da04c02044fa9045d98bb96" | 1752 | checksum = "705096c6f83bf68ea5d357a6aa01829ddbdac531b357b45abeca842938085baa" |
1765 | dependencies = [ | 1753 | dependencies = [ |
1766 | "ansi_term", | 1754 | "ansi_term", |
1767 | "chrono", | 1755 | "chrono", |
@@ -1867,9 +1855,9 @@ dependencies = [ | |||
1867 | 1855 | ||
1868 | [[package]] | 1856 | [[package]] |
1869 | name = "version_check" | 1857 | name = "version_check" |
1870 | version = "0.9.2" | 1858 | version = "0.9.3" |
1871 | source = "registry+https://github.com/rust-lang/crates.io-index" | 1859 | source = "registry+https://github.com/rust-lang/crates.io-index" |
1872 | checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" | 1860 | checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" |
1873 | 1861 | ||
1874 | [[package]] | 1862 | [[package]] |
1875 | name = "vfs" | 1863 | name = "vfs" |
@@ -1896,9 +1884,9 @@ dependencies = [ | |||
1896 | 1884 | ||
1897 | [[package]] | 1885 | [[package]] |
1898 | name = "walkdir" | 1886 | name = "walkdir" |
1899 | version = "2.3.1" | 1887 | version = "2.3.2" |
1900 | source = "registry+https://github.com/rust-lang/crates.io-index" | 1888 | source = "registry+https://github.com/rust-lang/crates.io-index" |
1901 | checksum = "777182bc735b6424e1a57516d35ed72cb8019d85c8c9bf536dccb3445c1a2f7d" | 1889 | checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" |
1902 | dependencies = [ | 1890 | dependencies = [ |
1903 | "same-file", | 1891 | "same-file", |
1904 | "winapi", | 1892 | "winapi", |
diff --git a/crates/base_db/src/input.rs b/crates/base_db/src/input.rs index e9e8dfc2e..07628935f 100644 --- a/crates/base_db/src/input.rs +++ b/crates/base_db/src/input.rs | |||
@@ -257,7 +257,8 @@ impl CrateGraph { | |||
257 | self.arena.keys().copied() | 257 | self.arena.keys().copied() |
258 | } | 258 | } |
259 | 259 | ||
260 | /// Returns an iterator over all transitive dependencies of the given crate. | 260 | /// Returns an iterator over all transitive dependencies of the given crate, |
261 | /// including the crate itself. | ||
261 | pub fn transitive_deps(&self, of: CrateId) -> impl Iterator<Item = CrateId> + '_ { | 262 | pub fn transitive_deps(&self, of: CrateId) -> impl Iterator<Item = CrateId> + '_ { |
262 | let mut worklist = vec![of]; | 263 | let mut worklist = vec![of]; |
263 | let mut deps = FxHashSet::default(); | 264 | let mut deps = FxHashSet::default(); |
@@ -270,17 +271,16 @@ impl CrateGraph { | |||
270 | worklist.extend(self[krate].dependencies.iter().map(|dep| dep.crate_id)); | 271 | worklist.extend(self[krate].dependencies.iter().map(|dep| dep.crate_id)); |
271 | } | 272 | } |
272 | 273 | ||
273 | deps.remove(&of); | ||
274 | deps.into_iter() | 274 | deps.into_iter() |
275 | } | 275 | } |
276 | 276 | ||
277 | /// Returns an iterator over all transitive reverse dependencies of the given crate. | 277 | /// Returns all transitive reverse dependencies of the given crate, |
278 | pub fn transitive_reverse_dependencies( | 278 | /// including the crate itself. |
279 | &self, | 279 | pub fn transitive_rev_deps(&self, of: CrateId) -> impl Iterator<Item = CrateId> + '_ { |
280 | of: CrateId, | ||
281 | ) -> impl Iterator<Item = CrateId> + '_ { | ||
282 | let mut worklist = vec![of]; | 280 | let mut worklist = vec![of]; |
283 | let mut rev_deps = FxHashSet::default(); | 281 | let mut rev_deps = FxHashSet::default(); |
282 | rev_deps.insert(of); | ||
283 | |||
284 | let mut inverted_graph = FxHashMap::<_, Vec<_>>::default(); | 284 | let mut inverted_graph = FxHashMap::<_, Vec<_>>::default(); |
285 | self.arena.iter().for_each(|(&krate, data)| { | 285 | self.arena.iter().for_each(|(&krate, data)| { |
286 | data.dependencies | 286 | data.dependencies |
diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index 30b96d7e2..eb7865c84 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs | |||
@@ -154,11 +154,7 @@ impl Crate { | |||
154 | } | 154 | } |
155 | 155 | ||
156 | pub fn transitive_reverse_dependencies(self, db: &dyn HirDatabase) -> Vec<Crate> { | 156 | pub fn transitive_reverse_dependencies(self, db: &dyn HirDatabase) -> Vec<Crate> { |
157 | db.crate_graph() | 157 | db.crate_graph().transitive_rev_deps(self.id).into_iter().map(|id| Crate { id }).collect() |
158 | .transitive_reverse_dependencies(self.id) | ||
159 | .into_iter() | ||
160 | .map(|id| Crate { id }) | ||
161 | .collect() | ||
162 | } | 158 | } |
163 | 159 | ||
164 | pub fn root_module(self, db: &dyn HirDatabase) -> Module { | 160 | pub fn root_module(self, db: &dyn HirDatabase) -> Module { |
@@ -856,6 +852,7 @@ impl Function { | |||
856 | }) | 852 | }) |
857 | .collect() | 853 | .collect() |
858 | } | 854 | } |
855 | |||
859 | pub fn method_params(self, db: &dyn HirDatabase) -> Option<Vec<Param>> { | 856 | pub fn method_params(self, db: &dyn HirDatabase) -> Option<Vec<Param>> { |
860 | if self.self_param(db).is_none() { | 857 | if self.self_param(db).is_none() { |
861 | return None; | 858 | return None; |
@@ -913,7 +910,7 @@ impl From<hir_ty::Mutability> for Access { | |||
913 | } | 910 | } |
914 | } | 911 | } |
915 | 912 | ||
916 | #[derive(Debug)] | 913 | #[derive(Clone, Debug)] |
917 | pub struct Param { | 914 | pub struct Param { |
918 | func: Function, | 915 | func: Function, |
919 | /// The index in parameter list, including self parameter. | 916 | /// The index in parameter list, including self parameter. |
@@ -926,13 +923,25 @@ impl Param { | |||
926 | &self.ty | 923 | &self.ty |
927 | } | 924 | } |
928 | 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 | |||
929 | pub fn pattern_source(&self, db: &dyn HirDatabase) -> Option<ast::Pat> { | 932 | pub fn pattern_source(&self, db: &dyn HirDatabase) -> Option<ast::Pat> { |
930 | 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()?; | ||
931 | if params.self_param().is_some() { | 939 | if params.self_param().is_some() { |
932 | params.params().nth(self.idx.checked_sub(1)?)?.pat() | 940 | params.params().nth(self.idx.checked_sub(1)?) |
933 | } else { | 941 | } else { |
934 | params.params().nth(self.idx)?.pat() | 942 | params.params().nth(self.idx) |
935 | } | 943 | } |
944 | .map(|value| InFile { file_id, value }) | ||
936 | } | 945 | } |
937 | } | 946 | } |
938 | 947 | ||
@@ -964,6 +973,14 @@ impl SelfParam { | |||
964 | Access::Owned => "self", | 973 | Access::Owned => "self", |
965 | } | 974 | } |
966 | } | 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 | } | ||
967 | } | 984 | } |
968 | 985 | ||
969 | impl HasVisibility for Function { | 986 | impl HasVisibility for Function { |
@@ -1121,6 +1138,14 @@ impl BuiltinType { | |||
1121 | } | 1138 | } |
1122 | 1139 | ||
1123 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] | 1140 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] |
1141 | pub enum MacroKind { | ||
1142 | Declarative, | ||
1143 | ProcMacro, | ||
1144 | Derive, | ||
1145 | BuiltIn, | ||
1146 | } | ||
1147 | |||
1148 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] | ||
1124 | pub struct MacroDef { | 1149 | pub struct MacroDef { |
1125 | pub(crate) id: MacroDefId, | 1150 | pub(crate) id: MacroDefId, |
1126 | } | 1151 | } |
@@ -1144,15 +1169,15 @@ impl MacroDef { | |||
1144 | } | 1169 | } |
1145 | } | 1170 | } |
1146 | 1171 | ||
1147 | /// Indicate it is a proc-macro | 1172 | pub fn kind(&self) -> MacroKind { |
1148 | pub fn is_proc_macro(&self) -> bool { | 1173 | match self.id.kind { |
1149 | matches!(self.id.kind, MacroDefKind::ProcMacro(..)) | 1174 | MacroDefKind::Declarative(_) => MacroKind::Declarative, |
1150 | } | 1175 | MacroDefKind::BuiltIn(_, _) => MacroKind::BuiltIn, |
1151 | 1176 | MacroDefKind::BuiltInDerive(_, _) => MacroKind::Derive, | |
1152 | /// Indicate it is a derive macro | 1177 | MacroDefKind::BuiltInEager(_, _) => MacroKind::BuiltIn, |
1153 | pub fn is_derive_macro(&self) -> bool { | 1178 | // FIXME might be a derive |
1154 | // FIXME: wrong for `ProcMacro` | 1179 | MacroDefKind::ProcMacro(_, _) => MacroKind::ProcMacro, |
1155 | matches!(self.id.kind, MacroDefKind::ProcMacro(..) | MacroDefKind::BuiltInDerive(..)) | 1180 | } |
1156 | } | 1181 | } |
1157 | } | 1182 | } |
1158 | 1183 | ||
@@ -1331,6 +1356,13 @@ impl Local { | |||
1331 | } | 1356 | } |
1332 | } | 1357 | } |
1333 | 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 | |||
1334 | // FIXME: why is this an option? It shouldn't be? | 1366 | // FIXME: why is this an option? It shouldn't be? |
1335 | pub fn name(self, db: &dyn HirDatabase) -> Option<Name> { | 1367 | pub fn name(self, db: &dyn HirDatabase) -> Option<Name> { |
1336 | let body = db.body(self.parent); | 1368 | let body = db.body(self.parent); |
@@ -1572,8 +1604,7 @@ impl Impl { | |||
1572 | pub fn all_for_trait(db: &dyn HirDatabase, trait_: Trait) -> Vec<Impl> { | 1604 | pub fn all_for_trait(db: &dyn HirDatabase, trait_: Trait) -> Vec<Impl> { |
1573 | let krate = trait_.module(db).krate(); | 1605 | let krate = trait_.module(db).krate(); |
1574 | let mut all = Vec::new(); | 1606 | let mut all = Vec::new(); |
1575 | for Crate { id } in krate.transitive_reverse_dependencies(db).into_iter().chain(Some(krate)) | 1607 | for Crate { id } in krate.transitive_reverse_dependencies(db).into_iter() { |
1576 | { | ||
1577 | let impls = db.trait_impls_in_crate(id); | 1608 | let impls = db.trait_impls_in_crate(id); |
1578 | all.extend(impls.for_trait(trait_.id).map(Self::from)) | 1609 | all.extend(impls.for_trait(trait_.id).map(Self::from)) |
1579 | } | 1610 | } |
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/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.rs b/crates/ide/src/references.rs index 379674530..95ed8a045 100644 --- a/crates/ide/src/references.rs +++ b/crates/ide/src/references.rs | |||
@@ -1294,4 +1294,34 @@ pub use level1::Foo; | |||
1294 | "#]], | 1294 | "#]], |
1295 | ); | 1295 | ); |
1296 | } | 1296 | } |
1297 | |||
1298 | #[test] | ||
1299 | fn test_decl_macro_references() { | ||
1300 | check( | ||
1301 | r#" | ||
1302 | //- /lib.rs crate:lib | ||
1303 | #[macro_use] | ||
1304 | mod qux; | ||
1305 | mod bar; | ||
1306 | |||
1307 | pub use self::foo; | ||
1308 | //- /qux.rs | ||
1309 | #[macro_export] | ||
1310 | macro_rules! foo$0 { | ||
1311 | () => {struct Foo;}; | ||
1312 | } | ||
1313 | //- /bar.rs | ||
1314 | foo!(); | ||
1315 | //- /other.rs crate:other deps:lib new_source_root: | ||
1316 | lib::foo!(); | ||
1317 | "#, | ||
1318 | expect![[r#" | ||
1319 | foo Macro FileId(1) 0..61 29..32 | ||
1320 | |||
1321 | FileId(0) 46..49 | ||
1322 | FileId(2) 0..3 | ||
1323 | FileId(3) 5..8 | ||
1324 | "#]], | ||
1325 | ); | ||
1326 | } | ||
1297 | } | 1327 | } |
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/ide_completion/src/completions.rs b/crates/ide_completion/src/completions.rs index cdac4e41a..6d572a836 100644 --- a/crates/ide_completion/src/completions.rs +++ b/crates/ide_completion/src/completions.rs | |||
@@ -26,7 +26,7 @@ use crate::{ | |||
26 | render::{ | 26 | render::{ |
27 | const_::render_const, | 27 | const_::render_const, |
28 | enum_variant::render_variant, | 28 | enum_variant::render_variant, |
29 | function::render_fn, | 29 | function::{render_fn, render_method}, |
30 | macro_::render_macro, | 30 | macro_::render_macro, |
31 | pattern::{render_struct_pat, render_variant_pat}, | 31 | pattern::{render_struct_pat, render_variant_pat}, |
32 | render_field, render_resolution, render_tuple_field, | 32 | render_field, render_resolution, render_tuple_field, |
@@ -123,6 +123,17 @@ impl Completions { | |||
123 | } | 123 | } |
124 | } | 124 | } |
125 | 125 | ||
126 | pub(crate) fn add_method( | ||
127 | &mut self, | ||
128 | ctx: &CompletionContext, | ||
129 | func: hir::Function, | ||
130 | local_name: Option<String>, | ||
131 | ) { | ||
132 | if let Some(item) = render_method(RenderContext::new(ctx), None, local_name, func) { | ||
133 | self.add(item) | ||
134 | } | ||
135 | } | ||
136 | |||
126 | pub(crate) fn add_variant_pat( | 137 | pub(crate) fn add_variant_pat( |
127 | &mut self, | 138 | &mut self, |
128 | ctx: &CompletionContext, | 139 | ctx: &CompletionContext, |
diff --git a/crates/ide_completion/src/completions/attribute.rs b/crates/ide_completion/src/completions/attribute.rs index e846678b4..b1505c74b 100644 --- a/crates/ide_completion/src/completions/attribute.rs +++ b/crates/ide_completion/src/completions/attribute.rs | |||
@@ -246,7 +246,8 @@ fn get_derive_names_in_scope(ctx: &CompletionContext) -> FxHashSet<String> { | |||
246 | let mut result = FxHashSet::default(); | 246 | let mut result = FxHashSet::default(); |
247 | ctx.scope.process_all_names(&mut |name, scope_def| { | 247 | ctx.scope.process_all_names(&mut |name, scope_def| { |
248 | if let hir::ScopeDef::MacroDef(mac) = scope_def { | 248 | if let hir::ScopeDef::MacroDef(mac) = scope_def { |
249 | if mac.is_derive_macro() { | 249 | // FIXME kind() doesn't check whether proc-macro is a derive |
250 | if mac.kind() == hir::MacroKind::Derive || mac.kind() == hir::MacroKind::ProcMacro { | ||
250 | result.insert(name.to_string()); | 251 | result.insert(name.to_string()); |
251 | } | 252 | } |
252 | } | 253 | } |
diff --git a/crates/ide_completion/src/completions/dot.rs b/crates/ide_completion/src/completions/dot.rs index cec2d0c3a..7e4efe589 100644 --- a/crates/ide_completion/src/completions/dot.rs +++ b/crates/ide_completion/src/completions/dot.rs | |||
@@ -51,7 +51,7 @@ fn complete_methods(acc: &mut Completions, ctx: &CompletionContext, receiver: &T | |||
51 | && ctx.scope.module().map_or(true, |m| func.is_visible_from(ctx.db, m)) | 51 | && ctx.scope.module().map_or(true, |m| func.is_visible_from(ctx.db, m)) |
52 | && seen_methods.insert(func.name(ctx.db)) | 52 | && seen_methods.insert(func.name(ctx.db)) |
53 | { | 53 | { |
54 | acc.add_function(ctx, func, None); | 54 | acc.add_method(ctx, func, None); |
55 | } | 55 | } |
56 | None::<()> | 56 | None::<()> |
57 | }); | 57 | }); |
diff --git a/crates/ide_completion/src/render.rs b/crates/ide_completion/src/render.rs index 23e00aa47..9ce49074f 100644 --- a/crates/ide_completion/src/render.rs +++ b/crates/ide_completion/src/render.rs | |||
@@ -148,8 +148,10 @@ impl<'a> Render<'a> { | |||
148 | ..CompletionRelevance::default() | 148 | ..CompletionRelevance::default() |
149 | }); | 149 | }); |
150 | 150 | ||
151 | if let Some(ref_match) = compute_ref_match(self.ctx.completion, ty) { | 151 | if let Some(_ref_match) = compute_ref_match(self.ctx.completion, ty) { |
152 | item.ref_match(ref_match); | 152 | // FIXME |
153 | // For now we don't properly calculate the edits for ref match | ||
154 | // completions on struct fields, so we've disabled them. See #8058. | ||
153 | } | 155 | } |
154 | 156 | ||
155 | item.build() | 157 | item.build() |
@@ -1313,4 +1315,42 @@ fn main() { | |||
1313 | "#]], | 1315 | "#]], |
1314 | ) | 1316 | ) |
1315 | } | 1317 | } |
1318 | |||
1319 | #[test] | ||
1320 | fn struct_field_method_ref() { | ||
1321 | check( | ||
1322 | r#" | ||
1323 | struct Foo { bar: u32 } | ||
1324 | impl Foo { fn baz(&self) -> u32 { 0 } } | ||
1325 | |||
1326 | fn foo(f: Foo) { let _: &u32 = f.b$0 } | ||
1327 | "#, | ||
1328 | // FIXME | ||
1329 | // Ideally we'd also suggest &f.bar and &f.baz() as exact | ||
1330 | // type matches. See #8058. | ||
1331 | expect![[r#" | ||
1332 | [ | ||
1333 | CompletionItem { | ||
1334 | label: "bar", | ||
1335 | source_range: 98..99, | ||
1336 | delete: 98..99, | ||
1337 | insert: "bar", | ||
1338 | kind: SymbolKind( | ||
1339 | Field, | ||
1340 | ), | ||
1341 | detail: "u32", | ||
1342 | }, | ||
1343 | CompletionItem { | ||
1344 | label: "baz()", | ||
1345 | source_range: 98..99, | ||
1346 | delete: 98..99, | ||
1347 | insert: "baz()$0", | ||
1348 | kind: Method, | ||
1349 | lookup: "baz", | ||
1350 | detail: "fn(&self) -> u32", | ||
1351 | }, | ||
1352 | ] | ||
1353 | "#]], | ||
1354 | ); | ||
1355 | } | ||
1316 | } | 1356 | } |
diff --git a/crates/ide_completion/src/render/function.rs b/crates/ide_completion/src/render/function.rs index 010303182..b1eba20e8 100644 --- a/crates/ide_completion/src/render/function.rs +++ b/crates/ide_completion/src/render/function.rs | |||
@@ -20,7 +20,17 @@ pub(crate) fn render_fn<'a>( | |||
20 | fn_: hir::Function, | 20 | fn_: hir::Function, |
21 | ) -> Option<CompletionItem> { | 21 | ) -> Option<CompletionItem> { |
22 | let _p = profile::span("render_fn"); | 22 | let _p = profile::span("render_fn"); |
23 | Some(FunctionRender::new(ctx, local_name, fn_)?.render(import_to_add)) | 23 | Some(FunctionRender::new(ctx, local_name, fn_, false)?.render(import_to_add)) |
24 | } | ||
25 | |||
26 | pub(crate) fn render_method<'a>( | ||
27 | ctx: RenderContext<'a>, | ||
28 | import_to_add: Option<ImportEdit>, | ||
29 | local_name: Option<String>, | ||
30 | fn_: hir::Function, | ||
31 | ) -> Option<CompletionItem> { | ||
32 | let _p = profile::span("render_method"); | ||
33 | Some(FunctionRender::new(ctx, local_name, fn_, true)?.render(import_to_add)) | ||
24 | } | 34 | } |
25 | 35 | ||
26 | #[derive(Debug)] | 36 | #[derive(Debug)] |
@@ -29,6 +39,7 @@ struct FunctionRender<'a> { | |||
29 | name: String, | 39 | name: String, |
30 | func: hir::Function, | 40 | func: hir::Function, |
31 | ast_node: Fn, | 41 | ast_node: Fn, |
42 | is_method: bool, | ||
32 | } | 43 | } |
33 | 44 | ||
34 | impl<'a> FunctionRender<'a> { | 45 | impl<'a> FunctionRender<'a> { |
@@ -36,11 +47,12 @@ impl<'a> FunctionRender<'a> { | |||
36 | ctx: RenderContext<'a>, | 47 | ctx: RenderContext<'a>, |
37 | local_name: Option<String>, | 48 | local_name: Option<String>, |
38 | fn_: hir::Function, | 49 | fn_: hir::Function, |
50 | is_method: bool, | ||
39 | ) -> Option<FunctionRender<'a>> { | 51 | ) -> Option<FunctionRender<'a>> { |
40 | let name = local_name.unwrap_or_else(|| fn_.name(ctx.db()).to_string()); | 52 | let name = local_name.unwrap_or_else(|| fn_.name(ctx.db()).to_string()); |
41 | let ast_node = fn_.source(ctx.db())?.value; | 53 | let ast_node = fn_.source(ctx.db())?.value; |
42 | 54 | ||
43 | Some(FunctionRender { ctx, name, func: fn_, ast_node }) | 55 | Some(FunctionRender { ctx, name, func: fn_, ast_node, is_method }) |
44 | } | 56 | } |
45 | 57 | ||
46 | fn render(self, import_to_add: Option<ImportEdit>) -> CompletionItem { | 58 | fn render(self, import_to_add: Option<ImportEdit>) -> CompletionItem { |
@@ -67,7 +79,12 @@ impl<'a> FunctionRender<'a> { | |||
67 | }); | 79 | }); |
68 | 80 | ||
69 | if let Some(ref_match) = compute_ref_match(self.ctx.completion, &ret_type) { | 81 | if let Some(ref_match) = compute_ref_match(self.ctx.completion, &ret_type) { |
70 | item.ref_match(ref_match); | 82 | // FIXME |
83 | // For now we don't properly calculate the edits for ref match | ||
84 | // completions on methods, so we've disabled them. See #8058. | ||
85 | if !self.is_method { | ||
86 | item.ref_match(ref_match); | ||
87 | } | ||
71 | } | 88 | } |
72 | 89 | ||
73 | item.build() | 90 | item.build() |
diff --git a/crates/ide_db/src/search.rs b/crates/ide_db/src/search.rs index 324817cd0..3634b2b26 100644 --- a/crates/ide_db/src/search.rs +++ b/crates/ide_db/src/search.rs | |||
@@ -7,7 +7,7 @@ | |||
7 | use std::{convert::TryInto, mem}; | 7 | use std::{convert::TryInto, mem}; |
8 | 8 | ||
9 | use base_db::{FileId, FileRange, SourceDatabase, SourceDatabaseExt}; | 9 | use base_db::{FileId, FileRange, SourceDatabase, SourceDatabaseExt}; |
10 | use hir::{DefWithBody, HasSource, Module, ModuleSource, Semantics, Visibility}; | 10 | use hir::{DefWithBody, HasAttrs, HasSource, InFile, ModuleSource, Semantics, Visibility}; |
11 | use once_cell::unsync::Lazy; | 11 | use once_cell::unsync::Lazy; |
12 | use rustc_hash::FxHashMap; | 12 | use rustc_hash::FxHashMap; |
13 | use syntax::{ast, match_ast, AstNode, TextRange, TextSize}; | 13 | use syntax::{ast, match_ast, AstNode, TextRange, TextSize}; |
@@ -78,6 +78,76 @@ impl SearchScope { | |||
78 | SearchScope { entries } | 78 | SearchScope { entries } |
79 | } | 79 | } |
80 | 80 | ||
81 | fn crate_graph(db: &RootDatabase) -> SearchScope { | ||
82 | let mut entries = FxHashMap::default(); | ||
83 | |||
84 | let graph = db.crate_graph(); | ||
85 | for krate in graph.iter() { | ||
86 | let root_file = graph[krate].root_file_id; | ||
87 | let source_root_id = db.file_source_root(root_file); | ||
88 | let source_root = db.source_root(source_root_id); | ||
89 | entries.extend(source_root.iter().map(|id| (id, None))); | ||
90 | } | ||
91 | SearchScope { entries } | ||
92 | } | ||
93 | |||
94 | fn reverse_dependencies(db: &RootDatabase, of: hir::Crate) -> SearchScope { | ||
95 | let mut entries = FxHashMap::default(); | ||
96 | for rev_dep in of.transitive_reverse_dependencies(db) { | ||
97 | let root_file = rev_dep.root_file(db); | ||
98 | let source_root_id = db.file_source_root(root_file); | ||
99 | let source_root = db.source_root(source_root_id); | ||
100 | entries.extend(source_root.iter().map(|id| (id, None))); | ||
101 | } | ||
102 | SearchScope { entries } | ||
103 | } | ||
104 | |||
105 | fn krate(db: &RootDatabase, of: hir::Crate) -> SearchScope { | ||
106 | let root_file = of.root_file(db); | ||
107 | let source_root_id = db.file_source_root(root_file); | ||
108 | let source_root = db.source_root(source_root_id); | ||
109 | SearchScope { | ||
110 | entries: source_root.iter().map(|id| (id, None)).collect::<FxHashMap<_, _>>(), | ||
111 | } | ||
112 | } | ||
113 | |||
114 | fn module(db: &RootDatabase, module: hir::Module) -> SearchScope { | ||
115 | let mut entries = FxHashMap::default(); | ||
116 | |||
117 | let mut to_visit = vec![module]; | ||
118 | let mut is_first = true; | ||
119 | while let Some(module) = to_visit.pop() { | ||
120 | let src = module.definition_source(db); | ||
121 | let file_id = src.file_id.original_file(db); | ||
122 | match src.value { | ||
123 | ModuleSource::Module(m) => { | ||
124 | if is_first { | ||
125 | let range = Some(m.syntax().text_range()); | ||
126 | entries.insert(file_id, range); | ||
127 | } else { | ||
128 | // We have already added the enclosing file to the search scope, | ||
129 | // so do nothing. | ||
130 | } | ||
131 | } | ||
132 | ModuleSource::BlockExpr(b) => { | ||
133 | if is_first { | ||
134 | let range = Some(b.syntax().text_range()); | ||
135 | entries.insert(file_id, range); | ||
136 | } else { | ||
137 | // We have already added the enclosing file to the search scope, | ||
138 | // so do nothing. | ||
139 | } | ||
140 | } | ||
141 | ModuleSource::SourceFile(_) => { | ||
142 | entries.insert(file_id, None); | ||
143 | } | ||
144 | }; | ||
145 | is_first = false; | ||
146 | to_visit.extend(module.children(db)); | ||
147 | } | ||
148 | SearchScope { entries } | ||
149 | } | ||
150 | |||
81 | pub fn empty() -> SearchScope { | 151 | pub fn empty() -> SearchScope { |
82 | SearchScope::new(FxHashMap::default()) | 152 | SearchScope::new(FxHashMap::default()) |
83 | } | 153 | } |
@@ -140,24 +210,15 @@ impl Definition { | |||
140 | let _p = profile::span("search_scope"); | 210 | let _p = profile::span("search_scope"); |
141 | 211 | ||
142 | if let Definition::ModuleDef(hir::ModuleDef::BuiltinType(_)) = self { | 212 | if let Definition::ModuleDef(hir::ModuleDef::BuiltinType(_)) = self { |
143 | let mut res = FxHashMap::default(); | 213 | return SearchScope::crate_graph(db); |
144 | |||
145 | let graph = db.crate_graph(); | ||
146 | for krate in graph.iter() { | ||
147 | let root_file = graph[krate].root_file_id; | ||
148 | let source_root_id = db.file_source_root(root_file); | ||
149 | let source_root = db.source_root(source_root_id); | ||
150 | res.extend(source_root.iter().map(|id| (id, None))); | ||
151 | } | ||
152 | return SearchScope::new(res); | ||
153 | } | 214 | } |
154 | 215 | ||
155 | let module = match self.module(db) { | 216 | let module = match self.module(db) { |
156 | Some(it) => it, | 217 | Some(it) => it, |
157 | None => return SearchScope::empty(), | 218 | None => return SearchScope::empty(), |
158 | }; | 219 | }; |
159 | let module_src = module.definition_source(db); | 220 | let InFile { file_id, value: module_source } = module.definition_source(db); |
160 | let file_id = module_src.file_id.original_file(db); | 221 | let file_id = file_id.original_file(db); |
161 | 222 | ||
162 | if let Definition::Local(var) = self { | 223 | if let Definition::Local(var) = self { |
163 | let range = match var.parent(db) { | 224 | let range = match var.parent(db) { |
@@ -165,9 +226,10 @@ impl Definition { | |||
165 | DefWithBody::Const(c) => c.source(db).map(|src| src.value.syntax().text_range()), | 226 | DefWithBody::Const(c) => c.source(db).map(|src| src.value.syntax().text_range()), |
166 | DefWithBody::Static(s) => s.source(db).map(|src| src.value.syntax().text_range()), | 227 | DefWithBody::Static(s) => s.source(db).map(|src| src.value.syntax().text_range()), |
167 | }; | 228 | }; |
168 | let mut res = FxHashMap::default(); | 229 | return match range { |
169 | res.insert(file_id, range); | 230 | Some(range) => SearchScope::file_range(FileRange { file_id, range }), |
170 | return SearchScope::new(res); | 231 | None => SearchScope::single_file(file_id), |
232 | }; | ||
171 | } | 233 | } |
172 | 234 | ||
173 | if let Definition::GenericParam(hir::GenericParam::LifetimeParam(param)) = self { | 235 | if let Definition::GenericParam(hir::GenericParam::LifetimeParam(param)) = self { |
@@ -198,75 +260,39 @@ impl Definition { | |||
198 | it.source(db).map(|src| src.value.syntax().text_range()) | 260 | it.source(db).map(|src| src.value.syntax().text_range()) |
199 | } | 261 | } |
200 | }; | 262 | }; |
201 | let mut res = FxHashMap::default(); | 263 | return match range { |
202 | res.insert(file_id, range); | 264 | Some(range) => SearchScope::file_range(FileRange { file_id, range }), |
203 | return SearchScope::new(res); | 265 | None => SearchScope::single_file(file_id), |
266 | }; | ||
204 | } | 267 | } |
205 | 268 | ||
206 | let vis = self.visibility(db); | 269 | if let Definition::Macro(macro_def) = self { |
207 | 270 | if macro_def.kind() == hir::MacroKind::Declarative { | |
208 | if let Some(Visibility::Module(module)) = vis.and_then(|it| it.into()) { | 271 | return if macro_def.attrs(db).by_key("macro_export").exists() { |
209 | let module: Module = module.into(); | 272 | SearchScope::reverse_dependencies(db, module.krate()) |
210 | let mut res = FxHashMap::default(); | 273 | } else { |
211 | 274 | SearchScope::krate(db, module.krate()) | |
212 | let mut to_visit = vec![module]; | ||
213 | let mut is_first = true; | ||
214 | while let Some(module) = to_visit.pop() { | ||
215 | let src = module.definition_source(db); | ||
216 | let file_id = src.file_id.original_file(db); | ||
217 | match src.value { | ||
218 | ModuleSource::Module(m) => { | ||
219 | if is_first { | ||
220 | let range = Some(m.syntax().text_range()); | ||
221 | res.insert(file_id, range); | ||
222 | } else { | ||
223 | // We have already added the enclosing file to the search scope, | ||
224 | // so do nothing. | ||
225 | } | ||
226 | } | ||
227 | ModuleSource::BlockExpr(b) => { | ||
228 | if is_first { | ||
229 | let range = Some(b.syntax().text_range()); | ||
230 | res.insert(file_id, range); | ||
231 | } else { | ||
232 | // We have already added the enclosing file to the search scope, | ||
233 | // so do nothing. | ||
234 | } | ||
235 | } | ||
236 | ModuleSource::SourceFile(_) => { | ||
237 | res.insert(file_id, None); | ||
238 | } | ||
239 | }; | 275 | }; |
240 | is_first = false; | ||
241 | to_visit.extend(module.children(db)); | ||
242 | } | 276 | } |
243 | |||
244 | return SearchScope::new(res); | ||
245 | } | 277 | } |
246 | 278 | ||
279 | let vis = self.visibility(db); | ||
247 | if let Some(Visibility::Public) = vis { | 280 | if let Some(Visibility::Public) = vis { |
248 | let source_root_id = db.file_source_root(file_id); | 281 | return SearchScope::reverse_dependencies(db, module.krate()); |
249 | let source_root = db.source_root(source_root_id); | 282 | } |
250 | let mut res = source_root.iter().map(|id| (id, None)).collect::<FxHashMap<_, _>>(); | 283 | if let Some(Visibility::Module(module)) = vis { |
251 | 284 | return SearchScope::module(db, module.into()); | |
252 | let krate = module.krate(); | ||
253 | for rev_dep in krate.transitive_reverse_dependencies(db) { | ||
254 | let root_file = rev_dep.root_file(db); | ||
255 | let source_root_id = db.file_source_root(root_file); | ||
256 | let source_root = db.source_root(source_root_id); | ||
257 | res.extend(source_root.iter().map(|id| (id, None))); | ||
258 | } | ||
259 | return SearchScope::new(res); | ||
260 | } | 285 | } |
261 | 286 | ||
262 | let mut res = FxHashMap::default(); | 287 | let range = match module_source { |
263 | let range = match module_src.value { | ||
264 | ModuleSource::Module(m) => Some(m.syntax().text_range()), | 288 | ModuleSource::Module(m) => Some(m.syntax().text_range()), |
265 | ModuleSource::BlockExpr(b) => Some(b.syntax().text_range()), | 289 | ModuleSource::BlockExpr(b) => Some(b.syntax().text_range()), |
266 | ModuleSource::SourceFile(_) => None, | 290 | ModuleSource::SourceFile(_) => None, |
267 | }; | 291 | }; |
268 | res.insert(file_id, range); | 292 | match range { |
269 | SearchScope::new(res) | 293 | Some(range) => SearchScope::file_range(FileRange { file_id, range }), |
294 | None => SearchScope::single_file(file_id), | ||
295 | } | ||
270 | } | 296 | } |
271 | 297 | ||
272 | pub fn usages<'a>(&'a self, sema: &'a Semantics<RootDatabase>) -> FindUsages<'a> { | 298 | pub fn usages<'a>(&'a self, sema: &'a Semantics<RootDatabase>) -> FindUsages<'a> { |
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 | } |