aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore2
-rw-r--r--Cargo.lock57
-rw-r--r--crates/ra_assists/src/handlers/add_from_impl_for_enum.rs4
-rw-r--r--crates/ra_assists/src/handlers/introduce_named_lifetime.rs303
-rw-r--r--crates/ra_assists/src/lib.rs2
-rw-r--r--crates/ra_assists/src/tests/generated.rs44
-rw-r--r--crates/ra_hir/src/code_model.rs8
-rw-r--r--crates/ra_hir_def/src/body/lower.rs68
-rw-r--r--crates/ra_hir_def/src/body/scope.rs4
-rw-r--r--crates/ra_hir_def/src/data.rs6
-rw-r--r--crates/ra_hir_def/src/expr.rs19
-rw-r--r--crates/ra_hir_expand/src/name.rs5
-rw-r--r--crates/ra_hir_ty/Cargo.toml4
-rw-r--r--crates/ra_hir_ty/src/infer.rs11
-rw-r--r--crates/ra_hir_ty/src/infer/coerce.rs4
-rw-r--r--crates/ra_hir_ty/src/infer/expr.rs61
-rw-r--r--crates/ra_hir_ty/src/tests/patterns.rs50
-rw-r--r--crates/ra_hir_ty/src/tests/simple.rs56
-rw-r--r--crates/ra_hir_ty/src/tests/traits.rs54
-rw-r--r--crates/ra_ide/src/completion.rs47
-rw-r--r--crates/ra_ide/src/completion/complete_postfix.rs6
-rw-r--r--crates/ra_ide/src/display/navigation_target.rs66
-rw-r--r--crates/ra_ide/src/display/structure.rs19
-rw-r--r--crates/ra_ide/src/expand_macro.rs11
-rw-r--r--crates/ra_ide/src/extend_selection.rs12
-rw-r--r--crates/ra_ide/src/goto_definition.rs11
-rw-r--r--crates/ra_ide/src/goto_implementation.rs (renamed from crates/ra_ide/src/impls.rs)11
-rw-r--r--crates/ra_ide/src/goto_type_definition.rs11
-rw-r--r--crates/ra_ide/src/hover.rs116
-rw-r--r--crates/ra_ide/src/inlay_hints.rs22
-rw-r--r--crates/ra_ide/src/join_lines.rs11
-rw-r--r--crates/ra_ide/src/lib.rs4
-rw-r--r--crates/ra_ide/src/matching_brace.rs13
-rw-r--r--crates/ra_ide/src/parent_module.rs12
-rw-r--r--crates/ra_ide/src/references.rs27
-rw-r--r--crates/ra_ide/src/runnables.rs261
-rw-r--r--crates/ra_ide/src/snapshots/highlight_injection.html1
-rw-r--r--crates/ra_ide/src/snapshots/highlight_strings.html2
-rw-r--r--crates/ra_ide/src/snapshots/highlight_unsafe.html48
-rw-r--r--crates/ra_ide/src/snapshots/highlighting.html1
-rw-r--r--crates/ra_ide/src/snapshots/rainbow_highlighting.html1
-rw-r--r--crates/ra_ide/src/ssr.rs24
-rw-r--r--crates/ra_ide/src/status.rs11
-rw-r--r--crates/ra_ide/src/syntax_highlighting.rs187
-rw-r--r--crates/ra_ide/src/syntax_highlighting/html.rs1
-rw-r--r--crates/ra_ide/src/syntax_highlighting/tags.rs6
-rw-r--r--crates/ra_ide/src/syntax_highlighting/tests.rs32
-rw-r--r--crates/ra_ide/src/syntax_tree.rs16
-rw-r--r--crates/ra_ide/src/typing.rs7
-rw-r--r--crates/ra_ide_db/src/search.rs40
-rw-r--r--crates/ra_ide_db/src/symbol_index.rs21
-rw-r--r--crates/ra_proc_macro_srv/Cargo.toml1
-rw-r--r--crates/ra_proc_macro_srv/src/tests/utils.rs3
-rw-r--r--crates/ra_project_model/src/cargo_workspace.rs2
-rw-r--r--crates/ra_syntax/src/ast.rs2
-rw-r--r--crates/ra_syntax/src/ast/generated/nodes.rs1
-rw-r--r--crates/ra_syntax/src/ast/tokens.rs5
-rw-r--r--crates/ra_syntax/src/syntax_node.rs4
-rw-r--r--crates/rust-analyzer/src/caps.rs3
-rw-r--r--crates/rust-analyzer/src/cargo_target_spec.rs85
-rw-r--r--crates/rust-analyzer/src/config.rs2
-rw-r--r--crates/rust-analyzer/src/diagnostics/snapshots/rust_analyzer__diagnostics__to_proto__tests__snap_rustc_unused_variable.snap2
-rw-r--r--crates/rust-analyzer/src/diagnostics/to_proto.rs3
-rw-r--r--crates/rust-analyzer/src/lsp_ext.rs30
-rw-r--r--crates/rust-analyzer/src/main_loop/handlers.rs162
-rw-r--r--crates/rust-analyzer/src/to_proto.rs41
-rw-r--r--crates/rust-analyzer/tests/heavy_tests/main.rs113
-rw-r--r--docs/dev/README.md105
-rw-r--r--docs/dev/lsp-extensions.md92
-rw-r--r--docs/dev/lsp-features.md72
-rw-r--r--docs/user/assists.md769
-rw-r--r--docs/user/features.md218
-rw-r--r--docs/user/generated_assists.adoc1015
-rw-r--r--docs/user/generated_features.adoc298
-rw-r--r--docs/user/manual.adoc (renamed from docs/user/readme.adoc)51
-rw-r--r--editors/code/package.json4
-rw-r--r--editors/code/src/commands.ts74
-rw-r--r--editors/code/src/debug.ts121
-rw-r--r--editors/code/src/lsp_ext.ts16
-rw-r--r--editors/code/src/run.ts96
-rw-r--r--editors/code/src/tasks.ts7
-rw-r--r--editors/code/src/toolchain.ts (renamed from editors/code/src/cargo.ts)117
-rw-r--r--editors/code/src/util.ts18
-rw-r--r--editors/code/tests/unit/launch_config.test.ts14
-rw-r--r--xtask/src/ast_src.rs2
-rw-r--r--xtask/src/codegen.rs89
-rw-r--r--xtask/src/codegen/gen_assists_docs.rs205
-rw-r--r--xtask/src/codegen/gen_feature_docs.rs75
-rw-r--r--xtask/src/lib.rs6
-rw-r--r--xtask/src/main.rs1
-rw-r--r--xtask/tests/tidy.rs14
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
10cargo-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"
112checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 112checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
113 113
114[[package]] 114[[package]]
115name = "chalk-base"
116version = "0.10.1-dev"
117source = "git+https://github.com/rust-lang/chalk.git?rev=5a3b871ca17529ab5aa5787594fabad1634936cb#5a3b871ca17529ab5aa5787594fabad1634936cb"
118dependencies = [
119 "lazy_static",
120]
121
122[[package]]
123name = "chalk-derive" 115name = "chalk-derive"
124version = "0.10.1-dev" 116version = "0.10.1-dev"
125source = "git+https://github.com/rust-lang/chalk.git?rev=5a3b871ca17529ab5aa5787594fabad1634936cb#5a3b871ca17529ab5aa5787594fabad1634936cb" 117source = "git+https://github.com/rust-lang/chalk.git?rev=329b7f3fdd2431ed6f6778cde53f22374c7d094c#329b7f3fdd2431ed6f6778cde53f22374c7d094c"
126dependencies = [ 118dependencies = [
127 "proc-macro2", 119 "proc-macro2",
128 "quote", 120 "quote",
@@ -133,9 +125,8 @@ dependencies = [
133[[package]] 125[[package]]
134name = "chalk-engine" 126name = "chalk-engine"
135version = "0.10.1-dev" 127version = "0.10.1-dev"
136source = "git+https://github.com/rust-lang/chalk.git?rev=5a3b871ca17529ab5aa5787594fabad1634936cb#5a3b871ca17529ab5aa5787594fabad1634936cb" 128source = "git+https://github.com/rust-lang/chalk.git?rev=329b7f3fdd2431ed6f6778cde53f22374c7d094c#329b7f3fdd2431ed6f6778cde53f22374c7d094c"
137dependencies = [ 129dependencies = [
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]]
145name = "chalk-ir" 136name = "chalk-ir"
146version = "0.10.1-dev" 137version = "0.10.1-dev"
147source = "git+https://github.com/rust-lang/chalk.git?rev=5a3b871ca17529ab5aa5787594fabad1634936cb#5a3b871ca17529ab5aa5787594fabad1634936cb" 138source = "git+https://github.com/rust-lang/chalk.git?rev=329b7f3fdd2431ed6f6778cde53f22374c7d094c#329b7f3fdd2431ed6f6778cde53f22374c7d094c"
148dependencies = [ 139dependencies = [
149 "chalk-base",
150 "chalk-derive", 140 "chalk-derive",
141 "lazy_static",
151] 142]
152 143
153[[package]] 144[[package]]
154name = "chalk-solve" 145name = "chalk-solve"
155version = "0.10.1-dev" 146version = "0.10.1-dev"
156source = "git+https://github.com/rust-lang/chalk.git?rev=5a3b871ca17529ab5aa5787594fabad1634936cb#5a3b871ca17529ab5aa5787594fabad1634936cb" 147source = "git+https://github.com/rust-lang/chalk.git?rev=329b7f3fdd2431ed6f6778cde53f22374c7d094c#329b7f3fdd2431ed6f6778cde53f22374c7d094c"
157dependencies = [ 148dependencies = [
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]]
255name = "crossbeam-queue" 245name = "crossbeam-queue"
256version = "0.2.1" 246version = "0.2.2"
257source = "registry+https://github.com/rust-lang/crates.io-index" 247source = "registry+https://github.com/rust-lang/crates.io-index"
258checksum = "c695eeca1e7173472a32221542ae469b3e9aac3a4fc81f7696bcad82029493db" 248checksum = "ab6bffe714b6bb07e42f201352c34f51fefd355ace793f9e638ebd52d23f98d2"
259dependencies = [ 249dependencies = [
260 "cfg-if", 250 "cfg-if",
261 "crossbeam-utils", 251 "crossbeam-utils",
@@ -650,9 +640,9 @@ dependencies = [
650 640
651[[package]] 641[[package]]
652name = "lsp-types" 642name = "lsp-types"
653version = "0.74.1" 643version = "0.74.2"
654source = "registry+https://github.com/rust-lang/crates.io-index" 644source = "registry+https://github.com/rust-lang/crates.io-index"
655checksum = "57c0e6a2b8837d27b29deb3f3e6dc1c6d2f57947677f9be1024e482ec5b59525" 645checksum = "b360754e89e0e13c114245131382ba921d4ff1efabb918e549422938aaa8d392"
656dependencies = [ 646dependencies = [
657 "base64", 647 "base64",
658 "bitflags", 648 "bitflags",
@@ -819,9 +809,9 @@ dependencies = [
819 809
820[[package]] 810[[package]]
821name = "paste" 811name = "paste"
822version = "0.1.14" 812version = "0.1.15"
823source = "registry+https://github.com/rust-lang/crates.io-index" 813source = "registry+https://github.com/rust-lang/crates.io-index"
824checksum = "3431e8f72b90f8a7af91dec890d9814000cb371258e0ec7370d93e085361f531" 814checksum = "d53181dcd37421c08d3b69f887784956674d09c3f9a47a04fece2b130a5b346b"
825dependencies = [ 815dependencies = [
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]]
831name = "paste-impl" 821name = "paste-impl"
832version = "0.1.14" 822version = "0.1.15"
833source = "registry+https://github.com/rust-lang/crates.io-index" 823source = "registry+https://github.com/rust-lang/crates.io-index"
834checksum = "25af5fc872ba284d8d84608bf8a0fa9b5376c96c23f503b007dfd9e34dde5606" 824checksum = "05ca490fa1c034a71412b4d1edcb904ec5a0981a4426c9eb2128c0fda7a68d17"
835dependencies = [ 825dependencies = [
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]]
1303name = "regex" 1294name = "regex"
1304version = "1.3.7" 1295version = "1.3.9"
1305source = "registry+https://github.com/rust-lang/crates.io-index" 1296source = "registry+https://github.com/rust-lang/crates.io-index"
1306checksum = "a6020f034922e3194c711b82a627453881bc4682166cabb07134a10c26ba7692" 1297checksum = "9c3780fcf44b193bc4d09f36d2a3c87b251da4a046c87795a0d35f4f927ad8e6"
1307dependencies = [ 1298dependencies = [
1308 "aho-corasick", 1299 "aho-corasick",
1309 "memchr", 1300 "memchr",
@@ -1313,9 +1304,9 @@ dependencies = [
1313 1304
1314[[package]] 1305[[package]]
1315name = "regex-syntax" 1306name = "regex-syntax"
1316version = "0.6.17" 1307version = "0.6.18"
1317source = "registry+https://github.com/rust-lang/crates.io-index" 1308source = "registry+https://github.com/rust-lang/crates.io-index"
1318checksum = "7fe5bd57d1d7414c6b5ed48563a2c855d995ff777729dcd91c369ec7fea395ae" 1309checksum = "26412eb97c6b088a6997e05f69403a802a92d520de2f8e63c2b65f9e0f47c4e8"
1319 1310
1320[[package]] 1311[[package]]
1321name = "relative-path" 1312name = "relative-path"
@@ -1501,18 +1492,18 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
1501 1492
1502[[package]] 1493[[package]]
1503name = "serde" 1494name = "serde"
1504version = "1.0.110" 1495version = "1.0.111"
1505source = "registry+https://github.com/rust-lang/crates.io-index" 1496source = "registry+https://github.com/rust-lang/crates.io-index"
1506checksum = "99e7b308464d16b56eba9964e4972a3eee817760ab60d88c3f86e1fecb08204c" 1497checksum = "c9124df5b40cbd380080b2cc6ab894c040a3070d995f5c9dc77e18c34a8ae37d"
1507dependencies = [ 1498dependencies = [
1508 "serde_derive", 1499 "serde_derive",
1509] 1500]
1510 1501
1511[[package]] 1502[[package]]
1512name = "serde_derive" 1503name = "serde_derive"
1513version = "1.0.110" 1504version = "1.0.111"
1514source = "registry+https://github.com/rust-lang/crates.io-index" 1505source = "registry+https://github.com/rust-lang/crates.io-index"
1515checksum = "818fbf6bfa9a42d3bfcaca148547aa00c7b915bec71d1757aa2d44ca68771984" 1506checksum = "3f2c3ac8e6ca1e9c80b8be1023940162bf81ae3cffbb1809474152f2ce1eb250"
1516dependencies = [ 1507dependencies = [
1517 "proc-macro2", 1508 "proc-macro2",
1518 "quote", 1509 "quote",
@@ -1586,9 +1577,9 @@ checksum = "ab16ced94dbd8a46c82fd81e3ed9a8727dac2977ea869d217bcc4ea1f122e81f"
1586 1577
1587[[package]] 1578[[package]]
1588name = "syn" 1579name = "syn"
1589version = "1.0.25" 1580version = "1.0.29"
1590source = "registry+https://github.com/rust-lang/crates.io-index" 1581source = "registry+https://github.com/rust-lang/crates.io-index"
1591checksum = "f14a640819f79b72a710c0be059dce779f9339ae046c8bef12c361d56702146f" 1582checksum = "bb37da98a55b1d08529362d9cbb863be17556873df2585904ab9d2bc951291d0"
1592dependencies = [ 1583dependencies = [
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
5use crate::{utils::FamousDefs, AssistContext, AssistId, Assists}; 5use 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 @@
1use ra_syntax::{
2 ast::{self, NameOwner, TypeAscriptionOwner, TypeParamsOwner},
3 AstNode, SyntaxKind, TextRange, TextSize,
4};
5use rustc_hash::FxHashSet;
6
7use crate::{assist_context::AssistBuilder, AssistContext, AssistId, Assists};
8
9static ASSIST_NAME: &str = "introduce_named_lifetime";
10static 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
37pub(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
53fn 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
96fn 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
111fn 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`.
128fn 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)]
150mod 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]
62fn doctest_add_from_impl_for_enum() {
63 check_doc_test(
64 "add_from_impl_for_enum",
65 r#####"
66enum A { <|>One(u32) }
67"#####,
68 r#####"
69enum A { One(u32) }
70
71impl From<u32> for A {
72 fn from(v: u32) -> Self {
73 A::One(v)
74 }
75}
76"#####,
77 )
78}
79
80#[test]
62fn doctest_add_function() { 81fn 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]
455fn doctest_introduce_named_lifetime() {
456 check_doc_test(
457 "introduce_named_lifetime",
458 r#####"
459impl Cursor<'_<|>> {
460 fn node(self) -> &SyntaxNode {
461 match self {
462 Cursor::Replace(node) | Cursor::Before(node) => node,
463 }
464 }
465}
466"#####,
467 r#####"
468impl<'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]
436fn doctest_introduce_variable() { 480fn 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(
138fn compute_expr_scopes(expr: ExprId, body: &Body, scopes: &mut ExprScopes, scope: ScopeId) { 138fn 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
28scoped-tls = "1" 28scoped-tls = "1"
29 29
30chalk-solve = { git = "https://github.com/rust-lang/chalk.git", rev = "5a3b871ca17529ab5aa5787594fabad1634936cb" } 30chalk-solve = { git = "https://github.com/rust-lang/chalk.git", rev = "329b7f3fdd2431ed6f6778cde53f22374c7d094c" }
31chalk-ir = { git = "https://github.com/rust-lang/chalk.git", rev = "5a3b871ca17529ab5aa5787594fabad1634936cb" } 31chalk-ir = { git = "https://github.com/rust-lang/chalk.git", rev = "329b7f3fdd2431ed6f6778cde53f22374c7d094c" }
32 32
33[dev-dependencies] 33[dev-dependencies]
34insta = "0.16.0" 34insta = "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> {
219struct BreakableContext { 219struct 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
225fn 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
224impl<'a> InferenceContext<'a> { 235impl<'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
24use super::{ 24use 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
29impl<'a> InferenceContext<'a> { 29impl<'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]
525fn match_ergonomics_in_closure_params() {
526 assert_snapshot!(
527 infer(r#"
528#[lang = "fn_once"]
529trait FnOnce<Args> {
530 type Output;
531}
532
533fn foo<T, U, F: FnOnce(T) -> U>(t: T, f: F) -> U { loop {} }
534
535fn 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]
1948fn infer_labelled_break_with_val() {
1949 assert_snapshot!(
1950 infer(r#"
1951fn 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]
2758fn integer_range_iterate() {
2759 let t = type_at(
2760 r#"
2761//- /main.rs crate:main deps:std
2762fn test() {
2763 for x in 0..100 { x<|>; }
2764}
2765
2766//- /std.rs crate:std
2767pub mod ops {
2768 pub struct Range<Idx> {
2769 pub start: Idx,
2770 pub end: Idx,
2771 }
2772}
2773
2774pub 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
2790trait Step {}
2791impl Step for i32 {}
2792impl Step for i64 {}
2793
2794impl<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
3mod completion_config; 1mod completion_config;
4mod completion_item; 2mod completion_item;
5mod completion_context; 3mod 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 2use ra_assists::utils::TryEnum;
3use ra_syntax::{ 3use ra_syntax::{
4 ast::{self, AstNode}, 4 ast::{self, AstNode},
5 TextRange, TextSize, 5 TextRange, TextSize,
6}; 6};
7use ra_text_edit::TextEdit; 7use ra_text_edit::TextEdit;
8 8
9use super::completion_config::SnippetCap;
10use crate::{ 9use 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};
17use ra_assists::utils::TryEnum; 16
17use super::completion_config::SnippetCap;
18 18
19pub(super) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) { 19pub(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
3use crate::TextRange;
4
5use ra_syntax::{ 1use 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// |===
21pub fn file_structure(file: &SourceFile) -> Vec<StructureNode> { 30pub 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
3use hir::Semantics; 1use hir::Semantics;
4use ra_ide_db::RootDatabase; 2use ra_ide_db::RootDatabase;
5use ra_syntax::{ 3use 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// |===
17pub(crate) fn expand_macro(db: &RootDatabase, position: FilePosition) -> Option<ExpandedMacro> { 24pub(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
3use std::iter::successors; 1use std::iter::successors;
4 2
5use hir::Semantics; 3use hir::Semantics;
@@ -14,6 +12,16 @@ use ra_syntax::{
14 12
15use crate::FileRange; 13use 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// |===
17pub(crate) fn extend_selection(db: &RootDatabase, frange: FileRange) -> TextRange { 25pub(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
3use hir::Semantics; 1use hir::Semantics;
4use ra_ide_db::{ 2use 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// |===
20pub(crate) fn goto_definition( 27pub(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
3use hir::{Crate, ImplDef, Semantics}; 1use hir::{Crate, ImplDef, Semantics};
4use ra_ide_db::RootDatabase; 2use ra_ide_db::RootDatabase;
5use ra_syntax::{algo::find_node_at_offset, ast, AstNode}; 3use ra_syntax::{algo::find_node_at_offset, ast, AstNode};
6 4
7use crate::{display::ToNav, FilePosition, NavigationTarget, RangeInfo}; 5use 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// |===
9pub(crate) fn goto_implementation( 16pub(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
3use ra_ide_db::RootDatabase; 1use ra_ide_db::RootDatabase;
4use ra_syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset}; 2use ra_syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset};
5 3
6use crate::{display::ToNav, FilePosition, NavigationTarget, RangeInfo}; 4use 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// |===
8pub(crate) fn goto_type_definition( 15pub(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 1use std::iter::once;
2//! source code items (e.g. function call, struct field, variable symbol...)
3 2
4use hir::{ 3use 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};
7use itertools::Itertools;
8use ra_db::SourceDatabase; 8use ra_db::SourceDatabase;
9use ra_ide_db::{ 9use 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};
24use itertools::Itertools;
25use 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.
67pub(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
65fn hover_text( 120fn 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
163pub(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
216fn pick_best(tokens: TokenAtOffset<SyntaxToken>) -> Option<SyntaxToken> { 218fn 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
3use hir::{Adt, HirDisplay, Semantics, Type}; 1use hir::{Adt, HirDisplay, Semantics, Type};
4use ra_ide_db::RootDatabase; 2use ra_ide_db::RootDatabase;
5use ra_prof::profile; 3use 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// |===
42pub(crate) fn inlay_hints( 60pub(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
3use itertools::Itertools; 1use itertools::Itertools;
4use ra_fmt::{compute_ws, extract_trivial_expression}; 2use ra_fmt::{compute_ws, extract_trivial_expression};
5use ra_syntax::{ 3use ra_syntax::{
@@ -11,6 +9,15 @@ use ra_syntax::{
11}; 9};
12use ra_text_edit::{TextEdit, TextEditBuilder}; 10use 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// |===
14pub fn join_lines(file: &SourceFile, range: TextRange) -> TextEdit { 21pub 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;
23mod runnables; 23mod runnables;
24mod goto_definition; 24mod goto_definition;
25mod goto_type_definition; 25mod goto_type_definition;
26mod goto_implementation;
26mod extend_selection; 27mod extend_selection;
27mod hover; 28mod hover;
28mod call_hierarchy; 29mod call_hierarchy;
@@ -30,7 +31,6 @@ mod call_info;
30mod syntax_highlighting; 31mod syntax_highlighting;
31mod parent_module; 32mod parent_module;
32mod references; 33mod references;
33mod impls;
34mod diagnostics; 34mod diagnostics;
35mod syntax_tree; 35mod syntax_tree;
36mod folding_ranges; 36mod 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
3use ra_syntax::{ast::AstNode, SourceFile, SyntaxKind, TextSize, T}; 1use 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// |===
5pub fn matching_brace(file: &SourceFile, offset: TextSize) -> Option<TextSize> { 14pub 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
3use hir::Semantics; 1use hir::Semantics;
4use ra_db::{CrateId, FileId, FilePosition}; 2use ra_db::{CrateId, FileId, FilePosition};
5use ra_ide_db::RootDatabase; 3use ra_ide_db::RootDatabase;
@@ -11,6 +9,16 @@ use test_utils::mark;
11 9
12use crate::NavigationTarget; 10use 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.
16pub(crate) fn parent_module(db: &RootDatabase, position: FilePosition) -> Vec<NavigationTarget> { 24pub(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 1use std::fmt;
2 2
3use hir::{AsAssocItem, Attrs, HirFileId, InFile, Semantics}; 3use hir::{AsAssocItem, Attrs, HirFileId, InFile, Semantics};
4use itertools::Itertools; 4use itertools::Itertools;
5use ra_cfg::CfgExpr;
5use ra_ide_db::RootDatabase; 6use ra_ide_db::RootDatabase;
6use ra_syntax::{ 7use 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
11use crate::FileId; 12use crate::{display::ToNav, FileId, NavigationTarget};
12use ast::DocCommentsOwner;
13use ra_cfg::CfgExpr;
14use std::fmt::Display;
15 13
16#[derive(Debug)] 14#[derive(Debug)]
17pub struct Runnable { 15pub 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
29impl Display for TestId { 27impl 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// |===
47pub(crate) fn runnables(db: &RootDatabase, file_id: FileId) -> Vec<Runnable> { 56pub(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">// =&gt; "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">// =&gt; "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">// =&gt; "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">// =&gt; "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">// =&gt; "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">// =&gt; "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">// =&gt; "{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>
3body { margin: 0; }
4pre { 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
3use std::{collections::HashMap, iter::once, str::FromStr}; 1use std::{collections::HashMap, iter::once, str::FromStr};
4 2
5use ra_db::{SourceDatabase, SourceDatabaseExt}; 3use ra_db::{SourceDatabase, SourceDatabaseExt};
@@ -25,6 +23,28 @@ impl std::fmt::Display for SsrError {
25 23
26impl std::error::Error for SsrError {} 24impl 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// |===
28pub fn parse_search_replace( 48pub 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
3use std::{fmt, iter::FromIterator, sync::Arc}; 1use std::{fmt, iter::FromIterator, sync::Arc};
4 2
5use hir::MacroFile; 3use 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// |===
29pub(crate) fn status(db: &RootDatabase) -> String { 36pub(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
3mod tags; 1mod tags;
4mod html; 2mod 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
36struct 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//
42impl 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
110pub(crate) fn highlight( 42pub(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)]
227struct HighlightedRangeStack {
228 stack: Vec<Vec<HighlightedRange>>,
229}
230
231/// We use a stack to implement the flattening logic for the highlighted
232/// syntax ranges.
233impl 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
294fn highlight_format_specifier(kind: FormatSpecifier) -> Option<HighlightTag> { 301fn 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]
263fn test_unsafe_highlighting() {
264 let (analysis, file_id) = single_file(
265 r#"
266unsafe fn unsafe_fn() {}
267
268struct HasUnsafeFn;
269
270impl HasUnsafeFn {
271 unsafe fn unsafe_method(&self) {}
272}
273
274fn 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 1use ra_db::{FileId, SourceDatabase};
2
3use ra_db::SourceDatabase;
4use ra_ide_db::RootDatabase; 2use ra_ide_db::RootDatabase;
5use ra_syntax::{ 3use 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
11pub 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// |===
13pub(crate) fn syntax_tree( 19pub(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
33pub(crate) const TRIGGER_CHARS: &str = ".=>"; 33pub(crate) const TRIGGER_CHARS: &str = ".=>";
34 34