aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock55
-rw-r--r--crates/ra_assists/src/assist_ctx.rs2
-rw-r--r--crates/ra_assists/src/assists/add_explicit_type.rs18
-rw-r--r--crates/ra_assists/src/assists/add_missing_impl_members.rs131
-rw-r--r--crates/ra_assists/src/doc_tests/generated.rs22
-rw-r--r--crates/ra_cargo_watch/Cargo.toml17
-rw-r--r--crates/ra_cargo_watch/src/conv.rs280
-rw-r--r--crates/ra_cargo_watch/src/conv/snapshots/test__snap_clippy_pass_by_ref.snap85
-rw-r--r--crates/ra_cargo_watch/src/conv/snapshots/test__snap_handles_macro_location.snap46
-rw-r--r--crates/ra_cargo_watch/src/conv/snapshots/test__snap_rustc_incompatible_type_for_trait.snap46
-rw-r--r--crates/ra_cargo_watch/src/conv/snapshots/test__snap_rustc_mismatched_type.snap46
-rw-r--r--crates/ra_cargo_watch/src/conv/snapshots/test__snap_rustc_unused_variable.snap70
-rw-r--r--crates/ra_cargo_watch/src/conv/snapshots/test__snap_rustc_wrong_number_of_parameters.snap65
-rw-r--r--crates/ra_cargo_watch/src/conv/test.rs700
-rw-r--r--crates/ra_cargo_watch/src/lib.rs392
-rw-r--r--crates/ra_hir/src/code_model.rs30
-rw-r--r--crates/ra_hir/src/lib.rs4
-rw-r--r--crates/ra_hir/src/source_binder.rs26
-rw-r--r--crates/ra_hir_def/src/adt.rs44
-rw-r--r--crates/ra_hir_def/src/body.rs6
-rw-r--r--crates/ra_hir_def/src/body/lower.rs25
-rw-r--r--crates/ra_hir_def/src/data.rs21
-rw-r--r--crates/ra_hir_def/src/item_scope.rs33
-rw-r--r--crates/ra_hir_def/src/lib.rs2
-rw-r--r--crates/ra_hir_def/src/nameres/collector.rs109
-rw-r--r--crates/ra_hir_def/src/nameres/path_resolution.rs72
-rw-r--r--crates/ra_hir_def/src/nameres/raw.rs35
-rw-r--r--crates/ra_hir_def/src/nameres/tests.rs6
-rw-r--r--crates/ra_hir_def/src/nameres/tests/globs.rs97
-rw-r--r--crates/ra_hir_def/src/nameres/tests/incremental.rs4
-rw-r--r--crates/ra_hir_def/src/path.rs8
-rw-r--r--crates/ra_hir_def/src/per_ns.rs50
-rw-r--r--crates/ra_hir_def/src/resolver.rs24
-rw-r--r--crates/ra_hir_def/src/visibility.rs120
-rw-r--r--crates/ra_hir_ty/src/display.rs12
-rw-r--r--crates/ra_hir_ty/src/infer.rs23
-rw-r--r--crates/ra_hir_ty/src/lib.rs31
-rw-r--r--crates/ra_hir_ty/src/tests.rs80
-rw-r--r--crates/ra_hir_ty/src/tests/regression.rs36
-rw-r--r--crates/ra_hir_ty/src/tests/simple.rs3
-rw-r--r--crates/ra_hir_ty/src/tests/traits.rs57
-rw-r--r--crates/ra_ide/src/completion/complete_dot.rs57
-rw-r--r--crates/ra_ide/src/inlay_hints.rs37
-rw-r--r--crates/ra_lsp_server/Cargo.toml3
-rw-r--r--crates/ra_lsp_server/src/caps.rs5
-rw-r--r--crates/ra_lsp_server/src/config.rs9
-rw-r--r--crates/ra_lsp_server/src/main_loop.rs44
-rw-r--r--crates/ra_lsp_server/src/main_loop/handlers.rs28
-rw-r--r--crates/ra_lsp_server/src/req.rs8
-rw-r--r--crates/ra_lsp_server/src/world.rs10
-rw-r--r--crates/ra_project_model/src/cargo_workspace.rs2
-rw-r--r--crates/ra_syntax/src/ast.rs4
-rw-r--r--crates/ra_syntax/src/ast/extensions.rs33
-rw-r--r--crates/ra_syntax/src/ast/generated.rs3
-rw-r--r--crates/ra_syntax/src/grammar.ron6
-rw-r--r--docs/user/assists.md22
-rw-r--r--editors/code/package.json52
-rw-r--r--editors/code/src/commands/cargo_watch.ts264
-rw-r--r--editors/code/src/commands/runnables.ts91
-rw-r--r--editors/code/src/commands/watch_status.ts42
-rw-r--r--editors/code/src/config.ts40
-rw-r--r--editors/code/src/extension.ts35
-rw-r--r--editors/code/src/server.ts5
-rw-r--r--editors/code/src/test/fixtures/rust-diagnostics/clippy/trivially_copy_pass_by_ref.json110
-rw-r--r--editors/code/src/test/fixtures/rust-diagnostics/error/E0053.json42
-rw-r--r--editors/code/src/test/fixtures/rust-diagnostics/error/E0061.json114
-rw-r--r--editors/code/src/test/fixtures/rust-diagnostics/error/E0277.json261
-rw-r--r--editors/code/src/test/fixtures/rust-diagnostics/error/E0308.json33
-rw-r--r--editors/code/src/test/fixtures/rust-diagnostics/warning/unused_variables.json72
-rw-r--r--editors/code/src/test/utils/diagnotics/SuggestedFix.test.ts134
-rw-r--r--editors/code/src/test/utils/diagnotics/SuggestedFixCollection.test.ts127
-rw-r--r--editors/code/src/test/utils/diagnotics/rust.test.ts236
-rw-r--r--editors/code/src/test/utils/diagnotics/vscode.test.ts98
-rw-r--r--editors/code/src/utils/diagnostics/SuggestedFix.ts67
-rw-r--r--editors/code/src/utils/diagnostics/SuggestedFixCollection.ts77
-rw-r--r--editors/code/src/utils/diagnostics/rust.ts299
-rw-r--r--editors/code/src/utils/diagnostics/vscode.ts14
-rw-r--r--editors/code/src/utils/processes.ts51
-rw-r--r--editors/code/src/utils/terminateProcess.sh12
-rw-r--r--xtask/src/main.rs2
-rw-r--r--xtask/tests/tidy-tests/cli.rs2
81 files changed, 3051 insertions, 2433 deletions
diff --git a/Cargo.lock b/Cargo.lock
index e724c1a76..b63b5ae13 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -10,7 +10,7 @@ dependencies = [
10 10
11[[package]] 11[[package]]
12name = "anyhow" 12name = "anyhow"
13version = "1.0.25" 13version = "1.0.26"
14source = "registry+https://github.com/rust-lang/crates.io-index" 14source = "registry+https://github.com/rust-lang/crates.io-index"
15 15
16[[package]] 16[[package]]
@@ -66,6 +66,11 @@ dependencies = [
66] 66]
67 67
68[[package]] 68[[package]]
69name = "base64"
70version = "0.11.0"
71source = "registry+https://github.com/rust-lang/crates.io-index"
72
73[[package]]
69name = "bit-set" 74name = "bit-set"
70version = "0.5.1" 75version = "0.5.1"
71source = "registry+https://github.com/rust-lang/crates.io-index" 76source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -232,7 +237,7 @@ dependencies = [
232 "crossbeam-channel 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 237 "crossbeam-channel 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
233 "crossbeam-deque 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", 238 "crossbeam-deque 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
234 "crossbeam-epoch 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", 239 "crossbeam-epoch 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
235 "crossbeam-queue 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 240 "crossbeam-queue 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
236 "crossbeam-utils 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", 241 "crossbeam-utils 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
237] 242]
238 243
@@ -268,9 +273,10 @@ dependencies = [
268 273
269[[package]] 274[[package]]
270name = "crossbeam-queue" 275name = "crossbeam-queue"
271version = "0.2.0" 276version = "0.2.1"
272source = "registry+https://github.com/rust-lang/crates.io-index" 277source = "registry+https://github.com/rust-lang/crates.io-index"
273dependencies = [ 278dependencies = [
279 "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
274 "crossbeam-utils 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", 280 "crossbeam-utils 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
275] 281]
276 282
@@ -620,9 +626,10 @@ dependencies = [
620 626
621[[package]] 627[[package]]
622name = "lsp-types" 628name = "lsp-types"
623version = "0.66.0" 629version = "0.67.1"
624source = "registry+https://github.com/rust-lang/crates.io-index" 630source = "registry+https://github.com/rust-lang/crates.io-index"
625dependencies = [ 631dependencies = [
632 "base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
626 "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 633 "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
627 "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", 634 "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)",
628 "serde_json 1.0.44 (registry+https://github.com/rust-lang/crates.io-index)", 635 "serde_json 1.0.44 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -892,6 +899,20 @@ dependencies = [
892] 899]
893 900
894[[package]] 901[[package]]
902name = "ra_cargo_watch"
903version = "0.1.0"
904dependencies = [
905 "cargo_metadata 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)",
906 "crossbeam-channel 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
907 "insta 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)",
908 "jod-thread 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
909 "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
910 "lsp-types 0.67.1 (registry+https://github.com/rust-lang/crates.io-index)",
911 "parking_lot 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
912 "serde_json 1.0.44 (registry+https://github.com/rust-lang/crates.io-index)",
913]
914
915[[package]]
895name = "ra_cfg" 916name = "ra_cfg"
896version = "0.1.0" 917version = "0.1.0"
897dependencies = [ 918dependencies = [
@@ -1034,7 +1055,7 @@ dependencies = [
1034 "ra_syntax 0.1.0", 1055 "ra_syntax 0.1.0",
1035 "ra_text_edit 0.1.0", 1056 "ra_text_edit 0.1.0",
1036 "rand 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", 1057 "rand 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
1037 "rayon 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 1058 "rayon 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
1038 "rustc-hash 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 1059 "rustc-hash 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
1039 "superslice 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 1060 "superslice 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
1040 "test_utils 0.1.0", 1061 "test_utils 0.1.0",
@@ -1050,8 +1071,9 @@ dependencies = [
1050 "jod-thread 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 1071 "jod-thread 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
1051 "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", 1072 "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
1052 "lsp-server 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", 1073 "lsp-server 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
1053 "lsp-types 0.66.0 (registry+https://github.com/rust-lang/crates.io-index)", 1074 "lsp-types 0.67.1 (registry+https://github.com/rust-lang/crates.io-index)",
1054 "parking_lot 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", 1075 "parking_lot 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
1076 "ra_cargo_watch 0.1.0",
1055 "ra_ide 0.1.0", 1077 "ra_ide 0.1.0",
1056 "ra_prof 0.1.0", 1078 "ra_prof 0.1.0",
1057 "ra_project_model 0.1.0", 1079 "ra_project_model 0.1.0",
@@ -1314,21 +1336,21 @@ dependencies = [
1314 1336
1315[[package]] 1337[[package]]
1316name = "rayon" 1338name = "rayon"
1317version = "1.2.1" 1339version = "1.3.0"
1318source = "registry+https://github.com/rust-lang/crates.io-index" 1340source = "registry+https://github.com/rust-lang/crates.io-index"
1319dependencies = [ 1341dependencies = [
1320 "crossbeam-deque 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", 1342 "crossbeam-deque 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
1321 "either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)", 1343 "either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
1322 "rayon-core 1.6.1 (registry+https://github.com/rust-lang/crates.io-index)", 1344 "rayon-core 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
1323] 1345]
1324 1346
1325[[package]] 1347[[package]]
1326name = "rayon-core" 1348name = "rayon-core"
1327version = "1.6.1" 1349version = "1.7.0"
1328source = "registry+https://github.com/rust-lang/crates.io-index" 1350source = "registry+https://github.com/rust-lang/crates.io-index"
1329dependencies = [ 1351dependencies = [
1330 "crossbeam-deque 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", 1352 "crossbeam-deque 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
1331 "crossbeam-queue 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 1353 "crossbeam-queue 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
1332 "crossbeam-utils 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", 1354 "crossbeam-utils 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
1333 "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 1355 "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
1334 "num_cpus 1.11.1 (registry+https://github.com/rust-lang/crates.io-index)", 1356 "num_cpus 1.11.1 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1746,7 +1768,7 @@ dependencies = [
1746name = "xtask" 1768name = "xtask"
1747version = "0.1.0" 1769version = "0.1.0"
1748dependencies = [ 1770dependencies = [
1749 "anyhow 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)", 1771 "anyhow 1.0.26 (registry+https://github.com/rust-lang/crates.io-index)",
1750 "pico-args 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", 1772 "pico-args 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
1751 "proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", 1773 "proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
1752 "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", 1774 "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1765,7 +1787,7 @@ dependencies = [
1765 1787
1766[metadata] 1788[metadata]
1767"checksum aho-corasick 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)" = "58fb5e95d83b38284460a5fda7d6470aa0b8844d283a0b614b8535e880800d2d" 1789"checksum aho-corasick 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)" = "58fb5e95d83b38284460a5fda7d6470aa0b8844d283a0b614b8535e880800d2d"
1768"checksum anyhow 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)" = "9267dff192e68f3399525901e709a48c1d3982c9c072fa32f2127a0cb0babf14" 1790"checksum anyhow 1.0.26 (registry+https://github.com/rust-lang/crates.io-index)" = "7825f6833612eb2414095684fcf6c635becf3ce97fe48cf6421321e93bfbd53c"
1769"checksum anymap 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)" = "33954243bd79057c2de7338850b85983a44588021f8a5fee574a8888c6de4344" 1791"checksum anymap 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)" = "33954243bd79057c2de7338850b85983a44588021f8a5fee574a8888c6de4344"
1770"checksum arrayvec 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8" 1792"checksum arrayvec 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8"
1771"checksum atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)" = "1803c647a3ec87095e7ae7acfca019e98de5ec9a7d01343f611cf3152ed71a90" 1793"checksum atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)" = "1803c647a3ec87095e7ae7acfca019e98de5ec9a7d01343f611cf3152ed71a90"
@@ -1773,6 +1795,7 @@ dependencies = [
1773"checksum backtrace 0.3.40 (registry+https://github.com/rust-lang/crates.io-index)" = "924c76597f0d9ca25d762c25a4d369d51267536465dc5064bdf0eb073ed477ea" 1795"checksum backtrace 0.3.40 (registry+https://github.com/rust-lang/crates.io-index)" = "924c76597f0d9ca25d762c25a4d369d51267536465dc5064bdf0eb073ed477ea"
1774"checksum backtrace-sys 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)" = "5d6575f128516de27e3ce99689419835fce9643a9b215a14d2b5b685be018491" 1796"checksum backtrace-sys 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)" = "5d6575f128516de27e3ce99689419835fce9643a9b215a14d2b5b685be018491"
1775"checksum base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0b25d992356d2eb0ed82172f5248873db5560c4721f564b13cb5193bda5e668e" 1797"checksum base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0b25d992356d2eb0ed82172f5248873db5560c4721f564b13cb5193bda5e668e"
1798"checksum base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7"
1776"checksum bit-set 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e84c238982c4b1e1ee668d136c510c67a13465279c0cb367ea6baf6310620a80" 1799"checksum bit-set 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e84c238982c4b1e1ee668d136c510c67a13465279c0cb367ea6baf6310620a80"
1777"checksum bit-vec 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f59bbe95d4e52a6398ec21238d31577f2b28a9d86807f06ca59d191d8440d0bb" 1800"checksum bit-vec 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f59bbe95d4e52a6398ec21238d31577f2b28a9d86807f06ca59d191d8440d0bb"
1778"checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" 1801"checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
@@ -1795,7 +1818,7 @@ dependencies = [
1795"checksum crossbeam-channel 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "acec9a3b0b3559f15aee4f90746c4e5e293b701c0f7d3925d24e01645267b68c" 1818"checksum crossbeam-channel 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "acec9a3b0b3559f15aee4f90746c4e5e293b701c0f7d3925d24e01645267b68c"
1796"checksum crossbeam-deque 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c3aa945d63861bfe624b55d153a39684da1e8c0bc8fba932f7ee3a3c16cea3ca" 1819"checksum crossbeam-deque 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c3aa945d63861bfe624b55d153a39684da1e8c0bc8fba932f7ee3a3c16cea3ca"
1797"checksum crossbeam-epoch 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5064ebdbf05ce3cb95e45c8b086f72263f4166b29b97f6baff7ef7fe047b55ac" 1820"checksum crossbeam-epoch 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5064ebdbf05ce3cb95e45c8b086f72263f4166b29b97f6baff7ef7fe047b55ac"
1798"checksum crossbeam-queue 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "dfd6515864a82d2f877b42813d4553292c6659498c9a2aa31bab5a15243c2700" 1821"checksum crossbeam-queue 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c695eeca1e7173472a32221542ae469b3e9aac3a4fc81f7696bcad82029493db"
1799"checksum crossbeam-utils 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ce446db02cdc3165b94ae73111e570793400d0794e46125cc4056c81cbb039f4" 1822"checksum crossbeam-utils 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ce446db02cdc3165b94ae73111e570793400d0794e46125cc4056c81cbb039f4"
1800"checksum difference 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198" 1823"checksum difference 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198"
1801"checksum drop_bomb 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "69b26e475fd29098530e709294e94e661974c851aed42512793f120fed4e199f" 1824"checksum drop_bomb 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "69b26e475fd29098530e709294e94e661974c851aed42512793f120fed4e199f"
@@ -1842,7 +1865,7 @@ dependencies = [
1842"checksum lock_api 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e57b3997725d2b60dbec1297f6c2e2957cc383db1cebd6be812163f969c7d586" 1865"checksum lock_api 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e57b3997725d2b60dbec1297f6c2e2957cc383db1cebd6be812163f969c7d586"
1843"checksum log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" 1866"checksum log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7"
1844"checksum lsp-server 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0ba36405bd742139ab79c246ca5adb7fde2fe1a0f495e2c8e2f607b607dedb12" 1867"checksum lsp-server 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0ba36405bd742139ab79c246ca5adb7fde2fe1a0f495e2c8e2f607b607dedb12"
1845"checksum lsp-types 0.66.0 (registry+https://github.com/rust-lang/crates.io-index)" = "49a2dddfe2791cbf4b5eff5a581e45becf47a24b128a62de80e7cc135bf50064" 1868"checksum lsp-types 0.67.1 (registry+https://github.com/rust-lang/crates.io-index)" = "aea9639ebf210bd5de66931cbdb2d4a8bcc1fa1e5b2dece7daa6b387ab42e803"
1846"checksum matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" 1869"checksum matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08"
1847"checksum memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "88579771288728879b57485cc7d6b07d648c9f0141eb955f8ab7f9d45394468e" 1870"checksum memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "88579771288728879b57485cc7d6b07d648c9f0141eb955f8ab7f9d45394468e"
1848"checksum memoffset 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "75189eb85871ea5c2e2c15abbdd541185f63b408415e5051f5cac122d8c774b9" 1871"checksum memoffset 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "75189eb85871ea5c2e2c15abbdd541185f63b408415e5051f5cac122d8c774b9"
@@ -1884,8 +1907,8 @@ dependencies = [
1884"checksum rand_pcg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44" 1907"checksum rand_pcg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44"
1885"checksum rand_pcg 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" 1908"checksum rand_pcg 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429"
1886"checksum rand_xorshift 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c" 1909"checksum rand_xorshift 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c"
1887"checksum rayon 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "43739f8831493b276363637423d3622d4bd6394ab6f0a9c4a552e208aeb7fddd" 1910"checksum rayon 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "db6ce3297f9c85e16621bb8cca38a06779ffc31bb8184e1be4bed2be4678a098"
1888"checksum rayon-core 1.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f8bf17de6f23b05473c437eb958b9c850bfc8af0961fe17b4cc92d5a627b4791" 1911"checksum rayon-core 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "08a89b46efaf957e52b18062fb2f4660f8b8a4dde1807ca002690868ef2c85a9"
1889"checksum rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" 1912"checksum rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2"
1890"checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" 1913"checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84"
1891"checksum regex 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dc220bd33bdce8f093101afe22a037b8eb0e5af33592e6a9caafff0d4cb81cbd" 1914"checksum regex 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dc220bd33bdce8f093101afe22a037b8eb0e5af33592e6a9caafff0d4cb81cbd"
diff --git a/crates/ra_assists/src/assist_ctx.rs b/crates/ra_assists/src/assist_ctx.rs
index 993aebc47..28152f724 100644
--- a/crates/ra_assists/src/assist_ctx.rs
+++ b/crates/ra_assists/src/assist_ctx.rs
@@ -46,7 +46,7 @@ pub(crate) enum Assist {
46/// 46///
47/// Note, however, that we don't actually use such two-phase logic at the 47/// Note, however, that we don't actually use such two-phase logic at the
48/// moment, because the LSP API is pretty awkward in this place, and it's much 48/// moment, because the LSP API is pretty awkward in this place, and it's much
49/// easier to just compute the edit eagerly :-)#[derive(Debug, Clone)] 49/// easier to just compute the edit eagerly :-)
50#[derive(Debug)] 50#[derive(Debug)]
51pub(crate) struct AssistCtx<'a, DB> { 51pub(crate) struct AssistCtx<'a, DB> {
52 pub(crate) db: &'a DB, 52 pub(crate) db: &'a DB,
diff --git a/crates/ra_assists/src/assists/add_explicit_type.rs b/crates/ra_assists/src/assists/add_explicit_type.rs
index eeb4ff39f..2c602a79e 100644
--- a/crates/ra_assists/src/assists/add_explicit_type.rs
+++ b/crates/ra_assists/src/assists/add_explicit_type.rs
@@ -74,6 +74,24 @@ mod tests {
74 } 74 }
75 75
76 #[test] 76 #[test]
77 fn add_explicit_type_works_for_macro_call() {
78 check_assist(
79 add_explicit_type,
80 "macro_rules! v { () => {0u64} } fn f() { let a<|> = v!(); }",
81 "macro_rules! v { () => {0u64} } fn f() { let a<|>: u64 = v!(); }",
82 );
83 }
84
85 #[test]
86 fn add_explicit_type_works_for_macro_call_recursive() {
87 check_assist(
88 add_explicit_type,
89 "macro_rules! u { () => {0u64} } macro_rules! v { () => {u!()} } fn f() { let a<|> = v!(); }",
90 "macro_rules! u { () => {0u64} } macro_rules! v { () => {u!()} } fn f() { let a<|>: u64 = v!(); }",
91 );
92 }
93
94 #[test]
77 fn add_explicit_type_not_applicable_if_ty_not_inferred() { 95 fn add_explicit_type_not_applicable_if_ty_not_inferred() {
78 check_assist_not_applicable(add_explicit_type, "fn f() { let a<|> = None; }"); 96 check_assist_not_applicable(add_explicit_type, "fn f() { let a<|> = None; }");
79 } 97 }
diff --git a/crates/ra_assists/src/assists/add_missing_impl_members.rs b/crates/ra_assists/src/assists/add_missing_impl_members.rs
index cef669cb5..bc49e71fe 100644
--- a/crates/ra_assists/src/assists/add_missing_impl_members.rs
+++ b/crates/ra_assists/src/assists/add_missing_impl_members.rs
@@ -1,3 +1,5 @@
1use std::collections::HashMap;
2
1use hir::{db::HirDatabase, HasSource}; 3use hir::{db::HirDatabase, HasSource};
2use ra_syntax::{ 4use ra_syntax::{
3 ast::{self, edit, make, AstNode, NameOwner}, 5 ast::{self, edit, make, AstNode, NameOwner},
@@ -17,26 +19,26 @@ enum AddMissingImplMembersMode {
17// Adds scaffold for required impl members. 19// Adds scaffold for required impl members.
18// 20//
19// ``` 21// ```
20// trait T { 22// trait Trait<T> {
21// Type X; 23// Type X;
22// fn foo(&self); 24// fn foo(&self) -> T;
23// fn bar(&self) {} 25// fn bar(&self) {}
24// } 26// }
25// 27//
26// impl T for () {<|> 28// impl Trait<u32> for () {<|>
27// 29//
28// } 30// }
29// ``` 31// ```
30// -> 32// ->
31// ``` 33// ```
32// trait T { 34// trait Trait<T> {
33// Type X; 35// Type X;
34// fn foo(&self); 36// fn foo(&self) -> T;
35// fn bar(&self) {} 37// fn bar(&self) {}
36// } 38// }
37// 39//
38// impl T for () { 40// impl Trait<u32> for () {
39// fn foo(&self) { unimplemented!() } 41// fn foo(&self) -> u32 { unimplemented!() }
40// 42//
41// } 43// }
42// ``` 44// ```
@@ -54,13 +56,13 @@ pub(crate) fn add_missing_impl_members(ctx: AssistCtx<impl HirDatabase>) -> Opti
54// Adds scaffold for overriding default impl members. 56// Adds scaffold for overriding default impl members.
55// 57//
56// ``` 58// ```
57// trait T { 59// trait Trait {
58// Type X; 60// Type X;
59// fn foo(&self); 61// fn foo(&self);
60// fn bar(&self) {} 62// fn bar(&self) {}
61// } 63// }
62// 64//
63// impl T for () { 65// impl Trait for () {
64// Type X = (); 66// Type X = ();
65// fn foo(&self) {}<|> 67// fn foo(&self) {}<|>
66// 68//
@@ -68,13 +70,13 @@ pub(crate) fn add_missing_impl_members(ctx: AssistCtx<impl HirDatabase>) -> Opti
68// ``` 70// ```
69// -> 71// ->
70// ``` 72// ```
71// trait T { 73// trait Trait {
72// Type X; 74// Type X;
73// fn foo(&self); 75// fn foo(&self);
74// fn bar(&self) {} 76// fn bar(&self) {}
75// } 77// }
76// 78//
77// impl T for () { 79// impl Trait for () {
78// Type X = (); 80// Type X = ();
79// fn foo(&self) {} 81// fn foo(&self) {}
80// fn bar(&self) {} 82// fn bar(&self) {}
@@ -99,7 +101,7 @@ fn add_missing_impl_members_inner(
99 let impl_node = ctx.find_node_at_offset::<ast::ImplBlock>()?; 101 let impl_node = ctx.find_node_at_offset::<ast::ImplBlock>()?;
100 let impl_item_list = impl_node.item_list()?; 102 let impl_item_list = impl_node.item_list()?;
101 103
102 let trait_def = { 104 let (trait_, trait_def) = {
103 let analyzer = ctx.source_analyzer(impl_node.syntax(), None); 105 let analyzer = ctx.source_analyzer(impl_node.syntax(), None);
104 106
105 resolve_target_trait_def(ctx.db, &analyzer, &impl_node)? 107 resolve_target_trait_def(ctx.db, &analyzer, &impl_node)?
@@ -132,10 +134,25 @@ fn add_missing_impl_members_inner(
132 return None; 134 return None;
133 } 135 }
134 136
137 let file_id = ctx.frange.file_id;
138 let db = ctx.db;
139
135 ctx.add_assist(AssistId(assist_id), label, |edit| { 140 ctx.add_assist(AssistId(assist_id), label, |edit| {
136 let n_existing_items = impl_item_list.impl_items().count(); 141 let n_existing_items = impl_item_list.impl_items().count();
142 let substs = get_syntactic_substs(impl_node).unwrap_or_default();
143 let generic_def: hir::GenericDef = trait_.into();
144 let substs_by_param: HashMap<_, _> = generic_def
145 .params(db)
146 .into_iter()
147 // this is a trait impl, so we need to skip the first type parameter -- this is a bit hacky
148 .skip(1)
149 .zip(substs.into_iter())
150 .collect();
137 let items = missing_items 151 let items = missing_items
138 .into_iter() 152 .into_iter()
153 .map(|it| {
154 substitute_type_params(db, hir::InFile::new(file_id.into(), it), &substs_by_param)
155 })
139 .map(|it| match it { 156 .map(|it| match it {
140 ast::ImplItem::FnDef(def) => ast::ImplItem::FnDef(add_body(def)), 157 ast::ImplItem::FnDef(def) => ast::ImplItem::FnDef(add_body(def)),
141 _ => it, 158 _ => it,
@@ -160,13 +177,63 @@ fn add_body(fn_def: ast::FnDef) -> ast::FnDef {
160 } 177 }
161} 178}
162 179
180// FIXME: It would probably be nicer if we could get this via HIR (i.e. get the
181// trait ref, and then go from the types in the substs back to the syntax)
182// FIXME: This should be a general utility (not even just for assists)
183fn get_syntactic_substs(impl_block: ast::ImplBlock) -> Option<Vec<ast::TypeRef>> {
184 let target_trait = impl_block.target_trait()?;
185 let path_type = match target_trait {
186 ast::TypeRef::PathType(path) => path,
187 _ => return None,
188 };
189 let type_arg_list = path_type.path()?.segment()?.type_arg_list()?;
190 let mut result = Vec::new();
191 for type_arg in type_arg_list.type_args() {
192 let type_arg: ast::TypeArg = type_arg;
193 result.push(type_arg.type_ref()?);
194 }
195 Some(result)
196}
197
198// FIXME: This should be a general utility (not even just for assists)
199fn substitute_type_params<N: AstNode>(
200 db: &impl HirDatabase,
201 node: hir::InFile<N>,
202 substs: &HashMap<hir::TypeParam, ast::TypeRef>,
203) -> N {
204 let type_param_replacements = node
205 .value
206 .syntax()
207 .descendants()
208 .filter_map(ast::TypeRef::cast)
209 .filter_map(|n| {
210 let path = match &n {
211 ast::TypeRef::PathType(path_type) => path_type.path()?,
212 _ => return None,
213 };
214 let analyzer = hir::SourceAnalyzer::new(db, node.with_value(n.syntax()), None);
215 let resolution = analyzer.resolve_path(db, &path)?;
216 match resolution {
217 hir::PathResolution::TypeParam(tp) => Some((n, substs.get(&tp)?.clone())),
218 _ => None,
219 }
220 })
221 .collect::<Vec<_>>();
222
223 if type_param_replacements.is_empty() {
224 node.value
225 } else {
226 edit::replace_descendants(&node.value, type_param_replacements.into_iter())
227 }
228}
229
163/// Given an `ast::ImplBlock`, resolves the target trait (the one being 230/// Given an `ast::ImplBlock`, resolves the target trait (the one being
164/// implemented) to a `ast::TraitDef`. 231/// implemented) to a `ast::TraitDef`.
165fn resolve_target_trait_def( 232fn resolve_target_trait_def(
166 db: &impl HirDatabase, 233 db: &impl HirDatabase,
167 analyzer: &hir::SourceAnalyzer, 234 analyzer: &hir::SourceAnalyzer,
168 impl_block: &ast::ImplBlock, 235 impl_block: &ast::ImplBlock,
169) -> Option<ast::TraitDef> { 236) -> Option<(hir::Trait, ast::TraitDef)> {
170 let ast_path = impl_block 237 let ast_path = impl_block
171 .target_trait() 238 .target_trait()
172 .map(|it| it.syntax().clone()) 239 .map(|it| it.syntax().clone())
@@ -174,7 +241,9 @@ fn resolve_target_trait_def(
174 .path()?; 241 .path()?;
175 242
176 match analyzer.resolve_path(db, &ast_path) { 243 match analyzer.resolve_path(db, &ast_path) {
177 Some(hir::PathResolution::Def(hir::ModuleDef::Trait(def))) => Some(def.source(db).value), 244 Some(hir::PathResolution::Def(hir::ModuleDef::Trait(def))) => {
245 Some((def, def.source(db).value))
246 }
178 _ => None, 247 _ => None,
179 } 248 }
180} 249}
@@ -281,6 +350,40 @@ impl Foo for S {
281 } 350 }
282 351
283 #[test] 352 #[test]
353 fn fill_in_type_params_1() {
354 check_assist(
355 add_missing_impl_members,
356 "
357trait Foo<T> { fn foo(&self, t: T) -> &T; }
358struct S;
359impl Foo<u32> for S { <|> }",
360 "
361trait Foo<T> { fn foo(&self, t: T) -> &T; }
362struct S;
363impl Foo<u32> for S {
364 <|>fn foo(&self, t: u32) -> &u32 { unimplemented!() }
365}",
366 );
367 }
368
369 #[test]
370 fn fill_in_type_params_2() {
371 check_assist(
372 add_missing_impl_members,
373 "
374trait Foo<T> { fn foo(&self, t: T) -> &T; }
375struct S;
376impl<U> Foo<U> for S { <|> }",
377 "
378trait Foo<T> { fn foo(&self, t: T) -> &T; }
379struct S;
380impl<U> Foo<U> for S {
381 <|>fn foo(&self, t: U) -> &U { unimplemented!() }
382}",
383 );
384 }
385
386 #[test]
284 fn test_cursor_after_empty_impl_block() { 387 fn test_cursor_after_empty_impl_block() {
285 check_assist( 388 check_assist(
286 add_missing_impl_members, 389 add_missing_impl_members,
diff --git a/crates/ra_assists/src/doc_tests/generated.rs b/crates/ra_assists/src/doc_tests/generated.rs
index 4586eeb59..7d84dc8fb 100644
--- a/crates/ra_assists/src/doc_tests/generated.rs
+++ b/crates/ra_assists/src/doc_tests/generated.rs
@@ -101,26 +101,26 @@ fn doctest_add_impl_default_members() {
101 check( 101 check(
102 "add_impl_default_members", 102 "add_impl_default_members",
103 r#####" 103 r#####"
104trait T { 104trait Trait {
105 Type X; 105 Type X;
106 fn foo(&self); 106 fn foo(&self);
107 fn bar(&self) {} 107 fn bar(&self) {}
108} 108}
109 109
110impl T for () { 110impl Trait for () {
111 Type X = (); 111 Type X = ();
112 fn foo(&self) {}<|> 112 fn foo(&self) {}<|>
113 113
114} 114}
115"#####, 115"#####,
116 r#####" 116 r#####"
117trait T { 117trait Trait {
118 Type X; 118 Type X;
119 fn foo(&self); 119 fn foo(&self);
120 fn bar(&self) {} 120 fn bar(&self) {}
121} 121}
122 122
123impl T for () { 123impl Trait for () {
124 Type X = (); 124 Type X = ();
125 fn foo(&self) {} 125 fn foo(&self) {}
126 fn bar(&self) {} 126 fn bar(&self) {}
@@ -135,25 +135,25 @@ fn doctest_add_impl_missing_members() {
135 check( 135 check(
136 "add_impl_missing_members", 136 "add_impl_missing_members",
137 r#####" 137 r#####"
138trait T { 138trait Trait<T> {
139 Type X; 139 Type X;
140 fn foo(&self); 140 fn foo(&self) -> T;
141 fn bar(&self) {} 141 fn bar(&self) {}
142} 142}
143 143
144impl T for () {<|> 144impl Trait<u32> for () {<|>
145 145
146} 146}
147"#####, 147"#####,
148 r#####" 148 r#####"
149trait T { 149trait Trait<T> {
150 Type X; 150 Type X;
151 fn foo(&self); 151 fn foo(&self) -> T;
152 fn bar(&self) {} 152 fn bar(&self) {}
153} 153}
154 154
155impl T for () { 155impl Trait<u32> for () {
156 fn foo(&self) { unimplemented!() } 156 fn foo(&self) -> u32 { unimplemented!() }
157 157
158} 158}
159"#####, 159"#####,
diff --git a/crates/ra_cargo_watch/Cargo.toml b/crates/ra_cargo_watch/Cargo.toml
new file mode 100644
index 000000000..bcc4648ff
--- /dev/null
+++ b/crates/ra_cargo_watch/Cargo.toml
@@ -0,0 +1,17 @@
1[package]
2edition = "2018"
3name = "ra_cargo_watch"
4version = "0.1.0"
5authors = ["rust-analyzer developers"]
6
7[dependencies]
8crossbeam-channel = "0.4"
9lsp-types = { version = "0.67.0", features = ["proposed"] }
10log = "0.4.3"
11cargo_metadata = "0.9.1"
12jod-thread = "0.1.0"
13parking_lot = "0.10.0"
14
15[dev-dependencies]
16insta = "0.12.0"
17serde_json = "1.0" \ No newline at end of file
diff --git a/crates/ra_cargo_watch/src/conv.rs b/crates/ra_cargo_watch/src/conv.rs
new file mode 100644
index 000000000..3bd4bf7a5
--- /dev/null
+++ b/crates/ra_cargo_watch/src/conv.rs
@@ -0,0 +1,280 @@
1//! This module provides the functionality needed to convert diagnostics from
2//! `cargo check` json format to the LSP diagnostic format.
3use cargo_metadata::diagnostic::{
4 Applicability, Diagnostic as RustDiagnostic, DiagnosticLevel, DiagnosticSpan,
5 DiagnosticSpanMacroExpansion,
6};
7use lsp_types::{
8 Diagnostic, DiagnosticRelatedInformation, DiagnosticSeverity, DiagnosticTag, Location,
9 NumberOrString, Position, Range, Url,
10};
11use std::{fmt::Write, path::PathBuf};
12
13#[cfg(test)]
14mod test;
15
16/// Converts a Rust level string to a LSP severity
17fn map_level_to_severity(val: DiagnosticLevel) -> Option<DiagnosticSeverity> {
18 match val {
19 DiagnosticLevel::Ice => Some(DiagnosticSeverity::Error),
20 DiagnosticLevel::Error => Some(DiagnosticSeverity::Error),
21 DiagnosticLevel::Warning => Some(DiagnosticSeverity::Warning),
22 DiagnosticLevel::Note => Some(DiagnosticSeverity::Information),
23 DiagnosticLevel::Help => Some(DiagnosticSeverity::Hint),
24 DiagnosticLevel::Unknown => None,
25 }
26}
27
28/// Check whether a file name is from macro invocation
29fn is_from_macro(file_name: &str) -> bool {
30 file_name.starts_with('<') && file_name.ends_with('>')
31}
32
33/// Converts a Rust macro span to a LSP location recursively
34fn map_macro_span_to_location(
35 span_macro: &DiagnosticSpanMacroExpansion,
36 workspace_root: &PathBuf,
37) -> Option<Location> {
38 if !is_from_macro(&span_macro.span.file_name) {
39 return Some(map_span_to_location(&span_macro.span, workspace_root));
40 }
41
42 if let Some(expansion) = &span_macro.span.expansion {
43 return map_macro_span_to_location(&expansion, workspace_root);
44 }
45
46 None
47}
48
49/// Converts a Rust span to a LSP location
50fn map_span_to_location(span: &DiagnosticSpan, workspace_root: &PathBuf) -> Location {
51 if is_from_macro(&span.file_name) && span.expansion.is_some() {
52 let expansion = span.expansion.as_ref().unwrap();
53 if let Some(macro_range) = map_macro_span_to_location(&expansion, workspace_root) {
54 return macro_range;
55 }
56 }
57
58 let mut file_name = workspace_root.clone();
59 file_name.push(&span.file_name);
60 let uri = Url::from_file_path(file_name).unwrap();
61
62 let range = Range::new(
63 Position::new(span.line_start as u64 - 1, span.column_start as u64 - 1),
64 Position::new(span.line_end as u64 - 1, span.column_end as u64 - 1),
65 );
66
67 Location { uri, range }
68}
69
70/// Converts a secondary Rust span to a LSP related information
71///
72/// If the span is unlabelled this will return `None`.
73fn map_secondary_span_to_related(
74 span: &DiagnosticSpan,
75 workspace_root: &PathBuf,
76) -> Option<DiagnosticRelatedInformation> {
77 if let Some(label) = &span.label {
78 let location = map_span_to_location(span, workspace_root);
79 Some(DiagnosticRelatedInformation { location, message: label.clone() })
80 } else {
81 // Nothing to label this with
82 None
83 }
84}
85
86/// Determines if diagnostic is related to unused code
87fn is_unused_or_unnecessary(rd: &RustDiagnostic) -> bool {
88 if let Some(code) = &rd.code {
89 match code.code.as_str() {
90 "dead_code" | "unknown_lints" | "unreachable_code" | "unused_attributes"
91 | "unused_imports" | "unused_macros" | "unused_variables" => true,
92 _ => false,
93 }
94 } else {
95 false
96 }
97}
98
99/// Determines if diagnostic is related to deprecated code
100fn is_deprecated(rd: &RustDiagnostic) -> bool {
101 if let Some(code) = &rd.code {
102 match code.code.as_str() {
103 "deprecated" => true,
104 _ => false,
105 }
106 } else {
107 false
108 }
109}
110
111#[derive(Debug)]
112pub struct SuggestedFix {
113 pub title: String,
114 pub location: Location,
115 pub replacement: String,
116 pub applicability: Applicability,
117 pub diagnostics: Vec<Diagnostic>,
118}
119
120impl std::cmp::PartialEq<SuggestedFix> for SuggestedFix {
121 fn eq(&self, other: &SuggestedFix) -> bool {
122 if self.title == other.title
123 && self.location == other.location
124 && self.replacement == other.replacement
125 {
126 // Applicability doesn't impl PartialEq...
127 match (&self.applicability, &other.applicability) {
128 (Applicability::MachineApplicable, Applicability::MachineApplicable) => true,
129 (Applicability::HasPlaceholders, Applicability::HasPlaceholders) => true,
130 (Applicability::MaybeIncorrect, Applicability::MaybeIncorrect) => true,
131 (Applicability::Unspecified, Applicability::Unspecified) => true,
132 _ => false,
133 }
134 } else {
135 false
136 }
137 }
138}
139
140enum MappedRustChildDiagnostic {
141 Related(DiagnosticRelatedInformation),
142 SuggestedFix(SuggestedFix),
143 MessageLine(String),
144}
145
146fn map_rust_child_diagnostic(
147 rd: &RustDiagnostic,
148 workspace_root: &PathBuf,
149) -> MappedRustChildDiagnostic {
150 let span: &DiagnosticSpan = match rd.spans.iter().find(|s| s.is_primary) {
151 Some(span) => span,
152 None => {
153 // `rustc` uses these spanless children as a way to print multi-line
154 // messages
155 return MappedRustChildDiagnostic::MessageLine(rd.message.clone());
156 }
157 };
158
159 // If we have a primary span use its location, otherwise use the parent
160 let location = map_span_to_location(&span, workspace_root);
161
162 if let Some(suggested_replacement) = &span.suggested_replacement {
163 // Include our replacement in the title unless it's empty
164 let title = if !suggested_replacement.is_empty() {
165 format!("{}: '{}'", rd.message, suggested_replacement)
166 } else {
167 rd.message.clone()
168 };
169
170 MappedRustChildDiagnostic::SuggestedFix(SuggestedFix {
171 title,
172 location,
173 replacement: suggested_replacement.clone(),
174 applicability: span.suggestion_applicability.clone().unwrap_or(Applicability::Unknown),
175 diagnostics: vec![],
176 })
177 } else {
178 MappedRustChildDiagnostic::Related(DiagnosticRelatedInformation {
179 location,
180 message: rd.message.clone(),
181 })
182 }
183}
184
185#[derive(Debug)]
186pub(crate) struct MappedRustDiagnostic {
187 pub location: Location,
188 pub diagnostic: Diagnostic,
189 pub suggested_fixes: Vec<SuggestedFix>,
190}
191
192/// Converts a Rust root diagnostic to LSP form
193///
194/// This flattens the Rust diagnostic by:
195///
196/// 1. Creating a LSP diagnostic with the root message and primary span.
197/// 2. Adding any labelled secondary spans to `relatedInformation`
198/// 3. Categorising child diagnostics as either `SuggestedFix`es,
199/// `relatedInformation` or additional message lines.
200///
201/// If the diagnostic has no primary span this will return `None`
202pub(crate) fn map_rust_diagnostic_to_lsp(
203 rd: &RustDiagnostic,
204 workspace_root: &PathBuf,
205) -> Option<MappedRustDiagnostic> {
206 let primary_span = rd.spans.iter().find(|s| s.is_primary)?;
207
208 let location = map_span_to_location(&primary_span, workspace_root);
209
210 let severity = map_level_to_severity(rd.level);
211 let mut primary_span_label = primary_span.label.as_ref();
212
213 let mut source = String::from("rustc");
214 let mut code = rd.code.as_ref().map(|c| c.code.clone());
215 if let Some(code_val) = &code {
216 // See if this is an RFC #2103 scoped lint (e.g. from Clippy)
217 let scoped_code: Vec<&str> = code_val.split("::").collect();
218 if scoped_code.len() == 2 {
219 source = String::from(scoped_code[0]);
220 code = Some(String::from(scoped_code[1]));
221 }
222 }
223
224 let mut related_information = vec![];
225 let mut tags = vec![];
226
227 for secondary_span in rd.spans.iter().filter(|s| !s.is_primary) {
228 let related = map_secondary_span_to_related(secondary_span, workspace_root);
229 if let Some(related) = related {
230 related_information.push(related);
231 }
232 }
233
234 let mut suggested_fixes = vec![];
235 let mut message = rd.message.clone();
236 for child in &rd.children {
237 let child = map_rust_child_diagnostic(&child, workspace_root);
238 match child {
239 MappedRustChildDiagnostic::Related(related) => related_information.push(related),
240 MappedRustChildDiagnostic::SuggestedFix(suggested_fix) => {
241 suggested_fixes.push(suggested_fix)
242 }
243 MappedRustChildDiagnostic::MessageLine(message_line) => {
244 write!(&mut message, "\n{}", message_line).unwrap();
245
246 // These secondary messages usually duplicate the content of the
247 // primary span label.
248 primary_span_label = None;
249 }
250 }
251 }
252
253 if let Some(primary_span_label) = primary_span_label {
254 write!(&mut message, "\n{}", primary_span_label).unwrap();
255 }
256
257 if is_unused_or_unnecessary(rd) {
258 tags.push(DiagnosticTag::Unnecessary);
259 }
260
261 if is_deprecated(rd) {
262 tags.push(DiagnosticTag::Deprecated);
263 }
264
265 let diagnostic = Diagnostic {
266 range: location.range,
267 severity,
268 code: code.map(NumberOrString::String),
269 source: Some(source),
270 message,
271 related_information: if !related_information.is_empty() {
272 Some(related_information)
273 } else {
274 None
275 },
276 tags: if !tags.is_empty() { Some(tags) } else { None },
277 };
278
279 Some(MappedRustDiagnostic { location, diagnostic, suggested_fixes })
280}
diff --git a/crates/ra_cargo_watch/src/conv/snapshots/test__snap_clippy_pass_by_ref.snap b/crates/ra_cargo_watch/src/conv/snapshots/test__snap_clippy_pass_by_ref.snap
new file mode 100644
index 000000000..cb0920914
--- /dev/null
+++ b/crates/ra_cargo_watch/src/conv/snapshots/test__snap_clippy_pass_by_ref.snap
@@ -0,0 +1,85 @@
1---
2source: crates/ra_cargo_watch/src/conv/test.rs
3expression: diag
4---
5MappedRustDiagnostic {
6 location: Location {
7 uri: "file:///test/compiler/mir/tagset.rs",
8 range: Range {
9 start: Position {
10 line: 41,
11 character: 23,
12 },
13 end: Position {
14 line: 41,
15 character: 28,
16 },
17 },
18 },
19 diagnostic: Diagnostic {
20 range: Range {
21 start: Position {
22 line: 41,
23 character: 23,
24 },
25 end: Position {
26 line: 41,
27 character: 28,
28 },
29 },
30 severity: Some(
31 Warning,
32 ),
33 code: Some(
34 String(
35 "trivially_copy_pass_by_ref",
36 ),
37 ),
38 source: Some(
39 "clippy",
40 ),
41 message: "this argument is passed by reference, but would be more efficient if passed by value\n#[warn(clippy::trivially_copy_pass_by_ref)] implied by #[warn(clippy::all)]\nfor further information visit https://rust-lang.github.io/rust-clippy/master/index.html#trivially_copy_pass_by_ref",
42 related_information: Some(
43 [
44 DiagnosticRelatedInformation {
45 location: Location {
46 uri: "file:///test/compiler/lib.rs",
47 range: Range {
48 start: Position {
49 line: 0,
50 character: 8,
51 },
52 end: Position {
53 line: 0,
54 character: 19,
55 },
56 },
57 },
58 message: "lint level defined here",
59 },
60 ],
61 ),
62 tags: None,
63 },
64 suggested_fixes: [
65 SuggestedFix {
66 title: "consider passing by value instead: \'self\'",
67 location: Location {
68 uri: "file:///test/compiler/mir/tagset.rs",
69 range: Range {
70 start: Position {
71 line: 41,
72 character: 23,
73 },
74 end: Position {
75 line: 41,
76 character: 28,
77 },
78 },
79 },
80 replacement: "self",
81 applicability: Unspecified,
82 diagnostics: [],
83 },
84 ],
85}
diff --git a/crates/ra_cargo_watch/src/conv/snapshots/test__snap_handles_macro_location.snap b/crates/ra_cargo_watch/src/conv/snapshots/test__snap_handles_macro_location.snap
new file mode 100644
index 000000000..19510ecc1
--- /dev/null
+++ b/crates/ra_cargo_watch/src/conv/snapshots/test__snap_handles_macro_location.snap
@@ -0,0 +1,46 @@
1---
2source: crates/ra_cargo_watch/src/conv/test.rs
3expression: diag
4---
5MappedRustDiagnostic {
6 location: Location {
7 uri: "file:///test/src/main.rs",
8 range: Range {
9 start: Position {
10 line: 1,
11 character: 4,
12 },
13 end: Position {
14 line: 1,
15 character: 26,
16 },
17 },
18 },
19 diagnostic: Diagnostic {
20 range: Range {
21 start: Position {
22 line: 1,
23 character: 4,
24 },
25 end: Position {
26 line: 1,
27 character: 26,
28 },
29 },
30 severity: Some(
31 Error,
32 ),
33 code: Some(
34 String(
35 "E0277",
36 ),
37 ),
38 source: Some(
39 "rustc",
40 ),
41 message: "can\'t compare `{integer}` with `&str`\nthe trait `std::cmp::PartialEq<&str>` is not implemented for `{integer}`",
42 related_information: None,
43 tags: None,
44 },
45 suggested_fixes: [],
46}
diff --git a/crates/ra_cargo_watch/src/conv/snapshots/test__snap_rustc_incompatible_type_for_trait.snap b/crates/ra_cargo_watch/src/conv/snapshots/test__snap_rustc_incompatible_type_for_trait.snap
new file mode 100644
index 000000000..cf683e4b6
--- /dev/null
+++ b/crates/ra_cargo_watch/src/conv/snapshots/test__snap_rustc_incompatible_type_for_trait.snap
@@ -0,0 +1,46 @@
1---
2source: crates/ra_cargo_watch/src/conv/test.rs
3expression: diag
4---
5MappedRustDiagnostic {
6 location: Location {
7 uri: "file:///test/compiler/ty/list_iter.rs",
8 range: Range {
9 start: Position {
10 line: 51,
11 character: 4,
12 },
13 end: Position {
14 line: 51,
15 character: 47,
16 },
17 },
18 },
19 diagnostic: Diagnostic {
20 range: Range {
21 start: Position {
22 line: 51,
23 character: 4,
24 },
25 end: Position {
26 line: 51,
27 character: 47,
28 },
29 },
30 severity: Some(
31 Error,
32 ),
33 code: Some(
34 String(
35 "E0053",
36 ),
37 ),
38 source: Some(
39 "rustc",
40 ),
41 message: "method `next` has an incompatible type for trait\nexpected type `fn(&mut ty::list_iter::ListIterator<\'list, M>) -> std::option::Option<&ty::Ref<M>>`\n found type `fn(&ty::list_iter::ListIterator<\'list, M>) -> std::option::Option<&\'list ty::Ref<M>>`",
42 related_information: None,
43 tags: None,
44 },
45 suggested_fixes: [],
46}
diff --git a/crates/ra_cargo_watch/src/conv/snapshots/test__snap_rustc_mismatched_type.snap b/crates/ra_cargo_watch/src/conv/snapshots/test__snap_rustc_mismatched_type.snap
new file mode 100644
index 000000000..8c1483c74
--- /dev/null
+++ b/crates/ra_cargo_watch/src/conv/snapshots/test__snap_rustc_mismatched_type.snap
@@ -0,0 +1,46 @@
1---
2source: crates/ra_cargo_watch/src/conv/test.rs
3expression: diag
4---
5MappedRustDiagnostic {
6 location: Location {
7 uri: "file:///test/runtime/compiler_support.rs",
8 range: Range {
9 start: Position {
10 line: 47,
11 character: 64,
12 },
13 end: Position {
14 line: 47,
15 character: 69,
16 },
17 },
18 },
19 diagnostic: Diagnostic {
20 range: Range {
21 start: Position {
22 line: 47,
23 character: 64,
24 },
25 end: Position {
26 line: 47,
27 character: 69,
28 },
29 },
30 severity: Some(
31 Error,
32 ),
33 code: Some(
34 String(
35 "E0308",
36 ),
37 ),
38 source: Some(
39 "rustc",
40 ),
41 message: "mismatched types\nexpected usize, found u32",
42 related_information: None,
43 tags: None,
44 },
45 suggested_fixes: [],
46}
diff --git a/crates/ra_cargo_watch/src/conv/snapshots/test__snap_rustc_unused_variable.snap b/crates/ra_cargo_watch/src/conv/snapshots/test__snap_rustc_unused_variable.snap
new file mode 100644
index 000000000..eb5a2247b
--- /dev/null
+++ b/crates/ra_cargo_watch/src/conv/snapshots/test__snap_rustc_unused_variable.snap
@@ -0,0 +1,70 @@
1---
2source: crates/ra_cargo_watch/src/conv/test.rs
3expression: diag
4---
5MappedRustDiagnostic {
6 location: Location {
7 uri: "file:///test/driver/subcommand/repl.rs",
8 range: Range {
9 start: Position {
10 line: 290,
11 character: 8,
12 },
13 end: Position {
14 line: 290,
15 character: 11,
16 },
17 },
18 },
19 diagnostic: Diagnostic {
20 range: Range {
21 start: Position {
22 line: 290,
23 character: 8,
24 },
25 end: Position {
26 line: 290,
27 character: 11,
28 },
29 },
30 severity: Some(
31 Warning,
32 ),
33 code: Some(
34 String(
35 "unused_variables",
36 ),
37 ),
38 source: Some(
39 "rustc",
40 ),
41 message: "unused variable: `foo`\n#[warn(unused_variables)] on by default",
42 related_information: None,
43 tags: Some(
44 [
45 Unnecessary,
46 ],
47 ),
48 },
49 suggested_fixes: [
50 SuggestedFix {
51 title: "consider prefixing with an underscore: \'_foo\'",
52 location: Location {
53 uri: "file:///test/driver/subcommand/repl.rs",
54 range: Range {
55 start: Position {
56 line: 290,
57 character: 8,
58 },
59 end: Position {
60 line: 290,
61 character: 11,
62 },
63 },
64 },
65 replacement: "_foo",
66 applicability: MachineApplicable,
67 diagnostics: [],
68 },
69 ],
70}
diff --git a/crates/ra_cargo_watch/src/conv/snapshots/test__snap_rustc_wrong_number_of_parameters.snap b/crates/ra_cargo_watch/src/conv/snapshots/test__snap_rustc_wrong_number_of_parameters.snap
new file mode 100644
index 000000000..2f4518931
--- /dev/null
+++ b/crates/ra_cargo_watch/src/conv/snapshots/test__snap_rustc_wrong_number_of_parameters.snap
@@ -0,0 +1,65 @@
1---
2source: crates/ra_cargo_watch/src/conv/test.rs
3expression: diag
4---
5MappedRustDiagnostic {
6 location: Location {
7 uri: "file:///test/compiler/ty/select.rs",
8 range: Range {
9 start: Position {
10 line: 103,
11 character: 17,
12 },
13 end: Position {
14 line: 103,
15 character: 29,
16 },
17 },
18 },
19 diagnostic: Diagnostic {
20 range: Range {
21 start: Position {
22 line: 103,
23 character: 17,
24 },
25 end: Position {
26 line: 103,
27 character: 29,
28 },
29 },
30 severity: Some(
31 Error,
32 ),
33 code: Some(
34 String(
35 "E0061",
36 ),
37 ),
38 source: Some(
39 "rustc",
40 ),
41 message: "this function takes 2 parameters but 3 parameters were supplied\nexpected 2 parameters",
42 related_information: Some(
43 [
44 DiagnosticRelatedInformation {
45 location: Location {
46 uri: "file:///test/compiler/ty/select.rs",
47 range: Range {
48 start: Position {
49 line: 218,
50 character: 4,
51 },
52 end: Position {
53 line: 230,
54 character: 5,
55 },
56 },
57 },
58 message: "defined here",
59 },
60 ],
61 ),
62 tags: None,
63 },
64 suggested_fixes: [],
65}
diff --git a/crates/ra_cargo_watch/src/conv/test.rs b/crates/ra_cargo_watch/src/conv/test.rs
new file mode 100644
index 000000000..6817245c2
--- /dev/null
+++ b/crates/ra_cargo_watch/src/conv/test.rs
@@ -0,0 +1,700 @@
1//! This module contains the large and verbose snapshot tests for the
2//! conversions between `cargo check` json and LSP diagnostics.
3use crate::*;
4
5fn parse_diagnostic(val: &str) -> cargo_metadata::diagnostic::Diagnostic {
6 serde_json::from_str::<cargo_metadata::diagnostic::Diagnostic>(val).unwrap()
7}
8
9#[test]
10fn snap_rustc_incompatible_type_for_trait() {
11 let diag = parse_diagnostic(
12 r##"{
13 "message": "method `next` has an incompatible type for trait",
14 "code": {
15 "code": "E0053",
16 "explanation": "\nThe parameters of any trait method must match between a trait implementation\nand the trait definition.\n\nHere are a couple examples of this error:\n\n```compile_fail,E0053\ntrait Foo {\n fn foo(x: u16);\n fn bar(&self);\n}\n\nstruct Bar;\n\nimpl Foo for Bar {\n // error, expected u16, found i16\n fn foo(x: i16) { }\n\n // error, types differ in mutability\n fn bar(&mut self) { }\n}\n```\n"
17 },
18 "level": "error",
19 "spans": [
20 {
21 "file_name": "compiler/ty/list_iter.rs",
22 "byte_start": 1307,
23 "byte_end": 1350,
24 "line_start": 52,
25 "line_end": 52,
26 "column_start": 5,
27 "column_end": 48,
28 "is_primary": true,
29 "text": [
30 {
31 "text": " fn next(&self) -> Option<&'list ty::Ref<M>> {",
32 "highlight_start": 5,
33 "highlight_end": 48
34 }
35 ],
36 "label": "types differ in mutability",
37 "suggested_replacement": null,
38 "suggestion_applicability": null,
39 "expansion": null
40 }
41 ],
42 "children": [
43 {
44 "message": "expected type `fn(&mut ty::list_iter::ListIterator<'list, M>) -> std::option::Option<&ty::Ref<M>>`\n found type `fn(&ty::list_iter::ListIterator<'list, M>) -> std::option::Option<&'list ty::Ref<M>>`",
45 "code": null,
46 "level": "note",
47 "spans": [],
48 "children": [],
49 "rendered": null
50 }
51 ],
52 "rendered": "error[E0053]: method `next` has an incompatible type for trait\n --> compiler/ty/list_iter.rs:52:5\n |\n52 | fn next(&self) -> Option<&'list ty::Ref<M>> {\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ types differ in mutability\n |\n = note: expected type `fn(&mut ty::list_iter::ListIterator<'list, M>) -> std::option::Option<&ty::Ref<M>>`\n found type `fn(&ty::list_iter::ListIterator<'list, M>) -> std::option::Option<&'list ty::Ref<M>>`\n\n"
53 }
54 "##,
55 );
56
57 let workspace_root = PathBuf::from("/test/");
58 let diag = map_rust_diagnostic_to_lsp(&diag, &workspace_root).expect("couldn't map diagnostic");
59 insta::assert_debug_snapshot!(diag);
60}
61
62#[test]
63fn snap_rustc_unused_variable() {
64 let diag = parse_diagnostic(
65 r##"{
66"message": "unused variable: `foo`",
67"code": {
68 "code": "unused_variables",
69 "explanation": null
70},
71"level": "warning",
72"spans": [
73 {
74 "file_name": "driver/subcommand/repl.rs",
75 "byte_start": 9228,
76 "byte_end": 9231,
77 "line_start": 291,
78 "line_end": 291,
79 "column_start": 9,
80 "column_end": 12,
81 "is_primary": true,
82 "text": [
83 {
84 "text": " let foo = 42;",
85 "highlight_start": 9,
86 "highlight_end": 12
87 }
88 ],
89 "label": null,
90 "suggested_replacement": null,
91 "suggestion_applicability": null,
92 "expansion": null
93 }
94],
95"children": [
96 {
97 "message": "#[warn(unused_variables)] on by default",
98 "code": null,
99 "level": "note",
100 "spans": [],
101 "children": [],
102 "rendered": null
103 },
104 {
105 "message": "consider prefixing with an underscore",
106 "code": null,
107 "level": "help",
108 "spans": [
109 {
110 "file_name": "driver/subcommand/repl.rs",
111 "byte_start": 9228,
112 "byte_end": 9231,
113 "line_start": 291,
114 "line_end": 291,
115 "column_start": 9,
116 "column_end": 12,
117 "is_primary": true,
118 "text": [
119 {
120 "text": " let foo = 42;",
121 "highlight_start": 9,
122 "highlight_end": 12
123 }
124 ],
125 "label": null,
126 "suggested_replacement": "_foo",
127 "suggestion_applicability": "MachineApplicable",
128 "expansion": null
129 }
130 ],
131 "children": [],
132 "rendered": null
133 }
134],
135"rendered": "warning: unused variable: `foo`\n --> driver/subcommand/repl.rs:291:9\n |\n291 | let foo = 42;\n | ^^^ help: consider prefixing with an underscore: `_foo`\n |\n = note: #[warn(unused_variables)] on by default\n\n"
136}"##,
137 );
138
139 let workspace_root = PathBuf::from("/test/");
140 let diag = map_rust_diagnostic_to_lsp(&diag, &workspace_root).expect("couldn't map diagnostic");
141 insta::assert_debug_snapshot!(diag);
142}
143
144#[test]
145fn snap_rustc_wrong_number_of_parameters() {
146 let diag = parse_diagnostic(
147 r##"{
148"message": "this function takes 2 parameters but 3 parameters were supplied",
149"code": {
150 "code": "E0061",
151 "explanation": "\nThe number of arguments passed to a function must match the number of arguments\nspecified in the function signature.\n\nFor example, a function like:\n\n```\nfn f(a: u16, b: &str) {}\n```\n\nMust always be called with exactly two arguments, e.g., `f(2, \"test\")`.\n\nNote that Rust does not have a notion of optional function arguments or\nvariadic functions (except for its C-FFI).\n"
152},
153"level": "error",
154"spans": [
155 {
156 "file_name": "compiler/ty/select.rs",
157 "byte_start": 8787,
158 "byte_end": 9241,
159 "line_start": 219,
160 "line_end": 231,
161 "column_start": 5,
162 "column_end": 6,
163 "is_primary": false,
164 "text": [
165 {
166 "text": " pub fn add_evidence(",
167 "highlight_start": 5,
168 "highlight_end": 25
169 },
170 {
171 "text": " &mut self,",
172 "highlight_start": 1,
173 "highlight_end": 19
174 },
175 {
176 "text": " target_poly: &ty::Ref<ty::Poly>,",
177 "highlight_start": 1,
178 "highlight_end": 41
179 },
180 {
181 "text": " evidence_poly: &ty::Ref<ty::Poly>,",
182 "highlight_start": 1,
183 "highlight_end": 43
184 },
185 {
186 "text": " ) {",
187 "highlight_start": 1,
188 "highlight_end": 8
189 },
190 {
191 "text": " match target_poly {",
192 "highlight_start": 1,
193 "highlight_end": 28
194 },
195 {
196 "text": " ty::Ref::Var(tvar, _) => self.add_var_evidence(tvar, evidence_poly),",
197 "highlight_start": 1,
198 "highlight_end": 81
199 },
200 {
201 "text": " ty::Ref::Fixed(target_ty) => {",
202 "highlight_start": 1,
203 "highlight_end": 43
204 },
205 {
206 "text": " let evidence_ty = evidence_poly.resolve_to_ty();",
207 "highlight_start": 1,
208 "highlight_end": 65
209 },
210 {
211 "text": " self.add_evidence_ty(target_ty, evidence_poly, evidence_ty)",
212 "highlight_start": 1,
213 "highlight_end": 76
214 },
215 {
216 "text": " }",
217 "highlight_start": 1,
218 "highlight_end": 14
219 },
220 {
221 "text": " }",
222 "highlight_start": 1,
223 "highlight_end": 10
224 },
225 {
226 "text": " }",
227 "highlight_start": 1,
228 "highlight_end": 6
229 }
230 ],
231 "label": "defined here",
232 "suggested_replacement": null,
233 "suggestion_applicability": null,
234 "expansion": null
235 },
236 {
237 "file_name": "compiler/ty/select.rs",
238 "byte_start": 4045,
239 "byte_end": 4057,
240 "line_start": 104,
241 "line_end": 104,
242 "column_start": 18,
243 "column_end": 30,
244 "is_primary": true,
245 "text": [
246 {
247 "text": " self.add_evidence(target_fixed, evidence_fixed, false);",
248 "highlight_start": 18,
249 "highlight_end": 30
250 }
251 ],
252 "label": "expected 2 parameters",
253 "suggested_replacement": null,
254 "suggestion_applicability": null,
255 "expansion": null
256 }
257],
258"children": [],
259"rendered": "error[E0061]: this function takes 2 parameters but 3 parameters were supplied\n --> compiler/ty/select.rs:104:18\n |\n104 | self.add_evidence(target_fixed, evidence_fixed, false);\n | ^^^^^^^^^^^^ expected 2 parameters\n...\n219 | / pub fn add_evidence(\n220 | | &mut self,\n221 | | target_poly: &ty::Ref<ty::Poly>,\n222 | | evidence_poly: &ty::Ref<ty::Poly>,\n... |\n230 | | }\n231 | | }\n | |_____- defined here\n\n"
260}"##,
261 );
262
263 let workspace_root = PathBuf::from("/test/");
264 let diag = map_rust_diagnostic_to_lsp(&diag, &workspace_root).expect("couldn't map diagnostic");
265 insta::assert_debug_snapshot!(diag);
266}
267
268#[test]
269fn snap_clippy_pass_by_ref() {
270 let diag = parse_diagnostic(
271 r##"{
272"message": "this argument is passed by reference, but would be more efficient if passed by value",
273"code": {
274 "code": "clippy::trivially_copy_pass_by_ref",
275 "explanation": null
276},
277"level": "warning",
278"spans": [
279 {
280 "file_name": "compiler/mir/tagset.rs",
281 "byte_start": 941,
282 "byte_end": 946,
283 "line_start": 42,
284 "line_end": 42,
285 "column_start": 24,
286 "column_end": 29,
287 "is_primary": true,
288 "text": [
289 {
290 "text": " pub fn is_disjoint(&self, other: Self) -> bool {",
291 "highlight_start": 24,
292 "highlight_end": 29
293 }
294 ],
295 "label": null,
296 "suggested_replacement": null,
297 "suggestion_applicability": null,
298 "expansion": null
299 }
300],
301"children": [
302 {
303 "message": "lint level defined here",
304 "code": null,
305 "level": "note",
306 "spans": [
307 {
308 "file_name": "compiler/lib.rs",
309 "byte_start": 8,
310 "byte_end": 19,
311 "line_start": 1,
312 "line_end": 1,
313 "column_start": 9,
314 "column_end": 20,
315 "is_primary": true,
316 "text": [
317 {
318 "text": "#![warn(clippy::all)]",
319 "highlight_start": 9,
320 "highlight_end": 20
321 }
322 ],
323 "label": null,
324 "suggested_replacement": null,
325 "suggestion_applicability": null,
326 "expansion": null
327 }
328 ],
329 "children": [],
330 "rendered": null
331 },
332 {
333 "message": "#[warn(clippy::trivially_copy_pass_by_ref)] implied by #[warn(clippy::all)]",
334 "code": null,
335 "level": "note",
336 "spans": [],
337 "children": [],
338 "rendered": null
339 },
340 {
341 "message": "for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#trivially_copy_pass_by_ref",
342 "code": null,
343 "level": "help",
344 "spans": [],
345 "children": [],
346 "rendered": null
347 },
348 {
349 "message": "consider passing by value instead",
350 "code": null,
351 "level": "help",
352 "spans": [
353 {
354 "file_name": "compiler/mir/tagset.rs",
355 "byte_start": 941,
356 "byte_end": 946,
357 "line_start": 42,
358 "line_end": 42,
359 "column_start": 24,
360 "column_end": 29,
361 "is_primary": true,
362 "text": [
363 {
364 "text": " pub fn is_disjoint(&self, other: Self) -> bool {",
365 "highlight_start": 24,
366 "highlight_end": 29
367 }
368 ],
369 "label": null,
370 "suggested_replacement": "self",
371 "suggestion_applicability": "Unspecified",
372 "expansion": null
373 }
374 ],
375 "children": [],
376 "rendered": null
377 }
378],
379"rendered": "warning: this argument is passed by reference, but would be more efficient if passed by value\n --> compiler/mir/tagset.rs:42:24\n |\n42 | pub fn is_disjoint(&self, other: Self) -> bool {\n | ^^^^^ help: consider passing by value instead: `self`\n |\nnote: lint level defined here\n --> compiler/lib.rs:1:9\n |\n1 | #![warn(clippy::all)]\n | ^^^^^^^^^^^\n = note: #[warn(clippy::trivially_copy_pass_by_ref)] implied by #[warn(clippy::all)]\n = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#trivially_copy_pass_by_ref\n\n"
380}"##,
381 );
382
383 let workspace_root = PathBuf::from("/test/");
384 let diag = map_rust_diagnostic_to_lsp(&diag, &workspace_root).expect("couldn't map diagnostic");
385 insta::assert_debug_snapshot!(diag);
386}
387
388#[test]
389fn snap_rustc_mismatched_type() {
390 let diag = parse_diagnostic(
391 r##"{
392"message": "mismatched types",
393"code": {
394 "code": "E0308",
395 "explanation": "\nThis error occurs when the compiler was unable to infer the concrete type of a\nvariable. It can occur for several cases, the most common of which is a\nmismatch in the expected type that the compiler inferred for a variable's\ninitializing expression, and the actual type explicitly assigned to the\nvariable.\n\nFor example:\n\n```compile_fail,E0308\nlet x: i32 = \"I am not a number!\";\n// ~~~ ~~~~~~~~~~~~~~~~~~~~\n// | |\n// | initializing expression;\n// | compiler infers type `&str`\n// |\n// type `i32` assigned to variable `x`\n```\n"
396},
397"level": "error",
398"spans": [
399 {
400 "file_name": "runtime/compiler_support.rs",
401 "byte_start": 1589,
402 "byte_end": 1594,
403 "line_start": 48,
404 "line_end": 48,
405 "column_start": 65,
406 "column_end": 70,
407 "is_primary": true,
408 "text": [
409 {
410 "text": " let layout = alloc::Layout::from_size_align_unchecked(size, align);",
411 "highlight_start": 65,
412 "highlight_end": 70
413 }
414 ],
415 "label": "expected usize, found u32",
416 "suggested_replacement": null,
417 "suggestion_applicability": null,
418 "expansion": null
419 }
420],
421"children": [],
422"rendered": "error[E0308]: mismatched types\n --> runtime/compiler_support.rs:48:65\n |\n48 | let layout = alloc::Layout::from_size_align_unchecked(size, align);\n | ^^^^^ expected usize, found u32\n\n"
423}"##,
424 );
425
426 let workspace_root = PathBuf::from("/test/");
427 let diag = map_rust_diagnostic_to_lsp(&diag, &workspace_root).expect("couldn't map diagnostic");
428 insta::assert_debug_snapshot!(diag);
429}
430
431#[test]
432fn snap_handles_macro_location() {
433 let diag = parse_diagnostic(
434 r##"{
435"rendered": "error[E0277]: can't compare `{integer}` with `&str`\n --> src/main.rs:2:5\n |\n2 | assert_eq!(1, \"love\");\n | ^^^^^^^^^^^^^^^^^^^^^^ no implementation for `{integer} == &str`\n |\n = help: the trait `std::cmp::PartialEq<&str>` is not implemented for `{integer}`\n = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info)\n\n",
436"children": [
437 {
438 "children": [],
439 "code": null,
440 "level": "help",
441 "message": "the trait `std::cmp::PartialEq<&str>` is not implemented for `{integer}`",
442 "rendered": null,
443 "spans": []
444 }
445],
446"code": {
447 "code": "E0277",
448 "explanation": "\nYou tried to use a type which doesn't implement some trait in a place which\nexpected that trait. Erroneous code example:\n\n```compile_fail,E0277\n// here we declare the Foo trait with a bar method\ntrait Foo {\n fn bar(&self);\n}\n\n// we now declare a function which takes an object implementing the Foo trait\nfn some_func<T: Foo>(foo: T) {\n foo.bar();\n}\n\nfn main() {\n // we now call the method with the i32 type, which doesn't implement\n // the Foo trait\n some_func(5i32); // error: the trait bound `i32 : Foo` is not satisfied\n}\n```\n\nIn order to fix this error, verify that the type you're using does implement\nthe trait. Example:\n\n```\ntrait Foo {\n fn bar(&self);\n}\n\nfn some_func<T: Foo>(foo: T) {\n foo.bar(); // we can now use this method since i32 implements the\n // Foo trait\n}\n\n// we implement the trait on the i32 type\nimpl Foo for i32 {\n fn bar(&self) {}\n}\n\nfn main() {\n some_func(5i32); // ok!\n}\n```\n\nOr in a generic context, an erroneous code example would look like:\n\n```compile_fail,E0277\nfn some_func<T>(foo: T) {\n println!(\"{:?}\", foo); // error: the trait `core::fmt::Debug` is not\n // implemented for the type `T`\n}\n\nfn main() {\n // We now call the method with the i32 type,\n // which *does* implement the Debug trait.\n some_func(5i32);\n}\n```\n\nNote that the error here is in the definition of the generic function: Although\nwe only call it with a parameter that does implement `Debug`, the compiler\nstill rejects the function: It must work with all possible input types. In\norder to make this example compile, we need to restrict the generic type we're\naccepting:\n\n```\nuse std::fmt;\n\n// Restrict the input type to types that implement Debug.\nfn some_func<T: fmt::Debug>(foo: T) {\n println!(\"{:?}\", foo);\n}\n\nfn main() {\n // Calling the method is still fine, as i32 implements Debug.\n some_func(5i32);\n\n // This would fail to compile now:\n // struct WithoutDebug;\n // some_func(WithoutDebug);\n}\n```\n\nRust only looks at the signature of the called function, as such it must\nalready specify all requirements that will be used for every type parameter.\n"
449},
450"level": "error",
451"message": "can't compare `{integer}` with `&str`",
452"spans": [
453 {
454 "byte_end": 155,
455 "byte_start": 153,
456 "column_end": 33,
457 "column_start": 31,
458 "expansion": {
459 "def_site_span": {
460 "byte_end": 940,
461 "byte_start": 0,
462 "column_end": 6,
463 "column_start": 1,
464 "expansion": null,
465 "file_name": "<::core::macros::assert_eq macros>",
466 "is_primary": false,
467 "label": null,
468 "line_end": 36,
469 "line_start": 1,
470 "suggested_replacement": null,
471 "suggestion_applicability": null,
472 "text": [
473 {
474 "highlight_end": 35,
475 "highlight_start": 1,
476 "text": "($ left : expr, $ right : expr) =>"
477 },
478 {
479 "highlight_end": 3,
480 "highlight_start": 1,
481 "text": "({"
482 },
483 {
484 "highlight_end": 33,
485 "highlight_start": 1,
486 "text": " match (& $ left, & $ right)"
487 },
488 {
489 "highlight_end": 7,
490 "highlight_start": 1,
491 "text": " {"
492 },
493 {
494 "highlight_end": 34,
495 "highlight_start": 1,
496 "text": " (left_val, right_val) =>"
497 },
498 {
499 "highlight_end": 11,
500 "highlight_start": 1,
501 "text": " {"
502 },
503 {
504 "highlight_end": 46,
505 "highlight_start": 1,
506 "text": " if ! (* left_val == * right_val)"
507 },
508 {
509 "highlight_end": 15,
510 "highlight_start": 1,
511 "text": " {"
512 },
513 {
514 "highlight_end": 25,
515 "highlight_start": 1,
516 "text": " panic !"
517 },
518 {
519 "highlight_end": 57,
520 "highlight_start": 1,
521 "text": " (r#\"assertion failed: `(left == right)`"
522 },
523 {
524 "highlight_end": 16,
525 "highlight_start": 1,
526 "text": " left: `{:?}`,"
527 },
528 {
529 "highlight_end": 18,
530 "highlight_start": 1,
531 "text": " right: `{:?}`\"#,"
532 },
533 {
534 "highlight_end": 47,
535 "highlight_start": 1,
536 "text": " & * left_val, & * right_val)"
537 },
538 {
539 "highlight_end": 15,
540 "highlight_start": 1,
541 "text": " }"
542 },
543 {
544 "highlight_end": 11,
545 "highlight_start": 1,
546 "text": " }"
547 },
548 {
549 "highlight_end": 7,
550 "highlight_start": 1,
551 "text": " }"
552 },
553 {
554 "highlight_end": 42,
555 "highlight_start": 1,
556 "text": " }) ; ($ left : expr, $ right : expr,) =>"
557 },
558 {
559 "highlight_end": 49,
560 "highlight_start": 1,
561 "text": "({ $ crate :: assert_eq ! ($ left, $ right) }) ;"
562 },
563 {
564 "highlight_end": 53,
565 "highlight_start": 1,
566 "text": "($ left : expr, $ right : expr, $ ($ arg : tt) +) =>"
567 },
568 {
569 "highlight_end": 3,
570 "highlight_start": 1,
571 "text": "({"
572 },
573 {
574 "highlight_end": 37,
575 "highlight_start": 1,
576 "text": " match (& ($ left), & ($ right))"
577 },
578 {
579 "highlight_end": 7,
580 "highlight_start": 1,
581 "text": " {"
582 },
583 {
584 "highlight_end": 34,
585 "highlight_start": 1,
586 "text": " (left_val, right_val) =>"
587 },
588 {
589 "highlight_end": 11,
590 "highlight_start": 1,
591 "text": " {"
592 },
593 {
594 "highlight_end": 46,
595 "highlight_start": 1,
596 "text": " if ! (* left_val == * right_val)"
597 },
598 {
599 "highlight_end": 15,
600 "highlight_start": 1,
601 "text": " {"
602 },
603 {
604 "highlight_end": 25,
605 "highlight_start": 1,
606 "text": " panic !"
607 },
608 {
609 "highlight_end": 57,
610 "highlight_start": 1,
611 "text": " (r#\"assertion failed: `(left == right)`"
612 },
613 {
614 "highlight_end": 16,
615 "highlight_start": 1,
616 "text": " left: `{:?}`,"
617 },
618 {
619 "highlight_end": 22,
620 "highlight_start": 1,
621 "text": " right: `{:?}`: {}\"#,"
622 },
623 {
624 "highlight_end": 72,
625 "highlight_start": 1,
626 "text": " & * left_val, & * right_val, $ crate :: format_args !"
627 },
628 {
629 "highlight_end": 33,
630 "highlight_start": 1,
631 "text": " ($ ($ arg) +))"
632 },
633 {
634 "highlight_end": 15,
635 "highlight_start": 1,
636 "text": " }"
637 },
638 {
639 "highlight_end": 11,
640 "highlight_start": 1,
641 "text": " }"
642 },
643 {
644 "highlight_end": 7,
645 "highlight_start": 1,
646 "text": " }"
647 },
648 {
649 "highlight_end": 6,
650 "highlight_start": 1,
651 "text": " }) ;"
652 }
653 ]
654 },
655 "macro_decl_name": "assert_eq!",
656 "span": {
657 "byte_end": 38,
658 "byte_start": 16,
659 "column_end": 27,
660 "column_start": 5,
661 "expansion": null,
662 "file_name": "src/main.rs",
663 "is_primary": false,
664 "label": null,
665 "line_end": 2,
666 "line_start": 2,
667 "suggested_replacement": null,
668 "suggestion_applicability": null,
669 "text": [
670 {
671 "highlight_end": 27,
672 "highlight_start": 5,
673 "text": " assert_eq!(1, \"love\");"
674 }
675 ]
676 }
677 },
678 "file_name": "<::core::macros::assert_eq macros>",
679 "is_primary": true,
680 "label": "no implementation for `{integer} == &str`",
681 "line_end": 7,
682 "line_start": 7,
683 "suggested_replacement": null,
684 "suggestion_applicability": null,
685 "text": [
686 {
687 "highlight_end": 33,
688 "highlight_start": 31,
689 "text": " if ! (* left_val == * right_val)"
690 }
691 ]
692 }
693]
694}"##,
695 );
696
697 let workspace_root = PathBuf::from("/test/");
698 let diag = map_rust_diagnostic_to_lsp(&diag, &workspace_root).expect("couldn't map diagnostic");
699 insta::assert_debug_snapshot!(diag);
700}
diff --git a/crates/ra_cargo_watch/src/lib.rs b/crates/ra_cargo_watch/src/lib.rs
new file mode 100644
index 000000000..e5c22e599
--- /dev/null
+++ b/crates/ra_cargo_watch/src/lib.rs
@@ -0,0 +1,392 @@
1//! cargo_check provides the functionality needed to run `cargo check` or
2//! another compatible command (f.x. clippy) in a background thread and provide
3//! LSP diagnostics based on the output of the command.
4use cargo_metadata::Message;
5use crossbeam_channel::{never, select, unbounded, Receiver, RecvError, Sender};
6use lsp_types::{
7 Diagnostic, Url, WorkDoneProgress, WorkDoneProgressBegin, WorkDoneProgressEnd,
8 WorkDoneProgressReport,
9};
10use parking_lot::RwLock;
11use std::{
12 collections::HashMap,
13 path::PathBuf,
14 process::{Command, Stdio},
15 sync::Arc,
16 thread::JoinHandle,
17 time::Instant,
18};
19
20mod conv;
21
22use crate::conv::{map_rust_diagnostic_to_lsp, MappedRustDiagnostic, SuggestedFix};
23
24#[derive(Clone, Debug)]
25pub struct CheckOptions {
26 pub enable: bool,
27 pub args: Vec<String>,
28 pub command: String,
29 pub all_targets: bool,
30}
31
32/// CheckWatcher wraps the shared state and communication machinery used for
33/// running `cargo check` (or other compatible command) and providing
34/// diagnostics based on the output.
35/// The spawned thread is shut down when this struct is dropped.
36#[derive(Debug)]
37pub struct CheckWatcher {
38 pub task_recv: Receiver<CheckTask>,
39 pub shared: Arc<RwLock<CheckWatcherSharedState>>,
40 cmd_send: Option<Sender<CheckCommand>>,
41 handle: Option<JoinHandle<()>>,
42}
43
44impl CheckWatcher {
45 pub fn new(options: &CheckOptions, workspace_root: PathBuf) -> CheckWatcher {
46 let options = options.clone();
47 let shared = Arc::new(RwLock::new(CheckWatcherSharedState::new()));
48
49 let (task_send, task_recv) = unbounded::<CheckTask>();
50 let (cmd_send, cmd_recv) = unbounded::<CheckCommand>();
51 let shared_ = shared.clone();
52 let handle = std::thread::spawn(move || {
53 let mut check = CheckWatcherState::new(options, workspace_root, shared_);
54 check.run(&task_send, &cmd_recv);
55 });
56 CheckWatcher { task_recv, cmd_send: Some(cmd_send), handle: Some(handle), shared }
57 }
58
59 /// Schedule a re-start of the cargo check worker.
60 pub fn update(&self) {
61 if let Some(cmd_send) = &self.cmd_send {
62 cmd_send.send(CheckCommand::Update).unwrap();
63 }
64 }
65}
66
67impl std::ops::Drop for CheckWatcher {
68 fn drop(&mut self) {
69 if let Some(handle) = self.handle.take() {
70 // Take the sender out of the option
71 let recv = self.cmd_send.take();
72
73 // Dropping the sender finishes the thread loop
74 drop(recv);
75
76 // Join the thread, it should finish shortly. We don't really care
77 // whether it panicked, so it is safe to ignore the result
78 let _ = handle.join();
79 }
80 }
81}
82
83#[derive(Debug)]
84pub struct CheckWatcherSharedState {
85 diagnostic_collection: HashMap<Url, Vec<Diagnostic>>,
86 suggested_fix_collection: HashMap<Url, Vec<SuggestedFix>>,
87}
88
89impl CheckWatcherSharedState {
90 fn new() -> CheckWatcherSharedState {
91 CheckWatcherSharedState {
92 diagnostic_collection: HashMap::new(),
93 suggested_fix_collection: HashMap::new(),
94 }
95 }
96
97 /// Clear the cached diagnostics, and schedule updating diagnostics by the
98 /// server, to clear stale results.
99 pub fn clear(&mut self, task_send: &Sender<CheckTask>) {
100 let cleared_files: Vec<Url> = self.diagnostic_collection.keys().cloned().collect();
101
102 self.diagnostic_collection.clear();
103 self.suggested_fix_collection.clear();
104
105 for uri in cleared_files {
106 task_send.send(CheckTask::Update(uri.clone())).unwrap();
107 }
108 }
109
110 pub fn diagnostics_for(&self, uri: &Url) -> Option<&[Diagnostic]> {
111 self.diagnostic_collection.get(uri).map(|d| d.as_slice())
112 }
113
114 pub fn fixes_for(&self, uri: &Url) -> Option<&[SuggestedFix]> {
115 self.suggested_fix_collection.get(uri).map(|d| d.as_slice())
116 }
117
118 fn add_diagnostic(&mut self, file_uri: Url, diagnostic: Diagnostic) {
119 let diagnostics = self.diagnostic_collection.entry(file_uri).or_default();
120
121 // If we're building multiple targets it's possible we've already seen this diagnostic
122 let is_duplicate = diagnostics.iter().any(|d| are_diagnostics_equal(d, &diagnostic));
123 if is_duplicate {
124 return;
125 }
126
127 diagnostics.push(diagnostic);
128 }
129
130 fn add_suggested_fix_for_diagnostic(
131 &mut self,
132 mut suggested_fix: SuggestedFix,
133 diagnostic: &Diagnostic,
134 ) {
135 let file_uri = suggested_fix.location.uri.clone();
136 let file_suggestions = self.suggested_fix_collection.entry(file_uri).or_default();
137
138 let existing_suggestion: Option<&mut SuggestedFix> =
139 file_suggestions.iter_mut().find(|s| s == &&suggested_fix);
140 if let Some(existing_suggestion) = existing_suggestion {
141 // The existing suggestion also applies to this new diagnostic
142 existing_suggestion.diagnostics.push(diagnostic.clone());
143 } else {
144 // We haven't seen this suggestion before
145 suggested_fix.diagnostics.push(diagnostic.clone());
146 file_suggestions.push(suggested_fix);
147 }
148 }
149}
150
151#[derive(Debug)]
152pub enum CheckTask {
153 /// Request a update of the given files diagnostics
154 Update(Url),
155
156 /// Request check progress notification to client
157 Status(WorkDoneProgress),
158}
159
160pub enum CheckCommand {
161 /// Request re-start of check thread
162 Update,
163}
164
165struct CheckWatcherState {
166 options: CheckOptions,
167 workspace_root: PathBuf,
168 watcher: WatchThread,
169 last_update_req: Option<Instant>,
170 shared: Arc<RwLock<CheckWatcherSharedState>>,
171}
172
173impl CheckWatcherState {
174 fn new(
175 options: CheckOptions,
176 workspace_root: PathBuf,
177 shared: Arc<RwLock<CheckWatcherSharedState>>,
178 ) -> CheckWatcherState {
179 let watcher = WatchThread::new(&options, &workspace_root);
180 CheckWatcherState { options, workspace_root, watcher, last_update_req: None, shared }
181 }
182
183 fn run(&mut self, task_send: &Sender<CheckTask>, cmd_recv: &Receiver<CheckCommand>) {
184 loop {
185 select! {
186 recv(&cmd_recv) -> cmd => match cmd {
187 Ok(cmd) => self.handle_command(cmd),
188 Err(RecvError) => {
189 // Command channel has closed, so shut down
190 break;
191 },
192 },
193 recv(self.watcher.message_recv) -> msg => match msg {
194 Ok(msg) => self.handle_message(msg, task_send),
195 Err(RecvError) => {
196 // Watcher finished, replace it with a never channel to
197 // avoid busy-waiting.
198 std::mem::replace(&mut self.watcher.message_recv, never());
199 },
200 }
201 };
202
203 if self.should_recheck() {
204 self.last_update_req.take();
205 self.shared.write().clear(task_send);
206
207 // By replacing the watcher, we drop the previous one which
208 // causes it to shut down automatically.
209 self.watcher = WatchThread::new(&self.options, &self.workspace_root);
210 }
211 }
212 }
213
214 fn should_recheck(&mut self) -> bool {
215 if let Some(_last_update_req) = &self.last_update_req {
216 // We currently only request an update on save, as we need up to
217 // date source on disk for cargo check to do it's magic, so we
218 // don't really need to debounce the requests at this point.
219 return true;
220 }
221 false
222 }
223
224 fn handle_command(&mut self, cmd: CheckCommand) {
225 match cmd {
226 CheckCommand::Update => self.last_update_req = Some(Instant::now()),
227 }
228 }
229
230 fn handle_message(&mut self, msg: CheckEvent, task_send: &Sender<CheckTask>) {
231 match msg {
232 CheckEvent::Begin => {
233 task_send
234 .send(CheckTask::Status(WorkDoneProgress::Begin(WorkDoneProgressBegin {
235 title: "Running 'cargo check'".to_string(),
236 cancellable: Some(false),
237 message: None,
238 percentage: None,
239 })))
240 .unwrap();
241 }
242
243 CheckEvent::End => {
244 task_send
245 .send(CheckTask::Status(WorkDoneProgress::End(WorkDoneProgressEnd {
246 message: None,
247 })))
248 .unwrap();
249 }
250
251 CheckEvent::Msg(Message::CompilerArtifact(msg)) => {
252 task_send
253 .send(CheckTask::Status(WorkDoneProgress::Report(WorkDoneProgressReport {
254 cancellable: Some(false),
255 message: Some(msg.target.name),
256 percentage: None,
257 })))
258 .unwrap();
259 }
260
261 CheckEvent::Msg(Message::CompilerMessage(msg)) => {
262 let map_result =
263 match map_rust_diagnostic_to_lsp(&msg.message, &self.workspace_root) {
264 Some(map_result) => map_result,
265 None => return,
266 };
267
268 let MappedRustDiagnostic { location, diagnostic, suggested_fixes } = map_result;
269 let file_uri = location.uri.clone();
270
271 if !suggested_fixes.is_empty() {
272 for suggested_fix in suggested_fixes {
273 self.shared
274 .write()
275 .add_suggested_fix_for_diagnostic(suggested_fix, &diagnostic);
276 }
277 }
278 self.shared.write().add_diagnostic(file_uri, diagnostic);
279
280 task_send.send(CheckTask::Update(location.uri)).unwrap();
281 }
282
283 CheckEvent::Msg(Message::BuildScriptExecuted(_msg)) => {}
284 CheckEvent::Msg(Message::Unknown) => {}
285 }
286 }
287}
288
289/// WatchThread exists to wrap around the communication needed to be able to
290/// run `cargo check` without blocking. Currently the Rust standard library
291/// doesn't provide a way to read sub-process output without blocking, so we
292/// have to wrap sub-processes output handling in a thread and pass messages
293/// back over a channel.
294/// The correct way to dispose of the thread is to drop it, on which the
295/// sub-process will be killed, and the thread will be joined.
296struct WatchThread {
297 handle: Option<JoinHandle<()>>,
298 message_recv: Receiver<CheckEvent>,
299}
300
301enum CheckEvent {
302 Begin,
303 Msg(cargo_metadata::Message),
304 End,
305}
306
307impl WatchThread {
308 fn new(options: &CheckOptions, workspace_root: &PathBuf) -> WatchThread {
309 let mut args: Vec<String> = vec![
310 options.command.clone(),
311 "--message-format=json".to_string(),
312 "--manifest-path".to_string(),
313 format!("{}/Cargo.toml", workspace_root.to_string_lossy()),
314 ];
315 if options.all_targets {
316 args.push("--all-targets".to_string());
317 }
318 args.extend(options.args.iter().cloned());
319
320 let (message_send, message_recv) = unbounded();
321 let enabled = options.enable;
322 let handle = std::thread::spawn(move || {
323 if !enabled {
324 return;
325 }
326
327 let mut command = Command::new("cargo")
328 .args(&args)
329 .stdout(Stdio::piped())
330 .stderr(Stdio::null())
331 .spawn()
332 .expect("couldn't launch cargo");
333
334 // If we trigger an error here, we will do so in the loop instead,
335 // which will break out of the loop, and continue the shutdown
336 let _ = message_send.send(CheckEvent::Begin);
337
338 for message in cargo_metadata::parse_messages(command.stdout.take().unwrap()) {
339 let message = match message {
340 Ok(message) => message,
341 Err(err) => {
342 log::error!("Invalid json from cargo check, ignoring: {}", err);
343 continue;
344 }
345 };
346
347 match message_send.send(CheckEvent::Msg(message)) {
348 Ok(()) => {}
349 Err(_err) => {
350 // The send channel was closed, so we want to shutdown
351 break;
352 }
353 }
354 }
355
356 // We can ignore any error here, as we are already in the progress
357 // of shutting down.
358 let _ = message_send.send(CheckEvent::End);
359
360 // It is okay to ignore the result, as it only errors if the process is already dead
361 let _ = command.kill();
362
363 // Again, we don't care about the exit status so just ignore the result
364 let _ = command.wait();
365 });
366 WatchThread { handle: Some(handle), message_recv }
367 }
368}
369
370impl std::ops::Drop for WatchThread {
371 fn drop(&mut self) {
372 if let Some(handle) = self.handle.take() {
373 // Replace our reciever with dummy one, so we can drop and close the
374 // one actually communicating with the thread
375 let recv = std::mem::replace(&mut self.message_recv, never());
376
377 // Dropping the original reciever initiates thread sub-process shutdown
378 drop(recv);
379
380 // Join the thread, it should finish shortly. We don't really care
381 // whether it panicked, so it is safe to ignore the result
382 let _ = handle.join();
383 }
384 }
385}
386
387fn are_diagnostics_equal(left: &Diagnostic, right: &Diagnostic) -> bool {
388 left.source == right.source
389 && left.severity == right.severity
390 && left.range == right.range
391 && left.message == right.message
392}
diff --git a/crates/ra_hir/src/code_model.rs b/crates/ra_hir/src/code_model.rs
index bcfc0d03e..488f74cfb 100644
--- a/crates/ra_hir/src/code_model.rs
+++ b/crates/ra_hir/src/code_model.rs
@@ -118,7 +118,7 @@ impl_froms!(
118 BuiltinType 118 BuiltinType
119); 119);
120 120
121pub use hir_def::attr::Attrs; 121pub use hir_def::{attr::Attrs, visibility::Visibility};
122 122
123impl Module { 123impl Module {
124 pub(crate) fn new(krate: Crate, crate_module_id: LocalModuleId) -> Module { 124 pub(crate) fn new(krate: Crate, crate_module_id: LocalModuleId) -> Module {
@@ -255,6 +255,15 @@ impl StructField {
255 } 255 }
256} 256}
257 257
258impl HasVisibility for StructField {
259 fn visibility(&self, db: &impl HirDatabase) -> Visibility {
260 let variant_data = self.parent.variant_data(db);
261 let visibility = &variant_data.fields()[self.id].visibility;
262 let parent_id: hir_def::VariantId = self.parent.into();
263 visibility.resolve(db, &parent_id.resolver(db))
264 }
265}
266
258#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 267#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
259pub struct Struct { 268pub struct Struct {
260 pub(crate) id: StructId, 269 pub(crate) id: StructId,
@@ -644,6 +653,17 @@ impl_froms!(
644 Const 653 Const
645); 654);
646 655
656impl GenericDef {
657 pub fn params(self, db: &impl HirDatabase) -> Vec<TypeParam> {
658 let generics: Arc<hir_def::generics::GenericParams> = db.generic_params(self.into());
659 generics
660 .types
661 .iter()
662 .map(|(local_id, _)| TypeParam { id: TypeParamId { parent: self.into(), local_id } })
663 .collect()
664 }
665}
666
647#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] 667#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
648pub struct Local { 668pub struct Local {
649 pub(crate) parent: DefWithBody, 669 pub(crate) parent: DefWithBody,
@@ -1030,3 +1050,11 @@ impl<T: Into<AttrDef> + Copy> Docs for T {
1030 db.documentation(def.into()) 1050 db.documentation(def.into())
1031 } 1051 }
1032} 1052}
1053
1054pub trait HasVisibility {
1055 fn visibility(&self, db: &impl HirDatabase) -> Visibility;
1056 fn is_visible_from(&self, db: &impl HirDatabase, module: Module) -> bool {
1057 let vis = self.visibility(db);
1058 vis.is_visible_from(db, module.id)
1059 }
1060}
diff --git a/crates/ra_hir/src/lib.rs b/crates/ra_hir/src/lib.rs
index 0008a8858..3d13978d4 100644
--- a/crates/ra_hir/src/lib.rs
+++ b/crates/ra_hir/src/lib.rs
@@ -40,8 +40,8 @@ mod from_source;
40pub use crate::{ 40pub use crate::{
41 code_model::{ 41 code_model::{
42 Adt, AssocItem, AttrDef, Const, Crate, CrateDependency, DefWithBody, Docs, Enum, 42 Adt, AssocItem, AttrDef, Const, Crate, CrateDependency, DefWithBody, Docs, Enum,
43 EnumVariant, FieldSource, Function, GenericDef, HasAttrs, ImplBlock, Local, MacroDef, 43 EnumVariant, FieldSource, Function, GenericDef, HasAttrs, HasVisibility, ImplBlock, Local,
44 Module, ModuleDef, ScopeDef, Static, Struct, StructField, Trait, Type, TypeAlias, 44 MacroDef, Module, ModuleDef, ScopeDef, Static, Struct, StructField, Trait, Type, TypeAlias,
45 TypeParam, Union, VariantDef, 45 TypeParam, Union, VariantDef,
46 }, 46 },
47 from_source::FromSource, 47 from_source::FromSource,
diff --git a/crates/ra_hir/src/source_binder.rs b/crates/ra_hir/src/source_binder.rs
index 85b378483..2c422af8b 100644
--- a/crates/ra_hir/src/source_binder.rs
+++ b/crates/ra_hir/src/source_binder.rs
@@ -215,8 +215,32 @@ impl SourceAnalyzer {
215 self.body_source_map.as_ref()?.node_pat(src) 215 self.body_source_map.as_ref()?.node_pat(src)
216 } 216 }
217 217
218 fn expand_expr(
219 &self,
220 db: &impl HirDatabase,
221 expr: InFile<&ast::Expr>,
222 ) -> Option<InFile<ast::Expr>> {
223 let macro_call = ast::MacroCall::cast(expr.value.syntax().clone())?;
224 let macro_file =
225 self.body_source_map.as_ref()?.node_macro_file(expr.with_value(&macro_call))?;
226 let expanded = db.parse_or_expand(macro_file)?;
227 let kind = expanded.kind();
228 let expr = InFile::new(macro_file, ast::Expr::cast(expanded)?);
229
230 if ast::MacroCall::can_cast(kind) {
231 self.expand_expr(db, expr.as_ref())
232 } else {
233 Some(expr)
234 }
235 }
236
218 pub fn type_of(&self, db: &impl HirDatabase, expr: &ast::Expr) -> Option<Type> { 237 pub fn type_of(&self, db: &impl HirDatabase, expr: &ast::Expr) -> Option<Type> {
219 let expr_id = self.expr_id(expr)?; 238 let expr_id = if let Some(expr) = self.expand_expr(db, InFile::new(self.file_id, expr)) {
239 self.body_source_map.as_ref()?.node_expr(expr.as_ref())?
240 } else {
241 self.expr_id(expr)?
242 };
243
220 let ty = self.infer.as_ref()?[expr_id].clone(); 244 let ty = self.infer.as_ref()?[expr_id].clone();
221 let environment = TraitEnvironment::lower(db, &self.resolver); 245 let environment = TraitEnvironment::lower(db, &self.resolver);
222 Some(Type { krate: self.resolver.krate()?, ty: InEnvironment { value: ty, environment } }) 246 Some(Type { krate: self.resolver.krate()?, ty: InEnvironment { value: ty, environment } })
diff --git a/crates/ra_hir_def/src/adt.rs b/crates/ra_hir_def/src/adt.rs
index d9ea693e3..aac5f3e15 100644
--- a/crates/ra_hir_def/src/adt.rs
+++ b/crates/ra_hir_def/src/adt.rs
@@ -9,11 +9,12 @@ use hir_expand::{
9}; 9};
10use ra_arena::{map::ArenaMap, Arena}; 10use ra_arena::{map::ArenaMap, Arena};
11use ra_prof::profile; 11use ra_prof::profile;
12use ra_syntax::ast::{self, NameOwner, TypeAscriptionOwner}; 12use ra_syntax::ast::{self, NameOwner, TypeAscriptionOwner, VisibilityOwner};
13 13
14use crate::{ 14use crate::{
15 db::DefDatabase, src::HasChildSource, src::HasSource, trace::Trace, type_ref::TypeRef, EnumId, 15 db::DefDatabase, src::HasChildSource, src::HasSource, trace::Trace, type_ref::TypeRef,
16 LocalEnumVariantId, LocalStructFieldId, Lookup, StructId, UnionId, VariantId, 16 visibility::RawVisibility, EnumId, LocalEnumVariantId, LocalStructFieldId, Lookup, StructId,
17 UnionId, VariantId,
17}; 18};
18 19
19/// Note that we use `StructData` for unions as well! 20/// Note that we use `StructData` for unions as well!
@@ -47,13 +48,14 @@ pub enum VariantData {
47pub struct StructFieldData { 48pub struct StructFieldData {
48 pub name: Name, 49 pub name: Name,
49 pub type_ref: TypeRef, 50 pub type_ref: TypeRef,
51 pub visibility: RawVisibility,
50} 52}
51 53
52impl StructData { 54impl StructData {
53 pub(crate) fn struct_data_query(db: &impl DefDatabase, id: StructId) -> Arc<StructData> { 55 pub(crate) fn struct_data_query(db: &impl DefDatabase, id: StructId) -> Arc<StructData> {
54 let src = id.lookup(db).source(db); 56 let src = id.lookup(db).source(db);
55 let name = src.value.name().map_or_else(Name::missing, |n| n.as_name()); 57 let name = src.value.name().map_or_else(Name::missing, |n| n.as_name());
56 let variant_data = VariantData::new(src.value.kind()); 58 let variant_data = VariantData::new(db, src.map(|s| s.kind()));
57 let variant_data = Arc::new(variant_data); 59 let variant_data = Arc::new(variant_data);
58 Arc::new(StructData { name, variant_data }) 60 Arc::new(StructData { name, variant_data })
59 } 61 }
@@ -61,10 +63,12 @@ impl StructData {
61 let src = id.lookup(db).source(db); 63 let src = id.lookup(db).source(db);
62 let name = src.value.name().map_or_else(Name::missing, |n| n.as_name()); 64 let name = src.value.name().map_or_else(Name::missing, |n| n.as_name());
63 let variant_data = VariantData::new( 65 let variant_data = VariantData::new(
64 src.value 66 db,
65 .record_field_def_list() 67 src.map(|s| {
66 .map(ast::StructKind::Record) 68 s.record_field_def_list()
67 .unwrap_or(ast::StructKind::Unit), 69 .map(ast::StructKind::Record)
70 .unwrap_or(ast::StructKind::Unit)
71 }),
68 ); 72 );
69 let variant_data = Arc::new(variant_data); 73 let variant_data = Arc::new(variant_data);
70 Arc::new(StructData { name, variant_data }) 74 Arc::new(StructData { name, variant_data })
@@ -77,7 +81,7 @@ impl EnumData {
77 let src = e.lookup(db).source(db); 81 let src = e.lookup(db).source(db);
78 let name = src.value.name().map_or_else(Name::missing, |n| n.as_name()); 82 let name = src.value.name().map_or_else(Name::missing, |n| n.as_name());
79 let mut trace = Trace::new_for_arena(); 83 let mut trace = Trace::new_for_arena();
80 lower_enum(&mut trace, &src.value); 84 lower_enum(db, &mut trace, &src);
81 Arc::new(EnumData { name, variants: trace.into_arena() }) 85 Arc::new(EnumData { name, variants: trace.into_arena() })
82 } 86 }
83 87
@@ -93,30 +97,31 @@ impl HasChildSource for EnumId {
93 fn child_source(&self, db: &impl DefDatabase) -> InFile<ArenaMap<Self::ChildId, Self::Value>> { 97 fn child_source(&self, db: &impl DefDatabase) -> InFile<ArenaMap<Self::ChildId, Self::Value>> {
94 let src = self.lookup(db).source(db); 98 let src = self.lookup(db).source(db);
95 let mut trace = Trace::new_for_map(); 99 let mut trace = Trace::new_for_map();
96 lower_enum(&mut trace, &src.value); 100 lower_enum(db, &mut trace, &src);
97 src.with_value(trace.into_map()) 101 src.with_value(trace.into_map())
98 } 102 }
99} 103}
100 104
101fn lower_enum( 105fn lower_enum(
106 db: &impl DefDatabase,
102 trace: &mut Trace<LocalEnumVariantId, EnumVariantData, ast::EnumVariant>, 107 trace: &mut Trace<LocalEnumVariantId, EnumVariantData, ast::EnumVariant>,
103 ast: &ast::EnumDef, 108 ast: &InFile<ast::EnumDef>,
104) { 109) {
105 for var in ast.variant_list().into_iter().flat_map(|it| it.variants()) { 110 for var in ast.value.variant_list().into_iter().flat_map(|it| it.variants()) {
106 trace.alloc( 111 trace.alloc(
107 || var.clone(), 112 || var.clone(),
108 || EnumVariantData { 113 || EnumVariantData {
109 name: var.name().map_or_else(Name::missing, |it| it.as_name()), 114 name: var.name().map_or_else(Name::missing, |it| it.as_name()),
110 variant_data: Arc::new(VariantData::new(var.kind())), 115 variant_data: Arc::new(VariantData::new(db, ast.with_value(var.kind()))),
111 }, 116 },
112 ); 117 );
113 } 118 }
114} 119}
115 120
116impl VariantData { 121impl VariantData {
117 fn new(flavor: ast::StructKind) -> Self { 122 fn new(db: &impl DefDatabase, flavor: InFile<ast::StructKind>) -> Self {
118 let mut trace = Trace::new_for_arena(); 123 let mut trace = Trace::new_for_arena();
119 match lower_struct(&mut trace, &flavor) { 124 match lower_struct(db, &mut trace, &flavor) {
120 StructKind::Tuple => VariantData::Tuple(trace.into_arena()), 125 StructKind::Tuple => VariantData::Tuple(trace.into_arena()),
121 StructKind::Record => VariantData::Record(trace.into_arena()), 126 StructKind::Record => VariantData::Record(trace.into_arena()),
122 StructKind::Unit => VariantData::Unit, 127 StructKind::Unit => VariantData::Unit,
@@ -163,7 +168,7 @@ impl HasChildSource for VariantId {
163 }), 168 }),
164 }; 169 };
165 let mut trace = Trace::new_for_map(); 170 let mut trace = Trace::new_for_map();
166 lower_struct(&mut trace, &src.value); 171 lower_struct(db, &mut trace, &src);
167 src.with_value(trace.into_map()) 172 src.with_value(trace.into_map())
168 } 173 }
169} 174}
@@ -175,14 +180,15 @@ enum StructKind {
175} 180}
176 181
177fn lower_struct( 182fn lower_struct(
183 db: &impl DefDatabase,
178 trace: &mut Trace< 184 trace: &mut Trace<
179 LocalStructFieldId, 185 LocalStructFieldId,
180 StructFieldData, 186 StructFieldData,
181 Either<ast::TupleFieldDef, ast::RecordFieldDef>, 187 Either<ast::TupleFieldDef, ast::RecordFieldDef>,
182 >, 188 >,
183 ast: &ast::StructKind, 189 ast: &InFile<ast::StructKind>,
184) -> StructKind { 190) -> StructKind {
185 match ast { 191 match &ast.value {
186 ast::StructKind::Tuple(fl) => { 192 ast::StructKind::Tuple(fl) => {
187 for (i, fd) in fl.fields().enumerate() { 193 for (i, fd) in fl.fields().enumerate() {
188 trace.alloc( 194 trace.alloc(
@@ -190,6 +196,7 @@ fn lower_struct(
190 || StructFieldData { 196 || StructFieldData {
191 name: Name::new_tuple_field(i), 197 name: Name::new_tuple_field(i),
192 type_ref: TypeRef::from_ast_opt(fd.type_ref()), 198 type_ref: TypeRef::from_ast_opt(fd.type_ref()),
199 visibility: RawVisibility::from_ast(db, ast.with_value(fd.visibility())),
193 }, 200 },
194 ); 201 );
195 } 202 }
@@ -202,6 +209,7 @@ fn lower_struct(
202 || StructFieldData { 209 || StructFieldData {
203 name: fd.name().map(|n| n.as_name()).unwrap_or_else(Name::missing), 210 name: fd.name().map(|n| n.as_name()).unwrap_or_else(Name::missing),
204 type_ref: TypeRef::from_ast_opt(fd.ascribed_type()), 211 type_ref: TypeRef::from_ast_opt(fd.ascribed_type()),
212 visibility: RawVisibility::from_ast(db, ast.with_value(fd.visibility())),
205 }, 213 },
206 ); 214 );
207 } 215 }
diff --git a/crates/ra_hir_def/src/body.rs b/crates/ra_hir_def/src/body.rs
index d3e4c50ae..142c52d35 100644
--- a/crates/ra_hir_def/src/body.rs
+++ b/crates/ra_hir_def/src/body.rs
@@ -163,6 +163,7 @@ pub struct BodySourceMap {
163 pat_map: FxHashMap<PatSource, PatId>, 163 pat_map: FxHashMap<PatSource, PatId>,
164 pat_map_back: ArenaMap<PatId, PatSource>, 164 pat_map_back: ArenaMap<PatId, PatSource>,
165 field_map: FxHashMap<(ExprId, usize), AstPtr<ast::RecordField>>, 165 field_map: FxHashMap<(ExprId, usize), AstPtr<ast::RecordField>>,
166 expansions: FxHashMap<InFile<AstPtr<ast::MacroCall>>, HirFileId>,
166} 167}
167 168
168impl Body { 169impl Body {
@@ -237,6 +238,11 @@ impl BodySourceMap {
237 self.expr_map.get(&src).cloned() 238 self.expr_map.get(&src).cloned()
238 } 239 }
239 240
241 pub fn node_macro_file(&self, node: InFile<&ast::MacroCall>) -> Option<HirFileId> {
242 let src = node.map(|it| AstPtr::new(it));
243 self.expansions.get(&src).cloned()
244 }
245
240 pub fn field_init_shorthand_expr(&self, node: InFile<&ast::RecordField>) -> Option<ExprId> { 246 pub fn field_init_shorthand_expr(&self, node: InFile<&ast::RecordField>) -> Option<ExprId> {
241 let src = node.map(|it| Either::Right(AstPtr::new(it))); 247 let src = node.map(|it| Either::Right(AstPtr::new(it)));
242 self.expr_map.get(&src).cloned() 248 self.expr_map.get(&src).cloned()
diff --git a/crates/ra_hir_def/src/body/lower.rs b/crates/ra_hir_def/src/body/lower.rs
index 5323af097..e656f9a41 100644
--- a/crates/ra_hir_def/src/body/lower.rs
+++ b/crates/ra_hir_def/src/body/lower.rs
@@ -446,14 +446,20 @@ where
446 } 446 }
447 } 447 }
448 // FIXME expand to statements in statement position 448 // FIXME expand to statements in statement position
449 ast::Expr::MacroCall(e) => match self.expander.enter_expand(self.db, e) { 449 ast::Expr::MacroCall(e) => {
450 Some((mark, expansion)) => { 450 let macro_call = self.expander.to_source(AstPtr::new(&e));
451 let id = self.collect_expr(expansion); 451 match self.expander.enter_expand(self.db, e.clone()) {
452 self.expander.exit(self.db, mark); 452 Some((mark, expansion)) => {
453 id 453 self.source_map
454 .expansions
455 .insert(macro_call, self.expander.current_file_id);
456 let id = self.collect_expr(expansion);
457 self.expander.exit(self.db, mark);
458 id
459 }
460 None => self.alloc_expr(Expr::Missing, syntax_ptr),
454 } 461 }
455 None => self.alloc_expr(Expr::Missing, syntax_ptr), 462 }
456 },
457 463
458 // FIXME implement HIR for these: 464 // FIXME implement HIR for these:
459 ast::Expr::Label(_e) => self.alloc_expr(Expr::Missing, syntax_ptr), 465 ast::Expr::Label(_e) => self.alloc_expr(Expr::Missing, syntax_ptr),
@@ -543,7 +549,10 @@ where
543 }; 549 };
544 self.body.item_scope.define_def(def); 550 self.body.item_scope.define_def(def);
545 if let Some(name) = name { 551 if let Some(name) = name {
546 self.body.item_scope.push_res(name.as_name(), def.into()); 552 let vis = crate::visibility::Visibility::Public; // FIXME determine correctly
553 self.body
554 .item_scope
555 .push_res(name.as_name(), crate::per_ns::PerNs::from_def(def, vis));
547 } 556 }
548 } 557 }
549 } 558 }
diff --git a/crates/ra_hir_def/src/data.rs b/crates/ra_hir_def/src/data.rs
index 1aa9a9b7d..c900a6a18 100644
--- a/crates/ra_hir_def/src/data.rs
+++ b/crates/ra_hir_def/src/data.rs
@@ -10,8 +10,9 @@ use ra_syntax::ast::{self, AstNode, ImplItem, ModuleItemOwner, NameOwner, TypeAs
10 10
11use crate::{ 11use crate::{
12 db::DefDatabase, 12 db::DefDatabase,
13 path::{path, GenericArgs, Path},
13 src::HasSource, 14 src::HasSource,
14 type_ref::{Mutability, TypeRef}, 15 type_ref::{Mutability, TypeBound, TypeRef},
15 AssocContainerId, AssocItemId, ConstId, ConstLoc, Expander, FunctionId, FunctionLoc, HasModule, 16 AssocContainerId, AssocItemId, ConstId, ConstLoc, Expander, FunctionId, FunctionLoc, HasModule,
16 ImplId, Intern, Lookup, ModuleId, StaticId, TraitId, TypeAliasId, TypeAliasLoc, 17 ImplId, Intern, Lookup, ModuleId, StaticId, TraitId, TypeAliasId, TypeAliasLoc,
17}; 18};
@@ -62,11 +63,29 @@ impl FunctionData {
62 TypeRef::unit() 63 TypeRef::unit()
63 }; 64 };
64 65
66 let ret_type = if src.value.is_async() {
67 let future_impl = desugar_future_path(ret_type);
68 let ty_bound = TypeBound::Path(future_impl);
69 TypeRef::ImplTrait(vec![ty_bound])
70 } else {
71 ret_type
72 };
73
65 let sig = FunctionData { name, params, ret_type, has_self_param }; 74 let sig = FunctionData { name, params, ret_type, has_self_param };
66 Arc::new(sig) 75 Arc::new(sig)
67 } 76 }
68} 77}
69 78
79fn desugar_future_path(orig: TypeRef) -> Path {
80 let path = path![std::future::Future];
81 let mut generic_args: Vec<_> = std::iter::repeat(None).take(path.segments.len() - 1).collect();
82 let mut last = GenericArgs::empty();
83 last.bindings.push((name![Output], orig));
84 generic_args.push(Some(Arc::new(last)));
85
86 Path::from_known_path(path, generic_args)
87}
88
70#[derive(Debug, Clone, PartialEq, Eq)] 89#[derive(Debug, Clone, PartialEq, Eq)]
71pub struct TypeAliasData { 90pub struct TypeAliasData {
72 pub name: Name, 91 pub name: Name,
diff --git a/crates/ra_hir_def/src/item_scope.rs b/crates/ra_hir_def/src/item_scope.rs
index b0288ee8d..fe7bb9779 100644
--- a/crates/ra_hir_def/src/item_scope.rs
+++ b/crates/ra_hir_def/src/item_scope.rs
@@ -5,7 +5,10 @@ use hir_expand::name::Name;
5use once_cell::sync::Lazy; 5use once_cell::sync::Lazy;
6use rustc_hash::FxHashMap; 6use rustc_hash::FxHashMap;
7 7
8use crate::{per_ns::PerNs, AdtId, BuiltinType, ImplId, MacroDefId, ModuleDefId, TraitId}; 8use crate::{
9 per_ns::PerNs, visibility::Visibility, AdtId, BuiltinType, ImplId, MacroDefId, ModuleDefId,
10 TraitId,
11};
9 12
10#[derive(Debug, Default, PartialEq, Eq)] 13#[derive(Debug, Default, PartialEq, Eq)]
11pub struct ItemScope { 14pub struct ItemScope {
@@ -30,7 +33,7 @@ pub struct ItemScope {
30static BUILTIN_SCOPE: Lazy<FxHashMap<Name, PerNs>> = Lazy::new(|| { 33static BUILTIN_SCOPE: Lazy<FxHashMap<Name, PerNs>> = Lazy::new(|| {
31 BuiltinType::ALL 34 BuiltinType::ALL
32 .iter() 35 .iter()
33 .map(|(name, ty)| (name.clone(), PerNs::types(ty.clone().into()))) 36 .map(|(name, ty)| (name.clone(), PerNs::types(ty.clone().into(), Visibility::Public)))
34 .collect() 37 .collect()
35}); 38});
36 39
@@ -144,8 +147,8 @@ impl ItemScope {
144 changed 147 changed
145 } 148 }
146 149
147 pub(crate) fn collect_resolutions(&self) -> Vec<(Name, PerNs)> { 150 pub(crate) fn resolutions<'a>(&'a self) -> impl Iterator<Item = (Name, PerNs)> + 'a {
148 self.visible.iter().map(|(name, res)| (name.clone(), res.clone())).collect() 151 self.visible.iter().map(|(name, res)| (name.clone(), res.clone()))
149 } 152 }
150 153
151 pub(crate) fn collect_legacy_macros(&self) -> FxHashMap<Name, MacroDefId> { 154 pub(crate) fn collect_legacy_macros(&self) -> FxHashMap<Name, MacroDefId> {
@@ -153,20 +156,20 @@ impl ItemScope {
153 } 156 }
154} 157}
155 158
156impl From<ModuleDefId> for PerNs { 159impl PerNs {
157 fn from(def: ModuleDefId) -> PerNs { 160 pub(crate) fn from_def(def: ModuleDefId, v: Visibility) -> PerNs {
158 match def { 161 match def {
159 ModuleDefId::ModuleId(_) => PerNs::types(def), 162 ModuleDefId::ModuleId(_) => PerNs::types(def, v),
160 ModuleDefId::FunctionId(_) => PerNs::values(def), 163 ModuleDefId::FunctionId(_) => PerNs::values(def, v),
161 ModuleDefId::AdtId(adt) => match adt { 164 ModuleDefId::AdtId(adt) => match adt {
162 AdtId::StructId(_) | AdtId::UnionId(_) => PerNs::both(def, def), 165 AdtId::StructId(_) | AdtId::UnionId(_) => PerNs::both(def, def, v),
163 AdtId::EnumId(_) => PerNs::types(def), 166 AdtId::EnumId(_) => PerNs::types(def, v),
164 }, 167 },
165 ModuleDefId::EnumVariantId(_) => PerNs::both(def, def), 168 ModuleDefId::EnumVariantId(_) => PerNs::both(def, def, v),
166 ModuleDefId::ConstId(_) | ModuleDefId::StaticId(_) => PerNs::values(def), 169 ModuleDefId::ConstId(_) | ModuleDefId::StaticId(_) => PerNs::values(def, v),
167 ModuleDefId::TraitId(_) => PerNs::types(def), 170 ModuleDefId::TraitId(_) => PerNs::types(def, v),
168 ModuleDefId::TypeAliasId(_) => PerNs::types(def), 171 ModuleDefId::TypeAliasId(_) => PerNs::types(def, v),
169 ModuleDefId::BuiltinType(_) => PerNs::types(def), 172 ModuleDefId::BuiltinType(_) => PerNs::types(def, v),
170 } 173 }
171 } 174 }
172} 175}
diff --git a/crates/ra_hir_def/src/lib.rs b/crates/ra_hir_def/src/lib.rs
index f6c7f38d1..61f044ecf 100644
--- a/crates/ra_hir_def/src/lib.rs
+++ b/crates/ra_hir_def/src/lib.rs
@@ -36,6 +36,8 @@ pub mod nameres;
36pub mod src; 36pub mod src;
37pub mod child_by_source; 37pub mod child_by_source;
38 38
39pub mod visibility;
40
39#[cfg(test)] 41#[cfg(test)]
40mod test_db; 42mod test_db;
41#[cfg(test)] 43#[cfg(test)]
diff --git a/crates/ra_hir_def/src/nameres/collector.rs b/crates/ra_hir_def/src/nameres/collector.rs
index b9f40d3dd..8a22b0585 100644
--- a/crates/ra_hir_def/src/nameres/collector.rs
+++ b/crates/ra_hir_def/src/nameres/collector.rs
@@ -24,6 +24,7 @@ use crate::{
24 }, 24 },
25 path::{ModPath, PathKind}, 25 path::{ModPath, PathKind},
26 per_ns::PerNs, 26 per_ns::PerNs,
27 visibility::Visibility,
27 AdtId, AstId, ConstLoc, ContainerId, EnumLoc, EnumVariantId, FunctionLoc, ImplLoc, Intern, 28 AdtId, AstId, ConstLoc, ContainerId, EnumLoc, EnumVariantId, FunctionLoc, ImplLoc, Intern,
28 LocalModuleId, ModuleDefId, ModuleId, StaticLoc, StructLoc, TraitLoc, TypeAliasLoc, UnionLoc, 29 LocalModuleId, ModuleDefId, ModuleId, StaticLoc, StructLoc, TraitLoc, TypeAliasLoc, UnionLoc,
29}; 30};
@@ -108,7 +109,7 @@ struct MacroDirective {
108struct DefCollector<'a, DB> { 109struct DefCollector<'a, DB> {
109 db: &'a DB, 110 db: &'a DB,
110 def_map: CrateDefMap, 111 def_map: CrateDefMap,
111 glob_imports: FxHashMap<LocalModuleId, Vec<(LocalModuleId, raw::Import)>>, 112 glob_imports: FxHashMap<LocalModuleId, Vec<(LocalModuleId, Visibility)>>,
112 unresolved_imports: Vec<ImportDirective>, 113 unresolved_imports: Vec<ImportDirective>,
113 resolved_imports: Vec<ImportDirective>, 114 resolved_imports: Vec<ImportDirective>,
114 unexpanded_macros: Vec<MacroDirective>, 115 unexpanded_macros: Vec<MacroDirective>,
@@ -214,7 +215,11 @@ where
214 // In Rust, `#[macro_export]` macros are unconditionally visible at the 215 // In Rust, `#[macro_export]` macros are unconditionally visible at the
215 // crate root, even if the parent modules is **not** visible. 216 // crate root, even if the parent modules is **not** visible.
216 if export { 217 if export {
217 self.update(self.def_map.root, &[(name, PerNs::macros(macro_))]); 218 self.update(
219 self.def_map.root,
220 &[(name, PerNs::macros(macro_, Visibility::Public))],
221 Visibility::Public,
222 );
218 } 223 }
219 } 224 }
220 225
@@ -348,9 +353,12 @@ where
348 353
349 fn record_resolved_import(&mut self, directive: &ImportDirective) { 354 fn record_resolved_import(&mut self, directive: &ImportDirective) {
350 let module_id = directive.module_id; 355 let module_id = directive.module_id;
351 let import_id = directive.import_id;
352 let import = &directive.import; 356 let import = &directive.import;
353 let def = directive.status.namespaces(); 357 let def = directive.status.namespaces();
358 let vis = self
359 .def_map
360 .resolve_visibility(self.db, module_id, &directive.import.visibility)
361 .unwrap_or(Visibility::Public);
354 362
355 if import.is_glob { 363 if import.is_glob {
356 log::debug!("glob import: {:?}", import); 364 log::debug!("glob import: {:?}", import);
@@ -366,9 +374,16 @@ where
366 let scope = &item_map[m.local_id].scope; 374 let scope = &item_map[m.local_id].scope;
367 375
368 // Module scoped macros is included 376 // Module scoped macros is included
369 let items = scope.collect_resolutions(); 377 let items = scope
370 378 .resolutions()
371 self.update(module_id, &items); 379 // only keep visible names...
380 .map(|(n, res)| {
381 (n, res.filter_visibility(|v| v.is_visible_from_other_crate()))
382 })
383 .filter(|(_, res)| !res.is_none())
384 .collect::<Vec<_>>();
385
386 self.update(module_id, &items, vis);
372 } else { 387 } else {
373 // glob import from same crate => we do an initial 388 // glob import from same crate => we do an initial
374 // import, and then need to propagate any further 389 // import, and then need to propagate any further
@@ -376,13 +391,25 @@ where
376 let scope = &self.def_map[m.local_id].scope; 391 let scope = &self.def_map[m.local_id].scope;
377 392
378 // Module scoped macros is included 393 // Module scoped macros is included
379 let items = scope.collect_resolutions(); 394 let items = scope
380 395 .resolutions()
381 self.update(module_id, &items); 396 // only keep visible names...
397 .map(|(n, res)| {
398 (
399 n,
400 res.filter_visibility(|v| {
401 v.is_visible_from_def_map(&self.def_map, module_id)
402 }),
403 )
404 })
405 .filter(|(_, res)| !res.is_none())
406 .collect::<Vec<_>>();
407
408 self.update(module_id, &items, vis);
382 // record the glob import in case we add further items 409 // record the glob import in case we add further items
383 let glob = self.glob_imports.entry(m.local_id).or_default(); 410 let glob = self.glob_imports.entry(m.local_id).or_default();
384 if !glob.iter().any(|it| *it == (module_id, import_id)) { 411 if !glob.iter().any(|(mid, _)| *mid == module_id) {
385 glob.push((module_id, import_id)); 412 glob.push((module_id, vis));
386 } 413 }
387 } 414 }
388 } 415 }
@@ -396,11 +423,11 @@ where
396 .map(|(local_id, variant_data)| { 423 .map(|(local_id, variant_data)| {
397 let name = variant_data.name.clone(); 424 let name = variant_data.name.clone();
398 let variant = EnumVariantId { parent: e, local_id }; 425 let variant = EnumVariantId { parent: e, local_id };
399 let res = PerNs::both(variant.into(), variant.into()); 426 let res = PerNs::both(variant.into(), variant.into(), vis);
400 (name, res) 427 (name, res)
401 }) 428 })
402 .collect::<Vec<_>>(); 429 .collect::<Vec<_>>();
403 self.update(module_id, &resolutions); 430 self.update(module_id, &resolutions, vis);
404 } 431 }
405 Some(d) => { 432 Some(d) => {
406 log::debug!("glob import {:?} from non-module/enum {:?}", import, d); 433 log::debug!("glob import {:?} from non-module/enum {:?}", import, d);
@@ -422,21 +449,24 @@ where
422 } 449 }
423 } 450 }
424 451
425 self.update(module_id, &[(name, def)]); 452 self.update(module_id, &[(name, def)], vis);
426 } 453 }
427 None => tested_by!(bogus_paths), 454 None => tested_by!(bogus_paths),
428 } 455 }
429 } 456 }
430 } 457 }
431 458
432 fn update(&mut self, module_id: LocalModuleId, resolutions: &[(Name, PerNs)]) { 459 fn update(&mut self, module_id: LocalModuleId, resolutions: &[(Name, PerNs)], vis: Visibility) {
433 self.update_recursive(module_id, resolutions, 0) 460 self.update_recursive(module_id, resolutions, vis, 0)
434 } 461 }
435 462
436 fn update_recursive( 463 fn update_recursive(
437 &mut self, 464 &mut self,
438 module_id: LocalModuleId, 465 module_id: LocalModuleId,
439 resolutions: &[(Name, PerNs)], 466 resolutions: &[(Name, PerNs)],
467 // All resolutions are imported with this visibility; the visibilies in
468 // the `PerNs` values are ignored and overwritten
469 vis: Visibility,
440 depth: usize, 470 depth: usize,
441 ) { 471 ) {
442 if depth > 100 { 472 if depth > 100 {
@@ -446,7 +476,7 @@ where
446 let scope = &mut self.def_map.modules[module_id].scope; 476 let scope = &mut self.def_map.modules[module_id].scope;
447 let mut changed = false; 477 let mut changed = false;
448 for (name, res) in resolutions { 478 for (name, res) in resolutions {
449 changed |= scope.push_res(name.clone(), *res); 479 changed |= scope.push_res(name.clone(), res.with_visibility(vis));
450 } 480 }
451 481
452 if !changed { 482 if !changed {
@@ -459,9 +489,13 @@ where
459 .flat_map(|v| v.iter()) 489 .flat_map(|v| v.iter())
460 .cloned() 490 .cloned()
461 .collect::<Vec<_>>(); 491 .collect::<Vec<_>>();
462 for (glob_importing_module, _glob_import) in glob_imports { 492 for (glob_importing_module, glob_import_vis) in glob_imports {
463 // We pass the glob import so that the tracked import in those modules is that glob import 493 // we know all resolutions have the same visibility (`vis`), so we
464 self.update_recursive(glob_importing_module, resolutions, depth + 1); 494 // just need to check that once
495 if !vis.is_visible_from_def_map(&self.def_map, glob_importing_module) {
496 continue;
497 }
498 self.update_recursive(glob_importing_module, resolutions, glob_import_vis, depth + 1);
465 } 499 }
466 } 500 }
467 501
@@ -633,9 +667,13 @@ where
633 let is_macro_use = attrs.by_key("macro_use").exists(); 667 let is_macro_use = attrs.by_key("macro_use").exists();
634 match module { 668 match module {
635 // inline module, just recurse 669 // inline module, just recurse
636 raw::ModuleData::Definition { name, items, ast_id } => { 670 raw::ModuleData::Definition { name, visibility, items, ast_id } => {
637 let module_id = 671 let module_id = self.push_child_module(
638 self.push_child_module(name.clone(), AstId::new(self.file_id, *ast_id), None); 672 name.clone(),
673 AstId::new(self.file_id, *ast_id),
674 None,
675 &visibility,
676 );
639 677
640 ModCollector { 678 ModCollector {
641 def_collector: &mut *self.def_collector, 679 def_collector: &mut *self.def_collector,
@@ -650,7 +688,7 @@ where
650 } 688 }
651 } 689 }
652 // out of line module, resolve, parse and recurse 690 // out of line module, resolve, parse and recurse
653 raw::ModuleData::Declaration { name, ast_id } => { 691 raw::ModuleData::Declaration { name, visibility, ast_id } => {
654 let ast_id = AstId::new(self.file_id, *ast_id); 692 let ast_id = AstId::new(self.file_id, *ast_id);
655 match self.mod_dir.resolve_declaration( 693 match self.mod_dir.resolve_declaration(
656 self.def_collector.db, 694 self.def_collector.db,
@@ -659,7 +697,12 @@ where
659 path_attr, 697 path_attr,
660 ) { 698 ) {
661 Ok((file_id, mod_dir)) => { 699 Ok((file_id, mod_dir)) => {
662 let module_id = self.push_child_module(name.clone(), ast_id, Some(file_id)); 700 let module_id = self.push_child_module(
701 name.clone(),
702 ast_id,
703 Some(file_id),
704 &visibility,
705 );
663 let raw_items = self.def_collector.db.raw_items(file_id.into()); 706 let raw_items = self.def_collector.db.raw_items(file_id.into());
664 ModCollector { 707 ModCollector {
665 def_collector: &mut *self.def_collector, 708 def_collector: &mut *self.def_collector,
@@ -690,7 +733,13 @@ where
690 name: Name, 733 name: Name,
691 declaration: AstId<ast::Module>, 734 declaration: AstId<ast::Module>,
692 definition: Option<FileId>, 735 definition: Option<FileId>,
736 visibility: &crate::visibility::RawVisibility,
693 ) -> LocalModuleId { 737 ) -> LocalModuleId {
738 let vis = self
739 .def_collector
740 .def_map
741 .resolve_visibility(self.def_collector.db, self.module_id, visibility)
742 .unwrap_or(Visibility::Public);
694 let modules = &mut self.def_collector.def_map.modules; 743 let modules = &mut self.def_collector.def_map.modules;
695 let res = modules.alloc(ModuleData::default()); 744 let res = modules.alloc(ModuleData::default());
696 modules[res].parent = Some(self.module_id); 745 modules[res].parent = Some(self.module_id);
@@ -702,7 +751,7 @@ where
702 let module = ModuleId { krate: self.def_collector.def_map.krate, local_id: res }; 751 let module = ModuleId { krate: self.def_collector.def_map.krate, local_id: res };
703 let def: ModuleDefId = module.into(); 752 let def: ModuleDefId = module.into();
704 self.def_collector.def_map.modules[self.module_id].scope.define_def(def); 753 self.def_collector.def_map.modules[self.module_id].scope.define_def(def);
705 self.def_collector.update(self.module_id, &[(name, def.into())]); 754 self.def_collector.update(self.module_id, &[(name, PerNs::from_def(def, vis))], vis);
706 res 755 res
707 } 756 }
708 757
@@ -716,6 +765,7 @@ where
716 765
717 let name = def.name.clone(); 766 let name = def.name.clone();
718 let container = ContainerId::ModuleId(module); 767 let container = ContainerId::ModuleId(module);
768 let vis = &def.visibility;
719 let def: ModuleDefId = match def.kind { 769 let def: ModuleDefId = match def.kind {
720 raw::DefKind::Function(ast_id) => FunctionLoc { 770 raw::DefKind::Function(ast_id) => FunctionLoc {
721 container: container.into(), 771 container: container.into(),
@@ -761,7 +811,12 @@ where
761 .into(), 811 .into(),
762 }; 812 };
763 self.def_collector.def_map.modules[self.module_id].scope.define_def(def); 813 self.def_collector.def_map.modules[self.module_id].scope.define_def(def);
764 self.def_collector.update(self.module_id, &[(name, def.into())]) 814 let vis = self
815 .def_collector
816 .def_map
817 .resolve_visibility(self.def_collector.db, self.module_id, vis)
818 .unwrap_or(Visibility::Public);
819 self.def_collector.update(self.module_id, &[(name, PerNs::from_def(def, vis))], vis)
765 } 820 }
766 821
767 fn collect_derives(&mut self, attrs: &Attrs, def: &raw::DefData) { 822 fn collect_derives(&mut self, attrs: &Attrs, def: &raw::DefData) {
diff --git a/crates/ra_hir_def/src/nameres/path_resolution.rs b/crates/ra_hir_def/src/nameres/path_resolution.rs
index 695014c7b..fd6422d60 100644
--- a/crates/ra_hir_def/src/nameres/path_resolution.rs
+++ b/crates/ra_hir_def/src/nameres/path_resolution.rs
@@ -21,6 +21,7 @@ use crate::{
21 nameres::{BuiltinShadowMode, CrateDefMap}, 21 nameres::{BuiltinShadowMode, CrateDefMap},
22 path::{ModPath, PathKind}, 22 path::{ModPath, PathKind},
23 per_ns::PerNs, 23 per_ns::PerNs,
24 visibility::{RawVisibility, Visibility},
24 AdtId, CrateId, EnumVariantId, LocalModuleId, ModuleDefId, ModuleId, 25 AdtId, CrateId, EnumVariantId, LocalModuleId, ModuleDefId, ModuleId,
25}; 26};
26 27
@@ -61,7 +62,35 @@ impl ResolvePathResult {
61 62
62impl CrateDefMap { 63impl CrateDefMap {
63 pub(super) fn resolve_name_in_extern_prelude(&self, name: &Name) -> PerNs { 64 pub(super) fn resolve_name_in_extern_prelude(&self, name: &Name) -> PerNs {
64 self.extern_prelude.get(name).map_or(PerNs::none(), |&it| PerNs::types(it)) 65 self.extern_prelude
66 .get(name)
67 .map_or(PerNs::none(), |&it| PerNs::types(it, Visibility::Public))
68 }
69
70 pub(crate) fn resolve_visibility(
71 &self,
72 db: &impl DefDatabase,
73 original_module: LocalModuleId,
74 visibility: &RawVisibility,
75 ) -> Option<Visibility> {
76 match visibility {
77 RawVisibility::Module(path) => {
78 let (result, remaining) =
79 self.resolve_path(db, original_module, &path, BuiltinShadowMode::Module);
80 if remaining.is_some() {
81 return None;
82 }
83 let types = result.take_types()?;
84 match types {
85 ModuleDefId::ModuleId(m) => Some(Visibility::Module(m)),
86 _ => {
87 // error: visibility needs to refer to module
88 None
89 }
90 }
91 }
92 RawVisibility::Public => Some(Visibility::Public),
93 }
65 } 94 }
66 95
67 // Returns Yes if we are sure that additions to `ItemMap` wouldn't change 96 // Returns Yes if we are sure that additions to `ItemMap` wouldn't change
@@ -88,17 +117,21 @@ impl CrateDefMap {
88 PathKind::DollarCrate(krate) => { 117 PathKind::DollarCrate(krate) => {
89 if krate == self.krate { 118 if krate == self.krate {
90 tested_by!(macro_dollar_crate_self); 119 tested_by!(macro_dollar_crate_self);
91 PerNs::types(ModuleId { krate: self.krate, local_id: self.root }.into()) 120 PerNs::types(
121 ModuleId { krate: self.krate, local_id: self.root }.into(),
122 Visibility::Public,
123 )
92 } else { 124 } else {
93 let def_map = db.crate_def_map(krate); 125 let def_map = db.crate_def_map(krate);
94 let module = ModuleId { krate, local_id: def_map.root }; 126 let module = ModuleId { krate, local_id: def_map.root };
95 tested_by!(macro_dollar_crate_other); 127 tested_by!(macro_dollar_crate_other);
96 PerNs::types(module.into()) 128 PerNs::types(module.into(), Visibility::Public)
97 } 129 }
98 } 130 }
99 PathKind::Crate => { 131 PathKind::Crate => PerNs::types(
100 PerNs::types(ModuleId { krate: self.krate, local_id: self.root }.into()) 132 ModuleId { krate: self.krate, local_id: self.root }.into(),
101 } 133 Visibility::Public,
134 ),
102 // plain import or absolute path in 2015: crate-relative with 135 // plain import or absolute path in 2015: crate-relative with
103 // fallback to extern prelude (with the simplification in 136 // fallback to extern prelude (with the simplification in
104 // rust-lang/rust#57745) 137 // rust-lang/rust#57745)
@@ -126,7 +159,10 @@ impl CrateDefMap {
126 let m = successors(Some(original_module), |m| self.modules[*m].parent) 159 let m = successors(Some(original_module), |m| self.modules[*m].parent)
127 .nth(lvl as usize); 160 .nth(lvl as usize);
128 if let Some(local_id) = m { 161 if let Some(local_id) = m {
129 PerNs::types(ModuleId { krate: self.krate, local_id }.into()) 162 PerNs::types(
163 ModuleId { krate: self.krate, local_id }.into(),
164 Visibility::Public,
165 )
130 } else { 166 } else {
131 log::debug!("super path in root module"); 167 log::debug!("super path in root module");
132 return ResolvePathResult::empty(ReachedFixedPoint::Yes); 168 return ResolvePathResult::empty(ReachedFixedPoint::Yes);
@@ -140,7 +176,7 @@ impl CrateDefMap {
140 }; 176 };
141 if let Some(def) = self.extern_prelude.get(&segment) { 177 if let Some(def) = self.extern_prelude.get(&segment) {
142 log::debug!("absolute path {:?} resolved to crate {:?}", path, def); 178 log::debug!("absolute path {:?} resolved to crate {:?}", path, def);
143 PerNs::types(*def) 179 PerNs::types(*def, Visibility::Public)
144 } else { 180 } else {
145 return ResolvePathResult::empty(ReachedFixedPoint::No); // extern crate declarations can add to the extern prelude 181 return ResolvePathResult::empty(ReachedFixedPoint::No); // extern crate declarations can add to the extern prelude
146 } 182 }
@@ -148,7 +184,7 @@ impl CrateDefMap {
148 }; 184 };
149 185
150 for (i, segment) in segments { 186 for (i, segment) in segments {
151 let curr = match curr_per_ns.take_types() { 187 let (curr, vis) = match curr_per_ns.take_types_vis() {
152 Some(r) => r, 188 Some(r) => r,
153 None => { 189 None => {
154 // we still have path segments left, but the path so far 190 // we still have path segments left, but the path so far
@@ -189,11 +225,11 @@ impl CrateDefMap {
189 match enum_data.variant(&segment) { 225 match enum_data.variant(&segment) {
190 Some(local_id) => { 226 Some(local_id) => {
191 let variant = EnumVariantId { parent: e, local_id }; 227 let variant = EnumVariantId { parent: e, local_id };
192 PerNs::both(variant.into(), variant.into()) 228 PerNs::both(variant.into(), variant.into(), Visibility::Public)
193 } 229 }
194 None => { 230 None => {
195 return ResolvePathResult::with( 231 return ResolvePathResult::with(
196 PerNs::types(e.into()), 232 PerNs::types(e.into(), vis),
197 ReachedFixedPoint::Yes, 233 ReachedFixedPoint::Yes,
198 Some(i), 234 Some(i),
199 Some(self.krate), 235 Some(self.krate),
@@ -211,7 +247,7 @@ impl CrateDefMap {
211 ); 247 );
212 248
213 return ResolvePathResult::with( 249 return ResolvePathResult::with(
214 PerNs::types(s), 250 PerNs::types(s, vis),
215 ReachedFixedPoint::Yes, 251 ReachedFixedPoint::Yes,
216 Some(i), 252 Some(i),
217 Some(self.krate), 253 Some(self.krate),
@@ -235,11 +271,15 @@ impl CrateDefMap {
235 // - current module / scope 271 // - current module / scope
236 // - extern prelude 272 // - extern prelude
237 // - std prelude 273 // - std prelude
238 let from_legacy_macro = 274 let from_legacy_macro = self[module]
239 self[module].scope.get_legacy_macro(name).map_or_else(PerNs::none, PerNs::macros); 275 .scope
276 .get_legacy_macro(name)
277 .map_or_else(PerNs::none, |m| PerNs::macros(m, Visibility::Public));
240 let from_scope = self[module].scope.get(name, shadow); 278 let from_scope = self[module].scope.get(name, shadow);
241 let from_extern_prelude = 279 let from_extern_prelude = self
242 self.extern_prelude.get(name).map_or(PerNs::none(), |&it| PerNs::types(it)); 280 .extern_prelude
281 .get(name)
282 .map_or(PerNs::none(), |&it| PerNs::types(it, Visibility::Public));
243 let from_prelude = self.resolve_in_prelude(db, name, shadow); 283 let from_prelude = self.resolve_in_prelude(db, name, shadow);
244 284
245 from_legacy_macro.or(from_scope).or(from_extern_prelude).or(from_prelude) 285 from_legacy_macro.or(from_scope).or(from_extern_prelude).or(from_prelude)
diff --git a/crates/ra_hir_def/src/nameres/raw.rs b/crates/ra_hir_def/src/nameres/raw.rs
index 73dc08745..fac1169ef 100644
--- a/crates/ra_hir_def/src/nameres/raw.rs
+++ b/crates/ra_hir_def/src/nameres/raw.rs
@@ -16,12 +16,15 @@ use hir_expand::{
16use ra_arena::{impl_arena_id, Arena, RawId}; 16use ra_arena::{impl_arena_id, Arena, RawId};
17use ra_prof::profile; 17use ra_prof::profile;
18use ra_syntax::{ 18use ra_syntax::{
19 ast::{self, AttrsOwner, NameOwner}, 19 ast::{self, AttrsOwner, NameOwner, VisibilityOwner},
20 AstNode, 20 AstNode,
21}; 21};
22use test_utils::tested_by; 22use test_utils::tested_by;
23 23
24use crate::{attr::Attrs, db::DefDatabase, path::ModPath, FileAstId, HirFileId, InFile}; 24use crate::{
25 attr::Attrs, db::DefDatabase, path::ModPath, visibility::RawVisibility, FileAstId, HirFileId,
26 InFile,
27};
25 28
26/// `RawItems` is a set of top-level items in a file (except for impls). 29/// `RawItems` is a set of top-level items in a file (except for impls).
27/// 30///
@@ -122,8 +125,17 @@ impl_arena_id!(Module);
122 125
123#[derive(Debug, PartialEq, Eq)] 126#[derive(Debug, PartialEq, Eq)]
124pub(super) enum ModuleData { 127pub(super) enum ModuleData {
125 Declaration { name: Name, ast_id: FileAstId<ast::Module> }, 128 Declaration {
126 Definition { name: Name, ast_id: FileAstId<ast::Module>, items: Vec<RawItem> }, 129 name: Name,
130 visibility: RawVisibility,
131 ast_id: FileAstId<ast::Module>,
132 },
133 Definition {
134 name: Name,
135 visibility: RawVisibility,
136 ast_id: FileAstId<ast::Module>,
137 items: Vec<RawItem>,
138 },
127} 139}
128 140
129#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 141#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
@@ -138,6 +150,7 @@ pub struct ImportData {
138 pub(super) is_prelude: bool, 150 pub(super) is_prelude: bool,
139 pub(super) is_extern_crate: bool, 151 pub(super) is_extern_crate: bool,
140 pub(super) is_macro_use: bool, 152 pub(super) is_macro_use: bool,
153 pub(super) visibility: RawVisibility,
141} 154}
142 155
143#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 156#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
@@ -148,6 +161,7 @@ impl_arena_id!(Def);
148pub(super) struct DefData { 161pub(super) struct DefData {
149 pub(super) name: Name, 162 pub(super) name: Name,
150 pub(super) kind: DefKind, 163 pub(super) kind: DefKind,
164 pub(super) visibility: RawVisibility,
151} 165}
152 166
153#[derive(Debug, PartialEq, Eq, Clone, Copy)] 167#[derive(Debug, PartialEq, Eq, Clone, Copy)]
@@ -218,6 +232,7 @@ impl RawItemsCollector {
218 232
219 fn add_item(&mut self, current_module: Option<Module>, item: ast::ModuleItem) { 233 fn add_item(&mut self, current_module: Option<Module>, item: ast::ModuleItem) {
220 let attrs = self.parse_attrs(&item); 234 let attrs = self.parse_attrs(&item);
235 let visibility = RawVisibility::from_ast_with_hygiene(item.visibility(), &self.hygiene);
221 let (kind, name) = match item { 236 let (kind, name) = match item {
222 ast::ModuleItem::Module(module) => { 237 ast::ModuleItem::Module(module) => {
223 self.add_module(current_module, module); 238 self.add_module(current_module, module);
@@ -266,7 +281,7 @@ impl RawItemsCollector {
266 }; 281 };
267 if let Some(name) = name { 282 if let Some(name) = name {
268 let name = name.as_name(); 283 let name = name.as_name();
269 let def = self.raw_items.defs.alloc(DefData { name, kind }); 284 let def = self.raw_items.defs.alloc(DefData { name, kind, visibility });
270 self.push_item(current_module, attrs, RawItemKind::Def(def)); 285 self.push_item(current_module, attrs, RawItemKind::Def(def));
271 } 286 }
272 } 287 }
@@ -277,10 +292,12 @@ impl RawItemsCollector {
277 None => return, 292 None => return,
278 }; 293 };
279 let attrs = self.parse_attrs(&module); 294 let attrs = self.parse_attrs(&module);
295 let visibility = RawVisibility::from_ast_with_hygiene(module.visibility(), &self.hygiene);
280 296
281 let ast_id = self.source_ast_id_map.ast_id(&module); 297 let ast_id = self.source_ast_id_map.ast_id(&module);
282 if module.has_semi() { 298 if module.has_semi() {
283 let item = self.raw_items.modules.alloc(ModuleData::Declaration { name, ast_id }); 299 let item =
300 self.raw_items.modules.alloc(ModuleData::Declaration { name, visibility, ast_id });
284 self.push_item(current_module, attrs, RawItemKind::Module(item)); 301 self.push_item(current_module, attrs, RawItemKind::Module(item));
285 return; 302 return;
286 } 303 }
@@ -288,6 +305,7 @@ impl RawItemsCollector {
288 if let Some(item_list) = module.item_list() { 305 if let Some(item_list) = module.item_list() {
289 let item = self.raw_items.modules.alloc(ModuleData::Definition { 306 let item = self.raw_items.modules.alloc(ModuleData::Definition {
290 name, 307 name,
308 visibility,
291 ast_id, 309 ast_id,
292 items: Vec::new(), 310 items: Vec::new(),
293 }); 311 });
@@ -302,6 +320,7 @@ impl RawItemsCollector {
302 // FIXME: cfg_attr 320 // FIXME: cfg_attr
303 let is_prelude = use_item.has_atom_attr("prelude_import"); 321 let is_prelude = use_item.has_atom_attr("prelude_import");
304 let attrs = self.parse_attrs(&use_item); 322 let attrs = self.parse_attrs(&use_item);
323 let visibility = RawVisibility::from_ast_with_hygiene(use_item.visibility(), &self.hygiene);
305 324
306 let mut buf = Vec::new(); 325 let mut buf = Vec::new();
307 ModPath::expand_use_item( 326 ModPath::expand_use_item(
@@ -315,6 +334,7 @@ impl RawItemsCollector {
315 is_prelude, 334 is_prelude,
316 is_extern_crate: false, 335 is_extern_crate: false,
317 is_macro_use: false, 336 is_macro_use: false,
337 visibility: visibility.clone(),
318 }; 338 };
319 buf.push(import_data); 339 buf.push(import_data);
320 }, 340 },
@@ -331,6 +351,8 @@ impl RawItemsCollector {
331 ) { 351 ) {
332 if let Some(name_ref) = extern_crate.name_ref() { 352 if let Some(name_ref) = extern_crate.name_ref() {
333 let path = ModPath::from_name_ref(&name_ref); 353 let path = ModPath::from_name_ref(&name_ref);
354 let visibility =
355 RawVisibility::from_ast_with_hygiene(extern_crate.visibility(), &self.hygiene);
334 let alias = extern_crate.alias().and_then(|a| a.name()).map(|it| it.as_name()); 356 let alias = extern_crate.alias().and_then(|a| a.name()).map(|it| it.as_name());
335 let attrs = self.parse_attrs(&extern_crate); 357 let attrs = self.parse_attrs(&extern_crate);
336 // FIXME: cfg_attr 358 // FIXME: cfg_attr
@@ -342,6 +364,7 @@ impl RawItemsCollector {
342 is_prelude: false, 364 is_prelude: false,
343 is_extern_crate: true, 365 is_extern_crate: true,
344 is_macro_use, 366 is_macro_use,
367 visibility,
345 }; 368 };
346 self.push_import(current_module, attrs, import_data); 369 self.push_import(current_module, attrs, import_data);
347 } 370 }
diff --git a/crates/ra_hir_def/src/nameres/tests.rs b/crates/ra_hir_def/src/nameres/tests.rs
index ff474b53b..78bcdc850 100644
--- a/crates/ra_hir_def/src/nameres/tests.rs
+++ b/crates/ra_hir_def/src/nameres/tests.rs
@@ -12,8 +12,8 @@ use test_utils::covers;
12 12
13use crate::{db::DefDatabase, nameres::*, test_db::TestDB, LocalModuleId}; 13use crate::{db::DefDatabase, nameres::*, test_db::TestDB, LocalModuleId};
14 14
15fn def_map(fixtute: &str) -> String { 15fn def_map(fixture: &str) -> String {
16 let dm = compute_crate_def_map(fixtute); 16 let dm = compute_crate_def_map(fixture);
17 render_crate_def_map(&dm) 17 render_crate_def_map(&dm)
18} 18}
19 19
@@ -32,7 +32,7 @@ fn render_crate_def_map(map: &CrateDefMap) -> String {
32 *buf += path; 32 *buf += path;
33 *buf += "\n"; 33 *buf += "\n";
34 34
35 let mut entries = map.modules[module].scope.collect_resolutions(); 35 let mut entries: Vec<_> = map.modules[module].scope.resolutions().collect();
36 entries.sort_by_key(|(name, _)| name.clone()); 36 entries.sort_by_key(|(name, _)| name.clone());
37 37
38 for (name, def) in entries { 38 for (name, def) in entries {
diff --git a/crates/ra_hir_def/src/nameres/tests/globs.rs b/crates/ra_hir_def/src/nameres/tests/globs.rs
index 5e24cb94d..71fa0abe8 100644
--- a/crates/ra_hir_def/src/nameres/tests/globs.rs
+++ b/crates/ra_hir_def/src/nameres/tests/globs.rs
@@ -74,6 +74,83 @@ fn glob_2() {
74} 74}
75 75
76#[test] 76#[test]
77fn glob_privacy_1() {
78 let map = def_map(
79 "
80 //- /lib.rs
81 mod foo;
82 use foo::*;
83
84 //- /foo/mod.rs
85 pub mod bar;
86 pub use self::bar::*;
87 struct PrivateStructFoo;
88
89 //- /foo/bar.rs
90 pub struct Baz;
91 struct PrivateStructBar;
92 pub use super::*;
93 ",
94 );
95 assert_snapshot!(map, @r###"
96 crate
97 Baz: t v
98 bar: t
99 foo: t
100
101 crate::foo
102 Baz: t v
103 PrivateStructFoo: t v
104 bar: t
105
106 crate::foo::bar
107 Baz: t v
108 PrivateStructBar: t v
109 PrivateStructFoo: t v
110 bar: t
111 "###
112 );
113}
114
115#[test]
116fn glob_privacy_2() {
117 let map = def_map(
118 "
119 //- /lib.rs
120 mod foo;
121 use foo::*;
122 use foo::bar::*;
123
124 //- /foo/mod.rs
125 mod bar;
126 fn Foo() {};
127 pub struct Foo {};
128
129 //- /foo/bar.rs
130 pub(super) struct PrivateBaz;
131 struct PrivateBar;
132 pub(crate) struct PubCrateStruct;
133 ",
134 );
135 assert_snapshot!(map, @r###"
136 crate
137 Foo: t
138 PubCrateStruct: t v
139 foo: t
140
141 crate::foo
142 Foo: t v
143 bar: t
144
145 crate::foo::bar
146 PrivateBar: t v
147 PrivateBaz: t v
148 PubCrateStruct: t v
149 "###
150 );
151}
152
153#[test]
77fn glob_across_crates() { 154fn glob_across_crates() {
78 covers!(glob_across_crates); 155 covers!(glob_across_crates);
79 let map = def_map( 156 let map = def_map(
@@ -93,6 +170,26 @@ fn glob_across_crates() {
93} 170}
94 171
95#[test] 172#[test]
173fn glob_privacy_across_crates() {
174 covers!(glob_across_crates);
175 let map = def_map(
176 "
177 //- /main.rs crate:main deps:test_crate
178 use test_crate::*;
179
180 //- /lib.rs crate:test_crate
181 pub struct Baz;
182 struct Foo;
183 ",
184 );
185 assert_snapshot!(map, @r###"
186 â‹®crate
187 â‹®Baz: t v
188 "###
189 );
190}
191
192#[test]
96fn glob_enum() { 193fn glob_enum() {
97 covers!(glob_enum); 194 covers!(glob_enum);
98 let map = def_map( 195 let map = def_map(
diff --git a/crates/ra_hir_def/src/nameres/tests/incremental.rs b/crates/ra_hir_def/src/nameres/tests/incremental.rs
index ef2e9435c..faeb7aa4d 100644
--- a/crates/ra_hir_def/src/nameres/tests/incremental.rs
+++ b/crates/ra_hir_def/src/nameres/tests/incremental.rs
@@ -116,7 +116,7 @@ fn typing_inside_a_macro_should_not_invalidate_def_map() {
116 let events = db.log_executed(|| { 116 let events = db.log_executed(|| {
117 let crate_def_map = db.crate_def_map(krate); 117 let crate_def_map = db.crate_def_map(krate);
118 let (_, module_data) = crate_def_map.modules.iter().last().unwrap(); 118 let (_, module_data) = crate_def_map.modules.iter().last().unwrap();
119 assert_eq!(module_data.scope.collect_resolutions().len(), 1); 119 assert_eq!(module_data.scope.resolutions().collect::<Vec<_>>().len(), 1);
120 }); 120 });
121 assert!(format!("{:?}", events).contains("crate_def_map"), "{:#?}", events) 121 assert!(format!("{:?}", events).contains("crate_def_map"), "{:#?}", events)
122 } 122 }
@@ -126,7 +126,7 @@ fn typing_inside_a_macro_should_not_invalidate_def_map() {
126 let events = db.log_executed(|| { 126 let events = db.log_executed(|| {
127 let crate_def_map = db.crate_def_map(krate); 127 let crate_def_map = db.crate_def_map(krate);
128 let (_, module_data) = crate_def_map.modules.iter().last().unwrap(); 128 let (_, module_data) = crate_def_map.modules.iter().last().unwrap();
129 assert_eq!(module_data.scope.collect_resolutions().len(), 1); 129 assert_eq!(module_data.scope.resolutions().collect::<Vec<_>>().len(), 1);
130 }); 130 });
131 assert!(!format!("{:?}", events).contains("crate_def_map"), "{:#?}", events) 131 assert!(!format!("{:?}", events).contains("crate_def_map"), "{:#?}", events)
132 } 132 }
diff --git a/crates/ra_hir_def/src/path.rs b/crates/ra_hir_def/src/path.rs
index 8e1294201..107d2d799 100644
--- a/crates/ra_hir_def/src/path.rs
+++ b/crates/ra_hir_def/src/path.rs
@@ -130,6 +130,14 @@ impl Path {
130 Path { type_anchor: None, mod_path: name_ref.as_name().into(), generic_args: vec![None] } 130 Path { type_anchor: None, mod_path: name_ref.as_name().into(), generic_args: vec![None] }
131 } 131 }
132 132
133 /// Converts a known mod path to `Path`.
134 pub(crate) fn from_known_path(
135 path: ModPath,
136 generic_args: Vec<Option<Arc<GenericArgs>>>,
137 ) -> Path {
138 Path { type_anchor: None, mod_path: path, generic_args }
139 }
140
133 pub fn kind(&self) -> &PathKind { 141 pub fn kind(&self) -> &PathKind {
134 &self.mod_path.kind 142 &self.mod_path.kind
135 } 143 }
diff --git a/crates/ra_hir_def/src/per_ns.rs b/crates/ra_hir_def/src/per_ns.rs
index 3a5105028..6e435c8c1 100644
--- a/crates/ra_hir_def/src/per_ns.rs
+++ b/crates/ra_hir_def/src/per_ns.rs
@@ -5,13 +5,13 @@
5 5
6use hir_expand::MacroDefId; 6use hir_expand::MacroDefId;
7 7
8use crate::ModuleDefId; 8use crate::{visibility::Visibility, ModuleDefId};
9 9
10#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] 10#[derive(Debug, Copy, Clone, PartialEq, Eq)]
11pub struct PerNs { 11pub struct PerNs {
12 pub types: Option<ModuleDefId>, 12 pub types: Option<(ModuleDefId, Visibility)>,
13 pub values: Option<ModuleDefId>, 13 pub values: Option<(ModuleDefId, Visibility)>,
14 pub macros: Option<MacroDefId>, 14 pub macros: Option<(MacroDefId, Visibility)>,
15} 15}
16 16
17impl Default for PerNs { 17impl Default for PerNs {
@@ -25,20 +25,20 @@ impl PerNs {
25 PerNs { types: None, values: None, macros: None } 25 PerNs { types: None, values: None, macros: None }
26 } 26 }
27 27
28 pub fn values(t: ModuleDefId) -> PerNs { 28 pub fn values(t: ModuleDefId, v: Visibility) -> PerNs {
29 PerNs { types: None, values: Some(t), macros: None } 29 PerNs { types: None, values: Some((t, v)), macros: None }
30 } 30 }
31 31
32 pub fn types(t: ModuleDefId) -> PerNs { 32 pub fn types(t: ModuleDefId, v: Visibility) -> PerNs {
33 PerNs { types: Some(t), values: None, macros: None } 33 PerNs { types: Some((t, v)), values: None, macros: None }
34 } 34 }
35 35
36 pub fn both(types: ModuleDefId, values: ModuleDefId) -> PerNs { 36 pub fn both(types: ModuleDefId, values: ModuleDefId, v: Visibility) -> PerNs {
37 PerNs { types: Some(types), values: Some(values), macros: None } 37 PerNs { types: Some((types, v)), values: Some((values, v)), macros: None }
38 } 38 }
39 39
40 pub fn macros(macro_: MacroDefId) -> PerNs { 40 pub fn macros(macro_: MacroDefId, v: Visibility) -> PerNs {
41 PerNs { types: None, values: None, macros: Some(macro_) } 41 PerNs { types: None, values: None, macros: Some((macro_, v)) }
42 } 42 }
43 43
44 pub fn is_none(&self) -> bool { 44 pub fn is_none(&self) -> bool {
@@ -46,15 +46,35 @@ impl PerNs {
46 } 46 }
47 47
48 pub fn take_types(self) -> Option<ModuleDefId> { 48 pub fn take_types(self) -> Option<ModuleDefId> {
49 self.types.map(|it| it.0)
50 }
51
52 pub fn take_types_vis(self) -> Option<(ModuleDefId, Visibility)> {
49 self.types 53 self.types
50 } 54 }
51 55
52 pub fn take_values(self) -> Option<ModuleDefId> { 56 pub fn take_values(self) -> Option<ModuleDefId> {
53 self.values 57 self.values.map(|it| it.0)
54 } 58 }
55 59
56 pub fn take_macros(self) -> Option<MacroDefId> { 60 pub fn take_macros(self) -> Option<MacroDefId> {
57 self.macros 61 self.macros.map(|it| it.0)
62 }
63
64 pub fn filter_visibility(self, mut f: impl FnMut(Visibility) -> bool) -> PerNs {
65 PerNs {
66 types: self.types.filter(|(_, v)| f(*v)),
67 values: self.values.filter(|(_, v)| f(*v)),
68 macros: self.macros.filter(|(_, v)| f(*v)),
69 }
70 }
71
72 pub fn with_visibility(self, vis: Visibility) -> PerNs {
73 PerNs {
74 types: self.types.map(|(it, _)| (it, vis)),
75 values: self.values.map(|(it, _)| (it, vis)),
76 macros: self.macros.map(|(it, _)| (it, vis)),
77 }
58 } 78 }
59 79
60 pub fn or(self, other: PerNs) -> PerNs { 80 pub fn or(self, other: PerNs) -> PerNs {
diff --git a/crates/ra_hir_def/src/resolver.rs b/crates/ra_hir_def/src/resolver.rs
index cf3c33d78..5d16dd087 100644
--- a/crates/ra_hir_def/src/resolver.rs
+++ b/crates/ra_hir_def/src/resolver.rs
@@ -19,6 +19,7 @@ use crate::{
19 nameres::CrateDefMap, 19 nameres::CrateDefMap,
20 path::{ModPath, PathKind}, 20 path::{ModPath, PathKind},
21 per_ns::PerNs, 21 per_ns::PerNs,
22 visibility::{RawVisibility, Visibility},
22 AdtId, AssocContainerId, ConstId, ContainerId, DefWithBodyId, EnumId, EnumVariantId, 23 AdtId, AssocContainerId, ConstId, ContainerId, DefWithBodyId, EnumId, EnumVariantId,
23 FunctionId, GenericDefId, HasModule, ImplId, LocalModuleId, Lookup, ModuleDefId, ModuleId, 24 FunctionId, GenericDefId, HasModule, ImplId, LocalModuleId, Lookup, ModuleDefId, ModuleId,
24 StaticId, StructId, TraitId, TypeAliasId, TypeParamId, VariantId, 25 StaticId, StructId, TraitId, TypeAliasId, TypeParamId, VariantId,
@@ -231,6 +232,23 @@ impl Resolver {
231 Some(res) 232 Some(res)
232 } 233 }
233 234
235 pub fn resolve_visibility(
236 &self,
237 db: &impl DefDatabase,
238 visibility: &RawVisibility,
239 ) -> Option<Visibility> {
240 match visibility {
241 RawVisibility::Module(_) => {
242 let (item_map, module) = match self.module() {
243 Some(it) => it,
244 None => return None,
245 };
246 item_map.resolve_visibility(db, module, visibility)
247 }
248 RawVisibility::Public => Some(Visibility::Public),
249 }
250 }
251
234 pub fn resolve_path_in_value_ns( 252 pub fn resolve_path_in_value_ns(
235 &self, 253 &self,
236 db: &impl DefDatabase, 254 db: &impl DefDatabase,
@@ -448,10 +466,10 @@ impl Scope {
448 f(name.clone(), ScopeDef::PerNs(def)); 466 f(name.clone(), ScopeDef::PerNs(def));
449 }); 467 });
450 m.crate_def_map[m.module_id].scope.legacy_macros().for_each(|(name, macro_)| { 468 m.crate_def_map[m.module_id].scope.legacy_macros().for_each(|(name, macro_)| {
451 f(name.clone(), ScopeDef::PerNs(PerNs::macros(macro_))); 469 f(name.clone(), ScopeDef::PerNs(PerNs::macros(macro_, Visibility::Public)));
452 }); 470 });
453 m.crate_def_map.extern_prelude.iter().for_each(|(name, &def)| { 471 m.crate_def_map.extern_prelude.iter().for_each(|(name, &def)| {
454 f(name.clone(), ScopeDef::PerNs(PerNs::types(def.into()))); 472 f(name.clone(), ScopeDef::PerNs(PerNs::types(def.into(), Visibility::Public)));
455 }); 473 });
456 if let Some(prelude) = m.crate_def_map.prelude { 474 if let Some(prelude) = m.crate_def_map.prelude {
457 let prelude_def_map = db.crate_def_map(prelude.krate); 475 let prelude_def_map = db.crate_def_map(prelude.krate);
@@ -626,7 +644,7 @@ impl HasResolver for ContainerId {
626 fn resolver(self, db: &impl DefDatabase) -> Resolver { 644 fn resolver(self, db: &impl DefDatabase) -> Resolver {
627 match self { 645 match self {
628 ContainerId::ModuleId(it) => it.resolver(db), 646 ContainerId::ModuleId(it) => it.resolver(db),
629 ContainerId::DefWithBodyId(it) => it.resolver(db), 647 ContainerId::DefWithBodyId(it) => it.module(db).resolver(db),
630 } 648 }
631 } 649 }
632} 650}
diff --git a/crates/ra_hir_def/src/visibility.rs b/crates/ra_hir_def/src/visibility.rs
new file mode 100644
index 000000000..d8296da4b
--- /dev/null
+++ b/crates/ra_hir_def/src/visibility.rs
@@ -0,0 +1,120 @@
1//! Defines hir-level representation of visibility (e.g. `pub` and `pub(crate)`).
2
3use hir_expand::{hygiene::Hygiene, InFile};
4use ra_syntax::ast;
5
6use crate::{
7 db::DefDatabase,
8 path::{ModPath, PathKind},
9 ModuleId,
10};
11
12/// Visibility of an item, not yet resolved.
13#[derive(Debug, Clone, PartialEq, Eq)]
14pub enum RawVisibility {
15 /// `pub(in module)`, `pub(crate)` or `pub(super)`. Also private, which is
16 /// equivalent to `pub(self)`.
17 Module(ModPath),
18 /// `pub`.
19 Public,
20}
21
22impl RawVisibility {
23 const fn private() -> RawVisibility {
24 let path = ModPath { kind: PathKind::Super(0), segments: Vec::new() };
25 RawVisibility::Module(path)
26 }
27
28 pub(crate) fn from_ast(
29 db: &impl DefDatabase,
30 node: InFile<Option<ast::Visibility>>,
31 ) -> RawVisibility {
32 Self::from_ast_with_hygiene(node.value, &Hygiene::new(db, node.file_id))
33 }
34
35 pub(crate) fn from_ast_with_hygiene(
36 node: Option<ast::Visibility>,
37 hygiene: &Hygiene,
38 ) -> RawVisibility {
39 let node = match node {
40 None => return RawVisibility::private(),
41 Some(node) => node,
42 };
43 match node.kind() {
44 ast::VisibilityKind::In(path) => {
45 let path = ModPath::from_src(path, hygiene);
46 let path = match path {
47 None => return RawVisibility::private(),
48 Some(path) => path,
49 };
50 RawVisibility::Module(path)
51 }
52 ast::VisibilityKind::PubCrate => {
53 let path = ModPath { kind: PathKind::Crate, segments: Vec::new() };
54 RawVisibility::Module(path)
55 }
56 ast::VisibilityKind::PubSuper => {
57 let path = ModPath { kind: PathKind::Super(1), segments: Vec::new() };
58 RawVisibility::Module(path)
59 }
60 ast::VisibilityKind::Pub => RawVisibility::Public,
61 }
62 }
63
64 pub fn resolve(
65 &self,
66 db: &impl DefDatabase,
67 resolver: &crate::resolver::Resolver,
68 ) -> Visibility {
69 // we fall back to public visibility (i.e. fail open) if the path can't be resolved
70 resolver.resolve_visibility(db, self).unwrap_or(Visibility::Public)
71 }
72}
73
74/// Visibility of an item, with the path resolved.
75#[derive(Debug, Copy, Clone, PartialEq, Eq)]
76pub enum Visibility {
77 /// Visibility is restricted to a certain module.
78 Module(ModuleId),
79 /// Visibility is unrestricted.
80 Public,
81}
82
83impl Visibility {
84 pub fn is_visible_from(self, db: &impl DefDatabase, from_module: ModuleId) -> bool {
85 let to_module = match self {
86 Visibility::Module(m) => m,
87 Visibility::Public => return true,
88 };
89 // if they're not in the same crate, it can't be visible
90 if from_module.krate != to_module.krate {
91 return false;
92 }
93 let def_map = db.crate_def_map(from_module.krate);
94 self.is_visible_from_def_map(&def_map, from_module.local_id)
95 }
96
97 pub(crate) fn is_visible_from_other_crate(self) -> bool {
98 match self {
99 Visibility::Module(_) => false,
100 Visibility::Public => true,
101 }
102 }
103
104 pub(crate) fn is_visible_from_def_map(
105 self,
106 def_map: &crate::nameres::CrateDefMap,
107 from_module: crate::LocalModuleId,
108 ) -> bool {
109 let to_module = match self {
110 Visibility::Module(m) => m,
111 Visibility::Public => return true,
112 };
113 // from_module needs to be a descendant of to_module
114 let mut ancestors = std::iter::successors(Some(from_module), |m| {
115 let parent_id = def_map[*m].parent?;
116 Some(parent_id)
117 });
118 ancestors.any(|m| m == to_module.local_id)
119 }
120}
diff --git a/crates/ra_hir_ty/src/display.rs b/crates/ra_hir_ty/src/display.rs
index dcca1bace..37def7c03 100644
--- a/crates/ra_hir_ty/src/display.rs
+++ b/crates/ra_hir_ty/src/display.rs
@@ -10,7 +10,7 @@ pub struct HirFormatter<'a, 'b, DB> {
10 buf: String, 10 buf: String,
11 curr_size: usize, 11 curr_size: usize,
12 max_size: Option<usize>, 12 max_size: Option<usize>,
13 should_display_default_types: bool, 13 omit_verbose_types: bool,
14} 14}
15 15
16pub trait HirDisplay { 16pub trait HirDisplay {
@@ -20,7 +20,7 @@ pub trait HirDisplay {
20 where 20 where
21 Self: Sized, 21 Self: Sized,
22 { 22 {
23 HirDisplayWrapper(db, self, None, true) 23 HirDisplayWrapper(db, self, None, false)
24 } 24 }
25 25
26 fn display_truncated<'a, DB>( 26 fn display_truncated<'a, DB>(
@@ -31,7 +31,7 @@ pub trait HirDisplay {
31 where 31 where
32 Self: Sized, 32 Self: Sized,
33 { 33 {
34 HirDisplayWrapper(db, self, max_size, false) 34 HirDisplayWrapper(db, self, max_size, true)
35 } 35 }
36} 36}
37 37
@@ -74,8 +74,8 @@ where
74 } 74 }
75 } 75 }
76 76
77 pub fn should_display_default_types(&self) -> bool { 77 pub fn omit_verbose_types(&self) -> bool {
78 self.should_display_default_types 78 self.omit_verbose_types
79 } 79 }
80} 80}
81 81
@@ -93,7 +93,7 @@ where
93 buf: String::with_capacity(20), 93 buf: String::with_capacity(20),
94 curr_size: 0, 94 curr_size: 0,
95 max_size: self.2, 95 max_size: self.2,
96 should_display_default_types: self.3, 96 omit_verbose_types: self.3,
97 }) 97 })
98 } 98 }
99} 99}
diff --git a/crates/ra_hir_ty/src/infer.rs b/crates/ra_hir_ty/src/infer.rs
index e97b81473..32c0d07a5 100644
--- a/crates/ra_hir_ty/src/infer.rs
+++ b/crates/ra_hir_ty/src/infer.rs
@@ -37,8 +37,8 @@ use test_utils::tested_by;
37use super::{ 37use super::{
38 primitive::{FloatTy, IntTy}, 38 primitive::{FloatTy, IntTy},
39 traits::{Guidance, Obligation, ProjectionPredicate, Solution}, 39 traits::{Guidance, Obligation, ProjectionPredicate, Solution},
40 ApplicationTy, InEnvironment, ProjectionTy, Substs, TraitEnvironment, TraitRef, Ty, TypeCtor, 40 ApplicationTy, GenericPredicate, InEnvironment, ProjectionTy, Substs, TraitEnvironment,
41 TypeWalk, Uncertain, 41 TraitRef, Ty, TypeCtor, TypeWalk, Uncertain,
42}; 42};
43use crate::{db::HirDatabase, infer::diagnostics::InferenceDiagnostic}; 43use crate::{db::HirDatabase, infer::diagnostics::InferenceDiagnostic};
44 44
@@ -379,6 +379,25 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> {
379 ) -> Ty { 379 ) -> Ty {
380 match assoc_ty { 380 match assoc_ty {
381 Some(res_assoc_ty) => { 381 Some(res_assoc_ty) => {
382 // FIXME:
383 // Check if inner_ty is is `impl Trait` and contained input TypeAlias id
384 // this is a workaround while Chalk assoc type projection doesn't always work yet,
385 // but once that is fixed I don't think we should keep this
386 // (we'll probably change how associated types are resolved anyway)
387 if let Ty::Opaque(ref predicates) = inner_ty {
388 for p in predicates.iter() {
389 if let GenericPredicate::Projection(projection) = p {
390 if projection.projection_ty.associated_ty == res_assoc_ty {
391 if let ty_app!(_, params) = &projection.ty {
392 if params.len() == 0 {
393 return projection.ty.clone();
394 }
395 }
396 }
397 }
398 }
399 }
400
382 let ty = self.table.new_type_var(); 401 let ty = self.table.new_type_var();
383 let builder = Substs::build_for_def(self.db, res_assoc_ty) 402 let builder = Substs::build_for_def(self.db, res_assoc_ty)
384 .push(inner_ty) 403 .push(inner_ty)
diff --git a/crates/ra_hir_ty/src/lib.rs b/crates/ra_hir_ty/src/lib.rs
index 48abf97c9..55b6dd836 100644
--- a/crates/ra_hir_ty/src/lib.rs
+++ b/crates/ra_hir_ty/src/lib.rs
@@ -821,6 +821,8 @@ impl TypeWalk for Ty {
821 } 821 }
822} 822}
823 823
824const TYPE_HINT_TRUNCATION: &str = "…";
825
824impl HirDisplay for &Ty { 826impl HirDisplay for &Ty {
825 fn hir_fmt(&self, f: &mut HirFormatter<impl HirDatabase>) -> fmt::Result { 827 fn hir_fmt(&self, f: &mut HirFormatter<impl HirDatabase>) -> fmt::Result {
826 HirDisplay::hir_fmt(*self, f) 828 HirDisplay::hir_fmt(*self, f)
@@ -830,7 +832,7 @@ impl HirDisplay for &Ty {
830impl HirDisplay for ApplicationTy { 832impl HirDisplay for ApplicationTy {
831 fn hir_fmt(&self, f: &mut HirFormatter<impl HirDatabase>) -> fmt::Result { 833 fn hir_fmt(&self, f: &mut HirFormatter<impl HirDatabase>) -> fmt::Result {
832 if f.should_truncate() { 834 if f.should_truncate() {
833 return write!(f, "…"); 835 return write!(f, "{}", TYPE_HINT_TRUNCATION);
834 } 836 }
835 837
836 match self.ctor { 838 match self.ctor {
@@ -908,9 +910,7 @@ impl HirDisplay for ApplicationTy {
908 write!(f, "<")?; 910 write!(f, "<")?;
909 911
910 let mut non_default_parameters = Vec::with_capacity(self.parameters.len()); 912 let mut non_default_parameters = Vec::with_capacity(self.parameters.len());
911 let parameters_to_write = if f.should_display_default_types() { 913 let parameters_to_write = if f.omit_verbose_types() {
912 self.parameters.0.as_ref()
913 } else {
914 match self 914 match self
915 .ctor 915 .ctor
916 .as_generic_def() 916 .as_generic_def()
@@ -935,6 +935,8 @@ impl HirDisplay for ApplicationTy {
935 &non_default_parameters 935 &non_default_parameters
936 } 936 }
937 } 937 }
938 } else {
939 self.parameters.0.as_ref()
938 }; 940 };
939 941
940 f.write_joined(parameters_to_write, ", ")?; 942 f.write_joined(parameters_to_write, ", ")?;
@@ -959,9 +961,16 @@ impl HirDisplay for ApplicationTy {
959 let sig = self.parameters[0] 961 let sig = self.parameters[0]
960 .callable_sig(f.db) 962 .callable_sig(f.db)
961 .expect("first closure parameter should contain signature"); 963 .expect("first closure parameter should contain signature");
962 write!(f, "|")?; 964 let return_type_hint = sig.ret().display(f.db);
963 f.write_joined(sig.params(), ", ")?; 965 if sig.params().is_empty() {
964 write!(f, "| -> {}", sig.ret().display(f.db))?; 966 write!(f, "|| -> {}", return_type_hint)?;
967 } else if f.omit_verbose_types() {
968 write!(f, "|{}| -> {}", TYPE_HINT_TRUNCATION, return_type_hint)?;
969 } else {
970 write!(f, "|")?;
971 f.write_joined(sig.params(), ", ")?;
972 write!(f, "| -> {}", return_type_hint)?;
973 };
965 } 974 }
966 } 975 }
967 Ok(()) 976 Ok(())
@@ -971,7 +980,7 @@ impl HirDisplay for ApplicationTy {
971impl HirDisplay for ProjectionTy { 980impl HirDisplay for ProjectionTy {
972 fn hir_fmt(&self, f: &mut HirFormatter<impl HirDatabase>) -> fmt::Result { 981 fn hir_fmt(&self, f: &mut HirFormatter<impl HirDatabase>) -> fmt::Result {
973 if f.should_truncate() { 982 if f.should_truncate() {
974 return write!(f, "…"); 983 return write!(f, "{}", TYPE_HINT_TRUNCATION);
975 } 984 }
976 985
977 let trait_name = f.db.trait_data(self.trait_(f.db)).name.clone(); 986 let trait_name = f.db.trait_data(self.trait_(f.db)).name.clone();
@@ -989,7 +998,7 @@ impl HirDisplay for ProjectionTy {
989impl HirDisplay for Ty { 998impl HirDisplay for Ty {
990 fn hir_fmt(&self, f: &mut HirFormatter<impl HirDatabase>) -> fmt::Result { 999 fn hir_fmt(&self, f: &mut HirFormatter<impl HirDatabase>) -> fmt::Result {
991 if f.should_truncate() { 1000 if f.should_truncate() {
992 return write!(f, "…"); 1001 return write!(f, "{}", TYPE_HINT_TRUNCATION);
993 } 1002 }
994 1003
995 match self { 1004 match self {
@@ -1074,7 +1083,7 @@ impl HirDisplay for Ty {
1074impl TraitRef { 1083impl TraitRef {
1075 fn hir_fmt_ext(&self, f: &mut HirFormatter<impl HirDatabase>, use_as: bool) -> fmt::Result { 1084 fn hir_fmt_ext(&self, f: &mut HirFormatter<impl HirDatabase>, use_as: bool) -> fmt::Result {
1076 if f.should_truncate() { 1085 if f.should_truncate() {
1077 return write!(f, "…"); 1086 return write!(f, "{}", TYPE_HINT_TRUNCATION);
1078 } 1087 }
1079 1088
1080 self.substs[0].hir_fmt(f)?; 1089 self.substs[0].hir_fmt(f)?;
@@ -1108,7 +1117,7 @@ impl HirDisplay for &GenericPredicate {
1108impl HirDisplay for GenericPredicate { 1117impl HirDisplay for GenericPredicate {
1109 fn hir_fmt(&self, f: &mut HirFormatter<impl HirDatabase>) -> fmt::Result { 1118 fn hir_fmt(&self, f: &mut HirFormatter<impl HirDatabase>) -> fmt::Result {
1110 if f.should_truncate() { 1119 if f.should_truncate() {
1111 return write!(f, "…"); 1120 return write!(f, "{}", TYPE_HINT_TRUNCATION);
1112 } 1121 }
1113 1122
1114 match self { 1123 match self {
diff --git a/crates/ra_hir_ty/src/tests.rs b/crates/ra_hir_ty/src/tests.rs
index d447b4571..d1f10e675 100644
--- a/crates/ra_hir_ty/src/tests.rs
+++ b/crates/ra_hir_ty/src/tests.rs
@@ -11,8 +11,8 @@ use std::fmt::Write;
11use std::sync::Arc; 11use std::sync::Arc;
12 12
13use hir_def::{ 13use hir_def::{
14 body::BodySourceMap, child_by_source::ChildBySource, db::DefDatabase, keys, 14 body::BodySourceMap, child_by_source::ChildBySource, db::DefDatabase, item_scope::ItemScope,
15 nameres::CrateDefMap, AssocItemId, DefWithBodyId, LocalModuleId, Lookup, ModuleDefId, 15 keys, nameres::CrateDefMap, AssocItemId, DefWithBodyId, LocalModuleId, Lookup, ModuleDefId,
16}; 16};
17use hir_expand::InFile; 17use hir_expand::InFile;
18use insta::assert_snapshot; 18use insta::assert_snapshot;
@@ -163,35 +163,69 @@ fn visit_module(
163 module_id: LocalModuleId, 163 module_id: LocalModuleId,
164 cb: &mut dyn FnMut(DefWithBodyId), 164 cb: &mut dyn FnMut(DefWithBodyId),
165) { 165) {
166 for decl in crate_def_map[module_id].scope.declarations() { 166 visit_scope(db, crate_def_map, &crate_def_map[module_id].scope, cb);
167 match decl {
168 ModuleDefId::FunctionId(it) => cb(it.into()),
169 ModuleDefId::ConstId(it) => cb(it.into()),
170 ModuleDefId::StaticId(it) => cb(it.into()),
171 ModuleDefId::TraitId(it) => {
172 let trait_data = db.trait_data(it);
173 for &(_, item) in trait_data.items.iter() {
174 match item {
175 AssocItemId::FunctionId(it) => cb(it.into()),
176 AssocItemId::ConstId(it) => cb(it.into()),
177 AssocItemId::TypeAliasId(_) => (),
178 }
179 }
180 }
181 ModuleDefId::ModuleId(it) => visit_module(db, crate_def_map, it.local_id, cb),
182 _ => (),
183 }
184 }
185 for impl_id in crate_def_map[module_id].scope.impls() { 167 for impl_id in crate_def_map[module_id].scope.impls() {
186 let impl_data = db.impl_data(impl_id); 168 let impl_data = db.impl_data(impl_id);
187 for &item in impl_data.items.iter() { 169 for &item in impl_data.items.iter() {
188 match item { 170 match item {
189 AssocItemId::FunctionId(it) => cb(it.into()), 171 AssocItemId::FunctionId(it) => {
190 AssocItemId::ConstId(it) => cb(it.into()), 172 let def = it.into();
173 cb(def);
174 let body = db.body(def);
175 visit_scope(db, crate_def_map, &body.item_scope, cb);
176 }
177 AssocItemId::ConstId(it) => {
178 let def = it.into();
179 cb(def);
180 let body = db.body(def);
181 visit_scope(db, crate_def_map, &body.item_scope, cb);
182 }
191 AssocItemId::TypeAliasId(_) => (), 183 AssocItemId::TypeAliasId(_) => (),
192 } 184 }
193 } 185 }
194 } 186 }
187
188 fn visit_scope(
189 db: &TestDB,
190 crate_def_map: &CrateDefMap,
191 scope: &ItemScope,
192 cb: &mut dyn FnMut(DefWithBodyId),
193 ) {
194 for decl in scope.declarations() {
195 match decl {
196 ModuleDefId::FunctionId(it) => {
197 let def = it.into();
198 cb(def);
199 let body = db.body(def);
200 visit_scope(db, crate_def_map, &body.item_scope, cb);
201 }
202 ModuleDefId::ConstId(it) => {
203 let def = it.into();
204 cb(def);
205 let body = db.body(def);
206 visit_scope(db, crate_def_map, &body.item_scope, cb);
207 }
208 ModuleDefId::StaticId(it) => {
209 let def = it.into();
210 cb(def);
211 let body = db.body(def);
212 visit_scope(db, crate_def_map, &body.item_scope, cb);
213 }
214 ModuleDefId::TraitId(it) => {
215 let trait_data = db.trait_data(it);
216 for &(_, item) in trait_data.items.iter() {
217 match item {
218 AssocItemId::FunctionId(it) => cb(it.into()),
219 AssocItemId::ConstId(it) => cb(it.into()),
220 AssocItemId::TypeAliasId(_) => (),
221 }
222 }
223 }
224 ModuleDefId::ModuleId(it) => visit_module(db, crate_def_map, it.local_id, cb),
225 _ => (),
226 }
227 }
228 }
195} 229}
196 230
197fn ellipsize(mut text: String, max_len: usize) -> String { 231fn ellipsize(mut text: String, max_len: usize) -> String {
diff --git a/crates/ra_hir_ty/src/tests/regression.rs b/crates/ra_hir_ty/src/tests/regression.rs
index 09d684ac2..8b3aa8564 100644
--- a/crates/ra_hir_ty/src/tests/regression.rs
+++ b/crates/ra_hir_ty/src/tests/regression.rs
@@ -1,7 +1,8 @@
1use super::infer;
2use insta::assert_snapshot; 1use insta::assert_snapshot;
3use test_utils::covers; 2use test_utils::covers;
4 3
4use super::infer;
5
5#[test] 6#[test]
6fn bug_484() { 7fn bug_484() {
7 assert_snapshot!( 8 assert_snapshot!(
@@ -331,3 +332,36 @@ pub fn main_loop() {
331 "### 332 "###
332 ); 333 );
333} 334}
335
336#[test]
337fn issue_2669() {
338 assert_snapshot!(
339 infer(
340 r#"trait A {}
341 trait Write {}
342 struct Response<T> {}
343
344 trait D {
345 fn foo();
346 }
347
348 impl<T:A> D for Response<T> {
349 fn foo() {
350 end();
351 fn end<W: Write>() {
352 let _x: T = loop {};
353 }
354 }
355 }"#
356 ),
357 @r###"
358 [147; 262) '{ ... }': ()
359 [161; 164) 'end': fn end<{unknown}>() -> ()
360 [161; 166) 'end()': ()
361 [199; 252) '{ ... }': ()
362 [221; 223) '_x': !
363 [230; 237) 'loop {}': !
364 [235; 237) '{}': ()
365 "###
366 )
367}
diff --git a/crates/ra_hir_ty/src/tests/simple.rs b/crates/ra_hir_ty/src/tests/simple.rs
index 3e5e163e3..00134c99b 100644
--- a/crates/ra_hir_ty/src/tests/simple.rs
+++ b/crates/ra_hir_ty/src/tests/simple.rs
@@ -1518,6 +1518,7 @@ fn test() {
1518 [167; 179) 'GLOBAL_CONST': u32 1518 [167; 179) 'GLOBAL_CONST': u32
1519 [189; 191) 'id': u32 1519 [189; 191) 'id': u32
1520 [194; 210) 'Foo::A..._CONST': u32 1520 [194; 210) 'Foo::A..._CONST': u32
1521 [126; 128) '99': u32
1521 "### 1522 "###
1522 ); 1523 );
1523} 1524}
@@ -1549,6 +1550,8 @@ fn test() {
1549 [233; 246) 'GLOBAL_STATIC': u32 1550 [233; 246) 'GLOBAL_STATIC': u32
1550 [256; 257) 'w': u32 1551 [256; 257) 'w': u32
1551 [260; 277) 'GLOBAL...IC_MUT': u32 1552 [260; 277) 'GLOBAL...IC_MUT': u32
1553 [118; 120) '99': u32
1554 [161; 163) '99': u32
1552 "### 1555 "###
1553 ); 1556 );
1554} 1557}
diff --git a/crates/ra_hir_ty/src/tests/traits.rs b/crates/ra_hir_ty/src/tests/traits.rs
index ae316922b..0bc72644a 100644
--- a/crates/ra_hir_ty/src/tests/traits.rs
+++ b/crates/ra_hir_ty/src/tests/traits.rs
@@ -38,6 +38,63 @@ mod future {
38} 38}
39 39
40#[test] 40#[test]
41fn infer_async() {
42 let (db, pos) = TestDB::with_position(
43 r#"
44//- /main.rs crate:main deps:std
45
46async fn foo() -> u64 {
47 128
48}
49
50fn test() {
51 let r = foo();
52 let v = r.await;
53 v<|>;
54}
55
56//- /std.rs crate:std
57#[prelude_import] use future::*;
58mod future {
59 trait Future {
60 type Output;
61 }
62}
63
64"#,
65 );
66 assert_eq!("u64", type_at_pos(&db, pos));
67}
68
69#[test]
70fn infer_desugar_async() {
71 let (db, pos) = TestDB::with_position(
72 r#"
73//- /main.rs crate:main deps:std
74
75async fn foo() -> u64 {
76 128
77}
78
79fn test() {
80 let r = foo();
81 r<|>;
82}
83
84//- /std.rs crate:std
85#[prelude_import] use future::*;
86mod future {
87 trait Future {
88 type Output;
89 }
90}
91
92"#,
93 );
94 assert_eq!("impl Future<Output = u64>", type_at_pos(&db, pos));
95}
96
97#[test]
41fn infer_try() { 98fn infer_try() {
42 let (db, pos) = TestDB::with_position( 99 let (db, pos) = TestDB::with_position(
43 r#" 100 r#"
diff --git a/crates/ra_ide/src/completion/complete_dot.rs b/crates/ra_ide/src/completion/complete_dot.rs
index 294964887..210a685e4 100644
--- a/crates/ra_ide/src/completion/complete_dot.rs
+++ b/crates/ra_ide/src/completion/complete_dot.rs
@@ -1,6 +1,6 @@
1//! FIXME: write short doc here 1//! FIXME: write short doc here
2 2
3use hir::Type; 3use hir::{HasVisibility, Type};
4 4
5use crate::completion::completion_item::CompletionKind; 5use crate::completion::completion_item::CompletionKind;
6use crate::{ 6use crate::{
@@ -38,9 +38,15 @@ pub(super) fn complete_dot(acc: &mut Completions, ctx: &CompletionContext) {
38fn complete_fields(acc: &mut Completions, ctx: &CompletionContext, receiver: &Type) { 38fn complete_fields(acc: &mut Completions, ctx: &CompletionContext, receiver: &Type) {
39 for receiver in receiver.autoderef(ctx.db) { 39 for receiver in receiver.autoderef(ctx.db) {
40 for (field, ty) in receiver.fields(ctx.db) { 40 for (field, ty) in receiver.fields(ctx.db) {
41 if ctx.module.map_or(false, |m| !field.is_visible_from(ctx.db, m)) {
42 // Skip private field. FIXME: If the definition location of the
43 // field is editable, we should show the completion
44 continue;
45 }
41 acc.add_field(ctx, field, &ty); 46 acc.add_field(ctx, field, &ty);
42 } 47 }
43 for (i, ty) in receiver.tuple_fields(ctx.db).into_iter().enumerate() { 48 for (i, ty) in receiver.tuple_fields(ctx.db).into_iter().enumerate() {
49 // FIXME: Handle visibility
44 acc.add_tuple_field(ctx, i, &ty); 50 acc.add_tuple_field(ctx, i, &ty);
45 } 51 }
46 } 52 }
@@ -187,6 +193,55 @@ mod tests {
187 } 193 }
188 194
189 #[test] 195 #[test]
196 fn test_struct_field_visibility_private() {
197 assert_debug_snapshot!(
198 do_ref_completion(
199 r"
200 mod inner {
201 struct A {
202 private_field: u32,
203 pub pub_field: u32,
204 pub(crate) crate_field: u32,
205 pub(super) super_field: u32,
206 }
207 }
208 fn foo(a: inner::A) {
209 a.<|>
210 }
211 ",
212 ),
213 @r###"
214 [
215 CompletionItem {
216 label: "crate_field",
217 source_range: [313; 313),
218 delete: [313; 313),
219 insert: "crate_field",
220 kind: Field,
221 detail: "u32",
222 },
223 CompletionItem {
224 label: "pub_field",
225 source_range: [313; 313),
226 delete: [313; 313),
227 insert: "pub_field",
228 kind: Field,
229 detail: "u32",
230 },
231 CompletionItem {
232 label: "super_field",
233 source_range: [313; 313),
234 delete: [313; 313),
235 insert: "super_field",
236 kind: Field,
237 detail: "u32",
238 },
239 ]
240 "###
241 );
242 }
243
244 #[test]
190 fn test_method_completion() { 245 fn test_method_completion() {
191 assert_debug_snapshot!( 246 assert_debug_snapshot!(
192 do_ref_completion( 247 do_ref_completion(
diff --git a/crates/ra_ide/src/inlay_hints.rs b/crates/ra_ide/src/inlay_hints.rs
index c5e406977..977aafc51 100644
--- a/crates/ra_ide/src/inlay_hints.rs
+++ b/crates/ra_ide/src/inlay_hints.rs
@@ -293,7 +293,7 @@ fn main() {
293 } 293 }
294 294
295 #[test] 295 #[test]
296 fn closure_parameter() { 296 fn closure_parameters() {
297 let (analysis, file_id) = single_file( 297 let (analysis, file_id) = single_file(
298 r#" 298 r#"
299fn main() { 299fn main() {
@@ -301,6 +301,11 @@ fn main() {
301 (0..2).for_each(|increment| { 301 (0..2).for_each(|increment| {
302 start += increment; 302 start += increment;
303 }) 303 })
304
305 let multiply = |a, b, c, d| a * b * c * d;
306 let _: i32 = multiply(1, 2, 3, 4);
307
308 let return_42 = || 42;
304}"#, 309}"#,
305 ); 310 );
306 311
@@ -316,6 +321,36 @@ fn main() {
316 kind: TypeHint, 321 kind: TypeHint,
317 label: "i32", 322 label: "i32",
318 }, 323 },
324 InlayHint {
325 range: [114; 122),
326 kind: TypeHint,
327 label: "|…| -> i32",
328 },
329 InlayHint {
330 range: [126; 127),
331 kind: TypeHint,
332 label: "i32",
333 },
334 InlayHint {
335 range: [129; 130),
336 kind: TypeHint,
337 label: "i32",
338 },
339 InlayHint {
340 range: [132; 133),
341 kind: TypeHint,
342 label: "i32",
343 },
344 InlayHint {
345 range: [135; 136),
346 kind: TypeHint,
347 label: "i32",
348 },
349 InlayHint {
350 range: [201; 210),
351 kind: TypeHint,
352 label: "|| -> i32",
353 },
319 ] 354 ]
320 "### 355 "###
321 ); 356 );
diff --git a/crates/ra_lsp_server/Cargo.toml b/crates/ra_lsp_server/Cargo.toml
index 60cbc38a9..9b7dcb6e9 100644
--- a/crates/ra_lsp_server/Cargo.toml
+++ b/crates/ra_lsp_server/Cargo.toml
@@ -14,7 +14,7 @@ serde_json = "1.0.34"
14serde = { version = "1.0.83", features = ["derive"] } 14serde = { version = "1.0.83", features = ["derive"] }
15crossbeam-channel = "0.4" 15crossbeam-channel = "0.4"
16log = "0.4.3" 16log = "0.4.3"
17lsp-types = { version = "0.66.0", features = ["proposed"] } 17lsp-types = { version = "0.67.0", features = ["proposed"] }
18rustc-hash = "1.0" 18rustc-hash = "1.0"
19parking_lot = "0.10.0" 19parking_lot = "0.10.0"
20jod-thread = "0.1.0" 20jod-thread = "0.1.0"
@@ -27,6 +27,7 @@ ra_project_model = { path = "../ra_project_model" }
27ra_prof = { path = "../ra_prof" } 27ra_prof = { path = "../ra_prof" }
28ra_vfs_glob = { path = "../ra_vfs_glob" } 28ra_vfs_glob = { path = "../ra_vfs_glob" }
29env_logger = { version = "0.7.1", default-features = false, features = ["humantime"] } 29env_logger = { version = "0.7.1", default-features = false, features = ["humantime"] }
30ra_cargo_watch = { path = "../ra_cargo_watch" }
30 31
31[dev-dependencies] 32[dev-dependencies]
32tempfile = "3" 33tempfile = "3"
diff --git a/crates/ra_lsp_server/src/caps.rs b/crates/ra_lsp_server/src/caps.rs
index eeca67ee1..0dee1f6fe 100644
--- a/crates/ra_lsp_server/src/caps.rs
+++ b/crates/ra_lsp_server/src/caps.rs
@@ -3,7 +3,7 @@
3use lsp_types::{ 3use lsp_types::{
4 CodeActionProviderCapability, CodeLensOptions, CompletionOptions, 4 CodeActionProviderCapability, CodeLensOptions, CompletionOptions,
5 DocumentOnTypeFormattingOptions, FoldingRangeProviderCapability, 5 DocumentOnTypeFormattingOptions, FoldingRangeProviderCapability,
6 ImplementationProviderCapability, RenameOptions, RenameProviderCapability, 6 ImplementationProviderCapability, RenameOptions, RenameProviderCapability, SaveOptions,
7 SelectionRangeProviderCapability, ServerCapabilities, SignatureHelpOptions, 7 SelectionRangeProviderCapability, ServerCapabilities, SignatureHelpOptions,
8 TextDocumentSyncCapability, TextDocumentSyncKind, TextDocumentSyncOptions, 8 TextDocumentSyncCapability, TextDocumentSyncKind, TextDocumentSyncOptions,
9 TypeDefinitionProviderCapability, WorkDoneProgressOptions, 9 TypeDefinitionProviderCapability, WorkDoneProgressOptions,
@@ -16,7 +16,7 @@ pub fn server_capabilities() -> ServerCapabilities {
16 change: Some(TextDocumentSyncKind::Full), 16 change: Some(TextDocumentSyncKind::Full),
17 will_save: None, 17 will_save: None,
18 will_save_wait_until: None, 18 will_save_wait_until: None,
19 save: None, 19 save: Some(SaveOptions::default()),
20 })), 20 })),
21 hover_provider: Some(true), 21 hover_provider: Some(true),
22 completion_provider: Some(CompletionOptions { 22 completion_provider: Some(CompletionOptions {
@@ -46,6 +46,7 @@ pub fn server_capabilities() -> ServerCapabilities {
46 more_trigger_character: Some(vec![".".to_string(), ">".to_string()]), 46 more_trigger_character: Some(vec![".".to_string(), ">".to_string()]),
47 }), 47 }),
48 selection_range_provider: Some(SelectionRangeProviderCapability::Simple(true)), 48 selection_range_provider: Some(SelectionRangeProviderCapability::Simple(true)),
49 semantic_highlighting: None,
49 folding_range_provider: Some(FoldingRangeProviderCapability::Simple(true)), 50 folding_range_provider: Some(FoldingRangeProviderCapability::Simple(true)),
50 rename_provider: Some(RenameProviderCapability::Options(RenameOptions { 51 rename_provider: Some(RenameProviderCapability::Options(RenameOptions {
51 prepare_provider: Some(true), 52 prepare_provider: Some(true),
diff --git a/crates/ra_lsp_server/src/config.rs b/crates/ra_lsp_server/src/config.rs
index 67942aa41..2d7948d74 100644
--- a/crates/ra_lsp_server/src/config.rs
+++ b/crates/ra_lsp_server/src/config.rs
@@ -32,6 +32,11 @@ pub struct ServerConfig {
32 32
33 pub max_inlay_hint_length: Option<usize>, 33 pub max_inlay_hint_length: Option<usize>,
34 34
35 pub cargo_watch_enable: bool,
36 pub cargo_watch_args: Vec<String>,
37 pub cargo_watch_command: String,
38 pub cargo_watch_all_targets: bool,
39
35 /// For internal usage to make integrated tests faster. 40 /// For internal usage to make integrated tests faster.
36 #[serde(deserialize_with = "nullable_bool_true")] 41 #[serde(deserialize_with = "nullable_bool_true")]
37 pub with_sysroot: bool, 42 pub with_sysroot: bool,
@@ -51,6 +56,10 @@ impl Default for ServerConfig {
51 use_client_watching: false, 56 use_client_watching: false,
52 lru_capacity: None, 57 lru_capacity: None,
53 max_inlay_hint_length: None, 58 max_inlay_hint_length: None,
59 cargo_watch_enable: true,
60 cargo_watch_args: Vec::new(),
61 cargo_watch_command: "check".to_string(),
62 cargo_watch_all_targets: true,
54 with_sysroot: true, 63 with_sysroot: true,
55 feature_flags: FxHashMap::default(), 64 feature_flags: FxHashMap::default(),
56 cargo_features: Default::default(), 65 cargo_features: Default::default(),
diff --git a/crates/ra_lsp_server/src/main_loop.rs b/crates/ra_lsp_server/src/main_loop.rs
index dda318e43..af1a487de 100644
--- a/crates/ra_lsp_server/src/main_loop.rs
+++ b/crates/ra_lsp_server/src/main_loop.rs
@@ -10,6 +10,7 @@ use std::{error::Error, fmt, panic, path::PathBuf, sync::Arc, time::Instant};
10use crossbeam_channel::{select, unbounded, RecvError, Sender}; 10use crossbeam_channel::{select, unbounded, RecvError, Sender};
11use lsp_server::{Connection, ErrorCode, Message, Notification, Request, RequestId, Response}; 11use lsp_server::{Connection, ErrorCode, Message, Notification, Request, RequestId, Response};
12use lsp_types::{ClientCapabilities, NumberOrString}; 12use lsp_types::{ClientCapabilities, NumberOrString};
13use ra_cargo_watch::{CheckOptions, CheckTask};
13use ra_ide::{Canceled, FeatureFlags, FileId, LibraryData, SourceRootId}; 14use ra_ide::{Canceled, FeatureFlags, FileId, LibraryData, SourceRootId};
14use ra_prof::profile; 15use ra_prof::profile;
15use ra_vfs::{VfsTask, Watch}; 16use ra_vfs::{VfsTask, Watch};
@@ -126,6 +127,12 @@ pub fn main_loop(
126 .and_then(|it| it.line_folding_only) 127 .and_then(|it| it.line_folding_only)
127 .unwrap_or(false), 128 .unwrap_or(false),
128 max_inlay_hint_length: config.max_inlay_hint_length, 129 max_inlay_hint_length: config.max_inlay_hint_length,
130 cargo_watch: CheckOptions {
131 enable: config.cargo_watch_enable,
132 args: config.cargo_watch_args,
133 command: config.cargo_watch_command,
134 all_targets: config.cargo_watch_all_targets,
135 },
129 } 136 }
130 }; 137 };
131 138
@@ -176,7 +183,11 @@ pub fn main_loop(
176 Ok(task) => Event::Vfs(task), 183 Ok(task) => Event::Vfs(task),
177 Err(RecvError) => Err("vfs died")?, 184 Err(RecvError) => Err("vfs died")?,
178 }, 185 },
179 recv(libdata_receiver) -> data => Event::Lib(data.unwrap()) 186 recv(libdata_receiver) -> data => Event::Lib(data.unwrap()),
187 recv(world_state.check_watcher.task_recv) -> task => match task {
188 Ok(task) => Event::CheckWatcher(task),
189 Err(RecvError) => Err("check watcher died")?,
190 }
180 }; 191 };
181 if let Event::Msg(Message::Request(req)) = &event { 192 if let Event::Msg(Message::Request(req)) = &event {
182 if connection.handle_shutdown(&req)? { 193 if connection.handle_shutdown(&req)? {
@@ -222,6 +233,7 @@ enum Event {
222 Task(Task), 233 Task(Task),
223 Vfs(VfsTask), 234 Vfs(VfsTask),
224 Lib(LibraryData), 235 Lib(LibraryData),
236 CheckWatcher(CheckTask),
225} 237}
226 238
227impl fmt::Debug for Event { 239impl fmt::Debug for Event {
@@ -259,6 +271,7 @@ impl fmt::Debug for Event {
259 Event::Task(it) => fmt::Debug::fmt(it, f), 271 Event::Task(it) => fmt::Debug::fmt(it, f),
260 Event::Vfs(it) => fmt::Debug::fmt(it, f), 272 Event::Vfs(it) => fmt::Debug::fmt(it, f),
261 Event::Lib(it) => fmt::Debug::fmt(it, f), 273 Event::Lib(it) => fmt::Debug::fmt(it, f),
274 Event::CheckWatcher(it) => fmt::Debug::fmt(it, f),
262 } 275 }
263 } 276 }
264} 277}
@@ -318,6 +331,28 @@ fn loop_turn(
318 world_state.maybe_collect_garbage(); 331 world_state.maybe_collect_garbage();
319 loop_state.in_flight_libraries -= 1; 332 loop_state.in_flight_libraries -= 1;
320 } 333 }
334 Event::CheckWatcher(task) => match task {
335 CheckTask::Update(uri) => {
336 // We manually send a diagnostic update when the watcher asks
337 // us to, to avoid the issue of having to change the file to
338 // receive updated diagnostics.
339 let path = uri.to_file_path().map_err(|()| format!("invalid uri: {}", uri))?;
340 if let Some(file_id) = world_state.vfs.read().path2file(&path) {
341 let params =
342 handlers::publish_diagnostics(&world_state.snapshot(), FileId(file_id.0))?;
343 let not = notification_new::<req::PublishDiagnostics>(params);
344 task_sender.send(Task::Notify(not)).unwrap();
345 }
346 }
347 CheckTask::Status(progress) => {
348 let params = req::ProgressParams {
349 token: req::ProgressToken::String("rustAnalyzer/cargoWatcher".to_string()),
350 value: req::ProgressParamsValue::WorkDone(progress),
351 };
352 let not = notification_new::<req::Progress>(params);
353 task_sender.send(Task::Notify(not)).unwrap();
354 }
355 },
321 Event::Msg(msg) => match msg { 356 Event::Msg(msg) => match msg {
322 Message::Request(req) => on_request( 357 Message::Request(req) => on_request(
323 world_state, 358 world_state,
@@ -517,6 +552,13 @@ fn on_notification(
517 } 552 }
518 Err(not) => not, 553 Err(not) => not,
519 }; 554 };
555 let not = match notification_cast::<req::DidSaveTextDocument>(not) {
556 Ok(_params) => {
557 state.check_watcher.update();
558 return Ok(());
559 }
560 Err(not) => not,
561 };
520 let not = match notification_cast::<req::DidCloseTextDocument>(not) { 562 let not = match notification_cast::<req::DidCloseTextDocument>(not) {
521 Ok(params) => { 563 Ok(params) => {
522 let uri = params.text_document.uri; 564 let uri = params.text_document.uri;
diff --git a/crates/ra_lsp_server/src/main_loop/handlers.rs b/crates/ra_lsp_server/src/main_loop/handlers.rs
index 39eb3df3e..331beab13 100644
--- a/crates/ra_lsp_server/src/main_loop/handlers.rs
+++ b/crates/ra_lsp_server/src/main_loop/handlers.rs
@@ -654,6 +654,29 @@ pub fn handle_code_action(
654 res.push(action.into()); 654 res.push(action.into());
655 } 655 }
656 656
657 for fix in world.check_watcher.read().fixes_for(&params.text_document.uri).into_iter().flatten()
658 {
659 let fix_range = fix.location.range.conv_with(&line_index);
660 if fix_range.intersection(&range).is_none() {
661 continue;
662 }
663
664 let edits = vec![TextEdit::new(fix.location.range, fix.replacement.clone())];
665 let mut edit_map = std::collections::HashMap::new();
666 edit_map.insert(fix.location.uri.clone(), edits);
667 let edit = WorkspaceEdit::new(edit_map);
668
669 let action = CodeAction {
670 title: fix.title.clone(),
671 kind: Some("quickfix".to_string()),
672 diagnostics: Some(fix.diagnostics.clone()),
673 edit: Some(edit),
674 command: None,
675 is_preferred: None,
676 };
677 res.push(action.into());
678 }
679
657 for assist in assists { 680 for assist in assists {
658 let title = assist.change.label.clone(); 681 let title = assist.change.label.clone();
659 let edit = assist.change.try_conv_with(&world)?; 682 let edit = assist.change.try_conv_with(&world)?;
@@ -820,7 +843,7 @@ pub fn publish_diagnostics(
820 let _p = profile("publish_diagnostics"); 843 let _p = profile("publish_diagnostics");
821 let uri = world.file_id_to_uri(file_id)?; 844 let uri = world.file_id_to_uri(file_id)?;
822 let line_index = world.analysis().file_line_index(file_id)?; 845 let line_index = world.analysis().file_line_index(file_id)?;
823 let diagnostics = world 846 let mut diagnostics: Vec<Diagnostic> = world
824 .analysis() 847 .analysis()
825 .diagnostics(file_id)? 848 .diagnostics(file_id)?
826 .into_iter() 849 .into_iter()
@@ -834,6 +857,9 @@ pub fn publish_diagnostics(
834 tags: None, 857 tags: None,
835 }) 858 })
836 .collect(); 859 .collect();
860 if let Some(check_diags) = world.check_watcher.read().diagnostics_for(&uri) {
861 diagnostics.extend(check_diags.iter().cloned());
862 }
837 Ok(req::PublishDiagnosticsParams { uri, diagnostics, version: None }) 863 Ok(req::PublishDiagnosticsParams { uri, diagnostics, version: None })
838} 864}
839 865
diff --git a/crates/ra_lsp_server/src/req.rs b/crates/ra_lsp_server/src/req.rs
index b34e6f9b8..40edaf677 100644
--- a/crates/ra_lsp_server/src/req.rs
+++ b/crates/ra_lsp_server/src/req.rs
@@ -9,10 +9,10 @@ pub use lsp_types::{
9 CodeLensParams, CompletionParams, CompletionResponse, DidChangeConfigurationParams, 9 CodeLensParams, CompletionParams, CompletionResponse, DidChangeConfigurationParams,
10 DidChangeWatchedFilesParams, DidChangeWatchedFilesRegistrationOptions, 10 DidChangeWatchedFilesParams, DidChangeWatchedFilesRegistrationOptions,
11 DocumentOnTypeFormattingParams, DocumentSymbolParams, DocumentSymbolResponse, 11 DocumentOnTypeFormattingParams, DocumentSymbolParams, DocumentSymbolResponse,
12 FileSystemWatcher, Hover, InitializeResult, MessageType, PublishDiagnosticsParams, 12 FileSystemWatcher, Hover, InitializeResult, MessageType, ProgressParams, ProgressParamsValue,
13 ReferenceParams, Registration, RegistrationParams, SelectionRange, SelectionRangeParams, 13 ProgressToken, PublishDiagnosticsParams, ReferenceParams, Registration, RegistrationParams,
14 ShowMessageParams, SignatureHelp, TextDocumentEdit, TextDocumentPositionParams, TextEdit, 14 SelectionRange, SelectionRangeParams, ShowMessageParams, SignatureHelp, TextDocumentEdit,
15 WorkspaceEdit, WorkspaceSymbolParams, 15 TextDocumentPositionParams, TextEdit, WorkspaceEdit, WorkspaceSymbolParams,
16}; 16};
17 17
18pub enum AnalyzerStatus {} 18pub enum AnalyzerStatus {}
diff --git a/crates/ra_lsp_server/src/world.rs b/crates/ra_lsp_server/src/world.rs
index 79431e7e6..4b3959e38 100644
--- a/crates/ra_lsp_server/src/world.rs
+++ b/crates/ra_lsp_server/src/world.rs
@@ -12,6 +12,7 @@ use crossbeam_channel::{unbounded, Receiver};
12use lsp_server::ErrorCode; 12use lsp_server::ErrorCode;
13use lsp_types::Url; 13use lsp_types::Url;
14use parking_lot::RwLock; 14use parking_lot::RwLock;
15use ra_cargo_watch::{CheckOptions, CheckWatcher, CheckWatcherSharedState};
15use ra_ide::{ 16use ra_ide::{
16 Analysis, AnalysisChange, AnalysisHost, CrateGraph, FeatureFlags, FileId, LibraryData, 17 Analysis, AnalysisChange, AnalysisHost, CrateGraph, FeatureFlags, FileId, LibraryData,
17 SourceRootId, 18 SourceRootId,
@@ -34,6 +35,7 @@ pub struct Options {
34 pub supports_location_link: bool, 35 pub supports_location_link: bool,
35 pub line_folding_only: bool, 36 pub line_folding_only: bool,
36 pub max_inlay_hint_length: Option<usize>, 37 pub max_inlay_hint_length: Option<usize>,
38 pub cargo_watch: CheckOptions,
37} 39}
38 40
39/// `WorldState` is the primary mutable state of the language server 41/// `WorldState` is the primary mutable state of the language server
@@ -52,6 +54,7 @@ pub struct WorldState {
52 pub vfs: Arc<RwLock<Vfs>>, 54 pub vfs: Arc<RwLock<Vfs>>,
53 pub task_receiver: Receiver<VfsTask>, 55 pub task_receiver: Receiver<VfsTask>,
54 pub latest_requests: Arc<RwLock<LatestRequests>>, 56 pub latest_requests: Arc<RwLock<LatestRequests>>,
57 pub check_watcher: CheckWatcher,
55} 58}
56 59
57/// An immutable snapshot of the world's state at a point in time. 60/// An immutable snapshot of the world's state at a point in time.
@@ -61,6 +64,7 @@ pub struct WorldSnapshot {
61 pub analysis: Analysis, 64 pub analysis: Analysis,
62 pub vfs: Arc<RwLock<Vfs>>, 65 pub vfs: Arc<RwLock<Vfs>>,
63 pub latest_requests: Arc<RwLock<LatestRequests>>, 66 pub latest_requests: Arc<RwLock<LatestRequests>>,
67 pub check_watcher: Arc<RwLock<CheckWatcherSharedState>>,
64} 68}
65 69
66impl WorldState { 70impl WorldState {
@@ -127,6 +131,10 @@ impl WorldState {
127 } 131 }
128 change.set_crate_graph(crate_graph); 132 change.set_crate_graph(crate_graph);
129 133
134 // FIXME: Figure out the multi-workspace situation
135 let check_watcher =
136 CheckWatcher::new(&options.cargo_watch, folder_roots.first().cloned().unwrap());
137
130 let mut analysis_host = AnalysisHost::new(lru_capacity, feature_flags); 138 let mut analysis_host = AnalysisHost::new(lru_capacity, feature_flags);
131 analysis_host.apply_change(change); 139 analysis_host.apply_change(change);
132 WorldState { 140 WorldState {
@@ -138,6 +146,7 @@ impl WorldState {
138 vfs: Arc::new(RwLock::new(vfs)), 146 vfs: Arc::new(RwLock::new(vfs)),
139 task_receiver, 147 task_receiver,
140 latest_requests: Default::default(), 148 latest_requests: Default::default(),
149 check_watcher,
141 } 150 }
142 } 151 }
143 152
@@ -199,6 +208,7 @@ impl WorldState {
199 analysis: self.analysis_host.analysis(), 208 analysis: self.analysis_host.analysis(),
200 vfs: Arc::clone(&self.vfs), 209 vfs: Arc::clone(&self.vfs),
201 latest_requests: Arc::clone(&self.latest_requests), 210 latest_requests: Arc::clone(&self.latest_requests),
211 check_watcher: self.check_watcher.shared.clone(),
202 } 212 }
203 } 213 }
204 214
diff --git a/crates/ra_project_model/src/cargo_workspace.rs b/crates/ra_project_model/src/cargo_workspace.rs
index c862d3912..1b3c246c7 100644
--- a/crates/ra_project_model/src/cargo_workspace.rs
+++ b/crates/ra_project_model/src/cargo_workspace.rs
@@ -165,7 +165,7 @@ impl CargoWorkspace {
165 // FIXME: `NoDefaultFeatures` is mutual exclusive with `SomeFeatures` 165 // FIXME: `NoDefaultFeatures` is mutual exclusive with `SomeFeatures`
166 // https://github.com/oli-obk/cargo_metadata/issues/79 166 // https://github.com/oli-obk/cargo_metadata/issues/79
167 meta.features(CargoOpt::NoDefaultFeatures); 167 meta.features(CargoOpt::NoDefaultFeatures);
168 } else { 168 } else if cargo_features.features.len() > 0 {
169 meta.features(CargoOpt::SomeFeatures(cargo_features.features.clone())); 169 meta.features(CargoOpt::SomeFeatures(cargo_features.features.clone()));
170 } 170 }
171 if let Some(parent) = cargo_toml.parent() { 171 if let Some(parent) = cargo_toml.parent() {
diff --git a/crates/ra_syntax/src/ast.rs b/crates/ra_syntax/src/ast.rs
index 277532a8c..89cb9a9f3 100644
--- a/crates/ra_syntax/src/ast.rs
+++ b/crates/ra_syntax/src/ast.rs
@@ -17,7 +17,9 @@ use crate::{
17 17
18pub use self::{ 18pub use self::{
19 expr_extensions::{ArrayExprKind, BinOp, ElseBranch, LiteralKind, PrefixOp, RangeOp}, 19 expr_extensions::{ArrayExprKind, BinOp, ElseBranch, LiteralKind, PrefixOp, RangeOp},
20 extensions::{FieldKind, PathSegmentKind, SelfParamKind, StructKind, TypeBoundKind}, 20 extensions::{
21 FieldKind, PathSegmentKind, SelfParamKind, StructKind, TypeBoundKind, VisibilityKind,
22 },
21 generated::*, 23 generated::*,
22 tokens::*, 24 tokens::*,
23 traits::*, 25 traits::*,
diff --git a/crates/ra_syntax/src/ast/extensions.rs b/crates/ra_syntax/src/ast/extensions.rs
index a8f625176..d9666cdca 100644
--- a/crates/ra_syntax/src/ast/extensions.rs
+++ b/crates/ra_syntax/src/ast/extensions.rs
@@ -221,6 +221,10 @@ impl ast::FnDef {
221 .and_then(|it| it.into_token()) 221 .and_then(|it| it.into_token())
222 .filter(|it| it.kind() == T![;]) 222 .filter(|it| it.kind() == T![;])
223 } 223 }
224
225 pub fn is_async(&self) -> bool {
226 self.syntax().children_with_tokens().any(|it| it.kind() == T![async])
227 }
224} 228}
225 229
226impl ast::LetStmt { 230impl ast::LetStmt {
@@ -409,3 +413,32 @@ impl ast::TraitDef {
409 self.syntax().children_with_tokens().any(|t| t.kind() == T![auto]) 413 self.syntax().children_with_tokens().any(|t| t.kind() == T![auto])
410 } 414 }
411} 415}
416
417pub enum VisibilityKind {
418 In(ast::Path),
419 PubCrate,
420 PubSuper,
421 Pub,
422}
423
424impl ast::Visibility {
425 pub fn kind(&self) -> VisibilityKind {
426 if let Some(path) = children(self).next() {
427 VisibilityKind::In(path)
428 } else if self.is_pub_crate() {
429 VisibilityKind::PubCrate
430 } else if self.is_pub_super() {
431 VisibilityKind::PubSuper
432 } else {
433 VisibilityKind::Pub
434 }
435 }
436
437 fn is_pub_crate(&self) -> bool {
438 self.syntax().children_with_tokens().any(|it| it.kind() == T![crate])
439 }
440
441 fn is_pub_super(&self) -> bool {
442 self.syntax().children_with_tokens().any(|it| it.kind() == T![super])
443 }
444}
diff --git a/crates/ra_syntax/src/ast/generated.rs b/crates/ra_syntax/src/ast/generated.rs
index 9f9d6e63c..e64c83d33 100644
--- a/crates/ra_syntax/src/ast/generated.rs
+++ b/crates/ra_syntax/src/ast/generated.rs
@@ -1064,6 +1064,7 @@ impl AstNode for ExternCrateItem {
1064 } 1064 }
1065} 1065}
1066impl ast::AttrsOwner for ExternCrateItem {} 1066impl ast::AttrsOwner for ExternCrateItem {}
1067impl ast::VisibilityOwner for ExternCrateItem {}
1067impl ExternCrateItem { 1068impl ExternCrateItem {
1068 pub fn name_ref(&self) -> Option<NameRef> { 1069 pub fn name_ref(&self) -> Option<NameRef> {
1069 AstChildren::new(&self.syntax).next() 1070 AstChildren::new(&self.syntax).next()
@@ -2006,6 +2007,7 @@ impl AstNode for ModuleItem {
2006 } 2007 }
2007} 2008}
2008impl ast::AttrsOwner for ModuleItem {} 2009impl ast::AttrsOwner for ModuleItem {}
2010impl ast::VisibilityOwner for ModuleItem {}
2009impl ModuleItem {} 2011impl ModuleItem {}
2010#[derive(Debug, Clone, PartialEq, Eq, Hash)] 2012#[derive(Debug, Clone, PartialEq, Eq, Hash)]
2011pub struct Name { 2013pub struct Name {
@@ -3893,6 +3895,7 @@ impl AstNode for UseItem {
3893 } 3895 }
3894} 3896}
3895impl ast::AttrsOwner for UseItem {} 3897impl ast::AttrsOwner for UseItem {}
3898impl ast::VisibilityOwner for UseItem {}
3896impl UseItem { 3899impl UseItem {
3897 pub fn use_tree(&self) -> Option<UseTree> { 3900 pub fn use_tree(&self) -> Option<UseTree> {
3898 AstChildren::new(&self.syntax).next() 3901 AstChildren::new(&self.syntax).next()
diff --git a/crates/ra_syntax/src/grammar.ron b/crates/ra_syntax/src/grammar.ron
index 08aafb610..e43a724f0 100644
--- a/crates/ra_syntax/src/grammar.ron
+++ b/crates/ra_syntax/src/grammar.ron
@@ -412,7 +412,7 @@ Grammar(
412 "ModuleItem": ( 412 "ModuleItem": (
413 enum: ["StructDef", "UnionDef", "EnumDef", "FnDef", "TraitDef", "TypeAliasDef", "ImplBlock", 413 enum: ["StructDef", "UnionDef", "EnumDef", "FnDef", "TraitDef", "TypeAliasDef", "ImplBlock",
414 "UseItem", "ExternCrateItem", "ConstDef", "StaticDef", "Module" ], 414 "UseItem", "ExternCrateItem", "ConstDef", "StaticDef", "Module" ],
415 traits: ["AttrsOwner"], 415 traits: ["AttrsOwner", "VisibilityOwner"],
416 ), 416 ),
417 "ImplItem": ( 417 "ImplItem": (
418 enum: ["FnDef", "TypeAliasDef", "ConstDef"], 418 enum: ["FnDef", "TypeAliasDef", "ConstDef"],
@@ -683,7 +683,7 @@ Grammar(
683 ] 683 ]
684 ), 684 ),
685 "UseItem": ( 685 "UseItem": (
686 traits: ["AttrsOwner"], 686 traits: ["AttrsOwner", "VisibilityOwner"],
687 options: [ "UseTree" ], 687 options: [ "UseTree" ],
688 ), 688 ),
689 "UseTree": ( 689 "UseTree": (
@@ -696,7 +696,7 @@ Grammar(
696 collections: [("use_trees", "UseTree")] 696 collections: [("use_trees", "UseTree")]
697 ), 697 ),
698 "ExternCrateItem": ( 698 "ExternCrateItem": (
699 traits: ["AttrsOwner"], 699 traits: ["AttrsOwner", "VisibilityOwner"],
700 options: ["NameRef", "Alias"], 700 options: ["NameRef", "Alias"],
701 ), 701 ),
702 "ArgList": ( 702 "ArgList": (
diff --git a/docs/user/assists.md b/docs/user/assists.md
index 334ba450f..ecf206f71 100644
--- a/docs/user/assists.md
+++ b/docs/user/assists.md
@@ -98,26 +98,26 @@ Adds scaffold for overriding default impl members.
98 98
99```rust 99```rust
100// BEFORE 100// BEFORE
101trait T { 101trait Trait {
102 Type X; 102 Type X;
103 fn foo(&self); 103 fn foo(&self);
104 fn bar(&self) {} 104 fn bar(&self) {}
105} 105}
106 106
107impl T for () { 107impl Trait for () {
108 Type X = (); 108 Type X = ();
109 fn foo(&self) {}┃ 109 fn foo(&self) {}┃
110 110
111} 111}
112 112
113// AFTER 113// AFTER
114trait T { 114trait Trait {
115 Type X; 115 Type X;
116 fn foo(&self); 116 fn foo(&self);
117 fn bar(&self) {} 117 fn bar(&self) {}
118} 118}
119 119
120impl T for () { 120impl Trait for () {
121 Type X = (); 121 Type X = ();
122 fn foo(&self) {} 122 fn foo(&self) {}
123 fn bar(&self) {} 123 fn bar(&self) {}
@@ -131,25 +131,25 @@ Adds scaffold for required impl members.
131 131
132```rust 132```rust
133// BEFORE 133// BEFORE
134trait T { 134trait Trait<T> {
135 Type X; 135 Type X;
136 fn foo(&self); 136 fn foo(&self) -> T;
137 fn bar(&self) {} 137 fn bar(&self) {}
138} 138}
139 139
140impl T for () {┃ 140impl Trait<u32> for () {┃
141 141
142} 142}
143 143
144// AFTER 144// AFTER
145trait T { 145trait Trait<T> {
146 Type X; 146 Type X;
147 fn foo(&self); 147 fn foo(&self) -> T;
148 fn bar(&self) {} 148 fn bar(&self) {}
149} 149}
150 150
151impl T for () { 151impl Trait<u32> for () {
152 fn foo(&self) { unimplemented!() } 152 fn foo(&self) -> u32 { unimplemented!() }
153 153
154} 154}
155``` 155```
diff --git a/editors/code/package.json b/editors/code/package.json
index 8e7046418..f28ce1772 100644
--- a/editors/code/package.json
+++ b/editors/code/package.json
@@ -18,7 +18,7 @@
18 "scripts": { 18 "scripts": {
19 "vscode:prepublish": "npm run compile", 19 "vscode:prepublish": "npm run compile",
20 "package": "vsce package", 20 "package": "vsce package",
21 "compile": "rollup -c && shx cp src/utils/terminateProcess.sh bundle/terminateProcess.sh", 21 "compile": "rollup -c",
22 "watch": "tsc -watch -p ./", 22 "watch": "tsc -watch -p ./",
23 "fix": "prettier **/*.{json,ts} --write && tslint --project . --fix", 23 "fix": "prettier **/*.{json,ts} --write && tslint --project . --fix",
24 "lint": "tslint --project .", 24 "lint": "tslint --project .",
@@ -134,16 +134,6 @@
134 "command": "rust-analyzer.reload", 134 "command": "rust-analyzer.reload",
135 "title": "Restart server", 135 "title": "Restart server",
136 "category": "Rust Analyzer" 136 "category": "Rust Analyzer"
137 },
138 {
139 "command": "rust-analyzer.startCargoWatch",
140 "title": "Start Cargo Watch",
141 "category": "Rust Analyzer"
142 },
143 {
144 "command": "rust-analyzer.stopCargoWatch",
145 "title": "Stop Cargo Watch",
146 "category": "Rust Analyzer"
147 } 137 }
148 ], 138 ],
149 "keybindings": [ 139 "keybindings": [
@@ -261,21 +251,6 @@
261 "default": "ra_lsp_server", 251 "default": "ra_lsp_server",
262 "description": "Path to ra_lsp_server executable" 252 "description": "Path to ra_lsp_server executable"
263 }, 253 },
264 "rust-analyzer.enableCargoWatchOnStartup": {
265 "type": "string",
266 "default": "ask",
267 "enum": [
268 "ask",
269 "enabled",
270 "disabled"
271 ],
272 "enumDescriptions": [
273 "Asks each time whether to run `cargo watch`",
274 "`cargo watch` is always started",
275 "Don't start `cargo watch`"
276 ],
277 "description": "Whether to run `cargo watch` on startup"
278 },
279 "rust-analyzer.excludeGlobs": { 254 "rust-analyzer.excludeGlobs": {
280 "type": "array", 255 "type": "array",
281 "default": [], 256 "default": [],
@@ -286,21 +261,21 @@
286 "default": true, 261 "default": true,
287 "description": "client provided file watching instead of notify watching." 262 "description": "client provided file watching instead of notify watching."
288 }, 263 },
264 "rust-analyzer.cargo-watch.enable": {
265 "type": "boolean",
266 "default": true,
267 "description": "Run `cargo check` for diagnostics on save"
268 },
289 "rust-analyzer.cargo-watch.arguments": { 269 "rust-analyzer.cargo-watch.arguments": {
290 "type": "string", 270 "type": "array",
291 "description": "`cargo-watch` arguments. (e.g: `--features=\"shumway,pdf\"` will run as `cargo watch -x \"check --features=\"shumway,pdf\"\"` )", 271 "description": "`cargo-watch` arguments. (e.g: `--features=\"shumway,pdf\"` will run as `cargo watch -x \"check --features=\"shumway,pdf\"\"` )",
292 "default": "" 272 "default": []
293 }, 273 },
294 "rust-analyzer.cargo-watch.command": { 274 "rust-analyzer.cargo-watch.command": {
295 "type": "string", 275 "type": "string",
296 "description": "`cargo-watch` command. (e.g: `clippy` will run as `cargo watch -x clippy` )", 276 "description": "`cargo-watch` command. (e.g: `clippy` will run as `cargo watch -x clippy` )",
297 "default": "check" 277 "default": "check"
298 }, 278 },
299 "rust-analyzer.cargo-watch.ignore": {
300 "type": "array",
301 "description": "A list of patterns for cargo-watch to ignore (will be passed as `--ignore`)",
302 "default": []
303 },
304 "rust-analyzer.cargo-watch.allTargets": { 279 "rust-analyzer.cargo-watch.allTargets": {
305 "type": "boolean", 280 "type": "boolean",
306 "description": "Check all targets and tests (will be passed as `--all-targets`)", 281 "description": "Check all targets and tests (will be passed as `--all-targets`)",
@@ -322,17 +297,6 @@
322 "default": "off", 297 "default": "off",
323 "description": "Trace requests to the ra_lsp_server" 298 "description": "Trace requests to the ra_lsp_server"
324 }, 299 },
325 "rust-analyzer.trace.cargo-watch": {
326 "type": "string",
327 "scope": "window",
328 "enum": [
329 "off",
330 "error",
331 "verbose"
332 ],
333 "default": "off",
334 "description": "Trace output of cargo-watch"
335 },
336 "rust-analyzer.lruCapacity": { 300 "rust-analyzer.lruCapacity": {
337 "type": "number", 301 "type": "number",
338 "default": null, 302 "default": null,
diff --git a/editors/code/src/commands/cargo_watch.ts b/editors/code/src/commands/cargo_watch.ts
deleted file mode 100644
index ac62bdd48..000000000
--- a/editors/code/src/commands/cargo_watch.ts
+++ /dev/null
@@ -1,264 +0,0 @@
1import * as child_process from 'child_process';
2import * as path from 'path';
3import * as vscode from 'vscode';
4
5import { Server } from '../server';
6import { terminate } from '../utils/processes';
7import { LineBuffer } from './line_buffer';
8import { StatusDisplay } from './watch_status';
9
10import {
11 mapRustDiagnosticToVsCode,
12 RustDiagnostic,
13} from '../utils/diagnostics/rust';
14import SuggestedFixCollection from '../utils/diagnostics/SuggestedFixCollection';
15import { areDiagnosticsEqual } from '../utils/diagnostics/vscode';
16
17export async function registerCargoWatchProvider(
18 subscriptions: vscode.Disposable[],
19): Promise<CargoWatchProvider | undefined> {
20 let cargoExists = false;
21
22 // Check if the working directory is valid cargo root path
23 const cargoTomlPath = path.join(vscode.workspace.rootPath!, 'Cargo.toml');
24 const cargoTomlUri = vscode.Uri.file(cargoTomlPath);
25 const cargoTomlFileInfo = await vscode.workspace.fs.stat(cargoTomlUri);
26
27 if (cargoTomlFileInfo) {
28 cargoExists = true;
29 }
30
31 if (!cargoExists) {
32 vscode.window.showErrorMessage(
33 `Couldn\'t find \'Cargo.toml\' at ${cargoTomlPath}`,
34 );
35 return;
36 }
37
38 const provider = new CargoWatchProvider();
39 subscriptions.push(provider);
40 return provider;
41}
42
43export class CargoWatchProvider implements vscode.Disposable {
44 private readonly diagnosticCollection: vscode.DiagnosticCollection;
45 private readonly statusDisplay: StatusDisplay;
46 private readonly outputChannel: vscode.OutputChannel;
47
48 private suggestedFixCollection: SuggestedFixCollection;
49 private codeActionDispose: vscode.Disposable;
50
51 private cargoProcess?: child_process.ChildProcess;
52
53 constructor() {
54 this.diagnosticCollection = vscode.languages.createDiagnosticCollection(
55 'rustc',
56 );
57 this.statusDisplay = new StatusDisplay(
58 Server.config.cargoWatchOptions.command,
59 );
60 this.outputChannel = vscode.window.createOutputChannel(
61 'Cargo Watch Trace',
62 );
63
64 // Track `rustc`'s suggested fixes so we can convert them to code actions
65 this.suggestedFixCollection = new SuggestedFixCollection();
66 this.codeActionDispose = vscode.languages.registerCodeActionsProvider(
67 [{ scheme: 'file', language: 'rust' }],
68 this.suggestedFixCollection,
69 {
70 providedCodeActionKinds:
71 SuggestedFixCollection.PROVIDED_CODE_ACTION_KINDS,
72 },
73 );
74 }
75
76 public start() {
77 if (this.cargoProcess) {
78 vscode.window.showInformationMessage(
79 'Cargo Watch is already running',
80 );
81 return;
82 }
83
84 let args =
85 Server.config.cargoWatchOptions.command + ' --message-format json';
86 if (Server.config.cargoWatchOptions.allTargets) {
87 args += ' --all-targets';
88 }
89 if (Server.config.cargoWatchOptions.command.length > 0) {
90 // Excape the double quote string:
91 args += ' ' + Server.config.cargoWatchOptions.arguments;
92 }
93 // Windows handles arguments differently than the unix-likes, so we need to wrap the args in double quotes
94 if (process.platform === 'win32') {
95 args = '"' + args + '"';
96 }
97
98 const ignoreFlags = Server.config.cargoWatchOptions.ignore.reduce(
99 (flags, pattern) => [...flags, '--ignore', pattern],
100 [] as string[],
101 );
102
103 // Start the cargo watch with json message
104 this.cargoProcess = child_process.spawn(
105 'cargo',
106 ['watch', '-x', args, ...ignoreFlags],
107 {
108 stdio: ['ignore', 'pipe', 'pipe'],
109 cwd: vscode.workspace.rootPath,
110 windowsVerbatimArguments: true,
111 },
112 );
113
114 if (!this.cargoProcess) {
115 vscode.window.showErrorMessage('Cargo Watch failed to start');
116 return;
117 }
118
119 const stdoutData = new LineBuffer();
120 this.cargoProcess.stdout?.on('data', (s: string) => {
121 stdoutData.processOutput(s, line => {
122 this.logInfo(line);
123 try {
124 this.parseLine(line);
125 } catch (err) {
126 this.logError(`Failed to parse: ${err}, content : ${line}`);
127 }
128 });
129 });
130
131 const stderrData = new LineBuffer();
132 this.cargoProcess.stderr?.on('data', (s: string) => {
133 stderrData.processOutput(s, line => {
134 this.logError('Error on cargo-watch : {\n' + line + '}\n');
135 });
136 });
137
138 this.cargoProcess.on('error', (err: Error) => {
139 this.logError(
140 'Error on cargo-watch process : {\n' + err.message + '}\n',
141 );
142 });
143
144 this.logInfo('cargo-watch started.');
145 }
146
147 public stop() {
148 if (this.cargoProcess) {
149 this.cargoProcess.kill();
150 terminate(this.cargoProcess);
151 this.cargoProcess = undefined;
152 } else {
153 vscode.window.showInformationMessage('Cargo Watch is not running');
154 }
155 }
156
157 public dispose(): void {
158 this.stop();
159
160 this.diagnosticCollection.clear();
161 this.diagnosticCollection.dispose();
162 this.outputChannel.dispose();
163 this.statusDisplay.dispose();
164 this.codeActionDispose.dispose();
165 }
166
167 private logInfo(line: string) {
168 if (Server.config.cargoWatchOptions.trace === 'verbose') {
169 this.outputChannel.append(line);
170 }
171 }
172
173 private logError(line: string) {
174 if (
175 Server.config.cargoWatchOptions.trace === 'error' ||
176 Server.config.cargoWatchOptions.trace === 'verbose'
177 ) {
178 this.outputChannel.append(line);
179 }
180 }
181
182 private parseLine(line: string) {
183 if (line.startsWith('[Running')) {
184 this.diagnosticCollection.clear();
185 this.suggestedFixCollection.clear();
186 this.statusDisplay.show();
187 }
188
189 if (line.startsWith('[Finished running')) {
190 this.statusDisplay.hide();
191 }
192
193 interface CargoArtifact {
194 reason: string;
195 package_id: string;
196 }
197
198 // https://github.com/rust-lang/cargo/blob/master/src/cargo/util/machine_message.rs
199 interface CargoMessage {
200 reason: string;
201 package_id: string;
202 message: RustDiagnostic;
203 }
204
205 // cargo-watch itself output non json format
206 // Ignore these lines
207 let data: CargoMessage;
208 try {
209 data = JSON.parse(line.trim());
210 } catch (error) {
211 this.logError(`Fail to parse to json : { ${error} }`);
212 return;
213 }
214
215 if (data.reason === 'compiler-artifact') {
216 const msg = data as CargoArtifact;
217
218 // The format of the package_id is "{name} {version} ({source_id})",
219 // https://github.com/rust-lang/cargo/blob/37ad03f86e895bb80b474c1c088322634f4725f5/src/cargo/core/package_id.rs#L53
220 this.statusDisplay.packageName = msg.package_id.split(' ')[0];
221 } else if (data.reason === 'compiler-message') {
222 const msg = data.message as RustDiagnostic;
223
224 const mapResult = mapRustDiagnosticToVsCode(msg);
225 if (!mapResult) {
226 return;
227 }
228
229 const { location, diagnostic, suggestedFixes } = mapResult;
230 const fileUri = location.uri;
231
232 const diagnostics: vscode.Diagnostic[] = [
233 ...(this.diagnosticCollection!.get(fileUri) || []),
234 ];
235
236 // If we're building multiple targets it's possible we've already seen this diagnostic
237 const isDuplicate = diagnostics.some(d =>
238 areDiagnosticsEqual(d, diagnostic),
239 );
240 if (isDuplicate) {
241 return;
242 }
243
244 diagnostics.push(diagnostic);
245 this.diagnosticCollection!.set(fileUri, diagnostics);
246
247 if (suggestedFixes.length) {
248 for (const suggestedFix of suggestedFixes) {
249 this.suggestedFixCollection.addSuggestedFixForDiagnostic(
250 suggestedFix,
251 diagnostic,
252 );
253 }
254
255 // Have VsCode query us for the code actions
256 vscode.commands.executeCommand(
257 'vscode.executeCodeActionProvider',
258 fileUri,
259 diagnostic.range,
260 );
261 }
262 }
263 }
264}
diff --git a/editors/code/src/commands/runnables.ts b/editors/code/src/commands/runnables.ts
index cf980e257..7728541de 100644
--- a/editors/code/src/commands/runnables.ts
+++ b/editors/code/src/commands/runnables.ts
@@ -1,11 +1,7 @@
1import * as child_process from 'child_process';
2
3import * as util from 'util';
4import * as vscode from 'vscode'; 1import * as vscode from 'vscode';
5import * as lc from 'vscode-languageclient'; 2import * as lc from 'vscode-languageclient';
6 3
7import { Server } from '../server'; 4import { Server } from '../server';
8import { CargoWatchProvider, registerCargoWatchProvider } from './cargo_watch';
9 5
10interface RunnablesParams { 6interface RunnablesParams {
11 textDocument: lc.TextDocumentIdentifier; 7 textDocument: lc.TextDocumentIdentifier;
@@ -131,90 +127,3 @@ export async function handleSingle(runnable: Runnable) {
131 127
132 return vscode.tasks.executeTask(task); 128 return vscode.tasks.executeTask(task);
133} 129}
134
135/**
136 * Interactively asks the user whether we should run `cargo check` in order to
137 * provide inline diagnostics; the user is met with a series of dialog boxes
138 * that, when accepted, allow us to `cargo install cargo-watch` and then run it.
139 */
140export async function interactivelyStartCargoWatch(
141 context: vscode.ExtensionContext,
142): Promise<CargoWatchProvider | undefined> {
143 if (Server.config.cargoWatchOptions.enableOnStartup === 'disabled') {
144 return;
145 }
146
147 if (Server.config.cargoWatchOptions.enableOnStartup === 'ask') {
148 const watch = await vscode.window.showInformationMessage(
149 'Start watching changes with cargo? (Executes `cargo watch`, provides inline diagnostics)',
150 'yes',
151 'no',
152 );
153 if (watch !== 'yes') {
154 return;
155 }
156 }
157
158 return startCargoWatch(context);
159}
160
161export async function startCargoWatch(
162 context: vscode.ExtensionContext,
163): Promise<CargoWatchProvider | undefined> {
164 const execPromise = util.promisify(child_process.exec);
165
166 const { stderr, code = 0 } = await execPromise(
167 'cargo watch --version',
168 ).catch(e => e);
169
170 if (stderr.includes('no such subcommand: `watch`')) {
171 const msg =
172 'The `cargo-watch` subcommand is not installed. Install? (takes ~1-2 minutes)';
173 const install = await vscode.window.showInformationMessage(
174 msg,
175 'yes',
176 'no',
177 );
178 if (install !== 'yes') {
179 return;
180 }
181
182 const label = 'install-cargo-watch';
183 const taskFinished = new Promise((resolve, _reject) => {
184 const disposable = vscode.tasks.onDidEndTask(({ execution }) => {
185 if (execution.task.name === label) {
186 disposable.dispose();
187 resolve();
188 }
189 });
190 });
191
192 vscode.tasks.executeTask(
193 createTask({
194 label,
195 bin: 'cargo',
196 args: ['install', 'cargo-watch'],
197 env: {},
198 }),
199 );
200 await taskFinished;
201 const output = await execPromise('cargo watch --version').catch(e => e);
202 if (output.stderr !== '') {
203 vscode.window.showErrorMessage(
204 `Couldn't install \`cargo-\`watch: ${output.stderr}`,
205 );
206 return;
207 }
208 } else if (code !== 0) {
209 vscode.window.showErrorMessage(
210 `\`cargo watch\` failed with ${code}: ${stderr}`,
211 );
212 return;
213 }
214
215 const provider = await registerCargoWatchProvider(context.subscriptions);
216 if (provider) {
217 provider.start();
218 }
219 return provider;
220}
diff --git a/editors/code/src/commands/watch_status.ts b/editors/code/src/commands/watch_status.ts
index 8d64394c7..10787b510 100644
--- a/editors/code/src/commands/watch_status.ts
+++ b/editors/code/src/commands/watch_status.ts
@@ -57,7 +57,49 @@ export class StatusDisplay implements vscode.Disposable {
57 this.statusBarItem.dispose(); 57 this.statusBarItem.dispose();
58 } 58 }
59 59
60 public handleProgressNotification(params: ProgressParams) {
61 const { token, value } = params;
62 if (token !== 'rustAnalyzer/cargoWatcher') {
63 return;
64 }
65
66 switch (value.kind) {
67 case 'begin':
68 this.show();
69 break;
70
71 case 'report':
72 if (value.message) {
73 this.packageName = value.message;
74 }
75 break;
76
77 case 'end':
78 this.hide();
79 break;
80 }
81 }
82
60 private frame() { 83 private frame() {
61 return spinnerFrames[(this.i = ++this.i % spinnerFrames.length)]; 84 return spinnerFrames[(this.i = ++this.i % spinnerFrames.length)];
62 } 85 }
63} 86}
87
88// FIXME: Replace this once vscode-languageclient is updated to LSP 3.15
89interface ProgressParams {
90 token: string;
91 value: WorkDoneProgress;
92}
93
94enum WorkDoneProgressKind {
95 Begin = 'begin',
96 Report = 'report',
97 End = 'end',
98}
99
100interface WorkDoneProgress {
101 kind: WorkDoneProgressKind;
102 message?: string;
103 cancelable?: boolean;
104 percentage?: string;
105}
diff --git a/editors/code/src/config.ts b/editors/code/src/config.ts
index c06dddb1c..a88be6e35 100644
--- a/editors/code/src/config.ts
+++ b/editors/code/src/config.ts
@@ -5,15 +5,10 @@ import { Server } from './server';
5 5
6const RA_LSP_DEBUG = process.env.__RA_LSP_SERVER_DEBUG; 6const RA_LSP_DEBUG = process.env.__RA_LSP_SERVER_DEBUG;
7 7
8export type CargoWatchStartupOptions = 'ask' | 'enabled' | 'disabled';
9export type CargoWatchTraceOptions = 'off' | 'error' | 'verbose';
10
11export interface CargoWatchOptions { 8export interface CargoWatchOptions {
12 enableOnStartup: CargoWatchStartupOptions; 9 enable: boolean;
13 arguments: string; 10 arguments: string[];
14 command: string; 11 command: string;
15 trace: CargoWatchTraceOptions;
16 ignore: string[];
17 allTargets: boolean; 12 allTargets: boolean;
18} 13}
19 14
@@ -37,11 +32,9 @@ export class Config {
37 // for internal use 32 // for internal use
38 public withSysroot: null | boolean = null; 33 public withSysroot: null | boolean = null;
39 public cargoWatchOptions: CargoWatchOptions = { 34 public cargoWatchOptions: CargoWatchOptions = {
40 enableOnStartup: 'ask', 35 enable: true,
41 trace: 'off', 36 arguments: [],
42 arguments: '',
43 command: '', 37 command: '',
44 ignore: [],
45 allTargets: true, 38 allTargets: true,
46 }; 39 };
47 public cargoFeatures: CargoFeatures = { 40 public cargoFeatures: CargoFeatures = {
@@ -104,23 +97,17 @@ export class Config {
104 RA_LSP_DEBUG || (config.get('raLspServerPath') as string); 97 RA_LSP_DEBUG || (config.get('raLspServerPath') as string);
105 } 98 }
106 99
107 if (config.has('enableCargoWatchOnStartup')) { 100 if (config.has('cargo-watch.enable')) {
108 this.cargoWatchOptions.enableOnStartup = config.get< 101 this.cargoWatchOptions.enable = config.get<boolean>(
109 CargoWatchStartupOptions 102 'cargo-watch.enable',
110 >('enableCargoWatchOnStartup', 'ask'); 103 true,
111 }
112
113 if (config.has('trace.cargo-watch')) {
114 this.cargoWatchOptions.trace = config.get<CargoWatchTraceOptions>(
115 'trace.cargo-watch',
116 'off',
117 ); 104 );
118 } 105 }
119 106
120 if (config.has('cargo-watch.arguments')) { 107 if (config.has('cargo-watch.arguments')) {
121 this.cargoWatchOptions.arguments = config.get<string>( 108 this.cargoWatchOptions.arguments = config.get<string[]>(
122 'cargo-watch.arguments', 109 'cargo-watch.arguments',
123 '', 110 [],
124 ); 111 );
125 } 112 }
126 113
@@ -131,13 +118,6 @@ export class Config {
131 ); 118 );
132 } 119 }
133 120
134 if (config.has('cargo-watch.ignore')) {
135 this.cargoWatchOptions.ignore = config.get<string[]>(
136 'cargo-watch.ignore',
137 [],
138 );
139 }
140
141 if (config.has('cargo-watch.allTargets')) { 121 if (config.has('cargo-watch.allTargets')) {
142 this.cargoWatchOptions.allTargets = config.get<boolean>( 122 this.cargoWatchOptions.allTargets = config.get<boolean>(
143 'cargo-watch.allTargets', 123 'cargo-watch.allTargets',
diff --git a/editors/code/src/extension.ts b/editors/code/src/extension.ts
index 815f3692c..1da10ebd0 100644
--- a/editors/code/src/extension.ts
+++ b/editors/code/src/extension.ts
@@ -2,14 +2,10 @@ import * as vscode from 'vscode';
2import * as lc from 'vscode-languageclient'; 2import * as lc from 'vscode-languageclient';
3 3
4import * as commands from './commands'; 4import * as commands from './commands';
5import { CargoWatchProvider } from './commands/cargo_watch';
6import { ExpandMacroContentProvider } from './commands/expand_macro'; 5import { ExpandMacroContentProvider } from './commands/expand_macro';
7import { HintsUpdater } from './commands/inlay_hints'; 6import { HintsUpdater } from './commands/inlay_hints';
8import {
9 interactivelyStartCargoWatch,
10 startCargoWatch,
11} from './commands/runnables';
12import { SyntaxTreeContentProvider } from './commands/syntaxTree'; 7import { SyntaxTreeContentProvider } from './commands/syntaxTree';
8import { StatusDisplay } from './commands/watch_status';
13import * as events from './events'; 9import * as events from './events';
14import * as notifications from './notifications'; 10import * as notifications from './notifications';
15import { Server } from './server'; 11import { Server } from './server';
@@ -88,6 +84,11 @@ export async function activate(context: vscode.ExtensionContext) {
88 overrideCommand('type', commands.onEnter.handle); 84 overrideCommand('type', commands.onEnter.handle);
89 } 85 }
90 86
87 const watchStatus = new StatusDisplay(
88 Server.config.cargoWatchOptions.command,
89 );
90 disposeOnDeactivation(watchStatus);
91
91 // Notifications are events triggered by the language server 92 // Notifications are events triggered by the language server
92 const allNotifications: Iterable<[ 93 const allNotifications: Iterable<[
93 string, 94 string,
@@ -97,6 +98,10 @@ export async function activate(context: vscode.ExtensionContext) {
97 'rust-analyzer/publishDecorations', 98 'rust-analyzer/publishDecorations',
98 notifications.publishDecorations.handle, 99 notifications.publishDecorations.handle,
99 ], 100 ],
101 [
102 '$/progress',
103 params => watchStatus.handleProgressNotification(params),
104 ],
100 ]; 105 ];
101 const syntaxTreeContentProvider = new SyntaxTreeContentProvider(); 106 const syntaxTreeContentProvider = new SyntaxTreeContentProvider();
102 const expandMacroContentProvider = new ExpandMacroContentProvider(); 107 const expandMacroContentProvider = new ExpandMacroContentProvider();
@@ -139,26 +144,6 @@ export async function activate(context: vscode.ExtensionContext) {
139 144
140 vscode.commands.registerCommand('rust-analyzer.reload', reloadCommand); 145 vscode.commands.registerCommand('rust-analyzer.reload', reloadCommand);
141 146
142 // Executing `cargo watch` provides us with inline diagnostics on save
143 let provider: CargoWatchProvider | undefined;
144 interactivelyStartCargoWatch(context).then(p => {
145 provider = p;
146 });
147 registerCommand('rust-analyzer.startCargoWatch', () => {
148 if (provider) {
149 provider.start();
150 } else {
151 startCargoWatch(context).then(p => {
152 provider = p;
153 });
154 }
155 });
156 registerCommand('rust-analyzer.stopCargoWatch', () => {
157 if (provider) {
158 provider.stop();
159 }
160 });
161
162 // Start the language server, finally! 147 // Start the language server, finally!
163 try { 148 try {
164 await startServer(); 149 await startServer();
diff --git a/editors/code/src/server.ts b/editors/code/src/server.ts
index 5ace1d0fa..ae81af848 100644
--- a/editors/code/src/server.ts
+++ b/editors/code/src/server.ts
@@ -55,6 +55,11 @@ export class Server {
55 publishDecorations: true, 55 publishDecorations: true,
56 lruCapacity: Server.config.lruCapacity, 56 lruCapacity: Server.config.lruCapacity,
57 maxInlayHintLength: Server.config.maxInlayHintLength, 57 maxInlayHintLength: Server.config.maxInlayHintLength,
58 cargoWatchEnable: Server.config.cargoWatchOptions.enable,
59 cargoWatchArgumets: Server.config.cargoWatchOptions.arguments,
60 cargoWatchCommand: Server.config.cargoWatchOptions.command,
61 cargoWatchAllTargets:
62 Server.config.cargoWatchOptions.allTargets,
58 excludeGlobs: Server.config.excludeGlobs, 63 excludeGlobs: Server.config.excludeGlobs,
59 useClientWatching: Server.config.useClientWatching, 64 useClientWatching: Server.config.useClientWatching,
60 featureFlags: Server.config.featureFlags, 65 featureFlags: Server.config.featureFlags,
diff --git a/editors/code/src/test/fixtures/rust-diagnostics/clippy/trivially_copy_pass_by_ref.json b/editors/code/src/test/fixtures/rust-diagnostics/clippy/trivially_copy_pass_by_ref.json
deleted file mode 100644
index d874e99bc..000000000
--- a/editors/code/src/test/fixtures/rust-diagnostics/clippy/trivially_copy_pass_by_ref.json
+++ /dev/null
@@ -1,110 +0,0 @@
1{
2 "message": "this argument is passed by reference, but would be more efficient if passed by value",
3 "code": {
4 "code": "clippy::trivially_copy_pass_by_ref",
5 "explanation": null
6 },
7 "level": "warning",
8 "spans": [
9 {
10 "file_name": "compiler/mir/tagset.rs",
11 "byte_start": 941,
12 "byte_end": 946,
13 "line_start": 42,
14 "line_end": 42,
15 "column_start": 24,
16 "column_end": 29,
17 "is_primary": true,
18 "text": [
19 {
20 "text": " pub fn is_disjoint(&self, other: Self) -> bool {",
21 "highlight_start": 24,
22 "highlight_end": 29
23 }
24 ],
25 "label": null,
26 "suggested_replacement": null,
27 "suggestion_applicability": null,
28 "expansion": null
29 }
30 ],
31 "children": [
32 {
33 "message": "lint level defined here",
34 "code": null,
35 "level": "note",
36 "spans": [
37 {
38 "file_name": "compiler/lib.rs",
39 "byte_start": 8,
40 "byte_end": 19,
41 "line_start": 1,
42 "line_end": 1,
43 "column_start": 9,
44 "column_end": 20,
45 "is_primary": true,
46 "text": [
47 {
48 "text": "#![warn(clippy::all)]",
49 "highlight_start": 9,
50 "highlight_end": 20
51 }
52 ],
53 "label": null,
54 "suggested_replacement": null,
55 "suggestion_applicability": null,
56 "expansion": null
57 }
58 ],
59 "children": [],
60 "rendered": null
61 },
62 {
63 "message": "#[warn(clippy::trivially_copy_pass_by_ref)] implied by #[warn(clippy::all)]",
64 "code": null,
65 "level": "note",
66 "spans": [],
67 "children": [],
68 "rendered": null
69 },
70 {
71 "message": "for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#trivially_copy_pass_by_ref",
72 "code": null,
73 "level": "help",
74 "spans": [],
75 "children": [],
76 "rendered": null
77 },
78 {
79 "message": "consider passing by value instead",
80 "code": null,
81 "level": "help",
82 "spans": [
83 {
84 "file_name": "compiler/mir/tagset.rs",
85 "byte_start": 941,
86 "byte_end": 946,
87 "line_start": 42,
88 "line_end": 42,
89 "column_start": 24,
90 "column_end": 29,
91 "is_primary": true,
92 "text": [
93 {
94 "text": " pub fn is_disjoint(&self, other: Self) -> bool {",
95 "highlight_start": 24,
96 "highlight_end": 29
97 }
98 ],
99 "label": null,
100 "suggested_replacement": "self",
101 "suggestion_applicability": "Unspecified",
102 "expansion": null
103 }
104 ],
105 "children": [],
106 "rendered": null
107 }
108 ],
109 "rendered": "warning: this argument is passed by reference, but would be more efficient if passed by value\n --> compiler/mir/tagset.rs:42:24\n |\n42 | pub fn is_disjoint(&self, other: Self) -> bool {\n | ^^^^^ help: consider passing by value instead: `self`\n |\nnote: lint level defined here\n --> compiler/lib.rs:1:9\n |\n1 | #![warn(clippy::all)]\n | ^^^^^^^^^^^\n = note: #[warn(clippy::trivially_copy_pass_by_ref)] implied by #[warn(clippy::all)]\n = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#trivially_copy_pass_by_ref\n\n"
110}
diff --git a/editors/code/src/test/fixtures/rust-diagnostics/error/E0053.json b/editors/code/src/test/fixtures/rust-diagnostics/error/E0053.json
deleted file mode 100644
index ea5c976d1..000000000
--- a/editors/code/src/test/fixtures/rust-diagnostics/error/E0053.json
+++ /dev/null
@@ -1,42 +0,0 @@
1{
2 "message": "method `next` has an incompatible type for trait",
3 "code": {
4 "code": "E0053",
5 "explanation": "\nThe parameters of any trait method must match between a trait implementation\nand the trait definition.\n\nHere are a couple examples of this error:\n\n```compile_fail,E0053\ntrait Foo {\n fn foo(x: u16);\n fn bar(&self);\n}\n\nstruct Bar;\n\nimpl Foo for Bar {\n // error, expected u16, found i16\n fn foo(x: i16) { }\n\n // error, types differ in mutability\n fn bar(&mut self) { }\n}\n```\n"
6 },
7 "level": "error",
8 "spans": [
9 {
10 "file_name": "compiler/ty/list_iter.rs",
11 "byte_start": 1307,
12 "byte_end": 1350,
13 "line_start": 52,
14 "line_end": 52,
15 "column_start": 5,
16 "column_end": 48,
17 "is_primary": true,
18 "text": [
19 {
20 "text": " fn next(&self) -> Option<&'list ty::Ref<M>> {",
21 "highlight_start": 5,
22 "highlight_end": 48
23 }
24 ],
25 "label": "types differ in mutability",
26 "suggested_replacement": null,
27 "suggestion_applicability": null,
28 "expansion": null
29 }
30 ],
31 "children": [
32 {
33 "message": "expected type `fn(&mut ty::list_iter::ListIterator<'list, M>) -> std::option::Option<&ty::Ref<M>>`\n found type `fn(&ty::list_iter::ListIterator<'list, M>) -> std::option::Option<&'list ty::Ref<M>>`",
34 "code": null,
35 "level": "note",
36 "spans": [],
37 "children": [],
38 "rendered": null
39 }
40 ],
41 "rendered": "error[E0053]: method `next` has an incompatible type for trait\n --> compiler/ty/list_iter.rs:52:5\n |\n52 | fn next(&self) -> Option<&'list ty::Ref<M>> {\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ types differ in mutability\n |\n = note: expected type `fn(&mut ty::list_iter::ListIterator<'list, M>) -> std::option::Option<&ty::Ref<M>>`\n found type `fn(&ty::list_iter::ListIterator<'list, M>) -> std::option::Option<&'list ty::Ref<M>>`\n\n"
42}
diff --git a/editors/code/src/test/fixtures/rust-diagnostics/error/E0061.json b/editors/code/src/test/fixtures/rust-diagnostics/error/E0061.json
deleted file mode 100644
index 3154d1098..000000000
--- a/editors/code/src/test/fixtures/rust-diagnostics/error/E0061.json
+++ /dev/null
@@ -1,114 +0,0 @@
1{
2 "message": "this function takes 2 parameters but 3 parameters were supplied",
3 "code": {
4 "code": "E0061",
5 "explanation": "\nThe number of arguments passed to a function must match the number of arguments\nspecified in the function signature.\n\nFor example, a function like:\n\n```\nfn f(a: u16, b: &str) {}\n```\n\nMust always be called with exactly two arguments, e.g., `f(2, \"test\")`.\n\nNote that Rust does not have a notion of optional function arguments or\nvariadic functions (except for its C-FFI).\n"
6 },
7 "level": "error",
8 "spans": [
9 {
10 "file_name": "compiler/ty/select.rs",
11 "byte_start": 8787,
12 "byte_end": 9241,
13 "line_start": 219,
14 "line_end": 231,
15 "column_start": 5,
16 "column_end": 6,
17 "is_primary": false,
18 "text": [
19 {
20 "text": " pub fn add_evidence(",
21 "highlight_start": 5,
22 "highlight_end": 25
23 },
24 {
25 "text": " &mut self,",
26 "highlight_start": 1,
27 "highlight_end": 19
28 },
29 {
30 "text": " target_poly: &ty::Ref<ty::Poly>,",
31 "highlight_start": 1,
32 "highlight_end": 41
33 },
34 {
35 "text": " evidence_poly: &ty::Ref<ty::Poly>,",
36 "highlight_start": 1,
37 "highlight_end": 43
38 },
39 {
40 "text": " ) {",
41 "highlight_start": 1,
42 "highlight_end": 8
43 },
44 {
45 "text": " match target_poly {",
46 "highlight_start": 1,
47 "highlight_end": 28
48 },
49 {
50 "text": " ty::Ref::Var(tvar, _) => self.add_var_evidence(tvar, evidence_poly),",
51 "highlight_start": 1,
52 "highlight_end": 81
53 },
54 {
55 "text": " ty::Ref::Fixed(target_ty) => {",
56 "highlight_start": 1,
57 "highlight_end": 43
58 },
59 {
60 "text": " let evidence_ty = evidence_poly.resolve_to_ty();",
61 "highlight_start": 1,
62 "highlight_end": 65
63 },
64 {
65 "text": " self.add_evidence_ty(target_ty, evidence_poly, evidence_ty)",
66 "highlight_start": 1,
67 "highlight_end": 76
68 },
69 {
70 "text": " }",
71 "highlight_start": 1,
72 "highlight_end": 14
73 },
74 {
75 "text": " }",
76 "highlight_start": 1,
77 "highlight_end": 10
78 },
79 {
80 "text": " }",
81 "highlight_start": 1,
82 "highlight_end": 6
83 }
84 ],
85 "label": "defined here",
86 "suggested_replacement": null,
87 "suggestion_applicability": null,
88 "expansion": null
89 },
90 {
91 "file_name": "compiler/ty/select.rs",
92 "byte_start": 4045,
93 "byte_end": 4057,
94 "line_start": 104,
95 "line_end": 104,
96 "column_start": 18,
97 "column_end": 30,
98 "is_primary": true,
99 "text": [
100 {
101 "text": " self.add_evidence(target_fixed, evidence_fixed, false);",
102 "highlight_start": 18,
103 "highlight_end": 30
104 }
105 ],
106 "label": "expected 2 parameters",
107 "suggested_replacement": null,
108 "suggestion_applicability": null,
109 "expansion": null
110 }
111 ],
112 "children": [],
113 "rendered": "error[E0061]: this function takes 2 parameters but 3 parameters were supplied\n --> compiler/ty/select.rs:104:18\n |\n104 | self.add_evidence(target_fixed, evidence_fixed, false);\n | ^^^^^^^^^^^^ expected 2 parameters\n...\n219 | / pub fn add_evidence(\n220 | | &mut self,\n221 | | target_poly: &ty::Ref<ty::Poly>,\n222 | | evidence_poly: &ty::Ref<ty::Poly>,\n... |\n230 | | }\n231 | | }\n | |_____- defined here\n\n"
114}
diff --git a/editors/code/src/test/fixtures/rust-diagnostics/error/E0277.json b/editors/code/src/test/fixtures/rust-diagnostics/error/E0277.json
deleted file mode 100644
index bfef33c7d..000000000
--- a/editors/code/src/test/fixtures/rust-diagnostics/error/E0277.json
+++ /dev/null
@@ -1,261 +0,0 @@
1{
2 "rendered": "error[E0277]: can't compare `{integer}` with `&str`\n --> src/main.rs:2:5\n |\n2 | assert_eq!(1, \"love\");\n | ^^^^^^^^^^^^^^^^^^^^^^ no implementation for `{integer} == &str`\n |\n = help: the trait `std::cmp::PartialEq<&str>` is not implemented for `{integer}`\n = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info)\n\n",
3 "children": [
4 {
5 "children": [],
6 "code": null,
7 "level": "help",
8 "message": "the trait `std::cmp::PartialEq<&str>` is not implemented for `{integer}`",
9 "rendered": null,
10 "spans": []
11 }
12 ],
13 "code": {
14 "code": "E0277",
15 "explanation": "\nYou tried to use a type which doesn't implement some trait in a place which\nexpected that trait. Erroneous code example:\n\n```compile_fail,E0277\n// here we declare the Foo trait with a bar method\ntrait Foo {\n fn bar(&self);\n}\n\n// we now declare a function which takes an object implementing the Foo trait\nfn some_func<T: Foo>(foo: T) {\n foo.bar();\n}\n\nfn main() {\n // we now call the method with the i32 type, which doesn't implement\n // the Foo trait\n some_func(5i32); // error: the trait bound `i32 : Foo` is not satisfied\n}\n```\n\nIn order to fix this error, verify that the type you're using does implement\nthe trait. Example:\n\n```\ntrait Foo {\n fn bar(&self);\n}\n\nfn some_func<T: Foo>(foo: T) {\n foo.bar(); // we can now use this method since i32 implements the\n // Foo trait\n}\n\n// we implement the trait on the i32 type\nimpl Foo for i32 {\n fn bar(&self) {}\n}\n\nfn main() {\n some_func(5i32); // ok!\n}\n```\n\nOr in a generic context, an erroneous code example would look like:\n\n```compile_fail,E0277\nfn some_func<T>(foo: T) {\n println!(\"{:?}\", foo); // error: the trait `core::fmt::Debug` is not\n // implemented for the type `T`\n}\n\nfn main() {\n // We now call the method with the i32 type,\n // which *does* implement the Debug trait.\n some_func(5i32);\n}\n```\n\nNote that the error here is in the definition of the generic function: Although\nwe only call it with a parameter that does implement `Debug`, the compiler\nstill rejects the function: It must work with all possible input types. In\norder to make this example compile, we need to restrict the generic type we're\naccepting:\n\n```\nuse std::fmt;\n\n// Restrict the input type to types that implement Debug.\nfn some_func<T: fmt::Debug>(foo: T) {\n println!(\"{:?}\", foo);\n}\n\nfn main() {\n // Calling the method is still fine, as i32 implements Debug.\n some_func(5i32);\n\n // This would fail to compile now:\n // struct WithoutDebug;\n // some_func(WithoutDebug);\n}\n```\n\nRust only looks at the signature of the called function, as such it must\nalready specify all requirements that will be used for every type parameter.\n"
16 },
17 "level": "error",
18 "message": "can't compare `{integer}` with `&str`",
19 "spans": [
20 {
21 "byte_end": 155,
22 "byte_start": 153,
23 "column_end": 33,
24 "column_start": 31,
25 "expansion": {
26 "def_site_span": {
27 "byte_end": 940,
28 "byte_start": 0,
29 "column_end": 6,
30 "column_start": 1,
31 "expansion": null,
32 "file_name": "<::core::macros::assert_eq macros>",
33 "is_primary": false,
34 "label": null,
35 "line_end": 36,
36 "line_start": 1,
37 "suggested_replacement": null,
38 "suggestion_applicability": null,
39 "text": [
40 {
41 "highlight_end": 35,
42 "highlight_start": 1,
43 "text": "($ left : expr, $ right : expr) =>"
44 },
45 {
46 "highlight_end": 3,
47 "highlight_start": 1,
48 "text": "({"
49 },
50 {
51 "highlight_end": 33,
52 "highlight_start": 1,
53 "text": " match (& $ left, & $ right)"
54 },
55 {
56 "highlight_end": 7,
57 "highlight_start": 1,
58 "text": " {"
59 },
60 {
61 "highlight_end": 34,
62 "highlight_start": 1,
63 "text": " (left_val, right_val) =>"
64 },
65 {
66 "highlight_end": 11,
67 "highlight_start": 1,
68 "text": " {"
69 },
70 {
71 "highlight_end": 46,
72 "highlight_start": 1,
73 "text": " if ! (* left_val == * right_val)"
74 },
75 {
76 "highlight_end": 15,
77 "highlight_start": 1,
78 "text": " {"
79 },
80 {
81 "highlight_end": 25,
82 "highlight_start": 1,
83 "text": " panic !"
84 },
85 {
86 "highlight_end": 57,
87 "highlight_start": 1,
88 "text": " (r#\"assertion failed: `(left == right)`"
89 },
90 {
91 "highlight_end": 16,
92 "highlight_start": 1,
93 "text": " left: `{:?}`,"
94 },
95 {
96 "highlight_end": 18,
97 "highlight_start": 1,
98 "text": " right: `{:?}`\"#,"
99 },
100 {
101 "highlight_end": 47,
102 "highlight_start": 1,
103 "text": " & * left_val, & * right_val)"
104 },
105 {
106 "highlight_end": 15,
107 "highlight_start": 1,
108 "text": " }"
109 },
110 {
111 "highlight_end": 11,
112 "highlight_start": 1,
113 "text": " }"
114 },
115 {
116 "highlight_end": 7,
117 "highlight_start": 1,
118 "text": " }"
119 },
120 {
121 "highlight_end": 42,
122 "highlight_start": 1,
123 "text": " }) ; ($ left : expr, $ right : expr,) =>"
124 },
125 {
126 "highlight_end": 49,
127 "highlight_start": 1,
128 "text": "({ $ crate :: assert_eq ! ($ left, $ right) }) ;"
129 },
130 {
131 "highlight_end": 53,
132 "highlight_start": 1,
133 "text": "($ left : expr, $ right : expr, $ ($ arg : tt) +) =>"
134 },
135 {
136 "highlight_end": 3,
137 "highlight_start": 1,
138 "text": "({"
139 },
140 {
141 "highlight_end": 37,
142 "highlight_start": 1,
143 "text": " match (& ($ left), & ($ right))"
144 },
145 {
146 "highlight_end": 7,
147 "highlight_start": 1,
148 "text": " {"
149 },
150 {
151 "highlight_end": 34,
152 "highlight_start": 1,
153 "text": " (left_val, right_val) =>"
154 },
155 {
156 "highlight_end": 11,
157 "highlight_start": 1,
158 "text": " {"
159 },
160 {
161 "highlight_end": 46,
162 "highlight_start": 1,
163 "text": " if ! (* left_val == * right_val)"
164 },
165 {
166 "highlight_end": 15,
167 "highlight_start": 1,
168 "text": " {"
169 },
170 {
171 "highlight_end": 25,
172 "highlight_start": 1,
173 "text": " panic !"
174 },
175 {
176 "highlight_end": 57,
177 "highlight_start": 1,
178 "text": " (r#\"assertion failed: `(left == right)`"
179 },
180 {
181 "highlight_end": 16,
182 "highlight_start": 1,
183 "text": " left: `{:?}`,"
184 },
185 {
186 "highlight_end": 22,
187 "highlight_start": 1,
188 "text": " right: `{:?}`: {}\"#,"
189 },
190 {
191 "highlight_end": 72,
192 "highlight_start": 1,
193 "text": " & * left_val, & * right_val, $ crate :: format_args !"
194 },
195 {
196 "highlight_end": 33,
197 "highlight_start": 1,
198 "text": " ($ ($ arg) +))"
199 },
200 {
201 "highlight_end": 15,
202 "highlight_start": 1,
203 "text": " }"
204 },
205 {
206 "highlight_end": 11,
207 "highlight_start": 1,
208 "text": " }"
209 },
210 {
211 "highlight_end": 7,
212 "highlight_start": 1,
213 "text": " }"
214 },
215 {
216 "highlight_end": 6,
217 "highlight_start": 1,
218 "text": " }) ;"
219 }
220 ]
221 },
222 "macro_decl_name": "assert_eq!",
223 "span": {
224 "byte_end": 38,
225 "byte_start": 16,
226 "column_end": 27,
227 "column_start": 5,
228 "expansion": null,
229 "file_name": "src/main.rs",
230 "is_primary": false,
231 "label": null,
232 "line_end": 2,
233 "line_start": 2,
234 "suggested_replacement": null,
235 "suggestion_applicability": null,
236 "text": [
237 {
238 "highlight_end": 27,
239 "highlight_start": 5,
240 "text": " assert_eq!(1, \"love\");"
241 }
242 ]
243 }
244 },
245 "file_name": "<::core::macros::assert_eq macros>",
246 "is_primary": true,
247 "label": "no implementation for `{integer} == &str`",
248 "line_end": 7,
249 "line_start": 7,
250 "suggested_replacement": null,
251 "suggestion_applicability": null,
252 "text": [
253 {
254 "highlight_end": 33,
255 "highlight_start": 31,
256 "text": " if ! (* left_val == * right_val)"
257 }
258 ]
259 }
260 ]
261}
diff --git a/editors/code/src/test/fixtures/rust-diagnostics/error/E0308.json b/editors/code/src/test/fixtures/rust-diagnostics/error/E0308.json
deleted file mode 100644
index fb23824a3..000000000
--- a/editors/code/src/test/fixtures/rust-diagnostics/error/E0308.json
+++ /dev/null
@@ -1,33 +0,0 @@
1{
2 "message": "mismatched types",
3 "code": {
4 "code": "E0308",
5 "explanation": "\nThis error occurs when the compiler was unable to infer the concrete type of a\nvariable. It can occur for several cases, the most common of which is a\nmismatch in the expected type that the compiler inferred for a variable's\ninitializing expression, and the actual type explicitly assigned to the\nvariable.\n\nFor example:\n\n```compile_fail,E0308\nlet x: i32 = \"I am not a number!\";\n// ~~~ ~~~~~~~~~~~~~~~~~~~~\n// | |\n// | initializing expression;\n// | compiler infers type `&str`\n// |\n// type `i32` assigned to variable `x`\n```\n"
6 },
7 "level": "error",
8 "spans": [
9 {
10 "file_name": "runtime/compiler_support.rs",
11 "byte_start": 1589,
12 "byte_end": 1594,
13 "line_start": 48,
14 "line_end": 48,
15 "column_start": 65,
16 "column_end": 70,
17 "is_primary": true,
18 "text": [
19 {
20 "text": " let layout = alloc::Layout::from_size_align_unchecked(size, align);",
21 "highlight_start": 65,
22 "highlight_end": 70
23 }
24 ],
25 "label": "expected usize, found u32",
26 "suggested_replacement": null,
27 "suggestion_applicability": null,
28 "expansion": null
29 }
30 ],
31 "children": [],
32 "rendered": "error[E0308]: mismatched types\n --> runtime/compiler_support.rs:48:65\n |\n48 | let layout = alloc::Layout::from_size_align_unchecked(size, align);\n | ^^^^^ expected usize, found u32\n\n"
33}
diff --git a/editors/code/src/test/fixtures/rust-diagnostics/warning/unused_variables.json b/editors/code/src/test/fixtures/rust-diagnostics/warning/unused_variables.json
deleted file mode 100644
index d1e2be722..000000000
--- a/editors/code/src/test/fixtures/rust-diagnostics/warning/unused_variables.json
+++ /dev/null
@@ -1,72 +0,0 @@
1{
2 "message": "unused variable: `foo`",
3 "code": {
4 "code": "unused_variables",
5 "explanation": null
6 },
7 "level": "warning",
8 "spans": [
9 {
10 "file_name": "driver/subcommand/repl.rs",
11 "byte_start": 9228,
12 "byte_end": 9231,
13 "line_start": 291,
14 "line_end": 291,
15 "column_start": 9,
16 "column_end": 12,
17 "is_primary": true,
18 "text": [
19 {
20 "text": " let foo = 42;",
21 "highlight_start": 9,
22 "highlight_end": 12
23 }
24 ],
25 "label": null,
26 "suggested_replacement": null,
27 "suggestion_applicability": null,
28 "expansion": null
29 }
30 ],
31 "children": [
32 {
33 "message": "#[warn(unused_variables)] on by default",
34 "code": null,
35 "level": "note",
36 "spans": [],
37 "children": [],
38 "rendered": null
39 },
40 {
41 "message": "consider prefixing with an underscore",
42 "code": null,
43 "level": "help",
44 "spans": [
45 {
46 "file_name": "driver/subcommand/repl.rs",
47 "byte_start": 9228,
48 "byte_end": 9231,
49 "line_start": 291,
50 "line_end": 291,
51 "column_start": 9,
52 "column_end": 12,
53 "is_primary": true,
54 "text": [
55 {
56 "text": " let foo = 42;",
57 "highlight_start": 9,
58 "highlight_end": 12
59 }
60 ],
61 "label": null,
62 "suggested_replacement": "_foo",
63 "suggestion_applicability": "MachineApplicable",
64 "expansion": null
65 }
66 ],
67 "children": [],
68 "rendered": null
69 }
70 ],
71 "rendered": "warning: unused variable: `foo`\n --> driver/subcommand/repl.rs:291:9\n |\n291 | let foo = 42;\n | ^^^ help: consider prefixing with an underscore: `_foo`\n |\n = note: #[warn(unused_variables)] on by default\n\n"
72}
diff --git a/editors/code/src/test/utils/diagnotics/SuggestedFix.test.ts b/editors/code/src/test/utils/diagnotics/SuggestedFix.test.ts
deleted file mode 100644
index 2b25eb705..000000000
--- a/editors/code/src/test/utils/diagnotics/SuggestedFix.test.ts
+++ /dev/null
@@ -1,134 +0,0 @@
1import * as assert from 'assert';
2import * as vscode from 'vscode';
3
4import { SuggestionApplicability } from '../../../utils/diagnostics/rust';
5import SuggestedFix from '../../../utils/diagnostics/SuggestedFix';
6
7const location1 = new vscode.Location(
8 vscode.Uri.file('/file/1'),
9 new vscode.Range(new vscode.Position(1, 2), new vscode.Position(3, 4)),
10);
11
12const location2 = new vscode.Location(
13 vscode.Uri.file('/file/2'),
14 new vscode.Range(new vscode.Position(5, 6), new vscode.Position(7, 8)),
15);
16
17describe('SuggestedFix', () => {
18 describe('isEqual', () => {
19 it('should treat identical instances as equal', () => {
20 const suggestion1 = new SuggestedFix(
21 'Replace me!',
22 location1,
23 'With this!',
24 );
25
26 const suggestion2 = new SuggestedFix(
27 'Replace me!',
28 location1,
29 'With this!',
30 );
31
32 assert(suggestion1.isEqual(suggestion2));
33 });
34
35 it('should treat instances with different titles as inequal', () => {
36 const suggestion1 = new SuggestedFix(
37 'Replace me!',
38 location1,
39 'With this!',
40 );
41
42 const suggestion2 = new SuggestedFix(
43 'Not the same title!',
44 location1,
45 'With this!',
46 );
47
48 assert(!suggestion1.isEqual(suggestion2));
49 });
50
51 it('should treat instances with different replacements as inequal', () => {
52 const suggestion1 = new SuggestedFix(
53 'Replace me!',
54 location1,
55 'With this!',
56 );
57
58 const suggestion2 = new SuggestedFix(
59 'Replace me!',
60 location1,
61 'With something else!',
62 );
63
64 assert(!suggestion1.isEqual(suggestion2));
65 });
66
67 it('should treat instances with different locations as inequal', () => {
68 const suggestion1 = new SuggestedFix(
69 'Replace me!',
70 location1,
71 'With this!',
72 );
73
74 const suggestion2 = new SuggestedFix(
75 'Replace me!',
76 location2,
77 'With this!',
78 );
79
80 assert(!suggestion1.isEqual(suggestion2));
81 });
82
83 it('should treat instances with different applicability as inequal', () => {
84 const suggestion1 = new SuggestedFix(
85 'Replace me!',
86 location1,
87 'With this!',
88 SuggestionApplicability.MachineApplicable,
89 );
90
91 const suggestion2 = new SuggestedFix(
92 'Replace me!',
93 location2,
94 'With this!',
95 SuggestionApplicability.HasPlaceholders,
96 );
97
98 assert(!suggestion1.isEqual(suggestion2));
99 });
100 });
101
102 describe('toCodeAction', () => {
103 it('should map a simple suggestion', () => {
104 const suggestion = new SuggestedFix(
105 'Replace me!',
106 location1,
107 'With this!',
108 );
109
110 const codeAction = suggestion.toCodeAction();
111 assert.strictEqual(codeAction.kind, vscode.CodeActionKind.QuickFix);
112 assert.strictEqual(codeAction.title, 'Replace me!');
113 assert.strictEqual(codeAction.isPreferred, false);
114
115 const edit = codeAction.edit;
116 if (!edit) {
117 assert.fail('Code Action edit unexpectedly missing');
118 return;
119 }
120
121 const editEntries = edit.entries();
122 assert.strictEqual(editEntries.length, 1);
123
124 const [[editUri, textEdits]] = editEntries;
125 assert.strictEqual(editUri.toString(), location1.uri.toString());
126
127 assert.strictEqual(textEdits.length, 1);
128 const [textEdit] = textEdits;
129
130 assert(textEdit.range.isEqual(location1.range));
131 assert.strictEqual(textEdit.newText, 'With this!');
132 });
133 });
134});
diff --git a/editors/code/src/test/utils/diagnotics/SuggestedFixCollection.test.ts b/editors/code/src/test/utils/diagnotics/SuggestedFixCollection.test.ts
deleted file mode 100644
index ef09013f4..000000000
--- a/editors/code/src/test/utils/diagnotics/SuggestedFixCollection.test.ts
+++ /dev/null
@@ -1,127 +0,0 @@
1import * as assert from 'assert';
2import * as vscode from 'vscode';
3
4import SuggestedFix from '../../../utils/diagnostics/SuggestedFix';
5import SuggestedFixCollection from '../../../utils/diagnostics/SuggestedFixCollection';
6
7const uri1 = vscode.Uri.file('/file/1');
8const uri2 = vscode.Uri.file('/file/2');
9
10const mockDocument1 = ({
11 uri: uri1,
12} as unknown) as vscode.TextDocument;
13
14const mockDocument2 = ({
15 uri: uri2,
16} as unknown) as vscode.TextDocument;
17
18const range1 = new vscode.Range(
19 new vscode.Position(1, 2),
20 new vscode.Position(3, 4),
21);
22const range2 = new vscode.Range(
23 new vscode.Position(5, 6),
24 new vscode.Position(7, 8),
25);
26
27const diagnostic1 = new vscode.Diagnostic(range1, 'First diagnostic');
28const diagnostic2 = new vscode.Diagnostic(range2, 'Second diagnostic');
29
30// This is a mutable object so return a fresh instance every time
31function suggestion1(): SuggestedFix {
32 return new SuggestedFix(
33 'Replace me!',
34 new vscode.Location(uri1, range1),
35 'With this!',
36 );
37}
38
39describe('SuggestedFixCollection', () => {
40 it('should add a suggestion then return it as a code action', () => {
41 const suggestedFixes = new SuggestedFixCollection();
42 suggestedFixes.addSuggestedFixForDiagnostic(suggestion1(), diagnostic1);
43
44 // Specify the document and range that exactly matches
45 const codeActions = suggestedFixes.provideCodeActions(
46 mockDocument1,
47 range1,
48 );
49
50 assert.strictEqual(codeActions.length, 1);
51 const [codeAction] = codeActions;
52 assert.strictEqual(codeAction.title, suggestion1().title);
53
54 const { diagnostics } = codeAction;
55 if (!diagnostics) {
56 assert.fail('Diagnostics unexpectedly missing');
57 return;
58 }
59
60 assert.strictEqual(diagnostics.length, 1);
61 assert.strictEqual(diagnostics[0], diagnostic1);
62 });
63
64 it('should not return code actions for different ranges', () => {
65 const suggestedFixes = new SuggestedFixCollection();
66 suggestedFixes.addSuggestedFixForDiagnostic(suggestion1(), diagnostic1);
67
68 const codeActions = suggestedFixes.provideCodeActions(
69 mockDocument1,
70 range2,
71 );
72
73 assert(!codeActions || codeActions.length === 0);
74 });
75
76 it('should not return code actions for different documents', () => {
77 const suggestedFixes = new SuggestedFixCollection();
78 suggestedFixes.addSuggestedFixForDiagnostic(suggestion1(), diagnostic1);
79
80 const codeActions = suggestedFixes.provideCodeActions(
81 mockDocument2,
82 range1,
83 );
84
85 assert(!codeActions || codeActions.length === 0);
86 });
87
88 it('should not return code actions that have been cleared', () => {
89 const suggestedFixes = new SuggestedFixCollection();
90 suggestedFixes.addSuggestedFixForDiagnostic(suggestion1(), diagnostic1);
91 suggestedFixes.clear();
92
93 const codeActions = suggestedFixes.provideCodeActions(
94 mockDocument1,
95 range1,
96 );
97
98 assert(!codeActions || codeActions.length === 0);
99 });
100
101 it('should merge identical suggestions together', () => {
102 const suggestedFixes = new SuggestedFixCollection();
103
104 // Add the same suggestion for two diagnostics
105 suggestedFixes.addSuggestedFixForDiagnostic(suggestion1(), diagnostic1);
106 suggestedFixes.addSuggestedFixForDiagnostic(suggestion1(), diagnostic2);
107
108 const codeActions = suggestedFixes.provideCodeActions(
109 mockDocument1,
110 range1,
111 );
112
113 assert.strictEqual(codeActions.length, 1);
114 const [codeAction] = codeActions;
115 const { diagnostics } = codeAction;
116
117 if (!diagnostics) {
118 assert.fail('Diagnostics unexpectedly missing');
119 return;
120 }
121
122 // We should be associated with both diagnostics
123 assert.strictEqual(diagnostics.length, 2);
124 assert.strictEqual(diagnostics[0], diagnostic1);
125 assert.strictEqual(diagnostics[1], diagnostic2);
126 });
127});
diff --git a/editors/code/src/test/utils/diagnotics/rust.test.ts b/editors/code/src/test/utils/diagnotics/rust.test.ts
deleted file mode 100644
index 358325cc8..000000000
--- a/editors/code/src/test/utils/diagnotics/rust.test.ts
+++ /dev/null
@@ -1,236 +0,0 @@
1import * as assert from 'assert';
2import * as fs from 'fs';
3import * as vscode from 'vscode';
4
5import {
6 MappedRustDiagnostic,
7 mapRustDiagnosticToVsCode,
8 RustDiagnostic,
9 SuggestionApplicability,
10} from '../../../utils/diagnostics/rust';
11
12function loadDiagnosticFixture(name: string): RustDiagnostic {
13 const jsonText = fs
14 .readFileSync(
15 // We're actually in our JavaScript output directory, climb out
16 `${__dirname}/../../../../src/test/fixtures/rust-diagnostics/${name}.json`,
17 )
18 .toString();
19
20 return JSON.parse(jsonText);
21}
22
23function mapFixtureToVsCode(name: string): MappedRustDiagnostic {
24 const rd = loadDiagnosticFixture(name);
25 const mapResult = mapRustDiagnosticToVsCode(rd);
26
27 if (!mapResult) {
28 return assert.fail('Mapping unexpectedly failed');
29 }
30 return mapResult;
31}
32
33describe('mapRustDiagnosticToVsCode', () => {
34 it('should map an incompatible type for trait error', () => {
35 const { diagnostic, suggestedFixes } = mapFixtureToVsCode(
36 'error/E0053',
37 );
38
39 assert.strictEqual(
40 diagnostic.severity,
41 vscode.DiagnosticSeverity.Error,
42 );
43 assert.strictEqual(diagnostic.source, 'rustc');
44 assert.strictEqual(
45 diagnostic.message,
46 [
47 `method \`next\` has an incompatible type for trait`,
48 `expected type \`fn(&mut ty::list_iter::ListIterator<'list, M>) -> std::option::Option<&ty::Ref<M>>\``,
49 ` found type \`fn(&ty::list_iter::ListIterator<'list, M>) -> std::option::Option<&'list ty::Ref<M>>\``,
50 ].join('\n'),
51 );
52 assert.strictEqual(diagnostic.code, 'E0053');
53 assert.deepStrictEqual(diagnostic.tags, []);
54
55 // No related information
56 assert.deepStrictEqual(diagnostic.relatedInformation, []);
57
58 // There are no suggested fixes
59 assert.strictEqual(suggestedFixes.length, 0);
60 });
61
62 it('should map an unused variable warning', () => {
63 const { diagnostic, suggestedFixes } = mapFixtureToVsCode(
64 'warning/unused_variables',
65 );
66
67 assert.strictEqual(
68 diagnostic.severity,
69 vscode.DiagnosticSeverity.Warning,
70 );
71 assert.strictEqual(
72 diagnostic.message,
73 [
74 'unused variable: `foo`',
75 '#[warn(unused_variables)] on by default',
76 ].join('\n'),
77 );
78 assert.strictEqual(diagnostic.code, 'unused_variables');
79 assert.strictEqual(diagnostic.source, 'rustc');
80 assert.deepStrictEqual(diagnostic.tags, [
81 vscode.DiagnosticTag.Unnecessary,
82 ]);
83
84 // No related information
85 assert.deepStrictEqual(diagnostic.relatedInformation, []);
86
87 // One suggested fix available to prefix the variable
88 assert.strictEqual(suggestedFixes.length, 1);
89 const [suggestedFix] = suggestedFixes;
90 assert.strictEqual(
91 suggestedFix.title,
92 'consider prefixing with an underscore: `_foo`',
93 );
94 assert.strictEqual(
95 suggestedFix.applicability,
96 SuggestionApplicability.MachineApplicable,
97 );
98 });
99
100 it('should map a wrong number of parameters error', () => {
101 const { diagnostic, suggestedFixes } = mapFixtureToVsCode(
102 'error/E0061',
103 );
104
105 assert.strictEqual(
106 diagnostic.severity,
107 vscode.DiagnosticSeverity.Error,
108 );
109 assert.strictEqual(
110 diagnostic.message,
111 [
112 'this function takes 2 parameters but 3 parameters were supplied',
113 'expected 2 parameters',
114 ].join('\n'),
115 );
116 assert.strictEqual(diagnostic.code, 'E0061');
117 assert.strictEqual(diagnostic.source, 'rustc');
118 assert.deepStrictEqual(diagnostic.tags, []);
119
120 // One related information for the original definition
121 const relatedInformation = diagnostic.relatedInformation;
122 if (!relatedInformation) {
123 assert.fail('Related information unexpectedly undefined');
124 return;
125 }
126 assert.strictEqual(relatedInformation.length, 1);
127 const [related] = relatedInformation;
128 assert.strictEqual(related.message, 'defined here');
129
130 // There are no suggested fixes
131 assert.strictEqual(suggestedFixes.length, 0);
132 });
133
134 it('should map a Clippy copy pass by ref warning', () => {
135 const { diagnostic, suggestedFixes } = mapFixtureToVsCode(
136 'clippy/trivially_copy_pass_by_ref',
137 );
138
139 assert.strictEqual(
140 diagnostic.severity,
141 vscode.DiagnosticSeverity.Warning,
142 );
143 assert.strictEqual(diagnostic.source, 'clippy');
144 assert.strictEqual(
145 diagnostic.message,
146 [
147 'this argument is passed by reference, but would be more efficient if passed by value',
148 '#[warn(clippy::trivially_copy_pass_by_ref)] implied by #[warn(clippy::all)]',
149 'for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#trivially_copy_pass_by_ref',
150 ].join('\n'),
151 );
152 assert.strictEqual(diagnostic.code, 'trivially_copy_pass_by_ref');
153 assert.deepStrictEqual(diagnostic.tags, []);
154
155 // One related information for the lint definition
156 const relatedInformation = diagnostic.relatedInformation;
157 if (!relatedInformation) {
158 assert.fail('Related information unexpectedly undefined');
159 return;
160 }
161 assert.strictEqual(relatedInformation.length, 1);
162 const [related] = relatedInformation;
163 assert.strictEqual(related.message, 'lint level defined here');
164
165 // One suggested fix to pass by value
166 assert.strictEqual(suggestedFixes.length, 1);
167 const [suggestedFix] = suggestedFixes;
168 assert.strictEqual(
169 suggestedFix.title,
170 'consider passing by value instead: `self`',
171 );
172 // Clippy does not mark this with any applicability
173 assert.strictEqual(
174 suggestedFix.applicability,
175 SuggestionApplicability.Unspecified,
176 );
177 });
178
179 it('should map a mismatched type error', () => {
180 const { diagnostic, suggestedFixes } = mapFixtureToVsCode(
181 'error/E0308',
182 );
183
184 assert.strictEqual(
185 diagnostic.severity,
186 vscode.DiagnosticSeverity.Error,
187 );
188 assert.strictEqual(
189 diagnostic.message,
190 ['mismatched types', 'expected usize, found u32'].join('\n'),
191 );
192 assert.strictEqual(diagnostic.code, 'E0308');
193 assert.strictEqual(diagnostic.source, 'rustc');
194 assert.deepStrictEqual(diagnostic.tags, []);
195
196 // No related information
197 assert.deepStrictEqual(diagnostic.relatedInformation, []);
198
199 // There are no suggested fixes
200 assert.strictEqual(suggestedFixes.length, 0);
201 });
202
203 it('should map a macro invocation location to normal file path', () => {
204 const { location, diagnostic, suggestedFixes } = mapFixtureToVsCode(
205 'error/E0277',
206 );
207
208 assert.strictEqual(
209 diagnostic.severity,
210 vscode.DiagnosticSeverity.Error,
211 );
212 assert.strictEqual(
213 diagnostic.message,
214 [
215 "can't compare `{integer}` with `&str`",
216 'the trait `std::cmp::PartialEq<&str>` is not implemented for `{integer}`',
217 ].join('\n'),
218 );
219 assert.strictEqual(diagnostic.code, 'E0277');
220 assert.strictEqual(diagnostic.source, 'rustc');
221 assert.deepStrictEqual(diagnostic.tags, []);
222
223 // No related information
224 assert.deepStrictEqual(diagnostic.relatedInformation, []);
225
226 // There are no suggested fixes
227 assert.strictEqual(suggestedFixes.length, 0);
228
229 // The file url should be normal file
230 // Ignore the first part because it depends on vs workspace location
231 assert.strictEqual(
232 location.uri.path.substr(-'src/main.rs'.length),
233 'src/main.rs',
234 );
235 });
236});
diff --git a/editors/code/src/test/utils/diagnotics/vscode.test.ts b/editors/code/src/test/utils/diagnotics/vscode.test.ts
deleted file mode 100644
index 4944dd032..000000000
--- a/editors/code/src/test/utils/diagnotics/vscode.test.ts
+++ /dev/null
@@ -1,98 +0,0 @@
1import * as assert from 'assert';
2import * as vscode from 'vscode';
3
4import { areDiagnosticsEqual } from '../../../utils/diagnostics/vscode';
5
6const range1 = new vscode.Range(
7 new vscode.Position(1, 2),
8 new vscode.Position(3, 4),
9);
10
11const range2 = new vscode.Range(
12 new vscode.Position(5, 6),
13 new vscode.Position(7, 8),
14);
15
16describe('areDiagnosticsEqual', () => {
17 it('should treat identical diagnostics as equal', () => {
18 const diagnostic1 = new vscode.Diagnostic(
19 range1,
20 'Hello, world!',
21 vscode.DiagnosticSeverity.Error,
22 );
23
24 const diagnostic2 = new vscode.Diagnostic(
25 range1,
26 'Hello, world!',
27 vscode.DiagnosticSeverity.Error,
28 );
29
30 assert(areDiagnosticsEqual(diagnostic1, diagnostic2));
31 });
32
33 it('should treat diagnostics with different sources as inequal', () => {
34 const diagnostic1 = new vscode.Diagnostic(
35 range1,
36 'Hello, world!',
37 vscode.DiagnosticSeverity.Error,
38 );
39 diagnostic1.source = 'rustc';
40
41 const diagnostic2 = new vscode.Diagnostic(
42 range1,
43 'Hello, world!',
44 vscode.DiagnosticSeverity.Error,
45 );
46 diagnostic2.source = 'clippy';
47
48 assert(!areDiagnosticsEqual(diagnostic1, diagnostic2));
49 });
50
51 it('should treat diagnostics with different ranges as inequal', () => {
52 const diagnostic1 = new vscode.Diagnostic(
53 range1,
54 'Hello, world!',
55 vscode.DiagnosticSeverity.Error,
56 );
57
58 const diagnostic2 = new vscode.Diagnostic(
59 range2,
60 'Hello, world!',
61 vscode.DiagnosticSeverity.Error,
62 );
63
64 assert(!areDiagnosticsEqual(diagnostic1, diagnostic2));
65 });
66
67 it('should treat diagnostics with different messages as inequal', () => {
68 const diagnostic1 = new vscode.Diagnostic(
69 range1,
70 'Hello, world!',
71 vscode.DiagnosticSeverity.Error,
72 );
73
74 const diagnostic2 = new vscode.Diagnostic(
75 range1,
76 'Goodbye!, world!',
77 vscode.DiagnosticSeverity.Error,
78 );
79
80 assert(!areDiagnosticsEqual(diagnostic1, diagnostic2));
81 });
82
83 it('should treat diagnostics with different severities as inequal', () => {
84 const diagnostic1 = new vscode.Diagnostic(
85 range1,
86 'Hello, world!',
87 vscode.DiagnosticSeverity.Warning,
88 );
89
90 const diagnostic2 = new vscode.Diagnostic(
91 range1,
92 'Hello, world!',
93 vscode.DiagnosticSeverity.Error,
94 );
95
96 assert(!areDiagnosticsEqual(diagnostic1, diagnostic2));
97 });
98});
diff --git a/editors/code/src/utils/diagnostics/SuggestedFix.ts b/editors/code/src/utils/diagnostics/SuggestedFix.ts
deleted file mode 100644
index 6e660bb61..000000000
--- a/editors/code/src/utils/diagnostics/SuggestedFix.ts
+++ /dev/null
@@ -1,67 +0,0 @@
1import * as vscode from 'vscode';
2
3import { SuggestionApplicability } from './rust';
4
5/**
6 * Model object for text replacements suggested by the Rust compiler
7 *
8 * This is an intermediate form between the raw `rustc` JSON and a
9 * `vscode.CodeAction`. It's optimised for the use-cases of
10 * `SuggestedFixCollection`.
11 */
12export default class SuggestedFix {
13 public readonly title: string;
14 public readonly location: vscode.Location;
15 public readonly replacement: string;
16 public readonly applicability: SuggestionApplicability;
17
18 /**
19 * Diagnostics this suggested fix could resolve
20 */
21 public diagnostics: vscode.Diagnostic[];
22
23 constructor(
24 title: string,
25 location: vscode.Location,
26 replacement: string,
27 applicability: SuggestionApplicability = SuggestionApplicability.Unspecified,
28 ) {
29 this.title = title;
30 this.location = location;
31 this.replacement = replacement;
32 this.applicability = applicability;
33 this.diagnostics = [];
34 }
35
36 /**
37 * Determines if this suggested fix is equivalent to another instance
38 */
39 public isEqual(other: SuggestedFix): boolean {
40 return (
41 this.title === other.title &&
42 this.location.range.isEqual(other.location.range) &&
43 this.replacement === other.replacement &&
44 this.applicability === other.applicability
45 );
46 }
47
48 /**
49 * Converts this suggested fix to a VS Code Quick Fix code action
50 */
51 public toCodeAction(): vscode.CodeAction {
52 const codeAction = new vscode.CodeAction(
53 this.title,
54 vscode.CodeActionKind.QuickFix,
55 );
56
57 const edit = new vscode.WorkspaceEdit();
58 edit.replace(this.location.uri, this.location.range, this.replacement);
59 codeAction.edit = edit;
60
61 codeAction.isPreferred =
62 this.applicability === SuggestionApplicability.MachineApplicable;
63
64 codeAction.diagnostics = [...this.diagnostics];
65 return codeAction;
66 }
67}
diff --git a/editors/code/src/utils/diagnostics/SuggestedFixCollection.ts b/editors/code/src/utils/diagnostics/SuggestedFixCollection.ts
deleted file mode 100644
index 57c9856cf..000000000
--- a/editors/code/src/utils/diagnostics/SuggestedFixCollection.ts
+++ /dev/null
@@ -1,77 +0,0 @@
1import * as vscode from 'vscode';
2import SuggestedFix from './SuggestedFix';
3
4/**
5 * Collection of suggested fixes across multiple documents
6 *
7 * This stores `SuggestedFix` model objects and returns them via the
8 * `vscode.CodeActionProvider` interface.
9 */
10export default class SuggestedFixCollection
11 implements vscode.CodeActionProvider {
12 public static PROVIDED_CODE_ACTION_KINDS = [vscode.CodeActionKind.QuickFix];
13
14 /**
15 * Map of document URI strings to suggested fixes
16 */
17 private suggestedFixes: Map<string, SuggestedFix[]>;
18
19 constructor() {
20 this.suggestedFixes = new Map();
21 }
22
23 /**
24 * Clears all suggested fixes across all documents
25 */
26 public clear(): void {
27 this.suggestedFixes = new Map();
28 }
29
30 /**
31 * Adds a suggested fix for the given diagnostic
32 *
33 * Some suggested fixes will appear in multiple diagnostics. For example,
34 * forgetting a `mut` on a variable will suggest changing the delaration on
35 * every mutable usage site. If the suggested fix has already been added
36 * this method will instead associate the existing fix with the new
37 * diagnostic.
38 */
39 public addSuggestedFixForDiagnostic(
40 suggestedFix: SuggestedFix,
41 diagnostic: vscode.Diagnostic,
42 ): void {
43 const fileUriString = suggestedFix.location.uri.toString();
44 const fileSuggestions = this.suggestedFixes.get(fileUriString) || [];
45
46 const existingSuggestion = fileSuggestions.find(s =>
47 s.isEqual(suggestedFix),
48 );
49
50 if (existingSuggestion) {
51 // The existing suggestion also applies to this new diagnostic
52 existingSuggestion.diagnostics.push(diagnostic);
53 } else {
54 // We haven't seen this suggestion before
55 suggestedFix.diagnostics.push(diagnostic);
56 fileSuggestions.push(suggestedFix);
57 }
58
59 this.suggestedFixes.set(fileUriString, fileSuggestions);
60 }
61
62 /**
63 * Filters suggested fixes by their document and range and converts them to
64 * code actions
65 */
66 public provideCodeActions(
67 document: vscode.TextDocument,
68 range: vscode.Range,
69 ): vscode.CodeAction[] {
70 const documentUriString = document.uri.toString();
71
72 const suggestedFixes = this.suggestedFixes.get(documentUriString);
73 return (suggestedFixes || [])
74 .filter(({ location }) => location.range.intersection(range))
75 .map(suggestedEdit => suggestedEdit.toCodeAction());
76 }
77}
diff --git a/editors/code/src/utils/diagnostics/rust.ts b/editors/code/src/utils/diagnostics/rust.ts
deleted file mode 100644
index 1f0c0d3e4..000000000
--- a/editors/code/src/utils/diagnostics/rust.ts
+++ /dev/null
@@ -1,299 +0,0 @@
1import * as path from 'path';
2import * as vscode from 'vscode';
3
4import SuggestedFix from './SuggestedFix';
5
6export enum SuggestionApplicability {
7 MachineApplicable = 'MachineApplicable',
8 HasPlaceholders = 'HasPlaceholders',
9 MaybeIncorrect = 'MaybeIncorrect',
10 Unspecified = 'Unspecified',
11}
12
13export interface RustDiagnosticSpanMacroExpansion {
14 span: RustDiagnosticSpan;
15 macro_decl_name: string;
16 def_site_span?: RustDiagnosticSpan;
17}
18
19// Reference:
20// https://github.com/rust-lang/rust/blob/master/src/libsyntax/json.rs
21export interface RustDiagnosticSpan {
22 line_start: number;
23 line_end: number;
24 column_start: number;
25 column_end: number;
26 is_primary: boolean;
27 file_name: string;
28 label?: string;
29 expansion?: RustDiagnosticSpanMacroExpansion;
30 suggested_replacement?: string;
31 suggestion_applicability?: SuggestionApplicability;
32}
33
34export interface RustDiagnostic {
35 spans: RustDiagnosticSpan[];
36 rendered: string;
37 message: string;
38 level: string;
39 code?: {
40 code: string;
41 };
42 children: RustDiagnostic[];
43}
44
45export interface MappedRustDiagnostic {
46 location: vscode.Location;
47 diagnostic: vscode.Diagnostic;
48 suggestedFixes: SuggestedFix[];
49}
50
51interface MappedRustChildDiagnostic {
52 related?: vscode.DiagnosticRelatedInformation;
53 suggestedFix?: SuggestedFix;
54 messageLine?: string;
55}
56
57/**
58 * Converts a Rust level string to a VsCode severity
59 */
60function mapLevelToSeverity(s: string): vscode.DiagnosticSeverity {
61 if (s === 'error') {
62 return vscode.DiagnosticSeverity.Error;
63 }
64 if (s.startsWith('warn')) {
65 return vscode.DiagnosticSeverity.Warning;
66 }
67 return vscode.DiagnosticSeverity.Information;
68}
69
70/**
71 * Check whether a file name is from macro invocation
72 */
73function isFromMacro(fileName: string): boolean {
74 return fileName.startsWith('<') && fileName.endsWith('>');
75}
76
77/**
78 * Converts a Rust macro span to a VsCode location recursively
79 */
80function mapMacroSpanToLocation(
81 spanMacro: RustDiagnosticSpanMacroExpansion,
82): vscode.Location | undefined {
83 if (!isFromMacro(spanMacro.span.file_name)) {
84 return mapSpanToLocation(spanMacro.span);
85 }
86
87 if (spanMacro.span.expansion) {
88 return mapMacroSpanToLocation(spanMacro.span.expansion);
89 }
90
91 return;
92}
93
94/**
95 * Converts a Rust span to a VsCode location
96 */
97function mapSpanToLocation(span: RustDiagnosticSpan): vscode.Location {
98 if (isFromMacro(span.file_name) && span.expansion) {
99 const macroLoc = mapMacroSpanToLocation(span.expansion);
100 if (macroLoc) {
101 return macroLoc;
102 }
103 }
104
105 const fileName = path.join(vscode.workspace.rootPath || '', span.file_name);
106 const fileUri = vscode.Uri.file(fileName);
107
108 const range = new vscode.Range(
109 new vscode.Position(span.line_start - 1, span.column_start - 1),
110 new vscode.Position(span.line_end - 1, span.column_end - 1),
111 );
112
113 return new vscode.Location(fileUri, range);
114}
115
116/**
117 * Converts a secondary Rust span to a VsCode related information
118 *
119 * If the span is unlabelled this will return `undefined`.
120 */
121function mapSecondarySpanToRelated(
122 span: RustDiagnosticSpan,
123): vscode.DiagnosticRelatedInformation | undefined {
124 if (!span.label) {
125 // Nothing to label this with
126 return;
127 }
128
129 const location = mapSpanToLocation(span);
130 return new vscode.DiagnosticRelatedInformation(location, span.label);
131}
132
133/**
134 * Determines if diagnostic is related to unused code
135 */
136function isUnusedOrUnnecessary(rd: RustDiagnostic): boolean {
137 if (!rd.code) {
138 return false;
139 }
140
141 return [
142 'dead_code',
143 'unknown_lints',
144 'unreachable_code',
145 'unused_attributes',
146 'unused_imports',
147 'unused_macros',
148 'unused_variables',
149 ].includes(rd.code.code);
150}
151
152/**
153 * Determines if diagnostic is related to deprecated code
154 */
155function isDeprecated(rd: RustDiagnostic): boolean {
156 if (!rd.code) {
157 return false;
158 }
159
160 return ['deprecated'].includes(rd.code.code);
161}
162
163/**
164 * Converts a Rust child diagnostic to a VsCode related information
165 *
166 * This can have three outcomes:
167 *
168 * 1. If this is no primary span this will return a `noteLine`
169 * 2. If there is a primary span with a suggested replacement it will return a
170 * `codeAction`.
171 * 3. If there is a primary span without a suggested replacement it will return
172 * a `related`.
173 */
174function mapRustChildDiagnostic(rd: RustDiagnostic): MappedRustChildDiagnostic {
175 const span = rd.spans.find(s => s.is_primary);
176
177 if (!span) {
178 // `rustc` uses these spanless children as a way to print multi-line
179 // messages
180 return { messageLine: rd.message };
181 }
182
183 // If we have a primary span use its location, otherwise use the parent
184 const location = mapSpanToLocation(span);
185
186 // We need to distinguish `null` from an empty string
187 if (span && typeof span.suggested_replacement === 'string') {
188 // Include our replacement in the title unless it's empty
189 const title = span.suggested_replacement
190 ? `${rd.message}: \`${span.suggested_replacement}\``
191 : rd.message;
192
193 return {
194 suggestedFix: new SuggestedFix(
195 title,
196 location,
197 span.suggested_replacement,
198 span.suggestion_applicability,
199 ),
200 };
201 } else {
202 const related = new vscode.DiagnosticRelatedInformation(
203 location,
204 rd.message,
205 );
206
207 return { related };
208 }
209}
210
211/**
212 * Converts a Rust root diagnostic to VsCode form
213 *
214 * This flattens the Rust diagnostic by:
215 *
216 * 1. Creating a `vscode.Diagnostic` with the root message and primary span.
217 * 2. Adding any labelled secondary spans to `relatedInformation`
218 * 3. Categorising child diagnostics as either `SuggestedFix`es,
219 * `relatedInformation` or additional message lines.
220 *
221 * If the diagnostic has no primary span this will return `undefined`
222 */
223export function mapRustDiagnosticToVsCode(
224 rd: RustDiagnostic,
225): MappedRustDiagnostic | undefined {
226 const primarySpan = rd.spans.find(s => s.is_primary);
227 if (!primarySpan) {
228 return;
229 }
230
231 const location = mapSpanToLocation(primarySpan);
232 const secondarySpans = rd.spans.filter(s => !s.is_primary);
233
234 const severity = mapLevelToSeverity(rd.level);
235 let primarySpanLabel = primarySpan.label;
236
237 const vd = new vscode.Diagnostic(location.range, rd.message, severity);
238
239 let source = 'rustc';
240 let code = rd.code && rd.code.code;
241 if (code) {
242 // See if this is an RFC #2103 scoped lint (e.g. from Clippy)
243 const scopedCode = code.split('::');
244 if (scopedCode.length === 2) {
245 [source, code] = scopedCode;
246 }
247 }
248
249 vd.source = source;
250 vd.code = code;
251 vd.relatedInformation = [];
252 vd.tags = [];
253
254 for (const secondarySpan of secondarySpans) {
255 const related = mapSecondarySpanToRelated(secondarySpan);
256 if (related) {
257 vd.relatedInformation.push(related);
258 }
259 }
260
261 const suggestedFixes = [];
262 for (const child of rd.children) {
263 const { related, suggestedFix, messageLine } = mapRustChildDiagnostic(
264 child,
265 );
266
267 if (related) {
268 vd.relatedInformation.push(related);
269 }
270 if (suggestedFix) {
271 suggestedFixes.push(suggestedFix);
272 }
273 if (messageLine) {
274 vd.message += `\n${messageLine}`;
275
276 // These secondary messages usually duplicate the content of the
277 // primary span label.
278 primarySpanLabel = undefined;
279 }
280 }
281
282 if (primarySpanLabel) {
283 vd.message += `\n${primarySpanLabel}`;
284 }
285
286 if (isUnusedOrUnnecessary(rd)) {
287 vd.tags.push(vscode.DiagnosticTag.Unnecessary);
288 }
289
290 if (isDeprecated(rd)) {
291 vd.tags.push(vscode.DiagnosticTag.Deprecated);
292 }
293
294 return {
295 location,
296 diagnostic: vd,
297 suggestedFixes,
298 };
299}
diff --git a/editors/code/src/utils/diagnostics/vscode.ts b/editors/code/src/utils/diagnostics/vscode.ts
deleted file mode 100644
index f4a5450e2..000000000
--- a/editors/code/src/utils/diagnostics/vscode.ts
+++ /dev/null
@@ -1,14 +0,0 @@
1import * as vscode from 'vscode';
2
3/** Compares two `vscode.Diagnostic`s for equality */
4export function areDiagnosticsEqual(
5 left: vscode.Diagnostic,
6 right: vscode.Diagnostic,
7): boolean {
8 return (
9 left.source === right.source &&
10 left.severity === right.severity &&
11 left.range.isEqual(right.range) &&
12 left.message === right.message
13 );
14}
diff --git a/editors/code/src/utils/processes.ts b/editors/code/src/utils/processes.ts
deleted file mode 100644
index a1d6b7eaf..000000000
--- a/editors/code/src/utils/processes.ts
+++ /dev/null
@@ -1,51 +0,0 @@
1'use strict';
2
3import * as cp from 'child_process';
4import ChildProcess = cp.ChildProcess;
5
6import { join } from 'path';
7
8const isWindows = process.platform === 'win32';
9const isMacintosh = process.platform === 'darwin';
10const isLinux = process.platform === 'linux';
11
12// this is very complex, but is basically copy-pased from VSCode implementation here:
13// https://github.com/Microsoft/vscode-languageserver-node/blob/dbfd37e35953ad0ee14c4eeced8cfbc41697b47e/client/src/utils/processes.ts#L15
14
15// And see discussion at
16// https://github.com/rust-analyzer/rust-analyzer/pull/1079#issuecomment-478908109
17
18export function terminate(process: ChildProcess, cwd?: string): boolean {
19 if (isWindows) {
20 try {
21 // This we run in Atom execFileSync is available.
22 // Ignore stderr since this is otherwise piped to parent.stderr
23 // which might be already closed.
24 const options: any = {
25 stdio: ['pipe', 'pipe', 'ignore'],
26 };
27 if (cwd) {
28 options.cwd = cwd;
29 }
30 cp.execFileSync(
31 'taskkill',
32 ['/T', '/F', '/PID', process.pid.toString()],
33 options,
34 );
35 return true;
36 } catch (err) {
37 return false;
38 }
39 } else if (isLinux || isMacintosh) {
40 try {
41 const cmd = join(__dirname, 'terminateProcess.sh');
42 const result = cp.spawnSync(cmd, [process.pid.toString()]);
43 return result.error ? false : true;
44 } catch (err) {
45 return false;
46 }
47 } else {
48 process.kill('SIGKILL');
49 return true;
50 }
51}
diff --git a/editors/code/src/utils/terminateProcess.sh b/editors/code/src/utils/terminateProcess.sh
deleted file mode 100644
index 2ec9e1c2e..000000000
--- a/editors/code/src/utils/terminateProcess.sh
+++ /dev/null
@@ -1,12 +0,0 @@
1#!/bin/bash
2
3terminateTree() {
4 for cpid in $(pgrep -P $1); do
5 terminateTree $cpid
6 done
7 kill -9 $1 > /dev/null 2>&1
8}
9
10for pid in $*; do
11 terminateTree $pid
12done \ No newline at end of file
diff --git a/xtask/src/main.rs b/xtask/src/main.rs
index 4201c6a6a..9cefad925 100644
--- a/xtask/src/main.rs
+++ b/xtask/src/main.rs
@@ -226,7 +226,7 @@ fn install_client(ClientOpt::VsCode: ClientOpt) -> Result<()> {
226 if !str::from_utf8(&output.stdout)?.contains("ra-lsp") { 226 if !str::from_utf8(&output.stdout)?.contains("ra-lsp") {
227 anyhow::bail!( 227 anyhow::bail!(
228 "Could not install the Visual Studio Code extension. \ 228 "Could not install the Visual Studio Code extension. \
229 Please make sure you have at least NodeJS 10.x installed and try again." 229 Please make sure you have at least NodeJS 10.x together with the latest version of VS Code installed and try again."
230 ); 230 );
231 } 231 }
232 232
diff --git a/xtask/tests/tidy-tests/cli.rs b/xtask/tests/tidy-tests/cli.rs
index 573ffadbf..f9ca45292 100644
--- a/xtask/tests/tidy-tests/cli.rs
+++ b/xtask/tests/tidy-tests/cli.rs
@@ -43,7 +43,7 @@ fn no_todo() {
43 return; 43 return;
44 } 44 }
45 let text = std::fs::read_to_string(e.path()).unwrap(); 45 let text = std::fs::read_to_string(e.path()).unwrap();
46 if text.contains("TODO") || text.contains("TOOD") { 46 if text.contains("TODO") || text.contains("TOOD") || text.contains("todo!") {
47 panic!( 47 panic!(
48 "\nTODO markers should not be committed to the master branch,\n\ 48 "\nTODO markers should not be committed to the master branch,\n\
49 use FIXME instead\n\ 49 use FIXME instead\n\