aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
Diffstat (limited to 'crates')
-rw-r--r--crates/ra_arena/src/map.rs10
-rw-r--r--crates/ra_assists/src/handlers/fill_match_arms.rs100
-rw-r--r--crates/ra_cargo_watch/src/lib.rs405
-rw-r--r--crates/ra_flycheck/Cargo.toml (renamed from crates/ra_cargo_watch/Cargo.toml)3
-rw-r--r--crates/ra_flycheck/src/conv.rs (renamed from crates/ra_cargo_watch/src/conv.rs)0
-rw-r--r--crates/ra_flycheck/src/conv/snapshots/ra_flycheck__conv__test__snap_clippy_pass_by_ref.snap (renamed from crates/ra_cargo_watch/src/conv/snapshots/ra_cargo_watch__conv__test__snap_clippy_pass_by_ref.snap)2
-rw-r--r--crates/ra_flycheck/src/conv/snapshots/ra_flycheck__conv__test__snap_handles_macro_location.snap (renamed from crates/ra_cargo_watch/src/conv/snapshots/ra_cargo_watch__conv__test__snap_handles_macro_location.snap)2
-rw-r--r--crates/ra_flycheck/src/conv/snapshots/ra_flycheck__conv__test__snap_macro_compiler_error.snap (renamed from crates/ra_cargo_watch/src/conv/snapshots/ra_cargo_watch__conv__test__snap_macro_compiler_error.snap)2
-rw-r--r--crates/ra_flycheck/src/conv/snapshots/ra_flycheck__conv__test__snap_multi_line_fix.snap (renamed from crates/ra_cargo_watch/src/conv/snapshots/ra_cargo_watch__conv__test__snap_multi_line_fix.snap)2
-rw-r--r--crates/ra_flycheck/src/conv/snapshots/ra_flycheck__conv__test__snap_rustc_incompatible_type_for_trait.snap (renamed from crates/ra_cargo_watch/src/conv/snapshots/ra_cargo_watch__conv__test__snap_rustc_incompatible_type_for_trait.snap)2
-rw-r--r--crates/ra_flycheck/src/conv/snapshots/ra_flycheck__conv__test__snap_rustc_mismatched_type.snap (renamed from crates/ra_cargo_watch/src/conv/snapshots/ra_cargo_watch__conv__test__snap_rustc_mismatched_type.snap)2
-rw-r--r--crates/ra_flycheck/src/conv/snapshots/ra_flycheck__conv__test__snap_rustc_unused_variable.snap (renamed from crates/ra_cargo_watch/src/conv/snapshots/ra_cargo_watch__conv__test__snap_rustc_unused_variable.snap)2
-rw-r--r--crates/ra_flycheck/src/conv/snapshots/ra_flycheck__conv__test__snap_rustc_wrong_number_of_parameters.snap (renamed from crates/ra_cargo_watch/src/conv/snapshots/ra_cargo_watch__conv__test__snap_rustc_wrong_number_of_parameters.snap)2
-rw-r--r--crates/ra_flycheck/src/conv/test.rs (renamed from crates/ra_cargo_watch/src/conv/test.rs)0
-rw-r--r--crates/ra_flycheck/src/lib.rs340
-rw-r--r--crates/ra_hir_def/src/body/lower.rs54
-rw-r--r--crates/ra_hir_expand/src/proc_macro.rs27
-rw-r--r--crates/ra_hir_ty/src/display.rs44
-rw-r--r--crates/ra_hir_ty/src/infer/pat.rs11
-rw-r--r--crates/ra_hir_ty/src/tests/coercion.rs6
-rw-r--r--crates/ra_hir_ty/src/tests/patterns.rs85
-rw-r--r--crates/ra_hir_ty/src/tests/regression.rs3
-rw-r--r--crates/ra_hir_ty/src/tests/simple.rs2
-rw-r--r--crates/ra_ide/src/completion.rs16
-rw-r--r--crates/ra_ide/src/completion/complete_postfix.rs2
-rw-r--r--crates/ra_ide/src/completion/complete_record.rs411
-rw-r--r--crates/ra_ide/src/completion/complete_record_literal.rs241
-rw-r--r--crates/ra_ide/src/completion/complete_record_pattern.rs118
-rw-r--r--crates/ra_ide/src/completion/completion_context.rs8
-rw-r--r--crates/ra_ide/src/completion/presentation.rs12
-rw-r--r--crates/ra_ide/src/completion/test_utils.rs6
-rw-r--r--crates/ra_ide/src/inlay_hints.rs101
-rw-r--r--crates/ra_ide/src/lib.rs17
-rw-r--r--crates/ra_ide/src/ssr.rs116
-rw-r--r--crates/ra_ide_db/src/lib.rs12
-rw-r--r--crates/ra_proc_macro/Cargo.toml5
-rw-r--r--crates/ra_proc_macro/src/lib.rs85
-rw-r--r--crates/ra_proc_macro/src/msg.rs93
-rw-r--r--crates/ra_proc_macro/src/process.rs196
-rw-r--r--crates/ra_proc_macro/src/rpc.rs266
-rw-r--r--crates/ra_prof/src/lib.rs8
-rw-r--r--crates/ra_project_model/Cargo.toml1
-rw-r--r--crates/ra_project_model/src/cargo_workspace.rs105
-rw-r--r--crates/ra_project_model/src/lib.rs135
-rw-r--r--crates/ra_syntax/src/ast/edit.rs141
-rw-r--r--crates/ra_syntax/src/ast/expr_extensions.rs5
-rw-r--r--crates/ra_syntax/src/parsing/text_tree_sink.rs13
-rw-r--r--crates/ra_syntax/test_data/parser/ok/0065_comment_newline.rs3
-rw-r--r--crates/ra_syntax/test_data/parser/ok/0065_comment_newline.txt17
-rw-r--r--crates/ra_tt/src/lib.rs7
-rw-r--r--crates/rust-analyzer/Cargo.toml2
-rw-r--r--crates/rust-analyzer/src/bin/main.rs31
-rw-r--r--crates/rust-analyzer/src/cargo_target_spec.rs2
-rw-r--r--crates/rust-analyzer/src/cli/analysis_bench.rs4
-rw-r--r--crates/rust-analyzer/src/cli/load_cargo.rs15
-rw-r--r--crates/rust-analyzer/src/config.rs261
-rw-r--r--crates/rust-analyzer/src/conv.rs2
-rw-r--r--crates/rust-analyzer/src/feature_flags.rs77
-rw-r--r--crates/rust-analyzer/src/lib.rs4
-rw-r--r--crates/rust-analyzer/src/main_loop.rs243
-rw-r--r--crates/rust-analyzer/src/main_loop/handlers.rs117
-rw-r--r--crates/rust-analyzer/src/main_loop/subscriptions.rs2
-rw-r--r--crates/rust-analyzer/src/req.rs36
-rw-r--r--crates/rust-analyzer/src/semantic_tokens.rs6
-rw-r--r--crates/rust-analyzer/src/vfs_glob.rs10
-rw-r--r--crates/rust-analyzer/src/world.rs162
-rw-r--r--crates/rust-analyzer/tests/heavy_tests/main.rs46
-rw-r--r--crates/rust-analyzer/tests/heavy_tests/support.rs55
-rw-r--r--crates/stdx/src/lib.rs2
69 files changed, 2642 insertions, 1681 deletions
diff --git a/crates/ra_arena/src/map.rs b/crates/ra_arena/src/map.rs
index 5e764113d..0f33907c0 100644
--- a/crates/ra_arena/src/map.rs
+++ b/crates/ra_arena/src/map.rs
@@ -14,14 +14,8 @@ pub struct ArenaMap<ID, V> {
14impl<T, V> ArenaMap<Idx<T>, V> { 14impl<T, V> ArenaMap<Idx<T>, V> {
15 pub fn insert(&mut self, id: Idx<T>, t: V) { 15 pub fn insert(&mut self, id: Idx<T>, t: V) {
16 let idx = Self::to_idx(id); 16 let idx = Self::to_idx(id);
17 if self.v.capacity() <= idx { 17
18 self.v.reserve(idx + 1 - self.v.capacity()); 18 self.v.resize_with((idx + 1).max(self.v.len()), || None);
19 }
20 if self.v.len() <= idx {
21 while self.v.len() <= idx {
22 self.v.push(None);
23 }
24 }
25 self.v[idx] = Some(t); 19 self.v[idx] = Some(t);
26 } 20 }
27 21
diff --git a/crates/ra_assists/src/handlers/fill_match_arms.rs b/crates/ra_assists/src/handlers/fill_match_arms.rs
index add82e5b1..8d1af9933 100644
--- a/crates/ra_assists/src/handlers/fill_match_arms.rs
+++ b/crates/ra_assists/src/handlers/fill_match_arms.rs
@@ -1,15 +1,11 @@
1//! FIXME: write short doc here
2
3use std::iter; 1use std::iter;
4 2
5use hir::{Adt, HasSource, ModuleDef, Semantics}; 3use hir::{Adt, HasSource, ModuleDef, Semantics};
6use itertools::Itertools; 4use itertools::Itertools;
7use ra_ide_db::RootDatabase; 5use ra_ide_db::RootDatabase;
6use ra_syntax::ast::{self, make, AstNode, MatchArm, NameOwner, Pat};
8 7
9use crate::{Assist, AssistCtx, AssistId}; 8use crate::{Assist, AssistCtx, AssistId};
10use ra_syntax::ast::{self, edit::IndentLevel, make, AstNode, NameOwner};
11
12use ast::{MatchArm, Pat};
13 9
14// Assist: fill_match_arms 10// Assist: fill_match_arms
15// 11//
@@ -97,10 +93,7 @@ pub(crate) fn fill_match_arms(ctx: AssistCtx) -> Option<Assist> {
97 } 93 }
98 94
99 ctx.add_assist(AssistId("fill_match_arms"), "Fill match arms", |edit| { 95 ctx.add_assist(AssistId("fill_match_arms"), "Fill match arms", |edit| {
100 arms.extend(missing_arms); 96 let new_arm_list = match_arm_list.remove_placeholder().append_arms(missing_arms);
101
102 let indent_level = IndentLevel::from_node(match_arm_list.syntax());
103 let new_arm_list = indent_level.increase_indent(make::match_arm_list(arms));
104 97
105 edit.target(match_expr.syntax().text_range()); 98 edit.target(match_expr.syntax().text_range());
106 edit.set_cursor(expr.syntax().text_range().start()); 99 edit.set_cursor(expr.syntax().text_range().start());
@@ -655,4 +648,93 @@ mod tests {
655 "#, 648 "#,
656 ); 649 );
657 } 650 }
651
652 #[test]
653 fn fill_match_arms_preserves_comments() {
654 check_assist(
655 fill_match_arms,
656 r#"
657 enum A {
658 One,
659 Two,
660 }
661 fn foo(a: A) {
662 match a {
663 // foo bar baz<|>
664 A::One => {}
665 // This is where the rest should be
666 }
667 }
668 "#,
669 r#"
670 enum A {
671 One,
672 Two,
673 }
674 fn foo(a: A) {
675 match <|>a {
676 // foo bar baz
677 A::One => {}
678 // This is where the rest should be
679 A::Two => {}
680 }
681 }
682 "#,
683 );
684 }
685
686 #[test]
687 fn fill_match_arms_preserves_comments_empty() {
688 check_assist(
689 fill_match_arms,
690 r#"
691 enum A {
692 One,
693 Two,
694 }
695 fn foo(a: A) {
696 match a {
697 // foo bar baz<|>
698 }
699 }
700 "#,
701 r#"
702 enum A {
703 One,
704 Two,
705 }
706 fn foo(a: A) {
707 match <|>a {
708 // foo bar baz
709 A::One => {}
710 A::Two => {}
711 }
712 }
713 "#,
714 );
715 }
716
717 #[test]
718 fn fill_match_arms_placeholder() {
719 check_assist(
720 fill_match_arms,
721 r#"
722 enum A { One, Two, }
723 fn foo(a: A) {
724 match a<|> {
725 _ => (),
726 }
727 }
728 "#,
729 r#"
730 enum A { One, Two, }
731 fn foo(a: A) {
732 match <|>a {
733 A::One => {}
734 A::Two => {}
735 }
736 }
737 "#,
738 );
739 }
658} 740}
diff --git a/crates/ra_cargo_watch/src/lib.rs b/crates/ra_cargo_watch/src/lib.rs
deleted file mode 100644
index 7c525c430..000000000
--- a/crates/ra_cargo_watch/src/lib.rs
+++ /dev/null
@@ -1,405 +0,0 @@
1//! cargo_check provides the functionality needed to run `cargo check` or
2//! another compatible command (f.x. clippy) in a background thread and provide
3//! LSP diagnostics based on the output of the command.
4use cargo_metadata::Message;
5use crossbeam_channel::{never, select, unbounded, Receiver, RecvError, Sender};
6use lsp_types::{
7 CodeAction, CodeActionOrCommand, Diagnostic, Url, WorkDoneProgress, WorkDoneProgressBegin,
8 WorkDoneProgressEnd, WorkDoneProgressReport,
9};
10use std::{
11 error, fmt,
12 io::{BufRead, BufReader},
13 path::{Path, PathBuf},
14 process::{Command, Stdio},
15 thread::JoinHandle,
16 time::Instant,
17};
18
19mod conv;
20
21use crate::conv::{map_rust_diagnostic_to_lsp, MappedRustDiagnostic};
22
23pub use crate::conv::url_from_path_with_drive_lowercasing;
24
25#[derive(Clone, Debug)]
26pub struct CheckOptions {
27 pub enable: bool,
28 pub args: Vec<String>,
29 pub command: String,
30 pub all_targets: bool,
31}
32
33/// CheckWatcher wraps the shared state and communication machinery used for
34/// running `cargo check` (or other compatible command) and providing
35/// diagnostics based on the output.
36/// The spawned thread is shut down when this struct is dropped.
37#[derive(Debug)]
38pub struct CheckWatcher {
39 pub task_recv: Receiver<CheckTask>,
40 cmd_send: Option<Sender<CheckCommand>>,
41 handle: Option<JoinHandle<()>>,
42}
43
44impl CheckWatcher {
45 pub fn new(options: &CheckOptions, workspace_root: PathBuf) -> CheckWatcher {
46 let options = options.clone();
47
48 let (task_send, task_recv) = unbounded::<CheckTask>();
49 let (cmd_send, cmd_recv) = unbounded::<CheckCommand>();
50 let handle = std::thread::spawn(move || {
51 let mut check = CheckWatcherThread::new(options, workspace_root);
52 check.run(&task_send, &cmd_recv);
53 });
54 CheckWatcher { task_recv, cmd_send: Some(cmd_send), handle: Some(handle) }
55 }
56
57 /// Returns a CheckWatcher that doesn't actually do anything
58 pub fn dummy() -> CheckWatcher {
59 CheckWatcher { task_recv: never(), cmd_send: None, handle: None }
60 }
61
62 /// Schedule a re-start of the cargo check worker.
63 pub fn update(&self) {
64 if let Some(cmd_send) = &self.cmd_send {
65 cmd_send.send(CheckCommand::Update).unwrap();
66 }
67 }
68}
69
70impl std::ops::Drop for CheckWatcher {
71 fn drop(&mut self) {
72 if let Some(handle) = self.handle.take() {
73 // Take the sender out of the option
74 let cmd_send = self.cmd_send.take();
75
76 // Dropping the sender finishes the thread loop
77 drop(cmd_send);
78
79 // Join the thread, it should finish shortly. We don't really care
80 // whether it panicked, so it is safe to ignore the result
81 let _ = handle.join();
82 }
83 }
84}
85
86#[derive(Debug)]
87pub enum CheckTask {
88 /// Request a clearing of all cached diagnostics from the check watcher
89 ClearDiagnostics,
90
91 /// Request adding a diagnostic with fixes included to a file
92 AddDiagnostic { url: Url, diagnostic: Diagnostic, fixes: Vec<CodeActionOrCommand> },
93
94 /// Request check progress notification to client
95 Status(WorkDoneProgress),
96}
97
98pub enum CheckCommand {
99 /// Request re-start of check thread
100 Update,
101}
102
103struct CheckWatcherThread {
104 options: CheckOptions,
105 workspace_root: PathBuf,
106 watcher: WatchThread,
107 last_update_req: Option<Instant>,
108}
109
110impl CheckWatcherThread {
111 fn new(options: CheckOptions, workspace_root: PathBuf) -> CheckWatcherThread {
112 CheckWatcherThread {
113 options,
114 workspace_root,
115 watcher: WatchThread::dummy(),
116 last_update_req: None,
117 }
118 }
119
120 fn run(&mut self, task_send: &Sender<CheckTask>, cmd_recv: &Receiver<CheckCommand>) {
121 loop {
122 select! {
123 recv(&cmd_recv) -> cmd => match cmd {
124 Ok(cmd) => self.handle_command(cmd),
125 Err(RecvError) => {
126 // Command channel has closed, so shut down
127 break;
128 },
129 },
130 recv(self.watcher.message_recv) -> msg => match msg {
131 Ok(msg) => self.handle_message(msg, task_send),
132 Err(RecvError) => {
133 // Watcher finished, replace it with a never channel to
134 // avoid busy-waiting.
135 std::mem::replace(&mut self.watcher.message_recv, never());
136 },
137 }
138 };
139
140 if self.should_recheck() {
141 self.last_update_req.take();
142 task_send.send(CheckTask::ClearDiagnostics).unwrap();
143
144 // Replace with a dummy watcher first so we drop the original and wait for completion
145 std::mem::replace(&mut self.watcher, WatchThread::dummy());
146
147 // Then create the actual new watcher
148 self.watcher = WatchThread::new(&self.options, &self.workspace_root);
149 }
150 }
151 }
152
153 fn should_recheck(&mut self) -> bool {
154 if let Some(_last_update_req) = &self.last_update_req {
155 // We currently only request an update on save, as we need up to
156 // date source on disk for cargo check to do it's magic, so we
157 // don't really need to debounce the requests at this point.
158 return true;
159 }
160 false
161 }
162
163 fn handle_command(&mut self, cmd: CheckCommand) {
164 match cmd {
165 CheckCommand::Update => self.last_update_req = Some(Instant::now()),
166 }
167 }
168
169 fn handle_message(&self, msg: CheckEvent, task_send: &Sender<CheckTask>) {
170 match msg {
171 CheckEvent::Begin => {
172 task_send
173 .send(CheckTask::Status(WorkDoneProgress::Begin(WorkDoneProgressBegin {
174 title: "Running 'cargo check'".to_string(),
175 cancellable: Some(false),
176 message: None,
177 percentage: None,
178 })))
179 .unwrap();
180 }
181
182 CheckEvent::End => {
183 task_send
184 .send(CheckTask::Status(WorkDoneProgress::End(WorkDoneProgressEnd {
185 message: None,
186 })))
187 .unwrap();
188 }
189
190 CheckEvent::Msg(Message::CompilerArtifact(msg)) => {
191 task_send
192 .send(CheckTask::Status(WorkDoneProgress::Report(WorkDoneProgressReport {
193 cancellable: Some(false),
194 message: Some(msg.target.name),
195 percentage: None,
196 })))
197 .unwrap();
198 }
199
200 CheckEvent::Msg(Message::CompilerMessage(msg)) => {
201 let map_result = map_rust_diagnostic_to_lsp(&msg.message, &self.workspace_root);
202 if map_result.is_empty() {
203 return;
204 }
205
206 for MappedRustDiagnostic { location, diagnostic, fixes } in map_result {
207 let fixes = fixes
208 .into_iter()
209 .map(|fix| {
210 CodeAction { diagnostics: Some(vec![diagnostic.clone()]), ..fix }.into()
211 })
212 .collect();
213
214 task_send
215 .send(CheckTask::AddDiagnostic { url: location.uri, diagnostic, fixes })
216 .unwrap();
217 }
218 }
219
220 CheckEvent::Msg(Message::BuildScriptExecuted(_msg)) => {}
221 CheckEvent::Msg(Message::Unknown) => {}
222 }
223 }
224}
225
226#[derive(Debug)]
227pub struct DiagnosticWithFixes {
228 diagnostic: Diagnostic,
229 fixes: Vec<CodeAction>,
230}
231
232/// WatchThread exists to wrap around the communication needed to be able to
233/// run `cargo check` without blocking. Currently the Rust standard library
234/// doesn't provide a way to read sub-process output without blocking, so we
235/// have to wrap sub-processes output handling in a thread and pass messages
236/// back over a channel.
237/// The correct way to dispose of the thread is to drop it, on which the
238/// sub-process will be killed, and the thread will be joined.
239struct WatchThread {
240 handle: Option<JoinHandle<()>>,
241 message_recv: Receiver<CheckEvent>,
242}
243
244enum CheckEvent {
245 Begin,
246 Msg(cargo_metadata::Message),
247 End,
248}
249
250#[derive(Debug)]
251pub struct CargoError(String);
252
253impl fmt::Display for CargoError {
254 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
255 write!(f, "Cargo failed: {}", self.0)
256 }
257}
258impl error::Error for CargoError {}
259
260pub fn run_cargo(
261 args: &[String],
262 current_dir: Option<&Path>,
263 on_message: &mut dyn FnMut(cargo_metadata::Message) -> bool,
264) -> Result<(), CargoError> {
265 let mut command = Command::new("cargo");
266 if let Some(current_dir) = current_dir {
267 command.current_dir(current_dir);
268 }
269
270 let mut child = command
271 .args(args)
272 .stdout(Stdio::piped())
273 .stderr(Stdio::null())
274 .stdin(Stdio::null())
275 .spawn()
276 .expect("couldn't launch cargo");
277
278 // We manually read a line at a time, instead of using serde's
279 // stream deserializers, because the deserializer cannot recover
280 // from an error, resulting in it getting stuck, because we try to
281 // be resillient against failures.
282 //
283 // Because cargo only outputs one JSON object per line, we can
284 // simply skip a line if it doesn't parse, which just ignores any
285 // erroneus output.
286 let stdout = BufReader::new(child.stdout.take().unwrap());
287 let mut read_at_least_one_message = false;
288
289 for line in stdout.lines() {
290 let line = match line {
291 Ok(line) => line,
292 Err(err) => {
293 log::error!("Couldn't read line from cargo: {}", err);
294 continue;
295 }
296 };
297
298 let message = serde_json::from_str::<cargo_metadata::Message>(&line);
299 let message = match message {
300 Ok(message) => message,
301 Err(err) => {
302 log::error!("Invalid json from cargo check, ignoring ({}): {:?} ", err, line);
303 continue;
304 }
305 };
306
307 read_at_least_one_message = true;
308
309 if !on_message(message) {
310 break;
311 }
312 }
313
314 // It is okay to ignore the result, as it only errors if the process is already dead
315 let _ = child.kill();
316
317 let err_msg = match child.wait() {
318 Ok(exit_code) if !exit_code.success() && !read_at_least_one_message => {
319 // FIXME: Read the stderr to display the reason, see `read2()` reference in PR comment:
320 // https://github.com/rust-analyzer/rust-analyzer/pull/3632#discussion_r395605298
321 format!(
322 "the command produced no valid metadata (exit code: {:?}): cargo {}",
323 exit_code,
324 args.join(" ")
325 )
326 }
327 Err(err) => format!("io error: {:?}", err),
328 Ok(_) => return Ok(()),
329 };
330
331 Err(CargoError(err_msg))
332}
333
334impl WatchThread {
335 fn dummy() -> WatchThread {
336 WatchThread { handle: None, message_recv: never() }
337 }
338
339 fn new(options: &CheckOptions, workspace_root: &Path) -> WatchThread {
340 let mut args: Vec<String> = vec![
341 options.command.clone(),
342 "--workspace".to_string(),
343 "--message-format=json".to_string(),
344 "--manifest-path".to_string(),
345 format!("{}/Cargo.toml", workspace_root.display()),
346 ];
347 if options.all_targets {
348 args.push("--all-targets".to_string());
349 }
350 args.extend(options.args.iter().cloned());
351
352 let (message_send, message_recv) = unbounded();
353 let workspace_root = workspace_root.to_owned();
354 let handle = if options.enable {
355 Some(std::thread::spawn(move || {
356 // If we trigger an error here, we will do so in the loop instead,
357 // which will break out of the loop, and continue the shutdown
358 let _ = message_send.send(CheckEvent::Begin);
359
360 let res = run_cargo(&args, Some(&workspace_root), &mut |message| {
361 // Skip certain kinds of messages to only spend time on what's useful
362 match &message {
363 Message::CompilerArtifact(artifact) if artifact.fresh => return true,
364 Message::BuildScriptExecuted(_) => return true,
365 Message::Unknown => return true,
366 _ => {}
367 }
368
369 // if the send channel was closed, we want to shutdown
370 message_send.send(CheckEvent::Msg(message)).is_ok()
371 });
372
373 if let Err(err) = res {
374 // FIXME: make the `message_send` to be `Sender<Result<CheckEvent, CargoError>>`
375 // to display user-caused misconfiguration errors instead of just logging them here
376 log::error!("Cargo watcher failed {:?}", err);
377 }
378
379 // We can ignore any error here, as we are already in the progress
380 // of shutting down.
381 let _ = message_send.send(CheckEvent::End);
382 }))
383 } else {
384 None
385 };
386 WatchThread { handle, message_recv }
387 }
388}
389
390impl std::ops::Drop for WatchThread {
391 fn drop(&mut self) {
392 if let Some(handle) = self.handle.take() {
393 // Replace our reciever with dummy one, so we can drop and close the
394 // one actually communicating with the thread
395 let recv = std::mem::replace(&mut self.message_recv, never());
396
397 // Dropping the original reciever initiates thread sub-process shutdown
398 drop(recv);
399
400 // Join the thread, it should finish shortly. We don't really care
401 // whether it panicked, so it is safe to ignore the result
402 let _ = handle.join();
403 }
404 }
405}
diff --git a/crates/ra_cargo_watch/Cargo.toml b/crates/ra_flycheck/Cargo.toml
index 741345a21..c9a9ddc12 100644
--- a/crates/ra_cargo_watch/Cargo.toml
+++ b/crates/ra_flycheck/Cargo.toml
@@ -1,6 +1,6 @@
1[package] 1[package]
2edition = "2018" 2edition = "2018"
3name = "ra_cargo_watch" 3name = "ra_flycheck"
4version = "0.1.0" 4version = "0.1.0"
5authors = ["rust-analyzer developers"] 5authors = ["rust-analyzer developers"]
6 6
@@ -10,6 +10,7 @@ lsp-types = { version = "0.73.0", features = ["proposed"] }
10log = "0.4.8" 10log = "0.4.8"
11cargo_metadata = "0.9.1" 11cargo_metadata = "0.9.1"
12serde_json = "1.0.48" 12serde_json = "1.0.48"
13jod-thread = "0.1.1"
13 14
14[dev-dependencies] 15[dev-dependencies]
15insta = "0.15.0" 16insta = "0.15.0"
diff --git a/crates/ra_cargo_watch/src/conv.rs b/crates/ra_flycheck/src/conv.rs
index 817543deb..817543deb 100644
--- a/crates/ra_cargo_watch/src/conv.rs
+++ b/crates/ra_flycheck/src/conv.rs
diff --git a/crates/ra_cargo_watch/src/conv/snapshots/ra_cargo_watch__conv__test__snap_clippy_pass_by_ref.snap b/crates/ra_flycheck/src/conv/snapshots/ra_flycheck__conv__test__snap_clippy_pass_by_ref.snap
index a59fa84fa..4c9db0385 100644
--- a/crates/ra_cargo_watch/src/conv/snapshots/ra_cargo_watch__conv__test__snap_clippy_pass_by_ref.snap
+++ b/crates/ra_flycheck/src/conv/snapshots/ra_flycheck__conv__test__snap_clippy_pass_by_ref.snap
@@ -1,5 +1,5 @@
1--- 1---
2source: crates/ra_cargo_watch/src/conv/test.rs 2source: crates/ra_flycheck/src/conv/test.rs
3expression: diag 3expression: diag
4--- 4---
5[ 5[
diff --git a/crates/ra_cargo_watch/src/conv/snapshots/ra_cargo_watch__conv__test__snap_handles_macro_location.snap b/crates/ra_flycheck/src/conv/snapshots/ra_flycheck__conv__test__snap_handles_macro_location.snap
index 61ae0c9ae..7cde4d867 100644
--- a/crates/ra_cargo_watch/src/conv/snapshots/ra_cargo_watch__conv__test__snap_handles_macro_location.snap
+++ b/crates/ra_flycheck/src/conv/snapshots/ra_flycheck__conv__test__snap_handles_macro_location.snap
@@ -1,5 +1,5 @@
1--- 1---
2source: crates/ra_cargo_watch/src/conv/test.rs 2source: crates/ra_flycheck/src/conv/test.rs
3expression: diag 3expression: diag
4--- 4---
5[ 5[
diff --git a/crates/ra_cargo_watch/src/conv/snapshots/ra_cargo_watch__conv__test__snap_macro_compiler_error.snap b/crates/ra_flycheck/src/conv/snapshots/ra_flycheck__conv__test__snap_macro_compiler_error.snap
index 641da1a58..1cc37e087 100644
--- a/crates/ra_cargo_watch/src/conv/snapshots/ra_cargo_watch__conv__test__snap_macro_compiler_error.snap
+++ b/crates/ra_flycheck/src/conv/snapshots/ra_flycheck__conv__test__snap_macro_compiler_error.snap
@@ -1,5 +1,5 @@
1--- 1---
2source: crates/ra_cargo_watch/src/conv/test.rs 2source: crates/ra_flycheck/src/conv/test.rs
3expression: diag 3expression: diag
4--- 4---
5[ 5[
diff --git a/crates/ra_cargo_watch/src/conv/snapshots/ra_cargo_watch__conv__test__snap_multi_line_fix.snap b/crates/ra_flycheck/src/conv/snapshots/ra_flycheck__conv__test__snap_multi_line_fix.snap
index 0557a2e79..615ed8378 100644
--- a/crates/ra_cargo_watch/src/conv/snapshots/ra_cargo_watch__conv__test__snap_multi_line_fix.snap
+++ b/crates/ra_flycheck/src/conv/snapshots/ra_flycheck__conv__test__snap_multi_line_fix.snap
@@ -1,5 +1,5 @@
1--- 1---
2source: crates/ra_cargo_watch/src/conv/test.rs 2source: crates/ra_flycheck/src/conv/test.rs
3expression: diag 3expression: diag
4--- 4---
5[ 5[
diff --git a/crates/ra_cargo_watch/src/conv/snapshots/ra_cargo_watch__conv__test__snap_rustc_incompatible_type_for_trait.snap b/crates/ra_flycheck/src/conv/snapshots/ra_flycheck__conv__test__snap_rustc_incompatible_type_for_trait.snap
index 754bc33a4..0df0fce18 100644
--- a/crates/ra_cargo_watch/src/conv/snapshots/ra_cargo_watch__conv__test__snap_rustc_incompatible_type_for_trait.snap
+++ b/crates/ra_flycheck/src/conv/snapshots/ra_flycheck__conv__test__snap_rustc_incompatible_type_for_trait.snap
@@ -1,5 +1,5 @@
1--- 1---
2source: crates/ra_cargo_watch/src/conv/test.rs 2source: crates/ra_flycheck/src/conv/test.rs
3expression: diag 3expression: diag
4--- 4---
5[ 5[
diff --git a/crates/ra_cargo_watch/src/conv/snapshots/ra_cargo_watch__conv__test__snap_rustc_mismatched_type.snap b/crates/ra_flycheck/src/conv/snapshots/ra_flycheck__conv__test__snap_rustc_mismatched_type.snap
index 78b7f7cc8..28ebcb3b3 100644
--- a/crates/ra_cargo_watch/src/conv/snapshots/ra_cargo_watch__conv__test__snap_rustc_mismatched_type.snap
+++ b/crates/ra_flycheck/src/conv/snapshots/ra_flycheck__conv__test__snap_rustc_mismatched_type.snap
@@ -1,5 +1,5 @@
1--- 1---
2source: crates/ra_cargo_watch/src/conv/test.rs 2source: crates/ra_flycheck/src/conv/test.rs
3expression: diag 3expression: diag
4--- 4---
5[ 5[
diff --git a/crates/ra_cargo_watch/src/conv/snapshots/ra_cargo_watch__conv__test__snap_rustc_unused_variable.snap b/crates/ra_flycheck/src/conv/snapshots/ra_flycheck__conv__test__snap_rustc_unused_variable.snap
index 5989ed202..5e0873281 100644
--- a/crates/ra_cargo_watch/src/conv/snapshots/ra_cargo_watch__conv__test__snap_rustc_unused_variable.snap
+++ b/crates/ra_flycheck/src/conv/snapshots/ra_flycheck__conv__test__snap_rustc_unused_variable.snap
@@ -1,5 +1,5 @@
1--- 1---
2source: crates/ra_cargo_watch/src/conv/test.rs 2source: crates/ra_flycheck/src/conv/test.rs
3expression: diag 3expression: diag
4--- 4---
5[ 5[
diff --git a/crates/ra_cargo_watch/src/conv/snapshots/ra_cargo_watch__conv__test__snap_rustc_wrong_number_of_parameters.snap b/crates/ra_flycheck/src/conv/snapshots/ra_flycheck__conv__test__snap_rustc_wrong_number_of_parameters.snap
index e34b546dc..e500d3cd6 100644
--- a/crates/ra_cargo_watch/src/conv/snapshots/ra_cargo_watch__conv__test__snap_rustc_wrong_number_of_parameters.snap
+++ b/crates/ra_flycheck/src/conv/snapshots/ra_flycheck__conv__test__snap_rustc_wrong_number_of_parameters.snap
@@ -1,5 +1,5 @@
1--- 1---
2source: crates/ra_cargo_watch/src/conv/test.rs 2source: crates/ra_flycheck/src/conv/test.rs
3expression: diag 3expression: diag
4--- 4---
5[ 5[
diff --git a/crates/ra_cargo_watch/src/conv/test.rs b/crates/ra_flycheck/src/conv/test.rs
index 4e81455ca..4e81455ca 100644
--- a/crates/ra_cargo_watch/src/conv/test.rs
+++ b/crates/ra_flycheck/src/conv/test.rs
diff --git a/crates/ra_flycheck/src/lib.rs b/crates/ra_flycheck/src/lib.rs
new file mode 100644
index 000000000..b54a30ab8
--- /dev/null
+++ b/crates/ra_flycheck/src/lib.rs
@@ -0,0 +1,340 @@
1//! cargo_check provides the functionality needed to run `cargo check` or
2//! another compatible command (f.x. clippy) in a background thread and provide
3//! LSP diagnostics based on the output of the command.
4mod conv;
5
6use std::{
7 env,
8 io::{self, BufRead, BufReader},
9 path::PathBuf,
10 process::{Command, Stdio},
11 time::Instant,
12};
13
14use cargo_metadata::Message;
15use crossbeam_channel::{never, select, unbounded, Receiver, RecvError, Sender};
16use lsp_types::{
17 CodeAction, CodeActionOrCommand, Diagnostic, Url, WorkDoneProgress, WorkDoneProgressBegin,
18 WorkDoneProgressEnd, WorkDoneProgressReport,
19};
20
21use crate::conv::{map_rust_diagnostic_to_lsp, MappedRustDiagnostic};
22
23pub use crate::conv::url_from_path_with_drive_lowercasing;
24
25#[derive(Clone, Debug, PartialEq, Eq)]
26pub enum FlycheckConfig {
27 CargoCommand { command: String, all_targets: bool, extra_args: Vec<String> },
28 CustomCommand { command: String, args: Vec<String> },
29}
30
31/// Flycheck wraps the shared state and communication machinery used for
32/// running `cargo check` (or other compatible command) and providing
33/// diagnostics based on the output.
34/// The spawned thread is shut down when this struct is dropped.
35#[derive(Debug)]
36pub struct Flycheck {
37 // XXX: drop order is significant
38 cmd_send: Sender<CheckCommand>,
39 handle: jod_thread::JoinHandle<()>,
40 pub task_recv: Receiver<CheckTask>,
41}
42
43impl Flycheck {
44 pub fn new(config: FlycheckConfig, workspace_root: PathBuf) -> Flycheck {
45 let (task_send, task_recv) = unbounded::<CheckTask>();
46 let (cmd_send, cmd_recv) = unbounded::<CheckCommand>();
47 let handle = jod_thread::spawn(move || {
48 FlycheckThread::new(config, workspace_root).run(&task_send, &cmd_recv);
49 });
50 Flycheck { task_recv, cmd_send, handle }
51 }
52
53 /// Schedule a re-start of the cargo check worker.
54 pub fn update(&self) {
55 self.cmd_send.send(CheckCommand::Update).unwrap();
56 }
57}
58
59#[derive(Debug)]
60pub enum CheckTask {
61 /// Request a clearing of all cached diagnostics from the check watcher
62 ClearDiagnostics,
63
64 /// Request adding a diagnostic with fixes included to a file
65 AddDiagnostic { url: Url, diagnostic: Diagnostic, fixes: Vec<CodeActionOrCommand> },
66
67 /// Request check progress notification to client
68 Status(WorkDoneProgress),
69}
70
71pub enum CheckCommand {
72 /// Request re-start of check thread
73 Update,
74}
75
76struct FlycheckThread {
77 config: FlycheckConfig,
78 workspace_root: PathBuf,
79 last_update_req: Option<Instant>,
80 // XXX: drop order is significant
81 message_recv: Receiver<CheckEvent>,
82 /// WatchThread exists to wrap around the communication needed to be able to
83 /// run `cargo check` without blocking. Currently the Rust standard library
84 /// doesn't provide a way to read sub-process output without blocking, so we
85 /// have to wrap sub-processes output handling in a thread and pass messages
86 /// back over a channel.
87 check_process: Option<jod_thread::JoinHandle<()>>,
88}
89
90impl FlycheckThread {
91 fn new(config: FlycheckConfig, workspace_root: PathBuf) -> FlycheckThread {
92 FlycheckThread {
93 config,
94 workspace_root,
95 last_update_req: None,
96 message_recv: never(),
97 check_process: None,
98 }
99 }
100
101 fn run(&mut self, task_send: &Sender<CheckTask>, cmd_recv: &Receiver<CheckCommand>) {
102 // If we rerun the thread, we need to discard the previous check results first
103 self.clean_previous_results(task_send);
104
105 loop {
106 select! {
107 recv(&cmd_recv) -> cmd => match cmd {
108 Ok(cmd) => self.handle_command(cmd),
109 Err(RecvError) => {
110 // Command channel has closed, so shut down
111 break;
112 },
113 },
114 recv(self.message_recv) -> msg => match msg {
115 Ok(msg) => self.handle_message(msg, task_send),
116 Err(RecvError) => {
117 // Watcher finished, replace it with a never channel to
118 // avoid busy-waiting.
119 self.message_recv = never();
120 self.check_process = None;
121 },
122 }
123 };
124
125 if self.should_recheck() {
126 self.last_update_req = None;
127 task_send.send(CheckTask::ClearDiagnostics).unwrap();
128 self.restart_check_process();
129 }
130 }
131 }
132
133 fn clean_previous_results(&self, task_send: &Sender<CheckTask>) {
134 task_send.send(CheckTask::ClearDiagnostics).unwrap();
135 task_send
136 .send(CheckTask::Status(WorkDoneProgress::End(WorkDoneProgressEnd { message: None })))
137 .unwrap();
138 }
139
140 fn should_recheck(&mut self) -> bool {
141 if let Some(_last_update_req) = &self.last_update_req {
142 // We currently only request an update on save, as we need up to
143 // date source on disk for cargo check to do it's magic, so we
144 // don't really need to debounce the requests at this point.
145 return true;
146 }
147 false
148 }
149
150 fn handle_command(&mut self, cmd: CheckCommand) {
151 match cmd {
152 CheckCommand::Update => self.last_update_req = Some(Instant::now()),
153 }
154 }
155
156 fn handle_message(&self, msg: CheckEvent, task_send: &Sender<CheckTask>) {
157 match msg {
158 CheckEvent::Begin => {
159 task_send
160 .send(CheckTask::Status(WorkDoneProgress::Begin(WorkDoneProgressBegin {
161 title: "Running 'cargo check'".to_string(),
162 cancellable: Some(false),
163 message: None,
164 percentage: None,
165 })))
166 .unwrap();
167 }
168
169 CheckEvent::End => {
170 task_send
171 .send(CheckTask::Status(WorkDoneProgress::End(WorkDoneProgressEnd {
172 message: None,
173 })))
174 .unwrap();
175 }
176
177 CheckEvent::Msg(Message::CompilerArtifact(msg)) => {
178 task_send
179 .send(CheckTask::Status(WorkDoneProgress::Report(WorkDoneProgressReport {
180 cancellable: Some(false),
181 message: Some(msg.target.name),
182 percentage: None,
183 })))
184 .unwrap();
185 }
186
187 CheckEvent::Msg(Message::CompilerMessage(msg)) => {
188 let map_result = map_rust_diagnostic_to_lsp(&msg.message, &self.workspace_root);
189 if map_result.is_empty() {
190 return;
191 }
192
193 for MappedRustDiagnostic { location, diagnostic, fixes } in map_result {
194 let fixes = fixes
195 .into_iter()
196 .map(|fix| {
197 CodeAction { diagnostics: Some(vec![diagnostic.clone()]), ..fix }.into()
198 })
199 .collect();
200
201 task_send
202 .send(CheckTask::AddDiagnostic { url: location.uri, diagnostic, fixes })
203 .unwrap();
204 }
205 }
206
207 CheckEvent::Msg(Message::BuildScriptExecuted(_msg)) => {}
208 CheckEvent::Msg(Message::Unknown) => {}
209 }
210 }
211
212 fn restart_check_process(&mut self) {
213 // First, clear and cancel the old thread
214 self.message_recv = never();
215 self.check_process = None;
216
217 let mut cmd = match &self.config {
218 FlycheckConfig::CargoCommand { command, all_targets, extra_args } => {
219 let mut cmd = Command::new(cargo_binary());
220 cmd.arg(command);
221 cmd.args(&["--workspace", "--message-format=json", "--manifest-path"]);
222 cmd.arg(self.workspace_root.join("Cargo.toml"));
223 if *all_targets {
224 cmd.arg("--all-targets");
225 }
226 cmd.args(extra_args);
227 cmd
228 }
229 FlycheckConfig::CustomCommand { command, args } => {
230 let mut cmd = Command::new(command);
231 cmd.args(args);
232 cmd
233 }
234 };
235 cmd.current_dir(&self.workspace_root);
236
237 let (message_send, message_recv) = unbounded();
238 self.message_recv = message_recv;
239 self.check_process = Some(jod_thread::spawn(move || {
240 // If we trigger an error here, we will do so in the loop instead,
241 // which will break out of the loop, and continue the shutdown
242 let _ = message_send.send(CheckEvent::Begin);
243
244 let res = run_cargo(cmd, &mut |message| {
245 // Skip certain kinds of messages to only spend time on what's useful
246 match &message {
247 Message::CompilerArtifact(artifact) if artifact.fresh => return true,
248 Message::BuildScriptExecuted(_) => return true,
249 Message::Unknown => return true,
250 _ => {}
251 }
252
253 // if the send channel was closed, we want to shutdown
254 message_send.send(CheckEvent::Msg(message)).is_ok()
255 });
256
257 if let Err(err) = res {
258 // FIXME: make the `message_send` to be `Sender<Result<CheckEvent, CargoError>>`
259 // to display user-caused misconfiguration errors instead of just logging them here
260 log::error!("Cargo watcher failed {:?}", err);
261 }
262
263 // We can ignore any error here, as we are already in the progress
264 // of shutting down.
265 let _ = message_send.send(CheckEvent::End);
266 }))
267 }
268}
269
270#[derive(Debug)]
271pub struct DiagnosticWithFixes {
272 diagnostic: Diagnostic,
273 fixes: Vec<CodeAction>,
274}
275
276enum CheckEvent {
277 Begin,
278 Msg(cargo_metadata::Message),
279 End,
280}
281
282fn run_cargo(
283 mut command: Command,
284 on_message: &mut dyn FnMut(cargo_metadata::Message) -> bool,
285) -> io::Result<()> {
286 let mut child =
287 command.stdout(Stdio::piped()).stderr(Stdio::null()).stdin(Stdio::null()).spawn()?;
288
289 // We manually read a line at a time, instead of using serde's
290 // stream deserializers, because the deserializer cannot recover
291 // from an error, resulting in it getting stuck, because we try to
292 // be resillient against failures.
293 //
294 // Because cargo only outputs one JSON object per line, we can
295 // simply skip a line if it doesn't parse, which just ignores any
296 // erroneus output.
297 let stdout = BufReader::new(child.stdout.take().unwrap());
298 let mut read_at_least_one_message = false;
299
300 for line in stdout.lines() {
301 let line = line?;
302
303 let message = serde_json::from_str::<cargo_metadata::Message>(&line);
304 let message = match message {
305 Ok(message) => message,
306 Err(err) => {
307 log::error!("Invalid json from cargo check, ignoring ({}): {:?} ", err, line);
308 continue;
309 }
310 };
311
312 read_at_least_one_message = true;
313
314 if !on_message(message) {
315 break;
316 }
317 }
318
319 // It is okay to ignore the result, as it only errors if the process is already dead
320 let _ = child.kill();
321
322 let exit_status = child.wait()?;
323 if !exit_status.success() && !read_at_least_one_message {
324 // FIXME: Read the stderr to display the reason, see `read2()` reference in PR comment:
325 // https://github.com/rust-analyzer/rust-analyzer/pull/3632#discussion_r395605298
326 return Err(io::Error::new(
327 io::ErrorKind::Other,
328 format!(
329 "the command produced no valid metadata (exit code: {:?}): {:?}",
330 exit_status, command
331 ),
332 ));
333 }
334
335 Ok(())
336}
337
338fn cargo_binary() -> String {
339 env::var("CARGO").unwrap_or_else(|_| "cargo".to_string())
340}
diff --git a/crates/ra_hir_def/src/body/lower.rs b/crates/ra_hir_def/src/body/lower.rs
index e8443dde8..28c570c76 100644
--- a/crates/ra_hir_def/src/body/lower.rs
+++ b/crates/ra_hir_def/src/body/lower.rs
@@ -417,26 +417,7 @@ impl ExprCollector<'_> {
417 } 417 }
418 } 418 }
419 419
420 ast::Expr::Literal(e) => { 420 ast::Expr::Literal(e) => self.alloc_expr(Expr::Literal(e.kind().into()), syntax_ptr),
421 let lit = match e.kind() {
422 LiteralKind::IntNumber { suffix } => {
423 let known_name = suffix.and_then(|it| BuiltinInt::from_suffix(&it));
424
425 Literal::Int(Default::default(), known_name)
426 }
427 LiteralKind::FloatNumber { suffix } => {
428 let known_name = suffix.and_then(|it| BuiltinFloat::from_suffix(&it));
429
430 Literal::Float(Default::default(), known_name)
431 }
432 LiteralKind::ByteString => Literal::ByteString(Default::default()),
433 LiteralKind::String => Literal::String(Default::default()),
434 LiteralKind::Byte => Literal::Int(Default::default(), Some(BuiltinInt::U8)),
435 LiteralKind::Bool => Literal::Bool(Default::default()),
436 LiteralKind::Char => Literal::Char(Default::default()),
437 };
438 self.alloc_expr(Expr::Literal(lit), syntax_ptr)
439 }
440 ast::Expr::IndexExpr(e) => { 421 ast::Expr::IndexExpr(e) => {
441 let base = self.collect_expr_opt(e.base()); 422 let base = self.collect_expr_opt(e.base());
442 let index = self.collect_expr_opt(e.index()); 423 let index = self.collect_expr_opt(e.index());
@@ -679,10 +660,19 @@ impl ExprCollector<'_> {
679 suffix: suffix.into_iter().map(|p| self.collect_pat(p)).collect(), 660 suffix: suffix.into_iter().map(|p| self.collect_pat(p)).collect(),
680 } 661 }
681 } 662 }
663 ast::Pat::LiteralPat(lit) => {
664 if let Some(ast_lit) = lit.literal() {
665 let expr = Expr::Literal(ast_lit.kind().into());
666 let expr_ptr = AstPtr::new(&ast::Expr::Literal(ast_lit));
667 let expr_id = self.alloc_expr(expr, expr_ptr);
668 Pat::Lit(expr_id)
669 } else {
670 Pat::Missing
671 }
672 }
682 673
683 // FIXME: implement 674 // FIXME: implement
684 ast::Pat::BoxPat(_) => Pat::Missing, 675 ast::Pat::BoxPat(_) => Pat::Missing,
685 ast::Pat::LiteralPat(_) => Pat::Missing,
686 ast::Pat::RangePat(_) => Pat::Missing, 676 ast::Pat::RangePat(_) => Pat::Missing,
687 }; 677 };
688 let ptr = AstPtr::new(&pat); 678 let ptr = AstPtr::new(&pat);
@@ -741,3 +731,25 @@ impl From<ast::BinOp> for BinaryOp {
741 } 731 }
742 } 732 }
743} 733}
734
735impl From<ast::LiteralKind> for Literal {
736 fn from(ast_lit_kind: ast::LiteralKind) -> Self {
737 match ast_lit_kind {
738 LiteralKind::IntNumber { suffix } => {
739 let known_name = suffix.and_then(|it| BuiltinInt::from_suffix(&it));
740
741 Literal::Int(Default::default(), known_name)
742 }
743 LiteralKind::FloatNumber { suffix } => {
744 let known_name = suffix.and_then(|it| BuiltinFloat::from_suffix(&it));
745
746 Literal::Float(Default::default(), known_name)
747 }
748 LiteralKind::ByteString => Literal::ByteString(Default::default()),
749 LiteralKind::String => Literal::String(Default::default()),
750 LiteralKind::Byte => Literal::Int(Default::default(), Some(BuiltinInt::U8)),
751 LiteralKind::Bool(val) => Literal::Bool(val),
752 LiteralKind::Char => Literal::Char(Default::default()),
753 }
754 }
755}
diff --git a/crates/ra_hir_expand/src/proc_macro.rs b/crates/ra_hir_expand/src/proc_macro.rs
index 4d270e0de..97d1208ec 100644
--- a/crates/ra_hir_expand/src/proc_macro.rs
+++ b/crates/ra_hir_expand/src/proc_macro.rs
@@ -9,6 +9,15 @@ pub struct ProcMacroExpander {
9 proc_macro_id: ProcMacroId, 9 proc_macro_id: ProcMacroId,
10} 10}
11 11
12macro_rules! err {
13 ($fmt:literal, $($tt:tt),*) => {
14 mbe::ExpandError::ProcMacroError(tt::ExpansionError::Unknown(format!($fmt, $($tt),*)))
15 };
16 ($fmt:literal) => {
17 mbe::ExpandError::ProcMacroError(tt::ExpansionError::Unknown($fmt.to_string()))
18 }
19}
20
12impl ProcMacroExpander { 21impl ProcMacroExpander {
13 pub fn new(krate: CrateId, proc_macro_id: ProcMacroId) -> ProcMacroExpander { 22 pub fn new(krate: CrateId, proc_macro_id: ProcMacroId) -> ProcMacroExpander {
14 ProcMacroExpander { krate, proc_macro_id } 23 ProcMacroExpander { krate, proc_macro_id }
@@ -25,8 +34,24 @@ impl ProcMacroExpander {
25 .proc_macro 34 .proc_macro
26 .get(self.proc_macro_id.0 as usize) 35 .get(self.proc_macro_id.0 as usize)
27 .clone() 36 .clone()
28 .ok_or_else(|| mbe::ExpandError::ConversionError)?; 37 .ok_or_else(|| err!("No derive macro found."))?;
38
39 let tt = remove_derive_atr(tt, &proc_macro.name)
40 .ok_or_else(|| err!("Fail to remove derive for custom derive"))?;
29 41
30 proc_macro.expander.expand(&tt, None).map_err(mbe::ExpandError::from) 42 proc_macro.expander.expand(&tt, None).map_err(mbe::ExpandError::from)
31 } 43 }
32} 44}
45
46fn remove_derive_atr(tt: &tt::Subtree, _name: &str) -> Option<tt::Subtree> {
47 // FIXME: proper handle the remove derive
48 // We assume the first 2 tokens are #[derive(name)]
49 if tt.token_trees.len() > 2 {
50 let mut tt = tt.clone();
51 tt.token_trees.remove(0);
52 tt.token_trees.remove(0);
53 return Some(tt);
54 }
55
56 None
57}
diff --git a/crates/ra_hir_ty/src/display.rs b/crates/ra_hir_ty/src/display.rs
index a6ef44a31..13ecd537a 100644
--- a/crates/ra_hir_ty/src/display.rs
+++ b/crates/ra_hir_ty/src/display.rs
@@ -159,20 +159,13 @@ impl HirDisplay for ApplicationTy {
159 } 159 }
160 TypeCtor::FnDef(def) => { 160 TypeCtor::FnDef(def) => {
161 let sig = f.db.callable_item_signature(def).subst(&self.parameters); 161 let sig = f.db.callable_item_signature(def).subst(&self.parameters);
162 let name = match def { 162 match def {
163 CallableDef::FunctionId(ff) => f.db.function_data(ff).name.clone(), 163 CallableDef::FunctionId(ff) => write!(f, "fn {}", f.db.function_data(ff).name)?,
164 CallableDef::StructId(s) => f.db.struct_data(s).name.clone(), 164 CallableDef::StructId(s) => write!(f, "{}", f.db.struct_data(s).name)?,
165 CallableDef::EnumVariantId(e) => { 165 CallableDef::EnumVariantId(e) => {
166 let enum_data = f.db.enum_data(e.parent); 166 write!(f, "{}", f.db.enum_data(e.parent).variants[e.local_id].name)?
167 enum_data.variants[e.local_id].name.clone()
168 } 167 }
169 }; 168 };
170 match def {
171 CallableDef::FunctionId(_) => write!(f, "fn {}", name)?,
172 CallableDef::StructId(_) | CallableDef::EnumVariantId(_) => {
173 write!(f, "{}", name)?
174 }
175 }
176 if self.parameters.len() > 0 { 169 if self.parameters.len() > 0 {
177 let generics = generics(f.db.upcast(), def.into()); 170 let generics = generics(f.db.upcast(), def.into());
178 let (parent_params, self_param, type_params, _impl_trait_params) = 171 let (parent_params, self_param, type_params, _impl_trait_params) =
@@ -197,8 +190,6 @@ impl HirDisplay for ApplicationTy {
197 }; 190 };
198 write!(f, "{}", name)?; 191 write!(f, "{}", name)?;
199 if self.parameters.len() > 0 { 192 if self.parameters.len() > 0 {
200 write!(f, "<")?;
201
202 let mut non_default_parameters = Vec::with_capacity(self.parameters.len()); 193 let mut non_default_parameters = Vec::with_capacity(self.parameters.len());
203 let parameters_to_write = if f.omit_verbose_types() { 194 let parameters_to_write = if f.omit_verbose_types() {
204 match self 195 match self
@@ -207,8 +198,8 @@ impl HirDisplay for ApplicationTy {
207 .map(|generic_def_id| f.db.generic_defaults(generic_def_id)) 198 .map(|generic_def_id| f.db.generic_defaults(generic_def_id))
208 .filter(|defaults| !defaults.is_empty()) 199 .filter(|defaults| !defaults.is_empty())
209 { 200 {
210 Option::None => self.parameters.0.as_ref(), 201 None => self.parameters.0.as_ref(),
211 Option::Some(default_parameters) => { 202 Some(default_parameters) => {
212 for (i, parameter) in self.parameters.iter().enumerate() { 203 for (i, parameter) in self.parameters.iter().enumerate() {
213 match (parameter, default_parameters.get(i)) { 204 match (parameter, default_parameters.get(i)) {
214 (&Ty::Unknown, _) | (_, None) => { 205 (&Ty::Unknown, _) | (_, None) => {
@@ -228,7 +219,7 @@ impl HirDisplay for ApplicationTy {
228 } else { 219 } else {
229 self.parameters.0.as_ref() 220 self.parameters.0.as_ref()
230 }; 221 };
231 222 write!(f, "<")?;
232 f.write_joined(parameters_to_write, ", ")?; 223 f.write_joined(parameters_to_write, ", ")?;
233 write!(f, ">")?; 224 write!(f, ">")?;
234 } 225 }
@@ -238,9 +229,9 @@ impl HirDisplay for ApplicationTy {
238 AssocContainerId::TraitId(it) => it, 229 AssocContainerId::TraitId(it) => it,
239 _ => panic!("not an associated type"), 230 _ => panic!("not an associated type"),
240 }; 231 };
241 let trait_name = f.db.trait_data(trait_).name.clone(); 232 let trait_ = f.db.trait_data(trait_);
242 let name = f.db.type_alias_data(type_alias).name.clone(); 233 let type_alias = f.db.type_alias_data(type_alias);
243 write!(f, "{}::{}", trait_name, name)?; 234 write!(f, "{}::{}", trait_.name, type_alias.name)?;
244 if self.parameters.len() > 0 { 235 if self.parameters.len() > 0 {
245 write!(f, "<")?; 236 write!(f, "<")?;
246 f.write_joined(&*self.parameters.0, ", ")?; 237 f.write_joined(&*self.parameters.0, ", ")?;
@@ -273,8 +264,8 @@ impl HirDisplay for ProjectionTy {
273 return write!(f, "{}", TYPE_HINT_TRUNCATION); 264 return write!(f, "{}", TYPE_HINT_TRUNCATION);
274 } 265 }
275 266
276 let trait_name = f.db.trait_data(self.trait_(f.db)).name.clone(); 267 let trait_ = f.db.trait_data(self.trait_(f.db));
277 write!(f, "<{} as {}", self.parameters[0].display(f.db), trait_name,)?; 268 write!(f, "<{} as {}", self.parameters[0].display(f.db), trait_.name)?;
278 if self.parameters.len() > 1 { 269 if self.parameters.len() > 1 {
279 write!(f, "<")?; 270 write!(f, "<")?;
280 f.write_joined(&self.parameters[1..], ", ")?; 271 f.write_joined(&self.parameters[1..], ", ")?;
@@ -319,7 +310,7 @@ impl HirDisplay for Ty {
319 Ty::Opaque(_) => write!(f, "impl ")?, 310 Ty::Opaque(_) => write!(f, "impl ")?,
320 _ => unreachable!(), 311 _ => unreachable!(),
321 }; 312 };
322 write_bounds_like_dyn_trait(&predicates, f)?; 313 write_bounds_like_dyn_trait(predicates, f)?;
323 } 314 }
324 Ty::Unknown => write!(f, "{{unknown}}")?, 315 Ty::Unknown => write!(f, "{{unknown}}")?,
325 Ty::Infer(..) => write!(f, "_")?, 316 Ty::Infer(..) => write!(f, "_")?,
@@ -352,7 +343,7 @@ fn write_bounds_like_dyn_trait(
352 // We assume that the self type is $0 (i.e. the 343 // We assume that the self type is $0 (i.e. the
353 // existential) here, which is the only thing that's 344 // existential) here, which is the only thing that's
354 // possible in actual Rust, and hence don't print it 345 // possible in actual Rust, and hence don't print it
355 write!(f, "{}", f.db.trait_data(trait_ref.trait_).name.clone())?; 346 write!(f, "{}", f.db.trait_data(trait_ref.trait_).name)?;
356 if trait_ref.substs.len() > 1 { 347 if trait_ref.substs.len() > 1 {
357 write!(f, "<")?; 348 write!(f, "<")?;
358 f.write_joined(&trait_ref.substs[1..], ", ")?; 349 f.write_joined(&trait_ref.substs[1..], ", ")?;
@@ -369,9 +360,8 @@ fn write_bounds_like_dyn_trait(
369 write!(f, "<")?; 360 write!(f, "<")?;
370 angle_open = true; 361 angle_open = true;
371 } 362 }
372 let name = 363 let type_alias = f.db.type_alias_data(projection_pred.projection_ty.associated_ty);
373 f.db.type_alias_data(projection_pred.projection_ty.associated_ty).name.clone(); 364 write!(f, "{} = ", type_alias.name)?;
374 write!(f, "{} = ", name)?;
375 projection_pred.ty.hir_fmt(f)?; 365 projection_pred.ty.hir_fmt(f)?;
376 } 366 }
377 GenericPredicate::Error => { 367 GenericPredicate::Error => {
@@ -405,7 +395,7 @@ impl TraitRef {
405 } else { 395 } else {
406 write!(f, ": ")?; 396 write!(f, ": ")?;
407 } 397 }
408 write!(f, "{}", f.db.trait_data(self.trait_).name.clone())?; 398 write!(f, "{}", f.db.trait_data(self.trait_).name)?;
409 if self.substs.len() > 1 { 399 if self.substs.len() > 1 {
410 write!(f, "<")?; 400 write!(f, "<")?;
411 f.write_joined(&self.substs[1..], ", ")?; 401 f.write_joined(&self.substs[1..], ", ")?;
diff --git a/crates/ra_hir_ty/src/infer/pat.rs b/crates/ra_hir_ty/src/infer/pat.rs
index baed6225b..86acd27f8 100644
--- a/crates/ra_hir_ty/src/infer/pat.rs
+++ b/crates/ra_hir_ty/src/infer/pat.rs
@@ -11,7 +11,7 @@ use hir_def::{
11use hir_expand::name::Name; 11use hir_expand::name::Name;
12use test_utils::tested_by; 12use test_utils::tested_by;
13 13
14use super::{BindingMode, InferenceContext}; 14use super::{BindingMode, Expectation, InferenceContext};
15use crate::{utils::variant_data, Substs, Ty, TypeCtor}; 15use crate::{utils::variant_data, Substs, Ty, TypeCtor};
16 16
17impl<'a> InferenceContext<'a> { 17impl<'a> InferenceContext<'a> {
@@ -198,7 +198,14 @@ impl<'a> InferenceContext<'a> {
198 198
199 Ty::apply_one(container_ty, elem_ty) 199 Ty::apply_one(container_ty, elem_ty)
200 } 200 }
201 _ => Ty::Unknown, 201 Pat::Wild => expected.clone(),
202 Pat::Range { start, end } => {
203 let start_ty = self.infer_expr(*start, &Expectation::has_type(expected.clone()));
204 let end_ty = self.infer_expr(*end, &Expectation::has_type(start_ty));
205 end_ty
206 }
207 Pat::Lit(expr) => self.infer_expr(*expr, &Expectation::has_type(expected.clone())),
208 Pat::Missing => Ty::Unknown,
202 }; 209 };
203 // use a new type variable if we got Ty::Unknown here 210 // use a new type variable if we got Ty::Unknown here
204 let ty = self.insert_type_vars_shallow(ty); 211 let ty = self.insert_type_vars_shallow(ty);
diff --git a/crates/ra_hir_ty/src/tests/coercion.rs b/crates/ra_hir_ty/src/tests/coercion.rs
index 1e303f5ce..3e3d55c04 100644
--- a/crates/ra_hir_ty/src/tests/coercion.rs
+++ b/crates/ra_hir_ty/src/tests/coercion.rs
@@ -275,12 +275,14 @@ fn test(i: i32) {
275 [70; 147) 'match ... }': &[i32] 275 [70; 147) 'match ... }': &[i32]
276 [76; 77) 'i': i32 276 [76; 77) 'i': i32
277 [88; 89) '2': i32 277 [88; 89) '2': i32
278 [88; 89) '2': i32
278 [93; 96) 'foo': fn foo<i32>(&[i32]) -> &[i32] 279 [93; 96) 'foo': fn foo<i32>(&[i32]) -> &[i32]
279 [93; 102) 'foo(&[2])': &[i32] 280 [93; 102) 'foo(&[2])': &[i32]
280 [97; 101) '&[2]': &[i32; _] 281 [97; 101) '&[2]': &[i32; _]
281 [98; 101) '[2]': [i32; _] 282 [98; 101) '[2]': [i32; _]
282 [99; 100) '2': i32 283 [99; 100) '2': i32
283 [112; 113) '1': i32 284 [112; 113) '1': i32
285 [112; 113) '1': i32
284 [117; 121) '&[1]': &[i32; _] 286 [117; 121) '&[1]': &[i32; _]
285 [118; 121) '[1]': [i32; _] 287 [118; 121) '[1]': [i32; _]
286 [119; 120) '1': i32 288 [119; 120) '1': i32
@@ -316,10 +318,12 @@ fn test(i: i32) {
316 [70; 147) 'match ... }': &[i32] 318 [70; 147) 'match ... }': &[i32]
317 [76; 77) 'i': i32 319 [76; 77) 'i': i32
318 [88; 89) '1': i32 320 [88; 89) '1': i32
321 [88; 89) '1': i32
319 [93; 97) '&[1]': &[i32; _] 322 [93; 97) '&[1]': &[i32; _]
320 [94; 97) '[1]': [i32; _] 323 [94; 97) '[1]': [i32; _]
321 [95; 96) '1': i32 324 [95; 96) '1': i32
322 [107; 108) '2': i32 325 [107; 108) '2': i32
326 [107; 108) '2': i32
323 [112; 115) 'foo': fn foo<i32>(&[i32]) -> &[i32] 327 [112; 115) 'foo': fn foo<i32>(&[i32]) -> &[i32]
324 [112; 121) 'foo(&[2])': &[i32] 328 [112; 121) 'foo(&[2])': &[i32]
325 [116; 120) '&[2]': &[i32; _] 329 [116; 120) '&[2]': &[i32; _]
@@ -357,9 +361,11 @@ fn test() {
357 [45; 142) 'match ... }': *const i32 361 [45; 142) 'match ... }': *const i32
358 [51; 52) '1': i32 362 [51; 52) '1': i32
359 [63; 64) '1': i32 363 [63; 64) '1': i32
364 [63; 64) '1': i32
360 [68; 69) 't': &mut i32 365 [68; 69) 't': &mut i32
361 [68; 81) 't as *mut i32': *mut i32 366 [68; 81) 't as *mut i32': *mut i32
362 [91; 92) '2': i32 367 [91; 92) '2': i32
368 [91; 92) '2': i32
363 [96; 97) 't': &mut i32 369 [96; 97) 't': &mut i32
364 [96; 105) 't as &i32': &i32 370 [96; 105) 't as &i32': &i32
365 [115; 116) '_': i32 371 [115; 116) '_': i32
diff --git a/crates/ra_hir_ty/src/tests/patterns.rs b/crates/ra_hir_ty/src/tests/patterns.rs
index 76aa32024..6e5d2247c 100644
--- a/crates/ra_hir_ty/src/tests/patterns.rs
+++ b/crates/ra_hir_ty/src/tests/patterns.rs
@@ -82,6 +82,90 @@ fn test(x: &i32) {
82} 82}
83 83
84#[test] 84#[test]
85fn infer_literal_pattern() {
86 assert_snapshot!(
87 infer_with_mismatches(r#"
88fn any<T>() -> T { loop {} }
89fn test(x: &i32) {
90 if let "foo" = any() {}
91 if let 1 = any() {}
92 if let 1u32 = any() {}
93 if let 1f32 = any() {}
94 if let 1.0 = any() {}
95 if let true = any() {}
96}
97"#, true),
98 @r###"
99 [18; 29) '{ loop {} }': T
100 [20; 27) 'loop {}': !
101 [25; 27) '{}': ()
102 [38; 39) 'x': &i32
103 [47; 209) '{ ...) {} }': ()
104 [53; 76) 'if let...y() {}': ()
105 [60; 65) '"foo"': &str
106 [60; 65) '"foo"': &str
107 [68; 71) 'any': fn any<&str>() -> &str
108 [68; 73) 'any()': &str
109 [74; 76) '{}': ()
110 [81; 100) 'if let...y() {}': ()
111 [88; 89) '1': i32
112 [88; 89) '1': i32
113 [92; 95) 'any': fn any<i32>() -> i32
114 [92; 97) 'any()': i32
115 [98; 100) '{}': ()
116 [105; 127) 'if let...y() {}': ()
117 [112; 116) '1u32': u32
118 [112; 116) '1u32': u32
119 [119; 122) 'any': fn any<u32>() -> u32
120 [119; 124) 'any()': u32
121 [125; 127) '{}': ()
122 [132; 154) 'if let...y() {}': ()
123 [139; 143) '1f32': f32
124 [139; 143) '1f32': f32
125 [146; 149) 'any': fn any<f32>() -> f32
126 [146; 151) 'any()': f32
127 [152; 154) '{}': ()
128 [159; 180) 'if let...y() {}': ()
129 [166; 169) '1.0': f64
130 [166; 169) '1.0': f64
131 [172; 175) 'any': fn any<f64>() -> f64
132 [172; 177) 'any()': f64
133 [178; 180) '{}': ()
134 [185; 207) 'if let...y() {}': ()
135 [192; 196) 'true': bool
136 [192; 196) 'true': bool
137 [199; 202) 'any': fn any<bool>() -> bool
138 [199; 204) 'any()': bool
139 [205; 207) '{}': ()
140 "###
141 );
142}
143
144#[test]
145fn infer_range_pattern() {
146 assert_snapshot!(
147 infer_with_mismatches(r#"
148fn test(x: &i32) {
149 if let 1..76 = 2u32 {}
150 if let 1..=76 = 2u32 {}
151}
152"#, true),
153 @r###"
154 [9; 10) 'x': &i32
155 [18; 76) '{ ...2 {} }': ()
156 [24; 46) 'if let...u32 {}': ()
157 [31; 36) '1..76': u32
158 [39; 43) '2u32': u32
159 [44; 46) '{}': ()
160 [51; 74) 'if let...u32 {}': ()
161 [58; 64) '1..=76': u32
162 [67; 71) '2u32': u32
163 [72; 74) '{}': ()
164 "###
165 );
166}
167
168#[test]
85fn infer_pattern_match_ergonomics() { 169fn infer_pattern_match_ergonomics() {
86 assert_snapshot!( 170 assert_snapshot!(
87 infer(r#" 171 infer(r#"
@@ -212,6 +296,7 @@ fn test() {
212 [59; 62) 'arr': [f64; _] 296 [59; 62) 'arr': [f64; _]
213 [73; 81) '[1.0, a]': [f64; _] 297 [73; 81) '[1.0, a]': [f64; _]
214 [74; 77) '1.0': f64 298 [74; 77) '1.0': f64
299 [74; 77) '1.0': f64
215 [79; 80) 'a': f64 300 [79; 80) 'a': f64
216 [85; 111) '{ ... }': () 301 [85; 111) '{ ... }': ()
217 [99; 100) 'a': f64 302 [99; 100) 'a': f64
diff --git a/crates/ra_hir_ty/src/tests/regression.rs b/crates/ra_hir_ty/src/tests/regression.rs
index a02e3ee05..2ee9b8f10 100644
--- a/crates/ra_hir_ty/src/tests/regression.rs
+++ b/crates/ra_hir_ty/src/tests/regression.rs
@@ -206,7 +206,8 @@ pub fn compute() {
206 [24; 106) 'match ... }': () 206 [24; 106) 'match ... }': ()
207 [30; 37) 'nope!()': {unknown} 207 [30; 37) 'nope!()': {unknown}
208 [48; 94) 'SizeSk...tail }': {unknown} 208 [48; 94) 'SizeSk...tail }': {unknown}
209 [82; 86) 'true': {unknown} 209 [82; 86) 'true': bool
210 [82; 86) 'true': bool
210 [88; 92) 'tail': {unknown} 211 [88; 92) 'tail': {unknown}
211 [98; 100) '{}': () 212 [98; 100) '{}': ()
212 "### 213 "###
diff --git a/crates/ra_hir_ty/src/tests/simple.rs b/crates/ra_hir_ty/src/tests/simple.rs
index c140bd513..a600b947d 100644
--- a/crates/ra_hir_ty/src/tests/simple.rs
+++ b/crates/ra_hir_ty/src/tests/simple.rs
@@ -948,6 +948,7 @@ fn foo() {
948 [165; 247) 'match ... }': i32 948 [165; 247) 'match ... }': i32
949 [171; 175) 'true': bool 949 [171; 175) 'true': bool
950 [186; 190) 'true': bool 950 [186; 190) 'true': bool
951 [186; 190) 'true': bool
951 [194; 195) '3': i32 952 [194; 195) '3': i32
952 [205; 206) '_': bool 953 [205; 206) '_': bool
953 [210; 241) '{ ... }': ! 954 [210; 241) '{ ... }': !
@@ -956,6 +957,7 @@ fn foo() {
956 [263; 320) 'match ... }': i32 957 [263; 320) 'match ... }': i32
957 [269; 273) 'true': bool 958 [269; 273) 'true': bool
958 [284; 288) 'true': bool 959 [284; 288) 'true': bool
960 [284; 288) 'true': bool
959 [292; 293) '4': i32 961 [292; 293) '4': i32
960 [303; 304) '_': bool 962 [303; 304) '_': bool
961 [308; 314) 'return': ! 963 [308; 314) 'return': !
diff --git a/crates/ra_ide/src/completion.rs b/crates/ra_ide/src/completion.rs
index cd0757be5..93157bbba 100644
--- a/crates/ra_ide/src/completion.rs
+++ b/crates/ra_ide/src/completion.rs
@@ -5,8 +5,7 @@ mod completion_context;
5mod presentation; 5mod presentation;
6 6
7mod complete_dot; 7mod complete_dot;
8mod complete_record_literal; 8mod complete_record;
9mod complete_record_pattern;
10mod complete_pattern; 9mod complete_pattern;
11mod complete_fn_param; 10mod complete_fn_param;
12mod complete_keyword; 11mod complete_keyword;
@@ -34,15 +33,15 @@ pub use crate::completion::completion_item::{
34}; 33};
35 34
36#[derive(Clone, Debug, PartialEq, Eq)] 35#[derive(Clone, Debug, PartialEq, Eq)]
37pub struct CompletionOptions { 36pub struct CompletionConfig {
38 pub enable_postfix_completions: bool, 37 pub enable_postfix_completions: bool,
39 pub add_call_parenthesis: bool, 38 pub add_call_parenthesis: bool,
40 pub add_call_argument_snippets: bool, 39 pub add_call_argument_snippets: bool,
41} 40}
42 41
43impl Default for CompletionOptions { 42impl Default for CompletionConfig {
44 fn default() -> Self { 43 fn default() -> Self {
45 CompletionOptions { 44 CompletionConfig {
46 enable_postfix_completions: true, 45 enable_postfix_completions: true,
47 add_call_parenthesis: true, 46 add_call_parenthesis: true,
48 add_call_argument_snippets: true, 47 add_call_argument_snippets: true,
@@ -75,9 +74,9 @@ impl Default for CompletionOptions {
75pub(crate) fn completions( 74pub(crate) fn completions(
76 db: &RootDatabase, 75 db: &RootDatabase,
77 position: FilePosition, 76 position: FilePosition,
78 options: &CompletionOptions, 77 config: &CompletionConfig,
79) -> Option<Completions> { 78) -> Option<Completions> {
80 let ctx = CompletionContext::new(db, position, options)?; 79 let ctx = CompletionContext::new(db, position, config)?;
81 80
82 let mut acc = Completions::default(); 81 let mut acc = Completions::default();
83 82
@@ -89,8 +88,7 @@ pub(crate) fn completions(
89 complete_path::complete_path(&mut acc, &ctx); 88 complete_path::complete_path(&mut acc, &ctx);
90 complete_scope::complete_scope(&mut acc, &ctx); 89 complete_scope::complete_scope(&mut acc, &ctx);
91 complete_dot::complete_dot(&mut acc, &ctx); 90 complete_dot::complete_dot(&mut acc, &ctx);
92 complete_record_literal::complete_record_literal(&mut acc, &ctx); 91 complete_record::complete_record(&mut acc, &ctx);
93 complete_record_pattern::complete_record_pattern(&mut acc, &ctx);
94 complete_pattern::complete_pattern(&mut acc, &ctx); 92 complete_pattern::complete_pattern(&mut acc, &ctx);
95 complete_postfix::complete_postfix(&mut acc, &ctx); 93 complete_postfix::complete_postfix(&mut acc, &ctx);
96 complete_macro_in_item_position::complete_macro_in_item_position(&mut acc, &ctx); 94 complete_macro_in_item_position::complete_macro_in_item_position(&mut acc, &ctx);
diff --git a/crates/ra_ide/src/completion/complete_postfix.rs b/crates/ra_ide/src/completion/complete_postfix.rs
index 0a00054b2..29c2881c6 100644
--- a/crates/ra_ide/src/completion/complete_postfix.rs
+++ b/crates/ra_ide/src/completion/complete_postfix.rs
@@ -15,7 +15,7 @@ use crate::{
15}; 15};
16 16
17pub(super) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) { 17pub(super) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) {
18 if !ctx.options.enable_postfix_completions { 18 if !ctx.config.enable_postfix_completions {
19 return; 19 return;
20 } 20 }
21 21
diff --git a/crates/ra_ide/src/completion/complete_record.rs b/crates/ra_ide/src/completion/complete_record.rs
new file mode 100644
index 000000000..01dd8c6db
--- /dev/null
+++ b/crates/ra_ide/src/completion/complete_record.rs
@@ -0,0 +1,411 @@
1//! Complete fields in record literals and patterns.
2use crate::completion::{CompletionContext, Completions};
3use ra_syntax::{ast, ast::NameOwner, SmolStr};
4
5pub(super) fn complete_record(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> {
6 let (ty, variant, already_present_fields) =
7 match (ctx.record_lit_pat.as_ref(), ctx.record_lit_syntax.as_ref()) {
8 (None, None) => return None,
9 (Some(_), Some(_)) => panic!("A record cannot be both a literal and a pattern"),
10 (Some(record_pat), _) => (
11 ctx.sema.type_of_pat(&record_pat.clone().into())?,
12 ctx.sema.resolve_record_pattern(record_pat)?,
13 pattern_ascribed_fields(record_pat),
14 ),
15 (_, Some(record_lit)) => (
16 ctx.sema.type_of_expr(&record_lit.clone().into())?,
17 ctx.sema.resolve_record_literal(record_lit)?,
18 literal_ascribed_fields(record_lit),
19 ),
20 };
21
22 for (field, field_ty) in ty.variant_fields(ctx.db, variant).into_iter().filter(|(field, _)| {
23 // FIXME: already_present_names better be `Vec<hir::Name>`
24 !already_present_fields.contains(&SmolStr::from(field.name(ctx.db).to_string()))
25 }) {
26 acc.add_field(ctx, field, &field_ty);
27 }
28 Some(())
29}
30
31fn literal_ascribed_fields(record_lit: &ast::RecordLit) -> Vec<SmolStr> {
32 record_lit
33 .record_field_list()
34 .map(|field_list| field_list.fields())
35 .map(|fields| {
36 fields
37 .into_iter()
38 .filter_map(|field| field.name_ref())
39 .map(|name_ref| name_ref.text().clone())
40 .collect()
41 })
42 .unwrap_or_default()
43}
44
45fn pattern_ascribed_fields(record_pat: &ast::RecordPat) -> Vec<SmolStr> {
46 record_pat
47 .record_field_pat_list()
48 .map(|pat_list| {
49 pat_list
50 .record_field_pats()
51 .filter_map(|fild_pat| fild_pat.name())
52 .chain(pat_list.bind_pats().filter_map(|bind_pat| bind_pat.name()))
53 .map(|name| name.text().clone())
54 .collect()
55 })
56 .unwrap_or_default()
57}
58
59#[cfg(test)]
60mod tests {
61 mod record_lit_tests {
62 use crate::completion::{test_utils::do_completion, CompletionItem, CompletionKind};
63 use insta::assert_debug_snapshot;
64
65 fn complete(code: &str) -> Vec<CompletionItem> {
66 do_completion(code, CompletionKind::Reference)
67 }
68
69 #[test]
70 fn test_record_pattern_field() {
71 let completions = complete(
72 r"
73 struct S { foo: u32 }
74
75 fn process(f: S) {
76 match f {
77 S { f<|>: 92 } => (),
78 }
79 }
80 ",
81 );
82 assert_debug_snapshot!(completions, @r###"
83 [
84 CompletionItem {
85 label: "foo",
86 source_range: [117; 118),
87 delete: [117; 118),
88 insert: "foo",
89 kind: Field,
90 detail: "u32",
91 },
92 ]
93 "###);
94 }
95
96 #[test]
97 fn test_record_pattern_enum_variant() {
98 let completions = complete(
99 r"
100 enum E {
101 S { foo: u32, bar: () }
102 }
103
104 fn process(e: E) {
105 match e {
106 E::S { <|> } => (),
107 }
108 }
109 ",
110 );
111 assert_debug_snapshot!(completions, @r###"
112 [
113 CompletionItem {
114 label: "bar",
115 source_range: [161; 161),
116 delete: [161; 161),
117 insert: "bar",
118 kind: Field,
119 detail: "()",
120 },
121 CompletionItem {
122 label: "foo",
123 source_range: [161; 161),
124 delete: [161; 161),
125 insert: "foo",
126 kind: Field,
127 detail: "u32",
128 },
129 ]
130 "###);
131 }
132
133 #[test]
134 fn test_record_pattern_field_in_simple_macro() {
135 let completions = complete(
136 r"
137 macro_rules! m { ($e:expr) => { $e } }
138 struct S { foo: u32 }
139
140 fn process(f: S) {
141 m!(match f {
142 S { f<|>: 92 } => (),
143 })
144 }
145 ",
146 );
147 assert_debug_snapshot!(completions, @r###"
148 [
149 CompletionItem {
150 label: "foo",
151 source_range: [171; 172),
152 delete: [171; 172),
153 insert: "foo",
154 kind: Field,
155 detail: "u32",
156 },
157 ]
158 "###);
159 }
160
161 #[test]
162 fn only_missing_fields_are_completed_in_destruct_pats() {
163 let completions = complete(
164 r"
165 struct S {
166 foo1: u32,
167 foo2: u32,
168 bar: u32,
169 baz: u32,
170 }
171
172 fn main() {
173 let s = S {
174 foo1: 1,
175 foo2: 2,
176 bar: 3,
177 baz: 4,
178 };
179 if let S { foo1, foo2: a, <|> } = s {}
180 }
181 ",
182 );
183 assert_debug_snapshot!(completions, @r###"
184 [
185 CompletionItem {
186 label: "bar",
187 source_range: [372; 372),
188 delete: [372; 372),
189 insert: "bar",
190 kind: Field,
191 detail: "u32",
192 },
193 CompletionItem {
194 label: "baz",
195 source_range: [372; 372),
196 delete: [372; 372),
197 insert: "baz",
198 kind: Field,
199 detail: "u32",
200 },
201 ]
202 "###);
203 }
204 }
205
206 mod record_pat_tests {
207 use crate::completion::{test_utils::do_completion, CompletionItem, CompletionKind};
208 use insta::assert_debug_snapshot;
209
210 fn complete(code: &str) -> Vec<CompletionItem> {
211 do_completion(code, CompletionKind::Reference)
212 }
213
214 #[test]
215 fn test_record_literal_deprecated_field() {
216 let completions = complete(
217 r"
218 struct A {
219 #[deprecated]
220 the_field: u32,
221 }
222 fn foo() {
223 A { the<|> }
224 }
225 ",
226 );
227 assert_debug_snapshot!(completions, @r###"
228 [
229 CompletionItem {
230 label: "the_field",
231 source_range: [142; 145),
232 delete: [142; 145),
233 insert: "the_field",
234 kind: Field,
235 detail: "u32",
236 deprecated: true,
237 },
238 ]
239 "###);
240 }
241
242 #[test]
243 fn test_record_literal_field() {
244 let completions = complete(
245 r"
246 struct A { the_field: u32 }
247 fn foo() {
248 A { the<|> }
249 }
250 ",
251 );
252 assert_debug_snapshot!(completions, @r###"
253 [
254 CompletionItem {
255 label: "the_field",
256 source_range: [83; 86),
257 delete: [83; 86),
258 insert: "the_field",
259 kind: Field,
260 detail: "u32",
261 },
262 ]
263 "###);
264 }
265
266 #[test]
267 fn test_record_literal_enum_variant() {
268 let completions = complete(
269 r"
270 enum E {
271 A { a: u32 }
272 }
273 fn foo() {
274 let _ = E::A { <|> }
275 }
276 ",
277 );
278 assert_debug_snapshot!(completions, @r###"
279 [
280 CompletionItem {
281 label: "a",
282 source_range: [119; 119),
283 delete: [119; 119),
284 insert: "a",
285 kind: Field,
286 detail: "u32",
287 },
288 ]
289 "###);
290 }
291
292 #[test]
293 fn test_record_literal_two_structs() {
294 let completions = complete(
295 r"
296 struct A { a: u32 }
297 struct B { b: u32 }
298
299 fn foo() {
300 let _: A = B { <|> }
301 }
302 ",
303 );
304 assert_debug_snapshot!(completions, @r###"
305 [
306 CompletionItem {
307 label: "b",
308 source_range: [119; 119),
309 delete: [119; 119),
310 insert: "b",
311 kind: Field,
312 detail: "u32",
313 },
314 ]
315 "###);
316 }
317
318 #[test]
319 fn test_record_literal_generic_struct() {
320 let completions = complete(
321 r"
322 struct A<T> { a: T }
323
324 fn foo() {
325 let _: A<u32> = A { <|> }
326 }
327 ",
328 );
329 assert_debug_snapshot!(completions, @r###"
330 [
331 CompletionItem {
332 label: "a",
333 source_range: [93; 93),
334 delete: [93; 93),
335 insert: "a",
336 kind: Field,
337 detail: "u32",
338 },
339 ]
340 "###);
341 }
342
343 #[test]
344 fn test_record_literal_field_in_simple_macro() {
345 let completions = complete(
346 r"
347 macro_rules! m { ($e:expr) => { $e } }
348 struct A { the_field: u32 }
349 fn foo() {
350 m!(A { the<|> })
351 }
352 ",
353 );
354 assert_debug_snapshot!(completions, @r###"
355 [
356 CompletionItem {
357 label: "the_field",
358 source_range: [137; 140),
359 delete: [137; 140),
360 insert: "the_field",
361 kind: Field,
362 detail: "u32",
363 },
364 ]
365 "###);
366 }
367
368 #[test]
369 fn only_missing_fields_are_completed() {
370 let completions = complete(
371 r"
372 struct S {
373 foo1: u32,
374 foo2: u32,
375 bar: u32,
376 baz: u32,
377 }
378
379 fn main() {
380 let foo1 = 1;
381 let s = S {
382 foo1,
383 foo2: 5,
384 <|>
385 }
386 }
387 ",
388 );
389 assert_debug_snapshot!(completions, @r###"
390 [
391 CompletionItem {
392 label: "bar",
393 source_range: [302; 302),
394 delete: [302; 302),
395 insert: "bar",
396 kind: Field,
397 detail: "u32",
398 },
399 CompletionItem {
400 label: "baz",
401 source_range: [302; 302),
402 delete: [302; 302),
403 insert: "baz",
404 kind: Field,
405 detail: "u32",
406 },
407 ]
408 "###);
409 }
410 }
411}
diff --git a/crates/ra_ide/src/completion/complete_record_literal.rs b/crates/ra_ide/src/completion/complete_record_literal.rs
deleted file mode 100644
index e4e764f58..000000000
--- a/crates/ra_ide/src/completion/complete_record_literal.rs
+++ /dev/null
@@ -1,241 +0,0 @@
1//! FIXME: write short doc here
2
3use crate::completion::{CompletionContext, Completions};
4use ra_syntax::SmolStr;
5
6/// Complete fields in fields literals.
7pub(super) fn complete_record_literal(acc: &mut Completions, ctx: &CompletionContext) {
8 let (ty, variant) = match ctx.record_lit_syntax.as_ref().and_then(|it| {
9 Some((ctx.sema.type_of_expr(&it.clone().into())?, ctx.sema.resolve_record_literal(it)?))
10 }) {
11 Some(it) => it,
12 _ => return,
13 };
14
15 let already_present_names: Vec<SmolStr> = ctx
16 .record_lit_syntax
17 .as_ref()
18 .and_then(|record_literal| record_literal.record_field_list())
19 .map(|field_list| field_list.fields())
20 .map(|fields| {
21 fields
22 .into_iter()
23 .filter_map(|field| field.name_ref())
24 .map(|name_ref| name_ref.text().clone())
25 .collect()
26 })
27 .unwrap_or_default();
28
29 for (field, field_ty) in ty.variant_fields(ctx.db, variant) {
30 if !already_present_names.contains(&SmolStr::from(field.name(ctx.db).to_string())) {
31 acc.add_field(ctx, field, &field_ty);
32 }
33 }
34}
35
36#[cfg(test)]
37mod tests {
38 use crate::completion::{test_utils::do_completion, CompletionItem, CompletionKind};
39 use insta::assert_debug_snapshot;
40
41 fn complete(code: &str) -> Vec<CompletionItem> {
42 do_completion(code, CompletionKind::Reference)
43 }
44
45 #[test]
46 fn test_record_literal_deprecated_field() {
47 let completions = complete(
48 r"
49 struct A {
50 #[deprecated]
51 the_field: u32,
52 }
53 fn foo() {
54 A { the<|> }
55 }
56 ",
57 );
58 assert_debug_snapshot!(completions, @r###"
59 [
60 CompletionItem {
61 label: "the_field",
62 source_range: [142; 145),
63 delete: [142; 145),
64 insert: "the_field",
65 kind: Field,
66 detail: "u32",
67 deprecated: true,
68 },
69 ]
70 "###);
71 }
72
73 #[test]
74 fn test_record_literal_field() {
75 let completions = complete(
76 r"
77 struct A { the_field: u32 }
78 fn foo() {
79 A { the<|> }
80 }
81 ",
82 );
83 assert_debug_snapshot!(completions, @r###"
84 [
85 CompletionItem {
86 label: "the_field",
87 source_range: [83; 86),
88 delete: [83; 86),
89 insert: "the_field",
90 kind: Field,
91 detail: "u32",
92 },
93 ]
94 "###);
95 }
96
97 #[test]
98 fn test_record_literal_enum_variant() {
99 let completions = complete(
100 r"
101 enum E {
102 A { a: u32 }
103 }
104 fn foo() {
105 let _ = E::A { <|> }
106 }
107 ",
108 );
109 assert_debug_snapshot!(completions, @r###"
110 [
111 CompletionItem {
112 label: "a",
113 source_range: [119; 119),
114 delete: [119; 119),
115 insert: "a",
116 kind: Field,
117 detail: "u32",
118 },
119 ]
120 "###);
121 }
122
123 #[test]
124 fn test_record_literal_two_structs() {
125 let completions = complete(
126 r"
127 struct A { a: u32 }
128 struct B { b: u32 }
129
130 fn foo() {
131 let _: A = B { <|> }
132 }
133 ",
134 );
135 assert_debug_snapshot!(completions, @r###"
136 [
137 CompletionItem {
138 label: "b",
139 source_range: [119; 119),
140 delete: [119; 119),
141 insert: "b",
142 kind: Field,
143 detail: "u32",
144 },
145 ]
146 "###);
147 }
148
149 #[test]
150 fn test_record_literal_generic_struct() {
151 let completions = complete(
152 r"
153 struct A<T> { a: T }
154
155 fn foo() {
156 let _: A<u32> = A { <|> }
157 }
158 ",
159 );
160 assert_debug_snapshot!(completions, @r###"
161 [
162 CompletionItem {
163 label: "a",
164 source_range: [93; 93),
165 delete: [93; 93),
166 insert: "a",
167 kind: Field,
168 detail: "u32",
169 },
170 ]
171 "###);
172 }
173
174 #[test]
175 fn test_record_literal_field_in_simple_macro() {
176 let completions = complete(
177 r"
178 macro_rules! m { ($e:expr) => { $e } }
179 struct A { the_field: u32 }
180 fn foo() {
181 m!(A { the<|> })
182 }
183 ",
184 );
185 assert_debug_snapshot!(completions, @r###"
186 [
187 CompletionItem {
188 label: "the_field",
189 source_range: [137; 140),
190 delete: [137; 140),
191 insert: "the_field",
192 kind: Field,
193 detail: "u32",
194 },
195 ]
196 "###);
197 }
198
199 #[test]
200 fn only_missing_fields_are_completed() {
201 let completions = complete(
202 r"
203 struct S {
204 foo1: u32,
205 foo2: u32,
206 bar: u32,
207 baz: u32,
208 }
209
210 fn main() {
211 let foo1 = 1;
212 let s = S {
213 foo1,
214 foo2: 5,
215 <|>
216 }
217 }
218 ",
219 );
220 assert_debug_snapshot!(completions, @r###"
221 [
222 CompletionItem {
223 label: "bar",
224 source_range: [302; 302),
225 delete: [302; 302),
226 insert: "bar",
227 kind: Field,
228 detail: "u32",
229 },
230 CompletionItem {
231 label: "baz",
232 source_range: [302; 302),
233 delete: [302; 302),
234 insert: "baz",
235 kind: Field,
236 detail: "u32",
237 },
238 ]
239 "###);
240 }
241}
diff --git a/crates/ra_ide/src/completion/complete_record_pattern.rs b/crates/ra_ide/src/completion/complete_record_pattern.rs
deleted file mode 100644
index 962376428..000000000
--- a/crates/ra_ide/src/completion/complete_record_pattern.rs
+++ /dev/null
@@ -1,118 +0,0 @@
1//! FIXME: write short doc here
2
3use crate::completion::{CompletionContext, Completions};
4
5pub(super) fn complete_record_pattern(acc: &mut Completions, ctx: &CompletionContext) {
6 let (ty, variant) = match ctx.record_lit_pat.as_ref().and_then(|it| {
7 Some((ctx.sema.type_of_pat(&it.clone().into())?, ctx.sema.resolve_record_pattern(it)?))
8 }) {
9 Some(it) => it,
10 _ => return,
11 };
12
13 for (field, field_ty) in ty.variant_fields(ctx.db, variant) {
14 acc.add_field(ctx, field, &field_ty);
15 }
16}
17
18#[cfg(test)]
19mod tests {
20 use crate::completion::{test_utils::do_completion, CompletionItem, CompletionKind};
21 use insta::assert_debug_snapshot;
22
23 fn complete(code: &str) -> Vec<CompletionItem> {
24 do_completion(code, CompletionKind::Reference)
25 }
26
27 #[test]
28 fn test_record_pattern_field() {
29 let completions = complete(
30 r"
31 struct S { foo: u32 }
32
33 fn process(f: S) {
34 match f {
35 S { f<|>: 92 } => (),
36 }
37 }
38 ",
39 );
40 assert_debug_snapshot!(completions, @r###"
41 [
42 CompletionItem {
43 label: "foo",
44 source_range: [117; 118),
45 delete: [117; 118),
46 insert: "foo",
47 kind: Field,
48 detail: "u32",
49 },
50 ]
51 "###);
52 }
53
54 #[test]
55 fn test_record_pattern_enum_variant() {
56 let completions = complete(
57 r"
58 enum E {
59 S { foo: u32, bar: () }
60 }
61
62 fn process(e: E) {
63 match e {
64 E::S { <|> } => (),
65 }
66 }
67 ",
68 );
69 assert_debug_snapshot!(completions, @r###"
70 [
71 CompletionItem {
72 label: "bar",
73 source_range: [161; 161),
74 delete: [161; 161),
75 insert: "bar",
76 kind: Field,
77 detail: "()",
78 },
79 CompletionItem {
80 label: "foo",
81 source_range: [161; 161),
82 delete: [161; 161),
83 insert: "foo",
84 kind: Field,
85 detail: "u32",
86 },
87 ]
88 "###);
89 }
90
91 #[test]
92 fn test_record_pattern_field_in_simple_macro() {
93 let completions = complete(
94 r"
95 macro_rules! m { ($e:expr) => { $e } }
96 struct S { foo: u32 }
97
98 fn process(f: S) {
99 m!(match f {
100 S { f<|>: 92 } => (),
101 })
102 }
103 ",
104 );
105 assert_debug_snapshot!(completions, @r###"
106 [
107 CompletionItem {
108 label: "foo",
109 source_range: [171; 172),
110 delete: [171; 172),
111 insert: "foo",
112 kind: Field,
113 detail: "u32",
114 },
115 ]
116 "###);
117 }
118}
diff --git a/crates/ra_ide/src/completion/completion_context.rs b/crates/ra_ide/src/completion/completion_context.rs
index 319e33b61..fdc0da2c5 100644
--- a/crates/ra_ide/src/completion/completion_context.rs
+++ b/crates/ra_ide/src/completion/completion_context.rs
@@ -11,7 +11,7 @@ use ra_syntax::{
11}; 11};
12use ra_text_edit::AtomTextEdit; 12use ra_text_edit::AtomTextEdit;
13 13
14use crate::{completion::CompletionOptions, FilePosition}; 14use crate::{completion::CompletionConfig, FilePosition};
15 15
16/// `CompletionContext` is created early during completion to figure out, where 16/// `CompletionContext` is created early during completion to figure out, where
17/// exactly is the cursor, syntax-wise. 17/// exactly is the cursor, syntax-wise.
@@ -19,7 +19,7 @@ use crate::{completion::CompletionOptions, FilePosition};
19pub(crate) struct CompletionContext<'a> { 19pub(crate) struct CompletionContext<'a> {
20 pub(super) sema: Semantics<'a, RootDatabase>, 20 pub(super) sema: Semantics<'a, RootDatabase>,
21 pub(super) db: &'a RootDatabase, 21 pub(super) db: &'a RootDatabase,
22 pub(super) options: &'a CompletionOptions, 22 pub(super) config: &'a CompletionConfig,
23 pub(super) offset: TextUnit, 23 pub(super) offset: TextUnit,
24 /// The token before the cursor, in the original file. 24 /// The token before the cursor, in the original file.
25 pub(super) original_token: SyntaxToken, 25 pub(super) original_token: SyntaxToken,
@@ -61,7 +61,7 @@ impl<'a> CompletionContext<'a> {
61 pub(super) fn new( 61 pub(super) fn new(
62 db: &'a RootDatabase, 62 db: &'a RootDatabase,
63 position: FilePosition, 63 position: FilePosition,
64 options: &'a CompletionOptions, 64 config: &'a CompletionConfig,
65 ) -> Option<CompletionContext<'a>> { 65 ) -> Option<CompletionContext<'a>> {
66 let sema = Semantics::new(db); 66 let sema = Semantics::new(db);
67 67
@@ -85,7 +85,7 @@ impl<'a> CompletionContext<'a> {
85 let mut ctx = CompletionContext { 85 let mut ctx = CompletionContext {
86 sema, 86 sema,
87 db, 87 db,
88 options, 88 config,
89 original_token, 89 original_token,
90 token, 90 token,
91 offset: position.offset, 91 offset: position.offset,
diff --git a/crates/ra_ide/src/completion/presentation.rs b/crates/ra_ide/src/completion/presentation.rs
index 60f1b83f3..1c7c0924d 100644
--- a/crates/ra_ide/src/completion/presentation.rs
+++ b/crates/ra_ide/src/completion/presentation.rs
@@ -106,7 +106,7 @@ impl Completions {
106 }; 106 };
107 107
108 // Add `<>` for generic types 108 // Add `<>` for generic types
109 if ctx.is_path_type && !ctx.has_type_args && ctx.options.add_call_parenthesis { 109 if ctx.is_path_type && !ctx.has_type_args && ctx.config.add_call_parenthesis {
110 let has_non_default_type_params = match resolution { 110 let has_non_default_type_params = match resolution {
111 ScopeDef::ModuleDef(Adt(it)) => it.has_non_default_type_params(ctx.db), 111 ScopeDef::ModuleDef(Adt(it)) => it.has_non_default_type_params(ctx.db),
112 ScopeDef::ModuleDef(TypeAlias(it)) => it.has_non_default_type_params(ctx.db), 112 ScopeDef::ModuleDef(TypeAlias(it)) => it.has_non_default_type_params(ctx.db),
@@ -211,14 +211,14 @@ impl Completions {
211 .detail(function_signature.to_string()); 211 .detail(function_signature.to_string());
212 212
213 // If not an import, add parenthesis automatically. 213 // If not an import, add parenthesis automatically.
214 if ctx.use_item_syntax.is_none() && !ctx.is_call && ctx.options.add_call_parenthesis { 214 if ctx.use_item_syntax.is_none() && !ctx.is_call && ctx.config.add_call_parenthesis {
215 tested_by!(inserts_parens_for_function_calls); 215 tested_by!(inserts_parens_for_function_calls);
216 216
217 let (snippet, label) = if params.is_empty() || has_self_param && params.len() == 1 { 217 let (snippet, label) = if params.is_empty() || has_self_param && params.len() == 1 {
218 (format!("{}()$0", name), format!("{}()", name)) 218 (format!("{}()$0", name), format!("{}()", name))
219 } else { 219 } else {
220 builder = builder.trigger_call_info(); 220 builder = builder.trigger_call_info();
221 let snippet = if ctx.options.add_call_argument_snippets { 221 let snippet = if ctx.config.add_call_argument_snippets {
222 let to_skip = if has_self_param { 1 } else { 0 }; 222 let to_skip = if has_self_param { 1 } else { 0 };
223 let function_params_snippet = function_signature 223 let function_params_snippet = function_signature
224 .parameter_names 224 .parameter_names
@@ -311,7 +311,7 @@ mod tests {
311 311
312 use crate::completion::{ 312 use crate::completion::{
313 test_utils::{do_completion, do_completion_with_options}, 313 test_utils::{do_completion, do_completion_with_options},
314 CompletionItem, CompletionKind, CompletionOptions, 314 CompletionConfig, CompletionItem, CompletionKind,
315 }; 315 };
316 316
317 fn do_reference_completion(ra_fixture: &str) -> Vec<CompletionItem> { 317 fn do_reference_completion(ra_fixture: &str) -> Vec<CompletionItem> {
@@ -320,7 +320,7 @@ mod tests {
320 320
321 fn do_reference_completion_with_options( 321 fn do_reference_completion_with_options(
322 ra_fixture: &str, 322 ra_fixture: &str,
323 options: CompletionOptions, 323 options: CompletionConfig,
324 ) -> Vec<CompletionItem> { 324 ) -> Vec<CompletionItem> {
325 do_completion_with_options(ra_fixture, CompletionKind::Reference, &options) 325 do_completion_with_options(ra_fixture, CompletionKind::Reference, &options)
326 } 326 }
@@ -589,7 +589,7 @@ mod tests {
589 s.f<|> 589 s.f<|>
590 } 590 }
591 ", 591 ",
592 CompletionOptions { 592 CompletionConfig {
593 add_call_argument_snippets: false, 593 add_call_argument_snippets: false,
594 .. Default::default() 594 .. Default::default()
595 } 595 }
diff --git a/crates/ra_ide/src/completion/test_utils.rs b/crates/ra_ide/src/completion/test_utils.rs
index 136857315..eb90b5279 100644
--- a/crates/ra_ide/src/completion/test_utils.rs
+++ b/crates/ra_ide/src/completion/test_utils.rs
@@ -1,19 +1,19 @@
1//! Runs completion for testing purposes. 1//! Runs completion for testing purposes.
2 2
3use crate::{ 3use crate::{
4 completion::{completion_item::CompletionKind, CompletionOptions}, 4 completion::{completion_item::CompletionKind, CompletionConfig},
5 mock_analysis::{analysis_and_position, single_file_with_position}, 5 mock_analysis::{analysis_and_position, single_file_with_position},
6 CompletionItem, 6 CompletionItem,
7}; 7};
8 8
9pub(crate) fn do_completion(code: &str, kind: CompletionKind) -> Vec<CompletionItem> { 9pub(crate) fn do_completion(code: &str, kind: CompletionKind) -> Vec<CompletionItem> {
10 do_completion_with_options(code, kind, &CompletionOptions::default()) 10 do_completion_with_options(code, kind, &CompletionConfig::default())
11} 11}
12 12
13pub(crate) fn do_completion_with_options( 13pub(crate) fn do_completion_with_options(
14 code: &str, 14 code: &str,
15 kind: CompletionKind, 15 kind: CompletionKind,
16 options: &CompletionOptions, 16 options: &CompletionConfig,
17) -> Vec<CompletionItem> { 17) -> Vec<CompletionItem> {
18 let (analysis, position) = if code.contains("//-") { 18 let (analysis, position) = if code.contains("//-") {
19 analysis_and_position(code) 19 analysis_and_position(code)
diff --git a/crates/ra_ide/src/inlay_hints.rs b/crates/ra_ide/src/inlay_hints.rs
index f4f0751c0..4b133b19b 100644
--- a/crates/ra_ide/src/inlay_hints.rs
+++ b/crates/ra_ide/src/inlay_hints.rs
@@ -11,14 +11,14 @@ use ra_syntax::{
11use crate::{FileId, FunctionSignature}; 11use crate::{FileId, FunctionSignature};
12 12
13#[derive(Clone, Debug, PartialEq, Eq)] 13#[derive(Clone, Debug, PartialEq, Eq)]
14pub struct InlayHintsOptions { 14pub struct InlayHintsConfig {
15 pub type_hints: bool, 15 pub type_hints: bool,
16 pub parameter_hints: bool, 16 pub parameter_hints: bool,
17 pub chaining_hints: bool, 17 pub chaining_hints: bool,
18 pub max_length: Option<usize>, 18 pub max_length: Option<usize>,
19} 19}
20 20
21impl Default for InlayHintsOptions { 21impl Default for InlayHintsConfig {
22 fn default() -> Self { 22 fn default() -> Self {
23 Self { type_hints: true, parameter_hints: true, chaining_hints: true, max_length: None } 23 Self { type_hints: true, parameter_hints: true, chaining_hints: true, max_length: None }
24 } 24 }
@@ -41,7 +41,7 @@ pub struct InlayHint {
41pub(crate) fn inlay_hints( 41pub(crate) fn inlay_hints(
42 db: &RootDatabase, 42 db: &RootDatabase,
43 file_id: FileId, 43 file_id: FileId,
44 options: &InlayHintsOptions, 44 config: &InlayHintsConfig,
45) -> Vec<InlayHint> { 45) -> Vec<InlayHint> {
46 let _p = profile("inlay_hints"); 46 let _p = profile("inlay_hints");
47 let sema = Semantics::new(db); 47 let sema = Semantics::new(db);
@@ -50,14 +50,14 @@ pub(crate) fn inlay_hints(
50 let mut res = Vec::new(); 50 let mut res = Vec::new();
51 for node in file.syntax().descendants() { 51 for node in file.syntax().descendants() {
52 if let Some(expr) = ast::Expr::cast(node.clone()) { 52 if let Some(expr) = ast::Expr::cast(node.clone()) {
53 get_chaining_hints(&mut res, &sema, options, expr); 53 get_chaining_hints(&mut res, &sema, config, expr);
54 } 54 }
55 55
56 match_ast! { 56 match_ast! {
57 match node { 57 match node {
58 ast::CallExpr(it) => { get_param_name_hints(&mut res, &sema, options, ast::Expr::from(it)); }, 58 ast::CallExpr(it) => { get_param_name_hints(&mut res, &sema, config, ast::Expr::from(it)); },
59 ast::MethodCallExpr(it) => { get_param_name_hints(&mut res, &sema, options, ast::Expr::from(it)); }, 59 ast::MethodCallExpr(it) => { get_param_name_hints(&mut res, &sema, config, ast::Expr::from(it)); },
60 ast::BindPat(it) => { get_bind_pat_hints(&mut res, &sema, options, it); }, 60 ast::BindPat(it) => { get_bind_pat_hints(&mut res, &sema, config, it); },
61 _ => (), 61 _ => (),
62 } 62 }
63 } 63 }
@@ -68,15 +68,14 @@ pub(crate) fn inlay_hints(
68fn get_chaining_hints( 68fn get_chaining_hints(
69 acc: &mut Vec<InlayHint>, 69 acc: &mut Vec<InlayHint>,
70 sema: &Semantics<RootDatabase>, 70 sema: &Semantics<RootDatabase>,
71 options: &InlayHintsOptions, 71 config: &InlayHintsConfig,
72 expr: ast::Expr, 72 expr: ast::Expr,
73) -> Option<()> { 73) -> Option<()> {
74 if !options.chaining_hints { 74 if !config.chaining_hints {
75 return None; 75 return None;
76 } 76 }
77 77
78 let ty = sema.type_of_expr(&expr)?; 78 if matches!(expr, ast::Expr::RecordLit(_)) {
79 if ty.is_unknown() {
80 return None; 79 return None;
81 } 80 }
82 81
@@ -95,7 +94,18 @@ fn get_chaining_hints(
95 let next = tokens.next()?.kind(); 94 let next = tokens.next()?.kind();
96 let next_next = tokens.next()?.kind(); 95 let next_next = tokens.next()?.kind();
97 if next == SyntaxKind::WHITESPACE && next_next == SyntaxKind::DOT { 96 if next == SyntaxKind::WHITESPACE && next_next == SyntaxKind::DOT {
98 let label = ty.display_truncated(sema.db, options.max_length).to_string(); 97 let ty = sema.type_of_expr(&expr)?;
98 if ty.is_unknown() {
99 return None;
100 }
101 if matches!(expr, ast::Expr::PathExpr(_)) {
102 if let Some(Adt::Struct(st)) = ty.as_adt() {
103 if st.fields(sema.db).is_empty() {
104 return None;
105 }
106 }
107 }
108 let label = ty.display_truncated(sema.db, config.max_length).to_string();
99 acc.push(InlayHint { 109 acc.push(InlayHint {
100 range: expr.syntax().text_range(), 110 range: expr.syntax().text_range(),
101 kind: InlayKind::ChainingHint, 111 kind: InlayKind::ChainingHint,
@@ -108,10 +118,10 @@ fn get_chaining_hints(
108fn get_param_name_hints( 118fn get_param_name_hints(
109 acc: &mut Vec<InlayHint>, 119 acc: &mut Vec<InlayHint>,
110 sema: &Semantics<RootDatabase>, 120 sema: &Semantics<RootDatabase>,
111 options: &InlayHintsOptions, 121 config: &InlayHintsConfig,
112 expr: ast::Expr, 122 expr: ast::Expr,
113) -> Option<()> { 123) -> Option<()> {
114 if !options.parameter_hints { 124 if !config.parameter_hints {
115 return None; 125 return None;
116 } 126 }
117 127
@@ -148,10 +158,10 @@ fn get_param_name_hints(
148fn get_bind_pat_hints( 158fn get_bind_pat_hints(
149 acc: &mut Vec<InlayHint>, 159 acc: &mut Vec<InlayHint>,
150 sema: &Semantics<RootDatabase>, 160 sema: &Semantics<RootDatabase>,
151 options: &InlayHintsOptions, 161 config: &InlayHintsConfig,
152 pat: ast::BindPat, 162 pat: ast::BindPat,
153) -> Option<()> { 163) -> Option<()> {
154 if !options.type_hints { 164 if !config.type_hints {
155 return None; 165 return None;
156 } 166 }
157 167
@@ -164,7 +174,7 @@ fn get_bind_pat_hints(
164 acc.push(InlayHint { 174 acc.push(InlayHint {
165 range: pat.syntax().text_range(), 175 range: pat.syntax().text_range(),
166 kind: InlayKind::TypeHint, 176 kind: InlayKind::TypeHint,
167 label: ty.display_truncated(sema.db, options.max_length).to_string().into(), 177 label: ty.display_truncated(sema.db, config.max_length).to_string().into(),
168 }); 178 });
169 Some(()) 179 Some(())
170} 180}
@@ -270,7 +280,7 @@ fn get_fn_signature(sema: &Semantics<RootDatabase>, expr: &ast::Expr) -> Option<
270 280
271#[cfg(test)] 281#[cfg(test)]
272mod tests { 282mod tests {
273 use crate::inlay_hints::InlayHintsOptions; 283 use crate::inlay_hints::InlayHintsConfig;
274 use insta::assert_debug_snapshot; 284 use insta::assert_debug_snapshot;
275 285
276 use crate::mock_analysis::single_file; 286 use crate::mock_analysis::single_file;
@@ -284,7 +294,7 @@ mod tests {
284 let _x = foo(4, 4); 294 let _x = foo(4, 4);
285 }"#, 295 }"#,
286 ); 296 );
287 assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsOptions{ parameter_hints: true, type_hints: false, chaining_hints: false, max_length: None}).unwrap(), @r###" 297 assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsConfig{ parameter_hints: true, type_hints: false, chaining_hints: false, max_length: None}).unwrap(), @r###"
288 [ 298 [
289 InlayHint { 299 InlayHint {
290 range: [106; 107), 300 range: [106; 107),
@@ -308,7 +318,7 @@ mod tests {
308 let _x = foo(4, 4); 318 let _x = foo(4, 4);
309 }"#, 319 }"#,
310 ); 320 );
311 assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsOptions{ type_hints: false, parameter_hints: false, chaining_hints: false, max_length: None}).unwrap(), @r###"[]"###); 321 assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsConfig{ type_hints: false, parameter_hints: false, chaining_hints: false, max_length: None}).unwrap(), @r###"[]"###);
312 } 322 }
313 323
314 #[test] 324 #[test]
@@ -320,7 +330,7 @@ mod tests {
320 let _x = foo(4, 4); 330 let _x = foo(4, 4);
321 }"#, 331 }"#,
322 ); 332 );
323 assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsOptions{ type_hints: true, parameter_hints: false, chaining_hints: false, max_length: None}).unwrap(), @r###" 333 assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsConfig{ type_hints: true, parameter_hints: false, chaining_hints: false, max_length: None}).unwrap(), @r###"
324 [ 334 [
325 InlayHint { 335 InlayHint {
326 range: [97; 99), 336 range: [97; 99),
@@ -344,7 +354,7 @@ fn main() {
344}"#, 354}"#,
345 ); 355 );
346 356
347 assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsOptions::default()).unwrap(), @r###" 357 assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsConfig::default()).unwrap(), @r###"
348 [ 358 [
349 InlayHint { 359 InlayHint {
350 range: [69; 71), 360 range: [69; 71),
@@ -401,7 +411,7 @@ fn main() {
401}"#, 411}"#,
402 ); 412 );
403 413
404 assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsOptions::default()).unwrap(), @r###" 414 assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsConfig::default()).unwrap(), @r###"
405 [ 415 [
406 InlayHint { 416 InlayHint {
407 range: [193; 197), 417 range: [193; 197),
@@ -481,7 +491,7 @@ fn main() {
481}"#, 491}"#,
482 ); 492 );
483 493
484 assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsOptions::default()).unwrap(), @r###" 494 assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsConfig::default()).unwrap(), @r###"
485 [ 495 [
486 InlayHint { 496 InlayHint {
487 range: [21; 30), 497 range: [21; 30),
@@ -545,7 +555,7 @@ fn main() {
545}"#, 555}"#,
546 ); 556 );
547 557
548 assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsOptions::default()).unwrap(), @r###" 558 assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsConfig::default()).unwrap(), @r###"
549 [ 559 [
550 InlayHint { 560 InlayHint {
551 range: [21; 30), 561 range: [21; 30),
@@ -595,7 +605,7 @@ fn main() {
595}"#, 605}"#,
596 ); 606 );
597 607
598 assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsOptions::default()).unwrap(), @r###" 608 assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsConfig::default()).unwrap(), @r###"
599 [ 609 [
600 InlayHint { 610 InlayHint {
601 range: [188; 192), 611 range: [188; 192),
@@ -690,7 +700,7 @@ fn main() {
690}"#, 700}"#,
691 ); 701 );
692 702
693 assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsOptions::default()).unwrap(), @r###" 703 assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsConfig::default()).unwrap(), @r###"
694 [ 704 [
695 InlayHint { 705 InlayHint {
696 range: [188; 192), 706 range: [188; 192),
@@ -785,7 +795,7 @@ fn main() {
785}"#, 795}"#,
786 ); 796 );
787 797
788 assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsOptions::default()).unwrap(), @r###" 798 assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsConfig::default()).unwrap(), @r###"
789 [ 799 [
790 InlayHint { 800 InlayHint {
791 range: [252; 256), 801 range: [252; 256),
@@ -857,7 +867,7 @@ fn main() {
857}"#, 867}"#,
858 ); 868 );
859 869
860 assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsOptions { max_length: Some(8), ..Default::default() }).unwrap(), @r###" 870 assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsConfig { max_length: Some(8), ..Default::default() }).unwrap(), @r###"
861 [ 871 [
862 InlayHint { 872 InlayHint {
863 range: [74; 75), 873 range: [74; 75),
@@ -945,7 +955,7 @@ fn main() {
945}"#, 955}"#,
946 ); 956 );
947 957
948 assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsOptions::default()).unwrap(), @r###" 958 assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsConfig::default()).unwrap(), @r###"
949 [ 959 [
950 InlayHint { 960 InlayHint {
951 range: [798; 809), 961 range: [798; 809),
@@ -1067,7 +1077,7 @@ fn main() {
1067}"#, 1077}"#,
1068 ); 1078 );
1069 1079
1070 assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsOptions { max_length: Some(8), ..Default::default() }).unwrap(), @r###" 1080 assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsConfig { max_length: Some(8), ..Default::default() }).unwrap(), @r###"
1071 [] 1081 []
1072 "### 1082 "###
1073 ); 1083 );
@@ -1093,7 +1103,7 @@ fn main() {
1093}"#, 1103}"#,
1094 ); 1104 );
1095 1105
1096 assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsOptions { max_length: Some(8), ..Default::default() }).unwrap(), @r###" 1106 assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsConfig { max_length: Some(8), ..Default::default() }).unwrap(), @r###"
1097 [] 1107 []
1098 "### 1108 "###
1099 ); 1109 );
@@ -1115,7 +1125,7 @@ fn main() {
1115 .into_c(); 1125 .into_c();
1116 }"#, 1126 }"#,
1117 ); 1127 );
1118 assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsOptions{ parameter_hints: false, type_hints: false, chaining_hints: true, max_length: None}).unwrap(), @r###" 1128 assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsConfig{ parameter_hints: false, type_hints: false, chaining_hints: true, max_length: None}).unwrap(), @r###"
1119 [ 1129 [
1120 InlayHint { 1130 InlayHint {
1121 range: [232; 269), 1131 range: [232; 269),
@@ -1144,7 +1154,7 @@ fn main() {
1144 let c = A(B(C)).into_b().into_c(); 1154 let c = A(B(C)).into_b().into_c();
1145 }"#, 1155 }"#,
1146 ); 1156 );
1147 assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsOptions{ parameter_hints: false, type_hints: false, chaining_hints: true, max_length: None}).unwrap(), @r###"[]"###); 1157 assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsConfig{ parameter_hints: false, type_hints: false, chaining_hints: true, max_length: None}).unwrap(), @r###"[]"###);
1148 } 1158 }
1149 1159
1150 #[test] 1160 #[test]
@@ -1154,32 +1164,35 @@ fn main() {
1154 struct A { pub b: B } 1164 struct A { pub b: B }
1155 struct B { pub c: C } 1165 struct B { pub c: C }
1156 struct C(pub bool); 1166 struct C(pub bool);
1167 struct D;
1168
1169 impl D {
1170 fn foo(&self) -> i32 { 42 }
1171 }
1157 1172
1158 fn main() { 1173 fn main() {
1159 let x = A { b: B { c: C(true) } } 1174 let x = A { b: B { c: C(true) } }
1160 .b 1175 .b
1161 .c 1176 .c
1162 .0; 1177 .0;
1178 let x = D
1179 .foo();
1163 }"#, 1180 }"#,
1164 ); 1181 );
1165 assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsOptions{ parameter_hints: false, type_hints: false, chaining_hints: true, max_length: None}).unwrap(), @r###" 1182 assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsConfig{ parameter_hints: false, type_hints: false, chaining_hints: true, max_length: None}).unwrap(), @r###"
1166 [ 1183 [
1167 InlayHint { 1184 InlayHint {
1168 range: [150; 221), 1185 range: [252; 323),
1169 kind: ChainingHint, 1186 kind: ChainingHint,
1170 label: "C", 1187 label: "C",
1171 }, 1188 },
1172 InlayHint { 1189 InlayHint {
1173 range: [150; 198), 1190 range: [252; 300),
1174 kind: ChainingHint, 1191 kind: ChainingHint,
1175 label: "B", 1192 label: "B",
1176 }, 1193 },
1177 InlayHint { 1194 ]
1178 range: [150; 175), 1195 "###);
1179 kind: ChainingHint,
1180 label: "A",
1181 },
1182 ]"###);
1183 } 1196 }
1184 1197
1185 #[test] 1198 #[test]
@@ -1204,7 +1217,7 @@ fn main() {
1204 .into_c(); 1217 .into_c();
1205 }"#, 1218 }"#,
1206 ); 1219 );
1207 assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsOptions{ parameter_hints: false, type_hints: false, chaining_hints: true, max_length: None}).unwrap(), @r###" 1220 assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsConfig{ parameter_hints: false, type_hints: false, chaining_hints: true, max_length: None}).unwrap(), @r###"
1208 [ 1221 [
1209 InlayHint { 1222 InlayHint {
1210 range: [403; 452), 1223 range: [403; 452),
diff --git a/crates/ra_ide/src/lib.rs b/crates/ra_ide/src/lib.rs
index e43414985..285381086 100644
--- a/crates/ra_ide/src/lib.rs
+++ b/crates/ra_ide/src/lib.rs
@@ -62,13 +62,13 @@ use crate::display::ToNav;
62pub use crate::{ 62pub use crate::{
63 assists::{Assist, AssistId}, 63 assists::{Assist, AssistId},
64 call_hierarchy::CallItem, 64 call_hierarchy::CallItem,
65 completion::{CompletionItem, CompletionItemKind, CompletionOptions, InsertTextFormat}, 65 completion::{CompletionConfig, CompletionItem, CompletionItemKind, InsertTextFormat},
66 diagnostics::Severity, 66 diagnostics::Severity,
67 display::{file_structure, FunctionSignature, NavigationTarget, StructureNode}, 67 display::{file_structure, FunctionSignature, NavigationTarget, StructureNode},
68 expand_macro::ExpandedMacro, 68 expand_macro::ExpandedMacro,
69 folding_ranges::{Fold, FoldKind}, 69 folding_ranges::{Fold, FoldKind},
70 hover::HoverResult, 70 hover::HoverResult,
71 inlay_hints::{InlayHint, InlayHintsOptions, InlayKind}, 71 inlay_hints::{InlayHint, InlayHintsConfig, InlayKind},
72 references::{Declaration, Reference, ReferenceAccess, ReferenceKind, ReferenceSearchResult}, 72 references::{Declaration, Reference, ReferenceAccess, ReferenceKind, ReferenceSearchResult},
73 runnables::{Runnable, RunnableKind, TestId}, 73 runnables::{Runnable, RunnableKind, TestId},
74 source_change::{FileSystemEdit, SourceChange, SourceFileEdit}, 74 source_change::{FileSystemEdit, SourceChange, SourceFileEdit},
@@ -138,6 +138,11 @@ impl AnalysisHost {
138 pub fn new(lru_capacity: Option<usize>) -> AnalysisHost { 138 pub fn new(lru_capacity: Option<usize>) -> AnalysisHost {
139 AnalysisHost { db: RootDatabase::new(lru_capacity) } 139 AnalysisHost { db: RootDatabase::new(lru_capacity) }
140 } 140 }
141
142 pub fn update_lru_capacity(&mut self, lru_capacity: Option<usize>) {
143 self.db.update_lru_capacity(lru_capacity);
144 }
145
141 /// Returns a snapshot of the current state, which you can query for 146 /// Returns a snapshot of the current state, which you can query for
142 /// semantic information. 147 /// semantic information.
143 pub fn analysis(&self) -> Analysis { 148 pub fn analysis(&self) -> Analysis {
@@ -320,9 +325,9 @@ impl Analysis {
320 pub fn inlay_hints( 325 pub fn inlay_hints(
321 &self, 326 &self,
322 file_id: FileId, 327 file_id: FileId,
323 inlay_hint_opts: &InlayHintsOptions, 328 config: &InlayHintsConfig,
324 ) -> Cancelable<Vec<InlayHint>> { 329 ) -> Cancelable<Vec<InlayHint>> {
325 self.with_db(|db| inlay_hints::inlay_hints(db, file_id, inlay_hint_opts)) 330 self.with_db(|db| inlay_hints::inlay_hints(db, file_id, config))
326 } 331 }
327 332
328 /// Returns the set of folding ranges. 333 /// Returns the set of folding ranges.
@@ -445,9 +450,9 @@ impl Analysis {
445 pub fn completions( 450 pub fn completions(
446 &self, 451 &self,
447 position: FilePosition, 452 position: FilePosition,
448 options: &CompletionOptions, 453 config: &CompletionConfig,
449 ) -> Cancelable<Option<Vec<CompletionItem>>> { 454 ) -> Cancelable<Option<Vec<CompletionItem>>> {
450 self.with_db(|db| completion::completions(db, position, options).map(Into::into)) 455 self.with_db(|db| completion::completions(db, position, config).map(Into::into))
451 } 456 }
452 457
453 /// Computes assists (aka code actions aka intentions) for the given 458 /// Computes assists (aka code actions aka intentions) for the given
diff --git a/crates/ra_ide/src/ssr.rs b/crates/ra_ide/src/ssr.rs
index 1c9710a5d..1abb891c1 100644
--- a/crates/ra_ide/src/ssr.rs
+++ b/crates/ra_ide/src/ssr.rs
@@ -5,7 +5,7 @@ use ra_db::{SourceDatabase, SourceDatabaseExt};
5use ra_ide_db::symbol_index::SymbolsDatabase; 5use ra_ide_db::symbol_index::SymbolsDatabase;
6use ra_ide_db::RootDatabase; 6use ra_ide_db::RootDatabase;
7use ra_syntax::ast::make::try_expr_from_text; 7use ra_syntax::ast::make::try_expr_from_text;
8use ra_syntax::ast::{AstToken, Comment}; 8use ra_syntax::ast::{AstToken, Comment, RecordField, RecordLit};
9use ra_syntax::{AstNode, SyntaxElement, SyntaxNode}; 9use ra_syntax::{AstNode, SyntaxElement, SyntaxNode};
10use ra_text_edit::{TextEdit, TextEditBuilder}; 10use ra_text_edit::{TextEdit, TextEditBuilder};
11use rustc_hash::FxHashMap; 11use rustc_hash::FxHashMap;
@@ -186,47 +186,102 @@ fn create_name<'a>(name: &str, vars: &'a mut Vec<Var>) -> Result<&'a str, SsrErr
186} 186}
187 187
188fn find(pattern: &SsrPattern, code: &SyntaxNode) -> SsrMatches { 188fn find(pattern: &SsrPattern, code: &SyntaxNode) -> SsrMatches {
189 fn check_record_lit(
190 pattern: RecordLit,
191 code: RecordLit,
192 placeholders: &[Var],
193 match_: Match,
194 ) -> Option<Match> {
195 let match_ = check_opt_nodes(pattern.path(), code.path(), placeholders, match_)?;
196
197 let mut pattern_fields =
198 pattern.record_field_list().map(|x| x.fields().collect()).unwrap_or(vec![]);
199 let mut code_fields =
200 code.record_field_list().map(|x| x.fields().collect()).unwrap_or(vec![]);
201
202 if pattern_fields.len() != code_fields.len() {
203 return None;
204 }
205
206 let by_name = |a: &RecordField, b: &RecordField| {
207 a.name_ref()
208 .map(|x| x.syntax().text().to_string())
209 .cmp(&b.name_ref().map(|x| x.syntax().text().to_string()))
210 };
211 pattern_fields.sort_by(by_name);
212 code_fields.sort_by(by_name);
213
214 pattern_fields.into_iter().zip(code_fields.into_iter()).fold(
215 Some(match_),
216 |accum, (a, b)| {
217 accum.and_then(|match_| check_opt_nodes(Some(a), Some(b), placeholders, match_))
218 },
219 )
220 }
221
222 fn check_opt_nodes(
223 pattern: Option<impl AstNode>,
224 code: Option<impl AstNode>,
225 placeholders: &[Var],
226 match_: Match,
227 ) -> Option<Match> {
228 match (pattern, code) {
229 (Some(pattern), Some(code)) => check(
230 &SyntaxElement::from(pattern.syntax().clone()),
231 &SyntaxElement::from(code.syntax().clone()),
232 placeholders,
233 match_,
234 ),
235 (None, None) => Some(match_),
236 _ => None,
237 }
238 }
239
189 fn check( 240 fn check(
190 pattern: &SyntaxElement, 241 pattern: &SyntaxElement,
191 code: &SyntaxElement, 242 code: &SyntaxElement,
192 placeholders: &[Var], 243 placeholders: &[Var],
193 mut match_: Match, 244 mut match_: Match,
194 ) -> Option<Match> { 245 ) -> Option<Match> {
195 match (pattern, code) { 246 match (&pattern, &code) {
196 (SyntaxElement::Token(ref pattern), SyntaxElement::Token(ref code)) => { 247 (SyntaxElement::Token(pattern), SyntaxElement::Token(code)) => {
197 if pattern.text() == code.text() { 248 if pattern.text() == code.text() {
198 Some(match_) 249 Some(match_)
199 } else { 250 } else {
200 None 251 None
201 } 252 }
202 } 253 }
203 (SyntaxElement::Node(ref pattern), SyntaxElement::Node(ref code)) => { 254 (SyntaxElement::Node(pattern), SyntaxElement::Node(code)) => {
204 if placeholders.iter().any(|n| n.0.as_str() == pattern.text()) { 255 if placeholders.iter().any(|n| n.0.as_str() == pattern.text()) {
205 match_.binding.insert(Var(pattern.text().to_string()), code.clone()); 256 match_.binding.insert(Var(pattern.text().to_string()), code.clone());
206 Some(match_) 257 Some(match_)
207 } else { 258 } else {
208 let mut pattern_children = pattern 259 if let (Some(pattern), Some(code)) =
209 .children_with_tokens() 260 (RecordLit::cast(pattern.clone()), RecordLit::cast(code.clone()))
210 .filter(|element| !element.kind().is_trivia()); 261 {
211 let mut code_children = 262 check_record_lit(pattern, code, placeholders, match_)
212 code.children_with_tokens().filter(|element| !element.kind().is_trivia()); 263 } else {
213 let new_ignored_comments = code.children_with_tokens().filter_map(|element| { 264 let mut pattern_children = pattern
214 element.as_token().and_then(|token| Comment::cast(token.clone())) 265 .children_with_tokens()
215 }); 266 .filter(|element| !element.kind().is_trivia());
216 match_.ignored_comments.extend(new_ignored_comments); 267 let mut code_children = code
217 let match_from_children = pattern_children 268 .children_with_tokens()
218 .by_ref() 269 .filter(|element| !element.kind().is_trivia());
219 .zip(code_children.by_ref()) 270 let new_ignored_comments =
220 .fold(Some(match_), |accum, (a, b)| { 271 code.children_with_tokens().filter_map(|element| {
221 accum.and_then(|match_| check(&a, &b, placeholders, match_)) 272 element.as_token().and_then(|token| Comment::cast(token.clone()))
222 }); 273 });
223 match_from_children.and_then(|match_| { 274 match_.ignored_comments.extend(new_ignored_comments);
224 if pattern_children.count() == 0 && code_children.count() == 0 { 275 pattern_children
225 Some(match_) 276 .by_ref()
226 } else { 277 .zip(code_children.by_ref())
227 None 278 .fold(Some(match_), |accum, (a, b)| {
228 } 279 accum.and_then(|match_| check(&a, &b, placeholders, match_))
229 }) 280 })
281 .filter(|_| {
282 pattern_children.next().is_none() && code_children.next().is_none()
283 })
284 }
230 } 285 }
231 } 286 }
232 _ => None, 287 _ => None,
@@ -434,4 +489,13 @@ mod tests {
434 "fn main() { bar(5)/* using 5 */ }", 489 "fn main() { bar(5)/* using 5 */ }",
435 ) 490 )
436 } 491 }
492
493 #[test]
494 fn ssr_struct_lit() {
495 assert_ssr_transform(
496 "foo{a: $a:expr, b: $b:expr} ==>> foo::new($a, $b)",
497 "fn main() { foo{b:2, a:1} }",
498 "fn main() { foo::new(1, 2) }",
499 )
500 }
437} 501}
diff --git a/crates/ra_ide_db/src/lib.rs b/crates/ra_ide_db/src/lib.rs
index 4faeefa8d..e6f2d36e9 100644
--- a/crates/ra_ide_db/src/lib.rs
+++ b/crates/ra_ide_db/src/lib.rs
@@ -115,12 +115,16 @@ impl RootDatabase {
115 db.set_crate_graph_with_durability(Default::default(), Durability::HIGH); 115 db.set_crate_graph_with_durability(Default::default(), Durability::HIGH);
116 db.set_local_roots_with_durability(Default::default(), Durability::HIGH); 116 db.set_local_roots_with_durability(Default::default(), Durability::HIGH);
117 db.set_library_roots_with_durability(Default::default(), Durability::HIGH); 117 db.set_library_roots_with_durability(Default::default(), Durability::HIGH);
118 let lru_capacity = lru_capacity.unwrap_or(ra_db::DEFAULT_LRU_CAP); 118 db.update_lru_capacity(lru_capacity);
119 db.query_mut(ra_db::ParseQuery).set_lru_capacity(lru_capacity);
120 db.query_mut(hir::db::ParseMacroQuery).set_lru_capacity(lru_capacity);
121 db.query_mut(hir::db::MacroExpandQuery).set_lru_capacity(lru_capacity);
122 db 119 db
123 } 120 }
121
122 pub fn update_lru_capacity(&mut self, lru_capacity: Option<usize>) {
123 let lru_capacity = lru_capacity.unwrap_or(ra_db::DEFAULT_LRU_CAP);
124 self.query_mut(ra_db::ParseQuery).set_lru_capacity(lru_capacity);
125 self.query_mut(hir::db::ParseMacroQuery).set_lru_capacity(lru_capacity);
126 self.query_mut(hir::db::MacroExpandQuery).set_lru_capacity(lru_capacity);
127 }
124} 128}
125 129
126impl salsa::ParallelDatabase for RootDatabase { 130impl salsa::ParallelDatabase for RootDatabase {
diff --git a/crates/ra_proc_macro/Cargo.toml b/crates/ra_proc_macro/Cargo.toml
index bc2c37296..d009ceb82 100644
--- a/crates/ra_proc_macro/Cargo.toml
+++ b/crates/ra_proc_macro/Cargo.toml
@@ -10,3 +10,8 @@ doctest = false
10 10
11[dependencies] 11[dependencies]
12ra_tt = { path = "../ra_tt" } 12ra_tt = { path = "../ra_tt" }
13serde = { version = "1.0", features = ["derive"] }
14serde_json = "1.0"
15log = "0.4.8"
16crossbeam-channel = "0.4.0"
17jod-thread = "0.1.1"
diff --git a/crates/ra_proc_macro/src/lib.rs b/crates/ra_proc_macro/src/lib.rs
index 5e21dd487..51fbb046a 100644
--- a/crates/ra_proc_macro/src/lib.rs
+++ b/crates/ra_proc_macro/src/lib.rs
@@ -5,55 +5,104 @@
5//! is used to provide basic infrastructure for communication between two 5//! is used to provide basic infrastructure for communication between two
6//! processes: Client (RA itself), Server (the external program) 6//! processes: Client (RA itself), Server (the external program)
7 7
8mod rpc;
9mod process;
10pub mod msg;
11
12use process::{ProcMacroProcessSrv, ProcMacroProcessThread};
8use ra_tt::{SmolStr, Subtree}; 13use ra_tt::{SmolStr, Subtree};
14use rpc::ProcMacroKind;
9use std::{ 15use std::{
10 path::{Path, PathBuf}, 16 path::{Path, PathBuf},
11 sync::Arc, 17 sync::Arc,
12}; 18};
13 19
14#[derive(Debug, Clone, PartialEq, Eq)] 20pub use rpc::{ExpansionResult, ExpansionTask};
21
22#[derive(Debug, Clone)]
15pub struct ProcMacroProcessExpander { 23pub struct ProcMacroProcessExpander {
16 process: Arc<ProcMacroProcessSrv>, 24 process: Arc<ProcMacroProcessSrv>,
25 dylib_path: PathBuf,
17 name: SmolStr, 26 name: SmolStr,
18} 27}
19 28
29impl Eq for ProcMacroProcessExpander {}
30impl PartialEq for ProcMacroProcessExpander {
31 fn eq(&self, other: &Self) -> bool {
32 self.name == other.name
33 && self.dylib_path == other.dylib_path
34 && Arc::ptr_eq(&self.process, &other.process)
35 }
36}
37
20impl ra_tt::TokenExpander for ProcMacroProcessExpander { 38impl ra_tt::TokenExpander for ProcMacroProcessExpander {
21 fn expand( 39 fn expand(
22 &self, 40 &self,
23 _subtree: &Subtree, 41 subtree: &Subtree,
24 _attr: Option<&Subtree>, 42 _attr: Option<&Subtree>,
25 ) -> Result<Subtree, ra_tt::ExpansionError> { 43 ) -> Result<Subtree, ra_tt::ExpansionError> {
26 // FIXME: do nothing for now 44 self.process.custom_derive(&self.dylib_path, subtree, &self.name)
27 Ok(Subtree::default())
28 } 45 }
29} 46}
30 47
31#[derive(Debug, Clone, PartialEq, Eq)] 48#[derive(Debug)]
32pub struct ProcMacroProcessSrv { 49enum ProcMacroClientKind {
33 path: PathBuf, 50 Process { process: Arc<ProcMacroProcessSrv>, thread: ProcMacroProcessThread },
51 Dummy,
34} 52}
35 53
36#[derive(Debug, Clone, PartialEq, Eq)] 54#[derive(Debug)]
37pub enum ProcMacroClient { 55pub struct ProcMacroClient {
38 Process { process: Arc<ProcMacroProcessSrv> }, 56 kind: ProcMacroClientKind,
39 Dummy,
40} 57}
41 58
42impl ProcMacroClient { 59impl ProcMacroClient {
43 pub fn extern_process(process_path: &Path) -> ProcMacroClient { 60 pub fn extern_process(process_path: &Path) -> Result<ProcMacroClient, std::io::Error> {
44 let process = ProcMacroProcessSrv { path: process_path.into() }; 61 let (thread, process) = ProcMacroProcessSrv::run(process_path)?;
45 ProcMacroClient::Process { process: Arc::new(process) } 62 Ok(ProcMacroClient {
63 kind: ProcMacroClientKind::Process { process: Arc::new(process), thread },
64 })
46 } 65 }
47 66
48 pub fn dummy() -> ProcMacroClient { 67 pub fn dummy() -> ProcMacroClient {
49 ProcMacroClient::Dummy 68 ProcMacroClient { kind: ProcMacroClientKind::Dummy }
50 } 69 }
51 70
52 pub fn by_dylib_path( 71 pub fn by_dylib_path(
53 &self, 72 &self,
54 _dylib_path: &Path, 73 dylib_path: &Path,
55 ) -> Vec<(SmolStr, Arc<dyn ra_tt::TokenExpander>)> { 74 ) -> Vec<(SmolStr, Arc<dyn ra_tt::TokenExpander>)> {
56 // FIXME: return empty for now 75 match &self.kind {
57 vec![] 76 ProcMacroClientKind::Dummy => vec![],
77 ProcMacroClientKind::Process { process, .. } => {
78 let macros = match process.find_proc_macros(dylib_path) {
79 Err(err) => {
80 eprintln!("Fail to find proc macro. Error: {:#?}", err);
81 return vec![];
82 }
83 Ok(macros) => macros,
84 };
85
86 macros
87 .into_iter()
88 .filter_map(|(name, kind)| {
89 // FIXME: Support custom derive only for now.
90 match kind {
91 ProcMacroKind::CustomDerive => {
92 let name = SmolStr::new(&name);
93 let expander: Arc<dyn ra_tt::TokenExpander> =
94 Arc::new(ProcMacroProcessExpander {
95 process: process.clone(),
96 name: name.clone(),
97 dylib_path: dylib_path.into(),
98 });
99 Some((name, expander))
100 }
101 _ => None,
102 }
103 })
104 .collect()
105 }
106 }
58 } 107 }
59} 108}
diff --git a/crates/ra_proc_macro/src/msg.rs b/crates/ra_proc_macro/src/msg.rs
new file mode 100644
index 000000000..aa95bcc8f
--- /dev/null
+++ b/crates/ra_proc_macro/src/msg.rs
@@ -0,0 +1,93 @@
1//! Defines messages for cross-process message based on `ndjson` wire protocol
2
3use std::{
4 convert::TryFrom,
5 io::{self, BufRead, Write},
6};
7
8use crate::{
9 rpc::{ListMacrosResult, ListMacrosTask},
10 ExpansionResult, ExpansionTask,
11};
12use serde::{de::DeserializeOwned, Deserialize, Serialize};
13
14#[derive(Debug, Serialize, Deserialize, Clone)]
15pub enum Request {
16 ListMacro(ListMacrosTask),
17 ExpansionMacro(ExpansionTask),
18}
19
20#[derive(Debug, Serialize, Deserialize, Clone)]
21pub enum Response {
22 Error(ResponseError),
23 ListMacro(ListMacrosResult),
24 ExpansionMacro(ExpansionResult),
25}
26
27macro_rules! impl_try_from_response {
28 ($ty:ty, $tag:ident) => {
29 impl TryFrom<Response> for $ty {
30 type Error = &'static str;
31 fn try_from(value: Response) -> Result<Self, Self::Error> {
32 match value {
33 Response::$tag(res) => Ok(res),
34 _ => Err("Fail to convert from response"),
35 }
36 }
37 }
38 };
39}
40
41impl_try_from_response!(ListMacrosResult, ListMacro);
42impl_try_from_response!(ExpansionResult, ExpansionMacro);
43
44#[derive(Debug, Serialize, Deserialize, Clone)]
45pub struct ResponseError {
46 pub code: ErrorCode,
47 pub message: String,
48}
49
50#[derive(Debug, Serialize, Deserialize, Clone)]
51pub enum ErrorCode {
52 ServerErrorEnd,
53 ExpansionError,
54}
55
56pub trait Message: Sized + Serialize + DeserializeOwned {
57 fn read(r: &mut impl BufRead) -> io::Result<Option<Self>> {
58 let text = match read_json(r)? {
59 None => return Ok(None),
60 Some(text) => text,
61 };
62 let msg = serde_json::from_str(&text)?;
63 Ok(Some(msg))
64 }
65 fn write(self, w: &mut impl Write) -> io::Result<()> {
66 let text = serde_json::to_string(&self)?;
67 write_json(w, &text)
68 }
69}
70
71impl Message for Request {}
72impl Message for Response {}
73
74fn read_json(inp: &mut impl BufRead) -> io::Result<Option<String>> {
75 let mut buf = String::new();
76 if inp.read_line(&mut buf)? == 0 {
77 return Ok(None);
78 }
79 // Remove ending '\n'
80 let buf = &buf[..buf.len() - 1];
81 if buf.is_empty() {
82 return Ok(None);
83 }
84 Ok(Some(buf.to_string()))
85}
86
87fn write_json(out: &mut impl Write, msg: &str) -> io::Result<()> {
88 log::debug!("> {}", msg);
89 out.write_all(msg.as_bytes())?;
90 out.write_all(b"\n")?;
91 out.flush()?;
92 Ok(())
93}
diff --git a/crates/ra_proc_macro/src/process.rs b/crates/ra_proc_macro/src/process.rs
new file mode 100644
index 000000000..e8c85be38
--- /dev/null
+++ b/crates/ra_proc_macro/src/process.rs
@@ -0,0 +1,196 @@
1//! Handle process life-time and message passing for proc-macro client
2
3use crossbeam_channel::{bounded, Receiver, Sender};
4use ra_tt::Subtree;
5
6use crate::msg::{ErrorCode, Message, Request, Response, ResponseError};
7use crate::rpc::{ExpansionResult, ExpansionTask, ListMacrosResult, ListMacrosTask, ProcMacroKind};
8
9use io::{BufRead, BufReader};
10use std::{
11 convert::{TryFrom, TryInto},
12 io::{self, Write},
13 path::{Path, PathBuf},
14 process::{Child, Command, Stdio},
15 sync::{Arc, Weak},
16};
17
18#[derive(Debug, Default)]
19pub(crate) struct ProcMacroProcessSrv {
20 inner: Option<Weak<Sender<Task>>>,
21}
22
23#[derive(Debug)]
24pub(crate) struct ProcMacroProcessThread {
25 // XXX: drop order is significant
26 sender: Arc<Sender<Task>>,
27 handle: jod_thread::JoinHandle<()>,
28}
29
30struct Task {
31 req: Request,
32 result_tx: Sender<Option<Response>>,
33}
34
35struct Process {
36 path: PathBuf,
37 child: Child,
38}
39
40impl Drop for Process {
41 fn drop(&mut self) {
42 let _ = self.child.kill();
43 }
44}
45
46impl Process {
47 fn run(process_path: &Path) -> Result<Process, io::Error> {
48 let child = Command::new(process_path.clone())
49 .stdin(Stdio::piped())
50 .stdout(Stdio::piped())
51 .stderr(Stdio::null())
52 .spawn()?;
53
54 Ok(Process { path: process_path.into(), child })
55 }
56
57 fn restart(&mut self) -> Result<(), io::Error> {
58 let _ = self.child.kill();
59 self.child = Command::new(self.path.clone())
60 .stdin(Stdio::piped())
61 .stdout(Stdio::piped())
62 .stderr(Stdio::null())
63 .spawn()?;
64 Ok(())
65 }
66
67 fn stdio(&mut self) -> Option<(impl Write, impl BufRead)> {
68 let stdin = self.child.stdin.take()?;
69 let stdout = self.child.stdout.take()?;
70 let read = BufReader::new(stdout);
71
72 Some((stdin, read))
73 }
74}
75
76impl ProcMacroProcessSrv {
77 pub fn run(
78 process_path: &Path,
79 ) -> Result<(ProcMacroProcessThread, ProcMacroProcessSrv), io::Error> {
80 let process = Process::run(process_path)?;
81
82 let (task_tx, task_rx) = bounded(0);
83 let handle = jod_thread::spawn(move || {
84 client_loop(task_rx, process);
85 });
86
87 let task_tx = Arc::new(task_tx);
88 let srv = ProcMacroProcessSrv { inner: Some(Arc::downgrade(&task_tx)) };
89 let thread = ProcMacroProcessThread { handle, sender: task_tx };
90
91 Ok((thread, srv))
92 }
93
94 pub fn find_proc_macros(
95 &self,
96 dylib_path: &Path,
97 ) -> Result<Vec<(String, ProcMacroKind)>, ra_tt::ExpansionError> {
98 let task = ListMacrosTask { lib: dylib_path.to_path_buf() };
99
100 let result: ListMacrosResult = self.send_task(Request::ListMacro(task))?;
101 Ok(result.macros)
102 }
103
104 pub fn custom_derive(
105 &self,
106 dylib_path: &Path,
107 subtree: &Subtree,
108 derive_name: &str,
109 ) -> Result<Subtree, ra_tt::ExpansionError> {
110 let task = ExpansionTask {
111 macro_body: subtree.clone(),
112 macro_name: derive_name.to_string(),
113 attributes: None,
114 lib: dylib_path.to_path_buf(),
115 };
116
117 let result: ExpansionResult = self.send_task(Request::ExpansionMacro(task))?;
118 Ok(result.expansion)
119 }
120
121 pub fn send_task<R>(&self, req: Request) -> Result<R, ra_tt::ExpansionError>
122 where
123 R: TryFrom<Response, Error = &'static str>,
124 {
125 let sender = match &self.inner {
126 None => return Err(ra_tt::ExpansionError::Unknown("No sender is found.".to_string())),
127 Some(it) => it,
128 };
129
130 let (result_tx, result_rx) = bounded(0);
131 let sender = match sender.upgrade() {
132 None => {
133 return Err(ra_tt::ExpansionError::Unknown("Proc macro process is closed.".into()))
134 }
135 Some(it) => it,
136 };
137 sender.send(Task { req: req.into(), result_tx }).unwrap();
138 let res = result_rx
139 .recv()
140 .map_err(|_| ra_tt::ExpansionError::Unknown("Proc macro thread is closed.".into()))?;
141
142 match res {
143 Some(Response::Error(err)) => {
144 return Err(ra_tt::ExpansionError::ExpansionError(err.message));
145 }
146 Some(res) => Ok(res.try_into().map_err(|err| {
147 ra_tt::ExpansionError::Unknown(format!(
148 "Fail to get response, reason : {:#?} ",
149 err
150 ))
151 })?),
152 None => Err(ra_tt::ExpansionError::Unknown("Empty result".into())),
153 }
154 }
155}
156
157fn client_loop(task_rx: Receiver<Task>, mut process: Process) {
158 let (mut stdin, mut stdout) = match process.stdio() {
159 None => return,
160 Some(it) => it,
161 };
162
163 for task in task_rx {
164 let Task { req, result_tx } = task;
165
166 match send_request(&mut stdin, &mut stdout, req) {
167 Ok(res) => result_tx.send(res).unwrap(),
168 Err(_err) => {
169 let res = Response::Error(ResponseError {
170 code: ErrorCode::ServerErrorEnd,
171 message: "Server closed".into(),
172 });
173 result_tx.send(res.into()).unwrap();
174 // Restart the process
175 if process.restart().is_err() {
176 break;
177 }
178 let stdio = match process.stdio() {
179 None => break,
180 Some(it) => it,
181 };
182 stdin = stdio.0;
183 stdout = stdio.1;
184 }
185 }
186 }
187}
188
189fn send_request(
190 mut writer: &mut impl Write,
191 mut reader: &mut impl BufRead,
192 req: Request,
193) -> Result<Option<Response>, io::Error> {
194 req.write(&mut writer)?;
195 Ok(Response::read(&mut reader)?)
196}
diff --git a/crates/ra_proc_macro/src/rpc.rs b/crates/ra_proc_macro/src/rpc.rs
new file mode 100644
index 000000000..66b3f55db
--- /dev/null
+++ b/crates/ra_proc_macro/src/rpc.rs
@@ -0,0 +1,266 @@
1//! Data struture serialization related stuffs for RPC
2//!
3//! Define all necessary rpc serialization data structure,
4//! which include ra_tt related data and some task messages.
5//! Although adding Serialize and Deserialize trait to ra_tt directly seem to be much easier,
6//! we deliberately duplicate the ra_tt struct with #[serde(with = "XXDef")]
7//! for separation of code responsibility.
8
9use ra_tt::{
10 Delimiter, DelimiterKind, Ident, Leaf, Literal, Punct, SmolStr, Spacing, Subtree, TokenId,
11 TokenTree,
12};
13use serde::{Deserialize, Serialize};
14use std::path::PathBuf;
15
16#[derive(Clone, Eq, PartialEq, Debug, Serialize, Deserialize)]
17pub struct ListMacrosTask {
18 pub lib: PathBuf,
19}
20
21#[derive(Clone, Eq, PartialEq, Debug, Serialize, Deserialize)]
22pub enum ProcMacroKind {
23 CustomDerive,
24 FuncLike,
25 Attr,
26}
27
28#[derive(Clone, Eq, PartialEq, Debug, Default, Serialize, Deserialize)]
29pub struct ListMacrosResult {
30 pub macros: Vec<(String, ProcMacroKind)>,
31}
32
33#[derive(Clone, Eq, PartialEq, Debug, Serialize, Deserialize)]
34pub struct ExpansionTask {
35 /// Argument of macro call.
36 ///
37 /// In custom derive that would be a struct or enum; in attribute-like macro - underlying
38 /// item; in function-like macro - the macro body.
39 #[serde(with = "SubtreeDef")]
40 pub macro_body: Subtree,
41
42 /// Names of macros to expand.
43 ///
44 /// In custom derive those are names of derived traits (`Serialize`, `Getters`, etc.). In
45 /// attribute-like and functiona-like macros - single name of macro itself (`show_streams`).
46 pub macro_name: String,
47
48 /// Possible attributes for the attribute-like macros.
49 #[serde(with = "opt_subtree_def")]
50 pub attributes: Option<Subtree>,
51
52 pub lib: PathBuf,
53}
54
55#[derive(Clone, Eq, PartialEq, Debug, Default, Serialize, Deserialize)]
56pub struct ExpansionResult {
57 #[serde(with = "SubtreeDef")]
58 pub expansion: Subtree,
59}
60
61#[derive(Serialize, Deserialize)]
62#[serde(remote = "DelimiterKind")]
63enum DelimiterKindDef {
64 Parenthesis,
65 Brace,
66 Bracket,
67}
68
69#[derive(Serialize, Deserialize)]
70#[serde(remote = "TokenId")]
71struct TokenIdDef(u32);
72
73#[derive(Serialize, Deserialize)]
74#[serde(remote = "Delimiter")]
75struct DelimiterDef {
76 #[serde(with = "TokenIdDef")]
77 pub id: TokenId,
78 #[serde(with = "DelimiterKindDef")]
79 pub kind: DelimiterKind,
80}
81
82#[derive(Serialize, Deserialize)]
83#[serde(remote = "Subtree")]
84struct SubtreeDef {
85 #[serde(default, with = "opt_delimiter_def")]
86 pub delimiter: Option<Delimiter>,
87 #[serde(with = "vec_token_tree")]
88 pub token_trees: Vec<TokenTree>,
89}
90
91#[derive(Serialize, Deserialize)]
92#[serde(remote = "TokenTree")]
93enum TokenTreeDef {
94 #[serde(with = "LeafDef")]
95 Leaf(Leaf),
96 #[serde(with = "SubtreeDef")]
97 Subtree(Subtree),
98}
99
100#[derive(Serialize, Deserialize)]
101#[serde(remote = "Leaf")]
102enum LeafDef {
103 #[serde(with = "LiteralDef")]
104 Literal(Literal),
105 #[serde(with = "PunctDef")]
106 Punct(Punct),
107 #[serde(with = "IdentDef")]
108 Ident(Ident),
109}
110
111#[derive(Serialize, Deserialize)]
112#[serde(remote = "Literal")]
113struct LiteralDef {
114 pub text: SmolStr,
115 #[serde(with = "TokenIdDef")]
116 pub id: TokenId,
117}
118
119#[derive(Serialize, Deserialize)]
120#[serde(remote = "Punct")]
121struct PunctDef {
122 pub char: char,
123 #[serde(with = "SpacingDef")]
124 pub spacing: Spacing,
125 #[serde(with = "TokenIdDef")]
126 pub id: TokenId,
127}
128
129#[derive(Serialize, Deserialize)]
130#[serde(remote = "Spacing")]
131enum SpacingDef {
132 Alone,
133 Joint,
134}
135
136#[derive(Serialize, Deserialize)]
137#[serde(remote = "Ident")]
138struct IdentDef {
139 pub text: SmolStr,
140 #[serde(with = "TokenIdDef")]
141 pub id: TokenId,
142}
143
144mod opt_delimiter_def {
145 use super::{Delimiter, DelimiterDef};
146 use serde::{Deserialize, Deserializer, Serialize, Serializer};
147
148 pub fn serialize<S>(value: &Option<Delimiter>, serializer: S) -> Result<S::Ok, S::Error>
149 where
150 S: Serializer,
151 {
152 #[derive(Serialize)]
153 struct Helper<'a>(#[serde(with = "DelimiterDef")] &'a Delimiter);
154 value.as_ref().map(Helper).serialize(serializer)
155 }
156
157 pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<Delimiter>, D::Error>
158 where
159 D: Deserializer<'de>,
160 {
161 #[derive(Deserialize)]
162 struct Helper(#[serde(with = "DelimiterDef")] Delimiter);
163 let helper = Option::deserialize(deserializer)?;
164 Ok(helper.map(|Helper(external)| external))
165 }
166}
167
168mod opt_subtree_def {
169 use super::{Subtree, SubtreeDef};
170 use serde::{Deserialize, Deserializer, Serialize, Serializer};
171
172 pub fn serialize<S>(value: &Option<Subtree>, serializer: S) -> Result<S::Ok, S::Error>
173 where
174 S: Serializer,
175 {
176 #[derive(Serialize)]
177 struct Helper<'a>(#[serde(with = "SubtreeDef")] &'a Subtree);
178 value.as_ref().map(Helper).serialize(serializer)
179 }
180
181 pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<Subtree>, D::Error>
182 where
183 D: Deserializer<'de>,
184 {
185 #[derive(Deserialize)]
186 struct Helper(#[serde(with = "SubtreeDef")] Subtree);
187 let helper = Option::deserialize(deserializer)?;
188 Ok(helper.map(|Helper(external)| external))
189 }
190}
191
192mod vec_token_tree {
193 use super::{TokenTree, TokenTreeDef};
194 use serde::{ser::SerializeSeq, Deserialize, Deserializer, Serialize, Serializer};
195
196 pub fn serialize<S>(value: &Vec<TokenTree>, serializer: S) -> Result<S::Ok, S::Error>
197 where
198 S: Serializer,
199 {
200 #[derive(Serialize)]
201 struct Helper<'a>(#[serde(with = "TokenTreeDef")] &'a TokenTree);
202
203 let items: Vec<_> = value.iter().map(Helper).collect();
204 let mut seq = serializer.serialize_seq(Some(items.len()))?;
205 for element in items {
206 seq.serialize_element(&element)?;
207 }
208 seq.end()
209 }
210
211 pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<TokenTree>, D::Error>
212 where
213 D: Deserializer<'de>,
214 {
215 #[derive(Deserialize)]
216 struct Helper(#[serde(with = "TokenTreeDef")] TokenTree);
217
218 let helper = Vec::deserialize(deserializer)?;
219 Ok(helper.into_iter().map(|Helper(external)| external).collect())
220 }
221}
222
223#[cfg(test)]
224mod tests {
225 use super::*;
226
227 fn fixture_token_tree() -> Subtree {
228 let mut subtree = Subtree::default();
229 subtree
230 .token_trees
231 .push(TokenTree::Leaf(Ident { text: "struct".into(), id: TokenId(0) }.into()));
232 subtree
233 .token_trees
234 .push(TokenTree::Leaf(Ident { text: "Foo".into(), id: TokenId(1) }.into()));
235 subtree.token_trees.push(TokenTree::Subtree(
236 Subtree {
237 delimiter: Some(Delimiter { id: TokenId(2), kind: DelimiterKind::Brace }),
238 token_trees: vec![],
239 }
240 .into(),
241 ));
242 subtree
243 }
244
245 #[test]
246 fn test_proc_macro_rpc_works() {
247 let tt = fixture_token_tree();
248 let task = ExpansionTask {
249 macro_body: tt.clone(),
250 macro_name: Default::default(),
251 attributes: None,
252 lib: Default::default(),
253 };
254
255 let json = serde_json::to_string(&task).unwrap();
256 let back: ExpansionTask = serde_json::from_str(&json).unwrap();
257
258 assert_eq!(task.macro_body, back.macro_body);
259
260 let result = ExpansionResult { expansion: tt.clone() };
261 let json = serde_json::to_string(&result).unwrap();
262 let back: ExpansionResult = serde_json::from_str(&json).unwrap();
263
264 assert_eq!(result, back);
265 }
266}
diff --git a/crates/ra_prof/src/lib.rs b/crates/ra_prof/src/lib.rs
index 9e167db96..00ea3a9b0 100644
--- a/crates/ra_prof/src/lib.rs
+++ b/crates/ra_prof/src/lib.rs
@@ -339,6 +339,14 @@ pub fn print_backtrace() {
339 let bt = backtrace::Backtrace::new(); 339 let bt = backtrace::Backtrace::new();
340 eprintln!("{:?}", bt); 340 eprintln!("{:?}", bt);
341} 341}
342#[cfg(not(feature = "backtrace"))]
343pub fn print_backtrace() {
344 eprintln!(
345 r#"enable the backtrace feature:
346 ra_prof = {{ path = "../ra_prof", features = [ "backtrace"] }}
347"#
348 );
349}
342 350
343thread_local!(static IN_SCOPE: RefCell<bool> = RefCell::new(false)); 351thread_local!(static IN_SCOPE: RefCell<bool> = RefCell::new(false));
344 352
diff --git a/crates/ra_project_model/Cargo.toml b/crates/ra_project_model/Cargo.toml
index cdcdd63c9..b10644b4b 100644
--- a/crates/ra_project_model/Cargo.toml
+++ b/crates/ra_project_model/Cargo.toml
@@ -16,7 +16,6 @@ cargo_metadata = "0.9.1"
16ra_arena = { path = "../ra_arena" } 16ra_arena = { path = "../ra_arena" }
17ra_db = { path = "../ra_db" } 17ra_db = { path = "../ra_db" }
18ra_cfg = { path = "../ra_cfg" } 18ra_cfg = { path = "../ra_cfg" }
19ra_cargo_watch = { path = "../ra_cargo_watch" }
20ra_proc_macro = { path = "../ra_proc_macro" } 19ra_proc_macro = { path = "../ra_proc_macro" }
21 20
22serde = { version = "1.0.104", features = ["derive"] } 21serde = { version = "1.0.104", features = ["derive"] }
diff --git a/crates/ra_project_model/src/cargo_workspace.rs b/crates/ra_project_model/src/cargo_workspace.rs
index 291594e2a..b50cda06f 100644
--- a/crates/ra_project_model/src/cargo_workspace.rs
+++ b/crates/ra_project_model/src/cargo_workspace.rs
@@ -1,17 +1,18 @@
1//! FIXME: write short doc here 1//! FIXME: write short doc here
2 2
3use std::{ 3use std::{
4 env,
5 ffi::OsStr,
4 ops, 6 ops,
5 path::{Path, PathBuf}, 7 path::{Path, PathBuf},
8 process::Command,
6}; 9};
7 10
8use anyhow::{Context, Result}; 11use anyhow::{Context, Result};
9use cargo_metadata::{BuildScript, CargoOpt, Message, MetadataCommand, PackageId}; 12use cargo_metadata::{BuildScript, CargoOpt, Message, MetadataCommand, PackageId};
10use ra_arena::{Arena, Idx}; 13use ra_arena::{Arena, Idx};
11use ra_cargo_watch::run_cargo;
12use ra_db::Edition; 14use ra_db::Edition;
13use rustc_hash::FxHashMap; 15use rustc_hash::FxHashMap;
14use serde::Deserialize;
15 16
16/// `CargoWorkspace` represents the logical structure of, well, a Cargo 17/// `CargoWorkspace` represents the logical structure of, well, a Cargo
17/// workspace. It pretty closely mirrors `cargo metadata` output. 18/// workspace. It pretty closely mirrors `cargo metadata` output.
@@ -41,9 +42,8 @@ impl ops::Index<Target> for CargoWorkspace {
41 } 42 }
42} 43}
43 44
44#[derive(Deserialize, Clone, Debug, PartialEq, Eq)] 45#[derive(Clone, Debug, PartialEq, Eq)]
45#[serde(rename_all = "camelCase", default)] 46pub struct CargoConfig {
46pub struct CargoFeatures {
47 /// Do not activate the `default` feature. 47 /// Do not activate the `default` feature.
48 pub no_default_features: bool, 48 pub no_default_features: bool,
49 49
@@ -58,9 +58,9 @@ pub struct CargoFeatures {
58 pub load_out_dirs_from_check: bool, 58 pub load_out_dirs_from_check: bool,
59} 59}
60 60
61impl Default for CargoFeatures { 61impl Default for CargoConfig {
62 fn default() -> Self { 62 fn default() -> Self {
63 CargoFeatures { 63 CargoConfig {
64 no_default_features: false, 64 no_default_features: false,
65 all_features: true, 65 all_features: true,
66 features: Vec::new(), 66 features: Vec::new(),
@@ -75,6 +75,7 @@ pub type Target = Idx<TargetData>;
75 75
76#[derive(Debug, Clone)] 76#[derive(Debug, Clone)]
77pub struct PackageData { 77pub struct PackageData {
78 pub version: String,
78 pub name: String, 79 pub name: String,
79 pub manifest: PathBuf, 80 pub manifest: PathBuf,
80 pub targets: Vec<Target>, 81 pub targets: Vec<Target>,
@@ -138,7 +139,7 @@ impl PackageData {
138impl CargoWorkspace { 139impl CargoWorkspace {
139 pub fn from_cargo_metadata( 140 pub fn from_cargo_metadata(
140 cargo_toml: &Path, 141 cargo_toml: &Path,
141 cargo_features: &CargoFeatures, 142 cargo_features: &CargoConfig,
142 ) -> Result<CargoWorkspace> { 143 ) -> Result<CargoWorkspace> {
143 let mut meta = MetadataCommand::new(); 144 let mut meta = MetadataCommand::new();
144 meta.manifest_path(cargo_toml); 145 meta.manifest_path(cargo_toml);
@@ -161,7 +162,7 @@ impl CargoWorkspace {
161 let mut out_dir_by_id = FxHashMap::default(); 162 let mut out_dir_by_id = FxHashMap::default();
162 let mut proc_macro_dylib_paths = FxHashMap::default(); 163 let mut proc_macro_dylib_paths = FxHashMap::default();
163 if cargo_features.load_out_dirs_from_check { 164 if cargo_features.load_out_dirs_from_check {
164 let resources = load_extern_resources(cargo_toml, cargo_features); 165 let resources = load_extern_resources(cargo_toml, cargo_features)?;
165 out_dir_by_id = resources.out_dirs; 166 out_dir_by_id = resources.out_dirs;
166 proc_macro_dylib_paths = resources.proc_dylib_paths; 167 proc_macro_dylib_paths = resources.proc_dylib_paths;
167 } 168 }
@@ -173,13 +174,15 @@ impl CargoWorkspace {
173 let ws_members = &meta.workspace_members; 174 let ws_members = &meta.workspace_members;
174 175
175 for meta_pkg in meta.packages { 176 for meta_pkg in meta.packages {
176 let cargo_metadata::Package { id, edition, name, manifest_path, .. } = meta_pkg; 177 let cargo_metadata::Package { id, edition, name, manifest_path, version, .. } =
178 meta_pkg;
177 let is_member = ws_members.contains(&id); 179 let is_member = ws_members.contains(&id);
178 let edition = edition 180 let edition = edition
179 .parse::<Edition>() 181 .parse::<Edition>()
180 .with_context(|| format!("Failed to parse edition {}", edition))?; 182 .with_context(|| format!("Failed to parse edition {}", edition))?;
181 let pkg = packages.alloc(PackageData { 183 let pkg = packages.alloc(PackageData {
182 name, 184 name,
185 version: version.to_string(),
183 manifest: manifest_path, 186 manifest: manifest_path,
184 targets: Vec::new(), 187 targets: Vec::new(),
185 is_member, 188 is_member,
@@ -249,6 +252,18 @@ impl CargoWorkspace {
249 pub fn workspace_root(&self) -> &Path { 252 pub fn workspace_root(&self) -> &Path {
250 &self.workspace_root 253 &self.workspace_root
251 } 254 }
255
256 pub fn package_flag(&self, package: &PackageData) -> String {
257 if self.is_unique(&*package.name) {
258 package.name.clone()
259 } else {
260 format!("{}:{}", package.name, package.version)
261 }
262 }
263
264 fn is_unique(&self, name: &str) -> bool {
265 self.packages.iter().filter(|(_, v)| v.name == name).count() == 1
266 }
252} 267}
253 268
254#[derive(Debug, Clone, Default)] 269#[derive(Debug, Clone, Default)]
@@ -257,48 +272,60 @@ pub struct ExternResources {
257 proc_dylib_paths: FxHashMap<PackageId, PathBuf>, 272 proc_dylib_paths: FxHashMap<PackageId, PathBuf>,
258} 273}
259 274
260pub fn load_extern_resources(cargo_toml: &Path, cargo_features: &CargoFeatures) -> ExternResources { 275pub fn load_extern_resources(
261 let mut args: Vec<String> = vec![ 276 cargo_toml: &Path,
262 "check".to_string(), 277 cargo_features: &CargoConfig,
263 "--message-format=json".to_string(), 278) -> Result<ExternResources> {
264 "--manifest-path".to_string(), 279 let mut cmd = Command::new(cargo_binary());
265 cargo_toml.display().to_string(), 280 cmd.args(&["check", "--message-format=json", "--manifest-path"]).arg(cargo_toml);
266 ];
267
268 if cargo_features.all_features { 281 if cargo_features.all_features {
269 args.push("--all-features".to_string()); 282 cmd.arg("--all-features");
270 } else if cargo_features.no_default_features { 283 } else if cargo_features.no_default_features {
271 // FIXME: `NoDefaultFeatures` is mutual exclusive with `SomeFeatures` 284 // FIXME: `NoDefaultFeatures` is mutual exclusive with `SomeFeatures`
272 // https://github.com/oli-obk/cargo_metadata/issues/79 285 // https://github.com/oli-obk/cargo_metadata/issues/79
273 args.push("--no-default-features".to_string()); 286 cmd.arg("--no-default-features");
274 } else { 287 } else {
275 args.extend(cargo_features.features.iter().cloned()); 288 cmd.args(&cargo_features.features);
276 } 289 }
277 290
278 let mut acc = ExternResources::default(); 291 let output = cmd.output()?;
279 let res = run_cargo(&args, cargo_toml.parent(), &mut |message| {
280 match message {
281 Message::BuildScriptExecuted(BuildScript { package_id, out_dir, .. }) => {
282 acc.out_dirs.insert(package_id, out_dir);
283 }
284 292
285 Message::CompilerArtifact(message) => { 293 let mut res = ExternResources::default();
286 if message.target.kind.contains(&"proc-macro".to_string()) { 294
287 let package_id = message.package_id; 295 for message in cargo_metadata::parse_messages(output.stdout.as_slice()) {
288 if let Some(filename) = message.filenames.get(0) { 296 if let Ok(message) = message {
289 acc.proc_dylib_paths.insert(package_id, filename.clone()); 297 match message {
298 Message::BuildScriptExecuted(BuildScript { package_id, out_dir, .. }) => {
299 res.out_dirs.insert(package_id, out_dir);
300 }
301
302 Message::CompilerArtifact(message) => {
303 if message.target.kind.contains(&"proc-macro".to_string()) {
304 let package_id = message.package_id;
305 // Skip rmeta file
306 if let Some(filename) =
307 message.filenames.iter().filter(|name| is_dylib(name)).next()
308 {
309 res.proc_dylib_paths.insert(package_id, filename.clone());
310 }
290 } 311 }
291 } 312 }
313 Message::CompilerMessage(_) => (),
314 Message::Unknown => (),
292 } 315 }
293 Message::CompilerMessage(_) => (),
294 Message::Unknown => (),
295 } 316 }
296 true 317 }
297 }); 318 Ok(res)
319}
298 320
299 if let Err(err) = res { 321// FIXME: File a better way to know if it is a dylib
300 log::error!("Failed to load outdirs: {:?}", err); 322fn is_dylib(path: &Path) -> bool {
323 match path.extension().and_then(OsStr::to_str).map(|it| it.to_string().to_lowercase()) {
324 None => false,
325 Some(ext) => matches!(ext.as_str(), "dll" | "dylib" | "so"),
301 } 326 }
327}
302 328
303 acc 329fn cargo_binary() -> String {
330 env::var("CARGO").unwrap_or_else(|_| "cargo".to_string())
304} 331}
diff --git a/crates/ra_project_model/src/lib.rs b/crates/ra_project_model/src/lib.rs
index 444d3bb3f..0ab64a1e0 100644
--- a/crates/ra_project_model/src/lib.rs
+++ b/crates/ra_project_model/src/lib.rs
@@ -19,7 +19,7 @@ use rustc_hash::FxHashMap;
19use serde_json::from_reader; 19use serde_json::from_reader;
20 20
21pub use crate::{ 21pub use crate::{
22 cargo_workspace::{CargoFeatures, CargoWorkspace, Package, Target, TargetKind}, 22 cargo_workspace::{CargoConfig, CargoWorkspace, Package, Target, TargetKind},
23 json_project::JsonProject, 23 json_project::JsonProject,
24 sysroot::Sysroot, 24 sysroot::Sysroot,
25}; 25};
@@ -62,30 +62,30 @@ pub struct PackageRoot {
62 /// Is a member of the current workspace 62 /// Is a member of the current workspace
63 is_member: bool, 63 is_member: bool,
64} 64}
65
66impl PackageRoot { 65impl PackageRoot {
67 pub fn new(path: PathBuf, is_member: bool) -> PackageRoot { 66 pub fn new_member(path: PathBuf) -> PackageRoot {
68 PackageRoot { path, is_member } 67 Self { path, is_member: true }
69 } 68 }
70 69 pub fn new_non_member(path: PathBuf) -> PackageRoot {
71 pub fn path(&self) -> &PathBuf { 70 Self { path, is_member: false }
71 }
72 pub fn path(&self) -> &Path {
72 &self.path 73 &self.path
73 } 74 }
74
75 pub fn is_member(&self) -> bool { 75 pub fn is_member(&self) -> bool {
76 self.is_member 76 self.is_member
77 } 77 }
78} 78}
79 79
80impl ProjectWorkspace { 80impl ProjectWorkspace {
81 pub fn discover(path: &Path, cargo_features: &CargoFeatures) -> Result<ProjectWorkspace> { 81 pub fn discover(path: &Path, cargo_features: &CargoConfig) -> Result<ProjectWorkspace> {
82 ProjectWorkspace::discover_with_sysroot(path, true, cargo_features) 82 ProjectWorkspace::discover_with_sysroot(path, true, cargo_features)
83 } 83 }
84 84
85 pub fn discover_with_sysroot( 85 pub fn discover_with_sysroot(
86 path: &Path, 86 path: &Path,
87 with_sysroot: bool, 87 with_sysroot: bool,
88 cargo_features: &CargoFeatures, 88 cargo_features: &CargoConfig,
89 ) -> Result<ProjectWorkspace> { 89 ) -> Result<ProjectWorkspace> {
90 match find_rust_project_json(path) { 90 match find_rust_project_json(path) {
91 Some(json_path) => { 91 Some(json_path) => {
@@ -130,70 +130,45 @@ impl ProjectWorkspace {
130 pub fn to_roots(&self) -> Vec<PackageRoot> { 130 pub fn to_roots(&self) -> Vec<PackageRoot> {
131 match self { 131 match self {
132 ProjectWorkspace::Json { project } => { 132 ProjectWorkspace::Json { project } => {
133 let mut roots = Vec::with_capacity(project.roots.len()); 133 project.roots.iter().map(|r| PackageRoot::new_member(r.path.clone())).collect()
134 for root in &project.roots {
135 roots.push(PackageRoot::new(root.path.clone(), true));
136 }
137 roots
138 }
139 ProjectWorkspace::Cargo { cargo, sysroot } => {
140 let mut roots = Vec::with_capacity(cargo.packages().len() + sysroot.crates().len());
141 for pkg in cargo.packages() {
142 let root = cargo[pkg].root().to_path_buf();
143 let member = cargo[pkg].is_member;
144 roots.push(PackageRoot::new(root, member));
145 }
146 for krate in sysroot.crates() {
147 roots.push(PackageRoot::new(sysroot[krate].root_dir().to_path_buf(), false))
148 }
149 roots
150 } 134 }
135 ProjectWorkspace::Cargo { cargo, sysroot } => cargo
136 .packages()
137 .map(|pkg| PackageRoot {
138 path: cargo[pkg].root().to_path_buf(),
139 is_member: cargo[pkg].is_member,
140 })
141 .chain(sysroot.crates().map(|krate| {
142 PackageRoot::new_non_member(sysroot[krate].root_dir().to_path_buf())
143 }))
144 .collect(),
151 } 145 }
152 } 146 }
153 147
154 pub fn out_dirs(&self) -> Vec<PathBuf> { 148 pub fn out_dirs(&self) -> Vec<PathBuf> {
155 match self { 149 match self {
156 ProjectWorkspace::Json { project } => { 150 ProjectWorkspace::Json { project } => {
157 let mut out_dirs = Vec::with_capacity(project.crates.len()); 151 project.crates.iter().filter_map(|krate| krate.out_dir.as_ref()).cloned().collect()
158 for krate in &project.crates {
159 if let Some(out_dir) = &krate.out_dir {
160 out_dirs.push(out_dir.to_path_buf());
161 }
162 }
163 out_dirs
164 } 152 }
165 ProjectWorkspace::Cargo { cargo, sysroot: _sysroot } => { 153 ProjectWorkspace::Cargo { cargo, sysroot: _ } => {
166 let mut out_dirs = Vec::with_capacity(cargo.packages().len()); 154 cargo.packages().filter_map(|pkg| cargo[pkg].out_dir.as_ref()).cloned().collect()
167 for pkg in cargo.packages() {
168 if let Some(out_dir) = &cargo[pkg].out_dir {
169 out_dirs.push(out_dir.to_path_buf());
170 }
171 }
172 out_dirs
173 } 155 }
174 } 156 }
175 } 157 }
176 158
177 pub fn proc_macro_dylib_paths(&self) -> Vec<PathBuf> { 159 pub fn proc_macro_dylib_paths(&self) -> Vec<PathBuf> {
178 match self { 160 match self {
179 ProjectWorkspace::Json { project } => { 161 ProjectWorkspace::Json { project } => project
180 let mut proc_macro_dylib_paths = Vec::with_capacity(project.crates.len()); 162 .crates
181 for krate in &project.crates { 163 .iter()
182 if let Some(out_dir) = &krate.proc_macro_dylib_path { 164 .filter_map(|krate| krate.proc_macro_dylib_path.as_ref())
183 proc_macro_dylib_paths.push(out_dir.to_path_buf()); 165 .cloned()
184 } 166 .collect(),
185 } 167 ProjectWorkspace::Cargo { cargo, sysroot: _sysroot } => cargo
186 proc_macro_dylib_paths 168 .packages()
187 } 169 .filter_map(|pkg| cargo[pkg].proc_macro_dylib_path.as_ref())
188 ProjectWorkspace::Cargo { cargo, sysroot: _sysroot } => { 170 .cloned()
189 let mut proc_macro_dylib_paths = Vec::with_capacity(cargo.packages().len()); 171 .collect(),
190 for pkg in cargo.packages() {
191 if let Some(dylib_path) = &cargo[pkg].proc_macro_dylib_path {
192 proc_macro_dylib_paths.push(dylib_path.to_path_buf());
193 }
194 }
195 proc_macro_dylib_paths
196 }
197 } 172 }
198 } 173 }
199 174
@@ -216,10 +191,12 @@ impl ProjectWorkspace {
216 let mut crate_graph = CrateGraph::default(); 191 let mut crate_graph = CrateGraph::default();
217 match self { 192 match self {
218 ProjectWorkspace::Json { project } => { 193 ProjectWorkspace::Json { project } => {
219 let mut crates = FxHashMap::default(); 194 let crates: FxHashMap<_, _> = project
220 for (id, krate) in project.crates.iter().enumerate() { 195 .crates
221 let crate_id = json_project::CrateId(id); 196 .iter()
222 if let Some(file_id) = load(&krate.root_module) { 197 .enumerate()
198 .filter_map(|(seq_index, krate)| {
199 let file_id = load(&krate.root_module)?;
223 let edition = match krate.edition { 200 let edition = match krate.edition {
224 json_project::Edition::Edition2015 => Edition::Edition2015, 201 json_project::Edition::Edition2015 => Edition::Edition2015,
225 json_project::Edition::Edition2018 => Edition::Edition2018, 202 json_project::Edition::Edition2018 => Edition::Edition2018,
@@ -249,8 +226,8 @@ impl ProjectWorkspace {
249 .clone() 226 .clone()
250 .map(|it| proc_macro_client.by_dylib_path(&it)); 227 .map(|it| proc_macro_client.by_dylib_path(&it));
251 // FIXME: No crate name in json definition such that we cannot add OUT_DIR to env 228 // FIXME: No crate name in json definition such that we cannot add OUT_DIR to env
252 crates.insert( 229 Some((
253 crate_id, 230 json_project::CrateId(seq_index),
254 crate_graph.add_crate_root( 231 crate_graph.add_crate_root(
255 file_id, 232 file_id,
256 edition, 233 edition,
@@ -261,9 +238,9 @@ impl ProjectWorkspace {
261 extern_source, 238 extern_source,
262 proc_macro.unwrap_or_default(), 239 proc_macro.unwrap_or_default(),
263 ), 240 ),
264 ); 241 ))
265 } 242 })
266 } 243 .collect();
267 244
268 for (id, krate) in project.crates.iter().enumerate() { 245 for (id, krate) in project.crates.iter().enumerate() {
269 for dep in &krate.deps { 246 for dep in &krate.deps {
@@ -287,9 +264,11 @@ impl ProjectWorkspace {
287 } 264 }
288 } 265 }
289 ProjectWorkspace::Cargo { cargo, sysroot } => { 266 ProjectWorkspace::Cargo { cargo, sysroot } => {
290 let mut sysroot_crates = FxHashMap::default(); 267 let sysroot_crates: FxHashMap<_, _> = sysroot
291 for krate in sysroot.crates() { 268 .crates()
292 if let Some(file_id) = load(&sysroot[krate].root) { 269 .filter_map(|krate| {
270 let file_id = load(&sysroot[krate].root)?;
271
293 // Crates from sysroot have `cfg(test)` disabled 272 // Crates from sysroot have `cfg(test)` disabled
294 let cfg_options = { 273 let cfg_options = {
295 let mut opts = default_cfg_options.clone(); 274 let mut opts = default_cfg_options.clone();
@@ -300,22 +279,22 @@ impl ProjectWorkspace {
300 let env = Env::default(); 279 let env = Env::default();
301 let extern_source = ExternSource::default(); 280 let extern_source = ExternSource::default();
302 let proc_macro = vec![]; 281 let proc_macro = vec![];
282 let crate_name = CrateName::new(&sysroot[krate].name)
283 .expect("Sysroot crate names should not contain dashes");
303 284
304 let crate_id = crate_graph.add_crate_root( 285 let crate_id = crate_graph.add_crate_root(
305 file_id, 286 file_id,
306 Edition::Edition2018, 287 Edition::Edition2018,
307 Some( 288 Some(crate_name),
308 CrateName::new(&sysroot[krate].name)
309 .expect("Sysroot crate names should not contain dashes"),
310 ),
311 cfg_options, 289 cfg_options,
312 env, 290 env,
313 extern_source, 291 extern_source,
314 proc_macro, 292 proc_macro,
315 ); 293 );
316 sysroot_crates.insert(krate, crate_id); 294 Some((krate, crate_id))
317 } 295 })
318 } 296 .collect();
297
319 for from in sysroot.crates() { 298 for from in sysroot.crates() {
320 for &to in sysroot[from].deps.iter() { 299 for &to in sysroot[from].deps.iter() {
321 let name = &sysroot[to].name; 300 let name = &sysroot[to].name;
diff --git a/crates/ra_syntax/src/ast/edit.rs b/crates/ra_syntax/src/ast/edit.rs
index 2304e00cf..b69cae234 100644
--- a/crates/ra_syntax/src/ast/edit.rs
+++ b/crates/ra_syntax/src/ast/edit.rs
@@ -46,14 +46,46 @@ impl ast::FnDef {
46 } 46 }
47} 47}
48 48
49fn make_multiline<N>(node: N) -> N
50where
51 N: AstNode + Clone,
52{
53 let l_curly = match node.syntax().children_with_tokens().find(|it| it.kind() == T!['{']) {
54 Some(it) => it,
55 None => return node,
56 };
57 let sibling = match l_curly.next_sibling_or_token() {
58 Some(it) => it,
59 None => return node,
60 };
61 let existing_ws = match sibling.as_token() {
62 None => None,
63 Some(tok) if tok.kind() != WHITESPACE => None,
64 Some(ws) => {
65 if ws.text().contains('\n') {
66 return node;
67 }
68 Some(ws.clone())
69 }
70 };
71
72 let indent = leading_indent(node.syntax()).unwrap_or_default();
73 let ws = tokens::WsBuilder::new(&format!("\n{}", indent));
74 let to_insert = iter::once(ws.ws().into());
75 match existing_ws {
76 None => node.insert_children(InsertPosition::After(l_curly), to_insert),
77 Some(ws) => node.replace_children(single_node(ws), to_insert),
78 }
79}
80
49impl ast::ItemList { 81impl ast::ItemList {
50 #[must_use] 82 #[must_use]
51 pub fn append_items(&self, items: impl Iterator<Item = ast::ImplItem>) -> ast::ItemList { 83 pub fn append_items(&self, items: impl IntoIterator<Item = ast::ImplItem>) -> ast::ItemList {
52 let mut res = self.clone(); 84 let mut res = self.clone();
53 if !self.syntax().text().contains_char('\n') { 85 if !self.syntax().text().contains_char('\n') {
54 res = res.make_multiline(); 86 res = make_multiline(res);
55 } 87 }
56 items.for_each(|it| res = res.append_item(it)); 88 items.into_iter().for_each(|it| res = res.append_item(it));
57 res 89 res
58 } 90 }
59 91
@@ -81,35 +113,6 @@ impl ast::ItemList {
81 fn l_curly(&self) -> Option<SyntaxElement> { 113 fn l_curly(&self) -> Option<SyntaxElement> {
82 self.syntax().children_with_tokens().find(|it| it.kind() == T!['{']) 114 self.syntax().children_with_tokens().find(|it| it.kind() == T!['{'])
83 } 115 }
84
85 fn make_multiline(&self) -> ast::ItemList {
86 let l_curly = match self.syntax().children_with_tokens().find(|it| it.kind() == T!['{']) {
87 Some(it) => it,
88 None => return self.clone(),
89 };
90 let sibling = match l_curly.next_sibling_or_token() {
91 Some(it) => it,
92 None => return self.clone(),
93 };
94 let existing_ws = match sibling.as_token() {
95 None => None,
96 Some(tok) if tok.kind() != WHITESPACE => None,
97 Some(ws) => {
98 if ws.text().contains('\n') {
99 return self.clone();
100 }
101 Some(ws.clone())
102 }
103 };
104
105 let indent = leading_indent(self.syntax()).unwrap_or_default();
106 let ws = tokens::WsBuilder::new(&format!("\n{}", indent));
107 let to_insert = iter::once(ws.ws().into());
108 match existing_ws {
109 None => self.insert_children(InsertPosition::After(l_curly), to_insert),
110 Some(ws) => self.replace_children(single_node(ws), to_insert),
111 }
112 }
113} 116}
114 117
115impl ast::RecordFieldList { 118impl ast::RecordFieldList {
@@ -334,6 +337,80 @@ impl ast::UseTree {
334 } 337 }
335} 338}
336 339
340impl ast::MatchArmList {
341 #[must_use]
342 pub fn append_arms(&self, items: impl IntoIterator<Item = ast::MatchArm>) -> ast::MatchArmList {
343 let mut res = self.clone();
344 res = res.strip_if_only_whitespace();
345 if !res.syntax().text().contains_char('\n') {
346 res = make_multiline(res);
347 }
348 items.into_iter().for_each(|it| res = res.append_arm(it));
349 res
350 }
351
352 fn strip_if_only_whitespace(&self) -> ast::MatchArmList {
353 let mut iter = self.syntax().children_with_tokens().skip_while(|it| it.kind() != T!['{']);
354 iter.next(); // Eat the curly
355 let mut inner = iter.take_while(|it| it.kind() != T!['}']);
356 if !inner.clone().all(|it| it.kind() == WHITESPACE) {
357 return self.clone();
358 }
359 let start = match inner.next() {
360 Some(s) => s,
361 None => return self.clone(),
362 };
363 let end = match inner.last() {
364 Some(s) => s,
365 None => start.clone(),
366 };
367 self.replace_children(start..=end, &mut iter::empty())
368 }
369
370 #[must_use]
371 pub fn remove_placeholder(&self) -> ast::MatchArmList {
372 let placeholder =
373 self.arms().find(|arm| matches!(arm.pat(), Some(ast::Pat::PlaceholderPat(_))));
374 if let Some(placeholder) = placeholder {
375 self.remove_arm(&placeholder)
376 } else {
377 self.clone()
378 }
379 }
380
381 #[must_use]
382 fn remove_arm(&self, arm: &ast::MatchArm) -> ast::MatchArmList {
383 let start = arm.syntax().clone();
384 let end = if let Some(comma) = start
385 .siblings_with_tokens(Direction::Next)
386 .skip(1)
387 .skip_while(|it| it.kind().is_trivia())
388 .next()
389 .filter(|it| it.kind() == T![,])
390 {
391 comma
392 } else {
393 start.clone().into()
394 };
395 self.replace_children(start.into()..=end, None)
396 }
397
398 #[must_use]
399 pub fn append_arm(&self, item: ast::MatchArm) -> ast::MatchArmList {
400 let r_curly = match self.syntax().children_with_tokens().find(|it| it.kind() == T!['}']) {
401 Some(t) => t,
402 None => return self.clone(),
403 };
404 let position = InsertPosition::Before(r_curly.into());
405 let arm_ws = tokens::WsBuilder::new(" ");
406 let match_indent = &leading_indent(self.syntax()).unwrap_or_default();
407 let match_ws = tokens::WsBuilder::new(&format!("\n{}", match_indent));
408 let to_insert: ArrayVec<[SyntaxElement; 3]> =
409 [arm_ws.ws().into(), item.syntax().clone().into(), match_ws.ws().into()].into();
410 self.insert_children(position, to_insert)
411 }
412}
413
337#[must_use] 414#[must_use]
338pub fn remove_attrs_and_docs<N: ast::AttrsOwner>(node: &N) -> N { 415pub fn remove_attrs_and_docs<N: ast::AttrsOwner>(node: &N) -> N {
339 N::cast(remove_attrs_and_docs_inner(node.syntax().clone())).unwrap() 416 N::cast(remove_attrs_and_docs_inner(node.syntax().clone())).unwrap()
diff --git a/crates/ra_syntax/src/ast/expr_extensions.rs b/crates/ra_syntax/src/ast/expr_extensions.rs
index 77cceb382..8bbd946c0 100644
--- a/crates/ra_syntax/src/ast/expr_extensions.rs
+++ b/crates/ra_syntax/src/ast/expr_extensions.rs
@@ -308,7 +308,7 @@ pub enum LiteralKind {
308 Byte, 308 Byte,
309 IntNumber { suffix: Option<SmolStr> }, 309 IntNumber { suffix: Option<SmolStr> },
310 FloatNumber { suffix: Option<SmolStr> }, 310 FloatNumber { suffix: Option<SmolStr> },
311 Bool, 311 Bool(bool),
312} 312}
313 313
314impl ast::Literal { 314impl ast::Literal {
@@ -355,7 +355,8 @@ impl ast::Literal {
355 LiteralKind::FloatNumber { suffix: Self::find_suffix(&text, &FLOAT_SUFFIXES) } 355 LiteralKind::FloatNumber { suffix: Self::find_suffix(&text, &FLOAT_SUFFIXES) }
356 } 356 }
357 STRING | RAW_STRING => LiteralKind::String, 357 STRING | RAW_STRING => LiteralKind::String,
358 T![true] | T![false] => LiteralKind::Bool, 358 T![true] => LiteralKind::Bool(true),
359 T![false] => LiteralKind::Bool(false),
359 BYTE_STRING | RAW_BYTE_STRING => LiteralKind::ByteString, 360 BYTE_STRING | RAW_BYTE_STRING => LiteralKind::ByteString,
360 CHAR => LiteralKind::Char, 361 CHAR => LiteralKind::Char,
361 BYTE => LiteralKind::Byte, 362 BYTE => LiteralKind::Byte,
diff --git a/crates/ra_syntax/src/parsing/text_tree_sink.rs b/crates/ra_syntax/src/parsing/text_tree_sink.rs
index dd202601d..87bb21cd9 100644
--- a/crates/ra_syntax/src/parsing/text_tree_sink.rs
+++ b/crates/ra_syntax/src/parsing/text_tree_sink.rs
@@ -149,10 +149,21 @@ fn n_attached_trivias<'a>(
149 MACRO_CALL | CONST_DEF | TYPE_ALIAS_DEF | STRUCT_DEF | ENUM_DEF | ENUM_VARIANT | FN_DEF 149 MACRO_CALL | CONST_DEF | TYPE_ALIAS_DEF | STRUCT_DEF | ENUM_DEF | ENUM_VARIANT | FN_DEF
150 | TRAIT_DEF | MODULE | RECORD_FIELD_DEF | STATIC_DEF => { 150 | TRAIT_DEF | MODULE | RECORD_FIELD_DEF | STATIC_DEF => {
151 let mut res = 0; 151 let mut res = 0;
152 for (i, (kind, text)) in trivias.enumerate() { 152 let mut trivias = trivias.enumerate().peekable();
153
154 while let Some((i, (kind, text))) = trivias.next() {
153 match kind { 155 match kind {
154 WHITESPACE => { 156 WHITESPACE => {
155 if text.contains("\n\n") { 157 if text.contains("\n\n") {
158 // we check whether the next token is a doc-comment
159 // and skip the whitespace in this case
160 if let Some((peek_kind, peek_text)) =
161 trivias.peek().map(|(_, pair)| pair)
162 {
163 if *peek_kind == COMMENT && peek_text.starts_with("///") {
164 continue;
165 }
166 }
156 break; 167 break;
157 } 168 }
158 } 169 }
diff --git a/crates/ra_syntax/test_data/parser/ok/0065_comment_newline.rs b/crates/ra_syntax/test_data/parser/ok/0065_comment_newline.rs
new file mode 100644
index 000000000..1fafe216b
--- /dev/null
+++ b/crates/ra_syntax/test_data/parser/ok/0065_comment_newline.rs
@@ -0,0 +1,3 @@
1/// Example
2
3fn test() {}
diff --git a/crates/ra_syntax/test_data/parser/ok/0065_comment_newline.txt b/crates/ra_syntax/test_data/parser/ok/0065_comment_newline.txt
new file mode 100644
index 000000000..91d0c3736
--- /dev/null
+++ b/crates/ra_syntax/test_data/parser/ok/0065_comment_newline.txt
@@ -0,0 +1,17 @@
1SOURCE_FILE@[0; 26)
2 FN_DEF@[0; 25)
3 COMMENT@[0; 11) "/// Example"
4 WHITESPACE@[11; 13) "\n\n"
5 FN_KW@[13; 15) "fn"
6 WHITESPACE@[15; 16) " "
7 NAME@[16; 20)
8 IDENT@[16; 20) "test"
9 PARAM_LIST@[20; 22)
10 L_PAREN@[20; 21) "("
11 R_PAREN@[21; 22) ")"
12 WHITESPACE@[22; 23) " "
13 BLOCK_EXPR@[23; 25)
14 BLOCK@[23; 25)
15 L_CURLY@[23; 24) "{"
16 R_CURLY@[24; 25) "}"
17 WHITESPACE@[25; 26) "\n"
diff --git a/crates/ra_tt/src/lib.rs b/crates/ra_tt/src/lib.rs
index 1015ce0a6..bd484aa30 100644
--- a/crates/ra_tt/src/lib.rs
+++ b/crates/ra_tt/src/lib.rs
@@ -189,7 +189,12 @@ impl Subtree {
189pub mod buffer; 189pub mod buffer;
190 190
191#[derive(Debug, PartialEq, Eq)] 191#[derive(Debug, PartialEq, Eq)]
192pub enum ExpansionError {} 192pub enum ExpansionError {
193 IOError(String),
194 JsonError(String),
195 Unknown(String),
196 ExpansionError(String),
197}
193 198
194pub trait TokenExpander: Debug + Send + Sync + RefUnwindSafe { 199pub trait TokenExpander: Debug + Send + Sync + RefUnwindSafe {
195 fn expand(&self, subtree: &Subtree, attrs: Option<&Subtree>) 200 fn expand(&self, subtree: &Subtree, attrs: Option<&Subtree>)
diff --git a/crates/rust-analyzer/Cargo.toml b/crates/rust-analyzer/Cargo.toml
index 8fe6799d2..f5f773432 100644
--- a/crates/rust-analyzer/Cargo.toml
+++ b/crates/rust-analyzer/Cargo.toml
@@ -33,7 +33,7 @@ threadpool = "1.7.1"
33stdx = { path = "../stdx" } 33stdx = { path = "../stdx" }
34 34
35lsp-server = "0.3.1" 35lsp-server = "0.3.1"
36ra_cargo_watch = { path = "../ra_cargo_watch" } 36ra_flycheck = { path = "../ra_flycheck" }
37ra_ide = { path = "../ra_ide" } 37ra_ide = { path = "../ra_ide" }
38ra_prof = { path = "../ra_prof" } 38ra_prof = { path = "../ra_prof" }
39ra_project_model = { path = "../ra_project_model" } 39ra_project_model = { path = "../ra_project_model" }
diff --git a/crates/rust-analyzer/src/bin/main.rs b/crates/rust-analyzer/src/bin/main.rs
index a744a6695..608f4f67b 100644
--- a/crates/rust-analyzer/src/bin/main.rs
+++ b/crates/rust-analyzer/src/bin/main.rs
@@ -4,8 +4,7 @@
4mod args; 4mod args;
5 5
6use lsp_server::Connection; 6use lsp_server::Connection;
7 7use rust_analyzer::{cli, config::Config, from_json, Result};
8use rust_analyzer::{cli, from_json, show_message, Result, ServerConfig};
9 8
10use crate::args::HelpPrinted; 9use crate::args::HelpPrinted;
11 10
@@ -78,24 +77,18 @@ fn run_server() -> Result<()> {
78 .filter(|workspaces| !workspaces.is_empty()) 77 .filter(|workspaces| !workspaces.is_empty())
79 .unwrap_or_else(|| vec![root]); 78 .unwrap_or_else(|| vec![root]);
80 79
81 let server_config = initialize_params 80 let config = {
82 .initialization_options 81 let mut config = Config::default();
83 .and_then(|v| { 82 if let Some(value) = &initialize_params.initialization_options {
84 from_json::<ServerConfig>("config", v) 83 config.update(value);
85 .map_err(|e| { 84 }
86 log::error!("{}", e); 85 if let Some(caps) = &initialize_params.capabilities.text_document {
87 show_message(lsp_types::MessageType::Error, e.to_string(), &connection.sender); 86 config.update_caps(caps);
88 }) 87 }
89 .ok() 88 config
90 }) 89 };
91 .unwrap_or_default();
92 90
93 rust_analyzer::main_loop( 91 rust_analyzer::main_loop(workspace_roots, config, connection)?;
94 workspace_roots,
95 initialize_params.capabilities,
96 server_config,
97 connection,
98 )?;
99 92
100 log::info!("shutting down IO..."); 93 log::info!("shutting down IO...");
101 io_threads.join()?; 94 io_threads.join()?;
diff --git a/crates/rust-analyzer/src/cargo_target_spec.rs b/crates/rust-analyzer/src/cargo_target_spec.rs
index f87bdcec5..942c30328 100644
--- a/crates/rust-analyzer/src/cargo_target_spec.rs
+++ b/crates/rust-analyzer/src/cargo_target_spec.rs
@@ -77,7 +77,7 @@ impl CargoTargetSpec {
77 ProjectWorkspace::Cargo { cargo, .. } => { 77 ProjectWorkspace::Cargo { cargo, .. } => {
78 let tgt = cargo.target_by_root(&path)?; 78 let tgt = cargo.target_by_root(&path)?;
79 Some(CargoTargetSpec { 79 Some(CargoTargetSpec {
80 package: cargo[cargo[tgt].package].name.clone(), 80 package: cargo.package_flag(&cargo[cargo[tgt].package]),
81 target: cargo[tgt].name.clone(), 81 target: cargo[tgt].name.clone(),
82 target_kind: cargo[tgt].kind, 82 target_kind: cargo[tgt].kind,
83 }) 83 })
diff --git a/crates/rust-analyzer/src/cli/analysis_bench.rs b/crates/rust-analyzer/src/cli/analysis_bench.rs
index 7164b0ade..7667873d5 100644
--- a/crates/rust-analyzer/src/cli/analysis_bench.rs
+++ b/crates/rust-analyzer/src/cli/analysis_bench.rs
@@ -12,7 +12,7 @@ use ra_db::{
12 salsa::{Database, Durability}, 12 salsa::{Database, Durability},
13 FileId, SourceDatabaseExt, 13 FileId, SourceDatabaseExt,
14}; 14};
15use ra_ide::{Analysis, AnalysisChange, AnalysisHost, CompletionOptions, FilePosition, LineCol}; 15use ra_ide::{Analysis, AnalysisChange, AnalysisHost, CompletionConfig, FilePosition, LineCol};
16 16
17use crate::cli::{load_cargo::load_cargo, Verbosity}; 17use crate::cli::{load_cargo::load_cargo, Verbosity};
18 18
@@ -102,7 +102,7 @@ pub fn analysis_bench(
102 let file_position = FilePosition { file_id, offset }; 102 let file_position = FilePosition { file_id, offset };
103 103
104 if is_completion { 104 if is_completion {
105 let options = CompletionOptions::default(); 105 let options = CompletionConfig::default();
106 let res = do_work(&mut host, file_id, |analysis| { 106 let res = do_work(&mut host, file_id, |analysis| {
107 analysis.completions(file_position, &options) 107 analysis.completions(file_position, &options)
108 }); 108 });
diff --git a/crates/rust-analyzer/src/cli/load_cargo.rs b/crates/rust-analyzer/src/cli/load_cargo.rs
index 832f04226..69133e4e4 100644
--- a/crates/rust-analyzer/src/cli/load_cargo.rs
+++ b/crates/rust-analyzer/src/cli/load_cargo.rs
@@ -8,7 +8,7 @@ use crossbeam_channel::{unbounded, Receiver};
8use ra_db::{ExternSourceId, FileId, SourceRootId}; 8use ra_db::{ExternSourceId, FileId, SourceRootId};
9use ra_ide::{AnalysisChange, AnalysisHost}; 9use ra_ide::{AnalysisChange, AnalysisHost};
10use ra_project_model::{ 10use ra_project_model::{
11 get_rustc_cfg_options, CargoFeatures, PackageRoot, ProcMacroClient, ProjectWorkspace, 11 get_rustc_cfg_options, CargoConfig, PackageRoot, ProcMacroClient, ProjectWorkspace,
12}; 12};
13use ra_vfs::{RootEntry, Vfs, VfsChange, VfsTask, Watch}; 13use ra_vfs::{RootEntry, Vfs, VfsChange, VfsTask, Watch};
14use rustc_hash::{FxHashMap, FxHashSet}; 14use rustc_hash::{FxHashMap, FxHashSet};
@@ -29,15 +29,14 @@ pub(crate) fn load_cargo(
29 let root = std::env::current_dir()?.join(root); 29 let root = std::env::current_dir()?.join(root);
30 let ws = ProjectWorkspace::discover( 30 let ws = ProjectWorkspace::discover(
31 root.as_ref(), 31 root.as_ref(),
32 &CargoFeatures { load_out_dirs_from_check, ..Default::default() }, 32 &CargoConfig { load_out_dirs_from_check, ..Default::default() },
33 )?; 33 )?;
34 34
35 let mut extern_dirs = FxHashSet::default(); 35 let mut extern_dirs = FxHashSet::default();
36 extern_dirs.extend(ws.out_dirs()); 36 extern_dirs.extend(ws.out_dirs());
37 37
38 let mut project_roots = ws.to_roots(); 38 let mut project_roots = ws.to_roots();
39 project_roots 39 project_roots.extend(extern_dirs.iter().cloned().map(PackageRoot::new_non_member));
40 .extend(extern_dirs.iter().map(|path| PackageRoot::new(path.to_path_buf(), false)));
41 40
42 let (sender, receiver) = unbounded(); 41 let (sender, receiver) = unbounded();
43 let sender = Box::new(move |t| sender.send(t).unwrap()); 42 let sender = Box::new(move |t| sender.send(t).unwrap());
@@ -46,7 +45,7 @@ pub(crate) fn load_cargo(
46 .iter() 45 .iter()
47 .map(|pkg_root| { 46 .map(|pkg_root| {
48 RootEntry::new( 47 RootEntry::new(
49 pkg_root.path().clone(), 48 pkg_root.path().to_owned(),
50 RustPackageFilterBuilder::default() 49 RustPackageFilterBuilder::default()
51 .set_member(pkg_root.is_member()) 50 .set_member(pkg_root.is_member())
52 .into_vfs_filter(), 51 .into_vfs_filter(),
@@ -58,12 +57,12 @@ pub(crate) fn load_cargo(
58 ); 57 );
59 58
60 let source_roots = roots 59 let source_roots = roots
61 .iter() 60 .into_iter()
62 .map(|&vfs_root| { 61 .map(|vfs_root| {
63 let source_root_id = vfs_root_to_id(vfs_root); 62 let source_root_id = vfs_root_to_id(vfs_root);
64 let project_root = project_roots 63 let project_root = project_roots
65 .iter() 64 .iter()
66 .find(|it| it.path() == &vfs.root2path(vfs_root)) 65 .find(|it| it.path() == vfs.root2path(vfs_root))
67 .unwrap() 66 .unwrap()
68 .clone(); 67 .clone();
69 (source_root_id, project_root) 68 (source_root_id, project_root)
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs
index 628ed107e..04f5bb473 100644
--- a/crates/rust-analyzer/src/config.rs
+++ b/crates/rust-analyzer/src/config.rs
@@ -7,112 +7,191 @@
7//! configure the server itself, feature flags are passed into analysis, and 7//! configure the server itself, feature flags are passed into analysis, and
8//! tweak things like automatic insertion of `()` in completions. 8//! tweak things like automatic insertion of `()` in completions.
9 9
10use rustc_hash::FxHashMap; 10use lsp_types::TextDocumentClientCapabilities;
11 11use ra_flycheck::FlycheckConfig;
12use ra_project_model::CargoFeatures; 12use ra_ide::{CompletionConfig, InlayHintsConfig};
13use serde::{Deserialize, Deserializer}; 13use ra_project_model::CargoConfig;
14 14use serde::Deserialize;
15/// Client provided initialization options
16#[derive(Deserialize, Clone, Debug, PartialEq, Eq)]
17#[serde(rename_all = "camelCase", default)]
18pub struct ServerConfig {
19 /// Whether the client supports our custom highlighting publishing decorations.
20 /// This is different to the highlightingOn setting, which is whether the user
21 /// wants our custom highlighting to be used.
22 ///
23 /// Defaults to `false`
24 #[serde(deserialize_with = "nullable_bool_false")]
25 pub publish_decorations: bool,
26
27 pub exclude_globs: Vec<String>,
28 #[serde(deserialize_with = "nullable_bool_false")]
29 pub use_client_watching: bool,
30 15
31 pub lru_capacity: Option<usize>, 16#[derive(Debug, Clone)]
17pub struct Config {
18 pub client_caps: ClientCapsConfig,
32 19
33 #[serde(deserialize_with = "nullable_bool_true")]
34 pub inlay_hints_type: bool,
35 #[serde(deserialize_with = "nullable_bool_true")]
36 pub inlay_hints_parameter: bool,
37 #[serde(deserialize_with = "nullable_bool_true")]
38 pub inlay_hints_chaining: bool,
39 pub inlay_hints_max_length: Option<usize>,
40
41 pub cargo_watch_enable: bool,
42 pub cargo_watch_args: Vec<String>,
43 pub cargo_watch_command: String,
44 pub cargo_watch_all_targets: bool,
45
46 /// For internal usage to make integrated tests faster.
47 #[serde(deserialize_with = "nullable_bool_true")]
48 pub with_sysroot: bool, 20 pub with_sysroot: bool,
21 pub publish_diagnostics: bool,
22 pub lru_capacity: Option<usize>,
23 pub proc_macro_srv: Option<String>,
24 pub files: FilesConfig,
25 pub notifications: NotificationsConfig,
49 26
50 /// Fine grained feature flags to disable specific features. 27 pub cargo: CargoConfig,
51 pub feature_flags: FxHashMap<String, bool>, 28 pub rustfmt: RustfmtConfig,
29 pub check: Option<FlycheckConfig>,
52 30
53 pub rustfmt_args: Vec<String>, 31 pub inlay_hints: InlayHintsConfig,
32 pub completion: CompletionConfig,
33 pub call_info_full: bool,
34}
54 35
55 /// Cargo feature configurations. 36#[derive(Debug, Clone)]
56 pub cargo_features: CargoFeatures, 37pub struct FilesConfig {
38 pub watcher: FilesWatcher,
39 pub exclude: Vec<String>,
40}
57 41
58 /// Enabled if the vscode_lldb extension is available. 42#[derive(Debug, Clone)]
59 pub vscode_lldb: bool, 43pub enum FilesWatcher {
44 Client,
45 Notify,
60} 46}
61 47
62impl Default for ServerConfig { 48#[derive(Debug, Clone)]
63 fn default() -> ServerConfig { 49pub struct NotificationsConfig {
64 ServerConfig { 50 pub workspace_loaded: bool,
65 publish_decorations: false, 51 pub cargo_toml_not_found: bool,
66 exclude_globs: Vec::new(), 52}
67 use_client_watching: false, 53
68 lru_capacity: None, 54#[derive(Debug, Clone)]
69 inlay_hints_type: true, 55pub enum RustfmtConfig {
70 inlay_hints_parameter: true, 56 Rustfmt {
71 inlay_hints_chaining: true, 57 extra_args: Vec<String>,
72 inlay_hints_max_length: None, 58 },
73 cargo_watch_enable: true, 59 #[allow(unused)]
74 cargo_watch_args: Vec::new(), 60 CustomCommand {
75 cargo_watch_command: "check".to_string(), 61 command: String,
76 cargo_watch_all_targets: true, 62 args: Vec<String>,
63 },
64}
65
66#[derive(Debug, Clone, Default)]
67pub struct ClientCapsConfig {
68 pub location_link: bool,
69 pub line_folding_only: bool,
70}
71
72impl Default for Config {
73 fn default() -> Self {
74 Config {
75 client_caps: ClientCapsConfig::default(),
76
77 with_sysroot: true, 77 with_sysroot: true,
78 feature_flags: FxHashMap::default(), 78 publish_diagnostics: true,
79 cargo_features: Default::default(), 79 lru_capacity: None,
80 rustfmt_args: Vec::new(), 80 proc_macro_srv: None,
81 vscode_lldb: false, 81 files: FilesConfig { watcher: FilesWatcher::Notify, exclude: Vec::new() },
82 notifications: NotificationsConfig {
83 workspace_loaded: true,
84 cargo_toml_not_found: true,
85 },
86
87 cargo: CargoConfig::default(),
88 rustfmt: RustfmtConfig::Rustfmt { extra_args: Vec::new() },
89 check: Some(FlycheckConfig::CargoCommand {
90 command: "check".to_string(),
91 all_targets: true,
92 extra_args: Vec::new(),
93 }),
94
95 inlay_hints: InlayHintsConfig {
96 type_hints: true,
97 parameter_hints: true,
98 chaining_hints: true,
99 max_length: None,
100 },
101 completion: CompletionConfig {
102 enable_postfix_completions: true,
103 add_call_parenthesis: true,
104 add_call_argument_snippets: true,
105 },
106 call_info_full: true,
82 } 107 }
83 } 108 }
84} 109}
85 110
86/// Deserializes a null value to a bool false by default 111impl Config {
87fn nullable_bool_false<'de, D>(deserializer: D) -> Result<bool, D::Error> 112 #[rustfmt::skip]
88where 113 pub fn update(&mut self, value: &serde_json::Value) {
89 D: Deserializer<'de>, 114 log::info!("Config::update({:#})", value);
90{ 115
91 let opt = Option::deserialize(deserializer)?; 116 let client_caps = self.client_caps.clone();
92 Ok(opt.unwrap_or(false)) 117 *self = Default::default();
93} 118 self.client_caps = client_caps;
119
120 set(value, "/withSysroot", &mut self.with_sysroot);
121 set(value, "/featureFlags/lsp.diagnostics", &mut self.publish_diagnostics);
122 set(value, "/lruCapacity", &mut self.lru_capacity);
123 if let Some(watcher) = get::<String>(value, "/files/watcher") {
124 self.files.watcher = match watcher.as_str() {
125 "client" => FilesWatcher::Client,
126 "notify"| _ => FilesWatcher::Notify,
127 }
128 }
129 set(value, "/notifications/workspaceLoaded", &mut self.notifications.workspace_loaded);
130 set(value, "/notifications/cargoTomlNotFound", &mut self.notifications.cargo_toml_not_found);
131
132 set(value, "/cargo/noDefaultFeatures", &mut self.cargo.no_default_features);
133 set(value, "/cargo/allFeatures", &mut self.cargo.all_features);
134 set(value, "/cargo/features", &mut self.cargo.features);
135 set(value, "/cargo/loadOutDirsFromCheck", &mut self.cargo.load_out_dirs_from_check);
136 if let Some(mut args) = get::<Vec<String>>(value, "/rustfmt/overrideCommand") {
137 if !args.is_empty() {
138 let command = args.remove(0);
139 self.rustfmt = RustfmtConfig::CustomCommand {
140 command,
141 args,
142 }
143 }
144 } else if let RustfmtConfig::Rustfmt { extra_args } = &mut self.rustfmt {
145 set(value, "/rustfmt/extraArgs", extra_args);
146 }
147 if let Some(false) = get(value, "/checkOnSave/enable") {
148 self.check = None
149 } else {
150 if let Some(mut args) = get::<Vec<String>>(value, "/checkOnSave/overrideCommand") {
151 if !args.is_empty() {
152 let command = args.remove(0);
153 self.check = Some(FlycheckConfig::CustomCommand {
154 command,
155 args,
156 })
157 }
158
159 } else if let Some(FlycheckConfig::CargoCommand { command, extra_args, all_targets }) = &mut self.check
160 {
161 set(value, "/checkOnSave/extraArgs", extra_args);
162 set(value, "/checkOnSave/command", command);
163 set(value, "/checkOnSave/allTargets", all_targets);
164 }
165 };
166
167 set(value, "/inlayHints/typeHints", &mut self.inlay_hints.type_hints);
168 set(value, "/inlayHints/parameterHints", &mut self.inlay_hints.parameter_hints);
169 set(value, "/inlayHints/chainingHints", &mut self.inlay_hints.chaining_hints);
170 set(value, "/inlayHints/maxLength", &mut self.inlay_hints.max_length);
171 set(value, "/completion/postfix/enable", &mut self.completion.enable_postfix_completions);
172 set(value, "/completion/addCallParenthesis", &mut self.completion.add_call_parenthesis);
173 set(value, "/completion/addCallArgumentSnippets", &mut self.completion.add_call_argument_snippets);
174 set(value, "/callInfo/full", &mut self.call_info_full);
175
176 log::info!("Config::update() = {:#?}", self);
177
178 fn get<'a, T: Deserialize<'a>>(value: &'a serde_json::Value, pointer: &str) -> Option<T> {
179 value.pointer(pointer).and_then(|it| T::deserialize(it).ok())
180 }
94 181
95/// Deserializes a null value to a bool true by default 182 fn set<'a, T: Deserialize<'a> + std::fmt::Debug>(value: &'a serde_json::Value, pointer: &str, slot: &mut T) {
96fn nullable_bool_true<'de, D>(deserializer: D) -> Result<bool, D::Error> 183 if let Some(new_value) = get(value, pointer) {
97where 184 *slot = new_value
98 D: Deserializer<'de>, 185 }
99{ 186 }
100 let opt = Option::deserialize(deserializer)?; 187 }
101 Ok(opt.unwrap_or(true))
102}
103 188
104#[cfg(test)] 189 pub fn update_caps(&mut self, caps: &TextDocumentClientCapabilities) {
105mod test { 190 if let Some(value) = caps.definition.as_ref().and_then(|it| it.link_support) {
106 use super::*; 191 self.client_caps.location_link = value;
107 192 }
108 #[test] 193 if let Some(value) = caps.folding_range.as_ref().and_then(|it| it.line_folding_only) {
109 fn deserialize_init_options_defaults() { 194 self.client_caps.line_folding_only = value
110 // check that null == default for both fields 195 }
111 let default = ServerConfig::default();
112 assert_eq!(default, serde_json::from_str(r#"{}"#).unwrap());
113 assert_eq!(
114 default,
115 serde_json::from_str(r#"{"publishDecorations":null, "lruCapacity":null}"#).unwrap()
116 );
117 } 196 }
118} 197}
diff --git a/crates/rust-analyzer/src/conv.rs b/crates/rust-analyzer/src/conv.rs
index 6edc03fe0..57c4c8ce5 100644
--- a/crates/rust-analyzer/src/conv.rs
+++ b/crates/rust-analyzer/src/conv.rs
@@ -579,7 +579,7 @@ impl TryConvWith<&WorldSnapshot> for (FileId, RangeInfo<Vec<NavigationTarget>>)
579 .into_iter() 579 .into_iter()
580 .map(|nav| (file_id, RangeInfo::new(range, nav))) 580 .map(|nav| (file_id, RangeInfo::new(range, nav)))
581 .try_conv_with_to_vec(world)?; 581 .try_conv_with_to_vec(world)?;
582 if world.options.supports_location_link { 582 if world.config.client_caps.location_link {
583 Ok(links.into()) 583 Ok(links.into())
584 } else { 584 } else {
585 let locations: Vec<Location> = links 585 let locations: Vec<Location> = links
diff --git a/crates/rust-analyzer/src/feature_flags.rs b/crates/rust-analyzer/src/feature_flags.rs
deleted file mode 100644
index dbb3f50a0..000000000
--- a/crates/rust-analyzer/src/feature_flags.rs
+++ /dev/null
@@ -1,77 +0,0 @@
1//! See docs for `FeatureFlags`.
2
3use rustc_hash::FxHashMap;
4
5// FIXME: looks like a much better design is to pass options to each call,
6// rather than to have a global ambient feature flags -- that way, the clients
7// can issue two successive calls with different options.
8
9/// Feature flags hold fine-grained toggles for all *user-visible* features of
10/// rust-analyzer.
11///
12/// The exists such that users are able to disable any annoying feature (and,
13/// with many users and many features, some features are bound to be annoying
14/// for some users)
15///
16/// Note that we purposefully use run-time checked strings, and not something
17/// checked at compile time, to keep things simple and flexible.
18///
19/// Also note that, at the moment, `FeatureFlags` also store features for
20/// `rust-analyzer`. This should be benign layering violation.
21#[derive(Debug)]
22pub struct FeatureFlags {
23 flags: FxHashMap<String, bool>,
24}
25
26impl FeatureFlags {
27 fn new(flags: &[(&str, bool)]) -> FeatureFlags {
28 let flags = flags
29 .iter()
30 .map(|&(name, value)| {
31 check_flag_name(name);
32 (name.to_string(), value)
33 })
34 .collect();
35 FeatureFlags { flags }
36 }
37
38 pub fn set(&mut self, flag: &str, value: bool) -> Result<(), ()> {
39 match self.flags.get_mut(flag) {
40 None => Err(()),
41 Some(slot) => {
42 *slot = value;
43 Ok(())
44 }
45 }
46 }
47
48 pub fn get(&self, flag: &str) -> bool {
49 match self.flags.get(flag) {
50 None => panic!("unknown flag: {:?}", flag),
51 Some(value) => *value,
52 }
53 }
54}
55
56impl Default for FeatureFlags {
57 fn default() -> FeatureFlags {
58 FeatureFlags::new(&[
59 ("lsp.diagnostics", true),
60 ("completion.insertion.add-call-parenthesis", true),
61 ("completion.insertion.add-argument-snippets", true),
62 ("completion.enable-postfix", true),
63 ("call-info.full", true),
64 ("notifications.workspace-loaded", true),
65 ("notifications.cargo-toml-not-found", true),
66 ])
67 }
68}
69
70fn check_flag_name(flag: &str) {
71 for c in flag.bytes() {
72 match c {
73 b'a'..=b'z' | b'-' | b'.' => (),
74 _ => panic!("flag name does not match conventions: {:?}", flag),
75 }
76 }
77}
diff --git a/crates/rust-analyzer/src/lib.rs b/crates/rust-analyzer/src/lib.rs
index e50e47b19..02953be30 100644
--- a/crates/rust-analyzer/src/lib.rs
+++ b/crates/rust-analyzer/src/lib.rs
@@ -33,18 +33,16 @@ mod conv;
33mod main_loop; 33mod main_loop;
34mod markdown; 34mod markdown;
35pub mod req; 35pub mod req;
36mod config; 36pub mod config;
37mod world; 37mod world;
38mod diagnostics; 38mod diagnostics;
39mod semantic_tokens; 39mod semantic_tokens;
40mod feature_flags;
41 40
42use serde::de::DeserializeOwned; 41use serde::de::DeserializeOwned;
43 42
44pub type Result<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>; 43pub type Result<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>;
45pub use crate::{ 44pub use crate::{
46 caps::server_capabilities, 45 caps::server_capabilities,
47 config::ServerConfig,
48 main_loop::LspError, 46 main_loop::LspError,
49 main_loop::{main_loop, show_message}, 47 main_loop::{main_loop, show_message},
50}; 48};
diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs
index 7825b0077..8d1429196 100644
--- a/crates/rust-analyzer/src/main_loop.rs
+++ b/crates/rust-analyzer/src/main_loop.rs
@@ -14,15 +14,16 @@ use std::{
14 time::{Duration, Instant}, 14 time::{Duration, Instant},
15}; 15};
16 16
17use crossbeam_channel::{select, unbounded, RecvError, Sender}; 17use crossbeam_channel::{never, select, unbounded, RecvError, Sender};
18use lsp_server::{Connection, ErrorCode, Message, Notification, Request, RequestId, Response}; 18use lsp_server::{Connection, ErrorCode, Message, Notification, Request, RequestId, Response};
19use lsp_types::{ 19use lsp_types::{
20 ClientCapabilities, NumberOrString, WorkDoneProgress, WorkDoneProgressBegin, 20 NumberOrString, WorkDoneProgress, WorkDoneProgressBegin, WorkDoneProgressCreateParams,
21 WorkDoneProgressCreateParams, WorkDoneProgressEnd, WorkDoneProgressReport, 21 WorkDoneProgressEnd, WorkDoneProgressReport,
22}; 22};
23use ra_cargo_watch::{url_from_path_with_drive_lowercasing, CheckOptions, CheckTask}; 23use ra_flycheck::{url_from_path_with_drive_lowercasing, CheckTask};
24use ra_ide::{Canceled, FileId, InlayHintsOptions, LibraryData, SourceRootId}; 24use ra_ide::{Canceled, FileId, LibraryData, SourceRootId};
25use ra_prof::profile; 25use ra_prof::profile;
26use ra_project_model::{PackageRoot, ProjectWorkspace};
26use ra_vfs::{VfsFile, VfsTask, Watch}; 27use ra_vfs::{VfsFile, VfsTask, Watch};
27use relative_path::RelativePathBuf; 28use relative_path::RelativePathBuf;
28use rustc_hash::FxHashSet; 29use rustc_hash::FxHashSet;
@@ -30,15 +31,15 @@ use serde::{de::DeserializeOwned, Serialize};
30use threadpool::ThreadPool; 31use threadpool::ThreadPool;
31 32
32use crate::{ 33use crate::{
34 config::{Config, FilesWatcher},
33 diagnostics::DiagnosticTask, 35 diagnostics::DiagnosticTask,
34 feature_flags::FeatureFlags,
35 main_loop::{ 36 main_loop::{
36 pending_requests::{PendingRequest, PendingRequests}, 37 pending_requests::{PendingRequest, PendingRequests},
37 subscriptions::Subscriptions, 38 subscriptions::Subscriptions,
38 }, 39 },
39 req, 40 req,
40 world::{Options, WorldSnapshot, WorldState}, 41 world::{WorldSnapshot, WorldState},
41 Result, ServerConfig, 42 Result,
42}; 43};
43 44
44#[derive(Debug)] 45#[derive(Debug)]
@@ -63,13 +64,8 @@ impl fmt::Display for LspError {
63 64
64impl Error for LspError {} 65impl Error for LspError {}
65 66
66pub fn main_loop( 67pub fn main_loop(ws_roots: Vec<PathBuf>, config: Config, connection: Connection) -> Result<()> {
67 ws_roots: Vec<PathBuf>, 68 log::info!("initial config: {:#?}", config);
68 client_caps: ClientCapabilities,
69 config: ServerConfig,
70 connection: Connection,
71) -> Result<()> {
72 log::info!("server_config: {:#?}", config);
73 69
74 // Windows scheduler implements priority boosts: if thread waits for an 70 // Windows scheduler implements priority boosts: if thread waits for an
75 // event (like a condvar), and event fires, priority of the thread is 71 // event (like a condvar), and event fires, priority of the thread is
@@ -92,22 +88,6 @@ pub fn main_loop(
92 88
93 let mut loop_state = LoopState::default(); 89 let mut loop_state = LoopState::default();
94 let mut world_state = { 90 let mut world_state = {
95 let feature_flags = {
96 let mut ff = FeatureFlags::default();
97 for (flag, value) in config.feature_flags {
98 if ff.set(flag.as_str(), value).is_err() {
99 log::error!("unknown feature flag: {:?}", flag);
100 show_message(
101 req::MessageType::Error,
102 format!("unknown feature flag: {:?}", flag),
103 &connection.sender,
104 );
105 }
106 }
107 ff
108 };
109 log::info!("feature_flags: {:#?}", feature_flags);
110
111 // FIXME: support dynamic workspace loading. 91 // FIXME: support dynamic workspace loading.
112 let workspaces = { 92 let workspaces = {
113 let mut loaded_workspaces = Vec::new(); 93 let mut loaded_workspaces = Vec::new();
@@ -115,7 +95,7 @@ pub fn main_loop(
115 let workspace = ra_project_model::ProjectWorkspace::discover_with_sysroot( 95 let workspace = ra_project_model::ProjectWorkspace::discover_with_sysroot(
116 ws_root.as_path(), 96 ws_root.as_path(),
117 config.with_sysroot, 97 config.with_sysroot,
118 &config.cargo_features, 98 &config.cargo,
119 ); 99 );
120 match workspace { 100 match workspace {
121 Ok(workspace) => loaded_workspaces.push(workspace), 101 Ok(workspace) => loaded_workspaces.push(workspace),
@@ -125,7 +105,7 @@ pub fn main_loop(
125 if let Some(ra_project_model::CargoTomlNotFoundError { .. }) = 105 if let Some(ra_project_model::CargoTomlNotFoundError { .. }) =
126 e.downcast_ref() 106 e.downcast_ref()
127 { 107 {
128 if !feature_flags.get("notifications.cargo-toml-not-found") { 108 if !config.notifications.cargo_toml_not_found {
129 continue; 109 continue;
130 } 110 }
131 } 111 }
@@ -142,17 +122,18 @@ pub fn main_loop(
142 }; 122 };
143 123
144 let globs = config 124 let globs = config
145 .exclude_globs 125 .files
126 .exclude
146 .iter() 127 .iter()
147 .map(|glob| crate::vfs_glob::Glob::new(glob)) 128 .map(|glob| crate::vfs_glob::Glob::new(glob))
148 .collect::<std::result::Result<Vec<_>, _>>()?; 129 .collect::<std::result::Result<Vec<_>, _>>()?;
149 130
150 if config.use_client_watching { 131 if let FilesWatcher::Client = config.files.watcher {
151 let registration_options = req::DidChangeWatchedFilesRegistrationOptions { 132 let registration_options = req::DidChangeWatchedFilesRegistrationOptions {
152 watchers: workspaces 133 watchers: workspaces
153 .iter() 134 .iter()
154 .flat_map(|ws| ws.to_roots()) 135 .flat_map(ProjectWorkspace::to_roots)
155 .filter(|root| root.is_member()) 136 .filter(PackageRoot::is_member)
156 .map(|root| format!("{}/**/*.rs", root.path().display())) 137 .map(|root| format!("{}/**/*.rs", root.path().display()))
157 .map(|glob_pattern| req::FileSystemWatcher { glob_pattern, kind: None }) 138 .map(|glob_pattern| req::FileSystemWatcher { glob_pattern, kind: None })
158 .collect(), 139 .collect(),
@@ -168,46 +149,19 @@ pub fn main_loop(
168 connection.sender.send(request.into()).unwrap(); 149 connection.sender.send(request.into()).unwrap();
169 } 150 }
170 151
171 let options = {
172 let text_document_caps = client_caps.text_document.as_ref();
173 Options {
174 publish_decorations: config.publish_decorations,
175 supports_location_link: text_document_caps
176 .and_then(|it| it.definition)
177 .and_then(|it| it.link_support)
178 .unwrap_or(false),
179 line_folding_only: text_document_caps
180 .and_then(|it| it.folding_range.as_ref())
181 .and_then(|it| it.line_folding_only)
182 .unwrap_or(false),
183 inlay_hints: InlayHintsOptions {
184 type_hints: config.inlay_hints_type,
185 parameter_hints: config.inlay_hints_parameter,
186 chaining_hints: config.inlay_hints_chaining,
187 max_length: config.inlay_hints_max_length,
188 },
189 cargo_watch: CheckOptions {
190 enable: config.cargo_watch_enable,
191 args: config.cargo_watch_args,
192 command: config.cargo_watch_command,
193 all_targets: config.cargo_watch_all_targets,
194 },
195 rustfmt_args: config.rustfmt_args,
196 vscode_lldb: config.vscode_lldb,
197 }
198 };
199
200 WorldState::new( 152 WorldState::new(
201 ws_roots, 153 ws_roots,
202 workspaces, 154 workspaces,
203 config.lru_capacity, 155 config.lru_capacity,
204 &globs, 156 &globs,
205 Watch(!config.use_client_watching), 157 Watch(matches!(config.files.watcher, FilesWatcher::Notify)),
206 options, 158 config,
207 feature_flags,
208 ) 159 )
209 }; 160 };
210 161
162 loop_state.roots_total = world_state.vfs.read().n_roots();
163 loop_state.roots_scanned = 0;
164
211 let pool = ThreadPool::default(); 165 let pool = ThreadPool::default();
212 let (task_sender, task_receiver) = unbounded::<Task>(); 166 let (task_sender, task_receiver) = unbounded::<Task>();
213 let (libdata_sender, libdata_receiver) = unbounded::<LibraryData>(); 167 let (libdata_sender, libdata_receiver) = unbounded::<LibraryData>();
@@ -229,7 +183,7 @@ pub fn main_loop(
229 Err(RecvError) => return Err("vfs died".into()), 183 Err(RecvError) => return Err("vfs died".into()),
230 }, 184 },
231 recv(libdata_receiver) -> data => Event::Lib(data.unwrap()), 185 recv(libdata_receiver) -> data => Event::Lib(data.unwrap()),
232 recv(world_state.check_watcher.task_recv) -> task => match task { 186 recv(world_state.flycheck.as_ref().map_or(&never(), |it| &it.task_recv)) -> task => match task {
233 Ok(task) => Event::CheckWatcher(task), 187 Ok(task) => Event::CheckWatcher(task),
234 Err(RecvError) => return Err("check watcher died".into()), 188 Err(RecvError) => return Err("check watcher died".into()),
235 } 189 }
@@ -297,9 +251,7 @@ impl fmt::Debug for Event {
297 } 251 }
298 } 252 }
299 Event::Task(Task::Notify(not)) => { 253 Event::Task(Task::Notify(not)) => {
300 if notification_is::<req::PublishDecorations>(not) 254 if notification_is::<req::PublishDiagnostics>(not) {
301 || notification_is::<req::PublishDiagnostics>(not)
302 {
303 return debug_verbose_not(not, f); 255 return debug_verbose_not(not, f);
304 } 256 }
305 } 257 }
@@ -333,7 +285,10 @@ struct LoopState {
333 in_flight_libraries: usize, 285 in_flight_libraries: usize,
334 pending_libraries: Vec<(SourceRootId, Vec<(FileId, RelativePathBuf, Arc<String>)>)>, 286 pending_libraries: Vec<(SourceRootId, Vec<(FileId, RelativePathBuf, Arc<String>)>)>,
335 workspace_loaded: bool, 287 workspace_loaded: bool,
336 roots_scanned_progress: Option<usize>, 288 roots_progress_reported: Option<usize>,
289 roots_scanned: usize,
290 roots_total: usize,
291 configuration_request_id: Option<RequestId>,
337} 292}
338 293
339impl LoopState { 294impl LoopState {
@@ -377,6 +332,7 @@ fn loop_turn(
377 world_state.add_lib(lib); 332 world_state.add_lib(lib);
378 world_state.maybe_collect_garbage(); 333 world_state.maybe_collect_garbage();
379 loop_state.in_flight_libraries -= 1; 334 loop_state.in_flight_libraries -= 1;
335 loop_state.roots_scanned += 1;
380 } 336 }
381 Event::CheckWatcher(task) => on_check_task(task, world_state, task_sender)?, 337 Event::CheckWatcher(task) => on_check_task(task, world_state, task_sender)?,
382 Event::Msg(msg) => match msg { 338 Event::Msg(msg) => match msg {
@@ -390,25 +346,41 @@ fn loop_turn(
390 req, 346 req,
391 )?, 347 )?,
392 Message::Notification(not) => { 348 Message::Notification(not) => {
393 on_notification( 349 on_notification(&connection.sender, world_state, loop_state, not)?;
394 &connection.sender,
395 world_state,
396 &mut loop_state.pending_requests,
397 &mut loop_state.subscriptions,
398 not,
399 )?;
400 } 350 }
401 Message::Response(resp) => { 351 Message::Response(resp) => {
402 let removed = loop_state.pending_responses.remove(&resp.id); 352 let removed = loop_state.pending_responses.remove(&resp.id);
403 if !removed { 353 if !removed {
404 log::error!("unexpected response: {:?}", resp) 354 log::error!("unexpected response: {:?}", resp)
405 } 355 }
356
357 if Some(&resp.id) == loop_state.configuration_request_id.as_ref() {
358 loop_state.configuration_request_id = None;
359 log::debug!("config update response: '{:?}", resp);
360 let Response { error, result, .. } = resp;
361
362 match (error, result) {
363 (Some(err), _) => {
364 log::error!("failed to fetch the server settings: {:?}", err)
365 }
366 (None, Some(configs)) => {
367 if let Some(new_config) = configs.get(0) {
368 let mut config = world_state.config.clone();
369 config.update(&new_config);
370 world_state.update_configuration(config);
371 }
372 }
373 (None, None) => {
374 log::error!("received empty server settings response from the client")
375 }
376 }
377 }
406 } 378 }
407 }, 379 },
408 }; 380 };
409 381
410 let mut state_changed = false; 382 let mut state_changed = false;
411 if let Some(changes) = world_state.process_changes() { 383 if let Some(changes) = world_state.process_changes(&mut loop_state.roots_scanned) {
412 state_changed = true; 384 state_changed = true;
413 loop_state.pending_libraries.extend(changes); 385 loop_state.pending_libraries.extend(changes);
414 } 386 }
@@ -427,28 +399,33 @@ fn loop_turn(
427 }); 399 });
428 } 400 }
429 401
402 let show_progress =
403 !loop_state.workspace_loaded && world_state.config.notifications.workspace_loaded;
404
430 if !loop_state.workspace_loaded 405 if !loop_state.workspace_loaded
431 && world_state.roots_to_scan == 0 406 && loop_state.roots_scanned == loop_state.roots_total
432 && loop_state.pending_libraries.is_empty() 407 && loop_state.pending_libraries.is_empty()
433 && loop_state.in_flight_libraries == 0 408 && loop_state.in_flight_libraries == 0
434 { 409 {
435 loop_state.workspace_loaded = true; 410 loop_state.workspace_loaded = true;
436 world_state.check_watcher.update(); 411 if let Some(flycheck) = &world_state.flycheck {
412 flycheck.update();
413 }
437 pool.execute({ 414 pool.execute({
438 let subs = loop_state.subscriptions.subscriptions(); 415 let subs = loop_state.subscriptions.subscriptions();
439 let snap = world_state.snapshot(); 416 let snap = world_state.snapshot();
440 move || snap.analysis().prime_caches(subs).unwrap_or_else(|_: Canceled| ()) 417 move || snap.analysis().prime_caches(subs).unwrap_or_else(|_: Canceled| ())
441 }); 418 });
442 send_startup_progress(&connection.sender, loop_state, world_state); 419 }
443 } else if !loop_state.workspace_loaded { 420
444 send_startup_progress(&connection.sender, loop_state, world_state); 421 if show_progress {
422 send_startup_progress(&connection.sender, loop_state);
445 } 423 }
446 424
447 if state_changed { 425 if state_changed {
448 update_file_notifications_on_threadpool( 426 update_file_notifications_on_threadpool(
449 pool, 427 pool,
450 world_state.snapshot(), 428 world_state.snapshot(),
451 world_state.options.publish_decorations,
452 task_sender.clone(), 429 task_sender.clone(),
453 loop_state.subscriptions.subscriptions(), 430 loop_state.subscriptions.subscriptions(),
454 ) 431 )
@@ -529,7 +506,6 @@ fn on_request(
529 .on::<req::GotoTypeDefinition>(handlers::handle_goto_type_definition)? 506 .on::<req::GotoTypeDefinition>(handlers::handle_goto_type_definition)?
530 .on::<req::ParentModule>(handlers::handle_parent_module)? 507 .on::<req::ParentModule>(handlers::handle_parent_module)?
531 .on::<req::Runnables>(handlers::handle_runnables)? 508 .on::<req::Runnables>(handlers::handle_runnables)?
532 .on::<req::DecorationsRequest>(handlers::handle_decorations)?
533 .on::<req::Completion>(handlers::handle_completion)? 509 .on::<req::Completion>(handlers::handle_completion)?
534 .on::<req::CodeActionRequest>(handlers::handle_code_action)? 510 .on::<req::CodeActionRequest>(handlers::handle_code_action)?
535 .on::<req::CodeLensRequest>(handlers::handle_code_lens)? 511 .on::<req::CodeLensRequest>(handlers::handle_code_lens)?
@@ -556,8 +532,7 @@ fn on_request(
556fn on_notification( 532fn on_notification(
557 msg_sender: &Sender<Message>, 533 msg_sender: &Sender<Message>,
558 state: &mut WorldState, 534 state: &mut WorldState,
559 pending_requests: &mut PendingRequests, 535 loop_state: &mut LoopState,
560 subs: &mut Subscriptions,
561 not: Notification, 536 not: Notification,
562) -> Result<()> { 537) -> Result<()> {
563 let not = match notification_cast::<req::Cancel>(not) { 538 let not = match notification_cast::<req::Cancel>(not) {
@@ -566,7 +541,7 @@ fn on_notification(
566 NumberOrString::Number(id) => id.into(), 541 NumberOrString::Number(id) => id.into(),
567 NumberOrString::String(id) => id.into(), 542 NumberOrString::String(id) => id.into(),
568 }; 543 };
569 if pending_requests.cancel(&id) { 544 if loop_state.pending_requests.cancel(&id) {
570 let response = Response::new_err( 545 let response = Response::new_err(
571 id, 546 id,
572 ErrorCode::RequestCanceled as i32, 547 ErrorCode::RequestCanceled as i32,
@@ -585,7 +560,7 @@ fn on_notification(
585 if let Some(file_id) = 560 if let Some(file_id) =
586 state.vfs.write().add_file_overlay(&path, params.text_document.text) 561 state.vfs.write().add_file_overlay(&path, params.text_document.text)
587 { 562 {
588 subs.add_sub(FileId(file_id.0)); 563 loop_state.subscriptions.add_sub(FileId(file_id.0));
589 } 564 }
590 return Ok(()); 565 return Ok(());
591 } 566 }
@@ -604,7 +579,9 @@ fn on_notification(
604 }; 579 };
605 let not = match notification_cast::<req::DidSaveTextDocument>(not) { 580 let not = match notification_cast::<req::DidSaveTextDocument>(not) {
606 Ok(_params) => { 581 Ok(_params) => {
607 state.check_watcher.update(); 582 if let Some(flycheck) = &state.flycheck {
583 flycheck.update();
584 }
608 return Ok(()); 585 return Ok(());
609 } 586 }
610 Err(not) => not, 587 Err(not) => not,
@@ -614,7 +591,7 @@ fn on_notification(
614 let uri = params.text_document.uri; 591 let uri = params.text_document.uri;
615 let path = uri.to_file_path().map_err(|()| format!("invalid uri: {}", uri))?; 592 let path = uri.to_file_path().map_err(|()| format!("invalid uri: {}", uri))?;
616 if let Some(file_id) = state.vfs.write().remove_file_overlay(path.as_path()) { 593 if let Some(file_id) = state.vfs.write().remove_file_overlay(path.as_path()) {
617 subs.remove_sub(FileId(file_id.0)); 594 loop_state.subscriptions.remove_sub(FileId(file_id.0));
618 } 595 }
619 let params = 596 let params =
620 req::PublishDiagnosticsParams { uri, diagnostics: Vec::new(), version: None }; 597 req::PublishDiagnosticsParams { uri, diagnostics: Vec::new(), version: None };
@@ -625,7 +602,22 @@ fn on_notification(
625 Err(not) => not, 602 Err(not) => not,
626 }; 603 };
627 let not = match notification_cast::<req::DidChangeConfiguration>(not) { 604 let not = match notification_cast::<req::DidChangeConfiguration>(not) {
628 Ok(_params) => { 605 Ok(_) => {
606 // As stated in https://github.com/microsoft/language-server-protocol/issues/676,
607 // this notification's parameters should be ignored and the actual config queried separately.
608 let request_id = loop_state.next_request_id();
609 let request = request_new::<req::WorkspaceConfiguration>(
610 request_id.clone(),
611 req::ConfigurationParams {
612 items: vec![req::ConfigurationItem {
613 scope_uri: None,
614 section: Some("rust-analyzer".to_string()),
615 }],
616 },
617 );
618 msg_sender.send(request.into())?;
619 loop_state.configuration_request_id = Some(request_id);
620
629 return Ok(()); 621 return Ok(());
630 } 622 }
631 Err(not) => not, 623 Err(not) => not,
@@ -706,21 +698,13 @@ fn on_diagnostic_task(task: DiagnosticTask, msg_sender: &Sender<Message>, state:
706 } 698 }
707} 699}
708 700
709fn send_startup_progress( 701fn send_startup_progress(sender: &Sender<Message>, loop_state: &mut LoopState) {
710 sender: &Sender<Message>, 702 let total: usize = loop_state.roots_total;
711 loop_state: &mut LoopState, 703 let prev = loop_state.roots_progress_reported;
712 world_state: &WorldState, 704 let progress = loop_state.roots_scanned;
713) { 705 loop_state.roots_progress_reported = Some(progress);
714 if !world_state.feature_flags.get("notifications.workspace-loaded") {
715 return;
716 }
717
718 let total: usize = world_state.workspaces.iter().map(|it| it.n_packages()).sum();
719 let prev_progress = loop_state.roots_scanned_progress;
720 let progress = total - world_state.roots_to_scan;
721 loop_state.roots_scanned_progress = Some(progress);
722 706
723 match (prev_progress, loop_state.workspace_loaded) { 707 match (prev, loop_state.workspace_loaded) {
724 (None, false) => { 708 (None, false) => {
725 let work_done_progress_create = request_new::<req::WorkDoneProgressCreate>( 709 let work_done_progress_create = request_new::<req::WorkDoneProgressCreate>(
726 loop_state.next_request_id(), 710 loop_state.next_request_id(),
@@ -755,14 +739,14 @@ fn send_startup_progress(
755 ), 739 ),
756 _ => {} 740 _ => {}
757 } 741 }
758}
759 742
760fn send_startup_progress_notif(sender: &Sender<Message>, work_done_progress: WorkDoneProgress) { 743 fn send_startup_progress_notif(sender: &Sender<Message>, work_done_progress: WorkDoneProgress) {
761 let notif = notification_new::<req::Progress>(req::ProgressParams { 744 let notif = notification_new::<req::Progress>(req::ProgressParams {
762 token: req::ProgressToken::String("rustAnalyzer/startup".into()), 745 token: req::ProgressToken::String("rustAnalyzer/startup".into()),
763 value: req::ProgressParamsValue::WorkDone(work_done_progress), 746 value: req::ProgressParamsValue::WorkDone(work_done_progress),
764 }); 747 });
765 sender.send(notif.into()).unwrap(); 748 sender.send(notif.into()).unwrap();
749 }
766} 750}
767 751
768struct PoolDispatcher<'a> { 752struct PoolDispatcher<'a> {
@@ -902,15 +886,13 @@ where
902fn update_file_notifications_on_threadpool( 886fn update_file_notifications_on_threadpool(
903 pool: &ThreadPool, 887 pool: &ThreadPool,
904 world: WorldSnapshot, 888 world: WorldSnapshot,
905 publish_decorations: bool,
906 task_sender: Sender<Task>, 889 task_sender: Sender<Task>,
907 subscriptions: Vec<FileId>, 890 subscriptions: Vec<FileId>,
908) { 891) {
909 log::trace!("updating notifications for {:?}", subscriptions); 892 log::trace!("updating notifications for {:?}", subscriptions);
910 let publish_diagnostics = world.feature_flags.get("lsp.diagnostics"); 893 if world.config.publish_diagnostics {
911 pool.execute(move || { 894 pool.execute(move || {
912 for file_id in subscriptions { 895 for file_id in subscriptions {
913 if publish_diagnostics {
914 match handlers::publish_diagnostics(&world, file_id) { 896 match handlers::publish_diagnostics(&world, file_id) {
915 Err(e) => { 897 Err(e) => {
916 if !is_canceled(&e) { 898 if !is_canceled(&e) {
@@ -922,21 +904,8 @@ fn update_file_notifications_on_threadpool(
922 } 904 }
923 } 905 }
924 } 906 }
925 if publish_decorations { 907 })
926 match handlers::publish_decorations(&world, file_id) { 908 }
927 Err(e) => {
928 if !is_canceled(&e) {
929 log::error!("failed to compute decorations: {:?}", e);
930 }
931 }
932 Ok(params) => {
933 let not = notification_new::<req::PublishDecorations>(params);
934 task_sender.send(Task::Notify(not)).unwrap();
935 }
936 }
937 }
938 }
939 });
940} 909}
941 910
942pub fn show_message(typ: req::MessageType, message: impl Into<String>, sender: &Sender<Message>) { 911pub fn show_message(typ: req::MessageType, message: impl Into<String>, sender: &Sender<Message>) {
diff --git a/crates/rust-analyzer/src/main_loop/handlers.rs b/crates/rust-analyzer/src/main_loop/handlers.rs
index 12f8ca297..b207f0764 100644
--- a/crates/rust-analyzer/src/main_loop/handlers.rs
+++ b/crates/rust-analyzer/src/main_loop/handlers.rs
@@ -14,13 +14,13 @@ use lsp_types::{
14 CodeAction, CodeActionResponse, CodeLens, Command, CompletionItem, Diagnostic, 14 CodeAction, CodeActionResponse, CodeLens, Command, CompletionItem, Diagnostic,
15 DocumentFormattingParams, DocumentHighlight, DocumentSymbol, FoldingRange, FoldingRangeParams, 15 DocumentFormattingParams, DocumentHighlight, DocumentSymbol, FoldingRange, FoldingRangeParams,
16 Hover, HoverContents, Location, MarkupContent, MarkupKind, Position, PrepareRenameResponse, 16 Hover, HoverContents, Location, MarkupContent, MarkupKind, Position, PrepareRenameResponse,
17 Range, RenameParams, SemanticTokens, SemanticTokensParams, SemanticTokensRangeParams, 17 Range, RenameParams, SemanticTokensParams, SemanticTokensRangeParams,
18 SemanticTokensRangeResult, SemanticTokensResult, SymbolInformation, TextDocumentIdentifier, 18 SemanticTokensRangeResult, SemanticTokensResult, SymbolInformation, TextDocumentIdentifier,
19 TextEdit, WorkspaceEdit, 19 TextEdit, WorkspaceEdit,
20}; 20};
21use ra_ide::{ 21use ra_ide::{
22 Assist, AssistId, CompletionOptions, FileId, FilePosition, FileRange, Query, RangeInfo, 22 Assist, AssistId, FileId, FilePosition, FileRange, Query, RangeInfo, Runnable, RunnableKind,
23 Runnable, RunnableKind, SearchScope, 23 SearchScope,
24}; 24};
25use ra_prof::profile; 25use ra_prof::profile;
26use ra_syntax::{AstNode, SyntaxKind, TextRange, TextUnit}; 26use ra_syntax::{AstNode, SyntaxKind, TextRange, TextUnit};
@@ -31,13 +31,14 @@ use stdx::format_to;
31 31
32use crate::{ 32use crate::{
33 cargo_target_spec::CargoTargetSpec, 33 cargo_target_spec::CargoTargetSpec,
34 config::RustfmtConfig,
34 conv::{ 35 conv::{
35 to_call_hierarchy_item, to_location, Conv, ConvWith, FoldConvCtx, MapConvWith, TryConvWith, 36 to_call_hierarchy_item, to_location, Conv, ConvWith, FoldConvCtx, MapConvWith, TryConvWith,
36 TryConvWithToVec, 37 TryConvWithToVec,
37 }, 38 },
38 diagnostics::DiagnosticTask, 39 diagnostics::DiagnosticTask,
39 from_json, 40 from_json,
40 req::{self, Decoration, InlayHint, InlayHintsParams}, 41 req::{self, InlayHint, InlayHintsParams},
41 semantic_tokens::SemanticTokensBuilder, 42 semantic_tokens::SemanticTokensBuilder,
42 world::WorldSnapshot, 43 world::WorldSnapshot,
43 LspError, Result, 44 LspError, Result,
@@ -388,15 +389,6 @@ pub fn handle_runnables(
388 Ok(res) 389 Ok(res)
389} 390}
390 391
391pub fn handle_decorations(
392 world: WorldSnapshot,
393 params: TextDocumentIdentifier,
394) -> Result<Vec<Decoration>> {
395 let _p = profile("handle_decorations");
396 let file_id = params.try_conv_with(&world)?;
397 highlight(&world, file_id)
398}
399
400pub fn handle_completion( 392pub fn handle_completion(
401 world: WorldSnapshot, 393 world: WorldSnapshot,
402 params: req::CompletionParams, 394 params: req::CompletionParams,
@@ -425,15 +417,7 @@ pub fn handle_completion(
425 return Ok(None); 417 return Ok(None);
426 } 418 }
427 419
428 let options = CompletionOptions { 420 let items = match world.analysis().completions(position, &world.config.completion)? {
429 enable_postfix_completions: world.feature_flags.get("completion.enable-postfix"),
430 add_call_parenthesis: world.feature_flags.get("completion.insertion.add-call-parenthesis"),
431 add_call_argument_snippets: world
432 .feature_flags
433 .get("completion.insertion.add-argument-snippets"),
434 };
435
436 let items = match world.analysis().completions(position, &options)? {
437 None => return Ok(None), 421 None => return Ok(None),
438 Some(items) => items, 422 Some(items) => items,
439 }; 423 };
@@ -457,7 +441,7 @@ pub fn handle_folding_range(
457 let ctx = FoldConvCtx { 441 let ctx = FoldConvCtx {
458 text: &text, 442 text: &text,
459 line_index: &line_index, 443 line_index: &line_index,
460 line_folding_only: world.options.line_folding_only, 444 line_folding_only: world.config.client_caps.line_folding_only,
461 }; 445 };
462 let res = Some(folds.into_iter().map_conv_with(&ctx).collect()); 446 let res = Some(folds.into_iter().map_conv_with(&ctx).collect());
463 Ok(res) 447 Ok(res)
@@ -470,7 +454,7 @@ pub fn handle_signature_help(
470 let _p = profile("handle_signature_help"); 454 let _p = profile("handle_signature_help");
471 let position = params.try_conv_with(&world)?; 455 let position = params.try_conv_with(&world)?;
472 if let Some(call_info) = world.analysis().call_info(position)? { 456 if let Some(call_info) = world.analysis().call_info(position)? {
473 let concise = !world.feature_flags.get("call-info.full"); 457 let concise = !world.config.call_info_full;
474 let mut active_parameter = call_info.active_parameter.map(|it| it as i64); 458 let mut active_parameter = call_info.active_parameter.map(|it| it as i64);
475 if concise && call_info.signature.has_self_param { 459 if concise && call_info.signature.has_self_param {
476 active_parameter = active_parameter.map(|it| it.saturating_sub(1)); 460 active_parameter = active_parameter.map(|it| it.saturating_sub(1));
@@ -610,13 +594,24 @@ pub fn handle_formatting(
610 let file_line_index = world.analysis().file_line_index(file_id)?; 594 let file_line_index = world.analysis().file_line_index(file_id)?;
611 let end_position = TextUnit::of_str(&file).conv_with(&file_line_index); 595 let end_position = TextUnit::of_str(&file).conv_with(&file_line_index);
612 596
613 let mut rustfmt = process::Command::new("rustfmt"); 597 let mut rustfmt = match &world.config.rustfmt {
614 rustfmt.args(&world.options.rustfmt_args); 598 RustfmtConfig::Rustfmt { extra_args } => {
615 if let Some(&crate_id) = crate_ids.first() { 599 let mut cmd = process::Command::new("rustfmt");
616 // Assume all crates are in the same edition 600 cmd.args(extra_args);
617 let edition = world.analysis().crate_edition(crate_id)?; 601 if let Some(&crate_id) = crate_ids.first() {
618 rustfmt.args(&["--edition", &edition.to_string()]); 602 // Assume all crates are in the same edition
619 } 603 let edition = world.analysis().crate_edition(crate_id)?;
604 cmd.arg("--edition");
605 cmd.arg(edition.to_string());
606 }
607 cmd
608 }
609 RustfmtConfig::CustomCommand { command, args } => {
610 let mut cmd = process::Command::new(command);
611 cmd.args(args);
612 cmd
613 }
614 };
620 615
621 if let Ok(path) = params.text_document.uri.to_file_path() { 616 if let Ok(path) = params.text_document.uri.to_file_path() {
622 if let Some(parent) = path.parent() { 617 if let Some(parent) = path.parent() {
@@ -815,23 +810,21 @@ pub fn handle_code_lens(
815 }; 810 };
816 lenses.push(lens); 811 lenses.push(lens);
817 812
818 if world.options.vscode_lldb { 813 if r.args[0] == "run" {
819 if r.args[0] == "run" { 814 r.args[0] = "build".into();
820 r.args[0] = "build".into(); 815 } else {
821 } else { 816 r.args.push("--no-run".into());
822 r.args.push("--no-run".into());
823 }
824 let debug_lens = CodeLens {
825 range: r.range,
826 command: Some(Command {
827 title: "Debug".into(),
828 command: "rust-analyzer.debugSingle".into(),
829 arguments: Some(vec![to_value(r).unwrap()]),
830 }),
831 data: None,
832 };
833 lenses.push(debug_lens);
834 } 817 }
818 let debug_lens = CodeLens {
819 range: r.range,
820 command: Some(Command {
821 title: "Debug".into(),
822 command: "rust-analyzer.debugSingle".into(),
823 arguments: Some(vec![to_value(r).unwrap()]),
824 }),
825 data: None,
826 };
827 lenses.push(debug_lens);
835 } 828 }
836 829
837 // Handle impls 830 // Handle impls
@@ -966,15 +959,6 @@ pub fn publish_diagnostics(world: &WorldSnapshot, file_id: FileId) -> Result<Dia
966 Ok(DiagnosticTask::SetNative(file_id, diagnostics)) 959 Ok(DiagnosticTask::SetNative(file_id, diagnostics))
967} 960}
968 961
969pub fn publish_decorations(
970 world: &WorldSnapshot,
971 file_id: FileId,
972) -> Result<req::PublishDecorationsParams> {
973 let _p = profile("publish_decorations");
974 let uri = world.file_id_to_uri(file_id)?;
975 Ok(req::PublishDecorationsParams { uri, decorations: highlight(&world, file_id)? })
976}
977
978fn to_lsp_runnable( 962fn to_lsp_runnable(
979 world: &WorldSnapshot, 963 world: &WorldSnapshot,
980 file_id: FileId, 964 file_id: FileId,
@@ -1004,21 +988,6 @@ fn to_lsp_runnable(
1004 }) 988 })
1005} 989}
1006 990
1007fn highlight(world: &WorldSnapshot, file_id: FileId) -> Result<Vec<Decoration>> {
1008 let line_index = world.analysis().file_line_index(file_id)?;
1009 let res = world
1010 .analysis()
1011 .highlight(file_id)?
1012 .into_iter()
1013 .map(|h| Decoration {
1014 range: h.range.conv_with(&line_index),
1015 tag: h.highlight.to_string(),
1016 binding_hash: h.binding_hash.map(|x| x.to_string()),
1017 })
1018 .collect();
1019 Ok(res)
1020}
1021
1022pub fn handle_inlay_hints( 991pub fn handle_inlay_hints(
1023 world: WorldSnapshot, 992 world: WorldSnapshot,
1024 params: InlayHintsParams, 993 params: InlayHintsParams,
@@ -1028,7 +997,7 @@ pub fn handle_inlay_hints(
1028 let analysis = world.analysis(); 997 let analysis = world.analysis();
1029 let line_index = analysis.file_line_index(file_id)?; 998 let line_index = analysis.file_line_index(file_id)?;
1030 Ok(analysis 999 Ok(analysis
1031 .inlay_hints(file_id, &world.options.inlay_hints)? 1000 .inlay_hints(file_id, &world.config.inlay_hints)?
1032 .into_iter() 1001 .into_iter()
1033 .map_conv_with(&line_index) 1002 .map_conv_with(&line_index)
1034 .collect()) 1003 .collect())
@@ -1145,7 +1114,7 @@ pub fn handle_semantic_tokens(
1145 } 1114 }
1146 } 1115 }
1147 1116
1148 let tokens = SemanticTokens { data: builder.build(), ..Default::default() }; 1117 let tokens = builder.build();
1149 1118
1150 Ok(Some(tokens.into())) 1119 Ok(Some(tokens.into()))
1151} 1120}
@@ -1166,7 +1135,7 @@ pub fn handle_semantic_tokens_range(
1166 builder.push(highlight_range.range.conv_with(&line_index), token_type, token_modifiers); 1135 builder.push(highlight_range.range.conv_with(&line_index), token_type, token_modifiers);
1167 } 1136 }
1168 1137
1169 let tokens = SemanticTokens { data: builder.build(), ..Default::default() }; 1138 let tokens = builder.build();
1170 1139
1171 Ok(Some(tokens.into())) 1140 Ok(Some(tokens.into()))
1172} 1141}
diff --git a/crates/rust-analyzer/src/main_loop/subscriptions.rs b/crates/rust-analyzer/src/main_loop/subscriptions.rs
index bee6437cf..2c76418be 100644
--- a/crates/rust-analyzer/src/main_loop/subscriptions.rs
+++ b/crates/rust-analyzer/src/main_loop/subscriptions.rs
@@ -17,6 +17,6 @@ impl Subscriptions {
17 self.subs.remove(&file_id); 17 self.subs.remove(&file_id);
18 } 18 }
19 pub(crate) fn subscriptions(&self) -> Vec<FileId> { 19 pub(crate) fn subscriptions(&self) -> Vec<FileId> {
20 self.subs.iter().cloned().collect() 20 self.subs.iter().copied().collect()
21 } 21 }
22} 22}
diff --git a/crates/rust-analyzer/src/req.rs b/crates/rust-analyzer/src/req.rs
index 8557294f6..b8b627e28 100644
--- a/crates/rust-analyzer/src/req.rs
+++ b/crates/rust-analyzer/src/req.rs
@@ -1,13 +1,13 @@
1//! Defines `rust-analyzer` specific custom messages. 1//! Defines `rust-analyzer` specific custom messages.
2 2
3use lsp_types::{Location, Position, Range, TextDocumentIdentifier, Url}; 3use lsp_types::{Location, Position, Range, TextDocumentIdentifier};
4use rustc_hash::FxHashMap; 4use rustc_hash::FxHashMap;
5use serde::{Deserialize, Serialize}; 5use serde::{Deserialize, Serialize};
6 6
7pub use lsp_types::{ 7pub use lsp_types::{
8 notification::*, request::*, ApplyWorkspaceEditParams, CodeActionParams, CodeLens, 8 notification::*, request::*, ApplyWorkspaceEditParams, CodeActionParams, CodeLens,
9 CodeLensParams, CompletionParams, CompletionResponse, DiagnosticTag, 9 CodeLensParams, CompletionParams, CompletionResponse, ConfigurationItem, ConfigurationParams,
10 DidChangeConfigurationParams, DidChangeWatchedFilesParams, 10 DiagnosticTag, DidChangeConfigurationParams, DidChangeWatchedFilesParams,
11 DidChangeWatchedFilesRegistrationOptions, DocumentOnTypeFormattingParams, DocumentSymbolParams, 11 DidChangeWatchedFilesRegistrationOptions, DocumentOnTypeFormattingParams, DocumentSymbolParams,
12 DocumentSymbolResponse, FileSystemWatcher, Hover, InitializeResult, MessageType, 12 DocumentSymbolResponse, FileSystemWatcher, Hover, InitializeResult, MessageType,
13 PartialResultParams, ProgressParams, ProgressParamsValue, ProgressToken, 13 PartialResultParams, ProgressParams, ProgressParamsValue, ProgressToken,
@@ -86,36 +86,6 @@ pub struct FindMatchingBraceParams {
86 pub offsets: Vec<Position>, 86 pub offsets: Vec<Position>,
87} 87}
88 88
89pub enum DecorationsRequest {}
90
91impl Request for DecorationsRequest {
92 type Params = TextDocumentIdentifier;
93 type Result = Vec<Decoration>;
94 const METHOD: &'static str = "rust-analyzer/decorationsRequest";
95}
96
97pub enum PublishDecorations {}
98
99impl Notification for PublishDecorations {
100 type Params = PublishDecorationsParams;
101 const METHOD: &'static str = "rust-analyzer/publishDecorations";
102}
103
104#[derive(Deserialize, Serialize, Debug)]
105#[serde(rename_all = "camelCase")]
106pub struct PublishDecorationsParams {
107 pub uri: Url,
108 pub decorations: Vec<Decoration>,
109}
110
111#[derive(Deserialize, Serialize, Debug)]
112#[serde(rename_all = "camelCase")]
113pub struct Decoration {
114 pub range: Range,
115 pub tag: String,
116 pub binding_hash: Option<String>,
117}
118
119pub enum ParentModule {} 89pub enum ParentModule {}
120 90
121impl Request for ParentModule { 91impl Request for ParentModule {
diff --git a/crates/rust-analyzer/src/semantic_tokens.rs b/crates/rust-analyzer/src/semantic_tokens.rs
index d9ba77050..2a66bbfd8 100644
--- a/crates/rust-analyzer/src/semantic_tokens.rs
+++ b/crates/rust-analyzer/src/semantic_tokens.rs
@@ -2,7 +2,7 @@
2 2
3use std::ops; 3use std::ops;
4 4
5use lsp_types::{Range, SemanticToken, SemanticTokenModifier, SemanticTokenType}; 5use lsp_types::{Range, SemanticToken, SemanticTokenModifier, SemanticTokenType, SemanticTokens};
6 6
7pub(crate) const ATTRIBUTE: SemanticTokenType = SemanticTokenType::new("attribute"); 7pub(crate) const ATTRIBUTE: SemanticTokenType = SemanticTokenType::new("attribute");
8pub(crate) const BUILTIN_TYPE: SemanticTokenType = SemanticTokenType::new("builtinType"); 8pub(crate) const BUILTIN_TYPE: SemanticTokenType = SemanticTokenType::new("builtinType");
@@ -109,8 +109,8 @@ impl SemanticTokensBuilder {
109 self.prev_char = range.start.character as u32; 109 self.prev_char = range.start.character as u32;
110 } 110 }
111 111
112 pub fn build(self) -> Vec<SemanticToken> { 112 pub fn build(self) -> SemanticTokens {
113 self.data 113 SemanticTokens { result_id: None, data: self.data }
114 } 114 }
115} 115}
116 116
diff --git a/crates/rust-analyzer/src/vfs_glob.rs b/crates/rust-analyzer/src/vfs_glob.rs
index 91b33f94e..ff37a7008 100644
--- a/crates/rust-analyzer/src/vfs_glob.rs
+++ b/crates/rust-analyzer/src/vfs_glob.rs
@@ -29,10 +29,14 @@ impl RustPackageFilterBuilder {
29 self.is_member = is_member; 29 self.is_member = is_member;
30 self 30 self
31 } 31 }
32 pub fn exclude(mut self, glob: Glob) -> RustPackageFilterBuilder { 32
33 self.exclude.add(glob); 33 pub fn exclude(mut self, globs: impl IntoIterator<Item = Glob>) -> RustPackageFilterBuilder {
34 for glob in globs.into_iter() {
35 self.exclude.add(glob);
36 }
34 self 37 self
35 } 38 }
39
36 pub fn into_vfs_filter(self) -> Box<dyn Filter> { 40 pub fn into_vfs_filter(self) -> Box<dyn Filter> {
37 let RustPackageFilterBuilder { is_member, mut exclude } = self; 41 let RustPackageFilterBuilder { is_member, mut exclude } = self;
38 for &glob in ALWAYS_IGNORED { 42 for &glob in ALWAYS_IGNORED {
@@ -87,7 +91,7 @@ fn test_globs() {
87 91
88 let filter = RustPackageFilterBuilder::default() 92 let filter = RustPackageFilterBuilder::default()
89 .set_member(true) 93 .set_member(true)
90 .exclude(Glob::new("src/llvm-project/**").unwrap()) 94 .exclude(std::iter::once(Glob::new("src/llvm-project/**").unwrap()))
91 .into_vfs_filter(); 95 .into_vfs_filter();
92 96
93 assert!(!filter.include_dir(RelativePath::new("src/llvm-project/clang"))); 97 assert!(!filter.include_dir(RelativePath::new("src/llvm-project/clang")));
diff --git a/crates/rust-analyzer/src/world.rs b/crates/rust-analyzer/src/world.rs
index 64a7b907e..365f57d8c 100644
--- a/crates/rust-analyzer/src/world.rs
+++ b/crates/rust-analyzer/src/world.rs
@@ -11,10 +11,9 @@ use std::{
11use crossbeam_channel::{unbounded, Receiver}; 11use crossbeam_channel::{unbounded, Receiver};
12use lsp_types::Url; 12use lsp_types::Url;
13use parking_lot::RwLock; 13use parking_lot::RwLock;
14use ra_cargo_watch::{url_from_path_with_drive_lowercasing, CheckOptions, CheckWatcher}; 14use ra_flycheck::{url_from_path_with_drive_lowercasing, Flycheck, FlycheckConfig};
15use ra_ide::{ 15use ra_ide::{
16 Analysis, AnalysisChange, AnalysisHost, CrateGraph, FileId, InlayHintsOptions, LibraryData, 16 Analysis, AnalysisChange, AnalysisHost, CrateGraph, FileId, LibraryData, SourceRootId,
17 SourceRootId,
18}; 17};
19use ra_project_model::{get_rustc_cfg_options, ProcMacroClient, ProjectWorkspace}; 18use ra_project_model::{get_rustc_cfg_options, ProcMacroClient, ProjectWorkspace};
20use ra_vfs::{LineEndings, RootEntry, Vfs, VfsChange, VfsFile, VfsRoot, VfsTask, Watch}; 19use ra_vfs::{LineEndings, RootEntry, Vfs, VfsChange, VfsFile, VfsRoot, VfsTask, Watch};
@@ -22,8 +21,8 @@ use relative_path::RelativePathBuf;
22use stdx::format_to; 21use stdx::format_to;
23 22
24use crate::{ 23use crate::{
24 config::Config,
25 diagnostics::{CheckFixes, DiagnosticCollection}, 25 diagnostics::{CheckFixes, DiagnosticCollection},
26 feature_flags::FeatureFlags,
27 main_loop::pending_requests::{CompletedRequest, LatestRequests}, 26 main_loop::pending_requests::{CompletedRequest, LatestRequests},
28 vfs_glob::{Glob, RustPackageFilterBuilder}, 27 vfs_glob::{Glob, RustPackageFilterBuilder},
29 LspError, Result, 28 LspError, Result,
@@ -31,15 +30,22 @@ use crate::{
31use ra_db::ExternSourceId; 30use ra_db::ExternSourceId;
32use rustc_hash::{FxHashMap, FxHashSet}; 31use rustc_hash::{FxHashMap, FxHashSet};
33 32
34#[derive(Debug, Clone)] 33fn create_flycheck(workspaces: &[ProjectWorkspace], config: &FlycheckConfig) -> Option<Flycheck> {
35pub struct Options { 34 // FIXME: Figure out the multi-workspace situation
36 pub publish_decorations: bool, 35 workspaces
37 pub supports_location_link: bool, 36 .iter()
38 pub line_folding_only: bool, 37 .find_map(|w| match w {
39 pub inlay_hints: InlayHintsOptions, 38 ProjectWorkspace::Cargo { cargo, .. } => Some(cargo),
40 pub rustfmt_args: Vec<String>, 39 ProjectWorkspace::Json { .. } => None,
41 pub cargo_watch: CheckOptions, 40 })
42 pub vscode_lldb: bool, 41 .map(|cargo| {
42 let cargo_project_root = cargo.workspace_root().to_path_buf();
43 Some(Flycheck::new(config.clone(), cargo_project_root))
44 })
45 .unwrap_or_else(|| {
46 log::warn!("Cargo check watching only supported for cargo workspaces, disabling");
47 None
48 })
43} 49}
44 50
45/// `WorldState` is the primary mutable state of the language server 51/// `WorldState` is the primary mutable state of the language server
@@ -49,24 +55,20 @@ pub struct Options {
49/// incremental salsa database. 55/// incremental salsa database.
50#[derive(Debug)] 56#[derive(Debug)]
51pub struct WorldState { 57pub struct WorldState {
52 pub options: Options, 58 pub config: Config,
53 pub feature_flags: Arc<FeatureFlags>,
54 //FIXME: this belongs to `LoopState` rather than to `WorldState`
55 pub roots_to_scan: usize,
56 pub roots: Vec<PathBuf>, 59 pub roots: Vec<PathBuf>,
57 pub workspaces: Arc<Vec<ProjectWorkspace>>, 60 pub workspaces: Arc<Vec<ProjectWorkspace>>,
58 pub analysis_host: AnalysisHost, 61 pub analysis_host: AnalysisHost,
59 pub vfs: Arc<RwLock<Vfs>>, 62 pub vfs: Arc<RwLock<Vfs>>,
60 pub task_receiver: Receiver<VfsTask>, 63 pub task_receiver: Receiver<VfsTask>,
61 pub latest_requests: Arc<RwLock<LatestRequests>>, 64 pub latest_requests: Arc<RwLock<LatestRequests>>,
62 pub check_watcher: CheckWatcher, 65 pub flycheck: Option<Flycheck>,
63 pub diagnostics: DiagnosticCollection, 66 pub diagnostics: DiagnosticCollection,
64} 67}
65 68
66/// An immutable snapshot of the world's state at a point in time. 69/// An immutable snapshot of the world's state at a point in time.
67pub struct WorldSnapshot { 70pub struct WorldSnapshot {
68 pub options: Options, 71 pub config: Config,
69 pub feature_flags: Arc<FeatureFlags>,
70 pub workspaces: Arc<Vec<ProjectWorkspace>>, 72 pub workspaces: Arc<Vec<ProjectWorkspace>>,
71 pub analysis: Analysis, 73 pub analysis: Analysis,
72 pub latest_requests: Arc<RwLock<LatestRequests>>, 74 pub latest_requests: Arc<RwLock<LatestRequests>>,
@@ -81,49 +83,39 @@ impl WorldState {
81 lru_capacity: Option<usize>, 83 lru_capacity: Option<usize>,
82 exclude_globs: &[Glob], 84 exclude_globs: &[Glob],
83 watch: Watch, 85 watch: Watch,
84 options: Options, 86 config: Config,
85 feature_flags: FeatureFlags,
86 ) -> WorldState { 87 ) -> WorldState {
87 let mut change = AnalysisChange::new(); 88 let mut change = AnalysisChange::new();
88 89
89 let mut roots = Vec::new(); 90 let extern_dirs: FxHashSet<_> =
90 roots.extend(folder_roots.iter().map(|path| { 91 workspaces.iter().flat_map(ProjectWorkspace::out_dirs).collect();
91 let mut filter = RustPackageFilterBuilder::default().set_member(true); 92
92 for glob in exclude_globs.iter() { 93 let roots: Vec<_> = {
93 filter = filter.exclude(glob.clone()); 94 let create_filter = |is_member| {
94 } 95 RustPackageFilterBuilder::default()
95 RootEntry::new(path.clone(), filter.into_vfs_filter()) 96 .set_member(is_member)
96 })); 97 .exclude(exclude_globs.iter().cloned())
97 for ws in workspaces.iter() { 98 .into_vfs_filter()
98 roots.extend(ws.to_roots().into_iter().map(|pkg_root| { 99 };
99 let mut filter = 100 folder_roots
100 RustPackageFilterBuilder::default().set_member(pkg_root.is_member()); 101 .iter()
101 for glob in exclude_globs.iter() { 102 .map(|path| RootEntry::new(path.clone(), create_filter(true)))
102 filter = filter.exclude(glob.clone()); 103 .chain(workspaces.iter().flat_map(ProjectWorkspace::to_roots).map(|pkg_root| {
103 } 104 RootEntry::new(pkg_root.path().to_owned(), create_filter(pkg_root.is_member()))
104 RootEntry::new(pkg_root.path().clone(), filter.into_vfs_filter()) 105 }))
105 })); 106 .chain(
106 } 107 extern_dirs
107 108 .iter()
108 let mut extern_dirs = FxHashSet::default(); 109 .map(|path| RootEntry::new(path.to_owned(), create_filter(false))),
109 for ws in workspaces.iter() { 110 )
110 extern_dirs.extend(ws.out_dirs()); 111 .collect()
111 } 112 };
112
113 let mut extern_source_roots = FxHashMap::default();
114
115 roots.extend(extern_dirs.iter().map(|path| {
116 let mut filter = RustPackageFilterBuilder::default().set_member(false);
117 for glob in exclude_globs.iter() {
118 filter = filter.exclude(glob.clone());
119 }
120 RootEntry::new(PathBuf::from(&path), filter.into_vfs_filter())
121 }));
122 113
123 let (task_sender, task_receiver) = unbounded(); 114 let (task_sender, task_receiver) = unbounded();
124 let task_sender = Box::new(move |t| task_sender.send(t).unwrap()); 115 let task_sender = Box::new(move |t| task_sender.send(t).unwrap());
125 let (mut vfs, vfs_roots) = Vfs::new(roots, task_sender, watch); 116 let (mut vfs, vfs_roots) = Vfs::new(roots, task_sender, watch);
126 let roots_to_scan = vfs_roots.len(); 117
118 let mut extern_source_roots = FxHashMap::default();
127 for r in vfs_roots { 119 for r in vfs_roots {
128 let vfs_root_path = vfs.root2path(r); 120 let vfs_root_path = vfs.root2path(r);
129 let is_local = folder_roots.iter().any(|it| vfs_root_path.starts_with(it)); 121 let is_local = folder_roots.iter().any(|it| vfs_root_path.starts_with(it));
@@ -151,8 +143,23 @@ impl WorldState {
151 vfs_file.map(|f| FileId(f.0)) 143 vfs_file.map(|f| FileId(f.0))
152 }; 144 };
153 145
154 let proc_macro_client = 146 let proc_macro_client = match &config.proc_macro_srv {
155 ProcMacroClient::extern_process(std::path::Path::new("ra_proc_macro_srv")); 147 None => ProcMacroClient::dummy(),
148 Some(srv) => {
149 let path = Path::new(&srv);
150 match ProcMacroClient::extern_process(path) {
151 Ok(it) => it,
152 Err(err) => {
153 log::error!(
154 "Fail to run ra_proc_macro_srv from path {}, error : {}",
155 path.to_string_lossy(),
156 err
157 );
158 ProcMacroClient::dummy()
159 }
160 }
161 }
162 };
156 163
157 workspaces 164 workspaces
158 .iter() 165 .iter()
@@ -169,43 +176,38 @@ impl WorldState {
169 }); 176 });
170 change.set_crate_graph(crate_graph); 177 change.set_crate_graph(crate_graph);
171 178
172 // FIXME: Figure out the multi-workspace situation 179 let flycheck = config.check.as_ref().and_then(|c| create_flycheck(&workspaces, c));
173 let check_watcher = workspaces
174 .iter()
175 .find_map(|w| match w {
176 ProjectWorkspace::Cargo { cargo, .. } => Some(cargo),
177 ProjectWorkspace::Json { .. } => None,
178 })
179 .map(|cargo| {
180 let cargo_project_root = cargo.workspace_root().to_path_buf();
181 CheckWatcher::new(&options.cargo_watch, cargo_project_root)
182 })
183 .unwrap_or_else(|| {
184 log::warn!("Cargo check watching only supported for cargo workspaces, disabling");
185 CheckWatcher::dummy()
186 });
187 180
188 let mut analysis_host = AnalysisHost::new(lru_capacity); 181 let mut analysis_host = AnalysisHost::new(lru_capacity);
189 analysis_host.apply_change(change); 182 analysis_host.apply_change(change);
190 WorldState { 183 WorldState {
191 options, 184 config: config,
192 feature_flags: Arc::new(feature_flags),
193 roots_to_scan,
194 roots: folder_roots, 185 roots: folder_roots,
195 workspaces: Arc::new(workspaces), 186 workspaces: Arc::new(workspaces),
196 analysis_host, 187 analysis_host,
197 vfs: Arc::new(RwLock::new(vfs)), 188 vfs: Arc::new(RwLock::new(vfs)),
198 task_receiver, 189 task_receiver,
199 latest_requests: Default::default(), 190 latest_requests: Default::default(),
200 check_watcher, 191 flycheck,
201 diagnostics: Default::default(), 192 diagnostics: Default::default(),
202 } 193 }
203 } 194 }
204 195
196 pub fn update_configuration(&mut self, config: Config) {
197 self.analysis_host.update_lru_capacity(config.lru_capacity);
198 if config.check != self.config.check {
199 self.flycheck =
200 config.check.as_ref().and_then(|it| create_flycheck(&self.workspaces, it));
201 }
202
203 self.config = config;
204 }
205
205 /// Returns a vec of libraries 206 /// Returns a vec of libraries
206 /// FIXME: better API here 207 /// FIXME: better API here
207 pub fn process_changes( 208 pub fn process_changes(
208 &mut self, 209 &mut self,
210 roots_scanned: &mut usize,
209 ) -> Option<Vec<(SourceRootId, Vec<(FileId, RelativePathBuf, Arc<String>)>)>> { 211 ) -> Option<Vec<(SourceRootId, Vec<(FileId, RelativePathBuf, Arc<String>)>)>> {
210 let changes = self.vfs.write().commit_changes(); 212 let changes = self.vfs.write().commit_changes();
211 if changes.is_empty() { 213 if changes.is_empty() {
@@ -219,7 +221,7 @@ impl WorldState {
219 let root_path = self.vfs.read().root2path(root); 221 let root_path = self.vfs.read().root2path(root);
220 let is_local = self.roots.iter().any(|r| root_path.starts_with(r)); 222 let is_local = self.roots.iter().any(|r| root_path.starts_with(r));
221 if is_local { 223 if is_local {
222 self.roots_to_scan -= 1; 224 *roots_scanned += 1;
223 for (file, path, text) in files { 225 for (file, path, text) in files {
224 change.add_file(SourceRootId(root.0), FileId(file.0), path, text); 226 change.add_file(SourceRootId(root.0), FileId(file.0), path, text);
225 } 227 }
@@ -247,7 +249,6 @@ impl WorldState {
247 } 249 }
248 250
249 pub fn add_lib(&mut self, data: LibraryData) { 251 pub fn add_lib(&mut self, data: LibraryData) {
250 self.roots_to_scan -= 1;
251 let mut change = AnalysisChange::new(); 252 let mut change = AnalysisChange::new();
252 change.add_library(data); 253 change.add_library(data);
253 self.analysis_host.apply_change(change); 254 self.analysis_host.apply_change(change);
@@ -255,8 +256,7 @@ impl WorldState {
255 256
256 pub fn snapshot(&self) -> WorldSnapshot { 257 pub fn snapshot(&self) -> WorldSnapshot {
257 WorldSnapshot { 258 WorldSnapshot {
258 options: self.options.clone(), 259 config: self.config.clone(),
259 feature_flags: Arc::clone(&self.feature_flags),
260 workspaces: Arc::clone(&self.workspaces), 260 workspaces: Arc::clone(&self.workspaces),
261 analysis: self.analysis_host.analysis(), 261 analysis: self.analysis_host.analysis(),
262 vfs: Arc::clone(&self.vfs), 262 vfs: Arc::clone(&self.vfs),
diff --git a/crates/rust-analyzer/tests/heavy_tests/main.rs b/crates/rust-analyzer/tests/heavy_tests/main.rs
index 145429571..638813311 100644
--- a/crates/rust-analyzer/tests/heavy_tests/main.rs
+++ b/crates/rust-analyzer/tests/heavy_tests/main.rs
@@ -9,7 +9,7 @@ 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, OnEnter, Runnables, RunnablesParams, 12 Formatting, GotoDefinition, OnEnter, Runnables, RunnablesParams,
13}; 13};
14use serde_json::json; 14use serde_json::json;
15use tempfile::TempDir; 15use tempfile::TempDir;
@@ -581,3 +581,47 @@ version = \"0.0.0\"
581 }), 581 }),
582 ); 582 );
583} 583}
584
585#[test]
586fn resolve_include_concat_env() {
587 if skip_slow_tests() {
588 return;
589 }
590
591 let server = Project::with_fixture(
592 r###"
593//- Cargo.toml
594[package]
595name = "foo"
596version = "0.0.0"
597
598//- build.rs
599use std::{env, fs, path::Path};
600
601fn main() {
602 let out_dir = env::var_os("OUT_DIR").unwrap();
603 let dest_path = Path::new(&out_dir).join("hello.rs");
604 fs::write(
605 &dest_path,
606 r#"pub fn message() -> &'static str { "Hello, World!" }"#,
607 )
608 .unwrap();
609 println!("cargo:rerun-if-changed=build.rs");
610}
611//- src/main.rs
612include!(concat!(env!("OUT_DIR"), "/hello.rs"));
613
614fn main() { message(); }
615"###,
616 )
617 .with_config(|config| {
618 config.cargo.load_out_dirs_from_check = true;
619 })
620 .server();
621 server.wait_until_workspace_is_loaded();
622 let res = server.send_request::<GotoDefinition>(TextDocumentPositionParams::new(
623 server.doc_id("src/main.rs"),
624 Position::new(2, 15),
625 ));
626 assert!(format!("{}", res).contains("hello.rs"));
627}
diff --git a/crates/rust-analyzer/tests/heavy_tests/support.rs b/crates/rust-analyzer/tests/heavy_tests/support.rs
index 1d7062bdf..7eebedff7 100644
--- a/crates/rust-analyzer/tests/heavy_tests/support.rs
+++ b/crates/rust-analyzer/tests/heavy_tests/support.rs
@@ -11,8 +11,7 @@ use lsp_server::{Connection, Message, Notification, Request};
11use lsp_types::{ 11use lsp_types::{
12 notification::{DidOpenTextDocument, Exit}, 12 notification::{DidOpenTextDocument, Exit},
13 request::Shutdown, 13 request::Shutdown,
14 ClientCapabilities, DidOpenTextDocumentParams, GotoCapability, TextDocumentClientCapabilities, 14 DidOpenTextDocumentParams, TextDocumentIdentifier, TextDocumentItem, Url, WorkDoneProgress,
15 TextDocumentIdentifier, TextDocumentItem, Url, WorkDoneProgress,
16}; 15};
17use serde::Serialize; 16use serde::Serialize;
18use serde_json::{to_string_pretty, Value}; 17use serde_json::{to_string_pretty, Value};
@@ -20,18 +19,22 @@ use tempfile::TempDir;
20use test_utils::{find_mismatch, parse_fixture}; 19use test_utils::{find_mismatch, parse_fixture};
21 20
22use req::{ProgressParams, ProgressParamsValue}; 21use req::{ProgressParams, ProgressParamsValue};
23use rust_analyzer::{main_loop, req, ServerConfig}; 22use rust_analyzer::{
23 config::{ClientCapsConfig, Config},
24 main_loop, req,
25};
24 26
25pub struct Project<'a> { 27pub struct Project<'a> {
26 fixture: &'a str, 28 fixture: &'a str,
27 with_sysroot: bool, 29 with_sysroot: bool,
28 tmp_dir: Option<TempDir>, 30 tmp_dir: Option<TempDir>,
29 roots: Vec<PathBuf>, 31 roots: Vec<PathBuf>,
32 config: Option<Box<dyn Fn(&mut Config)>>,
30} 33}
31 34
32impl<'a> Project<'a> { 35impl<'a> Project<'a> {
33 pub fn with_fixture(fixture: &str) -> Project { 36 pub fn with_fixture(fixture: &str) -> Project {
34 Project { fixture, tmp_dir: None, roots: vec![], with_sysroot: false } 37 Project { fixture, tmp_dir: None, roots: vec![], with_sysroot: false, config: None }
35 } 38 }
36 39
37 pub fn tmp_dir(mut self, tmp_dir: TempDir) -> Project<'a> { 40 pub fn tmp_dir(mut self, tmp_dir: TempDir) -> Project<'a> {
@@ -49,6 +52,11 @@ impl<'a> Project<'a> {
49 self 52 self
50 } 53 }
51 54
55 pub fn with_config(mut self, config: impl Fn(&mut Config) + 'static) -> Project<'a> {
56 self.config = Some(Box::new(config));
57 self
58 }
59
52 pub fn server(self) -> Server { 60 pub fn server(self) -> Server {
53 let tmp_dir = self.tmp_dir.unwrap_or_else(|| TempDir::new().unwrap()); 61 let tmp_dir = self.tmp_dir.unwrap_or_else(|| TempDir::new().unwrap());
54 static INIT: Once = Once::new(); 62 static INIT: Once = Once::new();
@@ -72,7 +80,17 @@ impl<'a> Project<'a> {
72 80
73 let roots = self.roots.into_iter().map(|root| tmp_dir.path().join(root)).collect(); 81 let roots = self.roots.into_iter().map(|root| tmp_dir.path().join(root)).collect();
74 82
75 Server::new(tmp_dir, self.with_sysroot, roots, paths) 83 let mut config = Config {
84 client_caps: ClientCapsConfig { location_link: true, ..Default::default() },
85 with_sysroot: self.with_sysroot,
86 ..Config::default()
87 };
88
89 if let Some(f) = &self.config {
90 f(&mut config)
91 }
92
93 Server::new(tmp_dir, config, roots, paths)
76 } 94 }
77} 95}
78 96
@@ -83,15 +101,16 @@ pub fn project(fixture: &str) -> Server {
83pub struct Server { 101pub struct Server {
84 req_id: Cell<u64>, 102 req_id: Cell<u64>,
85 messages: RefCell<Vec<Message>>, 103 messages: RefCell<Vec<Message>>,
86 dir: TempDir,
87 _thread: jod_thread::JoinHandle<()>, 104 _thread: jod_thread::JoinHandle<()>,
88 client: Connection, 105 client: Connection,
106 /// XXX: remove the tempdir last
107 dir: TempDir,
89} 108}
90 109
91impl Server { 110impl Server {
92 fn new( 111 fn new(
93 dir: TempDir, 112 dir: TempDir,
94 with_sysroot: bool, 113 config: Config,
95 roots: Vec<PathBuf>, 114 roots: Vec<PathBuf>,
96 files: Vec<(PathBuf, String)>, 115 files: Vec<(PathBuf, String)>,
97 ) -> Server { 116 ) -> Server {
@@ -102,26 +121,7 @@ impl Server {
102 121
103 let _thread = jod_thread::Builder::new() 122 let _thread = jod_thread::Builder::new()
104 .name("test server".to_string()) 123 .name("test server".to_string())
105 .spawn(move || { 124 .spawn(move || main_loop(roots, config, connection).unwrap())
106 main_loop(
107 roots,
108 ClientCapabilities {
109 workspace: None,
110 text_document: Some(TextDocumentClientCapabilities {
111 definition: Some(GotoCapability {
112 dynamic_registration: None,
113 link_support: Some(true),
114 }),
115 ..Default::default()
116 }),
117 window: None,
118 experimental: None,
119 },
120 ServerConfig { with_sysroot, ..ServerConfig::default() },
121 connection,
122 )
123 .unwrap()
124 })
125 .expect("failed to spawn a thread"); 125 .expect("failed to spawn a thread");
126 126
127 let res = 127 let res =
@@ -187,6 +187,7 @@ impl Server {
187 self.client.sender.send(r.into()).unwrap(); 187 self.client.sender.send(r.into()).unwrap();
188 while let Some(msg) = self.recv() { 188 while let Some(msg) = self.recv() {
189 match msg { 189 match msg {
190 Message::Request(req) if req.method == "window/workDoneProgress/create" => (),
190 Message::Request(req) => panic!("unexpected request: {:?}", req), 191 Message::Request(req) => panic!("unexpected request: {:?}", req),
191 Message::Notification(_) => (), 192 Message::Notification(_) => (),
192 Message::Response(res) => { 193 Message::Response(res) => {
diff --git a/crates/stdx/src/lib.rs b/crates/stdx/src/lib.rs
index 8492c17af..d2efa2236 100644
--- a/crates/stdx/src/lib.rs
+++ b/crates/stdx/src/lib.rs
@@ -5,7 +5,7 @@ use std::{cell::Cell, fmt};
5/// Appends formatted string to a `String`. 5/// Appends formatted string to a `String`.
6#[macro_export] 6#[macro_export]
7macro_rules! format_to { 7macro_rules! format_to {
8 (&buf:expr) => (); 8 ($buf:expr) => ();
9 ($buf:expr, $lit:literal $($arg:tt)*) => { 9 ($buf:expr, $lit:literal $($arg:tt)*) => {
10 { use ::std::fmt::Write as _; let _ = ::std::write!($buf, $lit $($arg)*); } 10 { use ::std::fmt::Write as _; let _ = ::std::write!($buf, $lit $($arg)*); }
11 }; 11 };