diff options
86 files changed, 3256 insertions, 2318 deletions
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 5050c558c..fb7afe9fd 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml | |||
@@ -11,6 +11,7 @@ env: | |||
11 | CARGO_INCREMENTAL: 0 | 11 | CARGO_INCREMENTAL: 0 |
12 | CARGO_NET_RETRY: 10 | 12 | CARGO_NET_RETRY: 10 |
13 | RUN_SLOW_TESTS: 1 | 13 | RUN_SLOW_TESTS: 1 |
14 | RUST_BACKTRACE: short | ||
14 | RUSTFLAGS: -D warnings | 15 | RUSTFLAGS: -D warnings |
15 | RUSTUP_MAX_RETRIES: 10 | 16 | RUSTUP_MAX_RETRIES: 10 |
16 | 17 | ||
diff --git a/Cargo.lock b/Cargo.lock index 8d81c4839..c07a9614a 100644 --- a/Cargo.lock +++ b/Cargo.lock | |||
@@ -11,9 +11,9 @@ dependencies = [ | |||
11 | 11 | ||
12 | [[package]] | 12 | [[package]] |
13 | name = "anyhow" | 13 | name = "anyhow" |
14 | version = "1.0.27" | 14 | version = "1.0.28" |
15 | source = "registry+https://github.com/rust-lang/crates.io-index" | 15 | source = "registry+https://github.com/rust-lang/crates.io-index" |
16 | checksum = "013a6e0a2cbe3d20f9c60b65458f7a7f7a5e636c5d0f45a5a6aee5d4b1f01785" | 16 | checksum = "d9a60d744a80c30fcb657dfe2c1b22bcb3e814c1a1e3674f32bf5820b570fbff" |
17 | 17 | ||
18 | [[package]] | 18 | [[package]] |
19 | name = "anymap" | 19 | name = "anymap" |
@@ -331,9 +331,9 @@ dependencies = [ | |||
331 | 331 | ||
332 | [[package]] | 332 | [[package]] |
333 | name = "filetime" | 333 | name = "filetime" |
334 | version = "0.2.8" | 334 | version = "0.2.9" |
335 | source = "registry+https://github.com/rust-lang/crates.io-index" | 335 | source = "registry+https://github.com/rust-lang/crates.io-index" |
336 | checksum = "1ff6d4dab0aa0c8e6346d46052e93b13a16cf847b54ed357087c35011048cc7d" | 336 | checksum = "f59efc38004c988e4201d11d263b8171f49a2e7ec0bdbb71773433f271504a5e" |
337 | dependencies = [ | 337 | dependencies = [ |
338 | "cfg-if", | 338 | "cfg-if", |
339 | "libc", | 339 | "libc", |
@@ -435,9 +435,9 @@ dependencies = [ | |||
435 | 435 | ||
436 | [[package]] | 436 | [[package]] |
437 | name = "hermit-abi" | 437 | name = "hermit-abi" |
438 | version = "0.1.8" | 438 | version = "0.1.10" |
439 | source = "registry+https://github.com/rust-lang/crates.io-index" | 439 | source = "registry+https://github.com/rust-lang/crates.io-index" |
440 | checksum = "1010591b26bbfe835e9faeabeb11866061cc7dcebffd56ad7d0942d0e61aefd8" | 440 | checksum = "725cf19794cf90aa94e65050cb4191ff5d8fa87a498383774c47b332e3af952e" |
441 | dependencies = [ | 441 | dependencies = [ |
442 | "libc", | 442 | "libc", |
443 | ] | 443 | ] |
@@ -563,9 +563,9 @@ dependencies = [ | |||
563 | 563 | ||
564 | [[package]] | 564 | [[package]] |
565 | name = "jod-thread" | 565 | name = "jod-thread" |
566 | version = "0.1.0" | 566 | version = "0.1.1" |
567 | source = "registry+https://github.com/rust-lang/crates.io-index" | 567 | source = "registry+https://github.com/rust-lang/crates.io-index" |
568 | checksum = "2f52a11f73b88fab829a0e4d9e13ea5982c7ac457c72eb3541d82a4afdfce4ff" | 568 | checksum = "4022656272c3e564a7cdebcaaba6518d844b0d0c1836597196efb5bfeb98bb49" |
569 | 569 | ||
570 | [[package]] | 570 | [[package]] |
571 | name = "kernel32-sys" | 571 | name = "kernel32-sys" |
@@ -792,9 +792,9 @@ dependencies = [ | |||
792 | 792 | ||
793 | [[package]] | 793 | [[package]] |
794 | name = "paste" | 794 | name = "paste" |
795 | version = "0.1.8" | 795 | version = "0.1.9" |
796 | source = "registry+https://github.com/rust-lang/crates.io-index" | 796 | source = "registry+https://github.com/rust-lang/crates.io-index" |
797 | checksum = "8292c1e1e81ddb552c4c90c36af201a0ce7e34995f55f0480f01052f242811c9" | 797 | checksum = "092d791bf7847f70bbd49085489fba25fc2c193571752bff9e36e74e72403932" |
798 | dependencies = [ | 798 | dependencies = [ |
799 | "paste-impl", | 799 | "paste-impl", |
800 | "proc-macro-hack", | 800 | "proc-macro-hack", |
@@ -802,9 +802,9 @@ dependencies = [ | |||
802 | 802 | ||
803 | [[package]] | 803 | [[package]] |
804 | name = "paste-impl" | 804 | name = "paste-impl" |
805 | version = "0.1.8" | 805 | version = "0.1.9" |
806 | source = "registry+https://github.com/rust-lang/crates.io-index" | 806 | source = "registry+https://github.com/rust-lang/crates.io-index" |
807 | checksum = "5e9c43f2645f06ee452544ad032886a75f3d1797b9487dcadcae9100ba58a51c" | 807 | checksum = "406c23fb4c45cc6f68a9bbabb8ec7bd6f8cfcbd17e9e8f72c2460282f8325729" |
808 | dependencies = [ | 808 | dependencies = [ |
809 | "proc-macro-hack", | 809 | "proc-macro-hack", |
810 | "proc-macro2", | 810 | "proc-macro2", |
@@ -842,15 +842,15 @@ checksum = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b" | |||
842 | 842 | ||
843 | [[package]] | 843 | [[package]] |
844 | name = "proc-macro-hack" | 844 | name = "proc-macro-hack" |
845 | version = "0.5.14" | 845 | version = "0.5.15" |
846 | source = "registry+https://github.com/rust-lang/crates.io-index" | 846 | source = "registry+https://github.com/rust-lang/crates.io-index" |
847 | checksum = "fcfdefadc3d57ca21cf17990a28ef4c0f7c61383a28cb7604cf4a18e6ede1420" | 847 | checksum = "0d659fe7c6d27f25e9d80a1a094c223f5246f6a6596453e09d7229bf42750b63" |
848 | 848 | ||
849 | [[package]] | 849 | [[package]] |
850 | name = "proc-macro2" | 850 | name = "proc-macro2" |
851 | version = "1.0.9" | 851 | version = "1.0.10" |
852 | source = "registry+https://github.com/rust-lang/crates.io-index" | 852 | source = "registry+https://github.com/rust-lang/crates.io-index" |
853 | checksum = "6c09721c6781493a2a492a96b5a5bf19b65917fe6728884e7c44dd0c60ca3435" | 853 | checksum = "df246d292ff63439fea9bc8c0a270bed0e390d5ebd4db4ba15aba81111b5abe3" |
854 | dependencies = [ | 854 | dependencies = [ |
855 | "unicode-xid", | 855 | "unicode-xid", |
856 | ] | 856 | ] |
@@ -887,18 +887,6 @@ dependencies = [ | |||
887 | ] | 887 | ] |
888 | 888 | ||
889 | [[package]] | 889 | [[package]] |
890 | name = "ra_cargo_watch" | ||
891 | version = "0.1.0" | ||
892 | dependencies = [ | ||
893 | "cargo_metadata", | ||
894 | "crossbeam-channel", | ||
895 | "insta", | ||
896 | "log", | ||
897 | "lsp-types", | ||
898 | "serde_json", | ||
899 | ] | ||
900 | |||
901 | [[package]] | ||
902 | name = "ra_cfg" | 890 | name = "ra_cfg" |
903 | version = "0.1.0" | 891 | version = "0.1.0" |
904 | dependencies = [ | 892 | dependencies = [ |
@@ -923,6 +911,19 @@ dependencies = [ | |||
923 | ] | 911 | ] |
924 | 912 | ||
925 | [[package]] | 913 | [[package]] |
914 | name = "ra_flycheck" | ||
915 | version = "0.1.0" | ||
916 | dependencies = [ | ||
917 | "cargo_metadata", | ||
918 | "crossbeam-channel", | ||
919 | "insta", | ||
920 | "jod-thread", | ||
921 | "log", | ||
922 | "lsp-types", | ||
923 | "serde_json", | ||
924 | ] | ||
925 | |||
926 | [[package]] | ||
926 | name = "ra_fmt" | 927 | name = "ra_fmt" |
927 | version = "0.1.0" | 928 | version = "0.1.0" |
928 | dependencies = [ | 929 | dependencies = [ |
@@ -1074,7 +1075,12 @@ dependencies = [ | |||
1074 | name = "ra_proc_macro" | 1075 | name = "ra_proc_macro" |
1075 | version = "0.1.0" | 1076 | version = "0.1.0" |
1076 | dependencies = [ | 1077 | dependencies = [ |
1078 | "crossbeam-channel", | ||
1079 | "jod-thread", | ||
1080 | "log", | ||
1077 | "ra_tt", | 1081 | "ra_tt", |
1082 | "serde", | ||
1083 | "serde_json", | ||
1078 | ] | 1084 | ] |
1079 | 1085 | ||
1080 | [[package]] | 1086 | [[package]] |
@@ -1095,7 +1101,6 @@ dependencies = [ | |||
1095 | "cargo_metadata", | 1101 | "cargo_metadata", |
1096 | "log", | 1102 | "log", |
1097 | "ra_arena", | 1103 | "ra_arena", |
1098 | "ra_cargo_watch", | ||
1099 | "ra_cfg", | 1104 | "ra_cfg", |
1100 | "ra_db", | 1105 | "ra_db", |
1101 | "ra_proc_macro", | 1106 | "ra_proc_macro", |
@@ -1294,8 +1299,8 @@ dependencies = [ | |||
1294 | "lsp-types", | 1299 | "lsp-types", |
1295 | "parking_lot", | 1300 | "parking_lot", |
1296 | "pico-args", | 1301 | "pico-args", |
1297 | "ra_cargo_watch", | ||
1298 | "ra_db", | 1302 | "ra_db", |
1303 | "ra_flycheck", | ||
1299 | "ra_hir", | 1304 | "ra_hir", |
1300 | "ra_hir_def", | 1305 | "ra_hir_def", |
1301 | "ra_hir_ty", | 1306 | "ra_hir_ty", |
@@ -1425,9 +1430,9 @@ dependencies = [ | |||
1425 | 1430 | ||
1426 | [[package]] | 1431 | [[package]] |
1427 | name = "serde_json" | 1432 | name = "serde_json" |
1428 | version = "1.0.48" | 1433 | version = "1.0.50" |
1429 | source = "registry+https://github.com/rust-lang/crates.io-index" | 1434 | source = "registry+https://github.com/rust-lang/crates.io-index" |
1430 | checksum = "9371ade75d4c2d6cb154141b9752cf3781ec9c05e0e5cf35060e1e70ee7b9c25" | 1435 | checksum = "78a7a12c167809363ec3bd7329fc0a3369056996de43c4b37ef3cd54a6ce4867" |
1431 | dependencies = [ | 1436 | dependencies = [ |
1432 | "itoa", | 1437 | "itoa", |
1433 | "ryu", | 1438 | "ryu", |
@@ -1650,9 +1655,9 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" | |||
1650 | 1655 | ||
1651 | [[package]] | 1656 | [[package]] |
1652 | name = "winapi-util" | 1657 | name = "winapi-util" |
1653 | version = "0.1.3" | 1658 | version = "0.1.4" |
1654 | source = "registry+https://github.com/rust-lang/crates.io-index" | 1659 | source = "registry+https://github.com/rust-lang/crates.io-index" |
1655 | checksum = "4ccfbf554c6ad11084fb7517daca16cfdcaccbdadba4fc336f032a8b12c2ad80" | 1660 | checksum = "fa515c5163a99cc82bab70fd3bfdd36d827be85de63737b40fcef2ce084a436e" |
1656 | dependencies = [ | 1661 | dependencies = [ |
1657 | "winapi 0.3.8", | 1662 | "winapi 0.3.8", |
1658 | ] | 1663 | ] |
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> { | |||
14 | impl<T, V> ArenaMap<Idx<T>, V> { | 14 | impl<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 | |||
3 | use std::iter; | 1 | use std::iter; |
4 | 2 | ||
5 | use hir::{Adt, HasSource, ModuleDef, Semantics}; | 3 | use hir::{Adt, HasSource, ModuleDef, Semantics}; |
6 | use itertools::Itertools; | 4 | use itertools::Itertools; |
7 | use ra_ide_db::RootDatabase; | 5 | use ra_ide_db::RootDatabase; |
6 | use ra_syntax::ast::{self, make, AstNode, MatchArm, NameOwner, Pat}; | ||
8 | 7 | ||
9 | use crate::{Assist, AssistCtx, AssistId}; | 8 | use crate::{Assist, AssistCtx, AssistId}; |
10 | use ra_syntax::ast::{self, edit::IndentLevel, make, AstNode, NameOwner}; | ||
11 | |||
12 | use 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. | ||
4 | use cargo_metadata::Message; | ||
5 | use crossbeam_channel::{never, select, unbounded, Receiver, RecvError, Sender}; | ||
6 | use lsp_types::{ | ||
7 | CodeAction, CodeActionOrCommand, Diagnostic, Url, WorkDoneProgress, WorkDoneProgressBegin, | ||
8 | WorkDoneProgressEnd, WorkDoneProgressReport, | ||
9 | }; | ||
10 | use std::{ | ||
11 | error, fmt, | ||
12 | io::{BufRead, BufReader}, | ||
13 | path::{Path, PathBuf}, | ||
14 | process::{Command, Stdio}, | ||
15 | thread::JoinHandle, | ||
16 | time::Instant, | ||
17 | }; | ||
18 | |||
19 | mod conv; | ||
20 | |||
21 | use crate::conv::{map_rust_diagnostic_to_lsp, MappedRustDiagnostic}; | ||
22 | |||
23 | pub use crate::conv::url_from_path_with_drive_lowercasing; | ||
24 | |||
25 | #[derive(Clone, Debug)] | ||
26 | pub 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)] | ||
38 | pub struct CheckWatcher { | ||
39 | pub task_recv: Receiver<CheckTask>, | ||
40 | cmd_send: Option<Sender<CheckCommand>>, | ||
41 | handle: Option<JoinHandle<()>>, | ||
42 | } | ||
43 | |||
44 | impl 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 | |||
70 | impl 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)] | ||
87 | pub 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 | |||
98 | pub enum CheckCommand { | ||
99 | /// Request re-start of check thread | ||
100 | Update, | ||
101 | } | ||
102 | |||
103 | struct CheckWatcherThread { | ||
104 | options: CheckOptions, | ||
105 | workspace_root: PathBuf, | ||
106 | watcher: WatchThread, | ||
107 | last_update_req: Option<Instant>, | ||
108 | } | ||
109 | |||
110 | impl 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)] | ||
227 | pub 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. | ||
239 | struct WatchThread { | ||
240 | handle: Option<JoinHandle<()>>, | ||
241 | message_recv: Receiver<CheckEvent>, | ||
242 | } | ||
243 | |||
244 | enum CheckEvent { | ||
245 | Begin, | ||
246 | Msg(cargo_metadata::Message), | ||
247 | End, | ||
248 | } | ||
249 | |||
250 | #[derive(Debug)] | ||
251 | pub struct CargoError(String); | ||
252 | |||
253 | impl fmt::Display for CargoError { | ||
254 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||
255 | write!(f, "Cargo failed: {}", self.0) | ||
256 | } | ||
257 | } | ||
258 | impl error::Error for CargoError {} | ||
259 | |||
260 | pub 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 | |||
334 | impl 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 | |||
390 | impl 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] |
2 | edition = "2018" | 2 | edition = "2018" |
3 | name = "ra_cargo_watch" | 3 | name = "ra_flycheck" |
4 | version = "0.1.0" | 4 | version = "0.1.0" |
5 | authors = ["rust-analyzer developers"] | 5 | authors = ["rust-analyzer developers"] |
6 | 6 | ||
@@ -10,6 +10,7 @@ lsp-types = { version = "0.73.0", features = ["proposed"] } | |||
10 | log = "0.4.8" | 10 | log = "0.4.8" |
11 | cargo_metadata = "0.9.1" | 11 | cargo_metadata = "0.9.1" |
12 | serde_json = "1.0.48" | 12 | serde_json = "1.0.48" |
13 | jod-thread = "0.1.1" | ||
13 | 14 | ||
14 | [dev-dependencies] | 15 | [dev-dependencies] |
15 | insta = "0.15.0" | 16 | insta = "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 | --- |
2 | source: crates/ra_cargo_watch/src/conv/test.rs | 2 | source: crates/ra_flycheck/src/conv/test.rs |
3 | expression: diag | 3 | expression: 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 | --- |
2 | source: crates/ra_cargo_watch/src/conv/test.rs | 2 | source: crates/ra_flycheck/src/conv/test.rs |
3 | expression: diag | 3 | expression: 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 | --- |
2 | source: crates/ra_cargo_watch/src/conv/test.rs | 2 | source: crates/ra_flycheck/src/conv/test.rs |
3 | expression: diag | 3 | expression: 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 | --- |
2 | source: crates/ra_cargo_watch/src/conv/test.rs | 2 | source: crates/ra_flycheck/src/conv/test.rs |
3 | expression: diag | 3 | expression: 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 | --- |
2 | source: crates/ra_cargo_watch/src/conv/test.rs | 2 | source: crates/ra_flycheck/src/conv/test.rs |
3 | expression: diag | 3 | expression: 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 | --- |
2 | source: crates/ra_cargo_watch/src/conv/test.rs | 2 | source: crates/ra_flycheck/src/conv/test.rs |
3 | expression: diag | 3 | expression: 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 | --- |
2 | source: crates/ra_cargo_watch/src/conv/test.rs | 2 | source: crates/ra_flycheck/src/conv/test.rs |
3 | expression: diag | 3 | expression: 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 | --- |
2 | source: crates/ra_cargo_watch/src/conv/test.rs | 2 | source: crates/ra_flycheck/src/conv/test.rs |
3 | expression: diag | 3 | expression: 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. | ||
4 | mod conv; | ||
5 | |||
6 | use std::{ | ||
7 | env, | ||
8 | io::{self, BufRead, BufReader}, | ||
9 | path::PathBuf, | ||
10 | process::{Command, Stdio}, | ||
11 | time::Instant, | ||
12 | }; | ||
13 | |||
14 | use cargo_metadata::Message; | ||
15 | use crossbeam_channel::{never, select, unbounded, Receiver, RecvError, Sender}; | ||
16 | use lsp_types::{ | ||
17 | CodeAction, CodeActionOrCommand, Diagnostic, Url, WorkDoneProgress, WorkDoneProgressBegin, | ||
18 | WorkDoneProgressEnd, WorkDoneProgressReport, | ||
19 | }; | ||
20 | |||
21 | use crate::conv::{map_rust_diagnostic_to_lsp, MappedRustDiagnostic}; | ||
22 | |||
23 | pub use crate::conv::url_from_path_with_drive_lowercasing; | ||
24 | |||
25 | #[derive(Clone, Debug, PartialEq, Eq)] | ||
26 | pub 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)] | ||
36 | pub 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 | |||
43 | impl 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)] | ||
60 | pub 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 | |||
71 | pub enum CheckCommand { | ||
72 | /// Request re-start of check thread | ||
73 | Update, | ||
74 | } | ||
75 | |||
76 | struct 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 | |||
90 | impl 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)] | ||
271 | pub struct DiagnosticWithFixes { | ||
272 | diagnostic: Diagnostic, | ||
273 | fixes: Vec<CodeAction>, | ||
274 | } | ||
275 | |||
276 | enum CheckEvent { | ||
277 | Begin, | ||
278 | Msg(cargo_metadata::Message), | ||
279 | End, | ||
280 | } | ||
281 | |||
282 | fn 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 | |||
338 | fn 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 | |||
735 | impl 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 | ||
12 | macro_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 | |||
12 | impl ProcMacroExpander { | 21 | impl 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 | |||
46 | fn 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::{ | |||
11 | use hir_expand::name::Name; | 11 | use hir_expand::name::Name; |
12 | use test_utils::tested_by; | 12 | use test_utils::tested_by; |
13 | 13 | ||
14 | use super::{BindingMode, InferenceContext}; | 14 | use super::{BindingMode, Expectation, InferenceContext}; |
15 | use crate::{utils::variant_data, Substs, Ty, TypeCtor}; | 15 | use crate::{utils::variant_data, Substs, Ty, TypeCtor}; |
16 | 16 | ||
17 | impl<'a> InferenceContext<'a> { | 17 | impl<'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] |
85 | fn infer_literal_pattern() { | ||
86 | assert_snapshot!( | ||
87 | infer_with_mismatches(r#" | ||
88 | fn any<T>() -> T { loop {} } | ||
89 | fn 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] | ||
145 | fn infer_range_pattern() { | ||
146 | assert_snapshot!( | ||
147 | infer_with_mismatches(r#" | ||
148 | fn 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] | ||
85 | fn infer_pattern_match_ergonomics() { | 169 | fn 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; | |||
5 | mod presentation; | 5 | mod presentation; |
6 | 6 | ||
7 | mod complete_dot; | 7 | mod complete_dot; |
8 | mod complete_record_literal; | 8 | mod complete_record; |
9 | mod complete_record_pattern; | ||
10 | mod complete_pattern; | 9 | mod complete_pattern; |
11 | mod complete_fn_param; | 10 | mod complete_fn_param; |
12 | mod complete_keyword; | 11 | mod 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)] |
37 | pub struct CompletionOptions { | 36 | pub 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 | ||
43 | impl Default for CompletionOptions { | 42 | impl 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 { | |||
75 | pub(crate) fn completions( | 74 | pub(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 | ||
17 | pub(super) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) { | 17 | pub(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. | ||
2 | use crate::completion::{CompletionContext, Completions}; | ||
3 | use ra_syntax::{ast, ast::NameOwner, SmolStr}; | ||
4 | |||
5 | pub(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 | |||
31 | fn 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 | |||
45 | fn 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)] | ||
60 | mod 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 | |||
3 | use crate::completion::{CompletionContext, Completions}; | ||
4 | use ra_syntax::SmolStr; | ||
5 | |||
6 | /// Complete fields in fields literals. | ||
7 | pub(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)] | ||
37 | mod 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 | |||
3 | use crate::completion::{CompletionContext, Completions}; | ||
4 | |||
5 | pub(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)] | ||
19 | mod 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 | }; |
12 | use ra_text_edit::AtomTextEdit; | 12 | use ra_text_edit::AtomTextEdit; |
13 | 13 | ||
14 | use crate::{completion::CompletionOptions, FilePosition}; | 14 | use 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}; | |||
19 | pub(crate) struct CompletionContext<'a> { | 19 | pub(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 | ||
3 | use crate::{ | 3 | use 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 | ||
9 | pub(crate) fn do_completion(code: &str, kind: CompletionKind) -> Vec<CompletionItem> { | 9 | pub(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 | ||
13 | pub(crate) fn do_completion_with_options( | 13 | pub(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::{ | |||
11 | use crate::{FileId, FunctionSignature}; | 11 | use crate::{FileId, FunctionSignature}; |
12 | 12 | ||
13 | #[derive(Clone, Debug, PartialEq, Eq)] | 13 | #[derive(Clone, Debug, PartialEq, Eq)] |
14 | pub struct InlayHintsOptions { | 14 | pub 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 | ||
21 | impl Default for InlayHintsOptions { | 21 | impl 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 { | |||
41 | pub(crate) fn inlay_hints( | 41 | pub(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( | |||
68 | fn get_chaining_hints( | 68 | fn 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( | |||
108 | fn get_param_name_hints( | 118 | fn 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( | |||
148 | fn get_bind_pat_hints( | 158 | fn 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)] |
272 | mod tests { | 282 | mod 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; | |||
62 | pub use crate::{ | 62 | pub 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}; | |||
5 | use ra_ide_db::symbol_index::SymbolsDatabase; | 5 | use ra_ide_db::symbol_index::SymbolsDatabase; |
6 | use ra_ide_db::RootDatabase; | 6 | use ra_ide_db::RootDatabase; |
7 | use ra_syntax::ast::make::try_expr_from_text; | 7 | use ra_syntax::ast::make::try_expr_from_text; |
8 | use ra_syntax::ast::{AstToken, Comment}; | 8 | use ra_syntax::ast::{AstToken, Comment, RecordField, RecordLit}; |
9 | use ra_syntax::{AstNode, SyntaxElement, SyntaxNode}; | 9 | use ra_syntax::{AstNode, SyntaxElement, SyntaxNode}; |
10 | use ra_text_edit::{TextEdit, TextEditBuilder}; | 10 | use ra_text_edit::{TextEdit, TextEditBuilder}; |
11 | use rustc_hash::FxHashMap; | 11 | use 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 | ||
188 | fn find(pattern: &SsrPattern, code: &SyntaxNode) -> SsrMatches { | 188 | fn 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); | ||