aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBenjamin Coenen <[email protected]>2020-05-10 11:31:55 +0100
committerBenjamin Coenen <[email protected]>2020-05-10 11:31:55 +0100
commite80903a96564c2239489a8c630a4748bf21a3659 (patch)
tree12b31a1fd12deb2120065cea5a558425c8c1984f
parent6203e9c4faee288f16d93dbb7dd0f1f8df487d83 (diff)
parent4578154b608fa075595103d0c933da60d55b25c8 (diff)
Merge branch 'master' of github.com:rust-analyzer/rust-analyzer into feat_4348
-rw-r--r--Cargo.lock50
-rw-r--r--crates/ra_assists/src/assist_context.rs (renamed from crates/ra_assists/src/assist_ctx.rs)262
-rw-r--r--crates/ra_assists/src/handlers/add_custom_impl.rs9
-rw-r--r--crates/ra_assists/src/handlers/add_derive.rs9
-rw-r--r--crates/ra_assists/src/handlers/add_explicit_type.rs25
-rw-r--r--crates/ra_assists/src/handlers/add_from_impl_for_enum.rs35
-rw-r--r--crates/ra_assists/src/handlers/add_function.rs204
-rw-r--r--crates/ra_assists/src/handlers/add_impl.rs61
-rw-r--r--crates/ra_assists/src/handlers/add_missing_impl_members.rs33
-rw-r--r--crates/ra_assists/src/handlers/add_new.rs8
-rw-r--r--crates/ra_assists/src/handlers/apply_demorgan.rs6
-rw-r--r--crates/ra_assists/src/handlers/auto_import.rs41
-rw-r--r--crates/ra_assists/src/handlers/change_return_type_to_result.rs10
-rw-r--r--crates/ra_assists/src/handlers/change_visibility.rs31
-rw-r--r--crates/ra_assists/src/handlers/early_return.rs168
-rw-r--r--crates/ra_assists/src/handlers/fill_match_arms.rs6
-rw-r--r--crates/ra_assists/src/handlers/flip_binexpr.rs6
-rw-r--r--crates/ra_assists/src/handlers/flip_comma.rs6
-rw-r--r--crates/ra_assists/src/handlers/flip_trait_bound.rs6
-rw-r--r--crates/ra_assists/src/handlers/inline_local_variable.rs31
-rw-r--r--crates/ra_assists/src/handlers/introduce_variable.rs6
-rw-r--r--crates/ra_assists/src/handlers/invert_if.rs36
-rw-r--r--crates/ra_assists/src/handlers/merge_imports.rs15
-rw-r--r--crates/ra_assists/src/handlers/merge_match_arms.rs8
-rw-r--r--crates/ra_assists/src/handlers/move_bounds.rs57
-rw-r--r--crates/ra_assists/src/handlers/move_guard.rs10
-rw-r--r--crates/ra_assists/src/handlers/raw_string.rs18
-rw-r--r--crates/ra_assists/src/handlers/remove_dbg.rs6
-rw-r--r--crates/ra_assists/src/handlers/remove_mut.rs6
-rw-r--r--crates/ra_assists/src/handlers/reorder_fields.rs29
-rw-r--r--crates/ra_assists/src/handlers/replace_if_let_with_match.rs58
-rw-r--r--crates/ra_assists/src/handlers/replace_let_with_if_let.rs15
-rw-r--r--crates/ra_assists/src/handlers/replace_qualified_name_with_use.rs19
-rw-r--r--crates/ra_assists/src/handlers/replace_unwrap_with_match.rs47
-rw-r--r--crates/ra_assists/src/handlers/split_import.rs8
-rw-r--r--crates/ra_assists/src/handlers/unwrap_block.rs6
-rw-r--r--crates/ra_assists/src/lib.rs98
-rw-r--r--crates/ra_assists/src/tests.rs41
-rw-r--r--crates/ra_assists/src/utils/insert_use.rs10
-rw-r--r--crates/ra_cfg/src/lib.rs7
-rw-r--r--crates/ra_flycheck/Cargo.toml1
-rw-r--r--crates/ra_flycheck/src/lib.rs11
-rw-r--r--crates/ra_hir/src/code_model.rs9
-rw-r--r--crates/ra_hir_ty/src/diagnostics.rs28
-rw-r--r--crates/ra_hir_ty/src/display.rs176
-rw-r--r--crates/ra_hir_ty/src/infer.rs60
-rw-r--r--crates/ra_hir_ty/src/infer/coerce.rs34
-rw-r--r--crates/ra_hir_ty/src/infer/expr.rs88
-rw-r--r--crates/ra_hir_ty/src/lib.rs10
-rw-r--r--crates/ra_hir_ty/src/marks.rs1
-rw-r--r--crates/ra_hir_ty/src/tests.rs35
-rw-r--r--crates/ra_hir_ty/src/tests/coercion.rs46
-rw-r--r--crates/ra_hir_ty/src/tests/display_source_code.rs23
-rw-r--r--crates/ra_hir_ty/src/tests/macros.rs2
-rw-r--r--crates/ra_hir_ty/src/tests/method_resolution.rs13
-rw-r--r--crates/ra_hir_ty/src/tests/never_type.rs177
-rw-r--r--crates/ra_hir_ty/src/tests/simple.rs14
-rw-r--r--crates/ra_ide/src/display/function_signature.rs23
-rw-r--r--crates/ra_ide/src/lib.rs8
-rw-r--r--crates/ra_ide/src/references/rename.rs62
-rw-r--r--crates/ra_ide_db/src/defs.rs10
-rw-r--r--crates/ra_project_model/Cargo.toml3
-rw-r--r--crates/ra_project_model/src/cargo_workspace.rs28
-rw-r--r--crates/ra_project_model/src/lib.rs100
-rw-r--r--crates/ra_project_model/src/sysroot.rs41
-rw-r--r--crates/ra_syntax/src/ast/edit.rs26
-rw-r--r--crates/ra_toolchain/Cargo.toml8
-rw-r--r--crates/ra_toolchain/src/lib.rs64
-rw-r--r--crates/rust-analyzer/src/main_loop.rs20
-rw-r--r--crates/rust-analyzer/src/main_loop/handlers.rs26
-rw-r--r--crates/rust-analyzer/tests/heavy_tests/main.rs110
-rw-r--r--crates/test_utils/src/lib.rs2
-rw-r--r--editors/code/package.json18
-rw-r--r--editors/code/src/cargo.ts81
-rw-r--r--editors/code/src/commands/runnables.ts71
-rw-r--r--editors/code/src/config.ts9
-rw-r--r--editors/code/src/inlay_hints.ts14
-rw-r--r--editors/code/src/main.ts8
-rw-r--r--editors/code/src/util.ts11
79 files changed, 1920 insertions, 1058 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 5d50a766f..41855f22e 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -68,9 +68,9 @@ dependencies = [
68 68
69[[package]] 69[[package]]
70name = "base64" 70name = "base64"
71version = "0.12.0" 71version = "0.12.1"
72source = "registry+https://github.com/rust-lang/crates.io-index" 72source = "registry+https://github.com/rust-lang/crates.io-index"
73checksum = "7d5ca2cd0adc3f48f9e9ea5a6bbdf9ccc0bfade884847e484d452414c7ccffb3" 73checksum = "53d1ccbaf7d9ec9537465a97bf19edc1a4e158ecb49fc16178202238c569cc42"
74 74
75[[package]] 75[[package]]
76name = "bitflags" 76name = "bitflags"
@@ -342,9 +342,9 @@ dependencies = [
342 342
343[[package]] 343[[package]]
344name = "filetime" 344name = "filetime"
345version = "0.2.9" 345version = "0.2.10"
346source = "registry+https://github.com/rust-lang/crates.io-index" 346source = "registry+https://github.com/rust-lang/crates.io-index"
347checksum = "f59efc38004c988e4201d11d263b8171f49a2e7ec0bdbb71773433f271504a5e" 347checksum = "affc17579b132fc2461adf7c575cc6e8b134ebca52c51f5411388965227dc695"
348dependencies = [ 348dependencies = [
349 "cfg-if", 349 "cfg-if",
350 "libc", 350 "libc",
@@ -465,6 +465,15 @@ dependencies = [
465] 465]
466 466
467[[package]] 467[[package]]
468name = "home"
469version = "0.5.3"
470source = "registry+https://github.com/rust-lang/crates.io-index"
471checksum = "2456aef2e6b6a9784192ae780c0f15bc57df0e918585282325e8c8ac27737654"
472dependencies = [
473 "winapi 0.3.8",
474]
475
476[[package]]
468name = "idna" 477name = "idna"
469version = "0.2.0" 478version = "0.2.0"
470source = "registry+https://github.com/rust-lang/crates.io-index" 479source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -610,18 +619,18 @@ checksum = "99e85c08494b21a9054e7fe1374a732aeadaff3980b6990b94bfd3a70f690005"
610 619
611[[package]] 620[[package]]
612name = "libloading" 621name = "libloading"
613version = "0.6.1" 622version = "0.6.2"
614source = "registry+https://github.com/rust-lang/crates.io-index" 623source = "registry+https://github.com/rust-lang/crates.io-index"
615checksum = "3c4f51b790f5bdb65acb4cc94bb81d7b2ee60348a5431ac1467d390b017600b0" 624checksum = "2cadb8e769f070c45df05c78c7520eb4cd17061d4ab262e43cfc68b4d00ac71c"
616dependencies = [ 625dependencies = [
617 "winapi 0.3.8", 626 "winapi 0.3.8",
618] 627]
619 628
620[[package]] 629[[package]]
621name = "linked-hash-map" 630name = "linked-hash-map"
622version = "0.5.2" 631version = "0.5.3"
623source = "registry+https://github.com/rust-lang/crates.io-index" 632source = "registry+https://github.com/rust-lang/crates.io-index"
624checksum = "ae91b68aebc4ddb91978b11a1b02ddd8602a05ec19002801c5666000e05e0f83" 633checksum = "8dd5a6d5999d9907cda8ed67bbd137d3af8085216c2ac62de5be860bd41f304a"
625 634
626[[package]] 635[[package]]
627name = "lock_api" 636name = "lock_api"
@@ -824,9 +833,9 @@ dependencies = [
824 833
825[[package]] 834[[package]]
826name = "paste" 835name = "paste"
827version = "0.1.11" 836version = "0.1.12"
828source = "registry+https://github.com/rust-lang/crates.io-index" 837source = "registry+https://github.com/rust-lang/crates.io-index"
829checksum = "a3c897744f63f34f7ae3a024d9162bb5001f4ad661dd24bea0dc9f075d2de1c6" 838checksum = "0a229b1c58c692edcaa5b9b0948084f130f55d2dcc15b02fcc5340b2b4521476"
830dependencies = [ 839dependencies = [
831 "paste-impl", 840 "paste-impl",
832 "proc-macro-hack", 841 "proc-macro-hack",
@@ -834,9 +843,9 @@ dependencies = [
834 843
835[[package]] 844[[package]]
836name = "paste-impl" 845name = "paste-impl"
837version = "0.1.11" 846version = "0.1.12"
838source = "registry+https://github.com/rust-lang/crates.io-index" 847source = "registry+https://github.com/rust-lang/crates.io-index"
839checksum = "66fd6f92e3594f2dd7b3fc23e42d82e292f7bcda6d8e5dcd167072327234ab89" 848checksum = "2e0bf239e447e67ff6d16a8bb5e4d4bd2343acf5066061c0e8e06ac5ba8ca68c"
840dependencies = [ 849dependencies = [
841 "proc-macro-hack", 850 "proc-macro-hack",
842 "proc-macro2", 851 "proc-macro2",
@@ -886,9 +895,9 @@ checksum = "0d659fe7c6d27f25e9d80a1a094c223f5246f6a6596453e09d7229bf42750b63"
886 895
887[[package]] 896[[package]]
888name = "proc-macro2" 897name = "proc-macro2"
889version = "1.0.10" 898version = "1.0.12"
890source = "registry+https://github.com/rust-lang/crates.io-index" 899source = "registry+https://github.com/rust-lang/crates.io-index"
891checksum = "df246d292ff63439fea9bc8c0a270bed0e390d5ebd4db4ba15aba81111b5abe3" 900checksum = "8872cf6f48eee44265156c111456a700ab3483686b3f96df4cf5481c89157319"
892dependencies = [ 901dependencies = [
893 "unicode-xid", 902 "unicode-xid",
894] 903]
@@ -958,6 +967,7 @@ dependencies = [
958 "jod-thread", 967 "jod-thread",
959 "log", 968 "log",
960 "lsp-types", 969 "lsp-types",
970 "ra_toolchain",
961 "serde_json", 971 "serde_json",
962] 972]
963 973
@@ -1163,6 +1173,7 @@ dependencies = [
1163 "ra_cfg", 1173 "ra_cfg",
1164 "ra_db", 1174 "ra_db",
1165 "ra_proc_macro", 1175 "ra_proc_macro",
1176 "ra_toolchain",
1166 "rustc-hash", 1177 "rustc-hash",
1167 "serde", 1178 "serde",
1168 "serde_json", 1179 "serde_json",
@@ -1195,6 +1206,13 @@ dependencies = [
1195] 1206]
1196 1207
1197[[package]] 1208[[package]]
1209name = "ra_toolchain"
1210version = "0.1.0"
1211dependencies = [
1212 "home",
1213]
1214
1215[[package]]
1198name = "ra_tt" 1216name = "ra_tt"
1199version = "0.1.0" 1217version = "0.1.0"
1200dependencies = [ 1218dependencies = [
@@ -1581,9 +1599,9 @@ checksum = "ab16ced94dbd8a46c82fd81e3ed9a8727dac2977ea869d217bcc4ea1f122e81f"
1581 1599
1582[[package]] 1600[[package]]
1583name = "syn" 1601name = "syn"
1584version = "1.0.18" 1602version = "1.0.19"
1585source = "registry+https://github.com/rust-lang/crates.io-index" 1603source = "registry+https://github.com/rust-lang/crates.io-index"
1586checksum = "410a7488c0a728c7ceb4ad59b9567eb4053d02e8cc7f5c0e0eeeb39518369213" 1604checksum = "e8e5aa70697bb26ee62214ae3288465ecec0000f05182f039b477001f08f5ae7"
1587dependencies = [ 1605dependencies = [
1588 "proc-macro2", 1606 "proc-macro2",
1589 "quote", 1607 "quote",
diff --git a/crates/ra_assists/src/assist_ctx.rs b/crates/ra_assists/src/assist_context.rs
index 871671de2..a680f752b 100644
--- a/crates/ra_assists/src/assist_ctx.rs
+++ b/crates/ra_assists/src/assist_context.rs
@@ -1,4 +1,6 @@
1//! This module defines `AssistCtx` -- the API surface that is exposed to assists. 1//! See `AssistContext`
2
3use algo::find_covering_element;
2use hir::Semantics; 4use hir::Semantics;
3use ra_db::{FileId, FileRange}; 5use ra_db::{FileId, FileRange};
4use ra_fmt::{leading_indent, reindent}; 6use ra_fmt::{leading_indent, reindent};
@@ -7,54 +9,25 @@ use ra_ide_db::{
7 RootDatabase, 9 RootDatabase,
8}; 10};
9use ra_syntax::{ 11use ra_syntax::{
10 algo::{self, find_covering_element, find_node_at_offset, SyntaxRewriter}, 12 algo::{self, find_node_at_offset, SyntaxRewriter},
11 AstNode, SourceFile, SyntaxElement, SyntaxKind, SyntaxNode, SyntaxToken, TextRange, TextSize, 13 AstNode, SourceFile, SyntaxElement, SyntaxKind, SyntaxNode, SyntaxToken, TextRange, TextSize,
12 TokenAtOffset, 14 TokenAtOffset,
13}; 15};
14use ra_text_edit::TextEditBuilder; 16use ra_text_edit::TextEditBuilder;
15 17
16use crate::{AssistId, AssistLabel, GroupLabel, ResolvedAssist}; 18use crate::{Assist, AssistId, GroupLabel, ResolvedAssist};
17
18#[derive(Clone, Debug)]
19pub(crate) struct Assist(pub(crate) Vec<AssistInfo>);
20
21#[derive(Clone, Debug)]
22pub(crate) struct AssistInfo {
23 pub(crate) label: AssistLabel,
24 pub(crate) group_label: Option<GroupLabel>,
25 pub(crate) source_change: Option<SourceChange>,
26}
27
28impl AssistInfo {
29 fn new(label: AssistLabel) -> AssistInfo {
30 AssistInfo { label, group_label: None, source_change: None }
31 }
32
33 fn resolved(self, source_change: SourceChange) -> AssistInfo {
34 AssistInfo { source_change: Some(source_change), ..self }
35 }
36
37 fn with_group(self, group_label: GroupLabel) -> AssistInfo {
38 AssistInfo { group_label: Some(group_label), ..self }
39 }
40
41 pub(crate) fn into_resolved(self) -> Option<ResolvedAssist> {
42 let label = self.label;
43 self.source_change.map(|source_change| ResolvedAssist { label, source_change })
44 }
45}
46 19
47/// `AssistCtx` allows to apply an assist or check if it could be applied. 20/// `AssistContext` allows to apply an assist or check if it could be applied.
48/// 21///
49/// Assists use a somewhat over-engineered approach, given the current needs. The 22/// Assists use a somewhat over-engineered approach, given the current needs.
50/// assists workflow consists of two phases. In the first phase, a user asks for 23/// The assists workflow consists of two phases. In the first phase, a user asks
51/// the list of available assists. In the second phase, the user picks a 24/// for the list of available assists. In the second phase, the user picks a
52/// particular assist and it gets applied. 25/// particular assist and it gets applied.
53/// 26///
54/// There are two peculiarities here: 27/// There are two peculiarities here:
55/// 28///
56/// * first, we ideally avoid computing more things then necessary to answer 29/// * first, we ideally avoid computing more things then necessary to answer "is
57/// "is assist applicable" in the first phase. 30/// assist applicable" in the first phase.
58/// * second, when we are applying assist, we don't have a guarantee that there 31/// * second, when we are applying assist, we don't have a guarantee that there
59/// weren't any changes between the point when user asked for assists and when 32/// weren't any changes between the point when user asked for assists and when
60/// they applied a particular assist. So, when applying assist, we need to do 33/// they applied a particular assist. So, when applying assist, we need to do
@@ -63,152 +36,157 @@ impl AssistInfo {
63/// To avoid repeating the same code twice for both "check" and "apply" 36/// To avoid repeating the same code twice for both "check" and "apply"
64/// functions, we use an approach reminiscent of that of Django's function based 37/// functions, we use an approach reminiscent of that of Django's function based
65/// views dealing with forms. Each assist receives a runtime parameter, 38/// views dealing with forms. Each assist receives a runtime parameter,
66/// `should_compute_edit`. It first check if an edit is applicable (potentially 39/// `resolve`. It first check if an edit is applicable (potentially computing
67/// computing info required to compute the actual edit). If it is applicable, 40/// info required to compute the actual edit). If it is applicable, and
68/// and `should_compute_edit` is `true`, it then computes the actual edit. 41/// `resolve` is `true`, it then computes the actual edit.
69/// 42///
70/// So, to implement the original assists workflow, we can first apply each edit 43/// So, to implement the original assists workflow, we can first apply each edit
71/// with `should_compute_edit = false`, and then applying the selected edit 44/// with `resolve = false`, and then applying the selected edit again, with
72/// again, with `should_compute_edit = true` this time. 45/// `resolve = true` this time.
73/// 46///
74/// Note, however, that we don't actually use such two-phase logic at the 47/// Note, however, that we don't actually use such two-phase logic at the
75/// moment, because the LSP API is pretty awkward in this place, and it's much 48/// moment, because the LSP API is pretty awkward in this place, and it's much
76/// easier to just compute the edit eagerly :-) 49/// easier to just compute the edit eagerly :-)
77#[derive(Clone)] 50pub(crate) struct AssistContext<'a> {
78pub(crate) struct AssistCtx<'a> { 51 pub(crate) sema: Semantics<'a, RootDatabase>,
79 pub(crate) sema: &'a Semantics<'a, RootDatabase>,
80 pub(crate) db: &'a RootDatabase, 52 pub(crate) db: &'a RootDatabase,
81 pub(crate) frange: FileRange, 53 pub(crate) frange: FileRange,
82 source_file: SourceFile, 54 source_file: SourceFile,
83 should_compute_edit: bool,
84} 55}
85 56
86impl<'a> AssistCtx<'a> { 57impl<'a> AssistContext<'a> {
87 pub fn new( 58 pub fn new(sema: Semantics<'a, RootDatabase>, frange: FileRange) -> AssistContext<'a> {
88 sema: &'a Semantics<'a, RootDatabase>,
89 frange: FileRange,
90 should_compute_edit: bool,
91 ) -> AssistCtx<'a> {
92 let source_file = sema.parse(frange.file_id); 59 let source_file = sema.parse(frange.file_id);
93 AssistCtx { sema, db: sema.db, frange, source_file, should_compute_edit } 60 let db = sema.db;
61 AssistContext { sema, db, frange, source_file }
94 } 62 }
95 63
96 pub(crate) fn add_assist( 64 // NB, this ignores active selection.
97 self, 65 pub(crate) fn offset(&self) -> TextSize {
98 id: AssistId, 66 self.frange.range.start()
99 label: impl Into<String>,
100 target: TextRange,
101 f: impl FnOnce(&mut ActionBuilder),
102 ) -> Option<Assist> {
103 let label = AssistLabel::new(id, label.into(), None, target);
104 let change_label = label.label.clone();
105 let mut info = AssistInfo::new(label);
106 if self.should_compute_edit {
107 let source_change = {
108 let mut edit = ActionBuilder::new(&self);
109 f(&mut edit);
110 edit.build(change_label)
111 };
112 info = info.resolved(source_change)
113 };
114
115 Some(Assist(vec![info]))
116 }
117
118 pub(crate) fn add_assist_group(self, group_name: impl Into<String>) -> AssistGroup<'a> {
119 let group = GroupLabel(group_name.into());
120 AssistGroup { ctx: self, group, assists: Vec::new() }
121 } 67 }
122 68
123 pub(crate) fn token_at_offset(&self) -> TokenAtOffset<SyntaxToken> { 69 pub(crate) fn token_at_offset(&self) -> TokenAtOffset<SyntaxToken> {
124 self.source_file.syntax().token_at_offset(self.frange.range.start()) 70 self.source_file.syntax().token_at_offset(self.offset())
125 } 71 }
126
127 pub(crate) fn find_token_at_offset(&self, kind: SyntaxKind) -> Option<SyntaxToken> { 72 pub(crate) fn find_token_at_offset(&self, kind: SyntaxKind) -> Option<SyntaxToken> {
128 self.token_at_offset().find(|it| it.kind() == kind) 73 self.token_at_offset().find(|it| it.kind() == kind)
129 } 74 }
130
131 pub(crate) fn find_node_at_offset<N: AstNode>(&self) -> Option<N> { 75 pub(crate) fn find_node_at_offset<N: AstNode>(&self) -> Option<N> {
132 find_node_at_offset(self.source_file.syntax(), self.frange.range.start()) 76 find_node_at_offset(self.source_file.syntax(), self.offset())
133 } 77 }
134
135 pub(crate) fn find_node_at_offset_with_descend<N: AstNode>(&self) -> Option<N> { 78 pub(crate) fn find_node_at_offset_with_descend<N: AstNode>(&self) -> Option<N> {
136 self.sema 79 self.sema.find_node_at_offset_with_descend(self.source_file.syntax(), self.offset())
137 .find_node_at_offset_with_descend(self.source_file.syntax(), self.frange.range.start())
138 } 80 }
139
140 pub(crate) fn covering_element(&self) -> SyntaxElement { 81 pub(crate) fn covering_element(&self) -> SyntaxElement {
141 find_covering_element(self.source_file.syntax(), self.frange.range) 82 find_covering_element(self.source_file.syntax(), self.frange.range)
142 } 83 }
84 // FIXME: remove
143 pub(crate) fn covering_node_for_range(&self, range: TextRange) -> SyntaxElement { 85 pub(crate) fn covering_node_for_range(&self, range: TextRange) -> SyntaxElement {
144 find_covering_element(self.source_file.syntax(), range) 86 find_covering_element(self.source_file.syntax(), range)
145 } 87 }
146} 88}
147 89
148pub(crate) struct AssistGroup<'a> { 90pub(crate) struct Assists {
149 ctx: AssistCtx<'a>, 91 resolve: bool,
150 group: GroupLabel, 92 file: FileId,
151 assists: Vec<AssistInfo>, 93 buf: Vec<(Assist, Option<SourceChange>)>,
152} 94}
153 95
154impl<'a> AssistGroup<'a> { 96impl Assists {
155 pub(crate) fn add_assist( 97 pub(crate) fn new_resolved(ctx: &AssistContext) -> Assists {
98 Assists { resolve: true, file: ctx.frange.file_id, buf: Vec::new() }
99 }
100 pub(crate) fn new_unresolved(ctx: &AssistContext) -> Assists {
101 Assists { resolve: false, file: ctx.frange.file_id, buf: Vec::new() }
102 }
103
104 pub(crate) fn finish_unresolved(self) -> Vec<Assist> {
105 assert!(!self.resolve);
106 self.finish()
107 .into_iter()
108 .map(|(label, edit)| {
109 assert!(edit.is_none());
110 label
111 })
112 .collect()
113 }
114
115 pub(crate) fn finish_resolved(self) -> Vec<ResolvedAssist> {
116 assert!(self.resolve);
117 self.finish()
118 .into_iter()
119 .map(|(label, edit)| ResolvedAssist { assist: label, source_change: edit.unwrap() })
120 .collect()
121 }
122
123 pub(crate) fn add(
156 &mut self, 124 &mut self,
157 id: AssistId, 125 id: AssistId,
158 label: impl Into<String>, 126 label: impl Into<String>,
159 target: TextRange, 127 target: TextRange,
160 f: impl FnOnce(&mut ActionBuilder), 128 f: impl FnOnce(&mut AssistBuilder),
161 ) { 129 ) -> Option<()> {
162 let label = AssistLabel::new(id, label.into(), Some(self.group.clone()), target); 130 let label = Assist::new(id, label.into(), None, target);
131 self.add_impl(label, f)
132 }
133 pub(crate) fn add_group(
134 &mut self,
135 group: &GroupLabel,
136 id: AssistId,
137 label: impl Into<String>,
138 target: TextRange,
139 f: impl FnOnce(&mut AssistBuilder),
140 ) -> Option<()> {
141 let label = Assist::new(id, label.into(), Some(group.clone()), target);
142 self.add_impl(label, f)
143 }
144 fn add_impl(&mut self, label: Assist, f: impl FnOnce(&mut AssistBuilder)) -> Option<()> {
163 let change_label = label.label.clone(); 145 let change_label = label.label.clone();
164 let mut info = AssistInfo::new(label).with_group(self.group.clone()); 146 let source_change = if self.resolve {
165 if self.ctx.should_compute_edit { 147 let mut builder = AssistBuilder::new(self.file);
166 let source_change = { 148 f(&mut builder);
167 let mut edit = ActionBuilder::new(&self.ctx); 149 Some(builder.finish(change_label))
168 f(&mut edit); 150 } else {
169 edit.build(change_label) 151 None
170 };
171 info = info.resolved(source_change)
172 }; 152 };
173 153
174 self.assists.push(info) 154 self.buf.push((label, source_change));
155 Some(())
175 } 156 }
176 157
177 pub(crate) fn finish(self) -> Option<Assist> { 158 fn finish(mut self) -> Vec<(Assist, Option<SourceChange>)> {
178 if self.assists.is_empty() { 159 self.buf.sort_by_key(|(label, _edit)| label.target.len());
179 None 160 self.buf
180 } else {
181 Some(Assist(self.assists))
182 }
183 } 161 }
184} 162}
185 163
186pub(crate) struct ActionBuilder<'a, 'b> { 164pub(crate) struct AssistBuilder {
187 edit: TextEditBuilder, 165 edit: TextEditBuilder,
188 cursor_position: Option<TextSize>, 166 cursor_position: Option<TextSize>,
189 file: FileId, 167 file: FileId,
190 ctx: &'a AssistCtx<'b>,
191} 168}
192 169
193impl<'a, 'b> ActionBuilder<'a, 'b> { 170impl AssistBuilder {
194 fn new(ctx: &'a AssistCtx<'b>) -> Self { 171 pub(crate) fn new(file: FileId) -> AssistBuilder {
195 Self { 172 AssistBuilder { edit: TextEditBuilder::default(), cursor_position: None, file }
196 edit: TextEditBuilder::default(),
197 cursor_position: None,
198 file: ctx.frange.file_id,
199 ctx,
200 }
201 } 173 }
202 174
203 pub(crate) fn ctx(&self) -> &AssistCtx<'b> { 175 /// Remove specified `range` of text.
204 &self.ctx 176 pub(crate) fn delete(&mut self, range: TextRange) {
177 self.edit.delete(range)
178 }
179 /// Append specified `text` at the given `offset`
180 pub(crate) fn insert(&mut self, offset: TextSize, text: impl Into<String>) {
181 self.edit.insert(offset, text.into())
205 } 182 }
206
207 /// Replaces specified `range` of text with a given string. 183 /// Replaces specified `range` of text with a given string.
208 pub(crate) fn replace(&mut self, range: TextRange, replace_with: impl Into<String>) { 184 pub(crate) fn replace(&mut self, range: TextRange, replace_with: impl Into<String>) {
209 self.edit.replace(range, replace_with.into()) 185 self.edit.replace(range, replace_with.into())
210 } 186 }
211 187 pub(crate) fn replace_ast<N: AstNode>(&mut self, old: N, new: N) {
188 algo::diff(old.syntax(), new.syntax()).into_text_edit(&mut self.edit)
189 }
212 /// Replaces specified `node` of text with a given string, reindenting the 190 /// Replaces specified `node` of text with a given string, reindenting the
213 /// string to maintain `node`'s existing indent. 191 /// string to maintain `node`'s existing indent.
214 // FIXME: remove in favor of ra_syntax::edit::IndentLevel::increase_indent 192 // FIXME: remove in favor of ra_syntax::edit::IndentLevel::increase_indent
@@ -223,42 +201,28 @@ impl<'a, 'b> ActionBuilder<'a, 'b> {
223 } 201 }
224 self.replace(node.text_range(), replace_with) 202 self.replace(node.text_range(), replace_with)
225 } 203 }
226 204 pub(crate) fn rewrite(&mut self, rewriter: SyntaxRewriter) {
227 /// Remove specified `range` of text. 205 let node = rewriter.rewrite_root().unwrap();
228 #[allow(unused)] 206 let new = rewriter.rewrite(&node);
229 pub(crate) fn delete(&mut self, range: TextRange) { 207 algo::diff(&node, &new).into_text_edit(&mut self.edit)
230 self.edit.delete(range)
231 }
232
233 /// Append specified `text` at the given `offset`
234 pub(crate) fn insert(&mut self, offset: TextSize, text: impl Into<String>) {
235 self.edit.insert(offset, text.into())
236 } 208 }
237 209
238 /// Specify desired position of the cursor after the assist is applied. 210 /// Specify desired position of the cursor after the assist is applied.
239 pub(crate) fn set_cursor(&mut self, offset: TextSize) { 211 pub(crate) fn set_cursor(&mut self, offset: TextSize) {
240 self.cursor_position = Some(offset) 212 self.cursor_position = Some(offset)
241 } 213 }
214 // FIXME: better API
215 pub(crate) fn set_file(&mut self, assist_file: FileId) {
216 self.file = assist_file;
217 }
242 218
219 // FIXME: kill this API
243 /// Get access to the raw `TextEditBuilder`. 220 /// Get access to the raw `TextEditBuilder`.
244 pub(crate) fn text_edit_builder(&mut self) -> &mut TextEditBuilder { 221 pub(crate) fn text_edit_builder(&mut self) -> &mut TextEditBuilder {
245 &mut self.edit 222 &mut self.edit
246 } 223 }
247 224
248 pub(crate) fn replace_ast<N: AstNode>(&mut self, old: N, new: N) { 225 fn finish(self, change_label: String) -> SourceChange {
249 algo::diff(old.syntax(), new.syntax()).into_text_edit(&mut self.edit)
250 }
251 pub(crate) fn rewrite(&mut self, rewriter: SyntaxRewriter) {
252 let node = rewriter.rewrite_root().unwrap();
253 let new = rewriter.rewrite(&node);
254 algo::diff(&node, &new).into_text_edit(&mut self.edit)
255 }
256
257 pub(crate) fn set_file(&mut self, assist_file: FileId) {
258 self.file = assist_file;
259 }
260
261 fn build(self, change_label: String) -> SourceChange {
262 let edit = self.edit.finish(); 226 let edit = self.edit.finish();
263 if edit.is_empty() && self.cursor_position.is_none() { 227 if edit.is_empty() && self.cursor_position.is_none() {
264 panic!("Only call `add_assist` if the assist can be applied") 228 panic!("Only call `add_assist` if the assist can be applied")
diff --git a/crates/ra_assists/src/handlers/add_custom_impl.rs b/crates/ra_assists/src/handlers/add_custom_impl.rs
index 869d4dc04..795a225a4 100644
--- a/crates/ra_assists/src/handlers/add_custom_impl.rs
+++ b/crates/ra_assists/src/handlers/add_custom_impl.rs
@@ -6,7 +6,10 @@ use ra_syntax::{
6}; 6};
7use stdx::SepBy; 7use stdx::SepBy;
8 8
9use crate::{Assist, AssistCtx, AssistId}; 9use crate::{
10 assist_context::{AssistContext, Assists},
11 AssistId,
12};
10 13
11// Assist: add_custom_impl 14// Assist: add_custom_impl
12// 15//
@@ -25,7 +28,7 @@ use crate::{Assist, AssistCtx, AssistId};
25// 28//
26// } 29// }
27// ``` 30// ```
28pub(crate) fn add_custom_impl(ctx: AssistCtx) -> Option<Assist> { 31pub(crate) fn add_custom_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
29 let input = ctx.find_node_at_offset::<ast::AttrInput>()?; 32 let input = ctx.find_node_at_offset::<ast::AttrInput>()?;
30 let attr = input.syntax().parent().and_then(ast::Attr::cast)?; 33 let attr = input.syntax().parent().and_then(ast::Attr::cast)?;
31 34
@@ -49,7 +52,7 @@ pub(crate) fn add_custom_impl(ctx: AssistCtx) -> Option<Assist> {
49 format!("Add custom impl '{}' for '{}'", trait_token.text().as_str(), annotated_name); 52 format!("Add custom impl '{}' for '{}'", trait_token.text().as_str(), annotated_name);
50 53
51 let target = attr.syntax().text_range(); 54 let target = attr.syntax().text_range();
52 ctx.add_assist(AssistId("add_custom_impl"), label, target, |edit| { 55 acc.add(AssistId("add_custom_impl"), label, target, |edit| {
53 let new_attr_input = input 56 let new_attr_input = input
54 .syntax() 57 .syntax()
55 .descendants_with_tokens() 58 .descendants_with_tokens()
diff --git a/crates/ra_assists/src/handlers/add_derive.rs b/crates/ra_assists/src/handlers/add_derive.rs
index 2a6bb1cae..fb08c19e9 100644
--- a/crates/ra_assists/src/handlers/add_derive.rs
+++ b/crates/ra_assists/src/handlers/add_derive.rs
@@ -4,7 +4,7 @@ use ra_syntax::{
4 TextSize, 4 TextSize,
5}; 5};
6 6
7use crate::{Assist, AssistCtx, AssistId}; 7use crate::{AssistContext, AssistId, Assists};
8 8
9// Assist: add_derive 9// Assist: add_derive
10// 10//
@@ -24,11 +24,11 @@ use crate::{Assist, AssistCtx, AssistId};
24// y: u32, 24// y: u32,
25// } 25// }
26// ``` 26// ```
27pub(crate) fn add_derive(ctx: AssistCtx) -> Option<Assist> { 27pub(crate) fn add_derive(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
28 let nominal = ctx.find_node_at_offset::<ast::NominalDef>()?; 28 let nominal = ctx.find_node_at_offset::<ast::NominalDef>()?;
29 let node_start = derive_insertion_offset(&nominal)?; 29 let node_start = derive_insertion_offset(&nominal)?;
30 let target = nominal.syntax().text_range(); 30 let target = nominal.syntax().text_range();
31 ctx.add_assist(AssistId("add_derive"), "Add `#[derive]`", target, |edit| { 31 acc.add(AssistId("add_derive"), "Add `#[derive]`", target, |edit| {
32 let derive_attr = nominal 32 let derive_attr = nominal
33 .attrs() 33 .attrs()
34 .filter_map(|x| x.as_simple_call()) 34 .filter_map(|x| x.as_simple_call())
@@ -57,9 +57,10 @@ fn derive_insertion_offset(nominal: &ast::NominalDef) -> Option<TextSize> {
57 57
58#[cfg(test)] 58#[cfg(test)]
59mod tests { 59mod tests {
60 use super::*;
61 use crate::tests::{check_assist, check_assist_target}; 60 use crate::tests::{check_assist, check_assist_target};
62 61
62 use super::*;
63
63 #[test] 64 #[test]
64 fn add_derive_new() { 65 fn add_derive_new() {
65 check_assist( 66 check_assist(
diff --git a/crates/ra_assists/src/handlers/add_explicit_type.rs b/crates/ra_assists/src/handlers/add_explicit_type.rs
index a59ec16b2..146cc75df 100644
--- a/crates/ra_assists/src/handlers/add_explicit_type.rs
+++ b/crates/ra_assists/src/handlers/add_explicit_type.rs
@@ -4,7 +4,7 @@ use ra_syntax::{
4 TextRange, 4 TextRange,
5}; 5};
6 6
7use crate::{Assist, AssistCtx, AssistId}; 7use crate::{AssistContext, AssistId, Assists};
8 8
9// Assist: add_explicit_type 9// Assist: add_explicit_type
10// 10//
@@ -21,8 +21,9 @@ use crate::{Assist, AssistCtx, AssistId};
21// let x: i32 = 92; 21// let x: i32 = 92;
22// } 22// }
23// ``` 23// ```
24pub(crate) fn add_explicit_type(ctx: AssistCtx) -> Option<Assist> { 24pub(crate) fn add_explicit_type(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
25 let stmt = ctx.find_node_at_offset::<LetStmt>()?; 25 let stmt = ctx.find_node_at_offset::<LetStmt>()?;
26 let module = ctx.sema.scope(stmt.syntax()).module()?;
26 let expr = stmt.initializer()?; 27 let expr = stmt.initializer()?;
27 let pat = stmt.pat()?; 28 let pat = stmt.pat()?;
28 // Must be a binding 29 // Must be a binding
@@ -57,17 +58,17 @@ pub(crate) fn add_explicit_type(ctx: AssistCtx) -> Option<Assist> {
57 return None; 58 return None;
58 } 59 }
59 60
60 let db = ctx.db; 61 let inferred_type = ty.display_source_code(ctx.db, module.into()).ok()?;
61 let new_type_string = ty.display_truncated(db, None).to_string(); 62 acc.add(
62 ctx.add_assist(
63 AssistId("add_explicit_type"), 63 AssistId("add_explicit_type"),
64 format!("Insert explicit type '{}'", new_type_string), 64 format!("Insert explicit type '{}'", inferred_type),
65 pat_range, 65 pat_range,
66 |edit| { 66 |builder| match ascribed_ty {
67 if let Some(ascribed_ty) = ascribed_ty { 67 Some(ascribed_ty) => {
68 edit.replace(ascribed_ty.syntax().text_range(), new_type_string); 68 builder.replace(ascribed_ty.syntax().text_range(), inferred_type);
69 } else { 69 }
70 edit.insert(name_range.end(), format!(": {}", new_type_string)); 70 None => {
71 builder.insert(name_range.end(), format!(": {}", inferred_type));
71 } 72 }
72 }, 73 },
73 ) 74 )
@@ -208,7 +209,7 @@ struct Test<K, T = u8> {
208} 209}
209 210
210fn main() { 211fn main() {
211 let test<|>: Test<i32> = Test { t: 23, k: 33 }; 212 let test<|>: Test<i32, u8> = Test { t: 23, k: 33 };
212}"#, 213}"#,
213 ); 214 );
214 } 215 }
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 81deb3dfa..6a49b7dbd 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
@@ -1,13 +1,10 @@
1use ra_ide_db::RootDatabase; 1use ra_ide_db::RootDatabase;
2use ra_syntax::{ 2use ra_syntax::ast::{self, AstNode, NameOwner};
3 ast::{self, AstNode, NameOwner},
4 TextSize,
5};
6use stdx::format_to; 3use stdx::format_to;
7
8use crate::{utils::FamousDefs, Assist, AssistCtx, AssistId};
9use test_utils::tested_by; 4use test_utils::tested_by;
10 5
6use crate::{utils::FamousDefs, AssistContext, AssistId, Assists};
7
11// Assist add_from_impl_for_enum 8// Assist add_from_impl_for_enum
12// 9//
13// Adds a From impl for an enum variant with one tuple field 10// Adds a From impl for an enum variant with one tuple field
@@ -25,7 +22,7 @@ use test_utils::tested_by;
25// } 22// }
26// } 23// }
27// ``` 24// ```
28pub(crate) fn add_from_impl_for_enum(ctx: AssistCtx) -> Option<Assist> { 25pub(crate) fn add_from_impl_for_enum(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
29 let variant = ctx.find_node_at_offset::<ast::EnumVariant>()?; 26 let variant = ctx.find_node_at_offset::<ast::EnumVariant>()?;
30 let variant_name = variant.name()?; 27 let variant_name = variant.name()?;
31 let enum_name = variant.parent_enum().name()?; 28 let enum_name = variant.parent_enum().name()?;
@@ -42,13 +39,13 @@ pub(crate) fn add_from_impl_for_enum(ctx: AssistCtx) -> Option<Assist> {
42 _ => return None, 39 _ => return None,
43 }; 40 };
44 41
45 if existing_from_impl(ctx.sema, &variant).is_some() { 42 if existing_from_impl(&ctx.sema, &variant).is_some() {
46 tested_by!(test_add_from_impl_already_exists); 43 tested_by!(test_add_from_impl_already_exists);
47 return None; 44 return None;
48 } 45 }
49 46
50 let target = variant.syntax().text_range(); 47 let target = variant.syntax().text_range();
51 ctx.add_assist( 48 acc.add(
52 AssistId("add_from_impl_for_enum"), 49 AssistId("add_from_impl_for_enum"),
53 "Add From impl for this enum variant", 50 "Add From impl for this enum variant",
54 target, 51 target,
@@ -69,7 +66,6 @@ impl From<{0}> for {1} {{
69 variant_name 66 variant_name
70 ); 67 );
71 edit.insert(start_offset, buf); 68 edit.insert(start_offset, buf);
72 edit.set_cursor(start_offset + TextSize::of("\n\n"));
73 }, 69 },
74 ) 70 )
75} 71}
@@ -97,19 +93,20 @@ fn existing_from_impl(
97 93
98#[cfg(test)] 94#[cfg(test)]
99mod tests { 95mod tests {
100 use super::*; 96 use test_utils::covers;
101 97
102 use crate::tests::{check_assist, check_assist_not_applicable}; 98 use crate::tests::{check_assist, check_assist_not_applicable};
103 use test_utils::covers; 99
100 use super::*;
104 101
105 #[test] 102 #[test]
106 fn test_add_from_impl_for_enum() { 103 fn test_add_from_impl_for_enum() {
107 check_assist( 104 check_assist(
108 add_from_impl_for_enum, 105 add_from_impl_for_enum,
109 "enum A { <|>One(u32) }", 106 "enum A { <|>One(u32) }",
110 r#"enum A { One(u32) } 107 r#"enum A { <|>One(u32) }
111 108
112<|>impl From<u32> for A { 109impl From<u32> for A {
113 fn from(v: u32) -> Self { 110 fn from(v: u32) -> Self {
114 A::One(v) 111 A::One(v)
115 } 112 }
@@ -121,10 +118,10 @@ mod tests {
121 fn test_add_from_impl_for_enum_complicated_path() { 118 fn test_add_from_impl_for_enum_complicated_path() {
122 check_assist( 119 check_assist(
123 add_from_impl_for_enum, 120 add_from_impl_for_enum,
124 "enum A { <|>One(foo::bar::baz::Boo) }", 121 r#"enum A { <|>One(foo::bar::baz::Boo) }"#,
125 r#"enum A { One(foo::bar::baz::Boo) } 122 r#"enum A { <|>One(foo::bar::baz::Boo) }
126 123
127<|>impl From<foo::bar::baz::Boo> for A { 124impl From<foo::bar::baz::Boo> for A {
128 fn from(v: foo::bar::baz::Boo) -> Self { 125 fn from(v: foo::bar::baz::Boo) -> Self {
129 A::One(v) 126 A::One(v)
130 } 127 }
@@ -184,9 +181,9 @@ impl From<String> for A {
184pub trait From<T> { 181pub trait From<T> {
185 fn from(T) -> Self; 182 fn from(T) -> Self;
186}"#, 183}"#,
187 r#"enum A { One(u32), Two(String), } 184 r#"enum A { <|>One(u32), Two(String), }
188 185
189<|>impl From<u32> for A { 186impl From<u32> for A {
190 fn from(v: u32) -> Self { 187 fn from(v: u32) -> Self {
191 A::One(v) 188 A::One(v)
192 } 189 }
diff --git a/crates/ra_assists/src/handlers/add_function.rs b/crates/ra_assists/src/handlers/add_function.rs
index 278079665..de016ae4e 100644
--- a/crates/ra_assists/src/handlers/add_function.rs
+++ b/crates/ra_assists/src/handlers/add_function.rs
@@ -1,14 +1,17 @@
1use hir::HirDisplay;
2use ra_db::FileId;
1use ra_syntax::{ 3use ra_syntax::{
2 ast::{self, AstNode}, 4 ast::{
5 self,
6 edit::{AstNodeEdit, IndentLevel},
7 ArgListOwner, AstNode, ModuleItemOwner,
8 },
3 SyntaxKind, SyntaxNode, TextSize, 9 SyntaxKind, SyntaxNode, TextSize,
4}; 10};
5
6use crate::{Assist, AssistCtx, AssistId};
7use ast::{edit::IndentLevel, ArgListOwner, ModuleItemOwner};
8use hir::HirDisplay;
9use ra_db::FileId;
10use rustc_hash::{FxHashMap, FxHashSet}; 11use rustc_hash::{FxHashMap, FxHashSet};
11 12
13use crate::{AssistContext, AssistId, Assists};
14
12// Assist: add_function 15// Assist: add_function
13// 16//
14// Adds a stub function with a signature matching the function under the cursor. 17// Adds a stub function with a signature matching the function under the cursor.
@@ -34,7 +37,7 @@ use rustc_hash::{FxHashMap, FxHashSet};
34// } 37// }
35// 38//
36// ``` 39// ```
37pub(crate) fn add_function(ctx: AssistCtx) -> Option<Assist> { 40pub(crate) fn add_function(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
38 let path_expr: ast::PathExpr = ctx.find_node_at_offset()?; 41 let path_expr: ast::PathExpr = ctx.find_node_at_offset()?;
39 let call = path_expr.syntax().parent().and_then(ast::CallExpr::cast)?; 42 let call = path_expr.syntax().parent().and_then(ast::CallExpr::cast)?;
40 let path = path_expr.path()?; 43 let path = path_expr.path()?;
@@ -44,22 +47,18 @@ pub(crate) fn add_function(ctx: AssistCtx) -> Option<Assist> {
44 return None; 47 return None;
45 } 48 }
46 49
47 let target_module = if let Some(qualifier) = path.qualifier() { 50 let target_module = match path.qualifier() {
48 if let Some(hir::PathResolution::Def(hir::ModuleDef::Module(module))) = 51 Some(qualifier) => match ctx.sema.resolve_path(&qualifier) {
49 ctx.sema.resolve_path(&qualifier) 52 Some(hir::PathResolution::Def(hir::ModuleDef::Module(module))) => Some(module),
50 { 53 _ => return None,
51 Some(module.definition_source(ctx.sema.db)) 54 },
52 } else { 55 None => None,
53 return None;
54 }
55 } else {
56 None
57 }; 56 };
58 57
59 let function_builder = FunctionBuilder::from_call(&ctx, &call, &path, target_module)?; 58 let function_builder = FunctionBuilder::from_call(&ctx, &call, &path, target_module)?;
60 59
61 let target = call.syntax().text_range(); 60 let target = call.syntax().text_range();
62 ctx.add_assist(AssistId("add_function"), "Add function", target, |edit| { 61 acc.add(AssistId("add_function"), "Add function", target, |edit| {
63 let function_template = function_builder.render(); 62 let function_template = function_builder.render();
64 edit.set_file(function_template.file); 63 edit.set_file(function_template.file);
65 edit.set_cursor(function_template.cursor_offset); 64 edit.set_cursor(function_template.cursor_offset);
@@ -84,25 +83,29 @@ struct FunctionBuilder {
84} 83}
85 84
86impl FunctionBuilder { 85impl FunctionBuilder {
87 /// Prepares a generated function that matches `call` in `generate_in` 86 /// Prepares a generated function that matches `call`.
88 /// (or as close to `call` as possible, if `generate_in` is `None`) 87 /// The function is generated in `target_module` or next to `call`
89 fn from_call( 88 fn from_call(
90 ctx: &AssistCtx, 89 ctx: &AssistContext,
91 call: &ast::CallExpr, 90 call: &ast::CallExpr,
92 path: &ast::Path, 91 path: &ast::Path,
93 target_module: Option<hir::InFile<hir::ModuleSource>>, 92 target_module: Option<hir::Module>,
94 ) -> Option<Self> { 93 ) -> Option<Self> {
95 let needs_pub = target_module.is_some();
96 let mut file = ctx.frange.file_id; 94 let mut file = ctx.frange.file_id;
97 let target = if let Some(target_module) = target_module { 95 let target = match &target_module {
98 let (in_file, target) = next_space_for_fn_in_module(ctx.sema.db, target_module)?; 96 Some(target_module) => {
99 file = in_file; 97 let module_source = target_module.definition_source(ctx.db);
100 target 98 let (in_file, target) = next_space_for_fn_in_module(ctx.sema.db, &module_source)?;
101 } else { 99 file = in_file;
102 next_space_for_fn_after_call_site(&call)? 100 target
101 }
102 None => next_space_for_fn_after_call_site(&call)?,
103 }; 103 };
104 let needs_pub = target_module.is_some();
105 let target_module = target_module.or_else(|| ctx.sema.scope(target.syntax()).module())?;
104 let fn_name = fn_name(&path)?; 106 let fn_name = fn_name(&path)?;
105 let (type_params, params) = fn_args(ctx, &call)?; 107 let (type_params, params) = fn_args(ctx, target_module, &call)?;
108
106 Some(Self { target, fn_name, type_params, params, file, needs_pub }) 109 Some(Self { target, fn_name, type_params, params, file, needs_pub })
107 } 110 }
108 111
@@ -117,17 +120,16 @@ impl FunctionBuilder {
117 let (fn_def, insert_offset) = match self.target { 120 let (fn_def, insert_offset) = match self.target {
118 GeneratedFunctionTarget::BehindItem(it) => { 121 GeneratedFunctionTarget::BehindItem(it) => {
119 let with_leading_blank_line = ast::make::add_leading_newlines(2, fn_def); 122 let with_leading_blank_line = ast::make::add_leading_newlines(2, fn_def);
120 let indented = IndentLevel::from_node(&it).increase_indent(with_leading_blank_line); 123 let indented = with_leading_blank_line.indent(IndentLevel::from_node(&it));
121 (indented, it.text_range().end()) 124 (indented, it.text_range().end())
122 } 125 }
123 GeneratedFunctionTarget::InEmptyItemList(it) => { 126 GeneratedFunctionTarget::InEmptyItemList(it) => {
124 let indent_once = IndentLevel(1); 127 let indent_once = IndentLevel(1);
125 let indent = IndentLevel::from_node(it.syntax()); 128 let indent = IndentLevel::from_node(it.syntax());
126
127 let fn_def = ast::make::add_leading_newlines(1, fn_def); 129 let fn_def = ast::make::add_leading_newlines(1, fn_def);
128 let fn_def = indent_once.increase_indent(fn_def); 130 let fn_def = fn_def.indent(indent_once);
129 let fn_def = ast::make::add_trailing_newlines(1, fn_def); 131 let fn_def = ast::make::add_trailing_newlines(1, fn_def);
130 let fn_def = indent.increase_indent(fn_def); 132 let fn_def = fn_def.indent(indent);
131 (fn_def, it.syntax().text_range().start() + TextSize::of('{')) 133 (fn_def, it.syntax().text_range().start() + TextSize::of('{'))
132 } 134 }
133 }; 135 };
@@ -145,6 +147,15 @@ enum GeneratedFunctionTarget {
145 InEmptyItemList(ast::ItemList), 147 InEmptyItemList(ast::ItemList),
146} 148}
147 149
150impl GeneratedFunctionTarget {
151 fn syntax(&self) -> &SyntaxNode {
152 match self {
153 GeneratedFunctionTarget::BehindItem(it) => it,
154 GeneratedFunctionTarget::InEmptyItemList(it) => it.syntax(),
155 }
156 }
157}
158
148fn fn_name(call: &ast::Path) -> Option<ast::Name> { 159fn fn_name(call: &ast::Path) -> Option<ast::Name> {
149 let name = call.segment()?.syntax().to_string(); 160 let name = call.segment()?.syntax().to_string();
150 Some(ast::make::name(&name)) 161 Some(ast::make::name(&name))
@@ -152,18 +163,18 @@ fn fn_name(call: &ast::Path) -> Option<ast::Name> {
152 163
153/// Computes the type variables and arguments required for the generated function 164/// Computes the type variables and arguments required for the generated function
154fn fn_args( 165fn fn_args(
155 ctx: &AssistCtx, 166 ctx: &AssistContext,
167 target_module: hir::Module,
156 call: &ast::CallExpr, 168 call: &ast::CallExpr,
157) -> Option<(Option<ast::TypeParamList>, ast::ParamList)> { 169) -> Option<(Option<ast::TypeParamList>, ast::ParamList)> {
158 let mut arg_names = Vec::new(); 170 let mut arg_names = Vec::new();
159 let mut arg_types = Vec::new(); 171 let mut arg_types = Vec::new();
160 for arg in call.arg_list()?.args() { 172 for arg in call.arg_list()?.args() {
161 let arg_name = match fn_arg_name(&arg) { 173 arg_names.push(match fn_arg_name(&arg) {
162 Some(name) => name, 174 Some(name) => name,
163 None => String::from("arg"), 175 None => String::from("arg"),
164 }; 176 });
165 arg_names.push(arg_name); 177 arg_types.push(match fn_arg_type(ctx, target_module, &arg) {
166 arg_types.push(match fn_arg_type(ctx, &arg) {
167 Some(ty) => ty, 178 Some(ty) => ty,
168 None => String::from("()"), 179 None => String::from("()"),
169 }); 180 });
@@ -219,12 +230,21 @@ fn fn_arg_name(fn_arg: &ast::Expr) -> Option<String> {
219 } 230 }
220} 231}
221 232
222fn fn_arg_type(ctx: &AssistCtx, fn_arg: &ast::Expr) -> Option<String> { 233fn fn_arg_type(
234 ctx: &AssistContext,
235 target_module: hir::Module,
236 fn_arg: &ast::Expr,
237) -> Option<String> {
223 let ty = ctx.sema.type_of_expr(fn_arg)?; 238 let ty = ctx.sema.type_of_expr(fn_arg)?;
224 if ty.is_unknown() { 239 if ty.is_unknown() {
225 return None; 240 return None;
226 } 241 }
227 Some(ty.display(ctx.sema.db).to_string()) 242
243 if let Ok(rendered) = ty.display_source_code(ctx.db, target_module.into()) {
244 Some(rendered)
245 } else {
246 None
247 }
228} 248}
229 249
230/// Returns the position inside the current mod or file 250/// Returns the position inside the current mod or file
@@ -253,10 +273,10 @@ fn next_space_for_fn_after_call_site(expr: &ast::CallExpr) -> Option<GeneratedFu
253 273
254fn next_space_for_fn_in_module( 274fn next_space_for_fn_in_module(
255 db: &dyn hir::db::AstDatabase, 275 db: &dyn hir::db::AstDatabase,
256 module: hir::InFile<hir::ModuleSource>, 276 module_source: &hir::InFile<hir::ModuleSource>,
257) -> Option<(FileId, GeneratedFunctionTarget)> { 277) -> Option<(FileId, GeneratedFunctionTarget)> {
258 let file = module.file_id.original_file(db); 278 let file = module_source.file_id.original_file(db);
259 let assist_item = match module.value { 279 let assist_item = match &module_source.value {
260 hir::ModuleSource::SourceFile(it) => { 280 hir::ModuleSource::SourceFile(it) => {
261 if let Some(last_item) = it.items().last() { 281 if let Some(last_item) = it.items().last() {
262 GeneratedFunctionTarget::BehindItem(last_item.syntax().clone()) 282 GeneratedFunctionTarget::BehindItem(last_item.syntax().clone())
@@ -600,8 +620,33 @@ fn bar(foo: impl Foo) {
600 } 620 }
601 621
602 #[test] 622 #[test]
603 #[ignore] 623 fn borrowed_arg() {
604 // FIXME print paths properly to make this test pass 624 check_assist(
625 add_function,
626 r"
627struct Baz;
628fn baz() -> Baz { todo!() }
629
630fn foo() {
631 bar<|>(&baz())
632}
633",
634 r"
635struct Baz;
636fn baz() -> Baz { todo!() }
637
638fn foo() {
639 bar(&baz())
640}
641
642fn bar(baz: &Baz) {
643 <|>todo!()
644}
645",
646 )
647 }
648
649 #[test]
605 fn add_function_with_qualified_path_arg() { 650 fn add_function_with_qualified_path_arg() {
606 check_assist( 651 check_assist(
607 add_function, 652 add_function,
@@ -610,10 +655,8 @@ mod Baz {
610 pub struct Bof; 655 pub struct Bof;
611 pub fn baz() -> Bof { Bof } 656 pub fn baz() -> Bof { Bof }
612} 657}
613mod Foo { 658fn foo() {
614 fn foo() { 659 <|>bar(Baz::baz())
615 <|>bar(super::Baz::baz())
616 }
617} 660}
618", 661",
619 r" 662 r"
@@ -621,14 +664,12 @@ mod Baz {
621 pub struct Bof; 664 pub struct Bof;
622 pub fn baz() -> Bof { Bof } 665 pub fn baz() -> Bof { Bof }
623} 666}
624mod Foo { 667fn foo() {
625 fn foo() { 668 bar(Baz::baz())
626 bar(super::Baz::baz()) 669}
627 }
628 670
629 fn bar(baz: super::Baz::Bof) { 671fn bar(baz: Baz::Bof) {
630 <|>todo!() 672 <|>todo!()
631 }
632} 673}
633", 674",
634 ) 675 )
@@ -810,6 +851,40 @@ fn foo() {
810 } 851 }
811 852
812 #[test] 853 #[test]
854 #[ignore]
855 // Ignored until local imports are supported.
856 // See https://github.com/rust-analyzer/rust-analyzer/issues/1165
857 fn qualified_path_uses_correct_scope() {
858 check_assist(
859 add_function,
860 "
861mod foo {
862 pub struct Foo;
863}
864fn bar() {
865 use foo::Foo;
866 let foo = Foo;
867 baz<|>(foo)
868}
869",
870 "
871mod foo {
872 pub struct Foo;
873}
874fn bar() {
875 use foo::Foo;
876 let foo = Foo;
877 baz(foo)
878}
879
880fn baz(foo: foo::Foo) {
881 <|>todo!()
882}
883",
884 )
885 }
886
887 #[test]
813 fn add_function_in_module_containing_other_items() { 888 fn add_function_in_module_containing_other_items() {
814 check_assist( 889 check_assist(
815 add_function, 890 add_function,
@@ -921,21 +996,6 @@ fn bar(baz: ()) {}
921 } 996 }
922 997
923 #[test] 998 #[test]
924 fn add_function_not_applicable_if_function_path_not_singleton() {
925 // In the future this assist could be extended to generate functions
926 // if the path is in the same crate (or even the same workspace).
927 // For the beginning, I think this is fine.
928 check_assist_not_applicable(
929 add_function,
930 r"
931fn foo() {
932 other_crate::bar<|>();
933}
934 ",
935 )
936 }
937
938 #[test]
939 #[ignore] 999 #[ignore]
940 fn create_method_with_no_args() { 1000 fn create_method_with_no_args() {
941 check_assist( 1001 check_assist(
diff --git a/crates/ra_assists/src/handlers/add_impl.rs b/crates/ra_assists/src/handlers/add_impl.rs
index 557344ebb..df114a0d8 100644
--- a/crates/ra_assists/src/handlers/add_impl.rs
+++ b/crates/ra_assists/src/handlers/add_impl.rs
@@ -4,7 +4,7 @@ use ra_syntax::{
4}; 4};
5use stdx::{format_to, SepBy}; 5use stdx::{format_to, SepBy};
6 6
7use crate::{Assist, AssistCtx, AssistId}; 7use crate::{AssistContext, AssistId, Assists};
8 8
9// Assist: add_impl 9// Assist: add_impl
10// 10//
@@ -25,43 +25,36 @@ use crate::{Assist, AssistCtx, AssistId};
25// 25//
26// } 26// }
27// ``` 27// ```
28pub(crate) fn add_impl(ctx: AssistCtx) -> Option<Assist> { 28pub(crate) fn add_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
29 let nominal = ctx.find_node_at_offset::<ast::NominalDef>()?; 29 let nominal = ctx.find_node_at_offset::<ast::NominalDef>()?;
30 let name = nominal.name()?; 30 let name = nominal.name()?;
31 let target = nominal.syntax().text_range(); 31 let target = nominal.syntax().text_range();
32 ctx.add_assist( 32 acc.add(AssistId("add_impl"), format!("Implement {}", name.text().as_str()), target, |edit| {
33 AssistId("add_impl"), 33 let type_params = nominal.type_param_list();
34 format!("Implement {}", name.text().as_str()), 34 let start_offset = nominal.syntax().text_range().end();
35 target, 35 let mut buf = String::new();
36 |edit| { 36 buf.push_str("\n\nimpl");
37 let type_params = nominal.type_param_list(); 37 if let Some(type_params) = &type_params {
38 let start_offset = nominal.syntax().text_range().end(); 38 format_to!(buf, "{}", type_params.syntax());
39 let mut buf = String::new(); 39 }
40 buf.push_str("\n\nimpl"); 40 buf.push_str(" ");
41 if let Some(type_params) = &type_params { 41 buf.push_str(name.text().as_str());
42 format_to!(buf, "{}", type_params.syntax()); 42 if let Some(type_params) = type_params {
43 } 43 let lifetime_params = type_params
44 buf.push_str(" "); 44 .lifetime_params()
45 buf.push_str(name.text().as_str()); 45 .filter_map(|it| it.lifetime_token())
46 if let Some(type_params) = type_params { 46 .map(|it| it.text().clone());
47 let lifetime_params = type_params 47 let type_params =
48 .lifetime_params() 48 type_params.type_params().filter_map(|it| it.name()).map(|it| it.text().clone());
49 .filter_map(|it| it.lifetime_token())
50 .map(|it| it.text().clone());
51 let type_params = type_params
52 .type_params()
53 .filter_map(|it| it.name())
54 .map(|it| it.text().clone());
55 49
56 let generic_params = lifetime_params.chain(type_params).sep_by(", "); 50 let generic_params = lifetime_params.chain(type_params).sep_by(", ");
57 format_to!(buf, "<{}>", generic_params) 51 format_to!(buf, "<{}>", generic_params)
58 } 52 }
59 buf.push_str(" {\n"); 53 buf.push_str(" {\n");
60 edit.set_cursor(start_offset + TextSize::of(&buf)); 54 edit.set_cursor(start_offset + TextSize::of(&buf));
61 buf.push_str("\n}"); 55 buf.push_str("\n}");
62 edit.insert(start_offset, buf); 56 edit.insert(start_offset, buf);
63 }, 57 })
64 )
65} 58}
66 59
67#[cfg(test)] 60#[cfg(test)]
diff --git a/crates/ra_assists/src/handlers/add_missing_impl_members.rs b/crates/ra_assists/src/handlers/add_missing_impl_members.rs
index 7df786590..c1ce87914 100644
--- a/crates/ra_assists/src/handlers/add_missing_impl_members.rs
+++ b/crates/ra_assists/src/handlers/add_missing_impl_members.rs
@@ -2,16 +2,17 @@ use hir::HasSource;
2use ra_syntax::{ 2use ra_syntax::{
3 ast::{ 3 ast::{
4 self, 4 self,
5 edit::{self, IndentLevel}, 5 edit::{self, AstNodeEdit, IndentLevel},
6 make, AstNode, NameOwner, 6 make, AstNode, NameOwner,
7 }, 7 },
8 SmolStr, 8 SmolStr,
9}; 9};
10 10
11use crate::{ 11use crate::{
12 assist_context::{AssistContext, Assists},
12 ast_transform::{self, AstTransform, QualifyPaths, SubstituteTypeParams}, 13 ast_transform::{self, AstTransform, QualifyPaths, SubstituteTypeParams},
13 utils::{get_missing_assoc_items, resolve_target_trait}, 14 utils::{get_missing_assoc_items, resolve_target_trait},
14 Assist, AssistCtx, AssistId, 15 AssistId,
15}; 16};
16 17
17#[derive(PartialEq)] 18#[derive(PartialEq)]
@@ -50,8 +51,9 @@ enum AddMissingImplMembersMode {
50// 51//
51// } 52// }
52// ``` 53// ```
53pub(crate) fn add_missing_impl_members(ctx: AssistCtx) -> Option<Assist> { 54pub(crate) fn add_missing_impl_members(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
54 add_missing_impl_members_inner( 55 add_missing_impl_members_inner(
56 acc,
55 ctx, 57 ctx,
56 AddMissingImplMembersMode::NoDefaultMethods, 58 AddMissingImplMembersMode::NoDefaultMethods,
57 "add_impl_missing_members", 59 "add_impl_missing_members",
@@ -91,8 +93,9 @@ pub(crate) fn add_missing_impl_members(ctx: AssistCtx) -> Option<Assist> {
91// 93//
92// } 94// }
93// ``` 95// ```
94pub(crate) fn add_missing_default_members(ctx: AssistCtx) -> Option<Assist> { 96pub(crate) fn add_missing_default_members(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
95 add_missing_impl_members_inner( 97 add_missing_impl_members_inner(
98 acc,
96 ctx, 99 ctx,
97 AddMissingImplMembersMode::DefaultMethodsOnly, 100 AddMissingImplMembersMode::DefaultMethodsOnly,
98 "add_impl_default_members", 101 "add_impl_default_members",
@@ -101,11 +104,12 @@ pub(crate) fn add_missing_default_members(ctx: AssistCtx) -> Option<Assist> {
101} 104}
102 105
103fn add_missing_impl_members_inner( 106fn add_missing_impl_members_inner(
104 ctx: AssistCtx, 107 acc: &mut Assists,
108 ctx: &AssistContext,
105 mode: AddMissingImplMembersMode, 109 mode: AddMissingImplMembersMode,
106 assist_id: &'static str, 110 assist_id: &'static str,
107 label: &'static str, 111 label: &'static str,
108) -> Option<Assist> { 112) -> Option<()> {
109 let _p = ra_prof::profile("add_missing_impl_members_inner"); 113 let _p = ra_prof::profile("add_missing_impl_members_inner");
110 let impl_def = ctx.find_node_at_offset::<ast::ImplDef>()?; 114 let impl_def = ctx.find_node_at_offset::<ast::ImplDef>()?;
111 let impl_item_list = impl_def.item_list()?; 115 let impl_item_list = impl_def.item_list()?;
@@ -142,12 +146,11 @@ fn add_missing_impl_members_inner(
142 return None; 146 return None;
143 } 147 }
144 148
145 let sema = ctx.sema;
146 let target = impl_def.syntax().text_range(); 149 let target = impl_def.syntax().text_range();
147 ctx.add_assist(AssistId(assist_id), label, target, |edit| { 150 acc.add(AssistId(assist_id), label, target, |edit| {
148 let n_existing_items = impl_item_list.assoc_items().count(); 151 let n_existing_items = impl_item_list.assoc_items().count();
149 let source_scope = sema.scope_for_def(trait_); 152 let source_scope = ctx.sema.scope_for_def(trait_);
150 let target_scope = sema.scope(impl_item_list.syntax()); 153 let target_scope = ctx.sema.scope(impl_item_list.syntax());
151 let ast_transform = QualifyPaths::new(&target_scope, &source_scope) 154 let ast_transform = QualifyPaths::new(&target_scope, &source_scope)
152 .or(SubstituteTypeParams::for_trait_impl(&source_scope, trait_, impl_def)); 155 .or(SubstituteTypeParams::for_trait_impl(&source_scope, trait_, impl_def));
153 let items = missing_items 156 let items = missing_items
@@ -170,13 +173,11 @@ fn add_missing_impl_members_inner(
170} 173}
171 174
172fn add_body(fn_def: ast::FnDef) -> ast::FnDef { 175fn add_body(fn_def: ast::FnDef) -> ast::FnDef {
173 if fn_def.body().is_none() { 176 if fn_def.body().is_some() {
174 let body = make::block_expr(None, Some(make::expr_todo())); 177 return fn_def;
175 let body = IndentLevel(1).increase_indent(body);
176 fn_def.with_body(body)
177 } else {
178 fn_def
179 } 178 }
179 let body = make::block_expr(None, Some(make::expr_todo())).indent(IndentLevel(1));
180 fn_def.with_body(body)
180} 181}
181 182
182#[cfg(test)] 183#[cfg(test)]
diff --git a/crates/ra_assists/src/handlers/add_new.rs b/crates/ra_assists/src/handlers/add_new.rs
index 1c3f8435a..fe7451dcf 100644
--- a/crates/ra_assists/src/handlers/add_new.rs
+++ b/crates/ra_assists/src/handlers/add_new.rs
@@ -7,7 +7,7 @@ use ra_syntax::{
7}; 7};
8use stdx::{format_to, SepBy}; 8use stdx::{format_to, SepBy};
9 9
10use crate::{Assist, AssistCtx, AssistId}; 10use crate::{AssistContext, AssistId, Assists};
11 11
12// Assist: add_new 12// Assist: add_new
13// 13//
@@ -29,7 +29,7 @@ use crate::{Assist, AssistCtx, AssistId};
29// } 29// }
30// 30//
31// ``` 31// ```
32pub(crate) fn add_new(ctx: AssistCtx) -> Option<Assist> { 32pub(crate) fn add_new(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
33 let strukt = ctx.find_node_at_offset::<ast::StructDef>()?; 33 let strukt = ctx.find_node_at_offset::<ast::StructDef>()?;
34 34
35 // We want to only apply this to non-union structs with named fields 35 // We want to only apply this to non-union structs with named fields
@@ -42,7 +42,7 @@ pub(crate) fn add_new(ctx: AssistCtx) -> Option<Assist> {
42 let impl_def = find_struct_impl(&ctx, &strukt)?; 42 let impl_def = find_struct_impl(&ctx, &strukt)?;
43 43
44 let target = strukt.syntax().text_range(); 44 let target = strukt.syntax().text_range();
45 ctx.add_assist(AssistId("add_new"), "Add default constructor", target, |edit| { 45 acc.add(AssistId("add_new"), "Add default constructor", target, |edit| {
46 let mut buf = String::with_capacity(512); 46 let mut buf = String::with_capacity(512);
47 47
48 if impl_def.is_some() { 48 if impl_def.is_some() {
@@ -123,7 +123,7 @@ fn generate_impl_text(strukt: &ast::StructDef, code: &str) -> String {
123// 123//
124// FIXME: change the new fn checking to a more semantic approach when that's more 124// FIXME: change the new fn checking to a more semantic approach when that's more
125// viable (e.g. we process proc macros, etc) 125// viable (e.g. we process proc macros, etc)
126fn find_struct_impl(ctx: &AssistCtx, strukt: &ast::StructDef) -> Option<Option<ast::ImplDef>> { 126fn find_struct_impl(ctx: &AssistContext, strukt: &ast::StructDef) -> Option<Option<ast::ImplDef>> {
127 let db = ctx.db; 127 let db = ctx.db;
128 let module = strukt.syntax().ancestors().find(|node| { 128 let module = strukt.syntax().ancestors().find(|node| {
129 ast::Module::can_cast(node.kind()) || ast::SourceFile::can_cast(node.kind()) 129 ast::Module::can_cast(node.kind()) || ast::SourceFile::can_cast(node.kind())
diff --git a/crates/ra_assists/src/handlers/apply_demorgan.rs b/crates/ra_assists/src/handlers/apply_demorgan.rs
index a5b26e5b9..0feba5e11 100644
--- a/crates/ra_assists/src/handlers/apply_demorgan.rs
+++ b/crates/ra_assists/src/handlers/apply_demorgan.rs
@@ -1,6 +1,6 @@
1use ra_syntax::ast::{self, AstNode}; 1use ra_syntax::ast::{self, AstNode};
2 2
3use crate::{utils::invert_boolean_expression, Assist, AssistCtx, AssistId}; 3use crate::{utils::invert_boolean_expression, AssistContext, AssistId, Assists};
4 4
5// Assist: apply_demorgan 5// Assist: apply_demorgan
6// 6//
@@ -21,7 +21,7 @@ use crate::{utils::invert_boolean_expression, Assist, AssistCtx, AssistId};
21// if !(x == 4 && y) {} 21// if !(x == 4 && y) {}
22// } 22// }
23// ``` 23// ```
24pub(crate) fn apply_demorgan(ctx: AssistCtx) -> Option<Assist> { 24pub(crate) fn apply_demorgan(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
25 let expr = ctx.find_node_at_offset::<ast::BinExpr>()?; 25 let expr = ctx.find_node_at_offset::<ast::BinExpr>()?;
26 let op = expr.op_kind()?; 26 let op = expr.op_kind()?;
27 let op_range = expr.op_token()?.text_range(); 27 let op_range = expr.op_token()?.text_range();
@@ -39,7 +39,7 @@ pub(crate) fn apply_demorgan(ctx: AssistCtx) -> Option<Assist> {
39 let rhs_range = rhs.syntax().text_range(); 39 let rhs_range = rhs.syntax().text_range();
40 let not_rhs = invert_boolean_expression(rhs); 40 let not_rhs = invert_boolean_expression(rhs);
41 41
42 ctx.add_assist(AssistId("apply_demorgan"), "Apply De Morgan's law", op_range, |edit| { 42 acc.add(AssistId("apply_demorgan"), "Apply De Morgan's law", op_range, |edit| {
43 edit.replace(op_range, opposite_op); 43 edit.replace(op_range, opposite_op);
44 edit.replace(lhs_range, format!("!({}", not_lhs.syntax().text())); 44 edit.replace(lhs_range, format!("!({}", not_lhs.syntax().text()));
45 edit.replace(rhs_range, format!("{})", not_rhs.syntax().text())); 45 edit.replace(rhs_range, format!("{})", not_rhs.syntax().text()));
diff --git a/crates/ra_assists/src/handlers/auto_import.rs b/crates/ra_assists/src/handlers/auto_import.rs
index 2224b9714..78d23150d 100644
--- a/crates/ra_assists/src/handlers/auto_import.rs
+++ b/crates/ra_assists/src/handlers/auto_import.rs
@@ -1,5 +1,6 @@
1use std::collections::BTreeSet; 1use std::collections::BTreeSet;
2 2
3use either::Either;
3use hir::{ 4use hir::{
4 AsAssocItem, AssocItemContainer, ModPath, Module, ModuleDef, PathResolution, Semantics, Trait, 5 AsAssocItem, AssocItemContainer, ModPath, Module, ModuleDef, PathResolution, Semantics, Trait,
5 Type, 6 Type,
@@ -12,12 +13,7 @@ use ra_syntax::{
12}; 13};
13use rustc_hash::FxHashSet; 14use rustc_hash::FxHashSet;
14 15
15use crate::{ 16use crate::{utils::insert_use_statement, AssistContext, AssistId, Assists, GroupLabel};
16 assist_ctx::{Assist, AssistCtx},
17 utils::insert_use_statement,
18 AssistId,
19};
20use either::Either;
21 17
22// Assist: auto_import 18// Assist: auto_import
23// 19//
@@ -38,7 +34,7 @@ use either::Either;
38// } 34// }
39// # pub mod std { pub mod collections { pub struct HashMap { } } } 35// # pub mod std { pub mod collections { pub struct HashMap { } } }
40// ``` 36// ```
41pub(crate) fn auto_import(ctx: AssistCtx) -> Option<Assist> { 37pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
42 let auto_import_assets = AutoImportAssets::new(&ctx)?; 38 let auto_import_assets = AutoImportAssets::new(&ctx)?;
43 let proposed_imports = auto_import_assets.search_for_imports(ctx.db); 39 let proposed_imports = auto_import_assets.search_for_imports(ctx.db);
44 if proposed_imports.is_empty() { 40 if proposed_imports.is_empty() {
@@ -46,13 +42,19 @@ pub(crate) fn auto_import(ctx: AssistCtx) -> Option<Assist> {
46 } 42 }
47 43
48 let range = ctx.sema.original_range(&auto_import_assets.syntax_under_caret).range; 44 let range = ctx.sema.original_range(&auto_import_assets.syntax_under_caret).range;
49 let mut group = ctx.add_assist_group(auto_import_assets.get_import_group_message()); 45 let group = auto_import_assets.get_import_group_message();
50 for import in proposed_imports { 46 for import in proposed_imports {
51 group.add_assist(AssistId("auto_import"), format!("Import `{}`", &import), range, |edit| { 47 acc.add_group(
52 insert_use_statement(&auto_import_assets.syntax_under_caret, &import, edit); 48 &group,
53 }); 49 AssistId("auto_import"),
50 format!("Import `{}`", &import),
51 range,
52 |builder| {
53 insert_use_statement(&auto_import_assets.syntax_under_caret, &import, ctx, builder);
54 },
55 );
54 } 56 }
55 group.finish() 57 Some(())
56} 58}
57 59
58#[derive(Debug)] 60#[derive(Debug)]
@@ -63,7 +65,7 @@ struct AutoImportAssets {
63} 65}
64 66
65impl AutoImportAssets { 67impl AutoImportAssets {
66 fn new(ctx: &AssistCtx) -> Option<Self> { 68 fn new(ctx: &AssistContext) -> Option<Self> {
67 if let Some(path_under_caret) = ctx.find_node_at_offset_with_descend::<ast::Path>() { 69 if let Some(path_under_caret) = ctx.find_node_at_offset_with_descend::<ast::Path>() {
68 Self::for_regular_path(path_under_caret, &ctx) 70 Self::for_regular_path(path_under_caret, &ctx)
69 } else { 71 } else {
@@ -71,7 +73,7 @@ impl AutoImportAssets {
71 } 73 }
72 } 74 }
73 75
74 fn for_method_call(method_call: ast::MethodCallExpr, ctx: &AssistCtx) -> Option<Self> { 76 fn for_method_call(method_call: ast::MethodCallExpr, ctx: &AssistContext) -> Option<Self> {
75 let syntax_under_caret = method_call.syntax().to_owned(); 77 let syntax_under_caret = method_call.syntax().to_owned();
76 let module_with_name_to_import = ctx.sema.scope(&syntax_under_caret).module()?; 78 let module_with_name_to_import = ctx.sema.scope(&syntax_under_caret).module()?;
77 Some(Self { 79 Some(Self {
@@ -81,7 +83,7 @@ impl AutoImportAssets {
81 }) 83 })
82 } 84 }
83 85
84 fn for_regular_path(path_under_caret: ast::Path, ctx: &AssistCtx) -> Option<Self> { 86 fn for_regular_path(path_under_caret: ast::Path, ctx: &AssistContext) -> Option<Self> {
85 let syntax_under_caret = path_under_caret.syntax().to_owned(); 87 let syntax_under_caret = path_under_caret.syntax().to_owned();
86 if syntax_under_caret.ancestors().find_map(ast::UseItem::cast).is_some() { 88 if syntax_under_caret.ancestors().find_map(ast::UseItem::cast).is_some() {
87 return None; 89 return None;
@@ -104,8 +106,8 @@ impl AutoImportAssets {
104 } 106 }
105 } 107 }
106 108
107 fn get_import_group_message(&self) -> String { 109 fn get_import_group_message(&self) -> GroupLabel {
108 match &self.import_candidate { 110 let name = match &self.import_candidate {
109 ImportCandidate::UnqualifiedName(name) => format!("Import {}", name), 111 ImportCandidate::UnqualifiedName(name) => format!("Import {}", name),
110 ImportCandidate::QualifierStart(qualifier_start) => { 112 ImportCandidate::QualifierStart(qualifier_start) => {
111 format!("Import {}", qualifier_start) 113 format!("Import {}", qualifier_start)
@@ -116,7 +118,8 @@ impl AutoImportAssets {
116 ImportCandidate::TraitMethod(_, trait_method_name) => { 118 ImportCandidate::TraitMethod(_, trait_method_name) => {
117 format!("Import a trait for method {}", trait_method_name) 119 format!("Import a trait for method {}", trait_method_name)
118 } 120 }
119 } 121 };
122 GroupLabel(name)
120 } 123 }
121 124
122 fn search_for_imports(&self, db: &RootDatabase) -> BTreeSet<ModPath> { 125 fn search_for_imports(&self, db: &RootDatabase) -> BTreeSet<ModPath> {
@@ -383,7 +386,7 @@ mod tests {
383 } 386 }
384 ", 387 ",
385 r" 388 r"
386 use PubMod1::PubStruct; 389 use PubMod3::PubStruct;
387 390
388 PubSt<|>ruct 391 PubSt<|>ruct
389 392
diff --git a/crates/ra_assists/src/handlers/change_return_type_to_result.rs b/crates/ra_assists/src/handlers/change_return_type_to_result.rs
index 1e8d986cd..5c907097e 100644
--- a/crates/ra_assists/src/handlers/change_return_type_to_result.rs
+++ b/crates/ra_assists/src/handlers/change_return_type_to_result.rs
@@ -1,11 +1,11 @@
1use ra_syntax::{ 1use ra_syntax::{
2 ast, AstNode, 2 ast::{self, BlockExpr, Expr, LoopBodyOwner},
3 AstNode,
3 SyntaxKind::{COMMENT, WHITESPACE}, 4 SyntaxKind::{COMMENT, WHITESPACE},
4 SyntaxNode, TextSize, 5 SyntaxNode, TextSize,
5}; 6};
6 7
7use crate::{Assist, AssistCtx, AssistId}; 8use crate::{AssistContext, AssistId, Assists};
8use ast::{BlockExpr, Expr, LoopBodyOwner};
9 9
10// Assist: change_return_type_to_result 10// Assist: change_return_type_to_result
11// 11//
@@ -18,7 +18,7 @@ use ast::{BlockExpr, Expr, LoopBodyOwner};
18// ``` 18// ```
19// fn foo() -> Result<i32, > { Ok(42i32) } 19// fn foo() -> Result<i32, > { Ok(42i32) }
20// ``` 20// ```
21pub(crate) fn change_return_type_to_result(ctx: AssistCtx) -> Option<Assist> { 21pub(crate) fn change_return_type_to_result(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
22 let fn_def = ctx.find_node_at_offset::<ast::FnDef>(); 22 let fn_def = ctx.find_node_at_offset::<ast::FnDef>();
23 let fn_def = &mut fn_def?; 23 let fn_def = &mut fn_def?;
24 let ret_type = &fn_def.ret_type()?.type_ref()?; 24 let ret_type = &fn_def.ret_type()?.type_ref()?;
@@ -33,7 +33,7 @@ pub(crate) fn change_return_type_to_result(ctx: AssistCtx) -> Option<Assist> {
33 return None; 33 return None;
34 } 34 }
35 35
36 ctx.add_assist( 36 acc.add(
37 AssistId("change_return_type_to_result"), 37 AssistId("change_return_type_to_result"),
38 "Change return type to Result", 38 "Change return type to Result",
39 ret_type.syntax().text_range(), 39 ret_type.syntax().text_range(),
diff --git a/crates/ra_assists/src/handlers/change_visibility.rs b/crates/ra_assists/src/handlers/change_visibility.rs
index 489db83e6..e631766ef 100644
--- a/crates/ra_assists/src/handlers/change_visibility.rs
+++ b/crates/ra_assists/src/handlers/change_visibility.rs
@@ -7,10 +7,10 @@ use ra_syntax::{
7 }, 7 },
8 SyntaxNode, TextSize, T, 8 SyntaxNode, TextSize, T,
9}; 9};
10
11use crate::{Assist, AssistCtx, AssistId};
12use test_utils::tested_by; 10use test_utils::tested_by;
13 11
12use crate::{AssistContext, AssistId, Assists};
13
14// Assist: change_visibility 14// Assist: change_visibility
15// 15//
16// Adds or changes existing visibility specifier. 16// Adds or changes existing visibility specifier.
@@ -22,14 +22,14 @@ use test_utils::tested_by;
22// ``` 22// ```
23// pub(crate) fn frobnicate() {} 23// pub(crate) fn frobnicate() {}
24// ``` 24// ```
25pub(crate) fn change_visibility(ctx: AssistCtx) -> Option<Assist> { 25pub(crate) fn change_visibility(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
26 if let Some(vis) = ctx.find_node_at_offset::<ast::Visibility>() { 26 if let Some(vis) = ctx.find_node_at_offset::<ast::Visibility>() {
27 return change_vis(ctx, vis); 27 return change_vis(acc, vis);
28 } 28 }
29 add_vis(ctx) 29 add_vis(acc, ctx)
30} 30}
31 31
32fn add_vis(ctx: AssistCtx) -> Option<Assist> { 32fn add_vis(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
33 let item_keyword = ctx.token_at_offset().find(|leaf| match leaf.kind() { 33 let item_keyword = ctx.token_at_offset().find(|leaf| match leaf.kind() {
34 T![const] | T![fn] | T![mod] | T![struct] | T![enum] | T![trait] => true, 34 T![const] | T![fn] | T![mod] | T![struct] | T![enum] | T![trait] => true,
35 _ => false, 35 _ => false,
@@ -66,15 +66,10 @@ fn add_vis(ctx: AssistCtx) -> Option<Assist> {
66 return None; 66 return None;
67 }; 67 };
68 68
69 ctx.add_assist( 69 acc.add(AssistId("change_visibility"), "Change visibility to pub(crate)", target, |edit| {
70 AssistId("change_visibility"), 70 edit.insert(offset, "pub(crate) ");
71 "Change visibility to pub(crate)", 71 edit.set_cursor(offset);
72 target, 72 })
73 |edit| {
74 edit.insert(offset, "pub(crate) ");
75 edit.set_cursor(offset);
76 },
77 )
78} 73}
79 74
80fn vis_offset(node: &SyntaxNode) -> TextSize { 75fn vis_offset(node: &SyntaxNode) -> TextSize {
@@ -88,10 +83,10 @@ fn vis_offset(node: &SyntaxNode) -> TextSize {
88 .unwrap_or_else(|| node.text_range().start()) 83 .unwrap_or_else(|| node.text_range().start())
89} 84}
90 85
91fn change_vis(ctx: AssistCtx, vis: ast::Visibility) -> Option<Assist> { 86fn change_vis(acc: &mut Assists, vis: ast::Visibility) -> Option<()> {
92 if vis.syntax().text() == "pub" { 87 if vis.syntax().text() == "pub" {
93 let target = vis.syntax().text_range(); 88 let target = vis.syntax().text_range();
94 return ctx.add_assist( 89 return acc.add(
95 AssistId("change_visibility"), 90 AssistId("change_visibility"),
96 "Change Visibility to pub(crate)", 91 "Change Visibility to pub(crate)",
97 target, 92 target,
@@ -103,7 +98,7 @@ fn change_vis(ctx: AssistCtx, vis: ast::Visibility) -> Option<Assist> {
103 } 98 }
104 if vis.syntax().text() == "pub(crate)" { 99 if vis.syntax().text() == "pub(crate)" {
105 let target = vis.syntax().text_range(); 100 let target = vis.syntax().text_range();
106 return ctx.add_assist( 101 return acc.add(
107 AssistId("change_visibility"), 102 AssistId("change_visibility"),
108 "Change visibility to pub", 103 "Change visibility to pub",
109 target, 104 target,
diff --git a/crates/ra_assists/src/handlers/early_return.rs b/crates/ra_assists/src/handlers/early_return.rs
index 4bd6040b2..66b296081 100644
--- a/crates/ra_assists/src/handlers/early_return.rs
+++ b/crates/ra_assists/src/handlers/early_return.rs
@@ -2,14 +2,18 @@ use std::{iter::once, ops::RangeInclusive};
2 2
3use ra_syntax::{ 3use ra_syntax::{
4 algo::replace_children, 4 algo::replace_children,
5 ast::{self, edit::IndentLevel, make}, 5 ast::{
6 self,
7 edit::{AstNodeEdit, IndentLevel},
8 make,
9 },
6 AstNode, 10 AstNode,
7 SyntaxKind::{FN_DEF, LOOP_EXPR, L_CURLY, R_CURLY, WHILE_EXPR, WHITESPACE}, 11 SyntaxKind::{FN_DEF, LOOP_EXPR, L_CURLY, R_CURLY, WHILE_EXPR, WHITESPACE},
8 SyntaxNode, 12 SyntaxNode,
9}; 13};
10 14
11use crate::{ 15use crate::{
12 assist_ctx::{Assist, AssistCtx}, 16 assist_context::{AssistContext, Assists},
13 utils::invert_boolean_expression, 17 utils::invert_boolean_expression,
14 AssistId, 18 AssistId,
15}; 19};
@@ -36,7 +40,7 @@ use crate::{
36// bar(); 40// bar();
37// } 41// }
38// ``` 42// ```
39pub(crate) fn convert_to_guarded_return(ctx: AssistCtx) -> Option<Assist> { 43pub(crate) fn convert_to_guarded_return(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
40 let if_expr: ast::IfExpr = ctx.find_node_at_offset()?; 44 let if_expr: ast::IfExpr = ctx.find_node_at_offset()?;
41 if if_expr.else_branch().is_some() { 45 if if_expr.else_branch().is_some() {
42 return None; 46 return None;
@@ -93,96 +97,90 @@ pub(crate) fn convert_to_guarded_return(ctx: AssistCtx) -> Option<Assist> {
93 } 97 }
94 98
95 then_block.syntax().last_child_or_token().filter(|t| t.kind() == R_CURLY)?; 99 then_block.syntax().last_child_or_token().filter(|t| t.kind() == R_CURLY)?;
96 let cursor_position = ctx.frange.range.start(); 100 let cursor_position = ctx.offset();
97 101
98 let target = if_expr.syntax().text_range(); 102 let target = if_expr.syntax().text_range();
99 ctx.add_assist( 103 acc.add(AssistId("convert_to_guarded_return"), "Convert to guarded return", target, |edit| {
100 AssistId("convert_to_guarded_return"), 104 let if_indent_level = IndentLevel::from_node(&if_expr.syntax());
101 "Convert to guarded return", 105 let new_block = match if_let_pat {
102 target, 106 None => {
103 |edit| { 107 // If.
104 let if_indent_level = IndentLevel::from_node(&if_expr.syntax()); 108 let new_expr = {
105 let new_block = match if_let_pat { 109 let then_branch =
106 None => { 110 make::block_expr(once(make::expr_stmt(early_expression).into()), None);
107 // If. 111 let cond = invert_boolean_expression(cond_expr);
108 let new_expr = { 112 make::expr_if(make::condition(cond, None), then_branch).indent(if_indent_level)
109 let then_branch = 113 };
110 make::block_expr(once(make::expr_stmt(early_expression).into()), None); 114 replace(new_expr.syntax(), &then_block, &parent_block, &if_expr)
111 let cond = invert_boolean_expression(cond_expr); 115 }
112 let e = make::expr_if(make::condition(cond, None), then_branch); 116 Some((path, bound_ident)) => {
113 if_indent_level.increase_indent(e) 117 // If-let.
114 }; 118 let match_expr = {
115 replace(new_expr.syntax(), &then_block, &parent_block, &if_expr) 119 let happy_arm = {
116 } 120 let pat = make::tuple_struct_pat(
117 Some((path, bound_ident)) => { 121 path,
118 // If-let. 122 once(make::bind_pat(make::name("it")).into()),
119 let match_expr = {
120 let happy_arm = {
121 let pat = make::tuple_struct_pat(
122 path,
123 once(make::bind_pat(make::name("it")).into()),
124 );
125 let expr = {
126 let name_ref = make::name_ref("it");
127 let segment = make::path_segment(name_ref);
128 let path = make::path_unqualified(segment);
129 make::expr_path(path)
130 };
131 make::match_arm(once(pat.into()), expr)
132 };
133
134 let sad_arm = make::match_arm(
135 // FIXME: would be cool to use `None` or `Err(_)` if appropriate
136 once(make::placeholder_pat().into()),
137 early_expression,
138 ); 123 );
139 124 let expr = {
140 make::expr_match(cond_expr, make::match_arm_list(vec![happy_arm, sad_arm])) 125 let name_ref = make::name_ref("it");
126 let segment = make::path_segment(name_ref);
127 let path = make::path_unqualified(segment);
128 make::expr_path(path)
129 };
130 make::match_arm(once(pat.into()), expr)
141 }; 131 };
142 132
143 let let_stmt = make::let_stmt( 133 let sad_arm = make::match_arm(
144 make::bind_pat(make::name(&bound_ident.syntax().to_string())).into(), 134 // FIXME: would be cool to use `None` or `Err(_)` if appropriate
145 Some(match_expr), 135 once(make::placeholder_pat().into()),
136 early_expression,
146 ); 137 );
147 let let_stmt = if_indent_level.increase_indent(let_stmt); 138
148 replace(let_stmt.syntax(), &then_block, &parent_block, &if_expr) 139 make::expr_match(cond_expr, make::match_arm_list(vec![happy_arm, sad_arm]))
149 } 140 };
150 }; 141
151 edit.replace_ast(parent_block, ast::BlockExpr::cast(new_block).unwrap()); 142 let let_stmt = make::let_stmt(
152 edit.set_cursor(cursor_position); 143 make::bind_pat(make::name(&bound_ident.syntax().to_string())).into(),
153 144 Some(match_expr),
154 fn replace(
155 new_expr: &SyntaxNode,
156 then_block: &ast::BlockExpr,
157 parent_block: &ast::BlockExpr,
158 if_expr: &ast::IfExpr,
159 ) -> SyntaxNode {
160 let then_block_items = IndentLevel::from(1).decrease_indent(then_block.clone());
161 let end_of_then = then_block_items.syntax().last_child_or_token().unwrap();
162 let end_of_then =
163 if end_of_then.prev_sibling_or_token().map(|n| n.kind()) == Some(WHITESPACE) {
164 end_of_then.prev_sibling_or_token().unwrap()
165 } else {
166 end_of_then
167 };
168 let mut then_statements = new_expr.children_with_tokens().chain(
169 then_block_items
170 .syntax()
171 .children_with_tokens()
172 .skip(1)
173 .take_while(|i| *i != end_of_then),
174 ); 145 );
175 replace_children( 146 let let_stmt = let_stmt.indent(if_indent_level);
176 &parent_block.syntax(), 147 replace(let_stmt.syntax(), &then_block, &parent_block, &if_expr)
177 RangeInclusive::new(
178 if_expr.clone().syntax().clone().into(),
179 if_expr.syntax().clone().into(),
180 ),
181 &mut then_statements,
182 )
183 } 148 }
184 }, 149 };
185 ) 150 edit.replace_ast(parent_block, ast::BlockExpr::cast(new_block).unwrap());
151 edit.set_cursor(cursor_position);
152
153 fn replace(
154 new_expr: &SyntaxNode,
155 then_block: &ast::BlockExpr,
156 parent_block: &ast::BlockExpr,
157 if_expr: &ast::IfExpr,
158 ) -> SyntaxNode {
159 let then_block_items = then_block.dedent(IndentLevel::from(1));
160 let end_of_then = then_block_items.syntax().last_child_or_token().unwrap();
161 let end_of_then =
162 if end_of_then.prev_sibling_or_token().map(|n| n.kind()) == Some(WHITESPACE) {
163 end_of_then.prev_sibling_or_token().unwrap()
164 } else {
165 end_of_then
166 };
167 let mut then_statements = new_expr.children_with_tokens().chain(
168 then_block_items
169 .syntax()
170 .children_with_tokens()
171 .skip(1)
172 .take_while(|i| *i != end_of_then),
173 );
174 replace_children(
175 &parent_block.syntax(),
176 RangeInclusive::new(
177 if_expr.clone().syntax().clone().into(),
178 if_expr.syntax().clone().into(),
179 ),
180 &mut then_statements,
181 )
182 }
183 })
186} 184}
187 185
188#[cfg(test)] 186#[cfg(test)]
diff --git a/crates/ra_assists/src/handlers/fill_match_arms.rs b/crates/ra_assists/src/handlers/fill_match_arms.rs
index 7c8f8bdf2..13c1e7e80 100644
--- a/crates/ra_assists/src/handlers/fill_match_arms.rs
+++ b/crates/ra_assists/src/handlers/fill_match_arms.rs
@@ -5,7 +5,7 @@ use itertools::Itertools;
5use ra_ide_db::RootDatabase; 5use ra_ide_db::RootDatabase;
6use ra_syntax::ast::{self, make, AstNode, MatchArm, NameOwner, Pat}; 6use ra_syntax::ast::{self, make, AstNode, MatchArm, NameOwner, Pat};
7 7
8use crate::{Assist, AssistCtx, AssistId}; 8use crate::{AssistContext, AssistId, Assists};
9 9
10// Assist: fill_match_arms 10// Assist: fill_match_arms
11// 11//
@@ -31,7 +31,7 @@ use crate::{Assist, AssistCtx, AssistId};
31// } 31// }
32// } 32// }
33// ``` 33// ```
34pub(crate) fn fill_match_arms(ctx: AssistCtx) -> Option<Assist> { 34pub(crate) fn fill_match_arms(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
35 let match_expr = ctx.find_node_at_offset::<ast::MatchExpr>()?; 35 let match_expr = ctx.find_node_at_offset::<ast::MatchExpr>()?;
36 let match_arm_list = match_expr.match_arm_list()?; 36 let match_arm_list = match_expr.match_arm_list()?;
37 37
@@ -93,7 +93,7 @@ pub(crate) fn fill_match_arms(ctx: AssistCtx) -> Option<Assist> {
93 } 93 }
94 94
95 let target = match_expr.syntax().text_range(); 95 let target = match_expr.syntax().text_range();
96 ctx.add_assist(AssistId("fill_match_arms"), "Fill match arms", target, |edit| { 96 acc.add(AssistId("fill_match_arms"), "Fill match arms", target, |edit| {
97 let new_arm_list = match_arm_list.remove_placeholder().append_arms(missing_arms); 97 let new_arm_list = match_arm_list.remove_placeholder().append_arms(missing_arms);
98 edit.set_cursor(expr.syntax().text_range().start()); 98 edit.set_cursor(expr.syntax().text_range().start());
99 edit.replace_ast(match_arm_list, new_arm_list); 99 edit.replace_ast(match_arm_list, new_arm_list);
diff --git a/crates/ra_assists/src/handlers/flip_binexpr.rs b/crates/ra_assists/src/handlers/flip_binexpr.rs
index cb7264d7b..692ba4895 100644
--- a/crates/ra_assists/src/handlers/flip_binexpr.rs
+++ b/crates/ra_assists/src/handlers/flip_binexpr.rs
@@ -1,6 +1,6 @@
1use ra_syntax::ast::{AstNode, BinExpr, BinOp}; 1use ra_syntax::ast::{AstNode, BinExpr, BinOp};
2 2
3use crate::{Assist, AssistCtx, AssistId}; 3use crate::{AssistContext, AssistId, Assists};
4 4
5// Assist: flip_binexpr 5// Assist: flip_binexpr
6// 6//
@@ -17,7 +17,7 @@ use crate::{Assist, AssistCtx, AssistId};
17// let _ = 2 + 90; 17// let _ = 2 + 90;
18// } 18// }
19// ``` 19// ```
20pub(crate) fn flip_binexpr(ctx: AssistCtx) -> Option<Assist> { 20pub(crate) fn flip_binexpr(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
21 let expr = ctx.find_node_at_offset::<BinExpr>()?; 21 let expr = ctx.find_node_at_offset::<BinExpr>()?;
22 let lhs = expr.lhs()?.syntax().clone(); 22 let lhs = expr.lhs()?.syntax().clone();
23 let rhs = expr.rhs()?.syntax().clone(); 23 let rhs = expr.rhs()?.syntax().clone();
@@ -33,7 +33,7 @@ pub(crate) fn flip_binexpr(ctx: AssistCtx) -> Option<Assist> {
33 return None; 33 return None;
34 } 34 }
35 35
36 ctx.add_assist(AssistId("flip_binexpr"), "Flip binary expression", op_range, |edit| { 36 acc.add(AssistId("flip_binexpr"), "Flip binary expression", op_range, |edit| {
37 if let FlipAction::FlipAndReplaceOp(new_op) = action { 37 if let FlipAction::FlipAndReplaceOp(new_op) = action {
38 edit.replace(op_range, new_op); 38 edit.replace(op_range, new_op);
39 } 39 }
diff --git a/crates/ra_assists/src/handlers/flip_comma.rs b/crates/ra_assists/src/handlers/flip_comma.rs
index 24982ae22..dfe2a7fed 100644
--- a/crates/ra_assists/src/handlers/flip_comma.rs
+++ b/crates/ra_assists/src/handlers/flip_comma.rs
@@ -1,6 +1,6 @@
1use ra_syntax::{algo::non_trivia_sibling, Direction, T}; 1use ra_syntax::{algo::non_trivia_sibling, Direction, T};
2 2
3use crate::{Assist, AssistCtx, AssistId}; 3use crate::{AssistContext, AssistId, Assists};
4 4
5// Assist: flip_comma 5// Assist: flip_comma
6// 6//
@@ -17,7 +17,7 @@ use crate::{Assist, AssistCtx, AssistId};
17// ((3, 4), (1, 2)); 17// ((3, 4), (1, 2));
18// } 18// }
19// ``` 19// ```
20pub(crate) fn flip_comma(ctx: AssistCtx) -> Option<Assist> { 20pub(crate) fn flip_comma(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
21 let comma = ctx.find_token_at_offset(T![,])?; 21 let comma = ctx.find_token_at_offset(T![,])?;
22 let prev = non_trivia_sibling(comma.clone().into(), Direction::Prev)?; 22 let prev = non_trivia_sibling(comma.clone().into(), Direction::Prev)?;
23 let next = non_trivia_sibling(comma.clone().into(), Direction::Next)?; 23 let next = non_trivia_sibling(comma.clone().into(), Direction::Next)?;
@@ -28,7 +28,7 @@ pub(crate) fn flip_comma(ctx: AssistCtx) -> Option<Assist> {
28 return None; 28 return None;
29 } 29 }
30 30
31 ctx.add_assist(AssistId("flip_comma"), "Flip comma", comma.text_range(), |edit| { 31 acc.add(AssistId("flip_comma"), "Flip comma", comma.text_range(), |edit| {
32 edit.replace(prev.text_range(), next.to_string()); 32 edit.replace(prev.text_range(), next.to_string());
33 edit.replace(next.text_range(), prev.to_string()); 33 edit.replace(next.text_range(), prev.to_string());
34 }) 34 })
diff --git a/crates/ra_assists/src/handlers/flip_trait_bound.rs b/crates/ra_assists/src/handlers/flip_trait_bound.rs
index 6a3b2df67..8a08702ab 100644
--- a/crates/ra_assists/src/handlers/flip_trait_bound.rs
+++ b/crates/ra_assists/src/handlers/flip_trait_bound.rs
@@ -4,7 +4,7 @@ use ra_syntax::{
4 Direction, T, 4 Direction, T,
5}; 5};
6 6
7use crate::{Assist, AssistCtx, AssistId}; 7use crate::{AssistContext, AssistId, Assists};
8 8
9// Assist: flip_trait_bound 9// Assist: flip_trait_bound
10// 10//
@@ -17,7 +17,7 @@ use crate::{Assist, AssistCtx, AssistId};
17// ``` 17// ```
18// fn foo<T: Copy + Clone>() { } 18// fn foo<T: Copy + Clone>() { }
19// ``` 19// ```
20pub(crate) fn flip_trait_bound(ctx: AssistCtx) -> Option<Assist> { 20pub(crate) fn flip_trait_bound(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
21 // We want to replicate the behavior of `flip_binexpr` by only suggesting 21 // We want to replicate the behavior of `flip_binexpr` by only suggesting
22 // the assist when the cursor is on a `+` 22 // the assist when the cursor is on a `+`
23 let plus = ctx.find_token_at_offset(T![+])?; 23 let plus = ctx.find_token_at_offset(T![+])?;
@@ -33,7 +33,7 @@ pub(crate) fn flip_trait_bound(ctx: AssistCtx) -> Option<Assist> {
33 ); 33 );
34 34
35 let target = plus.text_range(); 35 let target = plus.text_range();
36 ctx.add_assist(AssistId("flip_trait_bound"), "Flip trait bounds", target, |edit| { 36 acc.add(AssistId("flip_trait_bound"), "Flip trait bounds", target, |edit| {
37 edit.replace(before.text_range(), after.to_string()); 37 edit.replace(before.text_range(), after.to_string());
38 edit.replace(after.text_range(), before.to_string()); 38 edit.replace(after.text_range(), before.to_string());
39 }) 39 })
diff --git a/crates/ra_assists/src/handlers/inline_local_variable.rs b/crates/ra_assists/src/handlers/inline_local_variable.rs
index e5765c845..5b26814d3 100644
--- a/crates/ra_assists/src/handlers/inline_local_variable.rs
+++ b/crates/ra_assists/src/handlers/inline_local_variable.rs
@@ -5,7 +5,10 @@ use ra_syntax::{
5}; 5};
6use test_utils::tested_by; 6use test_utils::tested_by;
7 7
8use crate::{assist_ctx::ActionBuilder, Assist, AssistCtx, AssistId}; 8use crate::{
9 assist_context::{AssistContext, Assists},
10 AssistId,
11};
9 12
10// Assist: inline_local_variable 13// Assist: inline_local_variable
11// 14//
@@ -23,7 +26,7 @@ use crate::{assist_ctx::ActionBuilder, Assist, AssistCtx, AssistId};
23// (1 + 2) * 4; 26// (1 + 2) * 4;
24// } 27// }
25// ``` 28// ```
26pub(crate) fn inline_local_variable(ctx: AssistCtx) -> Option<Assist> { 29pub(crate) fn inline_local_variable(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
27 let let_stmt = ctx.find_node_at_offset::<ast::LetStmt>()?; 30 let let_stmt = ctx.find_node_at_offset::<ast::LetStmt>()?;
28 let bind_pat = match let_stmt.pat()? { 31 let bind_pat = match let_stmt.pat()? {
29 ast::Pat::BindPat(pat) => pat, 32 ast::Pat::BindPat(pat) => pat,
@@ -33,7 +36,7 @@ pub(crate) fn inline_local_variable(ctx: AssistCtx) -> Option<Assist> {
33 tested_by!(test_not_inline_mut_variable); 36 tested_by!(test_not_inline_mut_variable);
34 return None; 37 return None;
35 } 38 }
36 if !bind_pat.syntax().text_range().contains_inclusive(ctx.frange.range.start()) { 39 if !bind_pat.syntax().text_range().contains_inclusive(ctx.offset()) {
37 tested_by!(not_applicable_outside_of_bind_pat); 40 tested_by!(not_applicable_outside_of_bind_pat);
38 return None; 41 return None;
39 } 42 }
@@ -107,20 +110,14 @@ pub(crate) fn inline_local_variable(ctx: AssistCtx) -> Option<Assist> {
107 let init_in_paren = format!("({})", &init_str); 110 let init_in_paren = format!("({})", &init_str);
108 111
109 let target = bind_pat.syntax().text_range(); 112 let target = bind_pat.syntax().text_range();
110 ctx.add_assist( 113 acc.add(AssistId("inline_local_variable"), "Inline variable", target, move |builder| {
111 AssistId("inline_local_variable"), 114 builder.delete(delete_range);
112 "Inline variable", 115 for (desc, should_wrap) in refs.iter().zip(wrap_in_parens) {
113 target, 116 let replacement = if should_wrap { init_in_paren.clone() } else { init_str.clone() };
114 move |edit: &mut ActionBuilder| { 117 builder.replace(desc.file_range.range, replacement)
115 edit.delete(delete_range); 118 }
116 for (desc, should_wrap) in refs.iter().zip(wrap_in_parens) { 119 builder.set_cursor(delete_range.start())
117 let replacement = 120 })
118 if should_wrap { init_in_paren.clone() } else { init_str.clone() };
119 edit.replace(desc.file_range.range, replacement)
120 }
121 edit.set_cursor(delete_range.start())
122 },
123 )
124} 121}
125 122
126#[cfg(test)] 123#[cfg(test)]
diff --git a/crates/ra_assists/src/handlers/introduce_variable.rs b/crates/ra_assists/src/handlers/introduce_variable.rs
index 3c340ff3b..fdf3ada0d 100644
--- a/crates/ra_assists/src/handlers/introduce_variable.rs
+++ b/crates/ra_assists/src/handlers/introduce_variable.rs
@@ -9,7 +9,7 @@ use ra_syntax::{
9use stdx::format_to; 9use stdx::format_to;
10use test_utils::tested_by; 10use test_utils::tested_by;
11 11
12use crate::{Assist, AssistCtx, AssistId}; 12use crate::{AssistContext, AssistId, Assists};
13 13
14// Assist: introduce_variable 14// Assist: introduce_variable
15// 15//
@@ -27,7 +27,7 @@ use crate::{Assist, AssistCtx, AssistId};
27// var_name * 4; 27// var_name * 4;
28// } 28// }
29// ``` 29// ```
30pub(crate) fn introduce_variable(ctx: AssistCtx) -> Option<Assist> { 30pub(crate) fn introduce_variable(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
31 if ctx.frange.range.is_empty() { 31 if ctx.frange.range.is_empty() {
32 return None; 32 return None;
33 } 33 }
@@ -43,7 +43,7 @@ pub(crate) fn introduce_variable(ctx: AssistCtx) -> Option<Assist> {
43 return None; 43 return None;
44 } 44 }
45 let target = expr.syntax().text_range(); 45 let target = expr.syntax().text_range();
46 ctx.add_assist(AssistId("introduce_variable"), "Extract into variable", target, move |edit| { 46 acc.add(AssistId("introduce_variable"), "Extract into variable", target, move |edit| {
47 let mut buf = String::new(); 47 let mut buf = String::new();
48 48
49 let cursor_offset = if wrap_in_block { 49 let cursor_offset = if wrap_in_block {
diff --git a/crates/ra_assists/src/handlers/invert_if.rs b/crates/ra_assists/src/handlers/invert_if.rs
index b16271443..527c7caef 100644
--- a/crates/ra_assists/src/handlers/invert_if.rs
+++ b/crates/ra_assists/src/handlers/invert_if.rs
@@ -3,7 +3,11 @@ use ra_syntax::{
3 T, 3 T,
4}; 4};
5 5
6use crate::{utils::invert_boolean_expression, Assist, AssistCtx, AssistId}; 6use crate::{
7 assist_context::{AssistContext, Assists},
8 utils::invert_boolean_expression,
9 AssistId,
10};
7 11
8// Assist: invert_if 12// Assist: invert_if
9// 13//
@@ -24,7 +28,7 @@ use crate::{utils::invert_boolean_expression, Assist, AssistCtx, AssistId};
24// } 28// }
25// ``` 29// ```
26 30
27pub(crate) fn invert_if(ctx: AssistCtx) -> Option<Assist> { 31pub(crate) fn invert_if(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
28 let if_keyword = ctx.find_token_at_offset(T![if])?; 32 let if_keyword = ctx.find_token_at_offset(T![if])?;
29 let expr = ast::IfExpr::cast(if_keyword.parent())?; 33 let expr = ast::IfExpr::cast(if_keyword.parent())?;
30 let if_range = if_keyword.text_range(); 34 let if_range = if_keyword.text_range();
@@ -40,21 +44,21 @@ pub(crate) fn invert_if(ctx: AssistCtx) -> Option<Assist> {
40 44
41 let cond = expr.condition()?.expr()?; 45 let cond = expr.condition()?.expr()?;
42 let then_node = expr.then_branch()?.syntax().clone(); 46 let then_node = expr.then_branch()?.syntax().clone();
47 let else_block = match expr.else_branch()? {
48 ast::ElseBranch::Block(it) => it,
49 ast::ElseBranch::IfExpr(_) => return None,
50 };
43 51
44 if let ast::ElseBranch::Block(else_block) = expr.else_branch()? { 52 let cond_range = cond.syntax().text_range();
45 let cond_range = cond.syntax().text_range(); 53 let flip_cond = invert_boolean_expression(cond);
46 let flip_cond = invert_boolean_expression(cond); 54 let else_node = else_block.syntax();
47 let else_node = else_block.syntax(); 55 let else_range = else_node.text_range();
48 let else_range = else_node.text_range(); 56 let then_range = then_node.text_range();
49 let then_range = then_node.text_range(); 57 acc.add(AssistId("invert_if"), "Invert if", if_range, |edit| {
50 return ctx.add_assist(AssistId("invert_if"), "Invert if", if_range, |edit| { 58 edit.replace(cond_range, flip_cond.syntax().text());
51 edit.replace(cond_range, flip_cond.syntax().text()); 59 edit.replace(else_range, then_node.text());
52 edit.replace(else_range, then_node.text()); 60 edit.replace(then_range, else_node.text());
53 edit.replace(then_range, else_node.text()); 61 })
54 });
55 }
56
57 None
58} 62}
59 63
60#[cfg(test)] 64#[cfg(test)]
diff --git a/crates/ra_assists/src/handlers/merge_imports.rs b/crates/ra_assists/src/handlers/merge_imports.rs
index de74d83d8..ac3e53c27 100644
--- a/crates/ra_assists/src/handlers/merge_imports.rs
+++ b/crates/ra_assists/src/handlers/merge_imports.rs
@@ -6,7 +6,10 @@ use ra_syntax::{
6 AstNode, Direction, InsertPosition, SyntaxElement, T, 6 AstNode, Direction, InsertPosition, SyntaxElement, T,
7}; 7};
8 8
9use crate::{Assist, AssistCtx, AssistId}; 9use crate::{
10 assist_context::{AssistContext, Assists},
11 AssistId,
12};
10 13
11// Assist: merge_imports 14// Assist: merge_imports
12// 15//
@@ -20,10 +23,10 @@ use crate::{Assist, AssistCtx, AssistId};
20// ``` 23// ```
21// use std::{fmt::Formatter, io}; 24// use std::{fmt::Formatter, io};
22// ``` 25// ```
23pub(crate) fn merge_imports(ctx: AssistCtx) -> Option<Assist> { 26pub(crate) fn merge_imports(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
24 let tree: ast::UseTree = ctx.find_node_at_offset()?; 27 let tree: ast::UseTree = ctx.find_node_at_offset()?;
25 let mut rewriter = SyntaxRewriter::default(); 28 let mut rewriter = SyntaxRewriter::default();
26 let mut offset = ctx.frange.range.start(); 29 let mut offset = ctx.offset();
27 30
28 if let Some(use_item) = tree.syntax().parent().and_then(ast::UseItem::cast) { 31 if let Some(use_item) = tree.syntax().parent().and_then(ast::UseItem::cast) {
29 let (merged, to_delete) = next_prev() 32 let (merged, to_delete) = next_prev()
@@ -53,10 +56,10 @@ pub(crate) fn merge_imports(ctx: AssistCtx) -> Option<Assist> {
53 }; 56 };
54 57
55 let target = tree.syntax().text_range(); 58 let target = tree.syntax().text_range();
56 ctx.add_assist(AssistId("merge_imports"), "Merge imports", target, |edit| { 59 acc.add(AssistId("merge_imports"), "Merge imports", target, |builder| {
57 edit.rewrite(rewriter); 60 builder.rewrite(rewriter);
58 // FIXME: we only need because our diff is imprecise 61 // FIXME: we only need because our diff is imprecise
59 edit.set_cursor(offset); 62 builder.set_cursor(offset);
60 }) 63 })
61} 64}
62 65
diff --git a/crates/ra_assists/src/handlers/merge_match_arms.rs b/crates/ra_assists/src/handlers/merge_match_arms.rs
index 7c4d9d55d..d4e38aa6a 100644
--- a/crates/ra_assists/src/handlers/merge_match_arms.rs
+++ b/crates/ra_assists/src/handlers/merge_match_arms.rs
@@ -6,7 +6,7 @@ use ra_syntax::{
6 Direction, TextSize, 6 Direction, TextSize,
7}; 7};
8 8
9use crate::{Assist, AssistCtx, AssistId, TextRange}; 9use crate::{AssistContext, AssistId, Assists, TextRange};
10 10
11// Assist: merge_match_arms 11// Assist: merge_match_arms
12// 12//
@@ -32,7 +32,7 @@ use crate::{Assist, AssistCtx, AssistId, TextRange};
32// } 32// }
33// } 33// }
34// ``` 34// ```
35pub(crate) fn merge_match_arms(ctx: AssistCtx) -> Option<Assist> { 35pub(crate) fn merge_match_arms(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
36 let current_arm = ctx.find_node_at_offset::<ast::MatchArm>()?; 36 let current_arm = ctx.find_node_at_offset::<ast::MatchArm>()?;
37 // Don't try to handle arms with guards for now - can add support for this later 37 // Don't try to handle arms with guards for now - can add support for this later
38 if current_arm.guard().is_some() { 38 if current_arm.guard().is_some() {
@@ -45,7 +45,7 @@ pub(crate) fn merge_match_arms(ctx: AssistCtx) -> Option<Assist> {
45 InExpr(TextSize), 45 InExpr(TextSize),
46 InPat(TextSize), 46 InPat(TextSize),
47 } 47 }
48 let cursor_pos = ctx.frange.range.start(); 48 let cursor_pos = ctx.offset();
49 let cursor_pos = if current_expr.syntax().text_range().contains(cursor_pos) { 49 let cursor_pos = if current_expr.syntax().text_range().contains(cursor_pos) {
50 CursorPos::InExpr(current_text_range.end() - cursor_pos) 50 CursorPos::InExpr(current_text_range.end() - cursor_pos)
51 } else { 51 } else {
@@ -70,7 +70,7 @@ pub(crate) fn merge_match_arms(ctx: AssistCtx) -> Option<Assist> {
70 return None; 70 return None;
71 } 71 }
72 72
73 ctx.add_assist(AssistId("merge_match_arms"), "Merge match arms", current_text_range, |edit| { 73 acc.add(AssistId("merge_match_arms"), "Merge match arms", current_text_range, |edit| {
74 let pats = if arms_to_merge.iter().any(contains_placeholder) { 74 let pats = if arms_to_merge.iter().any(contains_placeholder) {
75 "_".into() 75 "_".into()
76 } else { 76 } else {
diff --git a/crates/ra_assists/src/handlers/move_bounds.rs b/crates/ra_assists/src/handlers/move_bounds.rs
index 44e50cb6e..a41aacfc3 100644
--- a/crates/ra_assists/src/handlers/move_bounds.rs
+++ b/crates/ra_assists/src/handlers/move_bounds.rs
@@ -5,7 +5,7 @@ use ra_syntax::{
5 T, 5 T,
6}; 6};
7 7
8use crate::{Assist, AssistCtx, AssistId}; 8use crate::{AssistContext, AssistId, Assists};
9 9
10// Assist: move_bounds_to_where_clause 10// Assist: move_bounds_to_where_clause
11// 11//
@@ -22,7 +22,7 @@ use crate::{Assist, AssistCtx, AssistId};
22// f(x) 22// f(x)
23// } 23// }
24// ``` 24// ```
25pub(crate) fn move_bounds_to_where_clause(ctx: AssistCtx) -> Option<Assist> { 25pub(crate) fn move_bounds_to_where_clause(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
26 let type_param_list = ctx.find_node_at_offset::<ast::TypeParamList>()?; 26 let type_param_list = ctx.find_node_at_offset::<ast::TypeParamList>()?;
27 27
28 let mut type_params = type_param_list.type_params(); 28 let mut type_params = type_param_list.type_params();
@@ -50,36 +50,29 @@ pub(crate) fn move_bounds_to_where_clause(ctx: AssistCtx) -> Option<Assist> {
50 }; 50 };
51 51
52 let target = type_param_list.syntax().text_range(); 52 let target = type_param_list.syntax().text_range();
53 ctx.add_assist( 53 acc.add(AssistId("move_bounds_to_where_clause"), "Move to where clause", target, |edit| {
54 AssistId("move_bounds_to_where_clause"), 54 let new_params = type_param_list
55 "Move to where clause", 55 .type_params()
56 target, 56 .filter(|it| it.type_bound_list().is_some())
57 |edit| { 57 .map(|type_param| {
58 let new_params = type_param_list 58 let without_bounds = type_param.remove_bounds();
59 .type_params() 59 (type_param, without_bounds)
60 .filter(|it| it.type_bound_list().is_some()) 60 });
61 .map(|type_param| { 61
62 let without_bounds = type_param.remove_bounds(); 62 let new_type_param_list = type_param_list.replace_descendants(new_params);
63 (type_param, without_bounds) 63 edit.replace_ast(type_param_list.clone(), new_type_param_list);
64 }); 64
65 65 let where_clause = {
66 let new_type_param_list = type_param_list.replace_descendants(new_params); 66 let predicates = type_param_list.type_params().filter_map(build_predicate);
67 edit.replace_ast(type_param_list.clone(), new_type_param_list); 67 make::where_clause(predicates)
68 68 };
69 let where_clause = { 69
70 let predicates = type_param_list.type_params().filter_map(build_predicate); 70 let to_insert = match anchor.prev_sibling_or_token() {
71 make::where_clause(predicates) 71 Some(ref elem) if elem.kind() == WHITESPACE => format!("{} ", where_clause.syntax()),
72 }; 72 _ => format!(" {}", where_clause.syntax()),
73 73 };
74 let to_insert = match anchor.prev_sibling_or_token() { 74 edit.insert(anchor.text_range().start(), to_insert);
75 Some(ref elem) if elem.kind() == WHITESPACE => { 75 })
76 format!("{} ", where_clause.syntax())
77 }
78 _ => format!(" {}", where_clause.syntax()),
79 };
80 edit.insert(anchor.text_range().start(), to_insert);
81 },
82 )
83} 76}
84 77
85fn build_predicate(param: ast::TypeParam) -> Option<ast::WherePred> { 78fn build_predicate(param: ast::TypeParam) -> Option<ast::WherePred> {
diff --git a/crates/ra_assists/src/handlers/move_guard.rs b/crates/ra_assists/src/handlers/move_guard.rs
index 29bc9a9ff..fc0335b57 100644
--- a/crates/ra_assists/src/handlers/move_guard.rs
+++ b/crates/ra_assists/src/handlers/move_guard.rs
@@ -4,7 +4,7 @@ use ra_syntax::{
4 TextSize, 4 TextSize,
5}; 5};
6 6
7use crate::{Assist, AssistCtx, AssistId}; 7use crate::{AssistContext, AssistId, Assists};
8 8
9// Assist: move_guard_to_arm_body 9// Assist: move_guard_to_arm_body
10// 10//
@@ -31,7 +31,7 @@ use crate::{Assist, AssistCtx, AssistId};
31// } 31// }
32// } 32// }
33// ``` 33// ```
34pub(crate) fn move_guard_to_arm_body(ctx: AssistCtx) -> Option<Assist> { 34pub(crate) fn move_guard_to_arm_body(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
35 let match_arm = ctx.find_node_at_offset::<MatchArm>()?; 35 let match_arm = ctx.find_node_at_offset::<MatchArm>()?;
36 let guard = match_arm.guard()?; 36 let guard = match_arm.guard()?;
37 let space_before_guard = guard.syntax().prev_sibling_or_token(); 37 let space_before_guard = guard.syntax().prev_sibling_or_token();
@@ -41,7 +41,7 @@ pub(crate) fn move_guard_to_arm_body(ctx: AssistCtx) -> Option<Assist> {
41 let buf = format!("if {} {{ {} }}", guard_conditions.syntax().text(), arm_expr.syntax().text()); 41 let buf = format!("if {} {{ {} }}", guard_conditions.syntax().text(), arm_expr.syntax().text());
42 42
43 let target = guard.syntax().text_range(); 43 let target = guard.syntax().text_range();
44 ctx.add_assist(AssistId("move_guard_to_arm_body"), "Move guard to arm body", target, |edit| { 44 acc.add(AssistId("move_guard_to_arm_body"), "Move guard to arm body", target, |edit| {
45 let offseting_amount = match space_before_guard.and_then(|it| it.into_token()) { 45 let offseting_amount = match space_before_guard.and_then(|it| it.into_token()) {
46 Some(tok) => { 46 Some(tok) => {
47 if ast::Whitespace::cast(tok.clone()).is_some() { 47 if ast::Whitespace::cast(tok.clone()).is_some() {
@@ -88,7 +88,7 @@ pub(crate) fn move_guard_to_arm_body(ctx: AssistCtx) -> Option<Assist> {
88// } 88// }
89// } 89// }
90// ``` 90// ```
91pub(crate) fn move_arm_cond_to_match_guard(ctx: AssistCtx) -> Option<Assist> { 91pub(crate) fn move_arm_cond_to_match_guard(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
92 let match_arm: MatchArm = ctx.find_node_at_offset::<MatchArm>()?; 92 let match_arm: MatchArm = ctx.find_node_at_offset::<MatchArm>()?;
93 let match_pat = match_arm.pat()?; 93 let match_pat = match_arm.pat()?;
94 94
@@ -109,7 +109,7 @@ pub(crate) fn move_arm_cond_to_match_guard(ctx: AssistCtx) -> Option<Assist> {
109 let buf = format!(" if {}", cond.syntax().text()); 109 let buf = format!(" if {}", cond.syntax().text());
110 110
111 let target = if_expr.syntax().text_range(); 111 let target = if_expr.syntax().text_range();
112 ctx.add_assist( 112 acc.add(
113 AssistId("move_arm_cond_to_match_guard"), 113 AssistId("move_arm_cond_to_match_guard"),
114 "Move condition to match guard", 114 "Move condition to match guard",
115 target, 115 target,
diff --git a/crates/ra_assists/src/handlers/raw_string.rs b/crates/ra_assists/src/handlers/raw_string.rs
index 155c679b4..c20ffe0b3 100644
--- a/crates/ra_assists/src/handlers/raw_string.rs
+++ b/crates/ra_assists/src/handlers/raw_string.rs
@@ -5,7 +5,7 @@ use ra_syntax::{
5 TextSize, 5 TextSize,
6}; 6};
7 7
8use crate::{Assist, AssistCtx, AssistId}; 8use crate::{AssistContext, AssistId, Assists};
9 9
10// Assist: make_raw_string 10// Assist: make_raw_string
11// 11//
@@ -22,11 +22,11 @@ use crate::{Assist, AssistCtx, AssistId};
22// r#"Hello, World!"#; 22// r#"Hello, World!"#;
23// } 23// }
24// ``` 24// ```
25pub(crate) fn make_raw_string(ctx: AssistCtx) -> Option<Assist> { 25pub(crate) fn make_raw_string(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
26 let token = ctx.find_token_at_offset(STRING).and_then(ast::String::cast)?; 26 let token = ctx.find_token_at_offset(STRING).and_then(ast::String::cast)?;
27 let value = token.value()?; 27 let value = token.value()?;
28 let target = token.syntax().text_range(); 28 let target = token.syntax().text_range();
29 ctx.add_assist(AssistId("make_raw_string"), "Rewrite as raw string", target, |edit| { 29 acc.add(AssistId("make_raw_string"), "Rewrite as raw string", target, |edit| {
30 let max_hash_streak = count_hashes(&value); 30 let max_hash_streak = count_hashes(&value);
31 let mut hashes = String::with_capacity(max_hash_streak + 1); 31 let mut hashes = String::with_capacity(max_hash_streak + 1);
32 for _ in 0..hashes.capacity() { 32 for _ in 0..hashes.capacity() {
@@ -51,11 +51,11 @@ pub(crate) fn make_raw_string(ctx: AssistCtx) -> Option<Assist> {
51// "Hello, \"World!\""; 51// "Hello, \"World!\"";
52// } 52// }
53// ``` 53// ```
54pub(crate) fn make_usual_string(ctx: AssistCtx) -> Option<Assist> { 54pub(crate) fn make_usual_string(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
55 let token = ctx.find_token_at_offset(RAW_STRING).and_then(ast::RawString::cast)?; 55 let token = ctx.find_token_at_offset(RAW_STRING).and_then(ast::RawString::cast)?;
56 let value = token.value()?; 56 let value = token.value()?;
57 let target = token.syntax().text_range(); 57 let target = token.syntax().text_range();
58 ctx.add_assist(AssistId("make_usual_string"), "Rewrite as regular string", target, |edit| { 58 acc.add(AssistId("make_usual_string"), "Rewrite as regular string", target, |edit| {
59 // parse inside string to escape `"` 59 // parse inside string to escape `"`
60 let escaped = value.escape_default().to_string(); 60 let escaped = value.escape_default().to_string();
61 edit.replace(token.syntax().text_range(), format!("\"{}\"", escaped)); 61 edit.replace(token.syntax().text_range(), format!("\"{}\"", escaped));
@@ -77,10 +77,10 @@ pub(crate) fn make_usual_string(ctx: AssistCtx) -> Option<Assist> {
77// r##"Hello, World!"##; 77// r##"Hello, World!"##;
78// } 78// }
79// ``` 79// ```
80pub(crate) fn add_hash(ctx: AssistCtx) -> Option<Assist> { 80pub(crate) fn add_hash(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
81 let token = ctx.find_token_at_offset(RAW_STRING)?; 81 let token = ctx.find_token_at_offset(RAW_STRING)?;
82 let target = token.text_range(); 82 let target = token.text_range();
83 ctx.add_assist(AssistId("add_hash"), "Add # to raw string", target, |edit| { 83 acc.add(AssistId("add_hash"), "Add # to raw string", target, |edit| {
84 edit.insert(token.text_range().start() + TextSize::of('r'), "#"); 84 edit.insert(token.text_range().start() + TextSize::of('r'), "#");
85 edit.insert(token.text_range().end(), "#"); 85 edit.insert(token.text_range().end(), "#");
86 }) 86 })
@@ -101,7 +101,7 @@ pub(crate) fn add_hash(ctx: AssistCtx) -> Option<Assist> {
101// r"Hello, World!"; 101// r"Hello, World!";
102// } 102// }
103// ``` 103// ```
104pub(crate) fn remove_hash(ctx: AssistCtx) -> Option<Assist> { 104pub(crate) fn remove_hash(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
105 let token = ctx.find_token_at_offset(RAW_STRING)?; 105 let token = ctx.find_token_at_offset(RAW_STRING)?;
106 let text = token.text().as_str(); 106 let text = token.text().as_str();
107 if text.starts_with("r\"") { 107 if text.starts_with("r\"") {
@@ -109,7 +109,7 @@ pub(crate) fn remove_hash(ctx: AssistCtx) -> Option<Assist> {
109 return None; 109 return None;
110 } 110 }
111 let target = token.text_range(); 111 let target = token.text_range();
112 ctx.add_assist(AssistId("remove_hash"), "Remove hash from raw string", target, |edit| { 112 acc.add(AssistId("remove_hash"), "Remove hash from raw string", target, |edit| {
113 let result = &text[2..text.len() - 1]; 113 let result = &text[2..text.len() - 1];
114 let result = if result.starts_with('\"') { 114 let result = if result.starts_with('\"') {
115 // FIXME: this logic is wrong, not only the last has has to handled specially 115 // FIXME: this logic is wrong, not only the last has has to handled specially
diff --git a/crates/ra_assists/src/handlers/remove_dbg.rs b/crates/ra_assists/src/handlers/remove_dbg.rs
index e6e02f2ae..8eef578cf 100644
--- a/crates/ra_assists/src/handlers/remove_dbg.rs
+++ b/crates/ra_assists/src/handlers/remove_dbg.rs
@@ -3,7 +3,7 @@ use ra_syntax::{
3 TextSize, T, 3 TextSize, T,
4}; 4};
5 5
6use crate::{Assist, AssistCtx, AssistId}; 6use crate::{AssistContext, AssistId, Assists};
7 7
8// Assist: remove_dbg 8// Assist: remove_dbg
9// 9//
@@ -20,7 +20,7 @@ use crate::{Assist, AssistCtx, AssistId};
20// 92; 20// 92;
21// } 21// }
22// ``` 22// ```
23pub(crate) fn remove_dbg(ctx: AssistCtx) -> Option<Assist> { 23pub(crate) fn remove_dbg(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
24 let macro_call = ctx.find_node_at_offset::<ast::MacroCall>()?; 24 let macro_call = ctx.find_node_at_offset::<ast::MacroCall>()?;
25 25
26 if !is_valid_macrocall(&macro_call, "dbg")? { 26 if !is_valid_macrocall(&macro_call, "dbg")? {
@@ -58,7 +58,7 @@ pub(crate) fn remove_dbg(ctx: AssistCtx) -> Option<Assist> {
58 }; 58 };
59 59
60 let target = macro_call.syntax().text_range(); 60 let target = macro_call.syntax().text_range();
61 ctx.add_assist(AssistId("remove_dbg"), "Remove dbg!()", target, |edit| { 61 acc.add(AssistId("remove_dbg"), "Remove dbg!()", target, |edit| {
62 edit.replace(macro_range, macro_content); 62 edit.replace(macro_range, macro_content);
63 edit.set_cursor(cursor_pos); 63 edit.set_cursor(cursor_pos);
64 }) 64 })
diff --git a/crates/ra_assists/src/handlers/remove_mut.rs b/crates/ra_assists/src/handlers/remove_mut.rs
index 9f72f879d..dce546db7 100644
--- a/crates/ra_assists/src/handlers/remove_mut.rs
+++ b/crates/ra_assists/src/handlers/remove_mut.rs
@@ -1,6 +1,6 @@
1use ra_syntax::{SyntaxKind, TextRange, T}; 1use ra_syntax::{SyntaxKind, TextRange, T};
2 2
3use crate::{Assist, AssistCtx, AssistId}; 3use crate::{AssistContext, AssistId, Assists};
4 4
5// Assist: remove_mut 5// Assist: remove_mut
6// 6//
@@ -17,7 +17,7 @@ use crate::{Assist, AssistCtx, AssistId};
17// fn feed(&self, amount: u32) {} 17// fn feed(&self, amount: u32) {}
18// } 18// }
19// ``` 19// ```
20pub(crate) fn remove_mut(ctx: AssistCtx) -> Option<Assist> { 20pub(crate) fn remove_mut(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
21 let mut_token = ctx.find_token_at_offset(T![mut])?; 21 let mut_token = ctx.find_token_at_offset(T![mut])?;
22 let delete_from = mut_token.text_range().start(); 22 let delete_from = mut_token.text_range().start();
23 let delete_to = match mut_token.next_token() { 23 let delete_to = match mut_token.next_token() {
@@ -26,7 +26,7 @@ pub(crate) fn remove_mut(ctx: AssistCtx) -> Option<Assist> {
26 }; 26 };
27 27
28 let target = mut_token.text_range(); 28 let target = mut_token.text_range();
29 ctx.add_assist(AssistId("remove_mut"), "Remove `mut` keyword", target, |edit| { 29 acc.add(AssistId("remove_mut"), "Remove `mut` keyword", target, |edit| {
30 edit.set_cursor(delete_from); 30 edit.set_cursor(delete_from);
31 edit.delete(TextRange::new(delete_from, delete_to)); 31 edit.delete(TextRange::new(delete_from, delete_to));
32 }) 32 })
diff --git a/crates/ra_assists/src/handlers/reorder_fields.rs b/crates/ra_assists/src/handlers/reorder_fields.rs
index 0b930dea2..757f6406e 100644
--- a/crates/ra_assists/src/handlers/reorder_fields.rs
+++ b/crates/ra_assists/src/handlers/reorder_fields.rs
@@ -3,18 +3,9 @@ use std::collections::HashMap;
3use hir::{Adt, ModuleDef, PathResolution, Semantics, Struct}; 3use hir::{Adt, ModuleDef, PathResolution, Semantics, Struct};
4use itertools::Itertools; 4use itertools::Itertools;
5use ra_ide_db::RootDatabase; 5use ra_ide_db::RootDatabase;
6use ra_syntax::{ 6use ra_syntax::{algo, ast, match_ast, AstNode, SyntaxKind, SyntaxKind::*, SyntaxNode};
7 algo, 7
8 ast::{self, Path, RecordLit, RecordPat}, 8use crate::{AssistContext, AssistId, Assists};
9 match_ast, AstNode, SyntaxKind,
10 SyntaxKind::*,
11 SyntaxNode,
12};
13
14use crate::{
15 assist_ctx::{Assist, AssistCtx},
16 AssistId,
17};
18 9
19// Assist: reorder_fields 10// Assist: reorder_fields
20// 11//
@@ -31,13 +22,13 @@ use crate::{
31// const test: Foo = Foo {foo: 1, bar: 0} 22// const test: Foo = Foo {foo: 1, bar: 0}
32// ``` 23// ```
33// 24//
34pub(crate) fn reorder_fields(ctx: AssistCtx) -> Option<Assist> { 25pub(crate) fn reorder_fields(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
35 reorder::<RecordLit>(ctx.clone()).or_else(|| reorder::<RecordPat>(ctx)) 26 reorder::<ast::RecordLit>(acc, ctx.clone()).or_else(|| reorder::<ast::RecordPat>(acc, ctx))
36} 27}
37 28
38fn reorder<R: AstNode>(ctx: AssistCtx) -> Option<Assist> { 29fn reorder<R: AstNode>(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
39 let record = ctx.find_node_at_offset::<R>()?; 30 let record = ctx.find_node_at_offset::<R>()?;
40 let path = record.syntax().children().find_map(Path::cast)?; 31 let path = record.syntax().children().find_map(ast::Path::cast)?;
41 32
42 let ranks = compute_fields_ranks(&path, &ctx)?; 33 let ranks = compute_fields_ranks(&path, &ctx)?;
43 34
@@ -51,7 +42,7 @@ fn reorder<R: AstNode>(ctx: AssistCtx) -> Option<Assist> {
51 } 42 }
52 43
53 let target = record.syntax().text_range(); 44 let target = record.syntax().text_range();
54 ctx.add_assist(AssistId("reorder_fields"), "Reorder record fields", target, |edit| { 45 acc.add(AssistId("reorder_fields"), "Reorder record fields", target, |edit| {
55 for (old, new) in fields.iter().zip(&sorted_fields) { 46 for (old, new) in fields.iter().zip(&sorted_fields) {
56 algo::diff(old, new).into_text_edit(edit.text_edit_builder()); 47 algo::diff(old, new).into_text_edit(edit.text_edit_builder());
57 } 48 }
@@ -96,9 +87,9 @@ fn struct_definition(path: &ast::Path, sema: &Semantics<RootDatabase>) -> Option
96 } 87 }
97} 88}
98 89
99fn compute_fields_ranks(path: &Path, ctx: &AssistCtx) -> Option<HashMap<String, usize>> { 90fn compute_fields_ranks(path: &ast::Path, ctx: &AssistContext) -> Option<HashMap<String, usize>> {
100 Some( 91 Some(
101 struct_definition(path, ctx.sema)? 92 struct_definition(path, &ctx.sema)?
102 .fields(ctx.db) 93 .fields(ctx.db)
103 .iter() 94 .iter()
104 .enumerate() 95 .enumerate()
diff --git a/crates/ra_assists/src/handlers/replace_if_let_with_match.rs b/crates/ra_assists/src/handlers/replace_if_let_with_match.rs
index 2eb8348f8..65f5fc6ab 100644
--- a/crates/ra_assists/src/handlers/replace_if_let_with_match.rs
+++ b/crates/ra_assists/src/handlers/replace_if_let_with_match.rs
@@ -1,10 +1,14 @@
1use ra_fmt::unwrap_trivial_block; 1use ra_fmt::unwrap_trivial_block;
2use ra_syntax::{ 2use ra_syntax::{
3 ast::{self, edit::IndentLevel, make}, 3 ast::{
4 self,
5 edit::{AstNodeEdit, IndentLevel},
6 make,
7 },
4 AstNode, 8 AstNode,
5}; 9};
6 10
7use crate::{utils::TryEnum, Assist, AssistCtx, AssistId}; 11use crate::{utils::TryEnum, AssistContext, AssistId, Assists};
8 12
9// Assist: replace_if_let_with_match 13// Assist: replace_if_let_with_match
10// 14//
@@ -32,7 +36,7 @@ use crate::{utils::TryEnum, Assist, AssistCtx, AssistId};
32// } 36// }
33// } 37// }
34// ``` 38// ```
35pub(crate) fn replace_if_let_with_match(ctx: AssistCtx) -> Option<Assist> { 39pub(crate) fn replace_if_let_with_match(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
36 let if_expr: ast::IfExpr = ctx.find_node_at_offset()?; 40 let if_expr: ast::IfExpr = ctx.find_node_at_offset()?;
37 let cond = if_expr.condition()?; 41 let cond = if_expr.condition()?;
38 let pat = cond.pat()?; 42 let pat = cond.pat()?;
@@ -43,36 +47,30 @@ pub(crate) fn replace_if_let_with_match(ctx: AssistCtx) -> Option<Assist> {
43 ast::ElseBranch::IfExpr(_) => return None, 47 ast::ElseBranch::IfExpr(_) => return None,
44 }; 48 };
45 49
46 let sema = ctx.sema;
47 let target = if_expr.syntax().text_range(); 50 let target = if_expr.syntax().text_range();
48 ctx.add_assist( 51 acc.add(AssistId("replace_if_let_with_match"), "Replace with match", target, move |edit| {
49 AssistId("replace_if_let_with_match"), 52 let match_expr = {
50 "Replace with match", 53 let then_arm = {
51 target, 54 let then_expr = unwrap_trivial_block(then_block);
52 move |edit| { 55 make::match_arm(vec![pat.clone()], then_expr)
53 let match_expr = {
54 let then_arm = {
55 let then_expr = unwrap_trivial_block(then_block);
56 make::match_arm(vec![pat.clone()], then_expr)
57 };
58 let else_arm = {
59 let pattern = sema
60 .type_of_pat(&pat)
61 .and_then(|ty| TryEnum::from_ty(sema, &ty))
62 .map(|it| it.sad_pattern())
63 .unwrap_or_else(|| make::placeholder_pat().into());
64 let else_expr = unwrap_trivial_block(else_block);
65 make::match_arm(vec![pattern], else_expr)
66 };
67 make::expr_match(expr, make::match_arm_list(vec![then_arm, else_arm]))
68 }; 56 };
57 let else_arm = {
58 let pattern = ctx
59 .sema
60 .type_of_pat(&pat)
61 .and_then(|ty| TryEnum::from_ty(&ctx.sema, &ty))
62 .map(|it| it.sad_pattern())
63 .unwrap_or_else(|| make::placeholder_pat().into());
64 let else_expr = unwrap_trivial_block(else_block);
65 make::match_arm(vec![pattern], else_expr)
66 };
67 make::expr_match(expr, make::match_arm_list(vec![then_arm, else_arm]))
68 .indent(IndentLevel::from_node(if_expr.syntax()))
69 };
69 70
70 let match_expr = IndentLevel::from_node(if_expr.syntax()).increase_indent(match_expr); 71 edit.set_cursor(if_expr.syntax().text_range().start());
71 72 edit.replace_ast::<ast::Expr>(if_expr.into(), match_expr);
72 edit.set_cursor(if_expr.syntax().text_range().start()); 73 })
73 edit.replace_ast::<ast::Expr>(if_expr.into(), match_expr);
74 },
75 )
76} 74}
77 75
78#[cfg(test)] 76#[cfg(test)]
diff --git a/crates/ra_assists/src/handlers/replace_let_with_if_let.rs b/crates/ra_assists/src/handlers/replace_let_with_if_let.rs
index a5509a567..482957dc6 100644
--- a/crates/ra_assists/src/handlers/replace_let_with_if_let.rs
+++ b/crates/ra_assists/src/handlers/replace_let_with_if_let.rs
@@ -9,11 +9,7 @@ use ra_syntax::{
9 AstNode, T, 9 AstNode, T,
10}; 10};
11 11
12use crate::{ 12use crate::{utils::TryEnum, AssistContext, AssistId, Assists};
13 assist_ctx::{Assist, AssistCtx},
14 utils::TryEnum,
15 AssistId,
16};
17 13
18// Assist: replace_let_with_if_let 14// Assist: replace_let_with_if_let
19// 15//
@@ -39,16 +35,16 @@ use crate::{
39// 35//
40// fn compute() -> Option<i32> { None } 36// fn compute() -> Option<i32> { None }
41// ``` 37// ```
42pub(crate) fn replace_let_with_if_let(ctx: AssistCtx) -> Option<Assist> { 38pub(crate) fn replace_let_with_if_let(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
43 let let_kw = ctx.find_token_at_offset(T![let])?; 39 let let_kw = ctx.find_token_at_offset(T![let])?;
44 let let_stmt = let_kw.ancestors().find_map(ast::LetStmt::cast)?; 40 let let_stmt = let_kw.ancestors().find_map(ast::LetStmt::cast)?;
45 let init = let_stmt.initializer()?; 41 let init = let_stmt.initializer()?;
46 let original_pat = let_stmt.pat()?; 42 let original_pat = let_stmt.pat()?;
47 let ty = ctx.sema.type_of_expr(&init)?; 43 let ty = ctx.sema.type_of_expr(&init)?;
48 let happy_variant = TryEnum::from_ty(ctx.sema, &ty).map(|it| it.happy_case()); 44 let happy_variant = TryEnum::from_ty(&ctx.sema, &ty).map(|it| it.happy_case());
49 45
50 let target = let_kw.text_range(); 46 let target = let_kw.text_range();
51 ctx.add_assist(AssistId("replace_let_with_if_let"), "Replace with if-let", target, |edit| { 47 acc.add(AssistId("replace_let_with_if_let"), "Replace with if-let", target, |edit| {
52 let with_placeholder: ast::Pat = match happy_variant { 48 let with_placeholder: ast::Pat = match happy_variant {
53 None => make::placeholder_pat().into(), 49 None => make::placeholder_pat().into(),
54 Some(var_name) => make::tuple_struct_pat( 50 Some(var_name) => make::tuple_struct_pat(
@@ -57,8 +53,7 @@ pub(crate) fn replace_let_with_if_let(ctx: AssistCtx) -> Option<Assist> {
57 ) 53 )
58 .into(), 54 .into(),
59 }; 55 };
60 let block = 56 let block = make::block_expr(None, None).indent(IndentLevel::from_node(let_stmt.syntax()));
61 IndentLevel::from_node(let_stmt.syntax()).increase_indent(make::block_expr(None, None));
62 let if_ = make::expr_if(make::condition(init, Some(with_placeholder)), block); 57 let if_ = make::expr_if(make::condition(init, Some(with_placeholder)), block);
63 let stmt = make::expr_stmt(if_); 58 let stmt = make::expr_stmt(if_);
64 59
diff --git a/crates/ra_assists/src/handlers/replace_qualified_name_with_use.rs b/crates/ra_assists/src/handlers/replace_qualified_name_with_use.rs
index fd41da64b..1a81d8a0e 100644
--- a/crates/ra_assists/src/handlers/replace_qualified_name_with_use.rs
+++ b/crates/ra_assists/src/handlers/replace_qualified_name_with_use.rs
@@ -1,11 +1,7 @@
1use hir; 1use hir;
2use ra_syntax::{ast, AstNode, SmolStr, TextRange}; 2use ra_syntax::{ast, AstNode, SmolStr, TextRange};
3 3
4use crate::{ 4use crate::{utils::insert_use_statement, AssistContext, AssistId, Assists};
5 assist_ctx::{Assist, AssistCtx},
6 utils::insert_use_statement,
7 AssistId,
8};
9 5
10// Assist: replace_qualified_name_with_use 6// Assist: replace_qualified_name_with_use
11// 7//
@@ -20,7 +16,10 @@ use crate::{
20// 16//
21// fn process(map: HashMap<String, String>) {} 17// fn process(map: HashMap<String, String>) {}
22// ``` 18// ```
23pub(crate) fn replace_qualified_name_with_use(ctx: AssistCtx) -> Option<Assist> { 19pub(crate) fn replace_qualified_name_with_use(
20 acc: &mut Assists,
21 ctx: &AssistContext,
22) -> Option<()> {
24 let path: ast::Path = ctx.find_node_at_offset()?; 23 let path: ast::Path = ctx.find_node_at_offset()?;
25 // We don't want to mess with use statements 24 // We don't want to mess with use statements
26 if path.syntax().ancestors().find_map(ast::UseItem::cast).is_some() { 25 if path.syntax().ancestors().find_map(ast::UseItem::cast).is_some() {
@@ -34,18 +33,18 @@ pub(crate) fn replace_qualified_name_with_use(ctx: AssistCtx) -> Option<Assist>
34 } 33 }
35 34
36 let target = path.syntax().text_range(); 35 let target = path.syntax().text_range();
37 ctx.add_assist( 36 acc.add(
38 AssistId("replace_qualified_name_with_use"), 37 AssistId("replace_qualified_name_with_use"),
39 "Replace qualified path with use", 38 "Replace qualified path with use",
40 target, 39 target,
41 |edit| { 40 |builder| {
42 let path_to_import = hir_path.mod_path().clone(); 41 let path_to_import = hir_path.mod_path().clone();
43 insert_use_statement(path.syntax(), &path_to_import, edit); 42 insert_use_statement(path.syntax(), &path_to_import, ctx, builder);
44 43
45 if let Some(last) = path.segment() { 44 if let Some(last) = path.segment() {
46 // Here we are assuming the assist will provide a correct use statement 45 // Here we are assuming the assist will provide a correct use statement
47 // so we can delete the path qualifier 46 // so we can delete the path qualifier
48 edit.delete(TextRange::new( 47 builder.delete(TextRange::new(
49 path.syntax().text_range().start(), 48 path.syntax().text_range().start(),
50 last.syntax().text_range().start(), 49 last.syntax().text_range().start(),
51 )); 50 ));
diff --git a/crates/ra_assists/src/handlers/replace_unwrap_with_match.rs b/crates/ra_assists/src/handlers/replace_unwrap_with_match.rs
index c6b73da67..c4b56f6e9 100644
--- a/crates/ra_assists/src/handlers/replace_unwrap_with_match.rs
+++ b/crates/ra_assists/src/handlers/replace_unwrap_with_match.rs
@@ -1,11 +1,15 @@
1use std::iter; 1use std::iter;
2 2
3use ra_syntax::{ 3use ra_syntax::{
4 ast::{self, edit::IndentLevel, make}, 4 ast::{
5 self,
6 edit::{AstNodeEdit, IndentLevel},
7 make,
8 },
5 AstNode, 9 AstNode,
6}; 10};
7 11
8use crate::{utils::TryEnum, Assist, AssistCtx, AssistId}; 12use crate::{utils::TryEnum, AssistContext, AssistId, Assists};
9 13
10// Assist: replace_unwrap_with_match 14// Assist: replace_unwrap_with_match
11// 15//
@@ -29,7 +33,7 @@ use crate::{utils::TryEnum, Assist, AssistCtx, AssistId};
29// }; 33// };
30// } 34// }
31// ``` 35// ```
32pub(crate) fn replace_unwrap_with_match(ctx: AssistCtx) -> Option<Assist> { 36pub(crate) fn replace_unwrap_with_match(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
33 let method_call: ast::MethodCallExpr = ctx.find_node_at_offset()?; 37 let method_call: ast::MethodCallExpr = ctx.find_node_at_offset()?;
34 let name = method_call.name_ref()?; 38 let name = method_call.name_ref()?;
35 if name.text() != "unwrap" { 39 if name.text() != "unwrap" {
@@ -37,33 +41,26 @@ pub(crate) fn replace_unwrap_with_match(ctx: AssistCtx) -> Option<Assist> {
37 } 41 }
38 let caller = method_call.expr()?; 42 let caller = method_call.expr()?;
39 let ty = ctx.sema.type_of_expr(&caller)?; 43 let ty = ctx.sema.type_of_expr(&caller)?;
40 let happy_variant = TryEnum::from_ty(ctx.sema, &ty)?.happy_case(); 44 let happy_variant = TryEnum::from_ty(&ctx.sema, &ty)?.happy_case();
41 let target = method_call.syntax().text_range(); 45 let target = method_call.syntax().text_range();
42 ctx.add_assist( 46 acc.add(AssistId("replace_unwrap_with_match"), "Replace unwrap with match", target, |edit| {
43 AssistId("replace_unwrap_with_match"), 47 let ok_path = make::path_unqualified(make::path_segment(make::name_ref(happy_variant)));
44 "Replace unwrap with match", 48 let it = make::bind_pat(make::name("a")).into();
45 target, 49 let ok_tuple = make::tuple_struct_pat(ok_path, iter::once(it)).into();
46 |edit| {
47 let ok_path = make::path_unqualified(make::path_segment(make::name_ref(happy_variant)));
48 let it = make::bind_pat(make::name("a")).into();
49 let ok_tuple = make::tuple_struct_pat(ok_path, iter::once(it)).into();
50 50
51 let bind_path = make::path_unqualified(make::path_segment(make::name_ref("a"))); 51 let bind_path = make::path_unqualified(make::path_segment(make::name_ref("a")));
52 let ok_arm = make::match_arm(iter::once(ok_tuple), make::expr_path(bind_path)); 52 let ok_arm = make::match_arm(iter::once(ok_tuple), make::expr_path(bind_path));
53 53
54 let unreachable_call = make::unreachable_macro_call().into(); 54 let unreachable_call = make::unreachable_macro_call().into();
55 let err_arm = 55 let err_arm = make::match_arm(iter::once(make::placeholder_pat().into()), unreachable_call);
56 make::match_arm(iter::once(make::placeholder_pat().into()), unreachable_call);
57 56
58 let match_arm_list = make::match_arm_list(vec![ok_arm, err_arm]); 57 let match_arm_list = make::match_arm_list(vec![ok_arm, err_arm]);
59 let match_expr = make::expr_match(caller.clone(), match_arm_list); 58 let match_expr = make::expr_match(caller.clone(), match_arm_list)
60 let match_expr = 59 .indent(IndentLevel::from_node(method_call.syntax()));
61 IndentLevel::from_node(method_call.syntax()).increase_indent(match_expr);
62 60
63 edit.set_cursor(caller.syntax().text_range().start()); 61 edit.set_cursor(caller.syntax().text_range().start());
64 edit.replace_ast::<ast::Expr>(method_call.into(), match_expr); 62 edit.replace_ast::<ast::Expr>(method_call.into(), match_expr);
65 }, 63 })
66 )
67} 64}
68 65
69#[cfg(test)] 66#[cfg(test)]
diff --git a/crates/ra_assists/src/handlers/split_import.rs b/crates/ra_assists/src/handlers/split_import.rs
index d49563974..b2757e50c 100644
--- a/crates/ra_assists/src/handlers/split_import.rs
+++ b/crates/ra_assists/src/handlers/split_import.rs
@@ -2,7 +2,7 @@ use std::iter::successors;
2 2
3use ra_syntax::{ast, AstNode, T}; 3use ra_syntax::{ast, AstNode, T};
4 4
5use crate::{Assist, AssistCtx, AssistId}; 5use crate::{AssistContext, AssistId, Assists};
6 6
7// Assist: split_import 7// Assist: split_import
8// 8//
@@ -15,7 +15,7 @@ use crate::{Assist, AssistCtx, AssistId};
15// ``` 15// ```
16// use std::{collections::HashMap}; 16// use std::{collections::HashMap};
17// ``` 17// ```
18pub(crate) fn split_import(ctx: AssistCtx) -> Option<Assist> { 18pub(crate) fn split_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
19 let colon_colon = ctx.find_token_at_offset(T![::])?; 19 let colon_colon = ctx.find_token_at_offset(T![::])?;
20 let path = ast::Path::cast(colon_colon.parent())?.qualifier()?; 20 let path = ast::Path::cast(colon_colon.parent())?.qualifier()?;
21 let top_path = successors(Some(path.clone()), |it| it.parent_path()).last()?; 21 let top_path = successors(Some(path.clone()), |it| it.parent_path()).last()?;
@@ -26,10 +26,10 @@ pub(crate) fn split_import(ctx: AssistCtx) -> Option<Assist> {
26 if new_tree == use_tree { 26 if new_tree == use_tree {
27 return None; 27 return None;
28 } 28 }
29 let cursor = ctx.frange.range.start(); 29 let cursor = ctx.offset();
30 30
31 let target = colon_colon.text_range(); 31 let target = colon_colon.text_range();
32 ctx.add_assist(AssistId("split_import"), "Split import", target, |edit| { 32 acc.add(AssistId("split_import"), "Split import", target, |edit| {
33 edit.replace_ast(use_tree, new_tree); 33 edit.replace_ast(use_tree, new_tree);
34 edit.set_cursor(cursor); 34 edit.set_cursor(cursor);
35 }) 35 })
diff --git a/crates/ra_assists/src/handlers/unwrap_block.rs b/crates/ra_assists/src/handlers/unwrap_block.rs
index 6df927abb..eba0631a4 100644
--- a/crates/ra_assists/src/handlers/unwrap_block.rs
+++ b/crates/ra_assists/src/handlers/unwrap_block.rs
@@ -1,4 +1,4 @@
1use crate::{Assist, AssistCtx, AssistId}; 1use crate::{AssistContext, AssistId, Assists};
2 2
3use ast::LoopBodyOwner; 3use ast::LoopBodyOwner;
4use ra_fmt::unwrap_trivial_block; 4use ra_fmt::unwrap_trivial_block;
@@ -21,7 +21,7 @@ use ra_syntax::{ast, match_ast, AstNode, TextRange, T};
21// println!("foo"); 21// println!("foo");
22// } 22// }
23// ``` 23// ```
24pub(crate) fn unwrap_block(ctx: AssistCtx) -> Option<Assist> { 24pub(crate) fn unwrap_block(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
25 let l_curly_token = ctx.find_token_at_offset(T!['{'])?; 25 let l_curly_token = ctx.find_token_at_offset(T!['{'])?;
26 let block = ast::BlockExpr::cast(l_curly_token.parent())?; 26 let block = ast::BlockExpr::cast(l_curly_token.parent())?;
27 let parent = block.syntax().parent()?; 27 let parent = block.syntax().parent()?;
@@ -58,7 +58,7 @@ pub(crate) fn unwrap_block(ctx: AssistCtx) -> Option<Assist> {
58 }; 58 };
59 59
60 let target = expr_to_unwrap.syntax().text_range(); 60 let target = expr_to_unwrap.syntax().text_range();
61 ctx.add_assist(AssistId("unwrap_block"), "Unwrap block", target, |edit| { 61 acc.add(AssistId("unwrap_block"), "Unwrap block", target, |edit| {
62 edit.set_cursor(expr.syntax().text_range().start()); 62 edit.set_cursor(expr.syntax().text_range().start());
63 63
64 let pat_start: &[_] = &[' ', '{', '\n']; 64 let pat_start: &[_] = &[' ', '{', '\n'];
diff --git a/crates/ra_assists/src/lib.rs b/crates/ra_assists/src/lib.rs
index 0473fd8c2..b6dc7cb1b 100644
--- a/crates/ra_assists/src/lib.rs
+++ b/crates/ra_assists/src/lib.rs
@@ -10,7 +10,7 @@ macro_rules! eprintln {
10 ($($tt:tt)*) => { stdx::eprintln!($($tt)*) }; 10 ($($tt:tt)*) => { stdx::eprintln!($($tt)*) };
11} 11}
12 12
13mod assist_ctx; 13mod assist_context;
14mod marks; 14mod marks;
15#[cfg(test)] 15#[cfg(test)]
16mod tests; 16mod tests;
@@ -22,15 +22,18 @@ use ra_db::FileRange;
22use ra_ide_db::{source_change::SourceChange, RootDatabase}; 22use ra_ide_db::{source_change::SourceChange, RootDatabase};
23use ra_syntax::TextRange; 23use ra_syntax::TextRange;
24 24
25pub(crate) use crate::assist_ctx::{Assist, AssistCtx}; 25pub(crate) use crate::assist_context::{AssistContext, Assists};
26 26
27/// Unique identifier of the assist, should not be shown to the user 27/// Unique identifier of the assist, should not be shown to the user
28/// directly. 28/// directly.
29#[derive(Debug, Clone, Copy, PartialEq, Eq)] 29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30pub struct AssistId(pub &'static str); 30pub struct AssistId(pub &'static str);
31 31
32#[derive(Clone, Debug)]
33pub struct GroupLabel(pub String);
34
32#[derive(Debug, Clone)] 35#[derive(Debug, Clone)]
33pub struct AssistLabel { 36pub struct Assist {
34 pub id: AssistId, 37 pub id: AssistId,
35 /// Short description of the assist, as shown in the UI. 38 /// Short description of the assist, as shown in the UI.
36 pub label: String, 39 pub label: String,
@@ -40,74 +43,69 @@ pub struct AssistLabel {
40 pub target: TextRange, 43 pub target: TextRange,
41} 44}
42 45
43#[derive(Clone, Debug)] 46#[derive(Debug, Clone)]
44pub struct GroupLabel(pub String); 47pub struct ResolvedAssist {
48 pub assist: Assist,
49 pub source_change: SourceChange,
50}
51
52impl Assist {
53 /// Return all the assists applicable at the given position.
54 ///
55 /// Assists are returned in the "unresolved" state, that is only labels are
56 /// returned, without actual edits.
57 pub fn unresolved(db: &RootDatabase, range: FileRange) -> Vec<Assist> {
58 let sema = Semantics::new(db);
59 let ctx = AssistContext::new(sema, range);
60 let mut acc = Assists::new_unresolved(&ctx);
61 handlers::all().iter().for_each(|handler| {
62 handler(&mut acc, &ctx);
63 });
64 acc.finish_unresolved()
65 }
66
67 /// Return all the assists applicable at the given position.
68 ///
69 /// Assists are returned in the "resolved" state, that is with edit fully
70 /// computed.
71 pub fn resolved(db: &RootDatabase, range: FileRange) -> Vec<ResolvedAssist> {
72 let sema = Semantics::new(db);
73 let ctx = AssistContext::new(sema, range);
74 let mut acc = Assists::new_resolved(&ctx);
75 handlers::all().iter().for_each(|handler| {
76 handler(&mut acc, &ctx);
77 });
78 acc.finish_resolved()
79 }
45 80
46impl AssistLabel {
47 pub(crate) fn new( 81 pub(crate) fn new(
48 id: AssistId, 82 id: AssistId,
49 label: String, 83 label: String,
50 group: Option<GroupLabel>, 84 group: Option<GroupLabel>,
51 target: TextRange, 85 target: TextRange,
52 ) -> AssistLabel { 86 ) -> Assist {
53 // FIXME: make fields private, so that this invariant can't be broken 87 // FIXME: make fields private, so that this invariant can't be broken
54 assert!(label.starts_with(|c: char| c.is_uppercase())); 88 assert!(label.starts_with(|c: char| c.is_uppercase()));
55 AssistLabel { id, label, group, target } 89 Assist { id, label, group, target }
56 } 90 }
57} 91}
58 92
59#[derive(Debug, Clone)]
60pub struct ResolvedAssist {
61 pub label: AssistLabel,
62 pub source_change: SourceChange,
63}
64
65/// Return all the assists applicable at the given position.
66///
67/// Assists are returned in the "unresolved" state, that is only labels are
68/// returned, without actual edits.
69pub fn unresolved_assists(db: &RootDatabase, range: FileRange) -> Vec<AssistLabel> {
70 let sema = Semantics::new(db);
71 let ctx = AssistCtx::new(&sema, range, false);
72 handlers::all()
73 .iter()
74 .filter_map(|f| f(ctx.clone()))
75 .flat_map(|it| it.0)
76 .map(|a| a.label)
77 .collect()
78}
79
80/// Return all the assists applicable at the given position.
81///
82/// Assists are returned in the "resolved" state, that is with edit fully
83/// computed.
84pub fn resolved_assists(db: &RootDatabase, range: FileRange) -> Vec<ResolvedAssist> {
85 let sema = Semantics::new(db);
86 let ctx = AssistCtx::new(&sema, range, true);
87 let mut a = handlers::all()
88 .iter()
89 .filter_map(|f| f(ctx.clone()))
90 .flat_map(|it| it.0)
91 .map(|it| it.into_resolved().unwrap())
92 .collect::<Vec<_>>();
93 a.sort_by_key(|it| it.label.target.len());
94 a
95}
96
97mod handlers { 93mod handlers {
98 use crate::{Assist, AssistCtx}; 94 use crate::{AssistContext, Assists};
99 95
100 pub(crate) type Handler = fn(AssistCtx) -> Option<Assist>; 96 pub(crate) type Handler = fn(&mut Assists, &AssistContext) -> Option<()>;
101 97
102 mod add_custom_impl; 98 mod add_custom_impl;
103 mod add_derive; 99 mod add_derive;
104 mod add_explicit_type; 100 mod add_explicit_type;
101 mod add_from_impl_for_enum;
105 mod add_function; 102 mod add_function;
106 mod add_impl; 103 mod add_impl;
107 mod add_missing_impl_members; 104 mod add_missing_impl_members;
108 mod add_new; 105 mod add_new;
109 mod apply_demorgan; 106 mod apply_demorgan;
110 mod auto_import; 107 mod auto_import;
108 mod change_return_type_to_result;
111 mod change_visibility; 109 mod change_visibility;
112 mod early_return; 110 mod early_return;
113 mod fill_match_arms; 111 mod fill_match_arms;
@@ -124,14 +122,12 @@ mod handlers {
124 mod raw_string; 122 mod raw_string;
125 mod remove_dbg; 123 mod remove_dbg;
126 mod remove_mut; 124 mod remove_mut;
125 mod reorder_fields;
127 mod replace_if_let_with_match; 126 mod replace_if_let_with_match;
128 mod replace_let_with_if_let; 127 mod replace_let_with_if_let;
129 mod replace_qualified_name_with_use; 128 mod replace_qualified_name_with_use;
130 mod replace_unwrap_with_match; 129 mod replace_unwrap_with_match;
131 mod split_import; 130 mod split_import;
132 mod change_return_type_to_result;
133 mod add_from_impl_for_enum;
134 mod reorder_fields;
135 mod unwrap_block; 131 mod unwrap_block;
136 132
137 pub(crate) fn all() -> &'static [Handler] { 133 pub(crate) fn all() -> &'static [Handler] {
diff --git a/crates/ra_assists/src/tests.rs b/crates/ra_assists/src/tests.rs
index 17e3ece9f..a3eacb8f1 100644
--- a/crates/ra_assists/src/tests.rs
+++ b/crates/ra_assists/src/tests.rs
@@ -11,7 +11,7 @@ use test_utils::{
11 RangeOrOffset, 11 RangeOrOffset,
12}; 12};
13 13
14use crate::{handlers::Handler, resolved_assists, AssistCtx}; 14use crate::{handlers::Handler, Assist, AssistContext, Assists};
15 15
16pub(crate) fn with_single_file(text: &str) -> (RootDatabase, FileId) { 16pub(crate) fn with_single_file(text: &str) -> (RootDatabase, FileId) {
17 let (mut db, file_id) = RootDatabase::with_single_file(text); 17 let (mut db, file_id) = RootDatabase::with_single_file(text);
@@ -41,16 +41,16 @@ fn check_doc_test(assist_id: &str, before: &str, after: &str) {
41 let (db, file_id) = crate::tests::with_single_file(&before); 41 let (db, file_id) = crate::tests::with_single_file(&before);
42 let frange = FileRange { file_id, range: selection.into() }; 42 let frange = FileRange { file_id, range: selection.into() };
43 43
44 let mut assist = resolved_assists(&db, frange) 44 let mut assist = Assist::resolved(&db, frange)
45 .into_iter() 45 .into_iter()
46 .find(|assist| assist.label.id.0 == assist_id) 46 .find(|assist| assist.assist.id.0 == assist_id)
47 .unwrap_or_else(|| { 47 .unwrap_or_else(|| {
48 panic!( 48 panic!(
49 "\n\nAssist is not applicable: {}\nAvailable assists: {}", 49 "\n\nAssist is not applicable: {}\nAvailable assists: {}",
50 assist_id, 50 assist_id,
51 resolved_assists(&db, frange) 51 Assist::resolved(&db, frange)
52 .into_iter() 52 .into_iter()
53 .map(|assist| assist.label.id.0) 53 .map(|assist| assist.assist.id.0)
54 .collect::<Vec<_>>() 54 .collect::<Vec<_>>()
55 .join(", ") 55 .join(", ")
56 ) 56 )
@@ -71,7 +71,7 @@ enum ExpectedResult<'a> {
71 Target(&'a str), 71 Target(&'a str),
72} 72}
73 73
74fn check(assist: Handler, before: &str, expected: ExpectedResult) { 74fn check(handler: Handler, before: &str, expected: ExpectedResult) {
75 let (text_without_caret, file_with_caret_id, range_or_offset, db) = if before.contains("//-") { 75 let (text_without_caret, file_with_caret_id, range_or_offset, db) = if before.contains("//-") {
76 let (mut db, position) = RootDatabase::with_position(before); 76 let (mut db, position) = RootDatabase::with_position(before);
77 db.set_local_roots(Arc::new(vec![db.file_source_root(position.file_id)])); 77 db.set_local_roots(Arc::new(vec![db.file_source_root(position.file_id)]));
@@ -90,17 +90,20 @@ fn check(assist: Handler, before: &str, expected: ExpectedResult) {
90 let frange = FileRange { file_id: file_with_caret_id, range: range_or_offset.into() }; 90 let frange = FileRange { file_id: file_with_caret_id, range: range_or_offset.into() };
91 91
92 let sema = Semantics::new(&db); 92 let sema = Semantics::new(&db);
93 let assist_ctx = AssistCtx::new(&sema, frange, true); 93 let ctx = AssistContext::new(sema, frange);
94 94 let mut acc = Assists::new_resolved(&ctx);
95 match (assist(assist_ctx), expected) { 95 handler(&mut acc, &ctx);
96 let mut res = acc.finish_resolved();
97 let assist = res.pop();
98 match (assist, expected) {
96 (Some(assist), ExpectedResult::After(after)) => { 99 (Some(assist), ExpectedResult::After(after)) => {
97 let mut action = assist.0[0].source_change.clone().unwrap(); 100 let mut source_change = assist.source_change;
98 let change = action.source_file_edits.pop().unwrap(); 101 let change = source_change.source_file_edits.pop().unwrap();
99 102
100 let mut actual = db.file_text(change.file_id).as_ref().to_owned(); 103 let mut actual = db.file_text(change.file_id).as_ref().to_owned();
101 change.edit.apply(&mut actual); 104 change.edit.apply(&mut actual);
102 105
103 match action.cursor_position { 106 match source_change.cursor_position {
104 None => { 107 None => {
105 if let RangeOrOffset::Offset(before_cursor_pos) = range_or_offset { 108 if let RangeOrOffset::Offset(before_cursor_pos) = range_or_offset {
106 let off = change 109 let off = change
@@ -116,7 +119,7 @@ fn check(assist: Handler, before: &str, expected: ExpectedResult) {
116 assert_eq_text!(after, &actual); 119 assert_eq_text!(after, &actual);
117 } 120 }
118 (Some(assist), ExpectedResult::Target(target)) => { 121 (Some(assist), ExpectedResult::Target(target)) => {
119 let range = assist.0[0].label.target; 122 let range = assist.assist.target;
120 assert_eq_text!(&text_without_caret[range], target); 123 assert_eq_text!(&text_without_caret[range], target);
121 } 124 }
122 (Some(_), ExpectedResult::NotApplicable) => panic!("assist should not be applicable!"), 125 (Some(_), ExpectedResult::NotApplicable) => panic!("assist should not be applicable!"),
@@ -133,14 +136,14 @@ fn assist_order_field_struct() {
133 let (before_cursor_pos, before) = extract_offset(before); 136 let (before_cursor_pos, before) = extract_offset(before);
134 let (db, file_id) = with_single_file(&before); 137 let (db, file_id) = with_single_file(&before);
135 let frange = FileRange { file_id, range: TextRange::empty(before_cursor_pos) }; 138 let frange = FileRange { file_id, range: TextRange::empty(before_cursor_pos) };
136 let assists = resolved_assists(&db, frange); 139 let assists = Assist::resolved(&db, frange);
137 let mut assists = assists.iter(); 140 let mut assists = assists.iter();
138 141
139 assert_eq!( 142 assert_eq!(
140 assists.next().expect("expected assist").label.label, 143 assists.next().expect("expected assist").assist.label,
141 "Change visibility to pub(crate)" 144 "Change visibility to pub(crate)"
142 ); 145 );
143 assert_eq!(assists.next().expect("expected assist").label.label, "Add `#[derive]`"); 146 assert_eq!(assists.next().expect("expected assist").assist.label, "Add `#[derive]`");
144} 147}
145 148
146#[test] 149#[test]
@@ -156,9 +159,9 @@ fn assist_order_if_expr() {
156 let (range, before) = extract_range(before); 159 let (range, before) = extract_range(before);
157 let (db, file_id) = with_single_file(&before); 160 let (db, file_id) = with_single_file(&before);
158 let frange = FileRange { file_id, range }; 161 let frange = FileRange { file_id, range };
159 let assists = resolved_assists(&db, frange); 162 let assists = Assist::resolved(&db, frange);
160 let mut assists = assists.iter(); 163 let mut assists = assists.iter();
161 164
162 assert_eq!(assists.next().expect("expected assist").label.label, "Extract into variable"); 165 assert_eq!(assists.next().expect("expected assist").assist.label, "Extract into variable");
163 assert_eq!(assists.next().expect("expected assist").label.label, "Replace with match"); 166 assert_eq!(assists.next().expect("expected assist").assist.label, "Replace with match");
164} 167}
diff --git a/crates/ra_assists/src/utils/insert_use.rs b/crates/ra_assists/src/utils/insert_use.rs
index c1f447efe..1214e3cd4 100644
--- a/crates/ra_assists/src/utils/insert_use.rs
+++ b/crates/ra_assists/src/utils/insert_use.rs
@@ -2,7 +2,6 @@
2// FIXME: rewrite according to the plan, outlined in 2// FIXME: rewrite according to the plan, outlined in
3// https://github.com/rust-analyzer/rust-analyzer/issues/3301#issuecomment-592931553 3// https://github.com/rust-analyzer/rust-analyzer/issues/3301#issuecomment-592931553
4 4
5use crate::assist_ctx::ActionBuilder;
6use hir::{self, ModPath}; 5use hir::{self, ModPath};
7use ra_syntax::{ 6use ra_syntax::{
8 ast::{self, NameOwner}, 7 ast::{self, NameOwner},
@@ -12,6 +11,8 @@ use ra_syntax::{
12}; 11};
13use ra_text_edit::TextEditBuilder; 12use ra_text_edit::TextEditBuilder;
14 13
14use crate::assist_context::{AssistBuilder, AssistContext};
15
15/// Creates and inserts a use statement for the given path to import. 16/// Creates and inserts a use statement for the given path to import.
16/// The use statement is inserted in the scope most appropriate to the 17/// The use statement is inserted in the scope most appropriate to the
17/// the cursor position given, additionally merged with the existing use imports. 18/// the cursor position given, additionally merged with the existing use imports.
@@ -19,10 +20,11 @@ pub(crate) fn insert_use_statement(
19 // Ideally the position of the cursor, used to 20 // Ideally the position of the cursor, used to
20 position: &SyntaxNode, 21 position: &SyntaxNode,
21 path_to_import: &ModPath, 22 path_to_import: &ModPath,
22 edit: &mut ActionBuilder, 23 ctx: &AssistContext,
24 builder: &mut AssistBuilder,
23) { 25) {
24 let target = path_to_import.to_string().split("::").map(SmolStr::new).collect::<Vec<_>>(); 26 let target = path_to_import.to_string().split("::").map(SmolStr::new).collect::<Vec<_>>();
25 let container = edit.ctx().sema.ancestors_with_macros(position.clone()).find_map(|n| { 27 let container = ctx.sema.ancestors_with_macros(position.clone()).find_map(|n| {
26 if let Some(module) = ast::Module::cast(n.clone()) { 28 if let Some(module) = ast::Module::cast(n.clone()) {
27 return module.item_list().map(|it| it.syntax().clone()); 29 return module.item_list().map(|it| it.syntax().clone());
28 } 30 }
@@ -31,7 +33,7 @@ pub(crate) fn insert_use_statement(
31 33
32 if let Some(container) = container { 34 if let Some(container) = container {
33 let action = best_action_for_target(container, position.clone(), &target); 35 let action = best_action_for_target(container, position.clone(), &target);
34 make_assist(&action, &target, edit.text_edit_builder()); 36 make_assist(&action, &target, builder.text_edit_builder());
35 } 37 }
36} 38}
37 39
diff --git a/crates/ra_cfg/src/lib.rs b/crates/ra_cfg/src/lib.rs
index 51d953f6e..57feabcb2 100644
--- a/crates/ra_cfg/src/lib.rs
+++ b/crates/ra_cfg/src/lib.rs
@@ -2,8 +2,6 @@
2 2
3mod cfg_expr; 3mod cfg_expr;
4 4
5use std::iter::IntoIterator;
6
7use ra_syntax::SmolStr; 5use ra_syntax::SmolStr;
8use rustc_hash::FxHashSet; 6use rustc_hash::FxHashSet;
9 7
@@ -48,9 +46,4 @@ impl CfgOptions {
48 pub fn insert_key_value(&mut self, key: SmolStr, value: SmolStr) { 46 pub fn insert_key_value(&mut self, key: SmolStr, value: SmolStr) {
49 self.key_values.insert((key, value)); 47 self.key_values.insert((key, value));
50 } 48 }
51
52 /// Shortcut to set features
53 pub fn insert_features(&mut self, iter: impl IntoIterator<Item = SmolStr>) {
54 iter.into_iter().for_each(|feat| self.insert_key_value("feature".into(), feat));
55 }
56} 49}
diff --git a/crates/ra_flycheck/Cargo.toml b/crates/ra_flycheck/Cargo.toml
index 3d5093264..03e557148 100644
--- a/crates/ra_flycheck/Cargo.toml
+++ b/crates/ra_flycheck/Cargo.toml
@@ -14,6 +14,7 @@ log = "0.4.8"
14cargo_metadata = "0.9.1" 14cargo_metadata = "0.9.1"
15serde_json = "1.0.48" 15serde_json = "1.0.48"
16jod-thread = "0.1.1" 16jod-thread = "0.1.1"
17ra_toolchain = { path = "../ra_toolchain" }
17 18
18[dev-dependencies] 19[dev-dependencies]
19insta = "0.16.0" 20insta = "0.16.0"
diff --git a/crates/ra_flycheck/src/lib.rs b/crates/ra_flycheck/src/lib.rs
index f27252949..68dcee285 100644
--- a/crates/ra_flycheck/src/lib.rs
+++ b/crates/ra_flycheck/src/lib.rs
@@ -4,7 +4,6 @@
4mod conv; 4mod conv;
5 5
6use std::{ 6use std::{
7 env,
8 io::{self, BufRead, BufReader}, 7 io::{self, BufRead, BufReader},
9 path::PathBuf, 8 path::PathBuf,
10 process::{Command, Stdio}, 9 process::{Command, Stdio},
@@ -216,10 +215,10 @@ impl FlycheckThread {
216 215
217 let mut cmd = match &self.config { 216 let mut cmd = match &self.config {
218 FlycheckConfig::CargoCommand { command, all_targets, all_features, extra_args } => { 217 FlycheckConfig::CargoCommand { command, all_targets, all_features, extra_args } => {
219 let mut cmd = Command::new(cargo_binary()); 218 let mut cmd = Command::new(ra_toolchain::cargo());
220 cmd.arg(command); 219 cmd.arg(command);
221 cmd.args(&["--workspace", "--message-format=json", "--manifest-path"]); 220 cmd.args(&["--workspace", "--message-format=json", "--manifest-path"])
222 cmd.arg(self.workspace_root.join("Cargo.toml")); 221 .arg(self.workspace_root.join("Cargo.toml"));
223 if *all_targets { 222 if *all_targets {
224 cmd.arg("--all-targets"); 223 cmd.arg("--all-targets");
225 } 224 }
@@ -337,7 +336,3 @@ fn run_cargo(
337 336
338 Ok(()) 337 Ok(())
339} 338}
340
341fn cargo_binary() -> String {
342 env::var("CARGO").unwrap_or_else(|_| "cargo".to_string())
343}
diff --git a/crates/ra_hir/src/code_model.rs b/crates/ra_hir/src/code_model.rs
index 7e840add5..6d8444462 100644
--- a/crates/ra_hir/src/code_model.rs
+++ b/crates/ra_hir/src/code_model.rs
@@ -22,8 +22,11 @@ use hir_expand::{
22 MacroDefId, MacroDefKind, 22 MacroDefId, MacroDefKind,
23}; 23};
24use hir_ty::{ 24use hir_ty::{
25 autoderef, display::HirFormatter, expr::ExprValidator, method_resolution, ApplicationTy, 25 autoderef,
26 Canonical, InEnvironment, Substs, TraitEnvironment, Ty, TyDefId, TypeCtor, 26 display::{HirDisplayError, HirFormatter},
27 expr::ExprValidator,
28 method_resolution, ApplicationTy, Canonical, InEnvironment, Substs, TraitEnvironment, Ty,
29 TyDefId, TypeCtor,
27}; 30};
28use ra_db::{CrateId, CrateName, Edition, FileId}; 31use ra_db::{CrateId, CrateName, Edition, FileId};
29use ra_prof::profile; 32use ra_prof::profile;
@@ -1341,7 +1344,7 @@ impl Type {
1341} 1344}
1342 1345
1343impl HirDisplay for Type { 1346impl HirDisplay for Type {
1344 fn hir_fmt(&self, f: &mut HirFormatter) -> std::fmt::Result { 1347 fn hir_fmt(&self, f: &mut HirFormatter) -> Result<(), HirDisplayError> {
1345 self.ty.value.hir_fmt(f) 1348 self.ty.value.hir_fmt(f)
1346 } 1349 }
1347} 1350}
diff --git a/crates/ra_hir_ty/src/diagnostics.rs b/crates/ra_hir_ty/src/diagnostics.rs
index c8fd54861..41ac70272 100644
--- a/crates/ra_hir_ty/src/diagnostics.rs
+++ b/crates/ra_hir_ty/src/diagnostics.rs
@@ -131,3 +131,31 @@ impl AstDiagnostic for MissingOkInTailExpr {
131 ast::Expr::cast(node).unwrap() 131 ast::Expr::cast(node).unwrap()
132 } 132 }
133} 133}
134
135#[derive(Debug)]
136pub struct BreakOutsideOfLoop {
137 pub file: HirFileId,
138 pub expr: AstPtr<ast::Expr>,
139}
140
141impl Diagnostic for BreakOutsideOfLoop {
142 fn message(&self) -> String {
143 "break outside of loop".to_string()
144 }
145 fn source(&self) -> InFile<SyntaxNodePtr> {
146 InFile { file_id: self.file, value: self.expr.clone().into() }
147 }
148 fn as_any(&self) -> &(dyn Any + Send + 'static) {
149 self
150 }
151}
152
153impl AstDiagnostic for BreakOutsideOfLoop {
154 type AST = ast::Expr;
155
156 fn ast(&self, db: &impl AstDatabase) -> Self::AST {
157 let root = db.parse_or_expand(self.file).unwrap();
158 let node = self.source().value.to_node(&root);
159 ast::Expr::cast(node).unwrap()
160 }
161}
diff --git a/crates/ra_hir_ty/src/display.rs b/crates/ra_hir_ty/src/display.rs
index d03bbd5a7..f5edaea8c 100644
--- a/crates/ra_hir_ty/src/display.rs
+++ b/crates/ra_hir_ty/src/display.rs
@@ -6,28 +6,42 @@ use crate::{
6 db::HirDatabase, utils::generics, ApplicationTy, CallableDef, FnSig, GenericPredicate, 6 db::HirDatabase, utils::generics, ApplicationTy, CallableDef, FnSig, GenericPredicate,
7 Obligation, ProjectionTy, Substs, TraitRef, Ty, TypeCtor, 7 Obligation, ProjectionTy, Substs, TraitRef, Ty, TypeCtor,
8}; 8};
9use hir_def::{generics::TypeParamProvenance, AdtId, AssocContainerId, Lookup}; 9use hir_def::{
10 find_path, generics::TypeParamProvenance, item_scope::ItemInNs, AdtId, AssocContainerId,
11 Lookup, ModuleId,
12};
10use hir_expand::name::Name; 13use hir_expand::name::Name;
11 14
12pub struct HirFormatter<'a, 'b> { 15pub struct HirFormatter<'a> {
13 pub db: &'a dyn HirDatabase, 16 pub db: &'a dyn HirDatabase,
14 fmt: &'a mut fmt::Formatter<'b>, 17 fmt: &'a mut dyn fmt::Write,
15 buf: String, 18 buf: String,
16 curr_size: usize, 19 curr_size: usize,
17 pub(crate) max_size: Option<usize>, 20 pub(crate) max_size: Option<usize>,
18 omit_verbose_types: bool, 21 omit_verbose_types: bool,
22 display_target: DisplayTarget,
19} 23}
20 24
21pub trait HirDisplay { 25pub trait HirDisplay {
22 fn hir_fmt(&self, f: &mut HirFormatter) -> fmt::Result; 26 fn hir_fmt(&self, f: &mut HirFormatter) -> Result<(), HirDisplayError>;
23 27
28 /// Returns a `Display`able type that is human-readable.
29 /// Use this for showing types to the user (e.g. diagnostics)
24 fn display<'a>(&'a self, db: &'a dyn HirDatabase) -> HirDisplayWrapper<'a, Self> 30 fn display<'a>(&'a self, db: &'a dyn HirDatabase) -> HirDisplayWrapper<'a, Self>
25 where 31 where
26 Self: Sized, 32 Self: Sized,
27 { 33 {
28 HirDisplayWrapper(db, self, None, false) 34 HirDisplayWrapper {
35 db,
36 t: self,
37 max_size: None,
38 omit_verbose_types: false,
39 display_target: DisplayTarget::Diagnostics,
40 }
29 } 41 }
30 42
43 /// Returns a `Display`able type that is human-readable and tries to be succinct.
44 /// Use this for showing types to the user where space is constrained (e.g. doc popups)
31 fn display_truncated<'a>( 45 fn display_truncated<'a>(
32 &'a self, 46 &'a self,
33 db: &'a dyn HirDatabase, 47 db: &'a dyn HirDatabase,
@@ -36,16 +50,46 @@ pub trait HirDisplay {
36 where 50 where
37 Self: Sized, 51 Self: Sized,
38 { 52 {
39 HirDisplayWrapper(db, self, max_size, true) 53 HirDisplayWrapper {
54 db,
55 t: self,
56 max_size,
57 omit_verbose_types: true,
58 display_target: DisplayTarget::Diagnostics,
59 }
60 }
61
62 /// Returns a String representation of `self` that can be inserted into the given module.
63 /// Use this when generating code (e.g. assists)
64 fn display_source_code<'a>(
65 &'a self,
66 db: &'a dyn HirDatabase,
67 module_id: ModuleId,
68 ) -> Result<String, DisplaySourceCodeError> {
69 let mut result = String::new();
70 match self.hir_fmt(&mut HirFormatter {
71 db,
72 fmt: &mut result,
73 buf: String::with_capacity(20),
74 curr_size: 0,
75 max_size: None,
76 omit_verbose_types: false,
77 display_target: DisplayTarget::SourceCode { module_id },
78 }) {
79 Ok(()) => {}
80 Err(HirDisplayError::FmtError) => panic!("Writing to String can't fail!"),
81 Err(HirDisplayError::DisplaySourceCodeError(e)) => return Err(e),
82 };
83 Ok(result)
40 } 84 }
41} 85}
42 86
43impl<'a, 'b> HirFormatter<'a, 'b> { 87impl<'a> HirFormatter<'a> {
44 pub fn write_joined<T: HirDisplay>( 88 pub fn write_joined<T: HirDisplay>(
45 &mut self, 89 &mut self,
46 iter: impl IntoIterator<Item = T>, 90 iter: impl IntoIterator<Item = T>,
47 sep: &str, 91 sep: &str,
48 ) -> fmt::Result { 92 ) -> Result<(), HirDisplayError> {
49 let mut first = true; 93 let mut first = true;
50 for e in iter { 94 for e in iter {
51 if !first { 95 if !first {
@@ -58,14 +102,14 @@ impl<'a, 'b> HirFormatter<'a, 'b> {
58 } 102 }
59 103
60 /// This allows using the `write!` macro directly with a `HirFormatter`. 104 /// This allows using the `write!` macro directly with a `HirFormatter`.
61 pub fn write_fmt(&mut self, args: fmt::Arguments) -> fmt::Result { 105 pub fn write_fmt(&mut self, args: fmt::Arguments) -> Result<(), HirDisplayError> {
62 // We write to a buffer first to track output size 106 // We write to a buffer first to track output size
63 self.buf.clear(); 107 self.buf.clear();
64 fmt::write(&mut self.buf, args)?; 108 fmt::write(&mut self.buf, args)?;
65 self.curr_size += self.buf.len(); 109 self.curr_size += self.buf.len();
66 110
67 // Then we write to the internal formatter from the buffer 111 // Then we write to the internal formatter from the buffer
68 self.fmt.write_str(&self.buf) 112 self.fmt.write_str(&self.buf).map_err(HirDisplayError::from)
69 } 113 }
70 114
71 pub fn should_truncate(&self) -> bool { 115 pub fn should_truncate(&self) -> bool {
@@ -81,34 +125,76 @@ impl<'a, 'b> HirFormatter<'a, 'b> {
81 } 125 }
82} 126}
83 127
84pub struct HirDisplayWrapper<'a, T>(&'a dyn HirDatabase, &'a T, Option<usize>, bool); 128#[derive(Clone, Copy)]
129enum DisplayTarget {
130 /// Display types for inlays, doc popups, autocompletion, etc...
131 /// Showing `{unknown}` or not qualifying paths is fine here.
132 /// There's no reason for this to fail.
133 Diagnostics,
134 /// Display types for inserting them in source files.
135 /// The generated code should compile, so paths need to be qualified.
136 SourceCode { module_id: ModuleId },
137}
138
139#[derive(Debug)]
140pub enum DisplaySourceCodeError {
141 PathNotFound,
142}
143
144pub enum HirDisplayError {
145 /// Errors that can occur when generating source code
146 DisplaySourceCodeError(DisplaySourceCodeError),
147 /// `FmtError` is required to be compatible with std::fmt::Display
148 FmtError,
149}
150impl From<fmt::Error> for HirDisplayError {
151 fn from(_: fmt::Error) -> Self {
152 Self::FmtError
153 }
154}
155
156pub struct HirDisplayWrapper<'a, T> {
157 db: &'a dyn HirDatabase,
158 t: &'a T,
159 max_size: Option<usize>,
160 omit_verbose_types: bool,
161 display_target: DisplayTarget,
162}
85 163
86impl<'a, T> fmt::Display for HirDisplayWrapper<'a, T> 164impl<'a, T> fmt::Display for HirDisplayWrapper<'a, T>
87where 165where
88 T: HirDisplay, 166 T: HirDisplay,
89{ 167{
90 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 168 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
91 self.1.hir_fmt(&mut HirFormatter { 169 match self.t.hir_fmt(&mut HirFormatter {
92 db: self.0, 170 db: self.db,
93 fmt: f, 171 fmt: f,
94 buf: String::with_capacity(20), 172 buf: String::with_capacity(20),
95 curr_size: 0, 173 curr_size: 0,
96 max_size: self.2, 174 max_size: self.max_size,
97 omit_verbose_types: self.3, 175 omit_verbose_types: self.omit_verbose_types,
98 }) 176 display_target: self.display_target,
177 }) {
178 Ok(()) => Ok(()),
179 Err(HirDisplayError::FmtError) => Err(fmt::Error),
180 Err(HirDisplayError::DisplaySourceCodeError(_)) => {
181 // This should never happen
182 panic!("HirDisplay failed when calling Display::fmt!")
183 }
184 }
99 } 185 }
100} 186}
101 187
102const TYPE_HINT_TRUNCATION: &str = "…"; 188const TYPE_HINT_TRUNCATION: &str = "…";
103 189
104impl HirDisplay for &Ty { 190impl HirDisplay for &Ty {
105 fn hir_fmt(&self, f: &mut HirFormatter) -> fmt::Result { 191 fn hir_fmt(&self, f: &mut HirFormatter) -> Result<(), HirDisplayError> {
106 HirDisplay::hir_fmt(*self, f) 192 HirDisplay::hir_fmt(*self, f)
107 } 193 }
108} 194}
109 195
110impl HirDisplay for ApplicationTy { 196impl HirDisplay for ApplicationTy {
111 fn hir_fmt(&self, f: &mut HirFormatter) -> fmt::Result { 197 fn hir_fmt(&self, f: &mut HirFormatter) -> Result<(), HirDisplayError> {
112 if f.should_truncate() { 198 if f.should_truncate() {
113 return write!(f, "{}", TYPE_HINT_TRUNCATION); 199 return write!(f, "{}", TYPE_HINT_TRUNCATION);
114 } 200 }
@@ -191,12 +277,30 @@ impl HirDisplay for ApplicationTy {
191 } 277 }
192 } 278 }
193 TypeCtor::Adt(def_id) => { 279 TypeCtor::Adt(def_id) => {
194 let name = match def_id { 280 match f.display_target {
195 AdtId::StructId(it) => f.db.struct_data(it).name.clone(), 281 DisplayTarget::Diagnostics => {
196 AdtId::UnionId(it) => f.db.union_data(it).name.clone(), 282 let name = match def_id {
197 AdtId::EnumId(it) => f.db.enum_data(it).name.clone(), 283 AdtId::StructId(it) => f.db.struct_data(it).name.clone(),
198 }; 284 AdtId::UnionId(it) => f.db.union_data(it).name.clone(),
199 write!(f, "{}", name)?; 285 AdtId::EnumId(it) => f.db.enum_data(it).name.clone(),
286 };
287 write!(f, "{}", name)?;
288 }
289 DisplayTarget::SourceCode { module_id } => {
290 if let Some(path) = find_path::find_path(
291 f.db.upcast(),
292 ItemInNs::Types(def_id.into()),
293 module_id,
294 ) {
295 write!(f, "{}", path)?;
296 } else {
297 return Err(HirDisplayError::DisplaySourceCodeError(
298 DisplaySourceCodeError::PathNotFound,
299 ));
300 }
301 }
302 }
303
200 if self.parameters.len() > 0 { 304 if self.parameters.len() > 0 {
201 let mut non_default_parameters = Vec::with_capacity(self.parameters.len()); 305 let mut non_default_parameters = Vec::with_capacity(self.parameters.len());
202 let parameters_to_write = if f.omit_verbose_types() { 306 let parameters_to_write = if f.omit_verbose_types() {
@@ -269,7 +373,7 @@ impl HirDisplay for ApplicationTy {
269} 373}
270 374
271impl HirDisplay for ProjectionTy { 375impl HirDisplay for ProjectionTy {
272 fn hir_fmt(&self, f: &mut HirFormatter) -> fmt::Result { 376 fn hir_fmt(&self, f: &mut HirFormatter) -> Result<(), HirDisplayError> {
273 if f.should_truncate() { 377 if f.should_truncate() {
274 return write!(f, "{}", TYPE_HINT_TRUNCATION); 378 return write!(f, "{}", TYPE_HINT_TRUNCATION);
275 } 379 }
@@ -287,7 +391,7 @@ impl HirDisplay for ProjectionTy {
287} 391}
288 392
289impl HirDisplay for Ty { 393impl HirDisplay for Ty {
290 fn hir_fmt(&self, f: &mut HirFormatter) -> fmt::Result { 394 fn hir_fmt(&self, f: &mut HirFormatter) -> Result<(), HirDisplayError> {
291 if f.should_truncate() { 395 if f.should_truncate() {
292 return write!(f, "{}", TYPE_HINT_TRUNCATION); 396 return write!(f, "{}", TYPE_HINT_TRUNCATION);
293 } 397 }
@@ -332,7 +436,7 @@ impl HirDisplay for Ty {
332fn write_bounds_like_dyn_trait( 436fn write_bounds_like_dyn_trait(
333 predicates: &[GenericPredicate], 437 predicates: &[GenericPredicate],
334 f: &mut HirFormatter, 438 f: &mut HirFormatter,
335) -> fmt::Result { 439) -> Result<(), HirDisplayError> {
336 // Note: This code is written to produce nice results (i.e. 440 // Note: This code is written to produce nice results (i.e.
337 // corresponding to surface Rust) for types that can occur in 441 // corresponding to surface Rust) for types that can occur in
338 // actual Rust. It will have weird results if the predicates 442 // actual Rust. It will have weird results if the predicates
@@ -394,7 +498,7 @@ fn write_bounds_like_dyn_trait(
394} 498}
395 499
396impl TraitRef { 500impl TraitRef {
397 fn hir_fmt_ext(&self, f: &mut HirFormatter, use_as: bool) -> fmt::Result { 501 fn hir_fmt_ext(&self, f: &mut HirFormatter, use_as: bool) -> Result<(), HirDisplayError> {
398 if f.should_truncate() { 502 if f.should_truncate() {
399 return write!(f, "{}", TYPE_HINT_TRUNCATION); 503 return write!(f, "{}", TYPE_HINT_TRUNCATION);
400 } 504 }
@@ -416,19 +520,19 @@ impl TraitRef {
416} 520}
417 521
418impl HirDisplay for TraitRef { 522impl HirDisplay for TraitRef {
419 fn hir_fmt(&self, f: &mut HirFormatter) -> fmt::Result { 523 fn hir_fmt(&self, f: &mut HirFormatter) -> Result<(), HirDisplayError> {
420 self.hir_fmt_ext(f, false) 524 self.hir_fmt_ext(f, false)
421 } 525 }
422} 526}
423 527
424impl HirDisplay for &GenericPredicate { 528impl HirDisplay for &GenericPredicate {
425 fn hir_fmt(&self, f: &mut HirFormatter) -> fmt::Result { 529 fn hir_fmt(&self, f: &mut HirFormatter) -> Result<(), HirDisplayError> {
426 HirDisplay::hir_fmt(*self, f) 530 HirDisplay::hir_fmt(*self, f)
427 } 531 }
428} 532}
429 533
430impl HirDisplay for GenericPredicate { 534impl HirDisplay for GenericPredicate {
431 fn hir_fmt(&self, f: &mut HirFormatter) -> fmt::Result { 535 fn hir_fmt(&self, f: &mut HirFormatter) -> Result<(), HirDisplayError> {
432 if f.should_truncate() { 536 if f.should_truncate() {
433 return write!(f, "{}", TYPE_HINT_TRUNCATION); 537 return write!(f, "{}", TYPE_HINT_TRUNCATION);
434 } 538 }
@@ -452,15 +556,15 @@ impl HirDisplay for GenericPredicate {
452} 556}
453 557
454impl HirDisplay for Obligation { 558impl HirDisplay for Obligation {
455 fn hir_fmt(&self, f: &mut HirFormatter) -> fmt::Result { 559 fn hir_fmt(&self, f: &mut HirFormatter) -> Result<(), HirDisplayError> {
456 match self { 560 Ok(match self {
457 Obligation::Trait(tr) => write!(f, "Implements({})", tr.display(f.db)), 561 Obligation::Trait(tr) => write!(f, "Implements({})", tr.display(f.db))?,
458 Obligation::Projection(proj) => write!( 562 Obligation::Projection(proj) => write!(
459 f, 563 f,
460 "Normalize({} => {})", 564 "Normalize({} => {})",
461 proj.projection_ty.display(f.db), 565 proj.projection_ty.display(f.db),
462 proj.ty.display(f.db) 566 proj.ty.display(f.db)
463 ), 567 )?,
464 } 568 })
465 } 569 }
466} 570}
diff --git a/crates/ra_hir_ty/src/infer.rs b/crates/ra_hir_ty/src/infer.rs
index bd4ef69a0..a21ad8d86 100644
--- a/crates/ra_hir_ty/src/infer.rs
+++ b/crates/ra_hir_ty/src/infer.rs
@@ -210,6 +210,13 @@ struct InferenceContext<'a> {
210 /// closures, but currently this is the only field that will change there, 210 /// closures, but currently this is the only field that will change there,
211 /// so it doesn't make sense. 211 /// so it doesn't make sense.
212 return_ty: Ty, 212 return_ty: Ty,
213 diverges: Diverges,
214 breakables: Vec<BreakableContext>,
215}
216
217#[derive(Clone, Debug)]
218struct BreakableContext {
219 pub may_break: bool,
213} 220}
214 221
215impl<'a> InferenceContext<'a> { 222impl<'a> InferenceContext<'a> {
@@ -224,6 +231,8 @@ impl<'a> InferenceContext<'a> {
224 owner, 231 owner,
225 body: db.body(owner), 232 body: db.body(owner),
226 resolver, 233 resolver,
234 diverges: Diverges::Maybe,
235 breakables: Vec::new(),
227 } 236 }
228 } 237 }
229 238
@@ -666,15 +675,57 @@ impl Expectation {
666 } 675 }
667} 676}
668 677
678#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
679enum Diverges {
680 Maybe,
681 Always,
682}
683
684impl Diverges {
685 fn is_always(self) -> bool {
686 self == Diverges::Always
687 }
688}
689
690impl std::ops::BitAnd for Diverges {
691 type Output = Self;
692 fn bitand(self, other: Self) -> Self {
693 std::cmp::min(self, other)
694 }
695}
696
697impl std::ops::BitOr for Diverges {
698 type Output = Self;
699 fn bitor(self, other: Self) -> Self {
700 std::cmp::max(self, other)
701 }
702}
703
704impl std::ops::BitAndAssign for Diverges {
705 fn bitand_assign(&mut self, other: Self) {
706 *self = *self & other;
707 }
708}
709
710impl std::ops::BitOrAssign for Diverges {
711 fn bitor_assign(&mut self, other: Self) {
712 *self = *self | other;
713 }
714}
715
669mod diagnostics { 716mod diagnostics {
670 use hir_def::{expr::ExprId, FunctionId}; 717 use hir_def::{expr::ExprId, FunctionId};
671 use hir_expand::diagnostics::DiagnosticSink; 718 use hir_expand::diagnostics::DiagnosticSink;
672 719
673 use crate::{db::HirDatabase, diagnostics::NoSuchField}; 720 use crate::{
721 db::HirDatabase,
722 diagnostics::{BreakOutsideOfLoop, NoSuchField},
723 };
674 724
675 #[derive(Debug, PartialEq, Eq, Clone)] 725 #[derive(Debug, PartialEq, Eq, Clone)]
676 pub(super) enum InferenceDiagnostic { 726 pub(super) enum InferenceDiagnostic {
677 NoSuchField { expr: ExprId, field: usize }, 727 NoSuchField { expr: ExprId, field: usize },
728 BreakOutsideOfLoop { expr: ExprId },
678 } 729 }
679 730
680 impl InferenceDiagnostic { 731 impl InferenceDiagnostic {
@@ -690,6 +741,13 @@ mod diagnostics {
690 let field = source_map.field_syntax(*expr, *field); 741 let field = source_map.field_syntax(*expr, *field);
691 sink.push(NoSuchField { file: field.file_id, field: field.value }) 742 sink.push(NoSuchField { file: field.file_id, field: field.value })
692 } 743 }
744 InferenceDiagnostic::BreakOutsideOfLoop { expr } => {
745 let (_, source_map) = db.body_with_source_map(owner.into());
746 let ptr = source_map
747 .expr_syntax(*expr)
748 .expect("break outside of loop in synthetic syntax");
749 sink.push(BreakOutsideOfLoop { file: ptr.file_id, expr: ptr.value })
750 }
693 } 751 }
694 } 752 }
695 } 753 }
diff --git a/crates/ra_hir_ty/src/infer/coerce.rs b/crates/ra_hir_ty/src/infer/coerce.rs
index 89200255a..173ec59ed 100644
--- a/crates/ra_hir_ty/src/infer/coerce.rs
+++ b/crates/ra_hir_ty/src/infer/coerce.rs
@@ -20,21 +20,35 @@ impl<'a> InferenceContext<'a> {
20 self.coerce_inner(from_ty, &to_ty) 20 self.coerce_inner(from_ty, &to_ty)
21 } 21 }
22 22
23 /// Merge two types from different branches, with possible implicit coerce. 23 /// Merge two types from different branches, with possible coercion.
24 /// 24 ///
25 /// Note that it is only possible that one type are coerced to another. 25 /// Mostly this means trying to coerce one to the other, but
26 /// Coercing both types to another least upper bound type is not possible in rustc, 26 /// - if we have two function types for different functions, we need to
27 /// which will simply result in "incompatible types" error. 27 /// coerce both to function pointers;
28 /// - if we were concerned with lifetime subtyping, we'd need to look for a
29 /// least upper bound.
28 pub(super) fn coerce_merge_branch(&mut self, ty1: &Ty, ty2: &Ty) -> Ty { 30 pub(super) fn coerce_merge_branch(&mut self, ty1: &Ty, ty2: &Ty) -> Ty {
29 if self.coerce(ty1, ty2) { 31 if self.coerce(ty1, ty2) {
30 ty2.clone() 32 ty2.clone()
31 } else if self.coerce(ty2, ty1) { 33 } else if self.coerce(ty2, ty1) {
32 ty1.clone() 34 ty1.clone()
33 } else { 35 } else {
34 tested_by!(coerce_merge_fail_fallback); 36 if let (ty_app!(TypeCtor::FnDef(_)), ty_app!(TypeCtor::FnDef(_))) = (ty1, ty2) {
35 // For incompatible types, we use the latter one as result 37 tested_by!(coerce_fn_reification);
36 // to be better recovery for `if` without `else`. 38 // Special case: two function types. Try to coerce both to
37 ty2.clone() 39 // pointers to have a chance at getting a match. See
40 // https://github.com/rust-lang/rust/blob/7b805396bf46dce972692a6846ce2ad8481c5f85/src/librustc_typeck/check/coercion.rs#L877-L916
41 let sig1 = ty1.callable_sig(self.db).expect("FnDef without callable sig");
42 let sig2 = ty2.callable_sig(self.db).expect("FnDef without callable sig");
43 let ptr_ty1 = Ty::fn_ptr(sig1);
44 let ptr_ty2 = Ty::fn_ptr(sig2);
45 self.coerce_merge_branch(&ptr_ty1, &ptr_ty2)
46 } else {
47 tested_by!(coerce_merge_fail_fallback);
48 // For incompatible types, we use the latter one as result
49 // to be better recovery for `if` without `else`.
50 ty2.clone()
51 }
38 } 52 }
39 } 53 }
40 54
@@ -84,9 +98,7 @@ impl<'a> InferenceContext<'a> {
84 match from_ty.callable_sig(self.db) { 98 match from_ty.callable_sig(self.db) {
85 None => return false, 99 None => return false,
86 Some(sig) => { 100 Some(sig) => {
87 let num_args = sig.params_and_return.len() as u16 - 1; 101 from_ty = Ty::fn_ptr(sig);
88 from_ty =
89 Ty::apply(TypeCtor::FnPtr { num_args }, Substs(sig.params_and_return));
90 } 102 }
91 } 103 }
92 } 104 }
diff --git a/crates/ra_hir_ty/src/infer/expr.rs b/crates/ra_hir_ty/src/infer/expr.rs
index 83f946eee..0b67d216a 100644
--- a/crates/ra_hir_ty/src/infer/expr.rs
+++ b/crates/ra_hir_ty/src/infer/expr.rs
@@ -1,7 +1,7 @@
1//! Type inference for expressions. 1//! Type inference for expressions.
2 2
3use std::iter::{repeat, repeat_with}; 3use std::iter::{repeat, repeat_with};
4use std::sync::Arc; 4use std::{mem, sync::Arc};
5 5
6use hir_def::{ 6use hir_def::{
7 builtin_type::Signedness, 7 builtin_type::Signedness,
@@ -21,11 +21,18 @@ use crate::{
21 Ty, TypeCtor, Uncertain, 21 Ty, TypeCtor, Uncertain,
22}; 22};
23 23
24use super::{BindingMode, Expectation, InferenceContext, InferenceDiagnostic, TypeMismatch}; 24use super::{
25 BindingMode, BreakableContext, Diverges, Expectation, InferenceContext, InferenceDiagnostic,
26 TypeMismatch,
27};
25 28
26impl<'a> InferenceContext<'a> { 29impl<'a> InferenceContext<'a> {
27 pub(super) fn infer_expr(&mut self, tgt_expr: ExprId, expected: &Expectation) -> Ty { 30 pub(super) fn infer_expr(&mut self, tgt_expr: ExprId, expected: &Expectation) -> Ty {
28 let ty = self.infer_expr_inner(tgt_expr, expected); 31 let ty = self.infer_expr_inner(tgt_expr, expected);
32 if ty.is_never() {
33 // Any expression that produces a value of type `!` must have diverged
34 self.diverges = Diverges::Always;
35 }
29 let could_unify = self.unify(&ty, &expected.ty); 36 let could_unify = self.unify(&ty, &expected.ty);
30 if !could_unify { 37 if !could_unify {
31 self.result.type_mismatches.insert( 38 self.result.type_mismatches.insert(
@@ -64,11 +71,18 @@ impl<'a> InferenceContext<'a> {
64 // if let is desugared to match, so this is always simple if 71 // if let is desugared to match, so this is always simple if
65 self.infer_expr(*condition, &Expectation::has_type(Ty::simple(TypeCtor::Bool))); 72 self.infer_expr(*condition, &Expectation::has_type(Ty::simple(TypeCtor::Bool)));
66 73
74 let condition_diverges = mem::replace(&mut self.diverges, Diverges::Maybe);
75 let mut both_arms_diverge = Diverges::Always;
76
67 let then_ty = self.infer_expr_inner(*then_branch, &expected); 77 let then_ty = self.infer_expr_inner(*then_branch, &expected);
78 both_arms_diverge &= mem::replace(&mut self.diverges, Diverges::Maybe);
68 let else_ty = match else_branch { 79 let else_ty = match else_branch {
69 Some(else_branch) => self.infer_expr_inner(*else_branch, &expected), 80 Some(else_branch) => self.infer_expr_inner(*else_branch, &expected),
70 None => Ty::unit(), 81 None => Ty::unit(),
71 }; 82 };
83 both_arms_diverge &= self.diverges;
84
85 self.diverges = condition_diverges | both_arms_diverge;
72 86
73 self.coerce_merge_branch(&then_ty, &else_ty) 87 self.coerce_merge_branch(&then_ty, &else_ty)
74 } 88 }
@@ -79,24 +93,43 @@ impl<'a> InferenceContext<'a> {
79 Ty::Unknown 93 Ty::Unknown
80 } 94 }
81 Expr::Loop { body } => { 95 Expr::Loop { body } => {
96 self.breakables.push(BreakableContext { may_break: false });
82 self.infer_expr(*body, &Expectation::has_type(Ty::unit())); 97 self.infer_expr(*body, &Expectation::has_type(Ty::unit()));
98
99 let ctxt = self.breakables.pop().expect("breakable stack broken");
100 if ctxt.may_break {
101 self.diverges = Diverges::Maybe;
102 }
83 // FIXME handle break with value 103 // FIXME handle break with value
84 Ty::simple(TypeCtor::Never) 104 if ctxt.may_break {
105 Ty::unit()
106 } else {
107 Ty::simple(TypeCtor::Never)
108 }
85 } 109 }
86 Expr::While { condition, body } => { 110 Expr::While { condition, body } => {
111 self.breakables.push(BreakableContext { may_break: false });
87 // while let is desugared to a match loop, so this is always simple while 112 // while let is desugared to a match loop, so this is always simple while
88 self.infer_expr(*condition, &Expectation::has_type(Ty::simple(TypeCtor::Bool))); 113 self.infer_expr(*condition, &Expectation::has_type(Ty::simple(TypeCtor::Bool)));
89 self.infer_expr(*body, &Expectation::has_type(Ty::unit())); 114 self.infer_expr(*body, &Expectation::has_type(Ty::unit()));
115 let _ctxt = self.breakables.pop().expect("breakable stack broken");
116 // the body may not run, so it diverging doesn't mean we diverge
117 self.diverges = Diverges::Maybe;
90 Ty::unit() 118 Ty::unit()
91 } 119 }
92 Expr::For { iterable, body, pat } => { 120 Expr::For { iterable, body, pat } => {
93 let iterable_ty = self.infer_expr(*iterable, &Expectation::none()); 121 let iterable_ty = self.infer_expr(*iterable, &Expectation::none());
94 122
123 self.breakables.push(BreakableContext { may_break: false });
95 let pat_ty = 124 let pat_ty =
96 self.resolve_associated_type(iterable_ty, self.resolve_into_iter_item()); 125 self.resolve_associated_type(iterable_ty, self.resolve_into_iter_item());
97 126
98 self.infer_pat(*pat, &pat_ty, BindingMode::default()); 127 self.infer_pat(*pat, &pat_ty, BindingMode::default());
128
99 self.infer_expr(*body, &Expectation::has_type(Ty::unit())); 129 self.infer_expr(*body, &Expectation::has_type(Ty::unit()));
130 let _ctxt = self.breakables.pop().expect("breakable stack broken");
131 // the body may not run, so it diverging doesn't mean we diverge
132 self.diverges = Diverges::Maybe;
100 Ty::unit() 133 Ty::unit()
101 } 134 }
102 Expr::Lambda { body, args, ret_type, arg_types } => { 135 Expr::Lambda { body, args, ret_type, arg_types } => {
@@ -132,10 +165,12 @@ impl<'a> InferenceContext<'a> {
132 // infer the body. 165 // infer the body.
133 self.coerce(&closure_ty, &expected.ty); 166 self.coerce(&closure_ty, &expected.ty);
134 167
135 let prev_ret_ty = std::mem::replace(&mut self.return_ty, ret_ty.clone()); 168 let prev_diverges = mem::replace(&mut self.diverges, Diverges::Maybe);
169 let prev_ret_ty = mem::replace(&mut self.return_ty, ret_ty.clone());
136 170
137 self.infer_expr_coerce(*body, &Expectation::has_type(ret_ty)); 171 self.infer_expr_coerce(*body, &Expectation::has_type(ret_ty));
138 172
173 self.diverges = prev_diverges;
139 self.return_ty = prev_ret_ty; 174 self.return_ty = prev_ret_ty;
140 175
141 closure_ty 176 closure_ty
@@ -165,7 +200,11 @@ impl<'a> InferenceContext<'a> {
165 self.table.new_type_var() 200 self.table.new_type_var()
166 }; 201 };
167 202
203 let matchee_diverges = self.diverges;
204 let mut all_arms_diverge = Diverges::Always;
205
168 for arm in arms { 206 for arm in arms {
207 self.diverges = Diverges::Maybe;
169 let _pat_ty = self.infer_pat(arm.pat, &input_ty, BindingMode::default()); 208 let _pat_ty = self.infer_pat(arm.pat, &input_ty, BindingMode::default());
170 if let Some(guard_expr) = arm.guard { 209 if let Some(guard_expr) = arm.guard {
171 self.infer_expr( 210 self.infer_expr(
@@ -175,9 +214,12 @@ impl<'a> InferenceContext<'a> {
175 } 214 }
176 215
177 let arm_ty = self.infer_expr_inner(arm.expr, &expected); 216 let arm_ty = self.infer_expr_inner(arm.expr, &expected);
217 all_arms_diverge &= self.diverges;
178 result_ty = self.coerce_merge_branch(&result_ty, &arm_ty); 218 result_ty = self.coerce_merge_branch(&result_ty, &arm_ty);
179 } 219 }
180 220
221 self.diverges = matchee_diverges | all_arms_diverge;
222
181 result_ty 223 result_ty
182 } 224 }
183 Expr::Path(p) => { 225 Expr::Path(p) => {
@@ -191,6 +233,13 @@ impl<'a> InferenceContext<'a> {
191 // FIXME handle break with value 233 // FIXME handle break with value
192 self.infer_expr(*expr, &Expectation::none()); 234 self.infer_expr(*expr, &Expectation::none());
193 } 235 }
236 if let Some(ctxt) = self.breakables.last_mut() {
237 ctxt.may_break = true;
238 } else {
239 self.push_diagnostic(InferenceDiagnostic::BreakOutsideOfLoop {
240 expr: tgt_expr,
241 });
242 }
194 Ty::simple(TypeCtor::Never) 243 Ty::simple(TypeCtor::Never)
195 } 244 }
196 Expr::Return { expr } => { 245 Expr::Return { expr } => {
@@ -501,8 +550,8 @@ impl<'a> InferenceContext<'a> {
501 } 550 }
502 Literal::ByteString(..) => { 551 Literal::ByteString(..) => {
503 let byte_type = Ty::simple(TypeCtor::Int(Uncertain::Known(IntTy::u8()))); 552 let byte_type = Ty::simple(TypeCtor::Int(Uncertain::Known(IntTy::u8())));
504 let slice_type = Ty::apply_one(TypeCtor::Slice, byte_type); 553 let array_type = Ty::apply_one(TypeCtor::Array, byte_type);
505 Ty::apply_one(TypeCtor::Ref(Mutability::Shared), slice_type) 554 Ty::apply_one(TypeCtor::Ref(Mutability::Shared), array_type)
506 } 555 }
507 Literal::Char(..) => Ty::simple(TypeCtor::Char), 556 Literal::Char(..) => Ty::simple(TypeCtor::Char),
508 Literal::Int(_v, ty) => Ty::simple(TypeCtor::Int((*ty).into())), 557 Literal::Int(_v, ty) => Ty::simple(TypeCtor::Int((*ty).into())),
@@ -522,7 +571,6 @@ impl<'a> InferenceContext<'a> {
522 tail: Option<ExprId>, 571 tail: Option<ExprId>,
523 expected: &Expectation, 572 expected: &Expectation,
524 ) -> Ty { 573 ) -> Ty {
525 let mut diverges = false;
526 for stmt in statements { 574 for stmt in statements {
527 match stmt { 575 match stmt {
528 Statement::Let { pat, type_ref, initializer } => { 576 Statement::Let { pat, type_ref, initializer } => {
@@ -544,9 +592,7 @@ impl<'a> InferenceContext<'a> {
544 self.infer_pat(*pat, &ty, BindingMode::default()); 592 self.infer_pat(*pat, &ty, BindingMode::default());
545 } 593 }
546 Statement::Expr(expr) => { 594 Statement::Expr(expr) => {
547 if let ty_app!(TypeCtor::Never) = self.infer_expr(*expr, &Expectation::none()) { 595 self.infer_expr(*expr, &Expectation::none());
548 diverges = true;
549 }
550 } 596 }
551 } 597 }
552 } 598 }
@@ -554,14 +600,22 @@ impl<'a> InferenceContext<'a> {
554 let ty = if let Some(expr) = tail { 600 let ty = if let Some(expr) = tail {
555 self.infer_expr_coerce(expr, expected) 601 self.infer_expr_coerce(expr, expected)
556 } else { 602 } else {
557 self.coerce(&Ty::unit(), expected.coercion_target()); 603 // Citing rustc: if there is no explicit tail expression,
558 Ty::unit() 604 // that is typically equivalent to a tail expression
605 // of `()` -- except if the block diverges. In that
606 // case, there is no value supplied from the tail
607 // expression (assuming there are no other breaks,
608 // this implies that the type of the block will be
609 // `!`).
610 if self.diverges.is_always() {
611 // we don't even make an attempt at coercion
612 self.table.new_maybe_never_type_var()
613 } else {
614 self.coerce(&Ty::unit(), expected.coercion_target());
615 Ty::unit()
616 }
559 }; 617 };
560 if diverges { 618 ty
561 Ty::simple(TypeCtor::Never)
562 } else {
563 ty
564 }
565 } 619 }
566 620
567 fn infer_method_call( 621 fn infer_method_call(
diff --git a/crates/ra_hir_ty/src/lib.rs b/crates/ra_hir_ty/src/lib.rs
index a6f56c661..e8f3482fe 100644
--- a/crates/ra_hir_ty/src/lib.rs
+++ b/crates/ra_hir_ty/src/lib.rs
@@ -683,6 +683,12 @@ impl Ty {
683 pub fn unit() -> Self { 683 pub fn unit() -> Self {
684 Ty::apply(TypeCtor::Tuple { cardinality: 0 }, Substs::empty()) 684 Ty::apply(TypeCtor::Tuple { cardinality: 0 }, Substs::empty())
685 } 685 }
686 pub fn fn_ptr(sig: FnSig) -> Self {
687 Ty::apply(
688 TypeCtor::FnPtr { num_args: sig.params().len() as u16 },
689 Substs(sig.params_and_return),
690 )
691 }
686 692
687 pub fn as_reference(&self) -> Option<(&Ty, Mutability)> { 693 pub fn as_reference(&self) -> Option<(&Ty, Mutability)> {
688 match self { 694 match self {
@@ -730,6 +736,10 @@ impl Ty {
730 } 736 }
731 } 737 }
732 738
739 pub fn is_never(&self) -> bool {
740 matches!(self, Ty::Apply(ApplicationTy { ctor: TypeCtor::Never, .. }))
741 }
742
733 /// If this is a `dyn Trait` type, this returns the `Trait` part. 743 /// If this is a `dyn Trait` type, this returns the `Trait` part.
734 pub fn dyn_trait_ref(&self) -> Option<&TraitRef> { 744 pub fn dyn_trait_ref(&self) -> Option<&TraitRef> {
735 match self { 745 match self {
diff --git a/crates/ra_hir_ty/src/marks.rs b/crates/ra_hir_ty/src/marks.rs
index de5cb1d6b..a39740143 100644
--- a/crates/ra_hir_ty/src/marks.rs
+++ b/crates/ra_hir_ty/src/marks.rs
@@ -7,5 +7,6 @@ test_utils::marks!(
7 impl_self_type_match_without_receiver 7 impl_self_type_match_without_receiver
8 match_ergonomics_ref 8 match_ergonomics_ref
9 coerce_merge_fail_fallback 9 coerce_merge_fail_fallback
10 coerce_fn_reification
10 trait_self_implements_self 11 trait_self_implements_self
11); 12);
diff --git a/crates/ra_hir_ty/src/tests.rs b/crates/ra_hir_ty/src/tests.rs
index d60732e19..1fe05c70c 100644
--- a/crates/ra_hir_ty/src/tests.rs
+++ b/crates/ra_hir_ty/src/tests.rs
@@ -6,6 +6,7 @@ mod patterns;
6mod traits; 6mod traits;
7mod method_resolution; 7mod method_resolution;
8mod macros; 8mod macros;
9mod display_source_code;
9 10
10use std::sync::Arc; 11use std::sync::Arc;
11 12
@@ -16,7 +17,7 @@ use hir_def::{
16 item_scope::ItemScope, 17 item_scope::ItemScope,
17 keys, 18 keys,
18 nameres::CrateDefMap, 19 nameres::CrateDefMap,
19 AssocItemId, DefWithBodyId, LocalModuleId, Lookup, ModuleDefId, 20 AssocItemId, DefWithBodyId, LocalModuleId, Lookup, ModuleDefId, ModuleId,
20}; 21};
21use hir_expand::{db::AstDatabase, InFile}; 22use hir_expand::{db::AstDatabase, InFile};
22use insta::assert_snapshot; 23use insta::assert_snapshot;
@@ -37,6 +38,18 @@ use crate::{
37// update the snapshots. 38// update the snapshots.
38 39
39fn type_at_pos(db: &TestDB, pos: FilePosition) -> String { 40fn type_at_pos(db: &TestDB, pos: FilePosition) -> String {
41 type_at_pos_displayed(db, pos, |ty, _| ty.display(db).to_string())
42}
43
44fn displayed_source_at_pos(db: &TestDB, pos: FilePosition) -> String {
45 type_at_pos_displayed(db, pos, |ty, module_id| ty.display_source_code(db, module_id).unwrap())
46}
47
48fn type_at_pos_displayed(
49 db: &TestDB,
50 pos: FilePosition,
51 display_fn: impl FnOnce(&Ty, ModuleId) -> String,
52) -> String {
40 let file = db.parse(pos.file_id).ok().unwrap(); 53 let file = db.parse(pos.file_id).ok().unwrap();
41 let expr = algo::find_node_at_offset::<ast::Expr>(file.syntax(), pos.offset).unwrap(); 54 let expr = algo::find_node_at_offset::<ast::Expr>(file.syntax(), pos.offset).unwrap();
42 let fn_def = expr.syntax().ancestors().find_map(ast::FnDef::cast).unwrap(); 55 let fn_def = expr.syntax().ancestors().find_map(ast::FnDef::cast).unwrap();
@@ -49,7 +62,7 @@ fn type_at_pos(db: &TestDB, pos: FilePosition) -> String {
49 if let Some(expr_id) = source_map.node_expr(InFile::new(pos.file_id.into(), &expr)) { 62 if let Some(expr_id) = source_map.node_expr(InFile::new(pos.file_id.into(), &expr)) {
50 let infer = db.infer(func.into()); 63 let infer = db.infer(func.into());
51 let ty = &infer[expr_id]; 64 let ty = &infer[expr_id];
52 return ty.display(db).to_string(); 65 return display_fn(ty, module);
53 } 66 }
54 panic!("Can't find expression") 67 panic!("Can't find expression")
55} 68}
@@ -518,3 +531,21 @@ fn missing_record_pat_field_no_diagnostic_if_not_exhaustive() {
518 531
519 assert_snapshot!(diagnostics, @""); 532 assert_snapshot!(diagnostics, @"");
520} 533}
534
535#[test]
536fn break_outside_of_loop() {
537 let diagnostics = TestDB::with_files(
538 r"
539 //- /lib.rs
540 fn foo() {
541 break;
542 }
543 ",
544 )
545 .diagnostics()
546 .0;
547
548 assert_snapshot!(diagnostics, @r###""break": break outside of loop
549 "###
550 );
551}
diff --git a/crates/ra_hir_ty/src/tests/coercion.rs b/crates/ra_hir_ty/src/tests/coercion.rs
index e6fb3e123..6dc4b2cd1 100644
--- a/crates/ra_hir_ty/src/tests/coercion.rs
+++ b/crates/ra_hir_ty/src/tests/coercion.rs
@@ -384,7 +384,7 @@ fn foo() -> u32 {
384} 384}
385"#, true), 385"#, true),
386 @r###" 386 @r###"
387 17..40 '{ ...own; }': ! 387 17..40 '{ ...own; }': u32
388 23..37 'return unknown': ! 388 23..37 'return unknown': !
389 30..37 'unknown': u32 389 30..37 'unknown': u32
390 "### 390 "###
@@ -514,7 +514,7 @@ fn foo() {
514 27..103 '{ ... }': &u32 514 27..103 '{ ... }': &u32
515 37..82 'if tru... }': () 515 37..82 'if tru... }': ()
516 40..44 'true': bool 516 40..44 'true': bool
517 45..82 '{ ... }': ! 517 45..82 '{ ... }': ()
518 59..71 'return &1u32': ! 518 59..71 'return &1u32': !
519 66..71 '&1u32': &u32 519 66..71 '&1u32': &u32
520 67..71 '1u32': u32 520 67..71 '1u32': u32
@@ -546,6 +546,48 @@ fn test() {
546} 546}
547 547
548#[test] 548#[test]
549fn coerce_fn_items_in_match_arms() {
550 covers!(coerce_fn_reification);
551 assert_snapshot!(
552 infer_with_mismatches(r#"
553fn foo1(x: u32) -> isize { 1 }
554fn foo2(x: u32) -> isize { 2 }
555fn foo3(x: u32) -> isize { 3 }
556fn test() {
557 let x = match 1 {
558 1 => foo1,
559 2 => foo2,
560 _ => foo3,
561 };
562}
563"#, true),
564 @r###"
565 9..10 'x': u32
566 26..31 '{ 1 }': isize
567 28..29 '1': isize
568 40..41 'x': u32
569 57..62 '{ 2 }': isize
570 59..60 '2': isize
571 71..72 'x': u32
572 88..93 '{ 3 }': isize
573 90..91 '3': isize
574 104..193 '{ ... }; }': ()
575 114..115 'x': fn(u32) -> isize
576 118..190 'match ... }': fn(u32) -> isize
577 124..125 '1': i32
578 136..137 '1': i32
579 136..137 '1': i32
580 141..145 'foo1': fn foo1(u32) -> isize
581 155..156 '2': i32
582 155..156 '2': i32
583 160..164 'foo2': fn foo2(u32) -> isize
584 174..175 '_': i32
585 179..183 'foo3': fn foo3(u32) -> isize
586 "###
587 );
588}
589
590#[test]
549fn coerce_closure_to_fn_ptr() { 591fn coerce_closure_to_fn_ptr() {
550 assert_snapshot!( 592 assert_snapshot!(
551 infer_with_mismatches(r#" 593 infer_with_mismatches(r#"
diff --git a/crates/ra_hir_ty/src/tests/display_source_code.rs b/crates/ra_hir_ty/src/tests/display_source_code.rs
new file mode 100644
index 000000000..ca1748615
--- /dev/null
+++ b/crates/ra_hir_ty/src/tests/display_source_code.rs
@@ -0,0 +1,23 @@
1use super::displayed_source_at_pos;
2use crate::test_db::TestDB;
3use ra_db::fixture::WithFixture;
4
5#[test]
6fn qualify_path_to_submodule() {
7 let (db, pos) = TestDB::with_position(
8 r#"
9//- /main.rs
10
11mod foo {
12 pub struct Foo;
13}
14
15fn bar() {
16 let foo: foo::Foo = foo::Foo;
17 foo<|>
18}
19
20"#,
21 );
22 assert_eq!("foo::Foo", displayed_source_at_pos(&db, pos));
23}
diff --git a/crates/ra_hir_ty/src/tests/macros.rs b/crates/ra_hir_ty/src/tests/macros.rs
index 07398ddcc..4c6099aa2 100644
--- a/crates/ra_hir_ty/src/tests/macros.rs
+++ b/crates/ra_hir_ty/src/tests/macros.rs
@@ -197,7 +197,7 @@ fn spam() {
197 !0..6 '1isize': isize 197 !0..6 '1isize': isize
198 !0..6 '1isize': isize 198 !0..6 '1isize': isize
199 !0..6 '1isize': isize 199 !0..6 '1isize': isize
200 54..457 '{ ...!(); }': ! 200 54..457 '{ ...!(); }': ()
201 88..109 'spam!(...am!())': {unknown} 201 88..109 'spam!(...am!())': {unknown}
202 115..134 'for _ ...!() {}': () 202 115..134 'for _ ...!() {}': ()
203 119..120 '_': {unknown} 203 119..120 '_': {unknown}
diff --git a/crates/ra_hir_ty/src/tests/method_resolution.rs b/crates/ra_hir_ty/src/tests/method_resolution.rs
index ab87f598a..67f964ab5 100644
--- a/crates/ra_hir_ty/src/tests/method_resolution.rs
+++ b/crates/ra_hir_ty/src/tests/method_resolution.rs
@@ -17,8 +17,8 @@ impl<T> [T] {
17#[lang = "slice_alloc"] 17#[lang = "slice_alloc"]
18impl<T> [T] {} 18impl<T> [T] {}
19 19
20fn test() { 20fn test(x: &[u8]) {
21 <[_]>::foo(b"foo"); 21 <[_]>::foo(x);
22} 22}
23"#), 23"#),
24 @r###" 24 @r###"
@@ -26,10 +26,11 @@ fn test() {
26 56..79 '{ ... }': T 26 56..79 '{ ... }': T
27 66..73 'loop {}': ! 27 66..73 'loop {}': !
28 71..73 '{}': () 28 71..73 '{}': ()
29 133..160 '{ ...o"); }': () 29 131..132 'x': &[u8]
30 139..149 '<[_]>::foo': fn foo<u8>(&[u8]) -> u8 30 141..163 '{ ...(x); }': ()
31 139..157 '<[_]>:..."foo")': u8 31 147..157 '<[_]>::foo': fn foo<u8>(&[u8]) -> u8
32 150..156 'b"foo"': &[u8] 32 147..160 '<[_]>::foo(x)': u8
33 158..159 'x': &[u8]
33 "### 34 "###
34 ); 35 );
35} 36}
diff --git a/crates/ra_hir_ty/src/tests/never_type.rs b/crates/ra_hir_ty/src/tests/never_type.rs
index a77209480..082c47208 100644
--- a/crates/ra_hir_ty/src/tests/never_type.rs
+++ b/crates/ra_hir_ty/src/tests/never_type.rs
@@ -1,4 +1,6 @@
1use super::type_at; 1use insta::assert_snapshot;
2
3use super::{infer_with_mismatches, type_at};
2 4
3#[test] 5#[test]
4fn infer_never1() { 6fn infer_never1() {
@@ -261,3 +263,176 @@ fn test(a: i32) {
261 ); 263 );
262 assert_eq!(t, "f64"); 264 assert_eq!(t, "f64");
263} 265}
266
267#[test]
268fn diverging_expression_1() {
269 let t = infer_with_mismatches(
270 r#"
271//- /main.rs
272fn test1() {
273 let x: u32 = return;
274}
275fn test2() {
276 let x: u32 = { return; };
277}
278fn test3() {
279 let x: u32 = loop {};
280}
281fn test4() {
282 let x: u32 = { loop {} };
283}
284fn test5() {
285 let x: u32 = { if true { loop {}; } else { loop {}; } };
286}
287fn test6() {
288 let x: u32 = { let y: u32 = { loop {}; }; };
289}
290"#,
291 true,
292 );
293 assert_snapshot!(t, @r###"
294 25..53 '{ ...urn; }': ()
295 35..36 'x': u32
296 44..50 'return': !
297 65..98 '{ ...; }; }': ()
298 75..76 'x': u32
299 84..95 '{ return; }': u32
300 86..92 'return': !
301 110..139 '{ ... {}; }': ()
302 120..121 'x': u32
303 129..136 'loop {}': !
304 134..136 '{}': ()
305 151..184 '{ ...} }; }': ()
306 161..162 'x': u32
307 170..181 '{ loop {} }': u32
308 172..179 'loop {}': !
309 177..179 '{}': ()
310 196..260 '{ ...} }; }': ()
311 206..207 'x': u32
312 215..257 '{ if t...}; } }': u32
313 217..255 'if tru... {}; }': u32
314 220..224 'true': bool
315 225..237 '{ loop {}; }': u32
316 227..234 'loop {}': !
317 232..234 '{}': ()
318 243..255 '{ loop {}; }': u32
319 245..252 'loop {}': !
320 250..252 '{}': ()
321 272..324 '{ ...; }; }': ()
322 282..283 'x': u32
323 291..321 '{ let ...; }; }': u32
324 297..298 'y': u32
325 306..318 '{ loop {}; }': u32
326 308..315 'loop {}': !
327 313..315 '{}': ()
328 "###);
329}
330
331#[test]
332fn diverging_expression_2() {
333 let t = infer_with_mismatches(
334 r#"
335//- /main.rs
336fn test1() {
337 // should give type mismatch
338 let x: u32 = { loop {}; "foo" };
339}
340"#,
341 true,
342 );
343 assert_snapshot!(t, @r###"
344 25..98 '{ ..." }; }': ()
345 68..69 'x': u32
346 77..95 '{ loop...foo" }': &str
347 79..86 'loop {}': !
348 84..86 '{}': ()
349 88..93 '"foo"': &str
350 77..95: expected u32, got &str
351 88..93: expected u32, got &str
352 "###);
353}
354
355#[test]
356fn diverging_expression_3_break() {
357 let t = infer_with_mismatches(
358 r#"
359//- /main.rs
360fn test1() {
361 // should give type mismatch
362 let x: u32 = { loop { break; } };
363}
364fn test2() {
365 // should give type mismatch
366 let x: u32 = { for a in b { break; }; };
367 // should give type mismatch as well
368 let x: u32 = { for a in b {}; };
369 // should give type mismatch as well
370 let x: u32 = { for a in b { return; }; };
371}
372fn test3() {
373 // should give type mismatch
374 let x: u32 = { while true { break; }; };
375 // should give type mismatch as well -- there's an implicit break, even if it's never hit
376 let x: u32 = { while true {}; };
377 // should give type mismatch as well
378 let x: u32 = { while true { return; }; };
379}
380"#,
381 true,
382 );
383 assert_snapshot!(t, @r###"
384 25..99 '{ ...} }; }': ()
385 68..69 'x': u32
386 77..96 '{ loop...k; } }': ()
387 79..94 'loop { break; }': ()
388 84..94 '{ break; }': ()
389 86..91 'break': !
390 77..96: expected u32, got ()
391 79..94: expected u32, got ()
392 111..357 '{ ...; }; }': ()
393 154..155 'x': u32
394 163..189 '{ for ...; }; }': ()
395 165..186 'for a ...eak; }': ()
396 169..170 'a': {unknown}
397 174..175 'b': {unknown}
398 176..186 '{ break; }': ()
399 178..183 'break': !
400 240..241 'x': u32
401 249..267 '{ for ... {}; }': ()
402 251..264 'for a in b {}': ()
403 255..256 'a': {unknown}
404 260..261 'b': {unknown}
405 262..264 '{}': ()
406 318..319 'x': u32
407 327..354 '{ for ...; }; }': ()
408 329..351 'for a ...urn; }': ()
409 333..334 'a': {unknown}
410 338..339 'b': {unknown}
411 340..351 '{ return; }': ()
412 342..348 'return': !
413 163..189: expected u32, got ()
414 249..267: expected u32, got ()
415 327..354: expected u32, got ()
416 369..668 '{ ...; }; }': ()
417 412..413 'x': u32
418 421..447 '{ whil...; }; }': ()
419 423..444 'while ...eak; }': ()
420 429..433 'true': bool
421 434..444 '{ break; }': ()
422 436..441 'break': !
423 551..552 'x': u32
424 560..578 '{ whil... {}; }': ()
425 562..575 'while true {}': ()
426 568..572 'true': bool
427 573..575 '{}': ()
428 629..630 'x': u32
429 638..665 '{ whil...; }; }': ()
430 640..662 'while ...urn; }': ()
431 646..650 'true': bool
432 651..662 '{ return; }': ()
433 653..659 'return': !
434 421..447: expected u32, got ()
435 560..578: expected u32, got ()
436 638..665: expected u32, got ()
437 "###);
438}
diff --git a/crates/ra_hir_ty/src/tests/simple.rs b/crates/ra_hir_ty/src/tests/simple.rs
index 3d3088965..3820175f6 100644
--- a/crates/ra_hir_ty/src/tests/simple.rs
+++ b/crates/ra_hir_ty/src/tests/simple.rs
@@ -179,7 +179,7 @@ fn test(a: u32, b: isize, c: !, d: &str) {
179 17..18 'b': isize 179 17..18 'b': isize
180 27..28 'c': ! 180 27..28 'c': !
181 33..34 'd': &str 181 33..34 'd': &str
182 42..121 '{ ...f32; }': ! 182 42..121 '{ ...f32; }': ()
183 48..49 'a': u32 183 48..49 'a': u32
184 55..56 'b': isize 184 55..56 'b': isize
185 62..63 'c': ! 185 62..63 'c': !
@@ -414,7 +414,7 @@ fn test() {
414 27..31 '5f32': f32 414 27..31 '5f32': f32
415 37..41 '5f64': f64 415 37..41 '5f64': f64
416 47..54 '"hello"': &str 416 47..54 '"hello"': &str
417 60..68 'b"bytes"': &[u8] 417 60..68 'b"bytes"': &[u8; _]
418 74..77 ''c'': char 418 74..77 ''c'': char
419 83..87 'b'b'': u8 419 83..87 'b'b'': u8
420 93..97 '3.14': f64 420 93..97 '3.14': f64
@@ -422,7 +422,7 @@ fn test() {
422 113..118 'false': bool 422 113..118 'false': bool
423 124..128 'true': bool 423 124..128 'true': bool
424 134..202 'r#" ... "#': &str 424 134..202 'r#" ... "#': &str
425 208..218 'br#"yolo"#': &[u8] 425 208..218 'br#"yolo"#': &[u8; _]
426 "### 426 "###
427 ); 427 );
428} 428}
@@ -935,7 +935,7 @@ fn foo() {
935 29..33 'true': bool 935 29..33 'true': bool
936 34..51 '{ ... }': i32 936 34..51 '{ ... }': i32
937 44..45 '1': i32 937 44..45 '1': i32
938 57..80 '{ ... }': ! 938 57..80 '{ ... }': i32
939 67..73 'return': ! 939 67..73 'return': !
940 90..93 '_x2': i32 940 90..93 '_x2': i32
941 96..149 'if tru... }': i32 941 96..149 'if tru... }': i32
@@ -951,7 +951,7 @@ fn foo() {
951 186..190 'true': bool 951 186..190 'true': bool
952 194..195 '3': i32 952 194..195 '3': i32
953 205..206 '_': bool 953 205..206 '_': bool
954 210..241 '{ ... }': ! 954 210..241 '{ ... }': i32
955 224..230 'return': ! 955 224..230 'return': !
956 257..260 '_x4': i32 956 257..260 '_x4': i32
957 263..320 'match ... }': i32 957 263..320 'match ... }': i32
@@ -1687,7 +1687,7 @@ fn foo() -> u32 {
1687 17..59 '{ ...; }; }': () 1687 17..59 '{ ...; }; }': ()
1688 27..28 'x': || -> usize 1688 27..28 'x': || -> usize
1689 31..56 '|| -> ...n 1; }': || -> usize 1689 31..56 '|| -> ...n 1; }': || -> usize
1690 43..56 '{ return 1; }': ! 1690 43..56 '{ return 1; }': usize
1691 45..53 'return 1': ! 1691 45..53 'return 1': !
1692 52..53 '1': usize 1692 52..53 '1': usize
1693 "### 1693 "###
@@ -1706,7 +1706,7 @@ fn foo() -> u32 {
1706 17..48 '{ ...; }; }': () 1706 17..48 '{ ...; }; }': ()
1707 27..28 'x': || -> () 1707 27..28 'x': || -> ()
1708 31..45 '|| { return; }': || -> () 1708 31..45 '|| { return; }': || -> ()
1709 34..45 '{ return; }': ! 1709 34..45 '{ return; }': ()
1710 36..42 'return': ! 1710 36..42 'return': !
1711 "### 1711 "###
1712 ); 1712 );
diff --git a/crates/ra_ide/src/display/function_signature.rs b/crates/ra_ide/src/display/function_signature.rs
index db3907fe6..9572debd8 100644
--- a/crates/ra_ide/src/display/function_signature.rs
+++ b/crates/ra_ide/src/display/function_signature.rs
@@ -1,5 +1,7 @@
1//! FIXME: write short doc here 1//! FIXME: write short doc here
2 2
3// FIXME: this modules relies on strings and AST way too much, and it should be
4// rewritten (matklad 2020-05-07)
3use std::{ 5use std::{
4 convert::From, 6 convert::From,
5 fmt::{self, Display}, 7 fmt::{self, Display},
@@ -82,8 +84,8 @@ impl FunctionSignature {
82 let ty = field.signature_ty(db); 84 let ty = field.signature_ty(db);
83 let raw_param = format!("{}", ty.display(db)); 85 let raw_param = format!("{}", ty.display(db));
84 86
85 if let Some(param_type) = raw_param.split(':').nth(1) { 87 if let Some(param_type) = raw_param.split(':').nth(1).and_then(|it| it.get(1..)) {
86 parameter_types.push(param_type[1..].to_string()); 88 parameter_types.push(param_type.to_string());
87 } else { 89 } else {
88 // useful when you have tuple struct 90 // useful when you have tuple struct
89 parameter_types.push(raw_param.clone()); 91 parameter_types.push(raw_param.clone());
@@ -127,8 +129,8 @@ impl FunctionSignature {
127 for field in variant.fields(db).into_iter() { 129 for field in variant.fields(db).into_iter() {
128 let ty = field.signature_ty(db); 130 let ty = field.signature_ty(db);
129 let raw_param = format!("{}", ty.display(db)); 131 let raw_param = format!("{}", ty.display(db));
130 if let Some(param_type) = raw_param.split(':').nth(1) { 132 if let Some(param_type) = raw_param.split(':').nth(1).and_then(|it| it.get(1..)) {
131 parameter_types.push(param_type[1..].to_string()); 133 parameter_types.push(param_type.to_string());
132 } else { 134 } else {
133 // The unwrap_or_else is useful when you have tuple 135 // The unwrap_or_else is useful when you have tuple
134 parameter_types.push(raw_param); 136 parameter_types.push(raw_param);
@@ -195,14 +197,23 @@ impl From<&'_ ast::FnDef> for FunctionSignature {
195 let raw_param = self_param.syntax().text().to_string(); 197 let raw_param = self_param.syntax().text().to_string();
196 198
197 res_types.push( 199 res_types.push(
198 raw_param.split(':').nth(1).unwrap_or_else(|| " Self")[1..].to_string(), 200 raw_param
201 .split(':')
202 .nth(1)
203 .and_then(|it| it.get(1..))
204 .unwrap_or_else(|| "Self")
205 .to_string(),
199 ); 206 );
200 res.push(raw_param); 207 res.push(raw_param);
201 } 208 }
202 209
203 res.extend(param_list.params().map(|param| param.syntax().text().to_string())); 210 res.extend(param_list.params().map(|param| param.syntax().text().to_string()));
204 res_types.extend(param_list.params().map(|param| { 211 res_types.extend(param_list.params().map(|param| {
205 param.syntax().text().to_string().split(':').nth(1).unwrap()[1..].to_string() 212 let param_text = param.syntax().text().to_string();
213 match param_text.split(':').nth(1).and_then(|it| it.get(1..)) {
214 Some(it) => it.to_string(),
215 None => param_text,
216 }
206 })); 217 }));
207 } 218 }
208 (has_self_param, res, res_types) 219 (has_self_param, res, res_types)
diff --git a/crates/ra_ide/src/lib.rs b/crates/ra_ide/src/lib.rs
index 737f87109..915199bd8 100644
--- a/crates/ra_ide/src/lib.rs
+++ b/crates/ra_ide/src/lib.rs
@@ -472,12 +472,12 @@ impl Analysis {
472 /// position. 472 /// position.
473 pub fn assists(&self, frange: FileRange) -> Cancelable<Vec<Assist>> { 473 pub fn assists(&self, frange: FileRange) -> Cancelable<Vec<Assist>> {
474 self.with_db(|db| { 474 self.with_db(|db| {
475 ra_assists::resolved_assists(db, frange) 475 ra_assists::Assist::resolved(db, frange)
476 .into_iter() 476 .into_iter()
477 .map(|assist| Assist { 477 .map(|assist| Assist {
478 id: assist.label.id, 478 id: assist.assist.id,
479 label: assist.label.label, 479 label: assist.assist.label,
480 group_label: assist.label.group.map(|it| it.0), 480 group_label: assist.assist.group.map(|it| it.0),
481 source_change: assist.source_change, 481 source_change: assist.source_change,
482 }) 482 })
483 .collect() 483 .collect()
diff --git a/crates/ra_ide/src/references/rename.rs b/crates/ra_ide/src/references/rename.rs
index 0398d53bc..2cbb82c1a 100644
--- a/crates/ra_ide/src/references/rename.rs
+++ b/crates/ra_ide/src/references/rename.rs
@@ -712,6 +712,68 @@ mod tests {
712 "###); 712 "###);
713 } 713 }
714 714
715 #[test]
716 fn test_enum_variant_from_module_1() {
717 test_rename(
718 r#"
719 mod foo {
720 pub enum Foo {
721 Bar<|>,
722 }
723 }
724
725 fn func(f: foo::Foo) {
726 match f {
727 foo::Foo::Bar => {}
728 }
729 }
730 "#,
731 "Baz",
732 r#"
733 mod foo {
734 pub enum Foo {
735 Baz,
736 }
737 }
738
739 fn func(f: foo::Foo) {
740 match f {
741 foo::Foo::Baz => {}
742 }
743 }
744 "#,
745 );
746 }
747
748 #[test]
749 fn test_enum_variant_from_module_2() {
750 test_rename(
751 r#"
752 mod foo {
753 pub struct Foo {
754 pub bar<|>: uint,
755 }
756 }
757
758 fn foo(f: foo::Foo) {
759 let _ = f.bar;
760 }
761 "#,
762 "baz",
763 r#"
764 mod foo {
765 pub struct Foo {
766 pub baz: uint,
767 }
768 }
769
770 fn foo(f: foo::Foo) {
771 let _ = f.baz;
772 }
773 "#,
774 );
775 }
776
715 fn test_rename(text: &str, new_name: &str, expected: &str) { 777 fn test_rename(text: &str, new_name: &str, expected: &str) {
716 let (analysis, position) = single_file_with_position(text); 778 let (analysis, position) = single_file_with_position(text);
717 let source_change = analysis.rename(position, new_name).unwrap(); 779 let source_change = analysis.rename(position, new_name).unwrap();
diff --git a/crates/ra_ide_db/src/defs.rs b/crates/ra_ide_db/src/defs.rs
index 40d0e77b5..f990e3bb9 100644
--- a/crates/ra_ide_db/src/defs.rs
+++ b/crates/ra_ide_db/src/defs.rs
@@ -6,7 +6,7 @@
6// FIXME: this badly needs rename/rewrite (matklad, 2020-02-06). 6// FIXME: this badly needs rename/rewrite (matklad, 2020-02-06).
7 7
8use hir::{ 8use hir::{
9 Field, HasVisibility, ImplDef, Local, MacroDef, Module, ModuleDef, Name, PathResolution, 9 Adt, Field, HasVisibility, ImplDef, Local, MacroDef, Module, ModuleDef, Name, PathResolution,
10 Semantics, TypeParam, Visibility, 10 Semantics, TypeParam, Visibility,
11}; 11};
12use ra_prof::profile; 12use ra_prof::profile;
@@ -47,7 +47,13 @@ impl Definition {
47 match self { 47 match self {
48 Definition::Macro(_) => None, 48 Definition::Macro(_) => None,
49 Definition::Field(sf) => Some(sf.visibility(db)), 49 Definition::Field(sf) => Some(sf.visibility(db)),
50 Definition::ModuleDef(def) => module?.visibility_of(db, def), 50 Definition::ModuleDef(def) => match def {
51 ModuleDef::EnumVariant(id) => {
52 let parent = id.parent_enum(db);
53 module?.visibility_of(db, &ModuleDef::Adt(Adt::Enum(parent)))
54 }
55 _ => module?.visibility_of(db, def),
56 },
51 Definition::SelfType(_) => None, 57 Definition::SelfType(_) => None,
52 Definition::Local(_) => None, 58 Definition::Local(_) => None,
53 Definition::TypeParam(_) => None, 59 Definition::TypeParam(_) => None,
diff --git a/crates/ra_project_model/Cargo.toml b/crates/ra_project_model/Cargo.toml
index 5e651fe70..a32a5daab 100644
--- a/crates/ra_project_model/Cargo.toml
+++ b/crates/ra_project_model/Cargo.toml
@@ -14,8 +14,9 @@ rustc-hash = "1.1.0"
14cargo_metadata = "0.9.1" 14cargo_metadata = "0.9.1"
15 15
16ra_arena = { path = "../ra_arena" } 16ra_arena = { path = "../ra_arena" }
17ra_db = { path = "../ra_db" }
18ra_cfg = { path = "../ra_cfg" } 17ra_cfg = { path = "../ra_cfg" }
18ra_db = { path = "../ra_db" }
19ra_toolchain = { path = "../ra_toolchain" }
19ra_proc_macro = { path = "../ra_proc_macro" } 20ra_proc_macro = { path = "../ra_proc_macro" }
20 21
21serde = { version = "1.0.106", features = ["derive"] } 22serde = { version = "1.0.106", features = ["derive"] }
diff --git a/crates/ra_project_model/src/cargo_workspace.rs b/crates/ra_project_model/src/cargo_workspace.rs
index 59f46a2a0..082af4f96 100644
--- a/crates/ra_project_model/src/cargo_workspace.rs
+++ b/crates/ra_project_model/src/cargo_workspace.rs
@@ -1,7 +1,6 @@
1//! FIXME: write short doc here 1//! FIXME: write short doc here
2 2
3use std::{ 3use std::{
4 env,
5 ffi::OsStr, 4 ffi::OsStr,
6 ops, 5 ops,
7 path::{Path, PathBuf}, 6 path::{Path, PathBuf},
@@ -87,6 +86,7 @@ pub struct PackageData {
87 pub dependencies: Vec<PackageDependency>, 86 pub dependencies: Vec<PackageDependency>,
88 pub edition: Edition, 87 pub edition: Edition,
89 pub features: Vec<String>, 88 pub features: Vec<String>,
89 pub cfgs: Vec<String>,
90 pub out_dir: Option<PathBuf>, 90 pub out_dir: Option<PathBuf>,
91 pub proc_macro_dylib_path: Option<PathBuf>, 91 pub proc_macro_dylib_path: Option<PathBuf>,
92} 92}
@@ -145,12 +145,8 @@ impl CargoWorkspace {
145 cargo_toml: &Path, 145 cargo_toml: &Path,
146 cargo_features: &CargoConfig, 146 cargo_features: &CargoConfig,
147 ) -> Result<CargoWorkspace> { 147 ) -> Result<CargoWorkspace> {
148 let _ = Command::new(cargo_binary())
149 .arg("--version")
150 .output()
151 .context("failed to run `cargo --version`, is `cargo` in PATH?")?;
152
153 let mut meta = MetadataCommand::new(); 148 let mut meta = MetadataCommand::new();
149 meta.cargo_path(ra_toolchain::cargo());
154 meta.manifest_path(cargo_toml); 150 meta.manifest_path(cargo_toml);
155 if cargo_features.all_features { 151 if cargo_features.all_features {
156 meta.features(CargoOpt::AllFeatures); 152 meta.features(CargoOpt::AllFeatures);
@@ -172,10 +168,12 @@ impl CargoWorkspace {
172 })?; 168 })?;
173 169
174 let mut out_dir_by_id = FxHashMap::default(); 170 let mut out_dir_by_id = FxHashMap::default();
171 let mut cfgs = FxHashMap::default();
175 let mut proc_macro_dylib_paths = FxHashMap::default(); 172 let mut proc_macro_dylib_paths = FxHashMap::default();
176 if cargo_features.load_out_dirs_from_check { 173 if cargo_features.load_out_dirs_from_check {
177 let resources = load_extern_resources(cargo_toml, cargo_features)?; 174 let resources = load_extern_resources(cargo_toml, cargo_features)?;
178 out_dir_by_id = resources.out_dirs; 175 out_dir_by_id = resources.out_dirs;
176 cfgs = resources.cfgs;
179 proc_macro_dylib_paths = resources.proc_dylib_paths; 177 proc_macro_dylib_paths = resources.proc_dylib_paths;
180 } 178 }
181 179
@@ -201,6 +199,7 @@ impl CargoWorkspace {
201 edition, 199 edition,
202 dependencies: Vec::new(), 200 dependencies: Vec::new(),
203 features: Vec::new(), 201 features: Vec::new(),
202 cfgs: cfgs.get(&id).cloned().unwrap_or_default(),
204 out_dir: out_dir_by_id.get(&id).cloned(), 203 out_dir: out_dir_by_id.get(&id).cloned(),
205 proc_macro_dylib_path: proc_macro_dylib_paths.get(&id).cloned(), 204 proc_macro_dylib_path: proc_macro_dylib_paths.get(&id).cloned(),
206 }); 205 });
@@ -282,13 +281,14 @@ impl CargoWorkspace {
282pub struct ExternResources { 281pub struct ExternResources {
283 out_dirs: FxHashMap<PackageId, PathBuf>, 282 out_dirs: FxHashMap<PackageId, PathBuf>,
284 proc_dylib_paths: FxHashMap<PackageId, PathBuf>, 283 proc_dylib_paths: FxHashMap<PackageId, PathBuf>,
284 cfgs: FxHashMap<PackageId, Vec<String>>,
285} 285}
286 286
287pub fn load_extern_resources( 287pub fn load_extern_resources(
288 cargo_toml: &Path, 288 cargo_toml: &Path,
289 cargo_features: &CargoConfig, 289 cargo_features: &CargoConfig,
290) -> Result<ExternResources> { 290) -> Result<ExternResources> {
291 let mut cmd = Command::new(cargo_binary()); 291 let mut cmd = Command::new(ra_toolchain::cargo());
292 cmd.args(&["check", "--message-format=json", "--manifest-path"]).arg(cargo_toml); 292 cmd.args(&["check", "--message-format=json", "--manifest-path"]).arg(cargo_toml);
293 if cargo_features.all_features { 293 if cargo_features.all_features {
294 cmd.arg("--all-features"); 294 cmd.arg("--all-features");
@@ -307,8 +307,14 @@ pub fn load_extern_resources(
307 for message in cargo_metadata::parse_messages(output.stdout.as_slice()) { 307 for message in cargo_metadata::parse_messages(output.stdout.as_slice()) {
308 if let Ok(message) = message { 308 if let Ok(message) = message {
309 match message { 309 match message {
310 Message::BuildScriptExecuted(BuildScript { package_id, out_dir, .. }) => { 310 Message::BuildScriptExecuted(BuildScript { package_id, out_dir, cfgs, .. }) => {
311 res.out_dirs.insert(package_id, out_dir); 311 res.out_dirs.insert(package_id.clone(), out_dir);
312 res.cfgs.insert(
313 package_id,
314 // FIXME: Current `cargo_metadata` uses `PathBuf` instead of `String`,
315 // change when https://github.com/oli-obk/cargo_metadata/pulls/112 reaches crates.io
316 cfgs.iter().filter_map(|c| c.to_str().map(|s| s.to_owned())).collect(),
317 );
312 } 318 }
313 319
314 Message::CompilerArtifact(message) => { 320 Message::CompilerArtifact(message) => {
@@ -336,7 +342,3 @@ fn is_dylib(path: &Path) -> bool {
336 Some(ext) => matches!(ext.as_str(), "dll" | "dylib" | "so"), 342 Some(ext) => matches!(ext.as_str(), "dll" | "dylib" | "so"),
337 } 343 }
338} 344}
339
340fn cargo_binary() -> String {
341 env::var("CARGO").unwrap_or_else(|_| "cargo".to_string())
342}
diff --git a/crates/ra_project_model/src/lib.rs b/crates/ra_project_model/src/lib.rs
index c2b33c1dc..4f098b706 100644
--- a/crates/ra_project_model/src/lib.rs
+++ b/crates/ra_project_model/src/lib.rs
@@ -8,7 +8,7 @@ use std::{
8 fs::{read_dir, File, ReadDir}, 8 fs::{read_dir, File, ReadDir},
9 io::{self, BufReader}, 9 io::{self, BufReader},
10 path::{Path, PathBuf}, 10 path::{Path, PathBuf},
11 process::Command, 11 process::{Command, Output},
12}; 12};
13 13
14use anyhow::{bail, Context, Result}; 14use anyhow::{bail, Context, Result};
@@ -88,46 +88,28 @@ impl ProjectRoot {
88 } 88 }
89 89
90 pub fn discover(path: &Path) -> io::Result<Vec<ProjectRoot>> { 90 pub fn discover(path: &Path) -> io::Result<Vec<ProjectRoot>> {
91 if let Some(project_json) = find_rust_project_json(path) { 91 if let Some(project_json) = find_in_parent_dirs(path, "rust-project.json") {
92 return Ok(vec![ProjectRoot::ProjectJson(project_json)]); 92 return Ok(vec![ProjectRoot::ProjectJson(project_json)]);
93 } 93 }
94 return find_cargo_toml(path) 94 return find_cargo_toml(path)
95 .map(|paths| paths.into_iter().map(ProjectRoot::CargoToml).collect()); 95 .map(|paths| paths.into_iter().map(ProjectRoot::CargoToml).collect());
96 96
97 fn find_rust_project_json(path: &Path) -> Option<PathBuf> {
98 if path.ends_with("rust-project.json") {
99 return Some(path.to_path_buf());
100 }
101
102 let mut curr = Some(path);
103 while let Some(path) = curr {
104 let candidate = path.join("rust-project.json");
105 if candidate.exists() {
106 return Some(candidate);
107 }
108 curr = path.parent();
109 }
110
111 None
112 }
113
114 fn find_cargo_toml(path: &Path) -> io::Result<Vec<PathBuf>> { 97 fn find_cargo_toml(path: &Path) -> io::Result<Vec<PathBuf>> {
115 if path.ends_with("Cargo.toml") { 98 match find_in_parent_dirs(path, "Cargo.toml") {
116 return Ok(vec![path.to_path_buf()]); 99 Some(it) => Ok(vec![it]),
100 None => Ok(find_cargo_toml_in_child_dir(read_dir(path)?)),
117 } 101 }
102 }
118 103
119 if let Some(p) = find_cargo_toml_in_parent_dir(path) { 104 fn find_in_parent_dirs(path: &Path, target_file_name: &str) -> Option<PathBuf> {
120 return Ok(vec![p]); 105 if path.ends_with(target_file_name) {
106 return Some(path.to_owned());
121 } 107 }
122 108
123 let entities = read_dir(path)?;
124 Ok(find_cargo_toml_in_child_dir(entities))
125 }
126
127 fn find_cargo_toml_in_parent_dir(path: &Path) -> Option<PathBuf> {
128 let mut curr = Some(path); 109 let mut curr = Some(path);
110
129 while let Some(path) = curr { 111 while let Some(path) = curr {
130 let candidate = path.join("Cargo.toml"); 112 let candidate = path.join(target_file_name);
131 if candidate.exists() { 113 if candidate.exists() {
132 return Some(candidate); 114 return Some(candidate);
133 } 115 }
@@ -139,14 +121,11 @@ impl ProjectRoot {
139 121
140 fn find_cargo_toml_in_child_dir(entities: ReadDir) -> Vec<PathBuf> { 122 fn find_cargo_toml_in_child_dir(entities: ReadDir) -> Vec<PathBuf> {
141 // Only one level down to avoid cycles the easy way and stop a runaway scan with large projects 123 // Only one level down to avoid cycles the easy way and stop a runaway scan with large projects
142 let mut valid_canditates = vec![]; 124 entities
143 for entity in entities.filter_map(Result::ok) { 125 .filter_map(Result::ok)
144 let candidate = entity.path().join("Cargo.toml"); 126 .map(|it| it.path().join("Cargo.toml"))
145 if candidate.exists() { 127 .filter(|it| it.exists())
146 valid_canditates.push(candidate) 128 .collect()
147 }
148 }
149 valid_canditates
150 } 129 }
151 } 130 }
152} 131}
@@ -398,7 +377,18 @@ impl ProjectWorkspace {
398 let edition = cargo[pkg].edition; 377 let edition = cargo[pkg].edition;
399 let cfg_options = { 378 let cfg_options = {
400 let mut opts = default_cfg_options.clone(); 379 let mut opts = default_cfg_options.clone();
401 opts.insert_features(cargo[pkg].features.iter().map(Into::into)); 380 for feature in cargo[pkg].features.iter() {
381 opts.insert_key_value("feature".into(), feature.into());
382 }
383 for cfg in cargo[pkg].cfgs.iter() {
384 match cfg.find('=') {
385 Some(split) => opts.insert_key_value(
386 cfg[..split].into(),
387 cfg[split + 1..].trim_matches('"').into(),
388 ),
389 None => opts.insert_atom(cfg.into()),
390 };
391 }
402 opts 392 opts
403 }; 393 };
404 let mut env = Env::default(); 394 let mut env = Env::default();
@@ -556,25 +546,18 @@ pub fn get_rustc_cfg_options(target: Option<&String>) -> CfgOptions {
556 } 546 }
557 } 547 }
558 548
559 match (|| -> Result<String> { 549 let rustc_cfgs = || -> Result<String> {
560 // `cfg(test)` and `cfg(debug_assertion)` are handled outside, so we suppress them here. 550 // `cfg(test)` and `cfg(debug_assertion)` are handled outside, so we suppress them here.
561 let mut cmd = Command::new("rustc"); 551 let mut cmd = Command::new(ra_toolchain::rustc());
562 cmd.args(&["--print", "cfg", "-O"]); 552 cmd.args(&["--print", "cfg", "-O"]);
563 if let Some(target) = target { 553 if let Some(target) = target {
564 cmd.args(&["--target", target.as_str()]); 554 cmd.args(&["--target", target.as_str()]);
565 } 555 }
566 let output = cmd.output().context("Failed to get output from rustc --print cfg -O")?; 556 let output = output(cmd)?;
567 if !output.status.success() {
568 bail!(
569 "rustc --print cfg -O exited with exit code ({})",
570 output
571 .status
572 .code()
573 .map_or(String::from("no exit code"), |code| format!("{}", code))
574 );
575 }
576 Ok(String::from_utf8(output.stdout)?) 557 Ok(String::from_utf8(output.stdout)?)
577 })() { 558 }();
559
560 match rustc_cfgs {
578 Ok(rustc_cfgs) => { 561 Ok(rustc_cfgs) => {
579 for line in rustc_cfgs.lines() { 562 for line in rustc_cfgs.lines() {
580 match line.find('=') { 563 match line.find('=') {
@@ -587,8 +570,21 @@ pub fn get_rustc_cfg_options(target: Option<&String>) -> CfgOptions {
587 } 570 }
588 } 571 }
589 } 572 }
590 Err(e) => log::error!("failed to get rustc cfgs: {}", e), 573 Err(e) => log::error!("failed to get rustc cfgs: {:#}", e),
591 } 574 }
592 575
593 cfg_options 576 cfg_options
594} 577}
578
579fn output(mut cmd: Command) -> Result<Output> {
580 let output = cmd.output().with_context(|| format!("{:?} failed", cmd))?;
581 if !output.status.success() {
582 match String::from_utf8(output.stderr) {
583 Ok(stderr) if !stderr.is_empty() => {
584 bail!("{:?} failed, {}\nstderr:\n{}", cmd, output.status, stderr)
585 }
586 _ => bail!("{:?} failed, {}", cmd, output.status),
587 }
588 }
589 Ok(output)
590}
diff --git a/crates/ra_project_model/src/sysroot.rs b/crates/ra_project_model/src/sysroot.rs
index 55ff5ad80..a8a196e64 100644
--- a/crates/ra_project_model/src/sysroot.rs
+++ b/crates/ra_project_model/src/sysroot.rs
@@ -1,14 +1,16 @@
1//! FIXME: write short doc here 1//! FIXME: write short doc here
2 2
3use anyhow::{bail, Context, Result};
4use std::{ 3use std::{
5 env, ops, 4 env, ops,
6 path::{Path, PathBuf}, 5 path::{Path, PathBuf},
7 process::{Command, Output}, 6 process::Command,
8}; 7};
9 8
9use anyhow::{bail, Result};
10use ra_arena::{Arena, Idx}; 10use ra_arena::{Arena, Idx};
11 11
12use crate::output;
13
12#[derive(Default, Debug, Clone)] 14#[derive(Default, Debug, Clone)]
13pub struct Sysroot { 15pub struct Sysroot {
14 crates: Arena<SysrootCrateData>, 16 crates: Arena<SysrootCrateData>,
@@ -84,43 +86,22 @@ impl Sysroot {
84 } 86 }
85} 87}
86 88
87fn create_command_text(program: &str, args: &[&str]) -> String {
88 format!("{} {}", program, args.join(" "))
89}
90
91fn run_command_in_cargo_dir(cargo_toml: &Path, program: &str, args: &[&str]) -> Result<Output> {
92 let output = Command::new(program)
93 .current_dir(cargo_toml.parent().unwrap())
94 .args(args)
95 .output()
96 .context(format!("{} failed", create_command_text(program, args)))?;
97 if !output.status.success() {
98 match output.status.code() {
99 Some(code) => bail!(
100 "failed to run the command: '{}' exited with code {}",
101 create_command_text(program, args),
102 code
103 ),
104 None => bail!(
105 "failed to run the command: '{}' terminated by signal",
106 create_command_text(program, args)
107 ),
108 };
109 }
110 Ok(output)
111}
112
113fn get_or_install_rust_src(cargo_toml: &Path) -> Result<PathBuf> { 89fn get_or_install_rust_src(cargo_toml: &Path) -> Result<PathBuf> {
114 if let Ok(path) = env::var("RUST_SRC_PATH") { 90 if let Ok(path) = env::var("RUST_SRC_PATH") {
115 return Ok(path.into()); 91 return Ok(path.into());
116 } 92 }
117 let rustc_output = run_command_in_cargo_dir(cargo_toml, "rustc", &["--print", "sysroot"])?; 93 let current_dir = cargo_toml.parent().unwrap();
94 let mut rustc = Command::new(ra_toolchain::rustc());
95 rustc.current_dir(current_dir).args(&["--print", "sysroot"]);
96 let rustc_output = output(rustc)?;
118 let stdout = String::from_utf8(rustc_output.stdout)?; 97 let stdout = String::from_utf8(rustc_output.stdout)?;
119 let sysroot_path = Path::new(stdout.trim()); 98 let sysroot_path = Path::new(stdout.trim());
120 let src_path = sysroot_path.join("lib/rustlib/src/rust/src"); 99 let src_path = sysroot_path.join("lib/rustlib/src/rust/src");
121 100
122 if !src_path.exists() { 101 if !src_path.exists() {
123 run_command_in_cargo_dir(cargo_toml, "rustup", &["component", "add", "rust-src"])?; 102 let mut rustup = Command::new(ra_toolchain::rustup());
103 rustup.current_dir(current_dir).args(&["component", "add", "rust-src"]);
104 let _output = output(rustup)?;
124 } 105 }
125 if !src_path.exists() { 106 if !src_path.exists() {
126 bail!( 107 bail!(
diff --git a/crates/ra_syntax/src/ast/edit.rs b/crates/ra_syntax/src/ast/edit.rs
index 3e6dd6061..24a1e1d91 100644
--- a/crates/ra_syntax/src/ast/edit.rs
+++ b/crates/ra_syntax/src/ast/edit.rs
@@ -453,11 +453,7 @@ impl IndentLevel {
453 IndentLevel(0) 453 IndentLevel(0)
454 } 454 }
455 455
456 pub fn increase_indent<N: AstNode>(self, node: N) -> N { 456 fn increase_indent(self, node: SyntaxNode) -> SyntaxNode {
457 N::cast(self._increase_indent(node.syntax().clone())).unwrap()
458 }
459
460 fn _increase_indent(self, node: SyntaxNode) -> SyntaxNode {
461 let mut rewriter = SyntaxRewriter::default(); 457 let mut rewriter = SyntaxRewriter::default();
462 node.descendants_with_tokens() 458 node.descendants_with_tokens()
463 .filter_map(|el| el.into_token()) 459 .filter_map(|el| el.into_token())
@@ -478,11 +474,7 @@ impl IndentLevel {
478 rewriter.rewrite(&node) 474 rewriter.rewrite(&node)
479 } 475 }
480 476
481 pub fn decrease_indent<N: AstNode>(self, node: N) -> N { 477 fn decrease_indent(self, node: SyntaxNode) -> SyntaxNode {
482 N::cast(self._decrease_indent(node.syntax().clone())).unwrap()
483 }
484
485 fn _decrease_indent(self, node: SyntaxNode) -> SyntaxNode {
486 let mut rewriter = SyntaxRewriter::default(); 478 let mut rewriter = SyntaxRewriter::default();
487 node.descendants_with_tokens() 479 node.descendants_with_tokens()
488 .filter_map(|el| el.into_token()) 480 .filter_map(|el| el.into_token())
@@ -521,7 +513,7 @@ fn prev_tokens(token: SyntaxToken) -> impl Iterator<Item = SyntaxToken> {
521 iter::successors(Some(token), |token| token.prev_token()) 513 iter::successors(Some(token), |token| token.prev_token())
522} 514}
523 515
524pub trait AstNodeEdit: AstNode + Sized { 516pub trait AstNodeEdit: AstNode + Clone + Sized {
525 #[must_use] 517 #[must_use]
526 fn insert_children( 518 fn insert_children(
527 &self, 519 &self,
@@ -558,9 +550,17 @@ pub trait AstNodeEdit: AstNode + Sized {
558 } 550 }
559 rewriter.rewrite_ast(self) 551 rewriter.rewrite_ast(self)
560 } 552 }
553 #[must_use]
554 fn indent(&self, indent: IndentLevel) -> Self {
555 Self::cast(indent.increase_indent(self.syntax().clone())).unwrap()
556 }
557 #[must_use]
558 fn dedent(&self, indent: IndentLevel) -> Self {
559 Self::cast(indent.decrease_indent(self.syntax().clone())).unwrap()
560 }
561} 561}
562 562
563impl<N: AstNode> AstNodeEdit for N {} 563impl<N: AstNode + Clone> AstNodeEdit for N {}
564 564
565fn single_node(element: impl Into<SyntaxElement>) -> RangeInclusive<SyntaxElement> { 565fn single_node(element: impl Into<SyntaxElement>) -> RangeInclusive<SyntaxElement> {
566 let element = element.into(); 566 let element = element.into();
@@ -580,7 +580,7 @@ fn test_increase_indent() {
580 _ => (), 580 _ => (),
581}" 581}"
582 ); 582 );
583 let indented = IndentLevel(2).increase_indent(arm_list); 583 let indented = arm_list.indent(IndentLevel(2));
584 assert_eq!( 584 assert_eq!(
585 indented.syntax().to_string(), 585 indented.syntax().to_string(),
586 "{ 586 "{
diff --git a/crates/ra_toolchain/Cargo.toml b/crates/ra_toolchain/Cargo.toml
new file mode 100644
index 000000000..1873fbe16
--- /dev/null
+++ b/crates/ra_toolchain/Cargo.toml
@@ -0,0 +1,8 @@
1[package]
2edition = "2018"
3name = "ra_toolchain"
4version = "0.1.0"
5authors = ["rust-analyzer developers"]
6
7[dependencies]
8home = "0.5.3"
diff --git a/crates/ra_toolchain/src/lib.rs b/crates/ra_toolchain/src/lib.rs
new file mode 100644
index 000000000..3c307a0ea
--- /dev/null
+++ b/crates/ra_toolchain/src/lib.rs
@@ -0,0 +1,64 @@
1//! This crate contains a single public function
2//! [`get_path_for_executable`](fn.get_path_for_executable.html).
3//! See docs there for more information.
4use std::{env, iter, path::PathBuf};
5
6pub fn cargo() -> PathBuf {
7 get_path_for_executable("cargo")
8}
9
10pub fn rustc() -> PathBuf {
11 get_path_for_executable("rustc")
12}
13
14pub fn rustup() -> PathBuf {
15 get_path_for_executable("rustup")
16}
17
18/// Return a `PathBuf` to use for the given executable.
19///
20/// E.g., `get_path_for_executable("cargo")` may return just `cargo` if that
21/// gives a valid Cargo executable; or it may return a full path to a valid
22/// Cargo.
23fn get_path_for_executable(executable_name: &'static str) -> PathBuf {
24 // The current implementation checks three places for an executable to use:
25 // 1) Appropriate environment variable (erroring if this is set but not a usable executable)
26 // example: for cargo, this checks $CARGO environment variable; for rustc, $RUSTC; etc
27 // 2) `<executable_name>`
28 // example: for cargo, this tries just `cargo`, which will succeed if `cargo` is on the $PATH
29 // 3) `~/.cargo/bin/<executable_name>`
30 // example: for cargo, this tries ~/.cargo/bin/cargo
31 // It seems that this is a reasonable place to try for cargo, rustc, and rustup
32 let env_var = executable_name.to_ascii_uppercase();
33 if let Some(path) = env::var_os(&env_var) {
34 return path.into();
35 }
36
37 if lookup_in_path(executable_name) {
38 return executable_name.into();
39 }
40
41 if let Some(mut path) = home::home_dir() {
42 path.push(".cargo");
43 path.push("bin");
44 path.push(executable_name);
45 if path.is_file() {
46 return path;
47 }
48 }
49 executable_name.into()
50}
51
52fn lookup_in_path(exec: &str) -> bool {
53 let paths = env::var_os("PATH").unwrap_or_default();
54 let mut candidates = env::split_paths(&paths).flat_map(|path| {
55 let candidate = path.join(&exec);
56 let with_exe = if env::consts::EXE_EXTENSION == "" {
57 None
58 } else {
59 Some(candidate.with_extension(env::consts::EXE_EXTENSION))
60 };
61 iter::once(candidate).chain(with_exe)
62 });
63 candidates.any(|it| it.is_file())
64}
diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs
index b77f0c5a9..17b0b95b9 100644
--- a/crates/rust-analyzer/src/main_loop.rs
+++ b/crates/rust-analyzer/src/main_loop.rs
@@ -96,23 +96,21 @@ pub fn main_loop(ws_roots: Vec<PathBuf>, config: Config, connection: Connection)
96 let mut world_state = { 96 let mut world_state = {
97 let workspaces = { 97 let workspaces = {
98 // FIXME: support dynamic workspace loading. 98 // FIXME: support dynamic workspace loading.
99 let mut visited = FxHashSet::default(); 99 let project_roots: FxHashSet<_> = ws_roots
100 let project_roots = ws_roots
101 .iter() 100 .iter()
102 .filter_map(|it| ra_project_model::ProjectRoot::discover(it).ok()) 101 .filter_map(|it| ra_project_model::ProjectRoot::discover(it).ok())
103 .flatten() 102 .flatten()
104 .filter(|it| visited.insert(it.clone())) 103 .collect();
105 .collect::<Vec<_>>();
106 104
107 if project_roots.is_empty() && config.notifications.cargo_toml_not_found { 105 if project_roots.is_empty() && config.notifications.cargo_toml_not_found {
108 show_message( 106 show_message(
109 req::MessageType::Error, 107 req::MessageType::Error,
110 format!( 108 format!(
111 "rust-analyzer failed to discover workspace, no Cargo.toml found, dirs searched: {}", 109 "rust-analyzer failed to discover workspace, no Cargo.toml found, dirs searched: {}",
112 ws_roots.iter().format_with(", ", |it, f| f(&it.display())) 110 ws_roots.iter().format_with(", ", |it, f| f(&it.display()))
113 ), 111 ),
114 &connection.sender, 112 &connection.sender,
115 ); 113 );
116 }; 114 };
117 115
118 project_roots 116 project_roots
diff --git a/crates/rust-analyzer/src/main_loop/handlers.rs b/crates/rust-analyzer/src/main_loop/handlers.rs
index 15e8305f8..f4353af64 100644
--- a/crates/rust-analyzer/src/main_loop/handlers.rs
+++ b/crates/rust-analyzer/src/main_loop/handlers.rs
@@ -42,6 +42,7 @@ use crate::{
42 world::WorldSnapshot, 42 world::WorldSnapshot,
43 LspError, Result, 43 LspError, Result,
44}; 44};
45use ra_project_model::TargetKind;
45 46
46pub fn handle_analyzer_status(world: WorldSnapshot, _: ()) -> Result<String> { 47pub fn handle_analyzer_status(world: WorldSnapshot, _: ()) -> Result<String> {
47 let _p = profile("handle_analyzer_status"); 48 let _p = profile("handle_analyzer_status");
@@ -384,16 +385,27 @@ pub fn handle_runnables(
384 let offset = params.position.map(|it| it.conv_with(&line_index)); 385 let offset = params.position.map(|it| it.conv_with(&line_index));
385 let mut res = Vec::new(); 386 let mut res = Vec::new();
386 let workspace_root = world.workspace_root_for(file_id); 387 let workspace_root = world.workspace_root_for(file_id);
388 let cargo_spec = CargoTargetSpec::for_file(&world, file_id)?;
387 for runnable in world.analysis().runnables(file_id)? { 389 for runnable in world.analysis().runnables(file_id)? {
388 if let Some(offset) = offset { 390 if let Some(offset) = offset {
389 if !runnable.range.contains_inclusive(offset) { 391 if !runnable.range.contains_inclusive(offset) {
390 continue; 392 continue;
391 } 393 }
392 } 394 }
395 // Do not suggest binary run on other target than binary
396 if let RunnableKind::Bin = runnable.kind {
397 if let Some(spec) = &cargo_spec {
398 match spec.target_kind {
399 TargetKind::Bin => {}
400 _ => continue,
401 }
402 }
403 }
393 res.push(to_lsp_runnable(&world, file_id, runnable)?); 404 res.push(to_lsp_runnable(&world, file_id, runnable)?);
394 } 405 }
406
395 // Add `cargo check` and `cargo test` for the whole package 407 // Add `cargo check` and `cargo test` for the whole package
396 match CargoTargetSpec::for_file(&world, file_id)? { 408 match cargo_spec {
397 Some(spec) => { 409 Some(spec) => {
398 for &cmd in ["check", "test"].iter() { 410 for &cmd in ["check", "test"].iter() {
399 res.push(req::Runnable { 411 res.push(req::Runnable {
@@ -831,13 +843,23 @@ pub fn handle_code_lens(
831 843
832 let mut lenses: Vec<CodeLens> = Default::default(); 844 let mut lenses: Vec<CodeLens> = Default::default();
833 845
846 let cargo_spec = CargoTargetSpec::for_file(&world, file_id)?;
834 // Gather runnables 847 // Gather runnables
835 for runnable in world.analysis().runnables(file_id)? { 848 for runnable in world.analysis().runnables(file_id)? {
836 let title = match &runnable.kind { 849 let title = match &runnable.kind {
837 RunnableKind::Test { .. } | RunnableKind::TestMod { .. } => "▶️\u{fe0e}Run Test", 850 RunnableKind::Test { .. } | RunnableKind::TestMod { .. } => "▶️\u{fe0e}Run Test",
838 RunnableKind::DocTest { .. } => "▶️\u{fe0e}Run Doctest", 851 RunnableKind::DocTest { .. } => "▶️\u{fe0e}Run Doctest",
839 RunnableKind::Bench { .. } => "Run Bench", 852 RunnableKind::Bench { .. } => "Run Bench",
840 RunnableKind::Bin => "Run", 853 RunnableKind::Bin => {
854 // Do not suggest binary run on other target than binary
855 match &cargo_spec {
856 Some(spec) => match spec.target_kind {
857 TargetKind::Bin => "Run",
858 _ => continue,
859 },
860 None => continue,
861 }
862 }
841 } 863 }
842 .to_string(); 864 .to_string();
843 let mut r = to_lsp_runnable(&world, file_id, runnable)?; 865 let mut r = to_lsp_runnable(&world, file_id, runnable)?;
diff --git a/crates/rust-analyzer/tests/heavy_tests/main.rs b/crates/rust-analyzer/tests/heavy_tests/main.rs
index 1efa5dd63..e459e3a3c 100644
--- a/crates/rust-analyzer/tests/heavy_tests/main.rs
+++ b/crates/rust-analyzer/tests/heavy_tests/main.rs
@@ -9,7 +9,8 @@ use lsp_types::{
9}; 9};
10use rust_analyzer::req::{ 10use rust_analyzer::req::{
11 CodeActionParams, CodeActionRequest, Completion, CompletionParams, DidOpenTextDocument, 11 CodeActionParams, CodeActionRequest, Completion, CompletionParams, DidOpenTextDocument,
12 Formatting, GotoDefinition, HoverRequest, OnEnter, Runnables, RunnablesParams, 12 Formatting, GotoDefinition, GotoTypeDefinition, HoverRequest, OnEnter, Runnables,
13 RunnablesParams,
13}; 14};
14use serde_json::json; 15use serde_json::json;
15use tempfile::TempDir; 16use tempfile::TempDir;
@@ -574,7 +575,7 @@ version = \"0.0.0\"
574} 575}
575 576
576#[test] 577#[test]
577fn resolve_include_concat_env() { 578fn out_dirs_check() {
578 if skip_slow_tests() { 579 if skip_slow_tests() {
579 return; 580 return;
580 } 581 }
@@ -597,11 +598,28 @@ fn main() {
597 r#"pub fn message() -> &'static str { "Hello, World!" }"#, 598 r#"pub fn message() -> &'static str { "Hello, World!" }"#,
598 ) 599 )
599 .unwrap(); 600 .unwrap();
601 println!("cargo:rustc-cfg=atom_cfg");
602 println!("cargo:rustc-cfg=featlike=\"set\"");
600 println!("cargo:rerun-if-changed=build.rs"); 603 println!("cargo:rerun-if-changed=build.rs");
601} 604}
602//- src/main.rs 605//- src/main.rs
603include!(concat!(env!("OUT_DIR"), "/hello.rs")); 606include!(concat!(env!("OUT_DIR"), "/hello.rs"));
604 607
608#[cfg(atom_cfg)]
609struct A;
610#[cfg(bad_atom_cfg)]
611struct A;
612#[cfg(featlike = "set")]
613struct B;
614#[cfg(featlike = "not_set")]
615struct B;
616
617fn main() {
618 let va = A;
619 let vb = B;
620 message();
621}
622
605fn main() { message(); } 623fn main() { message(); }
606"###, 624"###,
607 ) 625 )
@@ -613,12 +631,98 @@ fn main() { message(); }
613 let res = server.send_request::<GotoDefinition>(GotoDefinitionParams { 631 let res = server.send_request::<GotoDefinition>(GotoDefinitionParams {
614 text_document_position_params: TextDocumentPositionParams::new( 632 text_document_position_params: TextDocumentPositionParams::new(
615 server.doc_id("src/main.rs"), 633 server.doc_id("src/main.rs"),
616 Position::new(2, 15), 634 Position::new(14, 8),
617 ), 635 ),
618 work_done_progress_params: Default::default(), 636 work_done_progress_params: Default::default(),
619 partial_result_params: Default::default(), 637 partial_result_params: Default::default(),
620 }); 638 });
621 assert!(format!("{}", res).contains("hello.rs")); 639 assert!(format!("{}", res).contains("hello.rs"));
640 server.request::<GotoTypeDefinition>(
641 GotoDefinitionParams {
642 text_document_position_params: TextDocumentPositionParams::new(
643 server.doc_id("src/main.rs"),
644 Position::new(12, 9),
645 ),
646 work_done_progress_params: Default::default(),
647 partial_result_params: Default::default(),
648 },
649 json!([{
650 "originSelectionRange": {
651 "end": {
652 "character": 10,
653 "line": 12
654 },
655 "start": {
656 "character": 8,
657 "line": 12
658 }
659 },
660 "targetRange": {
661 "end": {
662 "character": 9,
663 "line": 3
664 },
665 "start": {
666 "character": 0,
667 "line": 2
668 }
669 },
670 "targetSelectionRange": {
671 "end": {
672 "character": 8,
673 "line": 3
674 },
675 "start": {
676 "character": 7,
677 "line": 3
678 }
679 },
680 "targetUri": "file:///[..]src/main.rs"
681 }]),
682 );
683 server.request::<GotoTypeDefinition>(
684 GotoDefinitionParams {
685 text_document_position_params: TextDocumentPositionParams::new(
686 server.doc_id("src/main.rs"),
687 Position::new(13, 9),
688 ),
689 work_done_progress_params: Default::default(),
690 partial_result_params: Default::default(),
691 },
692 json!([{
693 "originSelectionRange": {
694 "end": {
695 "character": 10,
696 "line": 13
697 },
698 "start": {
699 "character": 8,
700 "line":13
701 }
702 },
703 "targetRange": {
704 "end": {
705 "character": 9,
706 "line": 7
707 },
708 "start": {
709 "character": 0,
710 "line":6
711 }
712 },
713 "targetSelectionRange": {
714 "end": {
715 "character": 8,
716 "line": 7
717 },
718 "start": {
719 "character": 7,
720 "line": 7
721 }
722 },
723 "targetUri": "file:///[..]src/main.rs"
724 }]),
725 );
622} 726}
623 727
624#[test] 728#[test]
diff --git a/crates/test_utils/src/lib.rs b/crates/test_utils/src/lib.rs
index b13e13af2..b1e3c328f 100644
--- a/crates/test_utils/src/lib.rs
+++ b/crates/test_utils/src/lib.rs
@@ -270,7 +270,7 @@ fn parse_fixture_checks_further_indented_metadata() {
270} 270}
271 271
272#[test] 272#[test]
273fn parse_fixture_can_handle_unindented_first_line() { 273fn parse_fixture_can_handle_dedented_first_line() {
274 let fixture = "//- /lib.rs 274 let fixture = "//- /lib.rs
275 mod foo; 275 mod foo;
276 //- /foo.rs 276 //- /foo.rs
diff --git a/editors/code/package.json b/editors/code/package.json
index 12a08ba40..c6fc13519 100644
--- a/editors/code/package.json
+++ b/editors/code/package.json
@@ -300,6 +300,11 @@
300 "default": true, 300 "default": true,
301 "markdownDescription": "Check with all features (will be passed as `--all-features`)" 301 "markdownDescription": "Check with all features (will be passed as `--all-features`)"
302 }, 302 },
303 "rust-analyzer.inlayHints.enable": {
304 "type": "boolean",
305 "default": true,
306 "description": "Disable all inlay hints"
307 },
303 "rust-analyzer.inlayHints.typeHints": { 308 "rust-analyzer.inlayHints.typeHints": {
304 "type": "boolean", 309 "type": "boolean",
305 "default": true, 310 "default": true,
@@ -418,6 +423,16 @@
418 "default": { 423 "default": {
419 "/rustc/<id>": "${env:USERPROFILE}/.rustup/toolchains/<toolchain-id>/lib/rustlib/src/rust" 424 "/rustc/<id>": "${env:USERPROFILE}/.rustup/toolchains/<toolchain-id>/lib/rustlib/src/rust"
420 } 425 }
426 },
427 "rust-analyzer.debug.openDebugPane": {
428 "description": "Whether to open up the Debug Pane on debugging start.",
429 "type": "boolean",
430 "default": false
431 },
432 "rust-analyzer.debug.engineSettings": {
433 "type": "object",
434 "default": {},
435 "description": "Optional settings passed to the debug engine. Example:\n{ \"lldb\": { \"terminal\":\"external\"} }"
421 } 436 }
422 } 437 }
423 }, 438 },
@@ -589,6 +604,9 @@
589 "union": [ 604 "union": [
590 "entity.name.union" 605 "entity.name.union"
591 ], 606 ],
607 "struct": [
608 "entity.name.type.struct"
609 ],
592 "keyword.unsafe": [ 610 "keyword.unsafe": [
593 "keyword.other.unsafe" 611 "keyword.other.unsafe"
594 ], 612 ],
diff --git a/editors/code/src/cargo.ts b/editors/code/src/cargo.ts
index a328ba9bd..2a2c2e0e1 100644
--- a/editors/code/src/cargo.ts
+++ b/editors/code/src/cargo.ts
@@ -1,6 +1,9 @@
1import * as cp from 'child_process'; 1import * as cp from 'child_process';
2import * as os from 'os';
3import * as path from 'path';
2import * as readline from 'readline'; 4import * as readline from 'readline';
3import { OutputChannel } from 'vscode'; 5import { OutputChannel } from 'vscode';
6import { isValidExecutable } from './util';
4 7
5interface CompilationArtifact { 8interface CompilationArtifact {
6 fileName: string; 9 fileName: string;
@@ -10,17 +13,9 @@ interface CompilationArtifact {
10} 13}
11 14
12export class Cargo { 15export class Cargo {
13 rootFolder: string; 16 constructor(readonly rootFolder: string, readonly output: OutputChannel) { }
14 env?: Record<string, string>;
15 output: OutputChannel;
16
17 public constructor(cargoTomlFolder: string, output: OutputChannel, env: Record<string, string> | undefined = undefined) {
18 this.rootFolder = cargoTomlFolder;
19 this.output = output;
20 this.env = env;
21 }
22 17
23 public async artifactsFromArgs(cargoArgs: string[]): Promise<CompilationArtifact[]> { 18 private async artifactsFromArgs(cargoArgs: string[]): Promise<CompilationArtifact[]> {
24 const artifacts: CompilationArtifact[] = []; 19 const artifacts: CompilationArtifact[] = [];
25 20
26 try { 21 try {
@@ -37,17 +32,13 @@ export class Cargo {
37 isTest: message.profile.test 32 isTest: message.profile.test
38 }); 33 });
39 } 34 }
40 } 35 } else if (message.reason === 'compiler-message') {
41 else if (message.reason === 'compiler-message') {
42 this.output.append(message.message.rendered); 36 this.output.append(message.message.rendered);
43 } 37 }
44 }, 38 },
45 stderr => { 39 stderr => this.output.append(stderr),
46 this.output.append(stderr);
47 }
48 ); 40 );
49 } 41 } catch (err) {
50 catch (err) {
51 this.output.show(true); 42 this.output.show(true);
52 throw new Error(`Cargo invocation has failed: ${err}`); 43 throw new Error(`Cargo invocation has failed: ${err}`);
53 } 44 }
@@ -55,9 +46,8 @@ export class Cargo {
55 return artifacts; 46 return artifacts;
56 } 47 }
57 48
58 public async executableFromArgs(args: string[]): Promise<string> { 49 async executableFromArgs(args: readonly string[]): Promise<string> {
59 const cargoArgs = [...args]; // to remain args unchanged 50 const cargoArgs = [...args, "--message-format=json"];
60 cargoArgs.push("--message-format=json");
61 51
62 const artifacts = await this.artifactsFromArgs(cargoArgs); 52 const artifacts = await this.artifactsFromArgs(cargoArgs);
63 53
@@ -70,24 +60,27 @@ export class Cargo {
70 return artifacts[0].fileName; 60 return artifacts[0].fileName;
71 } 61 }
72 62
73 runCargo( 63 private runCargo(
74 cargoArgs: string[], 64 cargoArgs: string[],
75 onStdoutJson: (obj: any) => void, 65 onStdoutJson: (obj: any) => void,
76 onStderrString: (data: string) => void 66 onStderrString: (data: string) => void
77 ): Promise<number> { 67 ): Promise<number> {
78 return new Promise<number>((resolve, reject) => { 68 return new Promise((resolve, reject) => {
79 const cargo = cp.spawn('cargo', cargoArgs, { 69 let cargoPath;
70 try {
71 cargoPath = getCargoPathOrFail();
72 } catch (err) {
73 return reject(err);
74 }
75
76 const cargo = cp.spawn(cargoPath, cargoArgs, {
80 stdio: ['ignore', 'pipe', 'pipe'], 77 stdio: ['ignore', 'pipe', 'pipe'],
81 cwd: this.rootFolder, 78 cwd: this.rootFolder
82 env: this.env,
83 }); 79 });
84 80
85 cargo.on('error', err => { 81 cargo.on('error', err => reject(new Error(`could not launch cargo: ${err}`)));
86 reject(new Error(`could not launch cargo: ${err}`)); 82
87 }); 83 cargo.stderr.on('data', chunk => onStderrString(chunk.toString()));
88 cargo.stderr.on('data', chunk => {
89 onStderrString(chunk.toString());
90 });
91 84
92 const rl = readline.createInterface({ input: cargo.stdout }); 85 const rl = readline.createInterface({ input: cargo.stdout });
93 rl.on('line', line => { 86 rl.on('line', line => {
@@ -103,4 +96,28 @@ export class Cargo {
103 }); 96 });
104 }); 97 });
105 } 98 }
106} \ No newline at end of file 99}
100
101// Mirrors `ra_env::get_path_for_executable` implementation
102function getCargoPathOrFail(): string {
103 const envVar = process.env.CARGO;
104 const executableName = "cargo";
105
106 if (envVar) {
107 if (isValidExecutable(envVar)) return envVar;
108
109 throw new Error(`\`${envVar}\` environment variable points to something that's not a valid executable`);
110 }
111
112 if (isValidExecutable(executableName)) return executableName;
113
114 const standardLocation = path.join(os.homedir(), '.cargo', 'bin', executableName);
115
116 if (isValidExecutable(standardLocation)) return standardLocation;
117
118 throw new Error(
119 `Failed to find \`${executableName}\` executable. ` +
120 `Make sure \`${executableName}\` is in \`$PATH\`, ` +
121 `or set \`${envVar}\` to point to a valid executable.`
122 );
123}
diff --git a/editors/code/src/commands/runnables.ts b/editors/code/src/commands/runnables.ts
index d77e8188c..ae328d2a4 100644
--- a/editors/code/src/commands/runnables.ts
+++ b/editors/code/src/commands/runnables.ts
@@ -64,29 +64,20 @@ export function runSingle(ctx: Ctx): Cmd {
64 }; 64 };
65} 65}
66 66
67function getLldbDebugConfig(config: ra.Runnable, sourceFileMap: Record<string, string>): vscode.DebugConfiguration { 67function getLldbDebugConfig(config: ra.Runnable, executable: string, sourceFileMap?: Record<string, string>): vscode.DebugConfiguration {
68 return { 68 return {
69 type: "lldb", 69 type: "lldb",
70 request: "launch", 70 request: "launch",
71 name: config.label, 71 name: config.label,
72 cargo: { 72 program: executable,
73 args: config.args,
74 },
75 args: config.extraArgs, 73 args: config.extraArgs,
76 cwd: config.cwd, 74 cwd: config.cwd,
77 sourceMap: sourceFileMap 75 sourceMap: sourceFileMap,
76 sourceLanguages: ["rust"]
78 }; 77 };
79} 78}
80 79
81const debugOutput = vscode.window.createOutputChannel("Debug"); 80function getCppvsDebugConfig(config: ra.Runnable, executable: string, sourceFileMap?: Record<string, string>): vscode.DebugConfiguration {
82
83async function getCppvsDebugConfig(config: ra.Runnable, sourceFileMap: Record<string, string>): Promise<vscode.DebugConfiguration> {
84 debugOutput.clear();
85
86 const cargo = new Cargo(config.cwd || '.', debugOutput);
87 const executable = await cargo.executableFromArgs(config.args);
88
89 // if we are here, there were no compilation errors.
90 return { 81 return {
91 type: (os.platform() === "win32") ? "cppvsdbg" : 'cppdbg', 82 type: (os.platform() === "win32") ? "cppvsdbg" : 'cppdbg',
92 request: "launch", 83 request: "launch",
@@ -98,36 +89,62 @@ async function getCppvsDebugConfig(config: ra.Runnable, sourceFileMap: Record<st
98 }; 89 };
99} 90}
100 91
92const debugOutput = vscode.window.createOutputChannel("Debug");
93
94async function getDebugExecutable(config: ra.Runnable): Promise<string> {
95 const cargo = new Cargo(config.cwd || '.', debugOutput);
96 const executable = await cargo.executableFromArgs(config.args);
97
98 // if we are here, there were no compilation errors.
99 return executable;
100}
101
102type DebugConfigProvider = (config: ra.Runnable, executable: string, sourceFileMap?: Record<string, string>) => vscode.DebugConfiguration;
103
101export function debugSingle(ctx: Ctx): Cmd { 104export function debugSingle(ctx: Ctx): Cmd {
102 return async (config: ra.Runnable) => { 105 return async (config: ra.Runnable) => {
103 const editor = ctx.activeRustEditor; 106 const editor = ctx.activeRustEditor;
104 if (!editor) return; 107 if (!editor) return;
105 108
106 const lldbId = "vadimcn.vscode-lldb"; 109 const knownEngines: Record<string, DebugConfigProvider> = {
107 const cpptoolsId = "ms-vscode.cpptools"; 110 "vadimcn.vscode-lldb": getLldbDebugConfig,
111 "ms-vscode.cpptools": getCppvsDebugConfig
112 };
113 const debugOptions = ctx.config.debug;
108 114
109 const debugEngineId = ctx.config.debug.engine;
110 let debugEngine = null; 115 let debugEngine = null;
111 if (debugEngineId === "auto") { 116 if (debugOptions.engine === "auto") {
112 debugEngine = vscode.extensions.getExtension(lldbId); 117 for (var engineId in knownEngines) {
113 if (!debugEngine) { 118 debugEngine = vscode.extensions.getExtension(engineId);
114 debugEngine = vscode.extensions.getExtension(cpptoolsId); 119 if (debugEngine) break;
115 } 120 }
116 } 121 }
117 else { 122 else {
118 debugEngine = vscode.extensions.getExtension(debugEngineId); 123 debugEngine = vscode.extensions.getExtension(debugOptions.engine);
119 } 124 }
120 125
121 if (!debugEngine) { 126 if (!debugEngine) {
122 vscode.window.showErrorMessage(`Install [CodeLLDB](https://marketplace.visualstudio.com/items?itemName=${lldbId})` 127 vscode.window.showErrorMessage(`Install [CodeLLDB](https://marketplace.visualstudio.com/items?itemName=vadimcn.vscode-lldb)`
123 + ` or [MS C++ tools](https://marketplace.visualstudio.com/items?itemName=${cpptoolsId}) extension for debugging.`); 128 + ` or [MS C++ tools](https://marketplace.visualstudio.com/items?itemName=ms-vscode.cpptools) extension for debugging.`);
124 return; 129 return;
125 } 130 }
126 131
127 const debugConfig = lldbId === debugEngine.id 132 debugOutput.clear();
128 ? getLldbDebugConfig(config, ctx.config.debug.sourceFileMap) 133 if (ctx.config.debug.openUpDebugPane) {
129 : await getCppvsDebugConfig(config, ctx.config.debug.sourceFileMap); 134 debugOutput.show(true);
135 }
136
137 const executable = await getDebugExecutable(config);
138 const debugConfig = knownEngines[debugEngine.id](config, executable, debugOptions.sourceFileMap);
139 if (debugConfig.type in debugOptions.engineSettings) {
140 const settingsMap = (debugOptions.engineSettings as any)[debugConfig.type];
141 for (var key in settingsMap) {
142 debugConfig[key] = settingsMap[key];
143 }
144 }
130 145
146 debugOutput.appendLine("Launching debug configuration:");
147 debugOutput.appendLine(JSON.stringify(debugConfig, null, 2));
131 return vscode.debug.startDebugging(undefined, debugConfig); 148 return vscode.debug.startDebugging(undefined, debugConfig);
132 }; 149 };
133} 150}
diff --git a/editors/code/src/config.ts b/editors/code/src/config.ts
index 110e54180..be2e27aec 100644
--- a/editors/code/src/config.ts
+++ b/editors/code/src/config.ts
@@ -94,6 +94,7 @@ export class Config {
94 94
95 get inlayHints() { 95 get inlayHints() {
96 return { 96 return {
97 enable: this.get<boolean>("inlayHints.enable"),
97 typeHints: this.get<boolean>("inlayHints.typeHints"), 98 typeHints: this.get<boolean>("inlayHints.typeHints"),
98 parameterHints: this.get<boolean>("inlayHints.parameterHints"), 99 parameterHints: this.get<boolean>("inlayHints.parameterHints"),
99 chainingHints: this.get<boolean>("inlayHints.chainingHints"), 100 chainingHints: this.get<boolean>("inlayHints.chainingHints"),
@@ -108,10 +109,14 @@ export class Config {
108 } 109 }
109 110
110 get debug() { 111 get debug() {
112 // "/rustc/<id>" used by suggestions only.
113 const { ["/rustc/<id>"]: _, ...sourceFileMap } = this.get<Record<string, string>>("debug.sourceFileMap");
114
111 return { 115 return {
112 engine: this.get<string>("debug.engine"), 116 engine: this.get<string>("debug.engine"),
113 sourceFileMap: this.get<Record<string, string>>("debug.sourceFileMap"), 117 engineSettings: this.get<object>("debug.engineSettings"),
118 openUpDebugPane: this.get<boolean>("debug.openUpDebugPane"),
119 sourceFileMap: sourceFileMap,
114 }; 120 };
115 } 121 }
116
117} 122}
diff --git a/editors/code/src/inlay_hints.ts b/editors/code/src/inlay_hints.ts
index a09531797..a2b07d003 100644
--- a/editors/code/src/inlay_hints.ts
+++ b/editors/code/src/inlay_hints.ts
@@ -10,13 +10,13 @@ export function activateInlayHints(ctx: Ctx) {
10 const maybeUpdater = { 10 const maybeUpdater = {
11 updater: null as null | HintsUpdater, 11 updater: null as null | HintsUpdater,
12 async onConfigChange() { 12 async onConfigChange() {
13 if ( 13 const anyEnabled = ctx.config.inlayHints.typeHints
14 !ctx.config.inlayHints.typeHints && 14 || ctx.config.inlayHints.parameterHints
15 !ctx.config.inlayHints.parameterHints && 15 || ctx.config.inlayHints.chainingHints;
16 !ctx.config.inlayHints.chainingHints 16 const enabled = ctx.config.inlayHints.enable && anyEnabled;
17 ) { 17
18 return this.dispose(); 18 if (!enabled) return this.dispose();
19 } 19
20 await sleep(100); 20 await sleep(100);
21 if (this.updater) { 21 if (this.updater) {
22 this.updater.syncCacheAndRenderHints(); 22 this.updater.syncCacheAndRenderHints();
diff --git a/editors/code/src/main.ts b/editors/code/src/main.ts
index efd56a84b..9b020d001 100644
--- a/editors/code/src/main.ts
+++ b/editors/code/src/main.ts
@@ -8,10 +8,9 @@ import { activateInlayHints } from './inlay_hints';
8import { activateStatusDisplay } from './status_display'; 8import { activateStatusDisplay } from './status_display';
9import { Ctx } from './ctx'; 9import { Ctx } from './ctx';
10import { Config, NIGHTLY_TAG } from './config'; 10import { Config, NIGHTLY_TAG } from './config';
11import { log, assert } from './util'; 11import { log, assert, isValidExecutable } from './util';
12import { PersistentState } from './persistent_state'; 12import { PersistentState } from './persistent_state';
13import { fetchRelease, download } from './net'; 13import { fetchRelease, download } from './net';
14import { spawnSync } from 'child_process';
15import { activateTaskProvider } from './tasks'; 14import { activateTaskProvider } from './tasks';
16 15
17let ctx: Ctx | undefined; 16let ctx: Ctx | undefined;
@@ -179,10 +178,7 @@ async function bootstrapServer(config: Config, state: PersistentState): Promise<
179 178
180 log.debug("Using server binary at", path); 179 log.debug("Using server binary at", path);
181 180
182 const res = spawnSync(path, ["--version"], { encoding: 'utf8' }); 181 if (!isValidExecutable(path)) {
183 log.debug("Checked binary availability via --version", res);
184 log.debug(res, "--version output:", res.output);
185 if (res.status !== 0) {
186 throw new Error(`Failed to execute ${path} --version`); 182 throw new Error(`Failed to execute ${path} --version`);
187 } 183 }
188 184
diff --git a/editors/code/src/util.ts b/editors/code/src/util.ts
index 6f91f81d6..127a9e911 100644
--- a/editors/code/src/util.ts
+++ b/editors/code/src/util.ts
@@ -1,6 +1,7 @@
1import * as lc from "vscode-languageclient"; 1import * as lc from "vscode-languageclient";
2import * as vscode from "vscode"; 2import * as vscode from "vscode";
3import { strict as nativeAssert } from "assert"; 3import { strict as nativeAssert } from "assert";
4import { spawnSync } from "child_process";
4 5
5export function assert(condition: boolean, explanation: string): asserts condition { 6export function assert(condition: boolean, explanation: string): asserts condition {
6 try { 7 try {
@@ -82,3 +83,13 @@ export function isRustDocument(document: vscode.TextDocument): document is RustD
82export function isRustEditor(editor: vscode.TextEditor): editor is RustEditor { 83export function isRustEditor(editor: vscode.TextEditor): editor is RustEditor {
83 return isRustDocument(editor.document); 84 return isRustDocument(editor.document);
84} 85}
86
87export function isValidExecutable(path: string): boolean {
88 log.debug("Checking availability of a binary at", path);
89
90 const res = spawnSync(path, ["--version"], { encoding: 'utf8' });
91
92 log.debug(res, "--version output:", res.output);
93
94 return res.status === 0;
95}