diff options
91 files changed, 3737 insertions, 2021 deletions
diff --git a/.gitignore b/.gitignore index f835edef0..dab51647d 100644 --- a/.gitignore +++ b/.gitignore | |||
@@ -7,4 +7,4 @@ crates/*/target | |||
7 | *.log | 7 | *.log |
8 | *.iml | 8 | *.iml |
9 | .vscode/settings.json | 9 | .vscode/settings.json |
10 | cargo-timing*.html | 10 | *.html |
diff --git a/Cargo.lock b/Cargo.lock index 1159d0f1d..af27bfc85 100644 --- a/Cargo.lock +++ b/Cargo.lock | |||
@@ -112,17 +112,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | |||
112 | checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" | 112 | checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" |
113 | 113 | ||
114 | [[package]] | 114 | [[package]] |
115 | name = "chalk-base" | ||
116 | version = "0.10.1-dev" | ||
117 | source = "git+https://github.com/rust-lang/chalk.git?rev=5a3b871ca17529ab5aa5787594fabad1634936cb#5a3b871ca17529ab5aa5787594fabad1634936cb" | ||
118 | dependencies = [ | ||
119 | "lazy_static", | ||
120 | ] | ||
121 | |||
122 | [[package]] | ||
123 | name = "chalk-derive" | 115 | name = "chalk-derive" |
124 | version = "0.10.1-dev" | 116 | version = "0.10.1-dev" |
125 | source = "git+https://github.com/rust-lang/chalk.git?rev=5a3b871ca17529ab5aa5787594fabad1634936cb#5a3b871ca17529ab5aa5787594fabad1634936cb" | 117 | source = "git+https://github.com/rust-lang/chalk.git?rev=329b7f3fdd2431ed6f6778cde53f22374c7d094c#329b7f3fdd2431ed6f6778cde53f22374c7d094c" |
126 | dependencies = [ | 118 | dependencies = [ |
127 | "proc-macro2", | 119 | "proc-macro2", |
128 | "quote", | 120 | "quote", |
@@ -133,9 +125,8 @@ dependencies = [ | |||
133 | [[package]] | 125 | [[package]] |
134 | name = "chalk-engine" | 126 | name = "chalk-engine" |
135 | version = "0.10.1-dev" | 127 | version = "0.10.1-dev" |
136 | source = "git+https://github.com/rust-lang/chalk.git?rev=5a3b871ca17529ab5aa5787594fabad1634936cb#5a3b871ca17529ab5aa5787594fabad1634936cb" | 128 | source = "git+https://github.com/rust-lang/chalk.git?rev=329b7f3fdd2431ed6f6778cde53f22374c7d094c#329b7f3fdd2431ed6f6778cde53f22374c7d094c" |
137 | dependencies = [ | 129 | dependencies = [ |
138 | "chalk-base", | ||
139 | "chalk-derive", | 130 | "chalk-derive", |
140 | "chalk-ir", | 131 | "chalk-ir", |
141 | "rustc-hash", | 132 | "rustc-hash", |
@@ -144,18 +135,17 @@ dependencies = [ | |||
144 | [[package]] | 135 | [[package]] |
145 | name = "chalk-ir" | 136 | name = "chalk-ir" |
146 | version = "0.10.1-dev" | 137 | version = "0.10.1-dev" |
147 | source = "git+https://github.com/rust-lang/chalk.git?rev=5a3b871ca17529ab5aa5787594fabad1634936cb#5a3b871ca17529ab5aa5787594fabad1634936cb" | 138 | source = "git+https://github.com/rust-lang/chalk.git?rev=329b7f3fdd2431ed6f6778cde53f22374c7d094c#329b7f3fdd2431ed6f6778cde53f22374c7d094c" |
148 | dependencies = [ | 139 | dependencies = [ |
149 | "chalk-base", | ||
150 | "chalk-derive", | 140 | "chalk-derive", |
141 | "lazy_static", | ||
151 | ] | 142 | ] |
152 | 143 | ||
153 | [[package]] | 144 | [[package]] |
154 | name = "chalk-solve" | 145 | name = "chalk-solve" |
155 | version = "0.10.1-dev" | 146 | version = "0.10.1-dev" |
156 | source = "git+https://github.com/rust-lang/chalk.git?rev=5a3b871ca17529ab5aa5787594fabad1634936cb#5a3b871ca17529ab5aa5787594fabad1634936cb" | 147 | source = "git+https://github.com/rust-lang/chalk.git?rev=329b7f3fdd2431ed6f6778cde53f22374c7d094c#329b7f3fdd2431ed6f6778cde53f22374c7d094c" |
157 | dependencies = [ | 148 | dependencies = [ |
158 | "chalk-base", | ||
159 | "chalk-derive", | 149 | "chalk-derive", |
160 | "chalk-engine", | 150 | "chalk-engine", |
161 | "chalk-ir", | 151 | "chalk-ir", |
@@ -253,9 +243,9 @@ dependencies = [ | |||
253 | 243 | ||
254 | [[package]] | 244 | [[package]] |
255 | name = "crossbeam-queue" | 245 | name = "crossbeam-queue" |
256 | version = "0.2.1" | 246 | version = "0.2.2" |
257 | source = "registry+https://github.com/rust-lang/crates.io-index" | 247 | source = "registry+https://github.com/rust-lang/crates.io-index" |
258 | checksum = "c695eeca1e7173472a32221542ae469b3e9aac3a4fc81f7696bcad82029493db" | 248 | checksum = "ab6bffe714b6bb07e42f201352c34f51fefd355ace793f9e638ebd52d23f98d2" |
259 | dependencies = [ | 249 | dependencies = [ |
260 | "cfg-if", | 250 | "cfg-if", |
261 | "crossbeam-utils", | 251 | "crossbeam-utils", |
@@ -650,9 +640,9 @@ dependencies = [ | |||
650 | 640 | ||
651 | [[package]] | 641 | [[package]] |
652 | name = "lsp-types" | 642 | name = "lsp-types" |
653 | version = "0.74.1" | 643 | version = "0.74.2" |
654 | source = "registry+https://github.com/rust-lang/crates.io-index" | 644 | source = "registry+https://github.com/rust-lang/crates.io-index" |
655 | checksum = "57c0e6a2b8837d27b29deb3f3e6dc1c6d2f57947677f9be1024e482ec5b59525" | 645 | checksum = "b360754e89e0e13c114245131382ba921d4ff1efabb918e549422938aaa8d392" |
656 | dependencies = [ | 646 | dependencies = [ |
657 | "base64", | 647 | "base64", |
658 | "bitflags", | 648 | "bitflags", |
@@ -819,9 +809,9 @@ dependencies = [ | |||
819 | 809 | ||
820 | [[package]] | 810 | [[package]] |
821 | name = "paste" | 811 | name = "paste" |
822 | version = "0.1.14" | 812 | version = "0.1.15" |
823 | source = "registry+https://github.com/rust-lang/crates.io-index" | 813 | source = "registry+https://github.com/rust-lang/crates.io-index" |
824 | checksum = "3431e8f72b90f8a7af91dec890d9814000cb371258e0ec7370d93e085361f531" | 814 | checksum = "d53181dcd37421c08d3b69f887784956674d09c3f9a47a04fece2b130a5b346b" |
825 | dependencies = [ | 815 | dependencies = [ |
826 | "paste-impl", | 816 | "paste-impl", |
827 | "proc-macro-hack", | 817 | "proc-macro-hack", |
@@ -829,9 +819,9 @@ dependencies = [ | |||
829 | 819 | ||
830 | [[package]] | 820 | [[package]] |
831 | name = "paste-impl" | 821 | name = "paste-impl" |
832 | version = "0.1.14" | 822 | version = "0.1.15" |
833 | source = "registry+https://github.com/rust-lang/crates.io-index" | 823 | source = "registry+https://github.com/rust-lang/crates.io-index" |
834 | checksum = "25af5fc872ba284d8d84608bf8a0fa9b5376c96c23f503b007dfd9e34dde5606" | 824 | checksum = "05ca490fa1c034a71412b4d1edcb904ec5a0981a4426c9eb2128c0fda7a68d17" |
835 | dependencies = [ | 825 | dependencies = [ |
836 | "proc-macro-hack", | 826 | "proc-macro-hack", |
837 | "proc-macro2", | 827 | "proc-macro2", |
@@ -1129,6 +1119,7 @@ dependencies = [ | |||
1129 | "memmap", | 1119 | "memmap", |
1130 | "ra_mbe", | 1120 | "ra_mbe", |
1131 | "ra_proc_macro", | 1121 | "ra_proc_macro", |
1122 | "ra_toolchain", | ||
1132 | "ra_tt", | 1123 | "ra_tt", |
1133 | "serde_derive", | 1124 | "serde_derive", |
1134 | "test_utils", | 1125 | "test_utils", |
@@ -1301,9 +1292,9 @@ checksum = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" | |||
1301 | 1292 | ||
1302 | [[package]] | 1293 | [[package]] |
1303 | name = "regex" | 1294 | name = "regex" |
1304 | version = "1.3.7" | 1295 | version = "1.3.9" |
1305 | source = "registry+https://github.com/rust-lang/crates.io-index" | 1296 | source = "registry+https://github.com/rust-lang/crates.io-index" |
1306 | checksum = "a6020f034922e3194c711b82a627453881bc4682166cabb07134a10c26ba7692" | 1297 | checksum = "9c3780fcf44b193bc4d09f36d2a3c87b251da4a046c87795a0d35f4f927ad8e6" |
1307 | dependencies = [ | 1298 | dependencies = [ |
1308 | "aho-corasick", | 1299 | "aho-corasick", |
1309 | "memchr", | 1300 | "memchr", |
@@ -1313,9 +1304,9 @@ dependencies = [ | |||
1313 | 1304 | ||
1314 | [[package]] | 1305 | [[package]] |
1315 | name = "regex-syntax" | 1306 | name = "regex-syntax" |
1316 | version = "0.6.17" | 1307 | version = "0.6.18" |
1317 | source = "registry+https://github.com/rust-lang/crates.io-index" | 1308 | source = "registry+https://github.com/rust-lang/crates.io-index" |
1318 | checksum = "7fe5bd57d1d7414c6b5ed48563a2c855d995ff777729dcd91c369ec7fea395ae" | 1309 | checksum = "26412eb97c6b088a6997e05f69403a802a92d520de2f8e63c2b65f9e0f47c4e8" |
1319 | 1310 | ||
1320 | [[package]] | 1311 | [[package]] |
1321 | name = "relative-path" | 1312 | name = "relative-path" |
@@ -1501,18 +1492,18 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" | |||
1501 | 1492 | ||
1502 | [[package]] | 1493 | [[package]] |
1503 | name = "serde" | 1494 | name = "serde" |
1504 | version = "1.0.110" | 1495 | version = "1.0.111" |
1505 | source = "registry+https://github.com/rust-lang/crates.io-index" | 1496 | source = "registry+https://github.com/rust-lang/crates.io-index" |
1506 | checksum = "99e7b308464d16b56eba9964e4972a3eee817760ab60d88c3f86e1fecb08204c" | 1497 | checksum = "c9124df5b40cbd380080b2cc6ab894c040a3070d995f5c9dc77e18c34a8ae37d" |
1507 | dependencies = [ | 1498 | dependencies = [ |
1508 | "serde_derive", | 1499 | "serde_derive", |
1509 | ] | 1500 | ] |
1510 | 1501 | ||
1511 | [[package]] | 1502 | [[package]] |
1512 | name = "serde_derive" | 1503 | name = "serde_derive" |
1513 | version = "1.0.110" | 1504 | version = "1.0.111" |
1514 | source = "registry+https://github.com/rust-lang/crates.io-index" | 1505 | source = "registry+https://github.com/rust-lang/crates.io-index" |
1515 | checksum = "818fbf6bfa9a42d3bfcaca148547aa00c7b915bec71d1757aa2d44ca68771984" | 1506 | checksum = "3f2c3ac8e6ca1e9c80b8be1023940162bf81ae3cffbb1809474152f2ce1eb250" |
1516 | dependencies = [ | 1507 | dependencies = [ |
1517 | "proc-macro2", | 1508 | "proc-macro2", |
1518 | "quote", | 1509 | "quote", |
@@ -1586,9 +1577,9 @@ checksum = "ab16ced94dbd8a46c82fd81e3ed9a8727dac2977ea869d217bcc4ea1f122e81f" | |||
1586 | 1577 | ||
1587 | [[package]] | 1578 | [[package]] |
1588 | name = "syn" | 1579 | name = "syn" |
1589 | version = "1.0.25" | 1580 | version = "1.0.29" |
1590 | source = "registry+https://github.com/rust-lang/crates.io-index" | 1581 | source = "registry+https://github.com/rust-lang/crates.io-index" |
1591 | checksum = "f14a640819f79b72a710c0be059dce779f9339ae046c8bef12c361d56702146f" | 1582 | checksum = "bb37da98a55b1d08529362d9cbb863be17556873df2585904ab9d2bc951291d0" |
1592 | dependencies = [ | 1583 | dependencies = [ |
1593 | "proc-macro2", | 1584 | "proc-macro2", |
1594 | "quote", | 1585 | "quote", |
diff --git a/crates/ra_assists/src/handlers/add_from_impl_for_enum.rs b/crates/ra_assists/src/handlers/add_from_impl_for_enum.rs index 6a675e812..776bddf91 100644 --- a/crates/ra_assists/src/handlers/add_from_impl_for_enum.rs +++ b/crates/ra_assists/src/handlers/add_from_impl_for_enum.rs | |||
@@ -4,9 +4,9 @@ use test_utils::mark; | |||
4 | 4 | ||
5 | use crate::{utils::FamousDefs, AssistContext, AssistId, Assists}; | 5 | use crate::{utils::FamousDefs, AssistContext, AssistId, Assists}; |
6 | 6 | ||
7 | // Assist add_from_impl_for_enum | 7 | // Assist: add_from_impl_for_enum |
8 | // | 8 | // |
9 | // Adds a From impl for an enum variant with one tuple field | 9 | // Adds a From impl for an enum variant with one tuple field. |
10 | // | 10 | // |
11 | // ``` | 11 | // ``` |
12 | // enum A { <|>One(u32) } | 12 | // enum A { <|>One(u32) } |
diff --git a/crates/ra_assists/src/handlers/introduce_named_lifetime.rs b/crates/ra_assists/src/handlers/introduce_named_lifetime.rs new file mode 100644 index 000000000..beb5b7366 --- /dev/null +++ b/crates/ra_assists/src/handlers/introduce_named_lifetime.rs | |||
@@ -0,0 +1,303 @@ | |||
1 | use ra_syntax::{ | ||
2 | ast::{self, NameOwner, TypeAscriptionOwner, TypeParamsOwner}, | ||
3 | AstNode, SyntaxKind, TextRange, TextSize, | ||
4 | }; | ||
5 | use rustc_hash::FxHashSet; | ||
6 | |||
7 | use crate::{assist_context::AssistBuilder, AssistContext, AssistId, Assists}; | ||
8 | |||
9 | static ASSIST_NAME: &str = "introduce_named_lifetime"; | ||
10 | static ASSIST_LABEL: &str = "Introduce named lifetime"; | ||
11 | |||
12 | // Assist: introduce_named_lifetime | ||
13 | // | ||
14 | // Change an anonymous lifetime to a named lifetime. | ||
15 | // | ||
16 | // ``` | ||
17 | // impl Cursor<'_<|>> { | ||
18 | // fn node(self) -> &SyntaxNode { | ||
19 | // match self { | ||
20 | // Cursor::Replace(node) | Cursor::Before(node) => node, | ||
21 | // } | ||
22 | // } | ||
23 | // } | ||
24 | // ``` | ||
25 | // -> | ||
26 | // ``` | ||
27 | // impl<'a> Cursor<'a> { | ||
28 | // fn node(self) -> &SyntaxNode { | ||
29 | // match self { | ||
30 | // Cursor::Replace(node) | Cursor::Before(node) => node, | ||
31 | // } | ||
32 | // } | ||
33 | // } | ||
34 | // ``` | ||
35 | // FIXME: How can we handle renaming any one of multiple anonymous lifetimes? | ||
36 | // FIXME: should also add support for the case fun(f: &Foo) -> &<|>Foo | ||
37 | pub(crate) fn introduce_named_lifetime(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | ||
38 | let lifetime_token = ctx | ||
39 | .find_token_at_offset(SyntaxKind::LIFETIME) | ||
40 | .filter(|lifetime| lifetime.text() == "'_")?; | ||
41 | if let Some(fn_def) = lifetime_token.ancestors().find_map(ast::FnDef::cast) { | ||
42 | generate_fn_def_assist(acc, &fn_def, lifetime_token.text_range()) | ||
43 | } else if let Some(impl_def) = lifetime_token.ancestors().find_map(ast::ImplDef::cast) { | ||
44 | // only allow naming the last anonymous lifetime | ||
45 | lifetime_token.next_token().filter(|tok| tok.kind() == SyntaxKind::R_ANGLE)?; | ||
46 | generate_impl_def_assist(acc, &impl_def, lifetime_token.text_range()) | ||
47 | } else { | ||
48 | None | ||
49 | } | ||
50 | } | ||
51 | |||
52 | /// Generate the assist for the fn def case | ||
53 | fn generate_fn_def_assist( | ||
54 | acc: &mut Assists, | ||
55 | fn_def: &ast::FnDef, | ||
56 | lifetime_loc: TextRange, | ||
57 | ) -> Option<()> { | ||
58 | let param_list: ast::ParamList = fn_def.param_list()?; | ||
59 | let new_lifetime_param = generate_unique_lifetime_param_name(&fn_def.type_param_list())?; | ||
60 | let end_of_fn_ident = fn_def.name()?.ident_token()?.text_range().end(); | ||
61 | let self_param = | ||
62 | // use the self if it's a reference and has no explicit lifetime | ||
63 | param_list.self_param().filter(|p| p.lifetime_token().is_none() && p.amp_token().is_some()); | ||
64 | // compute the location which implicitly has the same lifetime as the anonymous lifetime | ||
65 | let loc_needing_lifetime = if let Some(self_param) = self_param { | ||
66 | // if we have a self reference, use that | ||
67 | Some(self_param.self_token()?.text_range().start()) | ||
68 | } else { | ||
69 | // otherwise, if there's a single reference parameter without a named liftime, use that | ||
70 | let fn_params_without_lifetime: Vec<_> = param_list | ||
71 | .params() | ||
72 | .filter_map(|param| match param.ascribed_type() { | ||
73 | Some(ast::TypeRef::ReferenceType(ascribed_type)) | ||
74 | if ascribed_type.lifetime_token() == None => | ||
75 | { | ||
76 | Some(ascribed_type.amp_token()?.text_range().end()) | ||
77 | } | ||
78 | _ => None, | ||
79 | }) | ||
80 | .collect(); | ||
81 | match fn_params_without_lifetime.len() { | ||
82 | 1 => Some(fn_params_without_lifetime.into_iter().nth(0)?), | ||
83 | 0 => None, | ||
84 | // multiple unnnamed is invalid. assist is not applicable | ||
85 | _ => return None, | ||
86 | } | ||
87 | }; | ||
88 | acc.add(AssistId(ASSIST_NAME), ASSIST_LABEL, lifetime_loc, |builder| { | ||
89 | add_lifetime_param(fn_def, builder, end_of_fn_ident, new_lifetime_param); | ||
90 | builder.replace(lifetime_loc, format!("'{}", new_lifetime_param)); | ||
91 | loc_needing_lifetime.map(|loc| builder.insert(loc, format!("'{} ", new_lifetime_param))); | ||
92 | }) | ||
93 | } | ||
94 | |||
95 | /// Generate the assist for the impl def case | ||
96 | fn generate_impl_def_assist( | ||
97 | acc: &mut Assists, | ||
98 | impl_def: &ast::ImplDef, | ||
99 | lifetime_loc: TextRange, | ||
100 | ) -> Option<()> { | ||
101 | let new_lifetime_param = generate_unique_lifetime_param_name(&impl_def.type_param_list())?; | ||
102 | let end_of_impl_kw = impl_def.impl_token()?.text_range().end(); | ||
103 | acc.add(AssistId(ASSIST_NAME), ASSIST_LABEL, lifetime_loc, |builder| { | ||
104 | add_lifetime_param(impl_def, builder, end_of_impl_kw, new_lifetime_param); | ||
105 | builder.replace(lifetime_loc, format!("'{}", new_lifetime_param)); | ||
106 | }) | ||
107 | } | ||
108 | |||
109 | /// Given a type parameter list, generate a unique lifetime parameter name | ||
110 | /// which is not in the list | ||
111 | fn generate_unique_lifetime_param_name( | ||
112 | existing_type_param_list: &Option<ast::TypeParamList>, | ||
113 | ) -> Option<char> { | ||
114 | match existing_type_param_list { | ||
115 | Some(type_params) => { | ||
116 | let used_lifetime_params: FxHashSet<_> = type_params | ||
117 | .lifetime_params() | ||
118 | .map(|p| p.syntax().text().to_string()[1..].to_owned()) | ||
119 | .collect(); | ||
120 | (b'a'..=b'z').map(char::from).find(|c| !used_lifetime_params.contains(&c.to_string())) | ||
121 | } | ||
122 | None => Some('a'), | ||
123 | } | ||
124 | } | ||
125 | |||
126 | /// Add the lifetime param to `builder`. If there are type parameters in `type_params_owner`, add it to the end. Otherwise | ||
127 | /// add new type params brackets with the lifetime parameter at `new_type_params_loc`. | ||
128 | fn add_lifetime_param<TypeParamsOwner: ast::TypeParamsOwner>( | ||
129 | type_params_owner: &TypeParamsOwner, | ||
130 | builder: &mut AssistBuilder, | ||
131 | new_type_params_loc: TextSize, | ||
132 | new_lifetime_param: char, | ||
133 | ) { | ||
134 | match type_params_owner.type_param_list() { | ||
135 | // add the new lifetime parameter to an existing type param list | ||
136 | Some(type_params) => { | ||
137 | builder.insert( | ||
138 | (u32::from(type_params.syntax().text_range().end()) - 1).into(), | ||
139 | format!(", '{}", new_lifetime_param), | ||
140 | ); | ||
141 | } | ||
142 | // create a new type param list containing only the new lifetime parameter | ||
143 | None => { | ||
144 | builder.insert(new_type_params_loc, format!("<'{}>", new_lifetime_param)); | ||
145 | } | ||
146 | } | ||
147 | } | ||
148 | |||
149 | #[cfg(test)] | ||
150 | mod tests { | ||
151 | use super::*; | ||
152 | use crate::tests::{check_assist, check_assist_not_applicable}; | ||
153 | |||
154 | #[test] | ||
155 | fn test_example_case() { | ||
156 | check_assist( | ||
157 | introduce_named_lifetime, | ||
158 | r#"impl Cursor<'_<|>> { | ||
159 | fn node(self) -> &SyntaxNode { | ||
160 | match self { | ||
161 | Cursor::Replace(node) | Cursor::Before(node) => node, | ||
162 | } | ||
163 | } | ||
164 | }"#, | ||
165 | r#"impl<'a> Cursor<'a> { | ||
166 | fn node(self) -> &SyntaxNode { | ||
167 | match self { | ||
168 | Cursor::Replace(node) | Cursor::Before(node) => node, | ||
169 | } | ||
170 | } | ||
171 | }"#, | ||
172 | ); | ||
173 | } | ||
174 | |||
175 | #[test] | ||
176 | fn test_example_case_simplified() { | ||
177 | check_assist( | ||
178 | introduce_named_lifetime, | ||
179 | r#"impl Cursor<'_<|>> {"#, | ||
180 | r#"impl<'a> Cursor<'a> {"#, | ||
181 | ); | ||
182 | } | ||
183 | |||
184 | #[test] | ||
185 | fn test_example_case_cursor_after_tick() { | ||
186 | check_assist( | ||
187 | introduce_named_lifetime, | ||
188 | r#"impl Cursor<'<|>_> {"#, | ||
189 | r#"impl<'a> Cursor<'a> {"#, | ||
190 | ); | ||
191 | } | ||
192 | |||
193 | #[test] | ||
194 | fn test_example_case_cursor_before_tick() { | ||
195 | check_assist( | ||
196 | introduce_named_lifetime, | ||
197 | r#"impl Cursor<<|>'_> {"#, | ||
198 | r#"impl<'a> Cursor<'a> {"#, | ||
199 | ); | ||
200 | } | ||
201 | |||
202 | #[test] | ||
203 | fn test_not_applicable_cursor_position() { | ||
204 | check_assist_not_applicable(introduce_named_lifetime, r#"impl Cursor<'_><|> {"#); | ||
205 | check_assist_not_applicable(introduce_named_lifetime, r#"impl Cursor<|><'_> {"#); | ||
206 | } | ||
207 | |||
208 | #[test] | ||
209 | fn test_not_applicable_lifetime_already_name() { | ||
210 | check_assist_not_applicable(introduce_named_lifetime, r#"impl Cursor<'a<|>> {"#); | ||
211 | check_assist_not_applicable(introduce_named_lifetime, r#"fn my_fun<'a>() -> X<'a<|>>"#); | ||
212 | } | ||
213 | |||
214 | #[test] | ||
215 | fn test_with_type_parameter() { | ||
216 | check_assist( | ||
217 | introduce_named_lifetime, | ||
218 | r#"impl<T> Cursor<T, '_<|>>"#, | ||
219 | r#"impl<T, 'a> Cursor<T, 'a>"#, | ||
220 | ); | ||
221 | } | ||
222 | |||
223 | #[test] | ||
224 | fn test_with_existing_lifetime_name_conflict() { | ||
225 | check_assist( | ||
226 | introduce_named_lifetime, | ||
227 | r#"impl<'a, 'b> Cursor<'a, 'b, '_<|>>"#, | ||
228 | r#"impl<'a, 'b, 'c> Cursor<'a, 'b, 'c>"#, | ||
229 | ); | ||
230 | } | ||
231 | |||
232 | #[test] | ||
233 | fn test_function_return_value_anon_lifetime_param() { | ||
234 | check_assist( | ||
235 | introduce_named_lifetime, | ||
236 | r#"fn my_fun() -> X<'_<|>>"#, | ||
237 | r#"fn my_fun<'a>() -> X<'a>"#, | ||
238 | ); | ||
239 | } | ||
240 | |||
241 | #[test] | ||
242 | fn test_function_return_value_anon_reference_lifetime() { | ||
243 | check_assist( | ||
244 | introduce_named_lifetime, | ||
245 | r#"fn my_fun() -> &'_<|> X"#, | ||
246 | r#"fn my_fun<'a>() -> &'a X"#, | ||
247 | ); | ||
248 | } | ||
249 | |||
250 | #[test] | ||
251 | fn test_function_param_anon_lifetime() { | ||
252 | check_assist( | ||
253 | introduce_named_lifetime, | ||
254 | r#"fn my_fun(x: X<'_<|>>)"#, | ||
255 | r#"fn my_fun<'a>(x: X<'a>)"#, | ||
256 | ); | ||
257 | } | ||
258 | |||
259 | #[test] | ||
260 | fn test_function_add_lifetime_to_params() { | ||
261 | check_assist( | ||
262 | introduce_named_lifetime, | ||
263 | r#"fn my_fun(f: &Foo) -> X<'_<|>>"#, | ||
264 | r#"fn my_fun<'a>(f: &'a Foo) -> X<'a>"#, | ||
265 | ); | ||
266 | } | ||
267 | |||
268 | #[test] | ||
269 | fn test_function_add_lifetime_to_params_in_presence_of_other_lifetime() { | ||
270 | check_assist( | ||
271 | introduce_named_lifetime, | ||
272 | r#"fn my_fun<'other>(f: &Foo, b: &'other Bar) -> X<'_<|>>"#, | ||
273 | r#"fn my_fun<'other, 'a>(f: &'a Foo, b: &'other Bar) -> X<'a>"#, | ||
274 | ); | ||
275 | } | ||
276 | |||
277 | #[test] | ||
278 | fn test_function_not_applicable_without_self_and_multiple_unnamed_param_lifetimes() { | ||
279 | // this is not permitted under lifetime elision rules | ||
280 | check_assist_not_applicable( | ||
281 | introduce_named_lifetime, | ||
282 | r#"fn my_fun(f: &Foo, b: &Bar) -> X<'_<|>>"#, | ||
283 | ); | ||
284 | } | ||
285 | |||
286 | #[test] | ||
287 | fn test_function_add_lifetime_to_self_ref_param() { | ||
288 | check_assist( | ||
289 | introduce_named_lifetime, | ||
290 | r#"fn my_fun<'other>(&self, f: &Foo, b: &'other Bar) -> X<'_<|>>"#, | ||
291 | r#"fn my_fun<'other, 'a>(&'a self, f: &Foo, b: &'other Bar) -> X<'a>"#, | ||
292 | ); | ||
293 | } | ||
294 | |||
295 | #[test] | ||
296 | fn test_function_add_lifetime_to_param_with_non_ref_self() { | ||
297 | check_assist( | ||
298 | introduce_named_lifetime, | ||
299 | r#"fn my_fun<'other>(self, f: &Foo, b: &'other Bar) -> X<'_<|>>"#, | ||
300 | r#"fn my_fun<'other, 'a>(self, f: &'a Foo, b: &'other Bar) -> X<'a>"#, | ||
301 | ); | ||
302 | } | ||
303 | } | ||
diff --git a/crates/ra_assists/src/lib.rs b/crates/ra_assists/src/lib.rs index 464bc03dd..fb5d59a87 100644 --- a/crates/ra_assists/src/lib.rs +++ b/crates/ra_assists/src/lib.rs | |||
@@ -121,6 +121,7 @@ mod handlers { | |||
121 | mod flip_comma; | 121 | mod flip_comma; |
122 | mod flip_trait_bound; | 122 | mod flip_trait_bound; |
123 | mod inline_local_variable; | 123 | mod inline_local_variable; |
124 | mod introduce_named_lifetime; | ||
124 | mod introduce_variable; | 125 | mod introduce_variable; |
125 | mod invert_if; | 126 | mod invert_if; |
126 | mod merge_imports; | 127 | mod merge_imports; |
@@ -160,6 +161,7 @@ mod handlers { | |||
160 | flip_comma::flip_comma, | 161 | flip_comma::flip_comma, |
161 | flip_trait_bound::flip_trait_bound, | 162 | flip_trait_bound::flip_trait_bound, |
162 | inline_local_variable::inline_local_variable, | 163 | inline_local_variable::inline_local_variable, |
164 | introduce_named_lifetime::introduce_named_lifetime, | ||
163 | introduce_variable::introduce_variable, | 165 | introduce_variable::introduce_variable, |
164 | invert_if::invert_if, | 166 | invert_if::invert_if, |
165 | merge_imports::merge_imports, | 167 | merge_imports::merge_imports, |
diff --git a/crates/ra_assists/src/tests/generated.rs b/crates/ra_assists/src/tests/generated.rs index 250e56a69..d17504529 100644 --- a/crates/ra_assists/src/tests/generated.rs +++ b/crates/ra_assists/src/tests/generated.rs | |||
@@ -59,6 +59,25 @@ fn main() { | |||
59 | } | 59 | } |
60 | 60 | ||
61 | #[test] | 61 | #[test] |
62 | fn doctest_add_from_impl_for_enum() { | ||
63 | check_doc_test( | ||
64 | "add_from_impl_for_enum", | ||
65 | r#####" | ||
66 | enum A { <|>One(u32) } | ||
67 | "#####, | ||
68 | r#####" | ||
69 | enum A { One(u32) } | ||
70 | |||
71 | impl From<u32> for A { | ||
72 | fn from(v: u32) -> Self { | ||
73 | A::One(v) | ||
74 | } | ||
75 | } | ||
76 | "#####, | ||
77 | ) | ||
78 | } | ||
79 | |||
80 | #[test] | ||
62 | fn doctest_add_function() { | 81 | fn doctest_add_function() { |
63 | check_doc_test( | 82 | check_doc_test( |
64 | "add_function", | 83 | "add_function", |
@@ -433,6 +452,31 @@ fn main() { | |||
433 | } | 452 | } |
434 | 453 | ||
435 | #[test] | 454 | #[test] |
455 | fn doctest_introduce_named_lifetime() { | ||
456 | check_doc_test( | ||
457 | "introduce_named_lifetime", | ||
458 | r#####" | ||
459 | impl Cursor<'_<|>> { | ||
460 | fn node(self) -> &SyntaxNode { | ||
461 | match self { | ||
462 | Cursor::Replace(node) | Cursor::Before(node) => node, | ||
463 | } | ||
464 | } | ||
465 | } | ||
466 | "#####, | ||
467 | r#####" | ||
468 | impl<'a> Cursor<'a> { | ||
469 | fn node(self) -> &SyntaxNode { | ||
470 | match self { | ||
471 | Cursor::Replace(node) | Cursor::Before(node) => node, | ||
472 | } | ||
473 | } | ||
474 | } | ||
475 | "#####, | ||
476 | ) | ||
477 | } | ||
478 | |||
479 | #[test] | ||
436 | fn doctest_introduce_variable() { | 480 | fn doctest_introduce_variable() { |
437 | check_doc_test( | 481 | check_doc_test( |
438 | "introduce_variable", | 482 | "introduce_variable", |
diff --git a/crates/ra_hir/src/code_model.rs b/crates/ra_hir/src/code_model.rs index e40aeffbc..4a06f3bcd 100644 --- a/crates/ra_hir/src/code_model.rs +++ b/crates/ra_hir/src/code_model.rs | |||
@@ -637,6 +637,10 @@ impl Function { | |||
637 | db.function_data(self.id).params.clone() | 637 | db.function_data(self.id).params.clone() |
638 | } | 638 | } |
639 | 639 | ||
640 | pub fn is_unsafe(self, db: &dyn HirDatabase) -> bool { | ||
641 | db.function_data(self.id).is_unsafe | ||
642 | } | ||
643 | |||
640 | pub fn diagnostics(self, db: &dyn HirDatabase, sink: &mut DiagnosticSink) { | 644 | pub fn diagnostics(self, db: &dyn HirDatabase, sink: &mut DiagnosticSink) { |
641 | let _p = profile("Function::diagnostics"); | 645 | let _p = profile("Function::diagnostics"); |
642 | let infer = db.infer(self.id.into()); | 646 | let infer = db.infer(self.id.into()); |
@@ -1190,6 +1194,10 @@ impl Type { | |||
1190 | ) | 1194 | ) |
1191 | } | 1195 | } |
1192 | 1196 | ||
1197 | pub fn is_raw_ptr(&self) -> bool { | ||
1198 | matches!(&self.ty.value, Ty::Apply(ApplicationTy { ctor: TypeCtor::RawPtr(..), .. })) | ||
1199 | } | ||
1200 | |||
1193 | pub fn contains_unknown(&self) -> bool { | 1201 | pub fn contains_unknown(&self) -> bool { |
1194 | return go(&self.ty.value); | 1202 | return go(&self.ty.value); |
1195 | 1203 | ||
diff --git a/crates/ra_hir_def/src/body/lower.rs b/crates/ra_hir_def/src/body/lower.rs index 905c0cf5d..f159f80af 100644 --- a/crates/ra_hir_def/src/body/lower.rs +++ b/crates/ra_hir_def/src/body/lower.rs | |||
@@ -134,7 +134,7 @@ impl ExprCollector<'_> { | |||
134 | self.make_expr(expr, Err(SyntheticSyntax)) | 134 | self.make_expr(expr, Err(SyntheticSyntax)) |
135 | } | 135 | } |
136 | fn empty_block(&mut self) -> ExprId { | 136 | fn empty_block(&mut self) -> ExprId { |
137 | self.alloc_expr_desugared(Expr::Block { statements: Vec::new(), tail: None }) | 137 | self.alloc_expr_desugared(Expr::Block { statements: Vec::new(), tail: None, label: None }) |
138 | } | 138 | } |
139 | fn missing_expr(&mut self) -> ExprId { | 139 | fn missing_expr(&mut self) -> ExprId { |
140 | self.alloc_expr_desugared(Expr::Missing) | 140 | self.alloc_expr_desugared(Expr::Missing) |
@@ -215,7 +215,16 @@ impl ExprCollector<'_> { | |||
215 | ast::Expr::BlockExpr(e) => self.collect_block(e), | 215 | ast::Expr::BlockExpr(e) => self.collect_block(e), |
216 | ast::Expr::LoopExpr(e) => { | 216 | ast::Expr::LoopExpr(e) => { |
217 | let body = self.collect_block_opt(e.loop_body()); | 217 | let body = self.collect_block_opt(e.loop_body()); |
218 | self.alloc_expr(Expr::Loop { body }, syntax_ptr) | 218 | self.alloc_expr( |
219 | Expr::Loop { | ||
220 | body, | ||
221 | label: e | ||
222 | .label() | ||
223 | .and_then(|l| l.lifetime_token()) | ||
224 | .map(|l| Name::new_lifetime(&l)), | ||
225 | }, | ||
226 | syntax_ptr, | ||
227 | ) | ||
219 | } | 228 | } |
220 | ast::Expr::WhileExpr(e) => { | 229 | ast::Expr::WhileExpr(e) => { |
221 | let body = self.collect_block_opt(e.loop_body()); | 230 | let body = self.collect_block_opt(e.loop_body()); |
@@ -230,25 +239,56 @@ impl ExprCollector<'_> { | |||
230 | let pat = self.collect_pat(pat); | 239 | let pat = self.collect_pat(pat); |
231 | let match_expr = self.collect_expr_opt(condition.expr()); | 240 | let match_expr = self.collect_expr_opt(condition.expr()); |
232 | let placeholder_pat = self.missing_pat(); | 241 | let placeholder_pat = self.missing_pat(); |
233 | let break_ = self.alloc_expr_desugared(Expr::Break { expr: None }); | 242 | let break_ = |
243 | self.alloc_expr_desugared(Expr::Break { expr: None, label: None }); | ||
234 | let arms = vec![ | 244 | let arms = vec![ |
235 | MatchArm { pat, expr: body, guard: None }, | 245 | MatchArm { pat, expr: body, guard: None }, |
236 | MatchArm { pat: placeholder_pat, expr: break_, guard: None }, | 246 | MatchArm { pat: placeholder_pat, expr: break_, guard: None }, |
237 | ]; | 247 | ]; |
238 | let match_expr = | 248 | let match_expr = |
239 | self.alloc_expr_desugared(Expr::Match { expr: match_expr, arms }); | 249 | self.alloc_expr_desugared(Expr::Match { expr: match_expr, arms }); |
240 | return self.alloc_expr(Expr::Loop { body: match_expr }, syntax_ptr); | 250 | return self.alloc_expr( |
251 | Expr::Loop { | ||
252 | body: match_expr, | ||
253 | label: e | ||
254 | .label() | ||
255 | .and_then(|l| l.lifetime_token()) | ||
256 | .map(|l| Name::new_lifetime(&l)), | ||
257 | }, | ||
258 | syntax_ptr, | ||
259 | ); | ||
241 | } | 260 | } |
242 | }, | 261 | }, |
243 | }; | 262 | }; |
244 | 263 | ||
245 | self.alloc_expr(Expr::While { condition, body }, syntax_ptr) | 264 | self.alloc_expr( |
265 | Expr::While { | ||
266 | condition, | ||
267 | body, | ||
268 | label: e | ||
269 | .label() | ||
270 | .and_then(|l| l.lifetime_token()) | ||
271 | .map(|l| Name::new_lifetime(&l)), | ||
272 | }, | ||
273 | syntax_ptr, | ||
274 | ) | ||
246 | } | 275 | } |
247 | ast::Expr::ForExpr(e) => { | 276 | ast::Expr::ForExpr(e) => { |
248 | let iterable = self.collect_expr_opt(e.iterable()); | 277 | let iterable = self.collect_expr_opt(e.iterable()); |
249 | let pat = self.collect_pat_opt(e.pat()); | 278 | let pat = self.collect_pat_opt(e.pat()); |
250 | let body = self.collect_block_opt(e.loop_body()); | 279 | let body = self.collect_block_opt(e.loop_body()); |
251 | self.alloc_expr(Expr::For { iterable, pat, body }, syntax_ptr) | 280 | self.alloc_expr( |
281 | Expr::For { | ||
282 | iterable, | ||
283 | pat, | ||
284 | body, | ||
285 | label: e | ||
286 | .label() | ||
287 | .and_then(|l| l.lifetime_token()) | ||
288 | .map(|l| Name::new_lifetime(&l)), | ||
289 | }, | ||
290 | syntax_ptr, | ||
291 | ) | ||
252 | } | 292 | } |
253 | ast::Expr::CallExpr(e) => { | 293 | ast::Expr::CallExpr(e) => { |
254 | let callee = self.collect_expr_opt(e.expr()); | 294 | let callee = self.collect_expr_opt(e.expr()); |
@@ -301,13 +341,16 @@ impl ExprCollector<'_> { | |||
301 | .unwrap_or(Expr::Missing); | 341 | .unwrap_or(Expr::Missing); |
302 | self.alloc_expr(path, syntax_ptr) | 342 | self.alloc_expr(path, syntax_ptr) |
303 | } | 343 | } |
304 | ast::Expr::ContinueExpr(_e) => { | 344 | ast::Expr::ContinueExpr(e) => self.alloc_expr( |
305 | // FIXME: labels | 345 | Expr::Continue { label: e.lifetime_token().map(|l| Name::new_lifetime(&l)) }, |
306 | self.alloc_expr(Expr::Continue, syntax_ptr) | 346 | syntax_ptr, |
307 | } | 347 | ), |
308 | ast::Expr::BreakExpr(e) => { | 348 | ast::Expr::BreakExpr(e) => { |
309 | let expr = e.expr().map(|e| self.collect_expr(e)); | 349 | let expr = e.expr().map(|e| self.collect_expr(e)); |
310 | self.alloc_expr(Expr::Break { expr }, syntax_ptr) | 350 | self.alloc_expr( |
351 | Expr::Break { expr, label: e.lifetime_token().map(|l| Name::new_lifetime(&l)) }, | ||
352 | syntax_ptr, | ||
353 | ) | ||
311 | } | 354 | } |
312 | ast::Expr::ParenExpr(e) => { | 355 | ast::Expr::ParenExpr(e) => { |
313 | let inner = self.collect_expr_opt(e.expr()); | 356 | let inner = self.collect_expr_opt(e.expr()); |
@@ -529,7 +572,8 @@ impl ExprCollector<'_> { | |||
529 | }) | 572 | }) |
530 | .collect(); | 573 | .collect(); |
531 | let tail = block.expr().map(|e| self.collect_expr(e)); | 574 | let tail = block.expr().map(|e| self.collect_expr(e)); |
532 | self.alloc_expr(Expr::Block { statements, tail }, syntax_node_ptr) | 575 | let label = block.label().and_then(|l| l.lifetime_token()).map(|t| Name::new_lifetime(&t)); |
576 | self.alloc_expr(Expr::Block { statements, tail, label }, syntax_node_ptr) | ||
533 | } | 577 | } |
534 | 578 | ||
535 | fn collect_block_items(&mut self, block: &ast::BlockExpr) { | 579 | fn collect_block_items(&mut self, block: &ast::BlockExpr) { |
diff --git a/crates/ra_hir_def/src/body/scope.rs b/crates/ra_hir_def/src/body/scope.rs index 09e92b74e..e48ff38f9 100644 --- a/crates/ra_hir_def/src/body/scope.rs +++ b/crates/ra_hir_def/src/body/scope.rs | |||
@@ -138,10 +138,10 @@ fn compute_block_scopes( | |||
138 | fn compute_expr_scopes(expr: ExprId, body: &Body, scopes: &mut ExprScopes, scope: ScopeId) { | 138 | fn compute_expr_scopes(expr: ExprId, body: &Body, scopes: &mut ExprScopes, scope: ScopeId) { |
139 | scopes.set_scope(expr, scope); | 139 | scopes.set_scope(expr, scope); |
140 | match &body[expr] { | 140 | match &body[expr] { |
141 | Expr::Block { statements, tail } => { | 141 | Expr::Block { statements, tail, .. } => { |
142 | compute_block_scopes(&statements, *tail, body, scopes, scope); | 142 | compute_block_scopes(&statements, *tail, body, scopes, scope); |
143 | } | 143 | } |
144 | Expr::For { iterable, pat, body: body_expr } => { | 144 | Expr::For { iterable, pat, body: body_expr, .. } => { |
145 | compute_expr_scopes(*iterable, body, scopes, scope); | 145 | compute_expr_scopes(*iterable, body, scopes, scope); |
146 | let scope = scopes.new_scope(scope); | 146 | let scope = scopes.new_scope(scope); |
147 | scopes.add_bindings(body, scope, *pat); | 147 | scopes.add_bindings(body, scope, *pat); |
diff --git a/crates/ra_hir_def/src/data.rs b/crates/ra_hir_def/src/data.rs index e2130d931..807195d25 100644 --- a/crates/ra_hir_def/src/data.rs +++ b/crates/ra_hir_def/src/data.rs | |||
@@ -34,6 +34,7 @@ pub struct FunctionData { | |||
34 | /// True if the first param is `self`. This is relevant to decide whether this | 34 | /// True if the first param is `self`. This is relevant to decide whether this |
35 | /// can be called as a method. | 35 | /// can be called as a method. |
36 | pub has_self_param: bool, | 36 | pub has_self_param: bool, |
37 | pub is_unsafe: bool, | ||
37 | pub visibility: RawVisibility, | 38 | pub visibility: RawVisibility, |
38 | } | 39 | } |
39 | 40 | ||
@@ -85,11 +86,14 @@ impl FunctionData { | |||
85 | ret_type | 86 | ret_type |
86 | }; | 87 | }; |
87 | 88 | ||
89 | let is_unsafe = src.value.unsafe_token().is_some(); | ||
90 | |||
88 | let vis_default = RawVisibility::default_for_container(loc.container); | 91 | let vis_default = RawVisibility::default_for_container(loc.container); |
89 | let visibility = | 92 | let visibility = |
90 | RawVisibility::from_ast_with_default(db, vis_default, src.map(|s| s.visibility())); | 93 | RawVisibility::from_ast_with_default(db, vis_default, src.map(|s| s.visibility())); |
91 | 94 | ||
92 | let sig = FunctionData { name, params, ret_type, has_self_param, visibility, attrs }; | 95 | let sig = |
96 | FunctionData { name, params, ret_type, has_self_param, is_unsafe, visibility, attrs }; | ||
93 | Arc::new(sig) | 97 | Arc::new(sig) |
94 | } | 98 | } |
95 | } | 99 | } |
diff --git a/crates/ra_hir_def/src/expr.rs b/crates/ra_hir_def/src/expr.rs index f25c6f958..ca49b26d1 100644 --- a/crates/ra_hir_def/src/expr.rs +++ b/crates/ra_hir_def/src/expr.rs | |||
@@ -52,18 +52,22 @@ pub enum Expr { | |||
52 | Block { | 52 | Block { |
53 | statements: Vec<Statement>, | 53 | statements: Vec<Statement>, |
54 | tail: Option<ExprId>, | 54 | tail: Option<ExprId>, |
55 | label: Option<Name>, | ||
55 | }, | 56 | }, |
56 | Loop { | 57 | Loop { |
57 | body: ExprId, | 58 | body: ExprId, |
59 | label: Option<Name>, | ||
58 | }, | 60 | }, |
59 | While { | 61 | While { |
60 | condition: ExprId, | 62 | condition: ExprId, |
61 | body: ExprId, | 63 | body: ExprId, |
64 | label: Option<Name>, | ||
62 | }, | 65 | }, |
63 | For { | 66 | For { |
64 | iterable: ExprId, | 67 | iterable: ExprId, |
65 | pat: PatId, | 68 | pat: PatId, |
66 | body: ExprId, | 69 | body: ExprId, |
70 | label: Option<Name>, | ||
67 | }, | 71 | }, |
68 | Call { | 72 | Call { |
69 | callee: ExprId, | 73 | callee: ExprId, |
@@ -79,9 +83,12 @@ pub enum Expr { | |||
79 | expr: ExprId, | 83 | expr: ExprId, |
80 | arms: Vec<MatchArm>, | 84 | arms: Vec<MatchArm>, |
81 | }, | 85 | }, |
82 | Continue, | 86 | Continue { |
87 | label: Option<Name>, | ||
88 | }, | ||
83 | Break { | 89 | Break { |
84 | expr: Option<ExprId>, | 90 | expr: Option<ExprId>, |
91 | label: Option<Name>, | ||
85 | }, | 92 | }, |
86 | Return { | 93 | Return { |
87 | expr: Option<ExprId>, | 94 | expr: Option<ExprId>, |
@@ -225,7 +232,7 @@ impl Expr { | |||
225 | f(*else_branch); | 232 | f(*else_branch); |
226 | } | 233 | } |
227 | } | 234 | } |
228 | Expr::Block { statements, tail } => { | 235 | Expr::Block { statements, tail, .. } => { |
229 | for stmt in statements { | 236 | for stmt in statements { |
230 | match stmt { | 237 | match stmt { |
231 | Statement::Let { initializer, .. } => { | 238 | Statement::Let { initializer, .. } => { |
@@ -241,8 +248,8 @@ impl Expr { | |||
241 | } | 248 | } |
242 | } | 249 | } |
243 | Expr::TryBlock { body } => f(*body), | 250 | Expr::TryBlock { body } => f(*body), |
244 | Expr::Loop { body } => f(*body), | 251 | Expr::Loop { body, .. } => f(*body), |
245 | Expr::While { condition, body } => { | 252 | Expr::While { condition, body, .. } => { |
246 | f(*condition); | 253 | f(*condition); |
247 | f(*body); | 254 | f(*body); |
248 | } | 255 | } |
@@ -268,8 +275,8 @@ impl Expr { | |||
268 | f(arm.expr); | 275 | f(arm.expr); |
269 | } | 276 | } |
270 | } | 277 | } |
271 | Expr::Continue => {} | 278 | Expr::Continue { .. } => {} |
272 | Expr::Break { expr } | Expr::Return { expr } => { | 279 | Expr::Break { expr, .. } | Expr::Return { expr } => { |
273 | if let Some(expr) = expr { | 280 | if let Some(expr) = expr { |
274 | f(*expr); | 281 | f(*expr); |
275 | } | 282 | } |
diff --git a/crates/ra_hir_expand/src/name.rs b/crates/ra_hir_expand/src/name.rs index fecce224e..ea495cb11 100644 --- a/crates/ra_hir_expand/src/name.rs +++ b/crates/ra_hir_expand/src/name.rs | |||
@@ -37,6 +37,11 @@ impl Name { | |||
37 | Name(Repr::TupleField(idx)) | 37 | Name(Repr::TupleField(idx)) |
38 | } | 38 | } |
39 | 39 | ||
40 | pub fn new_lifetime(lt: &ra_syntax::SyntaxToken) -> Name { | ||
41 | assert!(lt.kind() == ra_syntax::SyntaxKind::LIFETIME); | ||
42 | Name(Repr::Text(lt.text().clone())) | ||
43 | } | ||
44 | |||
40 | /// Shortcut to create inline plain text name | 45 | /// Shortcut to create inline plain text name |
41 | const fn new_inline_ascii(text: &[u8]) -> Name { | 46 | const fn new_inline_ascii(text: &[u8]) -> Name { |
42 | Name::new_text(SmolStr::new_inline_from_ascii(text.len(), text)) | 47 | Name::new_text(SmolStr::new_inline_from_ascii(text.len(), text)) |
diff --git a/crates/ra_hir_ty/Cargo.toml b/crates/ra_hir_ty/Cargo.toml index 6afed58a1..4b8dcdc07 100644 --- a/crates/ra_hir_ty/Cargo.toml +++ b/crates/ra_hir_ty/Cargo.toml | |||
@@ -27,8 +27,8 @@ test_utils = { path = "../test_utils" } | |||
27 | 27 | ||
28 | scoped-tls = "1" | 28 | scoped-tls = "1" |
29 | 29 | ||
30 | chalk-solve = { git = "https://github.com/rust-lang/chalk.git", rev = "5a3b871ca17529ab5aa5787594fabad1634936cb" } | 30 | chalk-solve = { git = "https://github.com/rust-lang/chalk.git", rev = "329b7f3fdd2431ed6f6778cde53f22374c7d094c" } |
31 | chalk-ir = { git = "https://github.com/rust-lang/chalk.git", rev = "5a3b871ca17529ab5aa5787594fabad1634936cb" } | 31 | chalk-ir = { git = "https://github.com/rust-lang/chalk.git", rev = "329b7f3fdd2431ed6f6778cde53f22374c7d094c" } |
32 | 32 | ||
33 | [dev-dependencies] | 33 | [dev-dependencies] |
34 | insta = "0.16.0" | 34 | insta = "0.16.0" |
diff --git a/crates/ra_hir_ty/src/infer.rs b/crates/ra_hir_ty/src/infer.rs index 957d6e0b5..dc77e88e5 100644 --- a/crates/ra_hir_ty/src/infer.rs +++ b/crates/ra_hir_ty/src/infer.rs | |||
@@ -219,6 +219,17 @@ struct InferenceContext<'a> { | |||
219 | struct BreakableContext { | 219 | struct BreakableContext { |
220 | pub may_break: bool, | 220 | pub may_break: bool, |
221 | pub break_ty: Ty, | 221 | pub break_ty: Ty, |
222 | pub label: Option<name::Name>, | ||
223 | } | ||
224 | |||
225 | fn find_breakable<'c>( | ||
226 | ctxs: &'c mut [BreakableContext], | ||
227 | label: Option<&name::Name>, | ||
228 | ) -> Option<&'c mut BreakableContext> { | ||
229 | match label { | ||
230 | Some(_) => ctxs.iter_mut().rev().find(|ctx| ctx.label.as_ref() == label), | ||
231 | None => ctxs.last_mut(), | ||
232 | } | ||
222 | } | 233 | } |
223 | 234 | ||
224 | impl<'a> InferenceContext<'a> { | 235 | impl<'a> InferenceContext<'a> { |
diff --git a/crates/ra_hir_ty/src/infer/coerce.rs b/crates/ra_hir_ty/src/infer/coerce.rs index 2ee9adb16..32c7c57cd 100644 --- a/crates/ra_hir_ty/src/infer/coerce.rs +++ b/crates/ra_hir_ty/src/infer/coerce.rs | |||
@@ -45,9 +45,7 @@ impl<'a> InferenceContext<'a> { | |||
45 | self.coerce_merge_branch(&ptr_ty1, &ptr_ty2) | 45 | self.coerce_merge_branch(&ptr_ty1, &ptr_ty2) |
46 | } else { | 46 | } else { |
47 | mark::hit!(coerce_merge_fail_fallback); | 47 | mark::hit!(coerce_merge_fail_fallback); |
48 | // For incompatible types, we use the latter one as result | 48 | ty1.clone() |
49 | // to be better recovery for `if` without `else`. | ||
50 | ty2.clone() | ||
51 | } | 49 | } |
52 | } | 50 | } |
53 | } | 51 | } |
diff --git a/crates/ra_hir_ty/src/infer/expr.rs b/crates/ra_hir_ty/src/infer/expr.rs index 54bab3476..4a98e2deb 100644 --- a/crates/ra_hir_ty/src/infer/expr.rs +++ b/crates/ra_hir_ty/src/infer/expr.rs | |||
@@ -22,8 +22,8 @@ use crate::{ | |||
22 | }; | 22 | }; |
23 | 23 | ||
24 | use super::{ | 24 | use super::{ |
25 | BindingMode, BreakableContext, Diverges, Expectation, InferenceContext, InferenceDiagnostic, | 25 | find_breakable, BindingMode, BreakableContext, Diverges, Expectation, InferenceContext, |
26 | TypeMismatch, | 26 | InferenceDiagnostic, TypeMismatch, |
27 | }; | 27 | }; |
28 | 28 | ||
29 | impl<'a> InferenceContext<'a> { | 29 | impl<'a> InferenceContext<'a> { |
@@ -86,16 +86,20 @@ impl<'a> InferenceContext<'a> { | |||
86 | 86 | ||
87 | self.coerce_merge_branch(&then_ty, &else_ty) | 87 | self.coerce_merge_branch(&then_ty, &else_ty) |
88 | } | 88 | } |
89 | Expr::Block { statements, tail } => self.infer_block(statements, *tail, expected), | 89 | Expr::Block { statements, tail, .. } => { |
90 | // FIXME: Breakable block inference | ||
91 | self.infer_block(statements, *tail, expected) | ||
92 | } | ||
90 | Expr::TryBlock { body } => { | 93 | Expr::TryBlock { body } => { |
91 | let _inner = self.infer_expr(*body, expected); | 94 | let _inner = self.infer_expr(*body, expected); |
92 | // FIXME should be std::result::Result<{inner}, _> | 95 | // FIXME should be std::result::Result<{inner}, _> |
93 | Ty::Unknown | 96 | Ty::Unknown |
94 | } | 97 | } |
95 | Expr::Loop { body } => { | 98 | Expr::Loop { body, label } => { |
96 | self.breakables.push(BreakableContext { | 99 | self.breakables.push(BreakableContext { |
97 | may_break: false, | 100 | may_break: false, |
98 | break_ty: self.table.new_type_var(), | 101 | break_ty: self.table.new_type_var(), |
102 | label: label.clone(), | ||
99 | }); | 103 | }); |
100 | self.infer_expr(*body, &Expectation::has_type(Ty::unit())); | 104 | self.infer_expr(*body, &Expectation::has_type(Ty::unit())); |
101 | 105 | ||
@@ -110,8 +114,12 @@ impl<'a> InferenceContext<'a> { | |||
110 | Ty::simple(TypeCtor::Never) | 114 | Ty::simple(TypeCtor::Never) |
111 | } | 115 | } |
112 | } | 116 | } |
113 | Expr::While { condition, body } => { | 117 | Expr::While { condition, body, label } => { |
114 | self.breakables.push(BreakableContext { may_break: false, break_ty: Ty::Unknown }); | 118 | self.breakables.push(BreakableContext { |
119 | may_break: false, | ||
120 | break_ty: Ty::Unknown, | ||
121 | label: label.clone(), | ||
122 | }); | ||
115 | // while let is desugared to a match loop, so this is always simple while | 123 | // while let is desugared to a match loop, so this is always simple while |
116 | self.infer_expr(*condition, &Expectation::has_type(Ty::simple(TypeCtor::Bool))); | 124 | self.infer_expr(*condition, &Expectation::has_type(Ty::simple(TypeCtor::Bool))); |
117 | self.infer_expr(*body, &Expectation::has_type(Ty::unit())); | 125 | self.infer_expr(*body, &Expectation::has_type(Ty::unit())); |
@@ -120,10 +128,14 @@ impl<'a> InferenceContext<'a> { | |||
120 | self.diverges = Diverges::Maybe; | 128 | self.diverges = Diverges::Maybe; |
121 | Ty::unit() | 129 | Ty::unit() |
122 | } | 130 | } |
123 | Expr::For { iterable, body, pat } => { | 131 | Expr::For { iterable, body, pat, label } => { |
124 | let iterable_ty = self.infer_expr(*iterable, &Expectation::none()); | 132 | let iterable_ty = self.infer_expr(*iterable, &Expectation::none()); |
125 | 133 | ||
126 | self.breakables.push(BreakableContext { may_break: false, break_ty: Ty::Unknown }); | 134 | self.breakables.push(BreakableContext { |
135 | may_break: false, | ||
136 | break_ty: Ty::Unknown, | ||
137 | label: label.clone(), | ||
138 | }); | ||
127 | let pat_ty = | 139 | let pat_ty = |
128 | self.resolve_associated_type(iterable_ty, self.resolve_into_iter_item()); | 140 | self.resolve_associated_type(iterable_ty, self.resolve_into_iter_item()); |
129 | 141 | ||
@@ -140,13 +152,13 @@ impl<'a> InferenceContext<'a> { | |||
140 | 152 | ||
141 | let mut sig_tys = Vec::new(); | 153 | let mut sig_tys = Vec::new(); |
142 | 154 | ||
143 | for (arg_pat, arg_type) in args.iter().zip(arg_types.iter()) { | 155 | // collect explicitly written argument types |
144 | let expected = if let Some(type_ref) = arg_type { | 156 | for arg_type in arg_types.iter() { |
157 | let arg_ty = if let Some(type_ref) = arg_type { | ||
145 | self.make_ty(type_ref) | 158 | self.make_ty(type_ref) |
146 | } else { | 159 | } else { |
147 | Ty::Unknown | 160 | self.table.new_type_var() |
148 | }; | 161 | }; |
149 | let arg_ty = self.infer_pat(*arg_pat, &expected, BindingMode::default()); | ||
150 | sig_tys.push(arg_ty); | 162 | sig_tys.push(arg_ty); |
151 | } | 163 | } |
152 | 164 | ||
@@ -158,7 +170,7 @@ impl<'a> InferenceContext<'a> { | |||
158 | sig_tys.push(ret_ty.clone()); | 170 | sig_tys.push(ret_ty.clone()); |
159 | let sig_ty = Ty::apply( | 171 | let sig_ty = Ty::apply( |
160 | TypeCtor::FnPtr { num_args: sig_tys.len() as u16 - 1 }, | 172 | TypeCtor::FnPtr { num_args: sig_tys.len() as u16 - 1 }, |
161 | Substs(sig_tys.into()), | 173 | Substs(sig_tys.clone().into()), |
162 | ); | 174 | ); |
163 | let closure_ty = | 175 | let closure_ty = |
164 | Ty::apply_one(TypeCtor::Closure { def: self.owner, expr: tgt_expr }, sig_ty); | 176 | Ty::apply_one(TypeCtor::Closure { def: self.owner, expr: tgt_expr }, sig_ty); |
@@ -168,6 +180,12 @@ impl<'a> InferenceContext<'a> { | |||
168 | // infer the body. | 180 | // infer the body. |
169 | self.coerce(&closure_ty, &expected.ty); | 181 | self.coerce(&closure_ty, &expected.ty); |
170 | 182 | ||
183 | // Now go through the argument patterns | ||
184 | for (arg_pat, arg_ty) in args.iter().zip(sig_tys) { | ||
185 | let resolved = self.resolve_ty_as_possible(arg_ty); | ||
186 | self.infer_pat(*arg_pat, &resolved, BindingMode::default()); | ||
187 | } | ||
188 | |||
171 | let prev_diverges = mem::replace(&mut self.diverges, Diverges::Maybe); | 189 | let prev_diverges = mem::replace(&mut self.diverges, Diverges::Maybe); |
172 | let prev_ret_ty = mem::replace(&mut self.return_ty, ret_ty.clone()); | 190 | let prev_ret_ty = mem::replace(&mut self.return_ty, ret_ty.clone()); |
173 | 191 | ||
@@ -230,23 +248,24 @@ impl<'a> InferenceContext<'a> { | |||
230 | let resolver = resolver_for_expr(self.db.upcast(), self.owner, tgt_expr); | 248 | let resolver = resolver_for_expr(self.db.upcast(), self.owner, tgt_expr); |
231 | self.infer_path(&resolver, p, tgt_expr.into()).unwrap_or(Ty::Unknown) | 249 | self.infer_path(&resolver, p, tgt_expr.into()).unwrap_or(Ty::Unknown) |
232 | } | 250 | } |
233 | Expr::Continue => Ty::simple(TypeCtor::Never), | 251 | Expr::Continue { .. } => Ty::simple(TypeCtor::Never), |
234 | Expr::Break { expr } => { | 252 | Expr::Break { expr, label } => { |
235 | let val_ty = if let Some(expr) = expr { | 253 | let val_ty = if let Some(expr) = expr { |
236 | self.infer_expr(*expr, &Expectation::none()) | 254 | self.infer_expr(*expr, &Expectation::none()) |
237 | } else { | 255 | } else { |
238 | Ty::unit() | 256 | Ty::unit() |
239 | }; | 257 | }; |
240 | 258 | ||
241 | let last_ty = if let Some(ctxt) = self.breakables.last() { | 259 | let last_ty = |
242 | ctxt.break_ty.clone() | 260 | if let Some(ctxt) = find_breakable(&mut self.breakables, label.as_ref()) { |
243 | } else { | 261 | ctxt.break_ty.clone() |
244 | Ty::Unknown | 262 | } else { |
245 | }; | 263 | Ty::Unknown |
264 | }; | ||
246 | 265 | ||
247 | let merged_type = self.coerce_merge_branch(&last_ty, &val_ty); | 266 | let merged_type = self.coerce_merge_branch(&last_ty, &val_ty); |
248 | 267 | ||
249 | if let Some(ctxt) = self.breakables.last_mut() { | 268 | if let Some(ctxt) = find_breakable(&mut self.breakables, label.as_ref()) { |
250 | ctxt.break_ty = merged_type; | 269 | ctxt.break_ty = merged_type; |
251 | ctxt.may_break = true; | 270 | ctxt.may_break = true; |
252 | } else { | 271 | } else { |
diff --git a/crates/ra_hir_ty/src/tests/patterns.rs b/crates/ra_hir_ty/src/tests/patterns.rs index 0c5f972a2..fe62587c0 100644 --- a/crates/ra_hir_ty/src/tests/patterns.rs +++ b/crates/ra_hir_ty/src/tests/patterns.rs | |||
@@ -520,3 +520,53 @@ fn main() { | |||
520 | 105..107 '()': () | 520 | 105..107 '()': () |
521 | ") | 521 | ") |
522 | } | 522 | } |
523 | |||
524 | #[test] | ||
525 | fn match_ergonomics_in_closure_params() { | ||
526 | assert_snapshot!( | ||
527 | infer(r#" | ||
528 | #[lang = "fn_once"] | ||
529 | trait FnOnce<Args> { | ||
530 | type Output; | ||
531 | } | ||
532 | |||
533 | fn foo<T, U, F: FnOnce(T) -> U>(t: T, f: F) -> U { loop {} } | ||
534 | |||
535 | fn test() { | ||
536 | foo(&(1, "a"), |&(x, y)| x); // normal, no match ergonomics | ||
537 | foo(&(1, "a"), |(x, y)| x); | ||
538 | } | ||
539 | "#), | ||
540 | @r###" | ||
541 | 94..95 't': T | ||
542 | 100..101 'f': F | ||
543 | 111..122 '{ loop {} }': U | ||
544 | 113..120 'loop {}': ! | ||
545 | 118..120 '{}': () | ||
546 | 134..233 '{ ... x); }': () | ||
547 | 140..143 'foo': fn foo<&(i32, &str), i32, |&(i32, &str)| -> i32>(&(i32, &str), |&(i32, &str)| -> i32) -> i32 | ||
548 | 140..167 'foo(&(...y)| x)': i32 | ||
549 | 144..153 '&(1, "a")': &(i32, &str) | ||
550 | 145..153 '(1, "a")': (i32, &str) | ||
551 | 146..147 '1': i32 | ||
552 | 149..152 '"a"': &str | ||
553 | 155..166 '|&(x, y)| x': |&(i32, &str)| -> i32 | ||
554 | 156..163 '&(x, y)': &(i32, &str) | ||
555 | 157..163 '(x, y)': (i32, &str) | ||
556 | 158..159 'x': i32 | ||
557 | 161..162 'y': &str | ||
558 | 165..166 'x': i32 | ||
559 | 204..207 'foo': fn foo<&(i32, &str), &i32, |&(i32, &str)| -> &i32>(&(i32, &str), |&(i32, &str)| -> &i32) -> &i32 | ||
560 | 204..230 'foo(&(...y)| x)': &i32 | ||
561 | 208..217 '&(1, "a")': &(i32, &str) | ||
562 | 209..217 '(1, "a")': (i32, &str) | ||
563 | 210..211 '1': i32 | ||
564 | 213..216 '"a"': &str | ||
565 | 219..229 '|(x, y)| x': |&(i32, &str)| -> &i32 | ||
566 | 220..226 '(x, y)': (i32, &str) | ||
567 | 221..222 'x': &i32 | ||
568 | 224..225 'y': &&str | ||
569 | 228..229 'x': &i32 | ||
570 | "### | ||
571 | ); | ||
572 | } | ||
diff --git a/crates/ra_hir_ty/src/tests/simple.rs b/crates/ra_hir_ty/src/tests/simple.rs index f1db34160..88309157b 100644 --- a/crates/ra_hir_ty/src/tests/simple.rs +++ b/crates/ra_hir_ty/src/tests/simple.rs | |||
@@ -957,7 +957,7 @@ fn main(foo: Foo) { | |||
957 | 51..107 'if tru... }': () | 957 | 51..107 'if tru... }': () |
958 | 54..58 'true': bool | 958 | 54..58 'true': bool |
959 | 59..67 '{ }': () | 959 | 59..67 '{ }': () |
960 | 73..107 'if fal... }': () | 960 | 73..107 'if fal... }': i32 |
961 | 76..81 'false': bool | 961 | 76..81 'false': bool |
962 | 82..107 '{ ... }': i32 | 962 | 82..107 '{ ... }': i32 |
963 | 92..95 'foo': Foo | 963 | 92..95 'foo': Foo |
@@ -1943,3 +1943,57 @@ fn test() { | |||
1943 | "### | 1943 | "### |
1944 | ); | 1944 | ); |
1945 | } | 1945 | } |
1946 | |||
1947 | #[test] | ||
1948 | fn infer_labelled_break_with_val() { | ||
1949 | assert_snapshot!( | ||
1950 | infer(r#" | ||
1951 | fn foo() { | ||
1952 | let _x = || 'outer: loop { | ||
1953 | let inner = 'inner: loop { | ||
1954 | let i = Default::default(); | ||
1955 | if (break 'outer i) { | ||
1956 | loop { break 'inner 5i8; }; | ||
1957 | } else if true { | ||
1958 | break 'inner 6; | ||
1959 | } | ||
1960 | break 7; | ||
1961 | }; | ||
1962 | break inner < 8; | ||
1963 | }; | ||
1964 | } | ||
1965 | "#), | ||
1966 | @r###" | ||
1967 | 10..336 '{ ... }; }': () | ||
1968 | 20..22 '_x': || -> bool | ||
1969 | 25..333 '|| 'ou... }': || -> bool | ||
1970 | 28..333 ''outer... }': bool | ||
1971 | 41..333 '{ ... }': () | ||
1972 | 55..60 'inner': i8 | ||
1973 | 63..301 ''inner... }': i8 | ||
1974 | 76..301 '{ ... }': () | ||
1975 | 94..95 'i': bool | ||
1976 | 98..114 'Defaul...efault': {unknown} | ||
1977 | 98..116 'Defaul...ault()': bool | ||
1978 | 130..270 'if (br... }': () | ||
1979 | 134..148 'break 'outer i': ! | ||
1980 | 147..148 'i': bool | ||
1981 | 150..209 '{ ... }': () | ||
1982 | 168..194 'loop {...5i8; }': ! | ||
1983 | 173..194 '{ brea...5i8; }': () | ||
1984 | 175..191 'break ...er 5i8': ! | ||
1985 | 188..191 '5i8': i8 | ||
1986 | 215..270 'if tru... }': () | ||
1987 | 218..222 'true': bool | ||
1988 | 223..270 '{ ... }': () | ||
1989 | 241..255 'break 'inner 6': ! | ||
1990 | 254..255 '6': i8 | ||
1991 | 283..290 'break 7': ! | ||
1992 | 289..290 '7': i8 | ||
1993 | 311..326 'break inner < 8': ! | ||
1994 | 317..322 'inner': i8 | ||
1995 | 317..326 'inner < 8': bool | ||
1996 | 325..326 '8': i8 | ||
1997 | "### | ||
1998 | ); | ||
1999 | } | ||
diff --git a/crates/ra_hir_ty/src/tests/traits.rs b/crates/ra_hir_ty/src/tests/traits.rs index 0419bc751..e8778d419 100644 --- a/crates/ra_hir_ty/src/tests/traits.rs +++ b/crates/ra_hir_ty/src/tests/traits.rs | |||
@@ -2665,7 +2665,6 @@ fn test() { | |||
2665 | Enum::Variant.test(); | 2665 | Enum::Variant.test(); |
2666 | } | 2666 | } |
2667 | "#, true), | 2667 | "#, true), |
2668 | // wrong result, because the built-in Copy impl for fn defs doesn't exist in Chalk yet | ||
2669 | @r###" | 2668 | @r###" |
2670 | 42..44 '{}': () | 2669 | 42..44 '{}': () |
2671 | 61..62 'T': {unknown} | 2670 | 61..62 'T': {unknown} |
@@ -2674,13 +2673,13 @@ fn test() { | |||
2674 | 146..150 'self': &Self | 2673 | 146..150 'self': &Self |
2675 | 202..282 '{ ...t(); }': () | 2674 | 202..282 '{ ...t(); }': () |
2676 | 208..211 'foo': fn foo() | 2675 | 208..211 'foo': fn foo() |
2677 | 208..218 'foo.test()': {unknown} | 2676 | 208..218 'foo.test()': bool |
2678 | 224..227 'bar': fn bar<{unknown}>({unknown}) -> {unknown} | 2677 | 224..227 'bar': fn bar<{unknown}>({unknown}) -> {unknown} |
2679 | 224..234 'bar.test()': {unknown} | 2678 | 224..234 'bar.test()': bool |
2680 | 240..246 'Struct': Struct(usize) -> Struct | 2679 | 240..246 'Struct': Struct(usize) -> Struct |
2681 | 240..253 'Struct.test()': {unknown} | 2680 | 240..253 'Struct.test()': bool |
2682 | 259..272 'Enum::Variant': Variant(usize) -> Enum | 2681 | 259..272 'Enum::Variant': Variant(usize) -> Enum |
2683 | 259..279 'Enum::...test()': {unknown} | 2682 | 259..279 'Enum::...test()': bool |
2684 | "### | 2683 | "### |
2685 | ); | 2684 | ); |
2686 | } | 2685 | } |
@@ -2754,3 +2753,48 @@ fn test() { | |||
2754 | "### | 2753 | "### |
2755 | ); | 2754 | ); |
2756 | } | 2755 | } |
2756 | |||
2757 | #[test] | ||
2758 | fn integer_range_iterate() { | ||
2759 | let t = type_at( | ||
2760 | r#" | ||
2761 | //- /main.rs crate:main deps:std | ||
2762 | fn test() { | ||
2763 | for x in 0..100 { x<|>; } | ||
2764 | } | ||
2765 | |||
2766 | //- /std.rs crate:std | ||
2767 | pub mod ops { | ||
2768 | pub struct Range<Idx> { | ||
2769 | pub start: Idx, | ||
2770 | pub end: Idx, | ||
2771 | } | ||
2772 | } | ||
2773 | |||
2774 | pub mod iter { | ||
2775 | pub trait Iterator { | ||
2776 | type Item; | ||
2777 | } | ||
2778 | |||
2779 | pub trait IntoIterator { | ||
2780 | type Item; | ||
2781 | type IntoIter: Iterator<Item = Self::Item>; | ||
2782 | } | ||
2783 | |||
2784 | impl<T> IntoIterator for T where T: Iterator { | ||
2785 | type Item = <T as Iterator>::Item; | ||
2786 | type IntoIter = Self; | ||
2787 | } | ||
2788 | } | ||
2789 | |||
2790 | trait Step {} | ||
2791 | impl Step for i32 {} | ||
2792 | impl Step for i64 {} | ||
2793 | |||
2794 | impl<A: Step> iter::Iterator for ops::Range<A> { | ||
2795 | type Item = A; | ||
2796 | } | ||
2797 | "#, | ||
2798 | ); | ||
2799 | assert_eq!(t, "i32"); | ||
2800 | } | ||
diff --git a/crates/ra_ide/src/completion.rs b/crates/ra_ide/src/completion.rs index 191300704..d890b69d2 100644 --- a/crates/ra_ide/src/completion.rs +++ b/crates/ra_ide/src/completion.rs | |||
@@ -1,5 +1,3 @@ | |||
1 | //! FIXME: write short doc here | ||
2 | |||
3 | mod completion_config; | 1 | mod completion_config; |
4 | mod completion_item; | 2 | mod completion_item; |
5 | mod completion_context; | 3 | mod completion_context; |
@@ -35,6 +33,51 @@ pub use crate::completion::{ | |||
35 | completion_item::{CompletionItem, CompletionItemKind, CompletionScore, InsertTextFormat}, | 33 | completion_item::{CompletionItem, CompletionItemKind, CompletionScore, InsertTextFormat}, |
36 | }; | 34 | }; |
37 | 35 | ||
36 | //FIXME: split the following feature into fine-grained features. | ||
37 | |||
38 | // Feature: Magic Completions | ||
39 | // | ||
40 | // In addition to usual reference completion, rust-analyzer provides some ✨magic✨ | ||
41 | // completions as well: | ||
42 | // | ||
43 | // Keywords like `if`, `else` `while`, `loop` are completed with braces, and cursor | ||
44 | // is placed at the appropriate position. Even though `if` is easy to type, you | ||
45 | // still want to complete it, to get ` { }` for free! `return` is inserted with a | ||
46 | // space or `;` depending on the return type of the function. | ||
47 | // | ||
48 | // When completing a function call, `()` are automatically inserted. If a function | ||
49 | // takes arguments, the cursor is positioned inside the parenthesis. | ||
50 | // | ||
51 | // There are postfix completions, which can be triggered by typing something like | ||
52 | // `foo().if`. The word after `.` determines postfix completion. Possible variants are: | ||
53 | // | ||
54 | // - `expr.if` -> `if expr {}` or `if let ... {}` for `Option` or `Result` | ||
55 | // - `expr.match` -> `match expr {}` | ||
56 | // - `expr.while` -> `while expr {}` or `while let ... {}` for `Option` or `Result` | ||
57 | // - `expr.ref` -> `&expr` | ||
58 | // - `expr.refm` -> `&mut expr` | ||
59 | // - `expr.not` -> `!expr` | ||
60 | // - `expr.dbg` -> `dbg!(expr)` | ||
61 | // | ||
62 | // There also snippet completions: | ||
63 | // | ||
64 | // .Expressions | ||
65 | // - `pd` -> `println!("{:?}")` | ||
66 | // - `ppd` -> `println!("{:#?}")` | ||
67 | // | ||
68 | // .Items | ||
69 | // - `tfn` -> `#[test] fn f(){}` | ||
70 | // - `tmod` -> | ||
71 | // ```rust | ||
72 | // #[cfg(test)] | ||
73 | // mod tests { | ||
74 | // use super::*; | ||
75 | // | ||
76 | // #[test] | ||
77 | // fn test_fn() {} | ||
78 | // } | ||
79 | // ``` | ||
80 | |||
38 | /// Main entry point for completion. We run completion as a two-phase process. | 81 | /// Main entry point for completion. We run completion as a two-phase process. |
39 | /// | 82 | /// |
40 | /// First, we look at the position and collect a so-called `CompletionContext. | 83 | /// First, we look at the position and collect a so-called `CompletionContext. |
diff --git a/crates/ra_ide/src/completion/complete_postfix.rs b/crates/ra_ide/src/completion/complete_postfix.rs index 02e660ca8..59b58bf98 100644 --- a/crates/ra_ide/src/completion/complete_postfix.rs +++ b/crates/ra_ide/src/completion/complete_postfix.rs | |||
@@ -1,12 +1,11 @@ | |||
1 | //! FIXME: write short doc here | 1 | //! FIXME: write short doc here |
2 | 2 | use ra_assists::utils::TryEnum; | |
3 | use ra_syntax::{ | 3 | use ra_syntax::{ |
4 | ast::{self, AstNode}, | 4 | ast::{self, AstNode}, |
5 | TextRange, TextSize, | 5 | TextRange, TextSize, |
6 | }; | 6 | }; |
7 | use ra_text_edit::TextEdit; | 7 | use ra_text_edit::TextEdit; |
8 | 8 | ||
9 | use super::completion_config::SnippetCap; | ||
10 | use crate::{ | 9 | use crate::{ |
11 | completion::{ | 10 | completion::{ |
12 | completion_context::CompletionContext, | 11 | completion_context::CompletionContext, |
@@ -14,7 +13,8 @@ use crate::{ | |||
14 | }, | 13 | }, |
15 | CompletionItem, | 14 | CompletionItem, |
16 | }; | 15 | }; |
17 | use ra_assists::utils::TryEnum; | 16 | |
17 | use super::completion_config::SnippetCap; | ||
18 | 18 | ||
19 | pub(super) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) { | 19 | pub(super) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) { |
20 | if !ctx.config.enable_postfix_completions { | 20 | if !ctx.config.enable_postfix_completions { |
diff --git a/crates/ra_ide/src/display/navigation_target.rs b/crates/ra_ide/src/display/navigation_target.rs index 5da28edd2..c7bb1e69f 100644 --- a/crates/ra_ide/src/display/navigation_target.rs +++ b/crates/ra_ide/src/display/navigation_target.rs | |||
@@ -92,15 +92,16 @@ impl NavigationTarget { | |||
92 | let name = module.name(db).map(|it| it.to_string().into()).unwrap_or_default(); | 92 | let name = module.name(db).map(|it| it.to_string().into()).unwrap_or_default(); |
93 | if let Some(src) = module.declaration_source(db) { | 93 | if let Some(src) = module.declaration_source(db) { |
94 | let frange = original_range(db, src.as_ref().map(|it| it.syntax())); | 94 | let frange = original_range(db, src.as_ref().map(|it| it.syntax())); |
95 | return NavigationTarget::from_syntax( | 95 | let mut res = NavigationTarget::from_syntax( |
96 | frange.file_id, | 96 | frange.file_id, |
97 | name, | 97 | name, |
98 | None, | 98 | None, |
99 | frange.range, | 99 | frange.range, |
100 | src.value.syntax().kind(), | 100 | src.value.syntax().kind(), |
101 | src.value.doc_comment_text(), | ||
102 | src.value.short_label(), | ||
103 | ); | 101 | ); |
102 | res.docs = src.value.doc_comment_text(); | ||
103 | res.description = src.value.short_label(); | ||
104 | return res; | ||
104 | } | 105 | } |
105 | module.to_nav(db) | 106 | module.to_nav(db) |
106 | } | 107 | } |
@@ -130,11 +131,9 @@ impl NavigationTarget { | |||
130 | } | 131 | } |
131 | 132 | ||
132 | /// Allows `NavigationTarget` to be created from a `NameOwner` | 133 | /// Allows `NavigationTarget` to be created from a `NameOwner` |
133 | fn from_named( | 134 | pub(crate) fn from_named( |
134 | db: &RootDatabase, | 135 | db: &RootDatabase, |
135 | node: InFile<&dyn ast::NameOwner>, | 136 | node: InFile<&dyn ast::NameOwner>, |
136 | docs: Option<String>, | ||
137 | description: Option<String>, | ||
138 | ) -> NavigationTarget { | 137 | ) -> NavigationTarget { |
139 | //FIXME: use `_` instead of empty string | 138 | //FIXME: use `_` instead of empty string |
140 | let name = node.value.name().map(|it| it.text().clone()).unwrap_or_default(); | 139 | let name = node.value.name().map(|it| it.text().clone()).unwrap_or_default(); |
@@ -148,8 +147,6 @@ impl NavigationTarget { | |||
148 | focus_range, | 147 | focus_range, |
149 | frange.range, | 148 | frange.range, |
150 | node.value.syntax().kind(), | 149 | node.value.syntax().kind(), |
151 | docs, | ||
152 | description, | ||
153 | ) | 150 | ) |
154 | } | 151 | } |
155 | 152 | ||
@@ -159,8 +156,6 @@ impl NavigationTarget { | |||
159 | focus_range: Option<TextRange>, | 156 | focus_range: Option<TextRange>, |
160 | full_range: TextRange, | 157 | full_range: TextRange, |
161 | kind: SyntaxKind, | 158 | kind: SyntaxKind, |
162 | docs: Option<String>, | ||
163 | description: Option<String>, | ||
164 | ) -> NavigationTarget { | 159 | ) -> NavigationTarget { |
165 | NavigationTarget { | 160 | NavigationTarget { |
166 | file_id, | 161 | file_id, |
@@ -169,8 +164,8 @@ impl NavigationTarget { | |||
169 | full_range, | 164 | full_range, |
170 | focus_range, | 165 | focus_range, |
171 | container_name: None, | 166 | container_name: None, |
172 | description, | 167 | description: None, |
173 | docs, | 168 | docs: None, |
174 | } | 169 | } |
175 | } | 170 | } |
176 | } | 171 | } |
@@ -238,12 +233,11 @@ where | |||
238 | { | 233 | { |
239 | fn to_nav(&self, db: &RootDatabase) -> NavigationTarget { | 234 | fn to_nav(&self, db: &RootDatabase) -> NavigationTarget { |
240 | let src = self.source(db); | 235 | let src = self.source(db); |
241 | NavigationTarget::from_named( | 236 | let mut res = |
242 | db, | 237 | NavigationTarget::from_named(db, src.as_ref().map(|it| it as &dyn ast::NameOwner)); |
243 | src.as_ref().map(|it| it as &dyn ast::NameOwner), | 238 | res.docs = src.value.doc_comment_text(); |
244 | src.value.doc_comment_text(), | 239 | res.description = src.value.short_label(); |
245 | src.value.short_label(), | 240 | res |
246 | ) | ||
247 | } | 241 | } |
248 | } | 242 | } |
249 | 243 | ||
@@ -258,15 +252,7 @@ impl ToNav for hir::Module { | |||
258 | } | 252 | } |
259 | }; | 253 | }; |
260 | let frange = original_range(db, src.with_value(syntax)); | 254 | let frange = original_range(db, src.with_value(syntax)); |
261 | NavigationTarget::from_syntax( | 255 | NavigationTarget::from_syntax(frange.file_id, name, focus, frange.range, syntax.kind()) |
262 | frange.file_id, | ||
263 | name, | ||
264 | focus, | ||
265 | frange.range, | ||
266 | syntax.kind(), | ||
267 | None, | ||
268 | None, | ||
269 | ) | ||
270 | } | 256 | } |
271 | } | 257 | } |
272 | 258 | ||
@@ -285,8 +271,6 @@ impl ToNav for hir::ImplDef { | |||
285 | None, | 271 | None, |
286 | frange.range, | 272 | frange.range, |
287 | src.value.syntax().kind(), | 273 | src.value.syntax().kind(), |
288 | None, | ||
289 | None, | ||
290 | ) | 274 | ) |
291 | } | 275 | } |
292 | } | 276 | } |
@@ -296,12 +280,12 @@ impl ToNav for hir::Field { | |||
296 | let src = self.source(db); | 280 | let src = self.source(db); |
297 | 281 | ||
298 | match &src.value { | 282 | match &src.value { |
299 | FieldSource::Named(it) => NavigationTarget::from_named( | 283 | FieldSource::Named(it) => { |
300 | db, | 284 | let mut res = NavigationTarget::from_named(db, src.with_value(it)); |
301 | src.with_value(it), | 285 | res.docs = it.doc_comment_text(); |
302 | it.doc_comment_text(), | 286 | res.description = it.short_label(); |
303 | it.short_label(), | 287 | res |
304 | ), | 288 | } |
305 | FieldSource::Pos(it) => { | 289 | FieldSource::Pos(it) => { |
306 | let frange = original_range(db, src.with_value(it.syntax())); | 290 | let frange = original_range(db, src.with_value(it.syntax())); |
307 | NavigationTarget::from_syntax( | 291 | NavigationTarget::from_syntax( |
@@ -310,8 +294,6 @@ impl ToNav for hir::Field { | |||
310 | None, | 294 | None, |
311 | frange.range, | 295 | frange.range, |
312 | it.syntax().kind(), | 296 | it.syntax().kind(), |
313 | None, | ||
314 | None, | ||
315 | ) | 297 | ) |
316 | } | 298 | } |
317 | } | 299 | } |
@@ -322,12 +304,10 @@ impl ToNav for hir::MacroDef { | |||
322 | fn to_nav(&self, db: &RootDatabase) -> NavigationTarget { | 304 | fn to_nav(&self, db: &RootDatabase) -> NavigationTarget { |
323 | let src = self.source(db); | 305 | let src = self.source(db); |
324 | log::debug!("nav target {:#?}", src.value.syntax()); | 306 | log::debug!("nav target {:#?}", src.value.syntax()); |
325 | NavigationTarget::from_named( | 307 | let mut res = |
326 | db, | 308 | NavigationTarget::from_named(db, src.as_ref().map(|it| it as &dyn ast::NameOwner)); |
327 | src.as_ref().map(|it| it as &dyn ast::NameOwner), | 309 | res.docs = src.value.doc_comment_text(); |
328 | src.value.doc_comment_text(), | 310 | res |
329 | None, | ||
330 | ) | ||
331 | } | 311 | } |
332 | } | 312 | } |
333 | 313 | ||
diff --git a/crates/ra_ide/src/display/structure.rs b/crates/ra_ide/src/display/structure.rs index 967eee5d2..aad5a8e4d 100644 --- a/crates/ra_ide/src/display/structure.rs +++ b/crates/ra_ide/src/display/structure.rs | |||
@@ -1,10 +1,6 @@ | |||
1 | //! FIXME: write short doc here | ||
2 | |||
3 | use crate::TextRange; | ||
4 | |||
5 | use ra_syntax::{ | 1 | use ra_syntax::{ |
6 | ast::{self, AttrsOwner, NameOwner, TypeAscriptionOwner, TypeParamsOwner}, | 2 | ast::{self, AttrsOwner, NameOwner, TypeAscriptionOwner, TypeParamsOwner}, |
7 | match_ast, AstNode, SourceFile, SyntaxKind, SyntaxNode, WalkEvent, | 3 | match_ast, AstNode, SourceFile, SyntaxKind, SyntaxNode, TextRange, WalkEvent, |
8 | }; | 4 | }; |
9 | 5 | ||
10 | #[derive(Debug, Clone)] | 6 | #[derive(Debug, Clone)] |
@@ -18,6 +14,19 @@ pub struct StructureNode { | |||
18 | pub deprecated: bool, | 14 | pub deprecated: bool, |
19 | } | 15 | } |
20 | 16 | ||
17 | // Feature: File Structure | ||
18 | // | ||
19 | // Provides a tree of the symbols defined in the file. Can be used to | ||
20 | // | ||
21 | // * fuzzy search symbol in a file (super useful) | ||
22 | // * draw breadcrumbs to describe the context around the cursor | ||
23 | // * draw outline of the file | ||
24 | // | ||
25 | // |=== | ||
26 | // | Editor | Shortcut | ||
27 | // | ||
28 | // | VS Code | kbd:[Ctrl+Shift+O] | ||
29 | // |=== | ||
21 | pub fn file_structure(file: &SourceFile) -> Vec<StructureNode> { | 30 | pub fn file_structure(file: &SourceFile) -> Vec<StructureNode> { |
22 | let mut res = Vec::new(); | 31 | let mut res = Vec::new(); |
23 | let mut stack = Vec::new(); | 32 | let mut stack = Vec::new(); |
diff --git a/crates/ra_ide/src/expand_macro.rs b/crates/ra_ide/src/expand_macro.rs index f536ba3e7..54a47aac0 100644 --- a/crates/ra_ide/src/expand_macro.rs +++ b/crates/ra_ide/src/expand_macro.rs | |||
@@ -1,5 +1,3 @@ | |||
1 | //! This modules implements "expand macro" functionality in the IDE | ||
2 | |||
3 | use hir::Semantics; | 1 | use hir::Semantics; |
4 | use ra_ide_db::RootDatabase; | 2 | use ra_ide_db::RootDatabase; |
5 | use ra_syntax::{ | 3 | use ra_syntax::{ |
@@ -14,6 +12,15 @@ pub struct ExpandedMacro { | |||
14 | pub expansion: String, | 12 | pub expansion: String, |
15 | } | 13 | } |
16 | 14 | ||
15 | // Feature: Expand Macro Recursively | ||
16 | // | ||
17 | // Shows the full macro expansion of the macro at current cursor. | ||
18 | // | ||
19 | // |=== | ||
20 | // | Editor | Action Name | ||
21 | // | ||
22 | // | VS Code | **Rust Analyzer: Expand macro recursively** | ||
23 | // |=== | ||
17 | pub(crate) fn expand_macro(db: &RootDatabase, position: FilePosition) -> Option<ExpandedMacro> { | 24 | pub(crate) fn expand_macro(db: &RootDatabase, position: FilePosition) -> Option<ExpandedMacro> { |
18 | let sema = Semantics::new(db); | 25 | let sema = Semantics::new(db); |
19 | let file = sema.parse(position.file_id); | 26 | let file = sema.parse(position.file_id); |
diff --git a/crates/ra_ide/src/extend_selection.rs b/crates/ra_ide/src/extend_selection.rs index 554594a43..a4bc93cdb 100644 --- a/crates/ra_ide/src/extend_selection.rs +++ b/crates/ra_ide/src/extend_selection.rs | |||
@@ -1,5 +1,3 @@ | |||
1 | //! FIXME: write short doc here | ||
2 | |||
3 | use std::iter::successors; | 1 | use std::iter::successors; |
4 | 2 | ||
5 | use hir::Semantics; | 3 | use hir::Semantics; |
@@ -14,6 +12,16 @@ use ra_syntax::{ | |||
14 | 12 | ||
15 | use crate::FileRange; | 13 | use crate::FileRange; |
16 | 14 | ||
15 | // Feature: Extend Selection | ||
16 | // | ||
17 | // Extends the current selection to the encompassing syntactic construct | ||
18 | // (expression, statement, item, module, etc). It works with multiple cursors. | ||
19 | // | ||
20 | // |=== | ||
21 | // | Editor | Shortcut | ||
22 | // | ||
23 | // | VS Code | kbd:[Ctrl+Shift+→] | ||
24 | // |=== | ||
17 | pub(crate) fn extend_selection(db: &RootDatabase, frange: FileRange) -> TextRange { | 25 | pub(crate) fn extend_selection(db: &RootDatabase, frange: FileRange) -> TextRange { |
18 | let sema = Semantics::new(db); | 26 | let sema = Semantics::new(db); |
19 | let src = sema.parse(frange.file_id); | 27 | let src = sema.parse(frange.file_id); |
diff --git a/crates/ra_ide/src/goto_definition.rs b/crates/ra_ide/src/goto_definition.rs index 90e85d419..a6c86e99c 100644 --- a/crates/ra_ide/src/goto_definition.rs +++ b/crates/ra_ide/src/goto_definition.rs | |||
@@ -1,5 +1,3 @@ | |||
1 | //! FIXME: write short doc here | ||
2 | |||
3 | use hir::Semantics; | 1 | use hir::Semantics; |
4 | use ra_ide_db::{ | 2 | use ra_ide_db::{ |
5 | defs::{classify_name, classify_name_ref}, | 3 | defs::{classify_name, classify_name_ref}, |
@@ -17,6 +15,15 @@ use crate::{ | |||
17 | FilePosition, NavigationTarget, RangeInfo, | 15 | FilePosition, NavigationTarget, RangeInfo, |
18 | }; | 16 | }; |
19 | 17 | ||
18 | // Feature: Go to Definition | ||
19 | // | ||
20 | // Navigates to the definition of an identifier. | ||
21 | // | ||
22 | // |=== | ||
23 | // | Editor | Shortcut | ||
24 | // | ||
25 | // | VS Code | kbd:[F12] | ||
26 | // |=== | ||
20 | pub(crate) fn goto_definition( | 27 | pub(crate) fn goto_definition( |
21 | db: &RootDatabase, | 28 | db: &RootDatabase, |
22 | position: FilePosition, | 29 | position: FilePosition, |
diff --git a/crates/ra_ide/src/impls.rs b/crates/ra_ide/src/goto_implementation.rs index ea2225f70..0cec0657e 100644 --- a/crates/ra_ide/src/impls.rs +++ b/crates/ra_ide/src/goto_implementation.rs | |||
@@ -1,11 +1,18 @@ | |||
1 | //! FIXME: write short doc here | ||
2 | |||
3 | use hir::{Crate, ImplDef, Semantics}; | 1 | use hir::{Crate, ImplDef, Semantics}; |
4 | use ra_ide_db::RootDatabase; | 2 | use ra_ide_db::RootDatabase; |
5 | use ra_syntax::{algo::find_node_at_offset, ast, AstNode}; | 3 | use ra_syntax::{algo::find_node_at_offset, ast, AstNode}; |
6 | 4 | ||
7 | use crate::{display::ToNav, FilePosition, NavigationTarget, RangeInfo}; | 5 | use crate::{display::ToNav, FilePosition, NavigationTarget, RangeInfo}; |
8 | 6 | ||
7 | // Feature: Go to Implementation | ||
8 | // | ||
9 | // Navigates to the impl block of structs, enums or traits. Also implemented as a code lens. | ||
10 | // | ||
11 | // |=== | ||
12 | // | Editor | Shortcut | ||
13 | // | ||
14 | // | VS Code | kbd:[Ctrl+F12] | ||
15 | // |=== | ||
9 | pub(crate) fn goto_implementation( | 16 | pub(crate) fn goto_implementation( |
10 | db: &RootDatabase, | 17 | db: &RootDatabase, |
11 | position: FilePosition, | 18 | position: FilePosition, |
diff --git a/crates/ra_ide/src/goto_type_definition.rs b/crates/ra_ide/src/goto_type_definition.rs index a84637489..91a3097fb 100644 --- a/crates/ra_ide/src/goto_type_definition.rs +++ b/crates/ra_ide/src/goto_type_definition.rs | |||
@@ -1,10 +1,17 @@ | |||
1 | //! FIXME: write short doc here | ||
2 | |||
3 | use ra_ide_db::RootDatabase; | 1 | use ra_ide_db::RootDatabase; |
4 | use ra_syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset}; | 2 | use ra_syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset}; |
5 | 3 | ||
6 | use crate::{display::ToNav, FilePosition, NavigationTarget, RangeInfo}; | 4 | use crate::{display::ToNav, FilePosition, NavigationTarget, RangeInfo}; |
7 | 5 | ||
6 | // Feature: Go to Type Definition | ||
7 | // | ||
8 | // Navigates to the type of an identifier. | ||
9 | // | ||
10 | // |=== | ||
11 | // | Editor | Action Name | ||
12 | // | ||
13 | // | VS Code | **Go to Type Definition* | ||
14 | // |=== | ||
8 | pub(crate) fn goto_type_definition( | 15 | pub(crate) fn goto_type_definition( |
9 | db: &RootDatabase, | 16 | db: &RootDatabase, |
10 | position: FilePosition, | 17 | position: FilePosition, |
diff --git a/crates/ra_ide/src/hover.rs b/crates/ra_ide/src/hover.rs index 3e721dcca..d96cb5596 100644 --- a/crates/ra_ide/src/hover.rs +++ b/crates/ra_ide/src/hover.rs | |||
@@ -1,10 +1,10 @@ | |||
1 | //! Logic for computing info that is displayed when the user hovers over any | 1 | use std::iter::once; |
2 | //! source code items (e.g. function call, struct field, variable symbol...) | ||
3 | 2 | ||
4 | use hir::{ | 3 | use hir::{ |
5 | Adt, AsAssocItem, AssocItemContainer, FieldSource, HasSource, HirDisplay, ModuleDef, | 4 | Adt, AsAssocItem, AssocItemContainer, FieldSource, HasSource, HirDisplay, ModuleDef, |
6 | ModuleSource, Semantics, | 5 | ModuleSource, Semantics, |
7 | }; | 6 | }; |
7 | use itertools::Itertools; | ||
8 | use ra_db::SourceDatabase; | 8 | use ra_db::SourceDatabase; |
9 | use ra_ide_db::{ | 9 | use ra_ide_db::{ |
10 | defs::{classify_name, classify_name_ref, Definition}, | 10 | defs::{classify_name, classify_name_ref, Definition}, |
@@ -21,8 +21,6 @@ use crate::{ | |||
21 | display::{macro_label, rust_code_markup, rust_code_markup_with_doc, ShortLabel}, | 21 | display::{macro_label, rust_code_markup, rust_code_markup_with_doc, ShortLabel}, |
22 | FilePosition, RangeInfo, | 22 | FilePosition, RangeInfo, |
23 | }; | 23 | }; |
24 | use itertools::Itertools; | ||
25 | use std::iter::once; | ||
26 | 24 | ||
27 | /// Contains the results when hovering over an item | 25 | /// Contains the results when hovering over an item |
28 | #[derive(Debug, Default)] | 26 | #[derive(Debug, Default)] |
@@ -62,6 +60,63 @@ impl HoverResult { | |||
62 | } | 60 | } |
63 | } | 61 | } |
64 | 62 | ||
63 | // Feature: Hover | ||
64 | // | ||
65 | // Shows additional information, like type of an expression or documentation for definition when "focusing" code. | ||
66 | // Focusing is usually hovering with a mouse, but can also be triggered with a shortcut. | ||
67 | pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option<RangeInfo<HoverResult>> { | ||
68 | let sema = Semantics::new(db); | ||
69 | let file = sema.parse(position.file_id).syntax().clone(); | ||
70 | let token = pick_best(file.token_at_offset(position.offset))?; | ||
71 | let token = sema.descend_into_macros(token); | ||
72 | |||
73 | let mut res = HoverResult::new(); | ||
74 | |||
75 | if let Some((node, name_kind)) = match_ast! { | ||
76 | match (token.parent()) { | ||
77 | ast::NameRef(name_ref) => { | ||
78 | classify_name_ref(&sema, &name_ref).map(|d| (name_ref.syntax().clone(), d.definition())) | ||
79 | }, | ||
80 | ast::Name(name) => { | ||
81 | classify_name(&sema, &name).map(|d| (name.syntax().clone(), d.definition())) | ||
82 | }, | ||
83 | _ => None, | ||
84 | } | ||
85 | } { | ||
86 | let range = sema.original_range(&node).range; | ||
87 | res.extend(hover_text_from_name_kind(db, name_kind)); | ||
88 | |||
89 | if !res.is_empty() { | ||
90 | return Some(RangeInfo::new(range, res)); | ||
91 | } | ||
92 | } | ||
93 | |||
94 | let node = token | ||
95 | .ancestors() | ||
96 | .find(|n| ast::Expr::cast(n.clone()).is_some() || ast::Pat::cast(n.clone()).is_some())?; | ||
97 | |||
98 | let ty = match_ast! { | ||
99 | match node { | ||
100 | ast::MacroCall(_it) => { | ||
101 | // If this node is a MACRO_CALL, it means that `descend_into_macros` failed to resolve. | ||
102 | // (e.g expanding a builtin macro). So we give up here. | ||
103 | return None; | ||
104 | }, | ||
105 | ast::Expr(it) => { | ||
106 | sema.type_of_expr(&it) | ||
107 | }, | ||
108 | ast::Pat(it) => { | ||
109 | sema.type_of_pat(&it) | ||
110 | }, | ||
111 | _ => None, | ||
112 | } | ||
113 | }?; | ||
114 | |||
115 | res.extend(Some(rust_code_markup(&ty.display(db)))); | ||
116 | let range = sema.original_range(&node).range; | ||
117 | Some(RangeInfo::new(range, res)) | ||
118 | } | ||
119 | |||
65 | fn hover_text( | 120 | fn hover_text( |
66 | docs: Option<String>, | 121 | docs: Option<String>, |
67 | desc: Option<String>, | 122 | desc: Option<String>, |
@@ -160,59 +215,6 @@ fn hover_text_from_name_kind(db: &RootDatabase, def: Definition) -> Option<Strin | |||
160 | } | 215 | } |
161 | } | 216 | } |
162 | 217 | ||
163 | pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option<RangeInfo<HoverResult>> { | ||
164 | let sema = Semantics::new(db); | ||
165 | let file = sema.parse(position.file_id).syntax().clone(); | ||
166 | let token = pick_best(file.token_at_offset(position.offset))?; | ||
167 | let token = sema.descend_into_macros(token); | ||
168 | |||
169 | let mut res = HoverResult::new(); | ||
170 | |||
171 | if let Some((node, name_kind)) = match_ast! { | ||
172 | match (token.parent()) { | ||
173 | ast::NameRef(name_ref) => { | ||
174 | classify_name_ref(&sema, &name_ref).map(|d| (name_ref.syntax().clone(), d.definition())) | ||
175 | }, | ||
176 | ast::Name(name) => { | ||
177 | classify_name(&sema, &name).map(|d| (name.syntax().clone(), d.definition())) | ||
178 | }, | ||
179 | _ => None, | ||
180 | } | ||
181 | } { | ||
182 | let range = sema.original_range(&node).range; | ||
183 | res.extend(hover_text_from_name_kind(db, name_kind)); | ||
184 | |||
185 | if !res.is_empty() { | ||
186 | return Some(RangeInfo::new(range, res)); | ||
187 | } | ||
188 | } | ||
189 | |||
190 | let node = token | ||
191 | .ancestors() | ||
192 | .find(|n| ast::Expr::cast(n.clone()).is_some() || ast::Pat::cast(n.clone()).is_some())?; | ||
193 | |||
194 | let ty = match_ast! { | ||
195 | match node { | ||
196 | ast::MacroCall(_it) => { | ||
197 | // If this node is a MACRO_CALL, it means that `descend_into_macros` failed to resolve. | ||
198 | // (e.g expanding a builtin macro). So we give up here. | ||
199 | return None; | ||
200 | }, | ||
201 | ast::Expr(it) => { | ||
202 | sema.type_of_expr(&it) | ||
203 | }, | ||
204 | ast::Pat(it) => { | ||
205 | sema.type_of_pat(&it) | ||
206 | }, | ||
207 | _ => None, | ||
208 | } | ||
209 | }?; | ||
210 | |||
211 | res.extend(Some(rust_code_markup(&ty.display(db)))); | ||
212 | let range = sema.original_range(&node).range; | ||
213 | Some(RangeInfo::new(range, res)) | ||
214 | } | ||
215 | |||
216 | fn pick_best(tokens: TokenAtOffset<SyntaxToken>) -> Option<SyntaxToken> { | 218 | fn pick_best(tokens: TokenAtOffset<SyntaxToken>) -> Option<SyntaxToken> { |
217 | return tokens.max_by_key(priority); | 219 | return tokens.max_by_key(priority); |
218 | fn priority(n: &SyntaxToken) -> usize { | 220 | fn priority(n: &SyntaxToken) -> usize { |
diff --git a/crates/ra_ide/src/inlay_hints.rs b/crates/ra_ide/src/inlay_hints.rs index b391f903a..75bd3c96b 100644 --- a/crates/ra_ide/src/inlay_hints.rs +++ b/crates/ra_ide/src/inlay_hints.rs | |||
@@ -1,5 +1,3 @@ | |||
1 | //! This module defines multiple types of inlay hints and their visibility | ||
2 | |||
3 | use hir::{Adt, HirDisplay, Semantics, Type}; | 1 | use hir::{Adt, HirDisplay, Semantics, Type}; |
4 | use ra_ide_db::RootDatabase; | 2 | use ra_ide_db::RootDatabase; |
5 | use ra_prof::profile; | 3 | use ra_prof::profile; |
@@ -39,6 +37,26 @@ pub struct InlayHint { | |||
39 | pub label: SmolStr, | 37 | pub label: SmolStr, |
40 | } | 38 | } |
41 | 39 | ||
40 | // Feature: Inlay Hints | ||
41 | // | ||
42 | // rust-analyzer shows additional information inline with the source code. | ||
43 | // Editors usually render this using read-only virtual text snippets interspersed with code. | ||
44 | // | ||
45 | // rust-analyzer shows hits for | ||
46 | // | ||
47 | // * types of local variables | ||
48 | // * names of function arguments | ||
49 | // * types of chained expressions | ||
50 | // | ||
51 | // **Note:** VS Code does not have native support for inlay hints https://github.com/microsoft/vscode/issues/16221[yet] and the hints are implemented using decorations. | ||
52 | // This approach has limitations, the caret movement and bracket highlighting near the edges of the hint may be weird: | ||
53 | // https://github.com/rust-analyzer/rust-analyzer/issues/1623[1], https://github.com/rust-analyzer/rust-analyzer/issues/3453[2]. | ||
54 | // | ||
55 | // |=== | ||
56 | // | Editor | Action Name | ||
57 | // | ||
58 | // | VS Code | **Rust Analyzer: Toggle inlay hints* | ||
59 | // |=== | ||
42 | pub(crate) fn inlay_hints( | 60 | pub(crate) fn inlay_hints( |
43 | db: &RootDatabase, | 61 | db: &RootDatabase, |
44 | file_id: FileId, | 62 | file_id: FileId, |
diff --git a/crates/ra_ide/src/join_lines.rs b/crates/ra_ide/src/join_lines.rs index af1ade8a1..5036c1fb0 100644 --- a/crates/ra_ide/src/join_lines.rs +++ b/crates/ra_ide/src/join_lines.rs | |||
@@ -1,5 +1,3 @@ | |||
1 | //! FIXME: write short doc here | ||
2 | |||
3 | use itertools::Itertools; | 1 | use itertools::Itertools; |
4 | use ra_fmt::{compute_ws, extract_trivial_expression}; | 2 | use ra_fmt::{compute_ws, extract_trivial_expression}; |
5 | use ra_syntax::{ | 3 | use ra_syntax::{ |
@@ -11,6 +9,15 @@ use ra_syntax::{ | |||
11 | }; | 9 | }; |
12 | use ra_text_edit::{TextEdit, TextEditBuilder}; | 10 | use ra_text_edit::{TextEdit, TextEditBuilder}; |
13 | 11 | ||
12 | // Feature: Join Lines | ||
13 | // | ||
14 | // Join selected lines into one, smartly fixing up whitespace, trailing commas, and braces. | ||
15 | // | ||
16 | // |=== | ||
17 | // | Editor | Action Name | ||
18 | // | ||
19 | // | VS Code | **Rust Analyzer: Join lines** | ||
20 | // |=== | ||
14 | pub fn join_lines(file: &SourceFile, range: TextRange) -> TextEdit { | 21 | pub fn join_lines(file: &SourceFile, range: TextRange) -> TextEdit { |
15 | let range = if range.is_empty() { | 22 | let range = if range.is_empty() { |
16 | let syntax = file.syntax(); | 23 | let syntax = file.syntax(); |
diff --git a/crates/ra_ide/src/lib.rs b/crates/ra_ide/src/lib.rs index d983cd910..12d5716e8 100644 --- a/crates/ra_ide/src/lib.rs +++ b/crates/ra_ide/src/lib.rs | |||
@@ -23,6 +23,7 @@ mod completion; | |||
23 | mod runnables; | 23 | mod runnables; |
24 | mod goto_definition; | 24 | mod goto_definition; |
25 | mod goto_type_definition; | 25 | mod goto_type_definition; |
26 | mod goto_implementation; | ||
26 | mod extend_selection; | 27 | mod extend_selection; |
27 | mod hover; | 28 | mod hover; |
28 | mod call_hierarchy; | 29 | mod call_hierarchy; |
@@ -30,7 +31,6 @@ mod call_info; | |||
30 | mod syntax_highlighting; | 31 | mod syntax_highlighting; |
31 | mod parent_module; | 32 | mod parent_module; |
32 | mod references; | 33 | mod references; |
33 | mod impls; | ||
34 | mod diagnostics; | 34 | mod diagnostics; |
35 | mod syntax_tree; | 35 | mod syntax_tree; |
36 | mod folding_ranges; | 36 | mod folding_ranges; |
@@ -373,7 +373,7 @@ impl Analysis { | |||
373 | &self, | 373 | &self, |
374 | position: FilePosition, | 374 | position: FilePosition, |
375 | ) -> Cancelable<Option<RangeInfo<Vec<NavigationTarget>>>> { | 375 | ) -> Cancelable<Option<RangeInfo<Vec<NavigationTarget>>>> { |
376 | self.with_db(|db| impls::goto_implementation(db, position)) | 376 | self.with_db(|db| goto_implementation::goto_implementation(db, position)) |
377 | } | 377 | } |
378 | 378 | ||
379 | /// Returns the type definitions for the symbol at `position`. | 379 | /// Returns the type definitions for the symbol at `position`. |
diff --git a/crates/ra_ide/src/matching_brace.rs b/crates/ra_ide/src/matching_brace.rs index b85348706..407a9636d 100644 --- a/crates/ra_ide/src/matching_brace.rs +++ b/crates/ra_ide/src/matching_brace.rs | |||
@@ -1,7 +1,16 @@ | |||
1 | //! FIXME: write short doc here | ||
2 | |||
3 | use ra_syntax::{ast::AstNode, SourceFile, SyntaxKind, TextSize, T}; | 1 | use ra_syntax::{ast::AstNode, SourceFile, SyntaxKind, TextSize, T}; |
4 | 2 | ||
3 | // Feature: Matching Brace | ||
4 | // | ||
5 | // If the cursor is on any brace (`<>(){}[]`) which is a part of a brace-pair, | ||
6 | // moves cursor to the matching brace. It uses the actual parser to determine | ||
7 | // braces, so it won't confuse generics with comparisons. | ||
8 | // | ||
9 | // |=== | ||
10 | // | Editor | Action Name | ||
11 | // | ||
12 | // | VS Code | **Rust Analyzer: Find matching brace** | ||
13 | // |=== | ||
5 | pub fn matching_brace(file: &SourceFile, offset: TextSize) -> Option<TextSize> { | 14 | pub fn matching_brace(file: &SourceFile, offset: TextSize) -> Option<TextSize> { |
6 | const BRACES: &[SyntaxKind] = | 15 | const BRACES: &[SyntaxKind] = |
7 | &[T!['{'], T!['}'], T!['['], T![']'], T!['('], T![')'], T![<], T![>]]; | 16 | &[T!['{'], T!['}'], T!['['], T![']'], T!['('], T![')'], T![<], T![>]]; |
diff --git a/crates/ra_ide/src/parent_module.rs b/crates/ra_ide/src/parent_module.rs index a083fb1eb..fa1535da5 100644 --- a/crates/ra_ide/src/parent_module.rs +++ b/crates/ra_ide/src/parent_module.rs | |||
@@ -1,5 +1,3 @@ | |||
1 | //! FIXME: write short doc here | ||
2 | |||
3 | use hir::Semantics; | 1 | use hir::Semantics; |
4 | use ra_db::{CrateId, FileId, FilePosition}; | 2 | use ra_db::{CrateId, FileId, FilePosition}; |
5 | use ra_ide_db::RootDatabase; | 3 | use ra_ide_db::RootDatabase; |
@@ -11,6 +9,16 @@ use test_utils::mark; | |||
11 | 9 | ||
12 | use crate::NavigationTarget; | 10 | use crate::NavigationTarget; |
13 | 11 | ||
12 | // Feature: Parent Module | ||
13 | // | ||
14 | // Navigates to the parent module of the current module. | ||
15 | // | ||
16 | // |=== | ||
17 | // | Editor | Action Name | ||
18 | // | ||
19 | // | VS Code | **Rust Analyzer: Locate parent module** | ||
20 | // |=== | ||
21 | |||
14 | /// This returns `Vec` because a module may be included from several places. We | 22 | /// This returns `Vec` because a module may be included from several places. We |
15 | /// don't handle this case yet though, so the Vec has length at most one. | 23 | /// don't handle this case yet though, so the Vec has length at most one. |
16 | pub(crate) fn parent_module(db: &RootDatabase, position: FilePosition) -> Vec<NavigationTarget> { | 24 | pub(crate) fn parent_module(db: &RootDatabase, position: FilePosition) -> Vec<NavigationTarget> { |
diff --git a/crates/ra_ide/src/references.rs b/crates/ra_ide/src/references.rs index 96444bf6a..bb40d2043 100644 --- a/crates/ra_ide/src/references.rs +++ b/crates/ra_ide/src/references.rs | |||
@@ -615,6 +615,33 @@ mod tests { | |||
615 | ); | 615 | ); |
616 | } | 616 | } |
617 | 617 | ||
618 | #[test] | ||
619 | fn test_find_all_refs_nested_module() { | ||
620 | let code = r#" | ||
621 | //- /lib.rs | ||
622 | mod foo { | ||
623 | mod bar; | ||
624 | } | ||
625 | |||
626 | fn f<|>() {} | ||
627 | |||
628 | //- /foo/bar.rs | ||
629 | use crate::f; | ||
630 | |||
631 | fn g() { | ||
632 | f(); | ||
633 | } | ||
634 | "#; | ||
635 | |||
636 | let (analysis, pos) = analysis_and_position(code); | ||
637 | let refs = analysis.find_all_refs(pos, None).unwrap().unwrap(); | ||
638 | check_result( | ||
639 | refs, | ||
640 | "f FN_DEF FileId(1) 25..34 28..29 Other", | ||
641 | &["FileId(2) 11..12 Other", "FileId(2) 27..28 StructLiteral"], | ||
642 | ); | ||
643 | } | ||
644 | |||
618 | fn get_all_refs(text: &str) -> ReferenceSearchResult { | 645 | fn get_all_refs(text: &str) -> ReferenceSearchResult { |
619 | let (analysis, position) = single_file_with_position(text); | 646 | let (analysis, position) = single_file_with_position(text); |
620 | analysis.find_all_refs(position, None).unwrap().unwrap() | 647 | analysis.find_all_refs(position, None).unwrap().unwrap() |
diff --git a/crates/ra_ide/src/runnables.rs b/crates/ra_ide/src/runnables.rs index 6e7e47199..f32ce0d22 100644 --- a/crates/ra_ide/src/runnables.rs +++ b/crates/ra_ide/src/runnables.rs | |||
@@ -1,21 +1,19 @@ | |||
1 | //! FIXME: write short doc here | 1 | use std::fmt; |
2 | 2 | ||
3 | use hir::{AsAssocItem, Attrs, HirFileId, InFile, Semantics}; | 3 | use hir::{AsAssocItem, Attrs, HirFileId, InFile, Semantics}; |
4 | use itertools::Itertools; | 4 | use itertools::Itertools; |
5 | use ra_cfg::CfgExpr; | ||
5 | use ra_ide_db::RootDatabase; | 6 | use ra_ide_db::RootDatabase; |
6 | use ra_syntax::{ | 7 | use ra_syntax::{ |
7 | ast::{self, AstNode, AttrsOwner, ModuleItemOwner, NameOwner}, | 8 | ast::{self, AstNode, AttrsOwner, DocCommentsOwner, ModuleItemOwner, NameOwner}, |
8 | match_ast, SyntaxNode, TextRange, | 9 | match_ast, SyntaxNode, |
9 | }; | 10 | }; |
10 | 11 | ||
11 | use crate::FileId; | 12 | use crate::{display::ToNav, FileId, NavigationTarget}; |
12 | use ast::DocCommentsOwner; | ||
13 | use ra_cfg::CfgExpr; | ||
14 | use std::fmt::Display; | ||
15 | 13 | ||
16 | #[derive(Debug)] | 14 | #[derive(Debug)] |
17 | pub struct Runnable { | 15 | pub struct Runnable { |
18 | pub range: TextRange, | 16 | pub nav: NavigationTarget, |
19 | pub kind: RunnableKind, | 17 | pub kind: RunnableKind, |
20 | pub cfg_exprs: Vec<CfgExpr>, | 18 | pub cfg_exprs: Vec<CfgExpr>, |
21 | } | 19 | } |
@@ -26,8 +24,8 @@ pub enum TestId { | |||
26 | Path(String), | 24 | Path(String), |
27 | } | 25 | } |
28 | 26 | ||
29 | impl Display for TestId { | 27 | impl fmt::Display for TestId { |
30 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { | 28 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
31 | match self { | 29 | match self { |
32 | TestId::Name(name) => write!(f, "{}", name), | 30 | TestId::Name(name) => write!(f, "{}", name), |
33 | TestId::Path(path) => write!(f, "{}", path), | 31 | TestId::Path(path) => write!(f, "{}", path), |
@@ -44,6 +42,17 @@ pub enum RunnableKind { | |||
44 | Bin, | 42 | Bin, |
45 | } | 43 | } |
46 | 44 | ||
45 | // Feature: Run | ||
46 | // | ||
47 | // Shows a popup suggesting to run a test/benchmark/binary **at the current cursor | ||
48 | // location**. Super useful for repeatedly running just a single test. Do bind this | ||
49 | // to a shortcut! | ||
50 | // | ||
51 | // |=== | ||
52 | // | Editor | Action Name | ||
53 | // | ||
54 | // | VS Code | **Rust Analyzer: Run** | ||
55 | // |=== | ||
47 | pub(crate) fn runnables(db: &RootDatabase, file_id: FileId) -> Vec<Runnable> { | 56 | pub(crate) fn runnables(db: &RootDatabase, file_id: FileId) -> Vec<Runnable> { |
48 | let sema = Semantics::new(db); | 57 | let sema = Semantics::new(db); |
49 | let source_file = sema.parse(file_id); | 58 | let source_file = sema.parse(file_id); |
@@ -122,7 +131,8 @@ fn runnable_fn( | |||
122 | let cfg_exprs = | 131 | let cfg_exprs = |
123 | attrs.by_key("cfg").tt_values().map(|subtree| ra_cfg::parse_cfg(subtree)).collect(); | 132 | attrs.by_key("cfg").tt_values().map(|subtree| ra_cfg::parse_cfg(subtree)).collect(); |
124 | 133 | ||
125 | Some(Runnable { range: fn_def.syntax().text_range(), kind, cfg_exprs }) | 134 | let nav = NavigationTarget::from_named(sema.db, InFile::new(file_id.into(), &fn_def)); |
135 | Some(Runnable { nav, kind, cfg_exprs }) | ||
126 | } | 136 | } |
127 | 137 | ||
128 | #[derive(Debug)] | 138 | #[derive(Debug)] |
@@ -174,7 +184,6 @@ fn runnable_mod( | |||
174 | if !has_test_function { | 184 | if !has_test_function { |
175 | return None; | 185 | return None; |
176 | } | 186 | } |
177 | let range = module.syntax().text_range(); | ||
178 | let module_def = sema.to_def(&module)?; | 187 | let module_def = sema.to_def(&module)?; |
179 | 188 | ||
180 | let path = module_def | 189 | let path = module_def |
@@ -188,7 +197,8 @@ fn runnable_mod( | |||
188 | let cfg_exprs = | 197 | let cfg_exprs = |
189 | attrs.by_key("cfg").tt_values().map(|subtree| ra_cfg::parse_cfg(subtree)).collect(); | 198 | attrs.by_key("cfg").tt_values().map(|subtree| ra_cfg::parse_cfg(subtree)).collect(); |
190 | 199 | ||
191 | Some(Runnable { range, kind: RunnableKind::TestMod { path }, cfg_exprs }) | 200 | let nav = module_def.to_nav(sema.db); |
201 | Some(Runnable { nav, kind: RunnableKind::TestMod { path }, cfg_exprs }) | ||
192 | } | 202 | } |
193 | 203 | ||
194 | #[cfg(test)] | 204 | #[cfg(test)] |
@@ -218,12 +228,38 @@ mod tests { | |||
218 | @r###" | 228 | @r###" |
219 | [ | 229 | [ |
220 | Runnable { | 230 | Runnable { |
221 | range: 1..21, | 231 | nav: NavigationTarget { |
232 | file_id: FileId( | ||
233 | 1, | ||
234 | ), | ||
235 | full_range: 1..21, | ||
236 | name: "main", | ||
237 | kind: FN_DEF, | ||
238 | focus_range: Some( | ||
239 | 12..16, | ||
240 | ), | ||
241 | container_name: None, | ||
242 | description: None, | ||
243 | docs: None, | ||
244 | }, | ||
222 | kind: Bin, | 245 | kind: Bin, |
223 | cfg_exprs: [], | 246 | cfg_exprs: [], |
224 | }, | 247 | }, |
225 | Runnable { | 248 | Runnable { |
226 | range: 22..46, | 249 | nav: NavigationTarget { |
250 | file_id: FileId( | ||
251 | 1, | ||
252 | ), | ||
253 | full_range: 22..46, | ||
254 | name: "test_foo", | ||
255 | kind: FN_DEF, | ||
256 | focus_range: Some( | ||
257 | 33..41, | ||
258 | ), | ||
259 | container_name: None, | ||
260 | description: None, | ||
261 | docs: None, | ||
262 | }, | ||
227 | kind: Test { | 263 | kind: Test { |
228 | test_id: Path( | 264 | test_id: Path( |
229 | "test_foo", | 265 | "test_foo", |
@@ -235,7 +271,20 @@ mod tests { | |||
235 | cfg_exprs: [], | 271 | cfg_exprs: [], |
236 | }, | 272 | }, |
237 | Runnable { | 273 | Runnable { |
238 | range: 47..81, | 274 | nav: NavigationTarget { |
275 | file_id: FileId( | ||
276 | 1, | ||
277 | ), | ||
278 | full_range: 47..81, | ||
279 | name: "test_foo", | ||
280 | kind: FN_DEF, | ||
281 | focus_range: Some( | ||
282 | 68..76, | ||
283 | ), | ||
284 | container_name: None, | ||
285 | description: None, | ||
286 | docs: None, | ||
287 | }, | ||
239 | kind: Test { | 288 | kind: Test { |
240 | test_id: Path( | 289 | test_id: Path( |
241 | "test_foo", | 290 | "test_foo", |
@@ -270,12 +319,38 @@ mod tests { | |||
270 | @r###" | 319 | @r###" |
271 | [ | 320 | [ |
272 | Runnable { | 321 | Runnable { |
273 | range: 1..21, | 322 | nav: NavigationTarget { |
323 | file_id: FileId( | ||
324 | 1, | ||
325 | ), | ||
326 | full_range: 1..21, | ||
327 | name: "main", | ||
328 | kind: FN_DEF, | ||
329 | focus_range: Some( | ||
330 | 12..16, | ||
331 | ), | ||
332 | container_name: None, | ||
333 | description: None, | ||
334 | docs: None, | ||
335 | }, | ||
274 | kind: Bin, | 336 | kind: Bin, |
275 | cfg_exprs: [], | 337 | cfg_exprs: [], |
276 | }, | 338 | }, |
277 | Runnable { | 339 | Runnable { |
278 | range: 22..64, | 340 | nav: NavigationTarget { |
341 | file_id: FileId( | ||
342 | 1, | ||
343 | ), | ||
344 | full_range: 22..64, | ||
345 | name: "foo", | ||
346 | kind: FN_DEF, | ||
347 | focus_range: Some( | ||
348 | 56..59, | ||
349 | ), | ||
350 | container_name: None, | ||
351 | description: None, | ||
352 | docs: None, | ||
353 | }, | ||
279 | kind: DocTest { | 354 | kind: DocTest { |
280 | test_id: Path( | 355 | test_id: Path( |
281 | "foo", | 356 | "foo", |
@@ -310,12 +385,38 @@ mod tests { | |||
310 | @r###" | 385 | @r###" |
311 | [ | 386 | [ |
312 | Runnable { | 387 | Runnable { |
313 | range: 1..21, | 388 | nav: NavigationTarget { |
389 | file_id: FileId( | ||
390 | 1, | ||
391 | ), | ||
392 | full_range: 1..21, | ||
393 | name: "main", | ||
394 | kind: FN_DEF, | ||
395 | focus_range: Some( | ||
396 | 12..16, | ||
397 | ), | ||
398 | container_name: None, | ||
399 | description: None, | ||
400 | docs: None, | ||
401 | }, | ||
314 | kind: Bin, | 402 | kind: Bin, |
315 | cfg_exprs: [], | 403 | cfg_exprs: [], |
316 | }, | 404 | }, |
317 | Runnable { | 405 | Runnable { |
318 | range: 51..105, | 406 | nav: NavigationTarget { |
407 | file_id: FileId( | ||
408 | 1, | ||
409 | ), | ||
410 | full_range: 51..105, | ||
411 | name: "foo", | ||
412 | kind: FN_DEF, | ||
413 | focus_range: Some( | ||
414 | 97..100, | ||
415 | ), | ||
416 | container_name: None, | ||
417 | description: None, | ||
418 | docs: None, | ||
419 | }, | ||
319 | kind: DocTest { | 420 | kind: DocTest { |
320 | test_id: Path( | 421 | test_id: Path( |
321 | "Data::foo", | 422 | "Data::foo", |
@@ -345,14 +446,40 @@ mod tests { | |||
345 | @r###" | 446 | @r###" |
346 | [ | 447 | [ |
347 | Runnable { | 448 | Runnable { |
348 | range: 1..59, | 449 | nav: NavigationTarget { |
450 | file_id: FileId( | ||
451 | 1, | ||
452 | ), | ||
453 | full_range: 1..59, | ||
454 | name: "test_mod", | ||
455 | kind: MODULE, | ||
456 | focus_range: Some( | ||
457 | 13..21, | ||
458 | ), | ||
459 | container_name: None, | ||
460 | description: None, | ||
461 | docs: None, | ||
462 | }, | ||
349 | kind: TestMod { | 463 | kind: TestMod { |
350 | path: "test_mod", | 464 | path: "test_mod", |
351 | }, | 465 | }, |
352 | cfg_exprs: [], | 466 | cfg_exprs: [], |
353 | }, | 467 | }, |
354 | Runnable { | 468 | Runnable { |
355 | range: 28..57, | 469 | nav: NavigationTarget { |
470 | file_id: FileId( | ||
471 | 1, | ||
472 | ), | ||
473 | full_range: 28..57, | ||
474 | name: "test_foo1", | ||
475 | kind: FN_DEF, | ||
476 | focus_range: Some( | ||
477 | 43..52, | ||
478 | ), | ||
479 | container_name: None, | ||
480 | description: None, | ||
481 | docs: None, | ||
482 | }, | ||
356 | kind: Test { | 483 | kind: Test { |
357 | test_id: Path( | 484 | test_id: Path( |
358 | "test_mod::test_foo1", | 485 | "test_mod::test_foo1", |
@@ -387,14 +514,40 @@ mod tests { | |||
387 | @r###" | 514 | @r###" |
388 | [ | 515 | [ |
389 | Runnable { | 516 | Runnable { |
390 | range: 23..85, | 517 | nav: NavigationTarget { |
518 | file_id: FileId( | ||
519 | 1, | ||
520 | ), | ||
521 | full_range: 23..85, | ||
522 | name: "test_mod", | ||
523 | kind: MODULE, | ||
524 | focus_range: Some( | ||
525 | 27..35, | ||
526 | ), | ||
527 | container_name: None, | ||
528 | description: None, | ||
529 | docs: None, | ||
530 | }, | ||
391 | kind: TestMod { | 531 | kind: TestMod { |
392 | path: "foo::test_mod", | 532 | path: "foo::test_mod", |
393 | }, | 533 | }, |
394 | cfg_exprs: [], | 534 | cfg_exprs: [], |
395 | }, | 535 | }, |
396 | Runnable { | 536 | Runnable { |
397 | range: 46..79, | 537 | nav: NavigationTarget { |
538 | file_id: FileId( | ||
539 | 1, | ||
540 | ), | ||
541 | full_range: 46..79, | ||
542 | name: "test_foo1", | ||
543 | kind: FN_DEF, | ||
544 | focus_range: Some( | ||
545 | 65..74, | ||
546 | ), | ||
547 | container_name: None, | ||
548 | description: None, | ||
549 | docs: None, | ||
550 | }, | ||
398 | kind: Test { | 551 | kind: Test { |
399 | test_id: Path( | 552 | test_id: Path( |
400 | "foo::test_mod::test_foo1", | 553 | "foo::test_mod::test_foo1", |
@@ -431,14 +584,40 @@ mod tests { | |||
431 | @r###" | 584 | @r###" |
432 | [ | 585 | [ |
433 | Runnable { | 586 | Runnable { |
434 | range: 41..115, | 587 | nav: NavigationTarget { |
588 | file_id: FileId( | ||
589 | 1, | ||
590 | ), | ||
591 | full_range: 41..115, | ||
592 | name: "test_mod", | ||
593 | kind: MODULE, | ||
594 | focus_range: Some( | ||
595 | 45..53, | ||
596 | ), | ||
597 | container_name: None, | ||
598 | description: None, | ||
599 | docs: None, | ||
600 | }, | ||
435 | kind: TestMod { | 601 | kind: TestMod { |
436 | path: "foo::bar::test_mod", | 602 | path: "foo::bar::test_mod", |
437 | }, | 603 | }, |
438 | cfg_exprs: [], | 604 | cfg_exprs: [], |
439 | }, | 605 | }, |
440 | Runnable { | 606 | Runnable { |
441 | range: 68..105, | 607 | nav: NavigationTarget { |
608 | file_id: FileId( | ||
609 | 1, | ||
610 | ), | ||
611 | full_range: 68..105, | ||
612 | name: "test_foo1", | ||
613 | kind: FN_DEF, | ||
614 | focus_range: Some( | ||
615 | 91..100, | ||
616 | ), | ||
617 | container_name: None, | ||
618 | description: None, | ||
619 | docs: None, | ||
620 | }, | ||
442 | kind: Test { | 621 | kind: Test { |
443 | test_id: Path( | 622 | test_id: Path( |
444 | "foo::bar::test_mod::test_foo1", | 623 | "foo::bar::test_mod::test_foo1", |
@@ -470,7 +649,20 @@ mod tests { | |||
470 | @r###" | 649 | @r###" |
471 | [ | 650 | [ |
472 | Runnable { | 651 | Runnable { |
473 | range: 1..58, | 652 | nav: NavigationTarget { |
653 | file_id: FileId( | ||
654 | 1, | ||
655 | ), | ||
656 | full_range: 1..58, | ||
657 | name: "test_foo1", | ||
658 | kind: FN_DEF, | ||
659 | focus_range: Some( | ||
660 | 44..53, | ||
661 | ), | ||
662 | container_name: None, | ||
663 | description: None, | ||
664 | docs: None, | ||
665 | }, | ||
474 | kind: Test { | 666 | kind: Test { |
475 | test_id: Path( | 667 | test_id: Path( |
476 | "test_foo1", | 668 | "test_foo1", |
@@ -507,7 +699,20 @@ mod tests { | |||
507 | @r###" | 699 | @r###" |
508 | [ | 700 | [ |
509 | Runnable { | 701 | Runnable { |
510 | range: 1..80, | 702 | nav: NavigationTarget { |
703 | file_id: FileId( | ||
704 | 1, | ||
705 | ), | ||
706 | full_range: 1..80, | ||
707 | name: "test_foo1", | ||
708 | kind: FN_DEF, | ||
709 | focus_range: Some( | ||
710 | 66..75, | ||
711 | ), | ||
712 | container_name: None, | ||
713 | description: None, | ||
714 | docs: None, | ||
715 | }, | ||
511 | kind: Test { | 716 | kind: Test { |
512 | test_id: Path( | 717 | test_id: Path( |
513 | "test_foo1", | 718 | "test_foo1", |
diff --git a/crates/ra_ide/src/snapshots/highlight_injection.html b/crates/ra_ide/src/snapshots/highlight_injection.html index 68fc589bc..fcdc98201 100644 --- a/crates/ra_ide/src/snapshots/highlight_injection.html +++ b/crates/ra_ide/src/snapshots/highlight_injection.html | |||
@@ -10,6 +10,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd | |||
10 | .string_literal { color: #CC9393; } | 10 | .string_literal { color: #CC9393; } |
11 | .field { color: #94BFF3; } | 11 | .field { color: #94BFF3; } |
12 | .function { color: #93E0E3; } | 12 | .function { color: #93E0E3; } |
13 | .operator.unsafe { color: #E28C14; } | ||
13 | .parameter { color: #94BFF3; } | 14 | .parameter { color: #94BFF3; } |
14 | .text { color: #DCDCCC; } | 15 | .text { color: #DCDCCC; } |
15 | .type { color: #7CB8BB; } | 16 | .type { color: #7CB8BB; } |
diff --git a/crates/ra_ide/src/snapshots/highlight_strings.html b/crates/ra_ide/src/snapshots/highlight_strings.html index 326744361..e97192b61 100644 --- a/crates/ra_ide/src/snapshots/highlight_strings.html +++ b/crates/ra_ide/src/snapshots/highlight_strings.html | |||
@@ -10,6 +10,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd | |||
10 | .string_literal { color: #CC9393; } | 10 | .string_literal { color: #CC9393; } |
11 | .field { color: #94BFF3; } | 11 | .field { color: #94BFF3; } |
12 | .function { color: #93E0E3; } | 12 | .function { color: #93E0E3; } |
13 | .operator.unsafe { color: #E28C14; } | ||
13 | .parameter { color: #94BFF3; } | 14 | .parameter { color: #94BFF3; } |
14 | .text { color: #DCDCCC; } | 15 | .text { color: #DCDCCC; } |
15 | .type { color: #7CB8BB; } | 16 | .type { color: #7CB8BB; } |
@@ -52,6 +53,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd | |||
52 | <span class="macro">println!</span>(<span class="string_literal">"</span><span class="format_specifier">{</span><span class="variable">argument</span><span class="format_specifier">}</span><span class="string_literal">"</span>, argument = <span class="string_literal">"test"</span>); <span class="comment">// => "test"</span> | 53 | <span class="macro">println!</span>(<span class="string_literal">"</span><span class="format_specifier">{</span><span class="variable">argument</span><span class="format_specifier">}</span><span class="string_literal">"</span>, argument = <span class="string_literal">"test"</span>); <span class="comment">// => "test"</span> |
53 | <span class="macro">println!</span>(<span class="string_literal">"</span><span class="format_specifier">{</span><span class="variable">name</span><span class="format_specifier">}</span><span class="string_literal"> </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal">"</span>, <span class="numeric_literal">1</span>, name = <span class="numeric_literal">2</span>); <span class="comment">// => "2 1"</span> | 54 | <span class="macro">println!</span>(<span class="string_literal">"</span><span class="format_specifier">{</span><span class="variable">name</span><span class="format_specifier">}</span><span class="string_literal"> </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal">"</span>, <span class="numeric_literal">1</span>, name = <span class="numeric_literal">2</span>); <span class="comment">// => "2 1"</span> |
54 | <span class="macro">println!</span>(<span class="string_literal">"</span><span class="format_specifier">{</span><span class="variable">a</span><span class="format_specifier">}</span><span class="string_literal"> </span><span class="format_specifier">{</span><span class="variable">c</span><span class="format_specifier">}</span><span class="string_literal"> </span><span class="format_specifier">{</span><span class="variable">b</span><span class="format_specifier">}</span><span class="string_literal">"</span>, a=<span class="string_literal">"a"</span>, b=<span class="char_literal">'b'</span>, c=<span class="numeric_literal">3</span>); <span class="comment">// => "a 3 b"</span> | 55 | <span class="macro">println!</span>(<span class="string_literal">"</span><span class="format_specifier">{</span><span class="variable">a</span><span class="format_specifier">}</span><span class="string_literal"> </span><span class="format_specifier">{</span><span class="variable">c</span><span class="format_specifier">}</span><span class="string_literal"> </span><span class="format_specifier">{</span><span class="variable">b</span><span class="format_specifier">}</span><span class="string_literal">"</span>, a=<span class="string_literal">"a"</span>, b=<span class="char_literal">'b'</span>, c=<span class="numeric_literal">3</span>); <span class="comment">// => "a 3 b"</span> |
56 | <span class="macro">println!</span>(<span class="string_literal">"{{</span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal">}}"</span>, <span class="numeric_literal">2</span>); <span class="comment">// => "{2}"</span> | ||
55 | <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="numeric_literal">5</span><span class="format_specifier">}</span><span class="string_literal">!"</span>, <span class="string_literal">"x"</span>); | 57 | <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="numeric_literal">5</span><span class="format_specifier">}</span><span class="string_literal">!"</span>, <span class="string_literal">"x"</span>); |
56 | <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="numeric_literal">1</span><span class="format_specifier">$</span><span class="format_specifier">}</span><span class="string_literal">!"</span>, <span class="string_literal">"x"</span>, <span class="numeric_literal">5</span>); | 58 | <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="numeric_literal">1</span><span class="format_specifier">$</span><span class="format_specifier">}</span><span class="string_literal">!"</span>, <span class="string_literal">"x"</span>, <span class="numeric_literal">5</span>); |
57 | <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="numeric_literal">1</span><span class="format_specifier">:</span><span class="numeric_literal">0</span><span class="format_specifier">$</span><span class="format_specifier">}</span><span class="string_literal">!"</span>, <span class="numeric_literal">5</span>, <span class="string_literal">"x"</span>); | 59 | <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="numeric_literal">1</span><span class="format_specifier">:</span><span class="numeric_literal">0</span><span class="format_specifier">$</span><span class="format_specifier">}</span><span class="string_literal">!"</span>, <span class="numeric_literal">5</span>, <span class="string_literal">"x"</span>); |
diff --git a/crates/ra_ide/src/snapshots/highlight_unsafe.html b/crates/ra_ide/src/snapshots/highlight_unsafe.html new file mode 100644 index 000000000..17ffc727c --- /dev/null +++ b/crates/ra_ide/src/snapshots/highlight_unsafe.html | |||
@@ -0,0 +1,48 @@ | |||
1 | |||
2 | <style> | ||
3 | body { margin: 0; } | ||
4 | pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padding: 0.4em; } | ||
5 | |||
6 | .lifetime { color: #DFAF8F; font-style: italic; } | ||
7 | .comment { color: #7F9F7F; } | ||
8 | .struct, .enum { color: #7CB8BB; } | ||
9 | .enum_variant { color: #BDE0F3; } | ||
10 | .string_literal { color: #CC9393; } | ||
11 | .field { color: #94BFF3; } | ||
12 | .function { color: #93E0E3; } | ||
13 | .operator.unsafe { color: #E28C14; } | ||
14 | .parameter { color: #94BFF3; } | ||
15 | .text { color: #DCDCCC; } | ||
16 | .type { color: #7CB8BB; } | ||
17 | .builtin_type { color: #8CD0D3; } | ||
18 | .type_param { color: #DFAF8F; } | ||
19 | .attribute { color: #94BFF3; } | ||
20 | .numeric_literal { color: #BFEBBF; } | ||
21 | .bool_literal { color: #BFE6EB; } | ||
22 | .macro { color: #94BFF3; } | ||
23 | .module { color: #AFD8AF; } | ||
24 | .variable { color: #DCDCCC; } | ||
25 | .format_specifier { color: #CC696B; } | ||
26 | .mutable { text-decoration: underline; } | ||
27 | |||
28 | .keyword { color: #F0DFAF; font-weight: bold; } | ||
29 | .keyword.unsafe { color: #BC8383; font-weight: bold; } | ||
30 | .control { font-style: italic; } | ||
31 | </style> | ||
32 | <pre><code><span class="keyword unsafe">unsafe</span> <span class="keyword">fn</span> <span class="function declaration unsafe">unsafe_fn</span>() {} | ||
33 | |||
34 | <span class="keyword">struct</span> <span class="struct declaration">HasUnsafeFn</span>; | ||
35 | |||
36 | <span class="keyword">impl</span> <span class="struct">HasUnsafeFn</span> { | ||
37 | <span class="keyword unsafe">unsafe</span> <span class="keyword">fn</span> <span class="function declaration unsafe">unsafe_method</span>(&<span class="self_keyword">self</span>) {} | ||
38 | } | ||
39 | |||
40 | <span class="keyword">fn</span> <span class="function declaration">main</span>() { | ||
41 | <span class="keyword">let</span> <span class="variable declaration">x</span> = &<span class="numeric_literal">5</span> <span class="keyword">as</span> *<span class="keyword">const</span> <span class="builtin_type">usize</span>; | ||
42 | <span class="keyword unsafe">unsafe</span> { | ||
43 | <span class="function unsafe">unsafe_fn</span>(); | ||
44 | <span class="struct">HasUnsafeFn</span>.<span class="function unsafe">unsafe_method</span>(); | ||
45 | <span class="keyword">let</span> <span class="variable declaration">y</span> = <span class="operator unsafe">*</span><span class="variable">x</span>; | ||
46 | <span class="keyword">let</span> <span class="variable declaration">z</span> = -<span class="variable">x</span>; | ||
47 | } | ||
48 | }</code></pre> \ No newline at end of file | ||
diff --git a/crates/ra_ide/src/snapshots/highlighting.html b/crates/ra_ide/src/snapshots/highlighting.html index 352e35095..42c5f3e55 100644 --- a/crates/ra_ide/src/snapshots/highlighting.html +++ b/crates/ra_ide/src/snapshots/highlighting.html | |||
@@ -10,6 +10,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd | |||
10 | .string_literal { color: #CC9393; } | 10 | .string_literal { color: #CC9393; } |
11 | .field { color: #94BFF3; } | 11 | .field { color: #94BFF3; } |
12 | .function { color: #93E0E3; } | 12 | .function { color: #93E0E3; } |
13 | .operator.unsafe { color: #E28C14; } | ||
13 | .parameter { color: #94BFF3; } | 14 | .parameter { color: #94BFF3; } |
14 | .text { color: #DCDCCC; } | 15 | .text { color: #DCDCCC; } |
15 | .type { color: #7CB8BB; } | 16 | .type { color: #7CB8BB; } |
diff --git a/crates/ra_ide/src/snapshots/rainbow_highlighting.html b/crates/ra_ide/src/snapshots/rainbow_highlighting.html index 2a0294f71..2dd61d20d 100644 --- a/crates/ra_ide/src/snapshots/rainbow_highlighting.html +++ b/crates/ra_ide/src/snapshots/rainbow_highlighting.html | |||
@@ -10,6 +10,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd | |||
10 | .string_literal { color: #CC9393; } | 10 | .string_literal { color: #CC9393; } |
11 | .field { color: #94BFF3; } | 11 | .field { color: #94BFF3; } |
12 | .function { color: #93E0E3; } | 12 | .function { color: #93E0E3; } |
13 | .operator.unsafe { color: #E28C14; } | ||
13 | .parameter { color: #94BFF3; } | 14 | .parameter { color: #94BFF3; } |
14 | .text { color: #DCDCCC; } | 15 | .text { color: #DCDCCC; } |
15 | .type { color: #7CB8BB; } | 16 | .type { color: #7CB8BB; } |
diff --git a/crates/ra_ide/src/ssr.rs b/crates/ra_ide/src/ssr.rs index 130d3b4c3..93e9aee1d 100644 --- a/crates/ra_ide/src/ssr.rs +++ b/crates/ra_ide/src/ssr.rs | |||
@@ -1,5 +1,3 @@ | |||
1 | //! structural search replace | ||
2 | |||
3 | use std::{collections::HashMap, iter::once, str::FromStr}; | 1 | use std::{collections::HashMap, iter::once, str::FromStr}; |
4 | 2 | ||
5 | use ra_db::{SourceDatabase, SourceDatabaseExt}; | 3 | use ra_db::{SourceDatabase, SourceDatabaseExt}; |
@@ -25,6 +23,28 @@ impl std::fmt::Display for SsrError { | |||
25 | 23 | ||
26 | impl std::error::Error for SsrError {} | 24 | impl std::error::Error for SsrError {} |
27 | 25 | ||
26 | // Feature: Structural Seach and Replace | ||
27 | // | ||
28 | // Search and replace with named wildcards that will match any expression. | ||
29 | // The syntax for a structural search replace command is `<search_pattern> ==>> <replace_pattern>`. | ||
30 | // A `$<name>:expr` placeholder in the search pattern will match any expression and `$<name>` will reference it in the replacement. | ||
31 | // Available via the command `rust-analyzer.ssr`. | ||
32 | // | ||
33 | // ```rust | ||
34 | // // Using structural search replace command [foo($a:expr, $b:expr) ==>> ($a).foo($b)] | ||
35 | // | ||
36 | // // BEFORE | ||
37 | // String::from(foo(y + 5, z)) | ||
38 | // | ||
39 | // // AFTER | ||
40 | // String::from((y + 5).foo(z)) | ||
41 | // ``` | ||
42 | // | ||
43 | // |=== | ||
44 | // | Editor | Action Name | ||
45 | // | ||
46 | // | VS Code | **Rust Analyzer: Structural Search Replace** | ||
47 | // |=== | ||
28 | pub fn parse_search_replace( | 48 | pub fn parse_search_replace( |
29 | query: &str, | 49 | query: &str, |
30 | parse_only: bool, | 50 | parse_only: bool, |
diff --git a/crates/ra_ide/src/status.rs b/crates/ra_ide/src/status.rs index 30eb5c995..5b7992920 100644 --- a/crates/ra_ide/src/status.rs +++ b/crates/ra_ide/src/status.rs | |||
@@ -1,5 +1,3 @@ | |||
1 | //! FIXME: write short doc here | ||
2 | |||
3 | use std::{fmt, iter::FromIterator, sync::Arc}; | 1 | use std::{fmt, iter::FromIterator, sync::Arc}; |
4 | 2 | ||
5 | use hir::MacroFile; | 3 | use hir::MacroFile; |
@@ -26,6 +24,15 @@ fn macro_syntax_tree_stats(db: &RootDatabase) -> SyntaxTreeStats { | |||
26 | db.query(hir::db::ParseMacroQuery).entries::<SyntaxTreeStats>() | 24 | db.query(hir::db::ParseMacroQuery).entries::<SyntaxTreeStats>() |
27 | } | 25 | } |
28 | 26 | ||
27 | // Feature: Status | ||
28 | // | ||
29 | // Shows internal statistic about memory usage of rust-analyzer. | ||
30 | // | ||
31 | // |=== | ||
32 | // | Editor | Action Name | ||
33 | // | ||
34 | // | VS Code | **Rust Analyzer: Status** | ||
35 | // |=== | ||
29 | pub(crate) fn status(db: &RootDatabase) -> String { | 36 | pub(crate) fn status(db: &RootDatabase) -> String { |
30 | let files_stats = db.query(FileTextQuery).entries::<FilesStats>(); | 37 | let files_stats = db.query(FileTextQuery).entries::<FilesStats>(); |
31 | let syntax_tree_stats = syntax_tree_stats(db); | 38 | let syntax_tree_stats = syntax_tree_stats(db); |
diff --git a/crates/ra_ide/src/syntax_highlighting.rs b/crates/ra_ide/src/syntax_highlighting.rs index 8a995d779..19ecd54d6 100644 --- a/crates/ra_ide/src/syntax_highlighting.rs +++ b/crates/ra_ide/src/syntax_highlighting.rs | |||
@@ -1,5 +1,3 @@ | |||
1 | //! Implements syntax highlighting. | ||
2 | |||
3 | mod tags; | 1 | mod tags; |
4 | mod html; | 2 | mod html; |
5 | #[cfg(test)] | 3 | #[cfg(test)] |
@@ -32,81 +30,15 @@ pub struct HighlightedRange { | |||
32 | pub binding_hash: Option<u64>, | 30 | pub binding_hash: Option<u64>, |
33 | } | 31 | } |
34 | 32 | ||
35 | #[derive(Debug)] | 33 | // Feature: Semantic Syntax Highlighting |
36 | struct HighlightedRangeStack { | 34 | // |
37 | stack: Vec<Vec<HighlightedRange>>, | 35 | // rust-analyzer highlights the code semantically. |
38 | } | 36 | // For example, `bar` in `foo::Bar` might be colored differently depending on whether `Bar` is an enum or a trait. |
39 | 37 | // rust-analyzer does not specify colors directly, instead it assigns tag (like `struct`) and a set of modifiers (like `declaration`) to each token. | |
40 | /// We use a stack to implement the flattening logic for the highlighted | 38 | // It's up to the client to map those to specific colors. |
41 | /// syntax ranges. | 39 | // |
42 | impl HighlightedRangeStack { | 40 | // The general rule is that a reference to an entity gets colored the same way as the entity itself. |
43 | fn new() -> Self { | 41 | // We also give special modifier for `mut` and `&mut` local variables. |
44 | Self { stack: vec![Vec::new()] } | ||
45 | } | ||
46 | |||
47 | fn push(&mut self) { | ||
48 | self.stack.push(Vec::new()); | ||
49 | } | ||
50 | |||
51 | /// Flattens the highlighted ranges. | ||
52 | /// | ||
53 | /// For example `#[cfg(feature = "foo")]` contains the nested ranges: | ||
54 | /// 1) parent-range: Attribute [0, 23) | ||
55 | /// 2) child-range: String [16, 21) | ||
56 | /// | ||
57 | /// The following code implements the flattening, for our example this results to: | ||
58 | /// `[Attribute [0, 16), String [16, 21), Attribute [21, 23)]` | ||
59 | fn pop(&mut self) { | ||
60 | let children = self.stack.pop().unwrap(); | ||
61 | let prev = self.stack.last_mut().unwrap(); | ||
62 | let needs_flattening = !children.is_empty() | ||
63 | && !prev.is_empty() | ||
64 | && prev.last().unwrap().range.contains_range(children.first().unwrap().range); | ||
65 | if !needs_flattening { | ||
66 | prev.extend(children); | ||
67 | } else { | ||
68 | let mut parent = prev.pop().unwrap(); | ||
69 | for ele in children { | ||
70 | assert!(parent.range.contains_range(ele.range)); | ||
71 | let mut cloned = parent.clone(); | ||
72 | parent.range = TextRange::new(parent.range.start(), ele.range.start()); | ||
73 | cloned.range = TextRange::new(ele.range.end(), cloned.range.end()); | ||
74 | if !parent.range.is_empty() { | ||
75 | prev.push(parent); | ||
76 | } | ||
77 | prev.push(ele); | ||
78 | parent = cloned; | ||
79 | } | ||
80 | if !parent.range.is_empty() { | ||
81 | prev.push(parent); | ||
82 | } | ||
83 | } | ||
84 | } | ||
85 | |||
86 | fn add(&mut self, range: HighlightedRange) { | ||
87 | self.stack | ||
88 | .last_mut() | ||
89 | .expect("during DFS traversal, the stack must not be empty") | ||
90 | .push(range) | ||
91 | } | ||
92 | |||
93 | fn flattened(mut self) -> Vec<HighlightedRange> { | ||
94 | assert_eq!( | ||
95 | self.stack.len(), | ||
96 | 1, | ||
97 | "after DFS traversal, the stack should only contain a single element" | ||
98 | ); | ||
99 | let mut res = self.stack.pop().unwrap(); | ||
100 | res.sort_by_key(|range| range.range.start()); | ||
101 | // Check that ranges are sorted and disjoint | ||
102 | assert!(res | ||
103 | .iter() | ||
104 | .zip(res.iter().skip(1)) | ||
105 | .all(|(left, right)| left.range.end() <= right.range.start())); | ||
106 | res | ||
107 | } | ||
108 | } | ||
109 | |||
110 | pub(crate) fn highlight( | 42 | pub(crate) fn highlight( |
111 | db: &RootDatabase, | 43 | db: &RootDatabase, |
112 | file_id: FileId, | 44 | file_id: FileId, |
@@ -291,6 +223,81 @@ pub(crate) fn highlight( | |||
291 | stack.flattened() | 223 | stack.flattened() |
292 | } | 224 | } |
293 | 225 | ||
226 | #[derive(Debug)] | ||
227 | struct HighlightedRangeStack { | ||
228 | stack: Vec<Vec<HighlightedRange>>, | ||
229 | } | ||
230 | |||
231 | /// We use a stack to implement the flattening logic for the highlighted | ||
232 | /// syntax ranges. | ||
233 | impl HighlightedRangeStack { | ||
234 | fn new() -> Self { | ||
235 | Self { stack: vec![Vec::new()] } | ||
236 | } | ||
237 | |||
238 | fn push(&mut self) { | ||
239 | self.stack.push(Vec::new()); | ||
240 | } | ||
241 | |||
242 | /// Flattens the highlighted ranges. | ||
243 | /// | ||
244 | /// For example `#[cfg(feature = "foo")]` contains the nested ranges: | ||
245 | /// 1) parent-range: Attribute [0, 23) | ||
246 | /// 2) child-range: String [16, 21) | ||
247 | /// | ||
248 | /// The following code implements the flattening, for our example this results to: | ||
249 | /// `[Attribute [0, 16), String [16, 21), Attribute [21, 23)]` | ||
250 | fn pop(&mut self) { | ||
251 | let children = self.stack.pop().unwrap(); | ||
252 | let prev = self.stack.last_mut().unwrap(); | ||
253 | let needs_flattening = !children.is_empty() | ||
254 | && !prev.is_empty() | ||
255 | && prev.last().unwrap().range.contains_range(children.first().unwrap().range); | ||
256 | if !needs_flattening { | ||
257 | prev.extend(children); | ||
258 | } else { | ||
259 | let mut parent = prev.pop().unwrap(); | ||
260 | for ele in children { | ||
261 | assert!(parent.range.contains_range(ele.range)); | ||
262 | let mut cloned = parent.clone(); | ||
263 | parent.range = TextRange::new(parent.range.start(), ele.range.start()); | ||
264 | cloned.range = TextRange::new(ele.range.end(), cloned.range.end()); | ||
265 | if !parent.range.is_empty() { | ||
266 | prev.push(parent); | ||
267 | } | ||
268 | prev.push(ele); | ||
269 | parent = cloned; | ||
270 | } | ||
271 | if !parent.range.is_empty() { | ||
272 | prev.push(parent); | ||
273 | } | ||
274 | } | ||
275 | } | ||
276 | |||
277 | fn add(&mut self, range: HighlightedRange) { | ||
278 | self.stack | ||
279 | .last_mut() | ||
280 | .expect("during DFS traversal, the stack must not be empty") | ||
281 | .push(range) | ||
282 | } | ||
283 | |||
284 | fn flattened(mut self) -> Vec<HighlightedRange> { | ||
285 | assert_eq!( | ||
286 | self.stack.len(), | ||
287 | 1, | ||
288 | "after DFS traversal, the stack should only contain a single element" | ||
289 | ); | ||
290 | let mut res = self.stack.pop().unwrap(); | ||
291 | res.sort_by_key(|range| range.range.start()); | ||
292 | // Check that ranges are sorted and disjoint | ||
293 | assert!(res | ||
294 | .iter() | ||
295 | .zip(res.iter().skip(1)) | ||
296 | .all(|(left, right)| left.range.end() <= right.range.start())); | ||
297 | res | ||
298 | } | ||
299 | } | ||
300 | |||
294 | fn highlight_format_specifier(kind: FormatSpecifier) -> Option<HighlightTag> { | 301 | fn highlight_format_specifier(kind: FormatSpecifier) -> Option<HighlightTag> { |
295 | Some(match kind { | 302 | Some(match kind { |
296 | FormatSpecifier::Open | 303 | FormatSpecifier::Open |
@@ -391,6 +398,7 @@ fn highlight_element( | |||
391 | INT_NUMBER | FLOAT_NUMBER => HighlightTag::NumericLiteral.into(), | 398 | INT_NUMBER | FLOAT_NUMBER => HighlightTag::NumericLiteral.into(), |
392 | BYTE => HighlightTag::ByteLiteral.into(), | 399 | BYTE => HighlightTag::ByteLiteral.into(), |
393 | CHAR => HighlightTag::CharLiteral.into(), | 400 | CHAR => HighlightTag::CharLiteral.into(), |
401 | QUESTION => Highlight::new(HighlightTag::Operator) | HighlightModifier::ControlFlow, | ||
394 | LIFETIME => { | 402 | LIFETIME => { |
395 | let h = Highlight::new(HighlightTag::Lifetime); | 403 | let h = Highlight::new(HighlightTag::Lifetime); |
396 | match element.parent().map(|it| it.kind()) { | 404 | match element.parent().map(|it| it.kind()) { |
@@ -398,6 +406,23 @@ fn highlight_element( | |||
398 | _ => h, | 406 | _ => h, |
399 | } | 407 | } |
400 | } | 408 | } |
409 | PREFIX_EXPR => { | ||
410 | let prefix_expr = element.into_node().and_then(ast::PrefixExpr::cast)?; | ||
411 | match prefix_expr.op_kind() { | ||
412 | Some(ast::PrefixOp::Deref) => {} | ||
413 | _ => return None, | ||
414 | } | ||
415 | |||
416 | let expr = prefix_expr.expr()?; | ||
417 | let ty = sema.type_of_expr(&expr)?; | ||
418 | if !ty.is_raw_ptr() { | ||
419 | return None; | ||
420 | } | ||
421 | |||
422 | let mut h = Highlight::new(HighlightTag::Operator); | ||
423 | h |= HighlightModifier::Unsafe; | ||
424 | h | ||
425 | } | ||
401 | 426 | ||
402 | k if k.is_keyword() => { | 427 | k if k.is_keyword() => { |
403 | let h = Highlight::new(HighlightTag::Keyword); | 428 | let h = Highlight::new(HighlightTag::Keyword); |
@@ -450,7 +475,13 @@ fn highlight_name(db: &RootDatabase, def: Definition) -> Highlight { | |||
450 | Definition::Field(_) => HighlightTag::Field, | 475 | Definition::Field(_) => HighlightTag::Field, |
451 | Definition::ModuleDef(def) => match def { | 476 | Definition::ModuleDef(def) => match def { |
452 | hir::ModuleDef::Module(_) => HighlightTag::Module, | 477 | hir::ModuleDef::Module(_) => HighlightTag::Module, |
453 | hir::ModuleDef::Function(_) => HighlightTag::Function, | 478 | hir::ModuleDef::Function(func) => { |
479 | let mut h = HighlightTag::Function.into(); | ||
480 | if func.is_unsafe(db) { | ||
481 | h |= HighlightModifier::Unsafe; | ||
482 | } | ||
483 | return h; | ||
484 | } | ||
454 | hir::ModuleDef::Adt(hir::Adt::Struct(_)) => HighlightTag::Struct, | 485 | hir::ModuleDef::Adt(hir::Adt::Struct(_)) => HighlightTag::Struct, |
455 | hir::ModuleDef::Adt(hir::Adt::Enum(_)) => HighlightTag::Enum, | 486 | hir::ModuleDef::Adt(hir::Adt::Enum(_)) => HighlightTag::Enum, |
456 | hir::ModuleDef::Adt(hir::Adt::Union(_)) => HighlightTag::Union, | 487 | hir::ModuleDef::Adt(hir::Adt::Union(_)) => HighlightTag::Union, |
diff --git a/crates/ra_ide/src/syntax_highlighting/html.rs b/crates/ra_ide/src/syntax_highlighting/html.rs index edfe61f39..7d946c98d 100644 --- a/crates/ra_ide/src/syntax_highlighting/html.rs +++ b/crates/ra_ide/src/syntax_highlighting/html.rs | |||
@@ -69,6 +69,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd | |||
69 | .string_literal { color: #CC9393; } | 69 | .string_literal { color: #CC9393; } |
70 | .field { color: #94BFF3; } | 70 | .field { color: #94BFF3; } |
71 | .function { color: #93E0E3; } | 71 | .function { color: #93E0E3; } |
72 | .operator.unsafe { color: #E28C14; } | ||
72 | .parameter { color: #94BFF3; } | 73 | .parameter { color: #94BFF3; } |
73 | .text { color: #DCDCCC; } | 74 | .text { color: #DCDCCC; } |
74 | .type { color: #7CB8BB; } | 75 | .type { color: #7CB8BB; } |
diff --git a/crates/ra_ide/src/syntax_highlighting/tags.rs b/crates/ra_ide/src/syntax_highlighting/tags.rs index 46c718c91..94f466966 100644 --- a/crates/ra_ide/src/syntax_highlighting/tags.rs +++ b/crates/ra_ide/src/syntax_highlighting/tags.rs | |||
@@ -24,12 +24,14 @@ pub enum HighlightTag { | |||
24 | Enum, | 24 | Enum, |
25 | EnumVariant, | 25 | EnumVariant, |
26 | Field, | 26 | Field, |
27 | FormatSpecifier, | ||
27 | Function, | 28 | Function, |
28 | Keyword, | 29 | Keyword, |
29 | Lifetime, | 30 | Lifetime, |
30 | Macro, | 31 | Macro, |
31 | Module, | 32 | Module, |
32 | NumericLiteral, | 33 | NumericLiteral, |
34 | Operator, | ||
33 | SelfKeyword, | 35 | SelfKeyword, |
34 | SelfType, | 36 | SelfType, |
35 | Static, | 37 | Static, |
@@ -41,7 +43,6 @@ pub enum HighlightTag { | |||
41 | Union, | 43 | Union, |
42 | Local, | 44 | Local, |
43 | UnresolvedReference, | 45 | UnresolvedReference, |
44 | FormatSpecifier, | ||
45 | } | 46 | } |
46 | 47 | ||
47 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] | 48 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] |
@@ -71,12 +72,14 @@ impl HighlightTag { | |||
71 | HighlightTag::Enum => "enum", | 72 | HighlightTag::Enum => "enum", |
72 | HighlightTag::EnumVariant => "enum_variant", | 73 | HighlightTag::EnumVariant => "enum_variant", |
73 | HighlightTag::Field => "field", | 74 | HighlightTag::Field => "field", |
75 | HighlightTag::FormatSpecifier => "format_specifier", | ||
74 | HighlightTag::Function => "function", | 76 | HighlightTag::Function => "function", |
75 | HighlightTag::Keyword => "keyword", | 77 | HighlightTag::Keyword => "keyword", |
76 | HighlightTag::Lifetime => "lifetime", | 78 | HighlightTag::Lifetime => "lifetime", |
77 | HighlightTag::Macro => "macro", | 79 | HighlightTag::Macro => "macro", |
78 | HighlightTag::Module => "module", | 80 | HighlightTag::Module => "module", |
79 | HighlightTag::NumericLiteral => "numeric_literal", | 81 | HighlightTag::NumericLiteral => "numeric_literal", |
82 | HighlightTag::Operator => "operator", | ||
80 | HighlightTag::SelfKeyword => "self_keyword", | 83 | HighlightTag::SelfKeyword => "self_keyword", |
81 | HighlightTag::SelfType => "self_type", | 84 | HighlightTag::SelfType => "self_type", |
82 | HighlightTag::Static => "static", | 85 | HighlightTag::Static => "static", |
@@ -88,7 +91,6 @@ impl HighlightTag { | |||
88 | HighlightTag::Union => "union", | 91 | HighlightTag::Union => "union", |
89 | HighlightTag::Local => "variable", | 92 | HighlightTag::Local => "variable", |
90 | HighlightTag::UnresolvedReference => "unresolved_reference", | 93 | HighlightTag::UnresolvedReference => "unresolved_reference", |
91 | HighlightTag::FormatSpecifier => "format_specifier", | ||
92 | } | 94 | } |
93 | } | 95 | } |
94 | } | 96 | } |
diff --git a/crates/ra_ide/src/syntax_highlighting/tests.rs b/crates/ra_ide/src/syntax_highlighting/tests.rs index eb43a23da..36a1aa419 100644 --- a/crates/ra_ide/src/syntax_highlighting/tests.rs +++ b/crates/ra_ide/src/syntax_highlighting/tests.rs | |||
@@ -218,6 +218,7 @@ fn main() { | |||
218 | println!("{argument}", argument = "test"); // => "test" | 218 | println!("{argument}", argument = "test"); // => "test" |
219 | println!("{name} {}", 1, name = 2); // => "2 1" | 219 | println!("{name} {}", 1, name = 2); // => "2 1" |
220 | println!("{a} {c} {b}", a="a", b='b', c=3); // => "a 3 b" | 220 | println!("{a} {c} {b}", a="a", b='b', c=3); // => "a 3 b" |
221 | println!("{{{}}}", 2); // => "{2}" | ||
221 | println!("Hello {:5}!", "x"); | 222 | println!("Hello {:5}!", "x"); |
222 | println!("Hello {:1$}!", "x", 5); | 223 | println!("Hello {:1$}!", "x", 5); |
223 | println!("Hello {1:0$}!", 5, "x"); | 224 | println!("Hello {1:0$}!", 5, "x"); |
@@ -257,3 +258,34 @@ fn main() { | |||
257 | fs::write(dst_file, &actual_html).unwrap(); | 258 | fs::write(dst_file, &actual_html).unwrap(); |
258 | assert_eq_text!(expected_html, actual_html); | 259 | assert_eq_text!(expected_html, actual_html); |
259 | } | 260 | } |
261 | |||
262 | #[test] | ||
263 | fn test_unsafe_highlighting() { | ||
264 | let (analysis, file_id) = single_file( | ||
265 | r#" | ||
266 | unsafe fn unsafe_fn() {} | ||
267 | |||
268 | struct HasUnsafeFn; | ||
269 | |||
270 | impl HasUnsafeFn { | ||
271 | unsafe fn unsafe_method(&self) {} | ||
272 | } | ||
273 | |||
274 | fn main() { | ||
275 | let x = &5 as *const usize; | ||
276 | unsafe { | ||
277 | unsafe_fn(); | ||
278 | HasUnsafeFn.unsafe_method(); | ||
279 | let y = *x; | ||
280 | let z = -x; | ||
281 | } | ||
282 | } | ||
283 | "# | ||
284 | .trim(), | ||
285 | ); | ||
286 | let dst_file = project_dir().join("crates/ra_ide/src/snapshots/highlight_unsafe.html"); | ||
287 | let actual_html = &analysis.highlight_as_html(file_id, false).unwrap(); | ||
288 | let expected_html = &read_text(&dst_file); | ||
289 | fs::write(dst_file, &actual_html).unwrap(); | ||
290 | assert_eq_text!(expected_html, actual_html); | ||
291 | } | ||
diff --git a/crates/ra_ide/src/syntax_tree.rs b/crates/ra_ide/src/syntax_tree.rs index 86c70ff83..a341684fd 100644 --- a/crates/ra_ide/src/syntax_tree.rs +++ b/crates/ra_ide/src/syntax_tree.rs | |||
@@ -1,6 +1,4 @@ | |||
1 | //! FIXME: write short doc here | 1 | use ra_db::{FileId, SourceDatabase}; |
2 | |||
3 | use ra_db::SourceDatabase; | ||
4 | use ra_ide_db::RootDatabase; | 2 | use ra_ide_db::RootDatabase; |
5 | use ra_syntax::{ | 3 | use ra_syntax::{ |
6 | algo, AstNode, NodeOrToken, SourceFile, | 4 | algo, AstNode, NodeOrToken, SourceFile, |
@@ -8,8 +6,16 @@ use ra_syntax::{ | |||
8 | SyntaxToken, TextRange, TextSize, | 6 | SyntaxToken, TextRange, TextSize, |
9 | }; | 7 | }; |
10 | 8 | ||
11 | pub use ra_db::FileId; | 9 | // Feature: Show Syntax Tree |
12 | 10 | // | |
11 | // Shows the parse tree of the current file. It exists mostly for debugging | ||
12 | // rust-analyzer itself. | ||
13 | // | ||
14 | // |=== | ||
15 | // | Editor | Action Name | ||
16 | // | ||
17 | // | VS Code | **Rust Analyzer: Show Syntax Tree** | ||
18 | // |=== | ||
13 | pub(crate) fn syntax_tree( | 19 | pub(crate) fn syntax_tree( |
14 | db: &RootDatabase, | 20 | db: &RootDatabase, |
15 | file_id: FileId, | 21 | file_id: FileId, |
diff --git a/crates/ra_ide/src/typing.rs b/crates/ra_ide/src/typing.rs index 39bb3b357..67e2c33a0 100644 --- a/crates/ra_ide/src/typing.rs +++ b/crates/ra_ide/src/typing.rs | |||
@@ -32,6 +32,13 @@ pub(crate) use on_enter::on_enter; | |||
32 | 32 | ||
33 | pub(crate) const TRIGGER_CHARS: &str = ".=>"; | 33 | pub(crate) const TRIGGER_CHARS: &str = ".=>"; |
34 | 34 | ||