aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/ci.yaml3
-rw-r--r--.github/workflows/publish.yml4
-rw-r--r--Cargo.lock98
-rw-r--r--crates/assists/src/assist_config.rs20
-rw-r--r--crates/assists/src/ast_transform.rs30
-rw-r--r--crates/assists/src/handlers/add_missing_impl_members.rs40
-rw-r--r--crates/assists/src/handlers/auto_import.rs21
-rw-r--r--crates/assists/src/handlers/expand_glob_import.rs30
-rw-r--r--crates/assists/src/handlers/extract_struct_from_enum_variant.rs31
-rw-r--r--crates/assists/src/handlers/merge_imports.rs175
-rw-r--r--crates/assists/src/handlers/remove_dbg.rs144
-rw-r--r--crates/assists/src/handlers/replace_impl_trait_with_generic.rs168
-rw-r--r--crates/assists/src/handlers/replace_qualified_name_with_use.rs58
-rw-r--r--crates/assists/src/lib.rs2
-rw-r--r--crates/assists/src/tests/generated.rs13
-rw-r--r--crates/assists/src/utils.rs3
-rw-r--r--crates/assists/src/utils/insert_use.rs1347
-rw-r--r--crates/base_db/src/input.rs8
-rw-r--r--crates/hir/src/code_model.rs11
-rw-r--r--crates/hir/src/semantics.rs27
-rw-r--r--crates/hir/src/source_analyzer.rs4
-rw-r--r--crates/hir_def/src/body/lower.rs11
-rw-r--r--crates/hir_def/src/data.rs2
-rw-r--r--crates/hir_def/src/diagnostics.rs42
-rw-r--r--crates/hir_def/src/expr.rs7
-rw-r--r--crates/hir_def/src/item_tree.rs9
-rw-r--r--crates/hir_def/src/item_tree/lower.rs13
-rw-r--r--crates/hir_def/src/item_tree/tests.rs8
-rw-r--r--crates/hir_def/src/nameres.rs93
-rw-r--r--crates/hir_def/src/nameres/collector.rs418
-rw-r--r--crates/hir_def/src/nameres/tests.rs1
-rw-r--r--crates/hir_def/src/nameres/tests/diagnostics.rs131
-rw-r--r--crates/hir_def/src/nameres/tests/mod_resolution.rs36
-rw-r--r--crates/hir_def/src/path.rs4
-rw-r--r--crates/hir_def/src/test_db.rs42
-rw-r--r--crates/hir_expand/src/db.rs6
-rw-r--r--crates/hir_expand/src/eager.rs2
-rw-r--r--crates/hir_expand/src/hygiene.rs2
-rw-r--r--crates/hir_expand/src/lib.rs2
-rw-r--r--crates/hir_ty/Cargo.toml6
-rw-r--r--crates/hir_ty/src/display.rs37
-rw-r--r--crates/hir_ty/src/infer.rs6
-rw-r--r--crates/hir_ty/src/infer/expr.rs11
-rw-r--r--crates/hir_ty/src/infer/pat.rs16
-rw-r--r--crates/hir_ty/src/lib.rs45
-rw-r--r--crates/hir_ty/src/lower.rs10
-rw-r--r--crates/hir_ty/src/method_resolution.rs8
-rw-r--r--crates/hir_ty/src/tests/method_resolution.rs36
-rw-r--r--crates/hir_ty/src/tests/patterns.rs25
-rw-r--r--crates/hir_ty/src/tests/simple.rs45
-rw-r--r--crates/hir_ty/src/tests/traits.rs40
-rw-r--r--crates/hir_ty/src/traits/chalk.rs141
-rw-r--r--crates/hir_ty/src/traits/chalk/interner.rs19
-rw-r--r--crates/hir_ty/src/traits/chalk/mapping.rs81
-rw-r--r--crates/hir_ty/src/traits/chalk/tls.rs14
-rw-r--r--crates/ide/src/completion.rs2
-rw-r--r--crates/ide/src/completion/complete_attribute.rs4
-rw-r--r--crates/ide/src/completion/complete_keyword.rs22
-rw-r--r--crates/ide/src/completion/complete_mod.rs324
-rw-r--r--crates/ide/src/completion/complete_qualified_path.rs24
-rw-r--r--crates/ide/src/completion/complete_trait_impl.rs377
-rw-r--r--crates/ide/src/completion/complete_unqualified_path.rs1
-rw-r--r--crates/ide/src/completion/completion_context.rs7
-rw-r--r--crates/ide/src/completion/patterns.rs1
-rw-r--r--crates/ide/src/diagnostics.rs62
-rw-r--r--crates/ide/src/display.rs9
-rw-r--r--crates/ide/src/hover.rs64
-rw-r--r--crates/ide/src/lib.rs4
-rw-r--r--crates/ide/src/syntax_highlighting.rs63
-rw-r--r--crates/ide/src/syntax_highlighting/test_data/highlighting.html13
-rw-r--r--crates/ide/src/syntax_highlighting/tests.rs13
-rw-r--r--crates/ide/src/typing.rs8
-rw-r--r--crates/ide_db/src/defs.rs10
-rw-r--r--crates/parser/src/grammar/patterns.rs4
-rw-r--r--crates/parser/src/syntax_kind/generated.rs2
-rw-r--r--crates/project_model/src/lib.rs40
-rw-r--r--crates/project_model/src/project_json.rs21
-rw-r--r--crates/project_model/src/sysroot.rs13
-rw-r--r--crates/rust-analyzer/Cargo.toml2
-rw-r--r--crates/rust-analyzer/src/bin/main.rs4
-rw-r--r--crates/rust-analyzer/src/caps.rs22
-rw-r--r--crates/rust-analyzer/src/config.rs28
-rw-r--r--crates/rust-analyzer/src/diagnostics/test_data/macro_compiler_error.txt45
-rw-r--r--crates/rust-analyzer/src/diagnostics/to_proto.rs51
-rw-r--r--crates/rust-analyzer/src/handlers.rs33
-rw-r--r--crates/rust-analyzer/src/main_loop.rs8
-rw-r--r--crates/rust-analyzer/src/reload.rs31
-rw-r--r--crates/rust-analyzer/src/semantic_tokens.rs3
-rw-r--r--crates/rust-analyzer/src/to_proto.rs20
-rw-r--r--crates/rust-analyzer/tests/rust-analyzer/main.rs36
-rw-r--r--crates/syntax/src/ast/edit.rs95
-rw-r--r--crates/syntax/src/ast/make.rs50
-rw-r--r--crates/syntax/test_data/parser/inline/ok/0102_record_pat_field_list.rast (renamed from crates/syntax/test_data/parser/inline/ok/0102_record_field_pat_list.rast)0
-rw-r--r--crates/syntax/test_data/parser/inline/ok/0102_record_pat_field_list.rs (renamed from crates/syntax/test_data/parser/inline/ok/0102_record_field_pat_list.rs)0
-rw-r--r--crates/syntax/test_data/parser/inline/ok/0145_record_pat_field.rast (renamed from crates/syntax/test_data/parser/inline/ok/0145_record_field_pat.rast)0
-rw-r--r--crates/syntax/test_data/parser/inline/ok/0145_record_pat_field.rs (renamed from crates/syntax/test_data/parser/inline/ok/0145_record_field_pat.rs)0
-rw-r--r--crates/text_edit/src/lib.rs9
-rw-r--r--crates/vfs/src/file_set.rs13
-rw-r--r--crates/vfs/src/vfs_path.rs74
-rw-r--r--docs/dev/syntax.md2
-rw-r--r--docs/user/manual.adoc5
-rw-r--r--editors/code/package-lock.json632
-rw-r--r--editors/code/package.json49
-rw-r--r--editors/code/src/client.ts15
-rw-r--r--editors/code/src/commands.ts14
-rw-r--r--editors/code/src/ctx.ts2
-rw-r--r--editors/code/src/lsp_ext.ts6
-rw-r--r--editors/code/src/util.ts2
-rw-r--r--xtask/src/codegen/gen_syntax.rs4
-rw-r--r--xtask/src/release.rs3
-rw-r--r--xtask/tests/tidy.rs36
111 files changed, 4413 insertions, 1575 deletions
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index fb077e28d..a19bc9ad3 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -30,6 +30,9 @@ jobs:
30 steps: 30 steps:
31 - name: Checkout repository 31 - name: Checkout repository
32 uses: actions/checkout@v2 32 uses: actions/checkout@v2
33 with:
34 ref: ${{ github.event.pull_request.head.sha }}
35 fetch-depth: 20
33 36
34 # We need to disable the existing toolchain to avoid updating rust-docs 37 # We need to disable the existing toolchain to avoid updating rust-docs
35 # which takes a long time. The fastest way to do this is to rename the 38 # which takes a long time. The fastest way to do this is to rename the
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index 0e26bb784..e54a3693b 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -2,8 +2,8 @@ name: publish
2on: 2on:
3 workflow_dispatch: # We can add version input when 1.0 is released and scheduled releases are removed 3 workflow_dispatch: # We can add version input when 1.0 is released and scheduled releases are removed
4 4
5 schedule: 5# schedule:
6 - cron: "0 0 * * *" # midnight UTC 6# - cron: "0 0 * * *" # midnight UTC
7 7
8 push: 8 push:
9 branches: 9 branches:
diff --git a/Cargo.lock b/Cargo.lock
index cec4462f2..7fecee1b5 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -129,9 +129,9 @@ checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de"
129 129
130[[package]] 130[[package]]
131name = "cargo_metadata" 131name = "cargo_metadata"
132version = "0.11.1" 132version = "0.11.2"
133source = "registry+https://github.com/rust-lang/crates.io-index" 133source = "registry+https://github.com/rust-lang/crates.io-index"
134checksum = "89fec17b16f1ac67908af82e47d0a90a7afd0e1827b181cd77504323d3263d35" 134checksum = "c990b1694d29f8e477f456db1b2fcd5dd1cd6e29d5be082df45213e8834eb39a"
135dependencies = [ 135dependencies = [
136 "semver", 136 "semver",
137 "serde", 137 "serde",
@@ -140,9 +140,9 @@ dependencies = [
140 140
141[[package]] 141[[package]]
142name = "cc" 142name = "cc"
143version = "1.0.59" 143version = "1.0.60"
144source = "registry+https://github.com/rust-lang/crates.io-index" 144source = "registry+https://github.com/rust-lang/crates.io-index"
145checksum = "66120af515773fb005778dc07c261bd201ec8ce50bd6e7144c927753fe013381" 145checksum = "ef611cc68ff783f18535d77ddd080185275713d852c4f5cbb6122c462a7a825c"
146 146
147[[package]] 147[[package]]
148name = "cfg" 148name = "cfg"
@@ -162,9 +162,9 @@ checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
162 162
163[[package]] 163[[package]]
164name = "chalk-derive" 164name = "chalk-derive"
165version = "0.23.0" 165version = "0.27.0"
166source = "registry+https://github.com/rust-lang/crates.io-index" 166source = "registry+https://github.com/rust-lang/crates.io-index"
167checksum = "c3cb438e961fd7f1183dc5e0bdcfd09253bf9b90592cf665d1ce6787d8a4908f" 167checksum = "d5444ff2a211fe2a863e44d16a368c3d8a314d489de21b8eeb6879f14dd5d4a8"
168dependencies = [ 168dependencies = [
169 "proc-macro2", 169 "proc-macro2",
170 "quote", 170 "quote",
@@ -174,9 +174,9 @@ dependencies = [
174 174
175[[package]] 175[[package]]
176name = "chalk-ir" 176name = "chalk-ir"
177version = "0.23.0" 177version = "0.27.0"
178source = "registry+https://github.com/rust-lang/crates.io-index" 178source = "registry+https://github.com/rust-lang/crates.io-index"
179checksum = "bb332abfcb015b148c6fbab39b1d13282745b0f7f312019dd8e138f5f3f0855d" 179checksum = "e39c3db1dd4abfaa7658faaa62e5fe998a982a592b710bd971fad5b6adfcfdef"
180dependencies = [ 180dependencies = [
181 "chalk-derive", 181 "chalk-derive",
182 "lazy_static", 182 "lazy_static",
@@ -184,9 +184,9 @@ dependencies = [
184 184
185[[package]] 185[[package]]
186name = "chalk-recursive" 186name = "chalk-recursive"
187version = "0.23.0" 187version = "0.27.0"
188source = "registry+https://github.com/rust-lang/crates.io-index" 188source = "registry+https://github.com/rust-lang/crates.io-index"
189checksum = "e7c7673f10c5fa1acf7fa07d4f4c5917cbcf161ed3a952d14530c79950de32d2" 189checksum = "3bfae328eff80ca54dcd0d731725bbb56136ac21c59261b68f1e5498e056b306"
190dependencies = [ 190dependencies = [
191 "chalk-derive", 191 "chalk-derive",
192 "chalk-ir", 192 "chalk-ir",
@@ -197,9 +197,9 @@ dependencies = [
197 197
198[[package]] 198[[package]]
199name = "chalk-solve" 199name = "chalk-solve"
200version = "0.23.0" 200version = "0.27.0"
201source = "registry+https://github.com/rust-lang/crates.io-index" 201source = "registry+https://github.com/rust-lang/crates.io-index"
202checksum = "802de4eff72e5a5d2828e6c07224c74d66949dc6308aff025d0ae2871a11b4eb" 202checksum = "a673abe3077adc25f8ee0894198aed494a5bb0ce50ee993900d0ee1a44e1948a"
203dependencies = [ 203dependencies = [
204 "chalk-derive", 204 "chalk-derive",
205 "chalk-ir", 205 "chalk-ir",
@@ -252,12 +252,12 @@ dependencies = [
252 252
253[[package]] 253[[package]]
254name = "crossbeam-channel" 254name = "crossbeam-channel"
255version = "0.4.3" 255version = "0.4.4"
256source = "registry+https://github.com/rust-lang/crates.io-index" 256source = "registry+https://github.com/rust-lang/crates.io-index"
257checksum = "09ee0cc8804d5393478d743b035099520087a5186f3b93fa58cec08fa62407b6" 257checksum = "b153fe7cbef478c567df0f972e02e6d736db11affe43dfc9c56a9374d1adfb87"
258dependencies = [ 258dependencies = [
259 "cfg-if",
260 "crossbeam-utils", 259 "crossbeam-utils",
260 "maybe-uninit",
261] 261]
262 262
263[[package]] 263[[package]]
@@ -311,9 +311,9 @@ checksum = "9bda8e21c04aca2ae33ffc2fd8c23134f3cac46db123ba97bd9d3f3b8a4a85e1"
311 311
312[[package]] 312[[package]]
313name = "either" 313name = "either"
314version = "1.6.0" 314version = "1.6.1"
315source = "registry+https://github.com/rust-lang/crates.io-index" 315source = "registry+https://github.com/rust-lang/crates.io-index"
316checksum = "cd56b59865bce947ac5958779cfa508f6c3b9497cc762b7e24a12d11ccde2c4f" 316checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
317 317
318[[package]] 318[[package]]
319name = "ena" 319name = "ena"
@@ -335,9 +335,9 @@ dependencies = [
335 335
336[[package]] 336[[package]]
337name = "expect-test" 337name = "expect-test"
338version = "1.0.0" 338version = "1.0.1"
339source = "registry+https://github.com/rust-lang/crates.io-index" 339source = "registry+https://github.com/rust-lang/crates.io-index"
340checksum = "3a3cb2ab12988de61df699d210428f3a0e26907b9d0978cf2c12e65ee90adb1d" 340checksum = "ceb96f3eaa0d4e8769c52dacfd4eb60183b817ed2f176171b3c691d5022b0f2e"
341dependencies = [ 341dependencies = [
342 "difference", 342 "difference",
343 "once_cell", 343 "once_cell",
@@ -451,12 +451,9 @@ dependencies = [
451 451
452[[package]] 452[[package]]
453name = "hashbrown" 453name = "hashbrown"
454version = "0.8.2" 454version = "0.9.0"
455source = "registry+https://github.com/rust-lang/crates.io-index" 455source = "registry+https://github.com/rust-lang/crates.io-index"
456checksum = "e91b62f79061a0bc2e046024cb7ba44b08419ed238ecbd9adbd787434b9e8c25" 456checksum = "00d63df3d41950fb462ed38308eea019113ad1508da725bbedcd0fa5a85ef5f7"
457dependencies = [
458 "autocfg",
459]
460 457
461[[package]] 458[[package]]
462name = "heck" 459name = "heck"
@@ -635,9 +632,9 @@ dependencies = [
635 632
636[[package]] 633[[package]]
637name = "indexmap" 634name = "indexmap"
638version = "1.5.1" 635version = "1.6.0"
639source = "registry+https://github.com/rust-lang/crates.io-index" 636source = "registry+https://github.com/rust-lang/crates.io-index"
640checksum = "86b45e59b16c76b11bf9738fd5d38879d3bd28ad292d7b313608becb17ae2df9" 637checksum = "55e2e4c765aa53a0424761bf9f41aa7a6ac1efa87238f59560640e27fca028f2"
641dependencies = [ 638dependencies = [
642 "autocfg", 639 "autocfg",
643 "hashbrown", 640 "hashbrown",
@@ -723,9 +720,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
723 720
724[[package]] 721[[package]]
725name = "libc" 722name = "libc"
726version = "0.2.76" 723version = "0.2.77"
727source = "registry+https://github.com/rust-lang/crates.io-index" 724source = "registry+https://github.com/rust-lang/crates.io-index"
728checksum = "755456fae044e6fa1ebbbd1b3e902ae19e73097ed4ed87bb79934a867c007bc3" 725checksum = "f2f96b10ec2560088a8e76961b00d47107b3a625fecb76dedb29ee7ccbf98235"
729 726
730[[package]] 727[[package]]
731name = "libloading" 728name = "libloading"
@@ -778,9 +775,9 @@ dependencies = [
778 775
779[[package]] 776[[package]]
780name = "lsp-types" 777name = "lsp-types"
781version = "0.79.0" 778version = "0.82.0"
782source = "registry+https://github.com/rust-lang/crates.io-index" 779source = "registry+https://github.com/rust-lang/crates.io-index"
783checksum = "7f1f86677fdbe8df5f88b99131b1424e50aad27bbe3e5900d221bc414bd72e9b" 780checksum = "db895abb8527cf59e3de893ab2acf52cf904faeb65e60ea6f373e11fe86464e8"
784dependencies = [ 781dependencies = [
785 "base64", 782 "base64",
786 "bitflags", 783 "bitflags",
@@ -860,11 +857,12 @@ dependencies = [
860 857
861[[package]] 858[[package]]
862name = "miniz_oxide" 859name = "miniz_oxide"
863version = "0.4.1" 860version = "0.4.2"
864source = "registry+https://github.com/rust-lang/crates.io-index" 861source = "registry+https://github.com/rust-lang/crates.io-index"
865checksum = "4d7559a8a40d0f97e1edea3220f698f78b1c5ab67532e49f68fde3910323b722" 862checksum = "c60c0dfe32c10b43a144bad8fc83538c52f58302c92300ea7ec7bf7b38d5a7b9"
866dependencies = [ 863dependencies = [
867 "adler", 864 "adler",
865 "autocfg",
868] 866]
869 867
870[[package]] 868[[package]]
@@ -912,9 +910,9 @@ dependencies = [
912 910
913[[package]] 911[[package]]
914name = "net2" 912name = "net2"
915version = "0.2.34" 913version = "0.2.35"
916source = "registry+https://github.com/rust-lang/crates.io-index" 914source = "registry+https://github.com/rust-lang/crates.io-index"
917checksum = "2ba7c918ac76704fb42afcbbb43891e72731f3dcca3bef2a19786297baf14af7" 915checksum = "3ebc3ec692ed7c9a255596c67808dee269f64655d8baf7b4f0638e51ba1d6853"
918dependencies = [ 916dependencies = [
919 "cfg-if", 917 "cfg-if",
920 "libc", 918 "libc",
@@ -1074,9 +1072,9 @@ checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6"
1074 1072
1075[[package]] 1073[[package]]
1076name = "proc-macro2" 1074name = "proc-macro2"
1077version = "1.0.19" 1075version = "1.0.21"
1078source = "registry+https://github.com/rust-lang/crates.io-index" 1076source = "registry+https://github.com/rust-lang/crates.io-index"
1079checksum = "04f5f085b5d71e2188cb8271e5da0161ad52c3f227a661a3c135fdf28e258b12" 1077checksum = "36e28516df94f3dd551a587da5357459d9b36d945a7c37c3557928c1c2ff2a2c"
1080dependencies = [ 1078dependencies = [
1081 "unicode-xid", 1079 "unicode-xid",
1082] 1080]
@@ -1189,9 +1187,9 @@ dependencies = [
1189 1187
1190[[package]] 1188[[package]]
1191name = "rayon-core" 1189name = "rayon-core"
1192version = "1.8.0" 1190version = "1.8.1"
1193source = "registry+https://github.com/rust-lang/crates.io-index" 1191source = "registry+https://github.com/rust-lang/crates.io-index"
1194checksum = "91739a34c4355b5434ce54c9086c5895604a9c278586d1f1aa95e04f66b525a0" 1192checksum = "e8c4fec834fb6e6d2dd5eece3c7b432a52f0ba887cf40e595190c4107edc08bf"
1195dependencies = [ 1193dependencies = [
1196 "crossbeam-channel", 1194 "crossbeam-channel",
1197 "crossbeam-deque", 1195 "crossbeam-deque",
@@ -1404,18 +1402,18 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
1404 1402
1405[[package]] 1403[[package]]
1406name = "serde" 1404name = "serde"
1407version = "1.0.115" 1405version = "1.0.116"
1408source = "registry+https://github.com/rust-lang/crates.io-index" 1406source = "registry+https://github.com/rust-lang/crates.io-index"
1409checksum = "e54c9a88f2da7238af84b5101443f0c0d0a3bbdc455e34a5c9497b1903ed55d5" 1407checksum = "96fe57af81d28386a513cbc6858332abc6117cfdb5999647c6444b8f43a370a5"
1410dependencies = [ 1408dependencies = [
1411 "serde_derive", 1409 "serde_derive",
1412] 1410]
1413 1411
1414[[package]] 1412[[package]]
1415name = "serde_derive" 1413name = "serde_derive"
1416version = "1.0.115" 1414version = "1.0.116"
1417source = "registry+https://github.com/rust-lang/crates.io-index" 1415source = "registry+https://github.com/rust-lang/crates.io-index"
1418checksum = "609feed1d0a73cc36a0182a840a9b37b4a82f0b1150369f0536a9e3f2a31dc48" 1416checksum = "f630a6370fd8e457873b4bd2ffdae75408bc291ba72be773772a4c2a065d9ae8"
1419dependencies = [ 1417dependencies = [
1420 "proc-macro2", 1418 "proc-macro2",
1421 "quote", 1419 "quote",
@@ -1495,9 +1493,9 @@ version = "0.0.0"
1495 1493
1496[[package]] 1494[[package]]
1497name = "syn" 1495name = "syn"
1498version = "1.0.39" 1496version = "1.0.41"
1499source = "registry+https://github.com/rust-lang/crates.io-index" 1497source = "registry+https://github.com/rust-lang/crates.io-index"
1500checksum = "891d8d6567fe7c7f8835a3a98af4208f3846fba258c1bc3c31d6e506239f11f9" 1498checksum = "6690e3e9f692504b941dc6c3b188fd28df054f7fb8469ab40680df52fdcc842b"
1501dependencies = [ 1499dependencies = [
1502 "proc-macro2", 1500 "proc-macro2",
1503 "quote", 1501 "quote",
@@ -1642,9 +1640,9 @@ dependencies = [
1642 1640
1643[[package]] 1641[[package]]
1644name = "tracing-core" 1642name = "tracing-core"
1645version = "0.1.15" 1643version = "0.1.16"
1646source = "registry+https://github.com/rust-lang/crates.io-index" 1644source = "registry+https://github.com/rust-lang/crates.io-index"
1647checksum = "4f0e00789804e99b20f12bc7003ca416309d28a6f495d6af58d1e2c2842461b5" 1645checksum = "5bcf46c1f1f06aeea2d6b81f3c863d0930a596c86ad1920d4e5bad6dd1d7119a"
1648dependencies = [ 1646dependencies = [
1649 "lazy_static", 1647 "lazy_static",
1650] 1648]
@@ -1662,9 +1660,9 @@ dependencies = [
1662 1660
1663[[package]] 1661[[package]]
1664name = "tracing-serde" 1662name = "tracing-serde"
1665version = "0.1.1" 1663version = "0.1.2"
1666source = "registry+https://github.com/rust-lang/crates.io-index" 1664source = "registry+https://github.com/rust-lang/crates.io-index"
1667checksum = "b6ccba2f8f16e0ed268fc765d9b7ff22e965e7185d32f8f1ec8294fe17d86e79" 1665checksum = "fb65ea441fbb84f9f6748fd496cf7f63ec9af5bca94dd86456978d055e8eb28b"
1668dependencies = [ 1666dependencies = [
1669 "serde", 1667 "serde",
1670 "tracing-core", 1668 "tracing-core",
@@ -1672,9 +1670,9 @@ dependencies = [
1672 1670
1673[[package]] 1671[[package]]
1674name = "tracing-subscriber" 1672name = "tracing-subscriber"
1675version = "0.2.11" 1673version = "0.2.12"
1676source = "registry+https://github.com/rust-lang/crates.io-index" 1674source = "registry+https://github.com/rust-lang/crates.io-index"
1677checksum = "abd165311cc4d7a555ad11cc77a37756df836182db0d81aac908c8184c584f40" 1675checksum = "82bb5079aa76438620837198db8a5c529fb9878c730bc2b28179b0241cf04c10"
1678dependencies = [ 1676dependencies = [
1679 "ansi_term", 1677 "ansi_term",
1680 "chrono", 1678 "chrono",
diff --git a/crates/assists/src/assist_config.rs b/crates/assists/src/assist_config.rs
index cda2abfb9..adf02edab 100644
--- a/crates/assists/src/assist_config.rs
+++ b/crates/assists/src/assist_config.rs
@@ -4,12 +4,13 @@
4//! module, and we use to statically check that we only produce snippet 4//! module, and we use to statically check that we only produce snippet
5//! assists if we are allowed to. 5//! assists if we are allowed to.
6 6
7use crate::AssistKind; 7use crate::{utils::MergeBehaviour, AssistKind};
8 8
9#[derive(Clone, Debug, PartialEq, Eq)] 9#[derive(Clone, Debug, PartialEq, Eq)]
10pub struct AssistConfig { 10pub struct AssistConfig {
11 pub snippet_cap: Option<SnippetCap>, 11 pub snippet_cap: Option<SnippetCap>,
12 pub allowed: Option<Vec<AssistKind>>, 12 pub allowed: Option<Vec<AssistKind>>,
13 pub insert_use: InsertUseConfig,
13} 14}
14 15
15impl AssistConfig { 16impl AssistConfig {
@@ -25,6 +26,21 @@ pub struct SnippetCap {
25 26
26impl Default for AssistConfig { 27impl Default for AssistConfig {
27 fn default() -> Self { 28 fn default() -> Self {
28 AssistConfig { snippet_cap: Some(SnippetCap { _private: () }), allowed: None } 29 AssistConfig {
30 snippet_cap: Some(SnippetCap { _private: () }),
31 allowed: None,
32 insert_use: InsertUseConfig::default(),
33 }
34 }
35}
36
37#[derive(Clone, Copy, Debug, PartialEq, Eq)]
38pub struct InsertUseConfig {
39 pub merge: Option<MergeBehaviour>,
40}
41
42impl Default for InsertUseConfig {
43 fn default() -> Self {
44 InsertUseConfig { merge: Some(MergeBehaviour::Full) }
29 } 45 }
30} 46}
diff --git a/crates/assists/src/ast_transform.rs b/crates/assists/src/ast_transform.rs
index 5216862ba..835da3bb2 100644
--- a/crates/assists/src/ast_transform.rs
+++ b/crates/assists/src/ast_transform.rs
@@ -18,6 +18,34 @@ pub fn apply<'a, N: AstNode>(transformer: &dyn AstTransform<'a>, node: N) -> N {
18 .rewrite_ast(&node) 18 .rewrite_ast(&node)
19} 19}
20 20
21/// `AstTransform` helps with applying bulk transformations to syntax nodes.
22///
23/// This is mostly useful for IDE code generation. If you paste some existing
24/// code into a new context (for example, to add method overrides to an `impl`
25/// block), you generally want to appropriately qualify the names, and sometimes
26/// you might want to substitute generic parameters as well:
27///
28/// ```
29/// mod x {
30/// pub struct A;
31/// pub trait T<U> { fn foo(&self, _: U) -> A; }
32/// }
33///
34/// mod y {
35/// use x::T;
36///
37/// impl T<()> for () {
38/// // If we invoke **Add Missing Members** here, we want to copy-paste `foo`.
39/// // But we want a slightly-modified version of it:
40/// fn foo(&self, _: ()) -> x::A {}
41/// }
42/// }
43/// ```
44///
45/// So, a single `AstTransform` describes such function from `SyntaxNode` to
46/// `SyntaxNode`. Note that the API here is a bit too high-order and high-brow.
47/// We'd want to somehow express this concept simpler, but so far nobody got to
48/// simplifying this!
21pub trait AstTransform<'a> { 49pub trait AstTransform<'a> {
22 fn get_substitution(&self, node: &syntax::SyntaxNode) -> Option<syntax::SyntaxNode>; 50 fn get_substitution(&self, node: &syntax::SyntaxNode) -> Option<syntax::SyntaxNode>;
23 51
@@ -166,7 +194,7 @@ impl<'a> QualifyPaths<'a> {
166 .map(|arg_list| apply(self, arg_list)); 194 .map(|arg_list| apply(self, arg_list));
167 if let Some(type_args) = type_args { 195 if let Some(type_args) = type_args {
168 let last_segment = path.segment().unwrap(); 196 let last_segment = path.segment().unwrap();
169 path = path.with_segment(last_segment.with_type_args(type_args)) 197 path = path.with_segment(last_segment.with_generic_args(type_args))
170 } 198 }
171 199
172 Some(path.syntax().clone()) 200 Some(path.syntax().clone())
diff --git a/crates/assists/src/handlers/add_missing_impl_members.rs b/crates/assists/src/handlers/add_missing_impl_members.rs
index 83a2ada9a..8df1d786b 100644
--- a/crates/assists/src/handlers/add_missing_impl_members.rs
+++ b/crates/assists/src/handlers/add_missing_impl_members.rs
@@ -111,8 +111,6 @@ fn add_missing_impl_members_inner(
111) -> Option<()> { 111) -> Option<()> {
112 let _p = profile::span("add_missing_impl_members_inner"); 112 let _p = profile::span("add_missing_impl_members_inner");
113 let impl_def = ctx.find_node_at_offset::<ast::Impl>()?; 113 let impl_def = ctx.find_node_at_offset::<ast::Impl>()?;
114 let impl_item_list = impl_def.assoc_item_list()?;
115
116 let trait_ = resolve_target_trait(&ctx.sema, &impl_def)?; 114 let trait_ = resolve_target_trait(&ctx.sema, &impl_def)?;
117 115
118 let def_name = |item: &ast::AssocItem| -> Option<SmolStr> { 116 let def_name = |item: &ast::AssocItem| -> Option<SmolStr> {
@@ -148,11 +146,14 @@ fn add_missing_impl_members_inner(
148 146
149 let target = impl_def.syntax().text_range(); 147 let target = impl_def.syntax().text_range();
150 acc.add(AssistId(assist_id, AssistKind::QuickFix), label, target, |builder| { 148 acc.add(AssistId(assist_id, AssistKind::QuickFix), label, target, |builder| {
149 let impl_item_list = impl_def.assoc_item_list().unwrap_or(make::assoc_item_list());
150
151 let n_existing_items = impl_item_list.assoc_items().count(); 151 let n_existing_items = impl_item_list.assoc_items().count();
152 let source_scope = ctx.sema.scope_for_def(trait_); 152 let source_scope = ctx.sema.scope_for_def(trait_);
153 let target_scope = ctx.sema.scope(impl_item_list.syntax()); 153 let target_scope = ctx.sema.scope(impl_def.syntax());
154 let ast_transform = QualifyPaths::new(&target_scope, &source_scope) 154 let ast_transform = QualifyPaths::new(&target_scope, &source_scope)
155 .or(SubstituteTypeParams::for_trait_impl(&source_scope, trait_, impl_def)); 155 .or(SubstituteTypeParams::for_trait_impl(&source_scope, trait_, impl_def.clone()));
156
156 let items = missing_items 157 let items = missing_items
157 .into_iter() 158 .into_iter()
158 .map(|it| ast_transform::apply(&*ast_transform, it)) 159 .map(|it| ast_transform::apply(&*ast_transform, it))
@@ -162,12 +163,14 @@ fn add_missing_impl_members_inner(
162 _ => it, 163 _ => it,
163 }) 164 })
164 .map(|it| edit::remove_attrs_and_docs(&it)); 165 .map(|it| edit::remove_attrs_and_docs(&it));
166
165 let new_impl_item_list = impl_item_list.append_items(items); 167 let new_impl_item_list = impl_item_list.append_items(items);
166 let first_new_item = new_impl_item_list.assoc_items().nth(n_existing_items).unwrap(); 168 let new_impl_def = impl_def.with_assoc_item_list(new_impl_item_list);
169 let first_new_item =
170 new_impl_def.assoc_item_list().unwrap().assoc_items().nth(n_existing_items).unwrap();
167 171
168 let original_range = impl_item_list.syntax().text_range();
169 match ctx.config.snippet_cap { 172 match ctx.config.snippet_cap {
170 None => builder.replace(original_range, new_impl_item_list.to_string()), 173 None => builder.replace(target, new_impl_def.to_string()),
171 Some(cap) => { 174 Some(cap) => {
172 let mut cursor = Cursor::Before(first_new_item.syntax()); 175 let mut cursor = Cursor::Before(first_new_item.syntax());
173 let placeholder; 176 let placeholder;
@@ -181,8 +184,8 @@ fn add_missing_impl_members_inner(
181 } 184 }
182 builder.replace_snippet( 185 builder.replace_snippet(
183 cap, 186 cap,
184 original_range, 187 target,
185 render_snippet(cap, new_impl_item_list.syntax(), cursor), 188 render_snippet(cap, new_impl_def.syntax(), cursor),
186 ) 189 )
187 } 190 }
188 }; 191 };
@@ -311,6 +314,25 @@ impl Foo for S {
311 } 314 }
312 315
313 #[test] 316 #[test]
317 fn test_impl_def_without_braces() {
318 check_assist(
319 add_missing_impl_members,
320 r#"
321trait Foo { fn foo(&self); }
322struct S;
323impl Foo for S<|>"#,
324 r#"
325trait Foo { fn foo(&self); }
326struct S;
327impl Foo for S {
328 fn foo(&self) {
329 ${0:todo!()}
330 }
331}"#,
332 );
333 }
334
335 #[test]
314 fn fill_in_type_params_1() { 336 fn fill_in_type_params_1() {
315 check_assist( 337 check_assist(
316 add_missing_impl_members, 338 add_missing_impl_members,
diff --git a/crates/assists/src/handlers/auto_import.rs b/crates/assists/src/handlers/auto_import.rs
index c4770f336..b5eb2c722 100644
--- a/crates/assists/src/handlers/auto_import.rs
+++ b/crates/assists/src/handlers/auto_import.rs
@@ -1,20 +1,20 @@
1use std::collections::BTreeSet; 1use std::collections::BTreeSet;
2 2
3use ast::make;
3use either::Either; 4use either::Either;
4use hir::{ 5use hir::{
5 AsAssocItem, AssocItemContainer, ModPath, Module, ModuleDef, PathResolution, Semantics, Trait, 6 AsAssocItem, AssocItemContainer, ModPath, Module, ModuleDef, PathResolution, Semantics, Trait,
6 Type, 7 Type,
7}; 8};
8use ide_db::{imports_locator, RootDatabase}; 9use ide_db::{imports_locator, RootDatabase};
10use insert_use::ImportScope;
9use rustc_hash::FxHashSet; 11use rustc_hash::FxHashSet;
10use syntax::{ 12use syntax::{
11 ast::{self, AstNode}, 13 ast::{self, AstNode},
12 SyntaxNode, 14 SyntaxNode,
13}; 15};
14 16
15use crate::{ 17use crate::{utils::insert_use, AssistContext, AssistId, AssistKind, Assists, GroupLabel};
16 utils::insert_use_statement, AssistContext, AssistId, AssistKind, Assists, GroupLabel,
17};
18 18
19// Assist: auto_import 19// Assist: auto_import
20// 20//
@@ -44,6 +44,9 @@ pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
44 44
45 let range = ctx.sema.original_range(&auto_import_assets.syntax_under_caret).range; 45 let range = ctx.sema.original_range(&auto_import_assets.syntax_under_caret).range;
46 let group = auto_import_assets.get_import_group_message(); 46 let group = auto_import_assets.get_import_group_message();
47 let scope =
48 ImportScope::find_insert_use_container(&auto_import_assets.syntax_under_caret, ctx)?;
49 let syntax = scope.as_syntax_node();
47 for import in proposed_imports { 50 for import in proposed_imports {
48 acc.add_group( 51 acc.add_group(
49 &group, 52 &group,
@@ -51,12 +54,12 @@ pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
51 format!("Import `{}`", &import), 54 format!("Import `{}`", &import),
52 range, 55 range,
53 |builder| { 56 |builder| {
54 insert_use_statement( 57 let new_syntax = insert_use(
55 &auto_import_assets.syntax_under_caret, 58 &scope,
56 &import.to_string(), 59 make::path_from_text(&import.to_string()),
57 ctx, 60 ctx.config.insert_use.merge,
58 builder.text_edit_builder(),
59 ); 61 );
62 builder.replace(syntax.text_range(), new_syntax.to_string())
60 }, 63 },
61 ); 64 );
62 } 65 }
@@ -358,7 +361,7 @@ mod tests {
358 } 361 }
359 ", 362 ",
360 r" 363 r"
361 use PubMod::{PubStruct2, PubStruct1}; 364 use PubMod::{PubStruct1, PubStruct2};
362 365
363 struct Test { 366 struct Test {
364 test: PubStruct2<u8>, 367 test: PubStruct2<u8>,
diff --git a/crates/assists/src/handlers/expand_glob_import.rs b/crates/assists/src/handlers/expand_glob_import.rs
index b39d040f6..e14ac7f65 100644
--- a/crates/assists/src/handlers/expand_glob_import.rs
+++ b/crates/assists/src/handlers/expand_glob_import.rs
@@ -4,7 +4,11 @@ use ide_db::{
4 defs::{classify_name_ref, Definition, NameRefClass}, 4 defs::{classify_name_ref, Definition, NameRefClass},
5 search::SearchScope, 5 search::SearchScope,
6}; 6};
7use syntax::{algo, ast, AstNode, Direction, SyntaxNode, SyntaxToken, T}; 7use syntax::{
8 algo,
9 ast::{self, make},
10 AstNode, Direction, SyntaxNode, SyntaxToken, T,
11};
8 12
9use crate::{ 13use crate::{
10 assist_context::{AssistBuilder, AssistContext, Assists}, 14 assist_context::{AssistBuilder, AssistContext, Assists},
@@ -249,7 +253,10 @@ fn replace_ast(
249 253
250 let new_use_trees: Vec<ast::UseTree> = names_to_import 254 let new_use_trees: Vec<ast::UseTree> = names_to_import
251 .iter() 255 .iter()
252 .map(|n| ast::make::use_tree(ast::make::path_from_text(&n.to_string()), None, None, false)) 256 .map(|n| {
257 let path = make::path_unqualified(make::path_segment(make::name_ref(&n.to_string())));
258 make::use_tree(path, None, None, false)
259 })
253 .collect(); 260 .collect();
254 261
255 let use_trees = [&existing_use_trees[..], &new_use_trees[..]].concat(); 262 let use_trees = [&existing_use_trees[..], &new_use_trees[..]].concat();
@@ -257,8 +264,8 @@ fn replace_ast(
257 match use_trees.as_slice() { 264 match use_trees.as_slice() {
258 [name] => { 265 [name] => {
259 if let Some(end_path) = name.path() { 266 if let Some(end_path) = name.path() {
260 let replacement = ast::make::use_tree( 267 let replacement = make::use_tree(
261 ast::make::path_from_text(&format!("{}::{}", path, end_path)), 268 make::path_from_text(&format!("{}::{}", path, end_path)),
262 None, 269 None,
263 None, 270 None,
264 false, 271 false,
@@ -273,15 +280,12 @@ fn replace_ast(
273 } 280 }
274 names => { 281 names => {
275 let replacement = match parent { 282 let replacement = match parent {
276 Either::Left(_) => ast::make::use_tree( 283 Either::Left(_) => {
277 path, 284 make::use_tree(path, Some(make::use_tree_list(names.to_owned())), None, false)
278 Some(ast::make::use_tree_list(names.to_owned())), 285 .syntax()
279 None, 286 .clone()
280 false, 287 }
281 ) 288 Either::Right(_) => make::use_tree_list(names.to_owned()).syntax().clone(),
282 .syntax()
283 .clone(),
284 Either::Right(_) => ast::make::use_tree_list(names.to_owned()).syntax().clone(),
285 }; 289 };
286 290
287 algo::diff( 291 algo::diff(
diff --git a/crates/assists/src/handlers/extract_struct_from_enum_variant.rs b/crates/assists/src/handlers/extract_struct_from_enum_variant.rs
index 8ac20210a..3ea50f375 100644
--- a/crates/assists/src/handlers/extract_struct_from_enum_variant.rs
+++ b/crates/assists/src/handlers/extract_struct_from_enum_variant.rs
@@ -10,9 +10,10 @@ use syntax::{
10}; 10};
11 11
12use crate::{ 12use crate::{
13 assist_context::AssistBuilder, utils::insert_use_statement, AssistContext, AssistId, 13 assist_context::AssistBuilder, utils::insert_use, AssistContext, AssistId, AssistKind, Assists,
14 AssistKind, Assists,
15}; 14};
15use ast::make;
16use insert_use::ImportScope;
16 17
17// Assist: extract_struct_from_enum_variant 18// Assist: extract_struct_from_enum_variant
18// 19//
@@ -94,6 +95,7 @@ fn existing_struct_def(db: &RootDatabase, variant_name: &str, variant: &EnumVari
94 .any(|(name, _)| name.to_string() == variant_name.to_string()) 95 .any(|(name, _)| name.to_string() == variant_name.to_string())
95} 96}
96 97
98#[allow(dead_code)]
97fn insert_import( 99fn insert_import(
98 ctx: &AssistContext, 100 ctx: &AssistContext,
99 builder: &mut AssistBuilder, 101 builder: &mut AssistBuilder,
@@ -107,12 +109,16 @@ fn insert_import(
107 if let Some(mut mod_path) = mod_path { 109 if let Some(mut mod_path) = mod_path {
108 mod_path.segments.pop(); 110 mod_path.segments.pop();
109 mod_path.segments.push(variant_hir_name.clone()); 111 mod_path.segments.push(variant_hir_name.clone());
110 insert_use_statement( 112 let scope = ImportScope::find_insert_use_container(path.syntax(), ctx)?;
111 path.syntax(), 113 let syntax = scope.as_syntax_node();
112 &mod_path.to_string(), 114
113 ctx, 115 let new_syntax = insert_use(
114 builder.text_edit_builder(), 116 &scope,
117 make::path_from_text(&mod_path.to_string()),
118 ctx.config.insert_use.merge,
115 ); 119 );
120 // FIXME: this will currently panic as multiple imports will have overlapping text ranges
121 builder.replace(syntax.text_range(), new_syntax.to_string())
116 } 122 }
117 Some(()) 123 Some(())
118} 124}
@@ -167,9 +173,9 @@ fn update_reference(
167 builder: &mut AssistBuilder, 173 builder: &mut AssistBuilder,
168 reference: Reference, 174 reference: Reference,
169 source_file: &SourceFile, 175 source_file: &SourceFile,
170 enum_module_def: &ModuleDef, 176 _enum_module_def: &ModuleDef,
171 variant_hir_name: &Name, 177 _variant_hir_name: &Name,
172 visited_modules_set: &mut FxHashSet<Module>, 178 _visited_modules_set: &mut FxHashSet<Module>,
173) -> Option<()> { 179) -> Option<()> {
174 let path_expr: ast::PathExpr = find_node_at_offset::<ast::PathExpr>( 180 let path_expr: ast::PathExpr = find_node_at_offset::<ast::PathExpr>(
175 source_file.syntax(), 181 source_file.syntax(),
@@ -178,13 +184,14 @@ fn update_reference(
178 let call = path_expr.syntax().parent().and_then(ast::CallExpr::cast)?; 184 let call = path_expr.syntax().parent().and_then(ast::CallExpr::cast)?;
179 let list = call.arg_list()?; 185 let list = call.arg_list()?;
180 let segment = path_expr.path()?.segment()?; 186 let segment = path_expr.path()?.segment()?;
181 let module = ctx.sema.scope(&path_expr.syntax()).module()?; 187 let _module = ctx.sema.scope(&path_expr.syntax()).module()?;
182 let list_range = list.syntax().text_range(); 188 let list_range = list.syntax().text_range();
183 let inside_list_range = TextRange::new( 189 let inside_list_range = TextRange::new(
184 list_range.start().checked_add(TextSize::from(1))?, 190 list_range.start().checked_add(TextSize::from(1))?,
185 list_range.end().checked_sub(TextSize::from(1))?, 191 list_range.end().checked_sub(TextSize::from(1))?,
186 ); 192 );
187 builder.edit_file(reference.file_range.file_id); 193 builder.edit_file(reference.file_range.file_id);
194 /* FIXME: this most likely requires AST-based editing, see `insert_import`
188 if !visited_modules_set.contains(&module) { 195 if !visited_modules_set.contains(&module) {
189 if insert_import(ctx, builder, &path_expr, &module, enum_module_def, variant_hir_name) 196 if insert_import(ctx, builder, &path_expr, &module, enum_module_def, variant_hir_name)
190 .is_some() 197 .is_some()
@@ -192,6 +199,7 @@ fn update_reference(
192 visited_modules_set.insert(module); 199 visited_modules_set.insert(module);
193 } 200 }
194 } 201 }
202 */
195 builder.replace(inside_list_range, format!("{}{}", segment, list)); 203 builder.replace(inside_list_range, format!("{}{}", segment, list));
196 Some(()) 204 Some(())
197} 205}
@@ -250,6 +258,7 @@ pub enum A { One(One) }"#,
250 } 258 }
251 259
252 #[test] 260 #[test]
261 #[ignore] // FIXME: this currently panics if `insert_import` is used
253 fn test_extract_struct_with_complex_imports() { 262 fn test_extract_struct_with_complex_imports() {
254 check_assist( 263 check_assist(
255 extract_struct_from_enum_variant, 264 extract_struct_from_enum_variant,
diff --git a/crates/assists/src/handlers/merge_imports.rs b/crates/assists/src/handlers/merge_imports.rs
index 35b884206..fe33cee53 100644
--- a/crates/assists/src/handlers/merge_imports.rs
+++ b/crates/assists/src/handlers/merge_imports.rs
@@ -1,14 +1,14 @@
1use std::iter::successors;
2
3use syntax::{ 1use syntax::{
4 algo::{neighbor, skip_trivia_token, SyntaxRewriter}, 2 algo::{neighbor, SyntaxRewriter},
5 ast::{self, edit::AstNodeEdit, make}, 3 ast, AstNode,
6 AstNode, Direction, InsertPosition, SyntaxElement, T,
7}; 4};
8 5
9use crate::{ 6use crate::{
10 assist_context::{AssistContext, Assists}, 7 assist_context::{AssistContext, Assists},
11 utils::next_prev, 8 utils::{
9 insert_use::{try_merge_imports, try_merge_trees},
10 next_prev, MergeBehaviour,
11 },
12 AssistId, AssistKind, 12 AssistId, AssistKind,
13}; 13};
14 14
@@ -30,23 +30,22 @@ pub(crate) fn merge_imports(acc: &mut Assists, ctx: &AssistContext) -> Option<()
30 let mut offset = ctx.offset(); 30 let mut offset = ctx.offset();
31 31
32 if let Some(use_item) = tree.syntax().parent().and_then(ast::Use::cast) { 32 if let Some(use_item) = tree.syntax().parent().and_then(ast::Use::cast) {
33 let (merged, to_delete) = next_prev() 33 let (merged, to_delete) =
34 .filter_map(|dir| neighbor(&use_item, dir)) 34 next_prev().filter_map(|dir| neighbor(&use_item, dir)).find_map(|use_item2| {
35 .filter_map(|it| Some((it.clone(), it.use_tree()?))) 35 try_merge_imports(&use_item, &use_item2, MergeBehaviour::Full).zip(Some(use_item2))
36 .find_map(|(use_item, use_tree)| {
37 Some((try_merge_trees(&tree, &use_tree)?, use_item))
38 })?; 36 })?;
39 37
40 rewriter.replace_ast(&tree, &merged); 38 rewriter.replace_ast(&use_item, &merged);
41 rewriter += to_delete.remove(); 39 rewriter += to_delete.remove();
42 40
43 if to_delete.syntax().text_range().end() < offset { 41 if to_delete.syntax().text_range().end() < offset {
44 offset -= to_delete.syntax().text_range().len(); 42 offset -= to_delete.syntax().text_range().len();
45 } 43 }
46 } else { 44 } else {
47 let (merged, to_delete) = next_prev() 45 let (merged, to_delete) =
48 .filter_map(|dir| neighbor(&tree, dir)) 46 next_prev().filter_map(|dir| neighbor(&tree, dir)).find_map(|use_tree| {
49 .find_map(|use_tree| Some((try_merge_trees(&tree, &use_tree)?, use_tree.clone())))?; 47 try_merge_trees(&tree, &use_tree, MergeBehaviour::Full).zip(Some(use_tree))
48 })?;
50 49
51 rewriter.replace_ast(&tree, &merged); 50 rewriter.replace_ast(&tree, &merged);
52 rewriter += to_delete.remove(); 51 rewriter += to_delete.remove();
@@ -67,66 +66,6 @@ pub(crate) fn merge_imports(acc: &mut Assists, ctx: &AssistContext) -> Option<()
67 ) 66 )
68} 67}
69 68
70fn try_merge_trees(old: &ast::UseTree, new: &ast::UseTree) -> Option<ast::UseTree> {
71 let lhs_path = old.path()?;
72 let rhs_path = new.path()?;
73
74 let (lhs_prefix, rhs_prefix) = common_prefix(&lhs_path, &rhs_path)?;
75
76 let lhs = old.split_prefix(&lhs_prefix);
77 let rhs = new.split_prefix(&rhs_prefix);
78
79 let should_insert_comma = lhs
80 .use_tree_list()?
81 .r_curly_token()
82 .and_then(|it| skip_trivia_token(it.prev_token()?, Direction::Prev))
83 .map(|it| it.kind() != T![,])
84 .unwrap_or(true);
85
86 let mut to_insert: Vec<SyntaxElement> = Vec::new();
87 if should_insert_comma {
88 to_insert.push(make::token(T![,]).into());
89 to_insert.push(make::tokens::single_space().into());
90 }
91 to_insert.extend(
92 rhs.use_tree_list()?
93 .syntax()
94 .children_with_tokens()
95 .filter(|it| it.kind() != T!['{'] && it.kind() != T!['}']),
96 );
97 let use_tree_list = lhs.use_tree_list()?;
98 let pos = InsertPosition::Before(use_tree_list.r_curly_token()?.into());
99 let use_tree_list = use_tree_list.insert_children(pos, to_insert);
100 Some(lhs.with_use_tree_list(use_tree_list))
101}
102
103fn common_prefix(lhs: &ast::Path, rhs: &ast::Path) -> Option<(ast::Path, ast::Path)> {
104 let mut res = None;
105 let mut lhs_curr = first_path(&lhs);
106 let mut rhs_curr = first_path(&rhs);
107 loop {
108 match (lhs_curr.segment(), rhs_curr.segment()) {
109 (Some(lhs), Some(rhs)) if lhs.syntax().text() == rhs.syntax().text() => (),
110 _ => break,
111 }
112 res = Some((lhs_curr.clone(), rhs_curr.clone()));
113
114 match (lhs_curr.parent_path(), rhs_curr.parent_path()) {
115 (Some(lhs), Some(rhs)) => {
116 lhs_curr = lhs;
117 rhs_curr = rhs;
118 }
119 _ => break,
120 }
121 }
122
123 res
124}
125
126fn first_path(path: &ast::Path) -> ast::Path {
127 successors(Some(path.clone()), |it| it.qualifier()).last().unwrap()
128}
129
130#[cfg(test)] 69#[cfg(test)]
131mod tests { 70mod tests {
132 use crate::tests::{check_assist, check_assist_not_applicable}; 71 use crate::tests::{check_assist, check_assist_not_applicable};
@@ -156,7 +95,7 @@ use std::fmt::Debug;
156use std::fmt<|>::Display; 95use std::fmt<|>::Display;
157", 96",
158 r" 97 r"
159use std::fmt::{Display, Debug}; 98use std::fmt::{Debug, Display};
160", 99",
161 ); 100 );
162 } 101 }
@@ -183,12 +122,84 @@ use std::fmt::{self, Display};
183use std::{fmt, <|>fmt::Display}; 122use std::{fmt, <|>fmt::Display};
184", 123",
185 r" 124 r"
186use std::{fmt::{Display, self}}; 125use std::{fmt::{self, Display}};
187", 126",
188 ); 127 );
189 } 128 }
190 129
191 #[test] 130 #[test]
131 fn skip_pub1() {
132 check_assist_not_applicable(
133 merge_imports,
134 r"
135pub use std::fmt<|>::Debug;
136use std::fmt::Display;
137",
138 );
139 }
140
141 #[test]
142 fn skip_pub_last() {
143 check_assist_not_applicable(
144 merge_imports,
145 r"
146use std::fmt<|>::Debug;
147pub use std::fmt::Display;
148",
149 );
150 }
151
152 #[test]
153 fn skip_pub_crate_pub() {
154 check_assist_not_applicable(
155 merge_imports,
156 r"
157pub(crate) use std::fmt<|>::Debug;
158pub use std::fmt::Display;
159",
160 );
161 }
162
163 #[test]
164 fn skip_pub_pub_crate() {
165 check_assist_not_applicable(
166 merge_imports,
167 r"
168pub use std::fmt<|>::Debug;
169pub(crate) use std::fmt::Display;
170",
171 );
172 }
173
174 #[test]
175 fn merge_pub() {
176 check_assist(
177 merge_imports,
178 r"
179pub use std::fmt<|>::Debug;
180pub use std::fmt::Display;
181",
182 r"
183pub use std::fmt::{Debug, Display};
184",
185 )
186 }
187
188 #[test]
189 fn merge_pub_crate() {
190 check_assist(
191 merge_imports,
192 r"
193pub(crate) use std::fmt<|>::Debug;
194pub(crate) use std::fmt::Display;
195",
196 r"
197pub(crate) use std::fmt::{Debug, Display};
198",
199 )
200 }
201
202 #[test]
192 fn test_merge_nested() { 203 fn test_merge_nested() {
193 check_assist( 204 check_assist(
194 merge_imports, 205 merge_imports,
@@ -199,13 +210,17 @@ use std::{fmt<|>::Debug, fmt::Display};
199use std::{fmt::{Debug, Display}}; 210use std::{fmt::{Debug, Display}};
200", 211",
201 ); 212 );
213 }
214
215 #[test]
216 fn test_merge_nested2() {
202 check_assist( 217 check_assist(
203 merge_imports, 218 merge_imports,
204 r" 219 r"
205use std::{fmt::Debug, fmt<|>::Display}; 220use std::{fmt::Debug, fmt<|>::Display};
206", 221",
207 r" 222 r"
208use std::{fmt::{Display, Debug}}; 223use std::{fmt::{Debug, Display}};
209", 224",
210 ); 225 );
211 } 226 }
@@ -299,9 +314,7 @@ use foo::<|>{
299}; 314};
300", 315",
301 r" 316 r"
302use foo::{ 317use foo::{FooBar, bar::baz};
303 FooBar,
304bar::baz};
305", 318",
306 ) 319 )
307 } 320 }
diff --git a/crates/assists/src/handlers/remove_dbg.rs b/crates/assists/src/handlers/remove_dbg.rs
index 4e252edf0..a8ab2aecc 100644
--- a/crates/assists/src/handlers/remove_dbg.rs
+++ b/crates/assists/src/handlers/remove_dbg.rs
@@ -1,6 +1,6 @@
1use syntax::{ 1use syntax::{
2 ast::{self, AstNode}, 2 ast::{self, AstNode},
3 TextRange, TextSize, T, 3 SyntaxElement, TextRange, TextSize, T,
4}; 4};
5 5
6use crate::{AssistContext, AssistId, AssistKind, Assists}; 6use crate::{AssistContext, AssistId, AssistKind, Assists};
@@ -22,62 +22,108 @@ use crate::{AssistContext, AssistId, AssistKind, Assists};
22// ``` 22// ```
23pub(crate) fn remove_dbg(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 23pub(crate) fn remove_dbg(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
24 let macro_call = ctx.find_node_at_offset::<ast::MacroCall>()?; 24 let macro_call = ctx.find_node_at_offset::<ast::MacroCall>()?;
25 let new_contents = adjusted_macro_contents(&macro_call)?;
25 26
26 if !is_valid_macrocall(&macro_call, "dbg")? { 27 let macro_text_range = macro_call.syntax().text_range();
27 return None;
28 }
29
30 let is_leaf = macro_call.syntax().next_sibling().is_none();
31
32 let macro_end = if macro_call.semicolon_token().is_some() { 28 let macro_end = if macro_call.semicolon_token().is_some() {
33 macro_call.syntax().text_range().end() - TextSize::of(';') 29 macro_text_range.end() - TextSize::of(';')
34 } else { 30 } else {
35 macro_call.syntax().text_range().end() 31 macro_text_range.end()
36 }; 32 };
37 33
38 // macro_range determines what will be deleted and replaced with macro_content 34 acc.add(
39 let macro_range = TextRange::new(macro_call.syntax().text_range().start(), macro_end); 35 AssistId("remove_dbg", AssistKind::Refactor),
40 let paste_instead_of_dbg = { 36 "Remove dbg!()",
41 let text = macro_call.token_tree()?.syntax().text(); 37 macro_text_range,
42 38 |builder| {
43 // leafiness determines if we should include the parenthesis or not 39 builder.replace(TextRange::new(macro_text_range.start(), macro_end), new_contents);
44 let slice_index: TextRange = if is_leaf { 40 },
45 // leaf means - we can extract the contents of the dbg! in text 41 )
46 TextRange::new(TextSize::of('('), text.len() - TextSize::of(')')) 42}
47 } else {
48 // not leaf - means we should keep the parens
49 TextRange::up_to(text.len())
50 };
51 text.slice(slice_index).to_string()
52 };
53 43
54 let target = macro_call.syntax().text_range(); 44fn adjusted_macro_contents(macro_call: &ast::MacroCall) -> Option<String> {
55 acc.add(AssistId("remove_dbg", AssistKind::Refactor), "Remove dbg!()", target, |builder| { 45 let contents = get_valid_macrocall_contents(&macro_call, "dbg")?;
56 builder.replace(macro_range, paste_instead_of_dbg); 46 let macro_text_with_brackets = macro_call.token_tree()?.syntax().text();
47 let macro_text_in_brackets = macro_text_with_brackets.slice(TextRange::new(
48 TextSize::of('('),
49 macro_text_with_brackets.len() - TextSize::of(')'),
50 ));
51
52 let is_leaf = macro_call.syntax().next_sibling().is_none();
53 Some(if !is_leaf && needs_parentheses_around_macro_contents(contents) {
54 format!("({})", macro_text_in_brackets)
55 } else {
56 macro_text_in_brackets.to_string()
57 }) 57 })
58} 58}
59 59
60/// Verifies that the given macro_call actually matches the given name 60/// Verifies that the given macro_call actually matches the given name
61/// and contains proper ending tokens 61/// and contains proper ending tokens, then returns the contents between the ending tokens
62fn is_valid_macrocall(macro_call: &ast::MacroCall, macro_name: &str) -> Option<bool> { 62fn get_valid_macrocall_contents(
63 macro_call: &ast::MacroCall,
64 macro_name: &str,
65) -> Option<Vec<SyntaxElement>> {
63 let path = macro_call.path()?; 66 let path = macro_call.path()?;
64 let name_ref = path.segment()?.name_ref()?; 67 let name_ref = path.segment()?.name_ref()?;
65 68
66 // Make sure it is actually a dbg-macro call, dbg followed by ! 69 // Make sure it is actually a dbg-macro call, dbg followed by !
67 let excl = path.syntax().next_sibling_or_token()?; 70 let excl = path.syntax().next_sibling_or_token()?;
68
69 if name_ref.text() != macro_name || excl.kind() != T![!] { 71 if name_ref.text() != macro_name || excl.kind() != T![!] {
70 return None; 72 return None;
71 } 73 }
72 74
73 let node = macro_call.token_tree()?.syntax().clone(); 75 let mut children_with_tokens = macro_call.token_tree()?.syntax().children_with_tokens();
74 let first_child = node.first_child_or_token()?; 76 let first_child = children_with_tokens.next()?;
75 let last_child = node.last_child_or_token()?; 77 let mut contents_between_brackets = children_with_tokens.collect::<Vec<_>>();
78 let last_child = contents_between_brackets.pop()?;
79
80 if contents_between_brackets.is_empty() {
81 None
82 } else {
83 match (first_child.kind(), last_child.kind()) {
84 (T!['('], T![')']) | (T!['['], T![']']) | (T!['{'], T!['}']) => {
85 Some(contents_between_brackets)
86 }
87 _ => None,
88 }
89 }
90}
76 91
77 match (first_child.kind(), last_child.kind()) { 92fn needs_parentheses_around_macro_contents(macro_contents: Vec<SyntaxElement>) -> bool {
78 (T!['('], T![')']) | (T!['['], T![']']) | (T!['{'], T!['}']) => Some(true), 93 if macro_contents.len() < 2 {
79 _ => Some(false), 94 return false;
80 } 95 }
96 let mut unpaired_brackets_in_contents = Vec::new();
97 for element in macro_contents {
98 match element.kind() {
99 T!['('] | T!['['] | T!['{'] => unpaired_brackets_in_contents.push(element),
100 T![')'] => {
101 if !matches!(unpaired_brackets_in_contents.pop(), Some(correct_bracket) if correct_bracket.kind() == T!['('])
102 {
103 return true;
104 }
105 }
106 T![']'] => {
107 if !matches!(unpaired_brackets_in_contents.pop(), Some(correct_bracket) if correct_bracket.kind() == T!['['])
108 {
109 return true;
110 }
111 }
112 T!['}'] => {
113 if !matches!(unpaired_brackets_in_contents.pop(), Some(correct_bracket) if correct_bracket.kind() == T!['{'])
114 {
115 return true;
116 }
117 }
118 symbol_kind => {
119 let symbol_not_in_bracket = unpaired_brackets_in_contents.is_empty();
120 if symbol_not_in_bracket && symbol_kind.is_punct() {
121 return true;
122 }
123 }
124 }
125 }
126 !unpaired_brackets_in_contents.is_empty()
81} 127}
82 128
83#[cfg(test)] 129#[cfg(test)]
@@ -157,12 +203,38 @@ fn foo(n: usize) {
157 } 203 }
158 204
159 #[test] 205 #[test]
206 fn remove_dbg_from_non_leaf_simple_expression() {
207 check_assist(
208 remove_dbg,
209 "
210fn main() {
211 let mut a = 1;
212 while dbg!<|>(a) < 10000 {
213 a += 1;
214 }
215}
216",
217 "
218fn main() {
219 let mut a = 1;
220 while a < 10000 {
221 a += 1;
222 }
223}
224",
225 );
226 }
227
228 #[test]
160 fn test_remove_dbg_keep_expression() { 229 fn test_remove_dbg_keep_expression() {
161 check_assist( 230 check_assist(
162 remove_dbg, 231 remove_dbg,
163 r#"let res = <|>dbg!(a + b).foo();"#, 232 r#"let res = <|>dbg!(a + b).foo();"#,
164 r#"let res = (a + b).foo();"#, 233 r#"let res = (a + b).foo();"#,
165 ); 234 );
235
236 check_assist(remove_dbg, r#"let res = <|>dbg!(2 + 2) * 5"#, r#"let res = (2 + 2) * 5"#);
237 check_assist(remove_dbg, r#"let res = <|>dbg![2 + 2] * 5"#, r#"let res = (2 + 2) * 5"#);
166 } 238 }
167 239
168 #[test] 240 #[test]
diff --git a/crates/assists/src/handlers/replace_impl_trait_with_generic.rs b/crates/assists/src/handlers/replace_impl_trait_with_generic.rs
new file mode 100644
index 000000000..6738bc134
--- /dev/null
+++ b/crates/assists/src/handlers/replace_impl_trait_with_generic.rs
@@ -0,0 +1,168 @@
1use syntax::ast::{self, edit::AstNodeEdit, make, AstNode, GenericParamsOwner};
2
3use crate::{AssistContext, AssistId, AssistKind, Assists};
4
5// Assist: replace_impl_trait_with_generic
6//
7// Replaces `impl Trait` function argument with the named generic.
8//
9// ```
10// fn foo(bar: <|>impl Bar) {}
11// ```
12// ->
13// ```
14// fn foo<B: Bar>(bar: B) {}
15// ```
16pub(crate) fn replace_impl_trait_with_generic(
17 acc: &mut Assists,
18 ctx: &AssistContext,
19) -> Option<()> {
20 let type_impl_trait = ctx.find_node_at_offset::<ast::ImplTraitType>()?;
21 let type_param = type_impl_trait.syntax().parent().and_then(ast::Param::cast)?;
22 let type_fn = type_param.syntax().ancestors().find_map(ast::Fn::cast)?;
23
24 let impl_trait_ty = type_impl_trait.type_bound_list()?;
25
26 let target = type_fn.syntax().text_range();
27 acc.add(
28 AssistId("replace_impl_trait_with_generic", AssistKind::RefactorRewrite),
29 "Replace impl trait with generic",
30 target,
31 |edit| {
32 let generic_letter = impl_trait_ty.to_string().chars().next().unwrap().to_string();
33
34 let generic_param_list = type_fn
35 .generic_param_list()
36 .unwrap_or_else(|| make::generic_param_list(None))
37 .append_param(make::generic_param(generic_letter.clone(), Some(impl_trait_ty)));
38
39 let new_type_fn = type_fn
40 .replace_descendant::<ast::Type>(type_impl_trait.into(), make::ty(&generic_letter))
41 .with_generic_param_list(generic_param_list);
42
43 edit.replace_ast(type_fn.clone(), new_type_fn);
44 },
45 )
46}
47
48#[cfg(test)]
49mod tests {
50 use super::*;
51
52 use crate::tests::check_assist;
53
54 #[test]
55 fn replace_impl_trait_with_generic_params() {
56 check_assist(
57 replace_impl_trait_with_generic,
58 r#"
59 fn foo<G>(bar: <|>impl Bar) {}
60 "#,
61 r#"
62 fn foo<G, B: Bar>(bar: B) {}
63 "#,
64 );
65 }
66
67 #[test]
68 fn replace_impl_trait_without_generic_params() {
69 check_assist(
70 replace_impl_trait_with_generic,
71 r#"
72 fn foo(bar: <|>impl Bar) {}
73 "#,
74 r#"
75 fn foo<B: Bar>(bar: B) {}
76 "#,
77 );
78 }
79
80 #[test]
81 fn replace_two_impl_trait_with_generic_params() {
82 check_assist(
83 replace_impl_trait_with_generic,
84 r#"
85 fn foo<G>(foo: impl Foo, bar: <|>impl Bar) {}
86 "#,
87 r#"
88 fn foo<G, B: Bar>(foo: impl Foo, bar: B) {}
89 "#,
90 );
91 }
92
93 #[test]
94 fn replace_impl_trait_with_empty_generic_params() {
95 check_assist(
96 replace_impl_trait_with_generic,
97 r#"
98 fn foo<>(bar: <|>impl Bar) {}
99 "#,
100 r#"
101 fn foo<B: Bar>(bar: B) {}
102 "#,
103 );
104 }
105
106 #[test]
107 fn replace_impl_trait_with_empty_multiline_generic_params() {
108 check_assist(
109 replace_impl_trait_with_generic,
110 r#"
111 fn foo<
112 >(bar: <|>impl Bar) {}
113 "#,
114 r#"
115 fn foo<B: Bar
116 >(bar: B) {}
117 "#,
118 );
119 }
120
121 #[test]
122 #[ignore = "This case is very rare but there is no simple solutions to fix it."]
123 fn replace_impl_trait_with_exist_generic_letter() {
124 check_assist(
125 replace_impl_trait_with_generic,
126 r#"
127 fn foo<B>(bar: <|>impl Bar) {}
128 "#,
129 r#"
130 fn foo<B, C: Bar>(bar: C) {}
131 "#,
132 );
133 }
134
135 #[test]
136 fn replace_impl_trait_with_multiline_generic_params() {
137 check_assist(
138 replace_impl_trait_with_generic,
139 r#"
140 fn foo<
141 G: Foo,
142 F,
143 H,
144 >(bar: <|>impl Bar) {}
145 "#,
146 r#"
147 fn foo<
148 G: Foo,
149 F,
150 H, B: Bar
151 >(bar: B) {}
152 "#,
153 );
154 }
155
156 #[test]
157 fn replace_impl_trait_multiple() {
158 check_assist(
159 replace_impl_trait_with_generic,
160 r#"
161 fn foo(bar: <|>impl Foo + Bar) {}
162 "#,
163 r#"
164 fn foo<F: Foo + Bar>(bar: F) {}
165 "#,
166 );
167 }
168}
diff --git a/crates/assists/src/handlers/replace_qualified_name_with_use.rs b/crates/assists/src/handlers/replace_qualified_name_with_use.rs
index 470e5f8ff..8ac907707 100644
--- a/crates/assists/src/handlers/replace_qualified_name_with_use.rs
+++ b/crates/assists/src/handlers/replace_qualified_name_with_use.rs
@@ -2,9 +2,10 @@ use syntax::{algo::SyntaxRewriter, ast, match_ast, AstNode, SyntaxNode, TextRang
2use test_utils::mark; 2use test_utils::mark;
3 3
4use crate::{ 4use crate::{
5 utils::{find_insert_use_container, insert_use_statement}, 5 utils::{insert_use, ImportScope},
6 AssistContext, AssistId, AssistKind, Assists, 6 AssistContext, AssistId, AssistKind, Assists,
7}; 7};
8use ast::make;
8 9
9// Assist: replace_qualified_name_with_use 10// Assist: replace_qualified_name_with_use
10// 11//
@@ -32,7 +33,7 @@ pub(crate) fn replace_qualified_name_with_use(
32 mark::hit!(dont_import_trivial_paths); 33 mark::hit!(dont_import_trivial_paths);
33 return None; 34 return None;
34 } 35 }
35 let path_to_import = path.to_string().clone(); 36 let path_to_import = path.to_string();
36 let path_to_import = match path.segment()?.generic_arg_list() { 37 let path_to_import = match path.segment()?.generic_arg_list() {
37 Some(generic_args) => { 38 Some(generic_args) => {
38 let generic_args_start = 39 let generic_args_start =
@@ -43,28 +44,26 @@ pub(crate) fn replace_qualified_name_with_use(
43 }; 44 };
44 45
45 let target = path.syntax().text_range(); 46 let target = path.syntax().text_range();
47 let scope = ImportScope::find_insert_use_container(path.syntax(), ctx)?;
48 let syntax = scope.as_syntax_node();
46 acc.add( 49 acc.add(
47 AssistId("replace_qualified_name_with_use", AssistKind::RefactorRewrite), 50 AssistId("replace_qualified_name_with_use", AssistKind::RefactorRewrite),
48 "Replace qualified path with use", 51 "Replace qualified path with use",
49 target, 52 target,
50 |builder| { 53 |builder| {
51 let container = match find_insert_use_container(path.syntax(), ctx) {
52 Some(c) => c,
53 None => return,
54 };
55 insert_use_statement(
56 path.syntax(),
57 &path_to_import.to_string(),
58 ctx,
59 builder.text_edit_builder(),
60 );
61
62 // Now that we've brought the name into scope, re-qualify all paths that could be 54 // Now that we've brought the name into scope, re-qualify all paths that could be
63 // affected (that is, all paths inside the node we added the `use` to). 55 // affected (that is, all paths inside the node we added the `use` to).
64 let mut rewriter = SyntaxRewriter::default(); 56 let mut rewriter = SyntaxRewriter::default();
65 let syntax = container.either(|l| l.syntax().clone(), |r| r.syntax().clone()); 57 shorten_paths(&mut rewriter, syntax.clone(), path);
66 shorten_paths(&mut rewriter, syntax, path); 58 let rewritten_syntax = rewriter.rewrite(&syntax);
67 builder.rewrite(rewriter); 59 if let Some(ref import_scope) = ImportScope::from(rewritten_syntax) {
60 let new_syntax = insert_use(
61 import_scope,
62 make::path_from_text(path_to_import),
63 ctx.config.insert_use.merge,
64 );
65 builder.replace(syntax.text_range(), new_syntax.to_string())
66 }
68 }, 67 },
69 ) 68 )
70} 69}
@@ -220,9 +219,10 @@ impl std::fmt::Debug<|> for Foo {
220} 219}
221 ", 220 ",
222 r" 221 r"
223use stdx;
224use std::fmt::Debug; 222use std::fmt::Debug;
225 223
224use stdx;
225
226impl Debug for Foo { 226impl Debug for Foo {
227} 227}
228 ", 228 ",
@@ -274,7 +274,7 @@ impl std::io<|> for Foo {
274} 274}
275 ", 275 ",
276 r" 276 r"
277use std::{io, fmt}; 277use std::{fmt, io};
278 278
279impl io for Foo { 279impl io for Foo {
280} 280}
@@ -293,7 +293,7 @@ impl std::fmt::Debug<|> for Foo {
293} 293}
294 ", 294 ",
295 r" 295 r"
296use std::fmt::{self, Debug, }; 296use std::fmt::{self, Debug};
297 297
298impl Debug for Foo { 298impl Debug for Foo {
299} 299}
@@ -331,7 +331,7 @@ impl std::fmt::nested<|> for Foo {
331} 331}
332", 332",
333 r" 333 r"
334use std::fmt::{Debug, nested::{Display, self}}; 334use std::fmt::{Debug, nested::{self, Display}};
335 335
336impl nested for Foo { 336impl nested for Foo {
337} 337}
@@ -369,7 +369,7 @@ impl std::fmt::nested::Debug<|> for Foo {
369} 369}
370", 370",
371 r" 371 r"
372use std::fmt::{Debug, nested::{Display, Debug}}; 372use std::fmt::{Debug, nested::{Debug, Display}};
373 373
374impl Debug for Foo { 374impl Debug for Foo {
375} 375}
@@ -388,7 +388,7 @@ impl std::fmt::nested::Display<|> for Foo {
388} 388}
389", 389",
390 r" 390 r"
391use std::fmt::{nested::Display, Debug}; 391use std::fmt::{Debug, nested::Display};
392 392
393impl Display for Foo { 393impl Display for Foo {
394} 394}
@@ -428,10 +428,7 @@ use crate::{
428fn foo() { crate::ty::lower<|>::trait_env() } 428fn foo() { crate::ty::lower<|>::trait_env() }
429", 429",
430 r" 430 r"
431use crate::{ 431use crate::{AssocItem, ty::{Substs, Ty, lower}};
432 ty::{Substs, Ty, lower},
433 AssocItem,
434};
435 432
436fn foo() { lower::trait_env() } 433fn foo() { lower::trait_env() }
437", 434",
@@ -451,6 +448,8 @@ impl foo::Debug<|> for Foo {
451 r" 448 r"
452use std::fmt as foo; 449use std::fmt as foo;
453 450
451use foo::Debug;
452
454impl Debug for Foo { 453impl Debug for Foo {
455} 454}
456", 455",
@@ -515,6 +514,7 @@ fn main() {
515 ", 514 ",
516 r" 515 r"
517#![allow(dead_code)] 516#![allow(dead_code)]
517
518use std::fmt::Debug; 518use std::fmt::Debug;
519 519
520fn main() { 520fn main() {
@@ -647,9 +647,8 @@ impl std::io<|> for Foo {
647} 647}
648 ", 648 ",
649 r" 649 r"
650use std::io;
651
652pub use std::fmt; 650pub use std::fmt;
651use std::io;
653 652
654impl io for Foo { 653impl io for Foo {
655} 654}
@@ -668,9 +667,8 @@ impl std::io<|> for Foo {
668} 667}
669 ", 668 ",
670 r" 669 r"
671use std::io;
672
673pub(crate) use std::fmt; 670pub(crate) use std::fmt;
671use std::io;
674 672
675impl io for Foo { 673impl io for Foo {
676} 674}
diff --git a/crates/assists/src/lib.rs b/crates/assists/src/lib.rs
index 2e0d191a6..cbac53e71 100644
--- a/crates/assists/src/lib.rs
+++ b/crates/assists/src/lib.rs
@@ -155,6 +155,7 @@ mod handlers {
155 mod remove_unused_param; 155 mod remove_unused_param;
156 mod reorder_fields; 156 mod reorder_fields;
157 mod replace_if_let_with_match; 157 mod replace_if_let_with_match;
158 mod replace_impl_trait_with_generic;
158 mod replace_let_with_if_let; 159 mod replace_let_with_if_let;
159 mod replace_qualified_name_with_use; 160 mod replace_qualified_name_with_use;
160 mod replace_unwrap_with_match; 161 mod replace_unwrap_with_match;
@@ -202,6 +203,7 @@ mod handlers {
202 remove_unused_param::remove_unused_param, 203 remove_unused_param::remove_unused_param,
203 reorder_fields::reorder_fields, 204 reorder_fields::reorder_fields,
204 replace_if_let_with_match::replace_if_let_with_match, 205 replace_if_let_with_match::replace_if_let_with_match,
206 replace_impl_trait_with_generic::replace_impl_trait_with_generic,
205 replace_let_with_if_let::replace_let_with_if_let, 207 replace_let_with_if_let::replace_let_with_if_let,
206 replace_qualified_name_with_use::replace_qualified_name_with_use, 208 replace_qualified_name_with_use::replace_qualified_name_with_use,
207 replace_unwrap_with_match::replace_unwrap_with_match, 209 replace_unwrap_with_match::replace_unwrap_with_match,
diff --git a/crates/assists/src/tests/generated.rs b/crates/assists/src/tests/generated.rs
index 04c8fd1f9..27d15adb0 100644
--- a/crates/assists/src/tests/generated.rs
+++ b/crates/assists/src/tests/generated.rs
@@ -815,6 +815,19 @@ fn handle(action: Action) {
815} 815}
816 816
817#[test] 817#[test]
818fn doctest_replace_impl_trait_with_generic() {
819 check_doc_test(
820 "replace_impl_trait_with_generic",
821 r#####"
822fn foo(bar: <|>impl Bar) {}
823"#####,
824 r#####"
825fn foo<B: Bar>(bar: B) {}
826"#####,
827 )
828}
829
830#[test]
818fn doctest_replace_let_with_if_let() { 831fn doctest_replace_let_with_if_let() {
819 check_doc_test( 832 check_doc_test(
820 "replace_let_with_if_let", 833 "replace_let_with_if_let",
diff --git a/crates/assists/src/utils.rs b/crates/assists/src/utils.rs
index daa7b64f7..b0511ceb6 100644
--- a/crates/assists/src/utils.rs
+++ b/crates/assists/src/utils.rs
@@ -16,7 +16,8 @@ use syntax::{
16 16
17use crate::assist_config::SnippetCap; 17use crate::assist_config::SnippetCap;
18 18
19pub(crate) use insert_use::{find_insert_use_container, insert_use_statement}; 19pub use insert_use::MergeBehaviour;
20pub(crate) use insert_use::{insert_use, ImportScope};
20 21
21pub(crate) fn unwrap_trivial_block(block: ast::BlockExpr) -> ast::Expr { 22pub(crate) fn unwrap_trivial_block(block: ast::BlockExpr) -> ast::Expr {
22 extract_trivial_expression(&block) 23 extract_trivial_expression(&block)
diff --git a/crates/assists/src/utils/insert_use.rs b/crates/assists/src/utils/insert_use.rs
index 49096a67c..09f4a2224 100644
--- a/crates/assists/src/utils/insert_use.rs
+++ b/crates/assists/src/utils/insert_use.rs
@@ -1,546 +1,933 @@
1//! Handle syntactic aspects of inserting a new `use`. 1//! Handle syntactic aspects of inserting a new `use`.
2// FIXME: rewrite according to the plan, outlined in 2use std::{
3// https://github.com/rust-analyzer/rust-analyzer/issues/3301#issuecomment-592931553 3 cmp::Ordering,
4 4 iter::{self, successors},
5use std::iter::successors; 5};
6 6
7use either::Either; 7use ast::{
8 edit::{AstNodeEdit, IndentLevel},
9 PathSegmentKind, VisibilityOwner,
10};
8use syntax::{ 11use syntax::{
9 ast::{self, NameOwner, VisibilityOwner}, 12 algo,
10 AstNode, AstToken, Direction, SmolStr, 13 ast::{self, make, AstNode},
11 SyntaxKind::{PATH, PATH_SEGMENT}, 14 InsertPosition, SyntaxElement, SyntaxNode,
12 SyntaxNode, SyntaxToken, T,
13}; 15};
14use text_edit::TextEditBuilder;
15
16use crate::assist_context::AssistContext;
17
18/// Determines the containing syntax node in which to insert a `use` statement affecting `position`.
19pub(crate) fn find_insert_use_container(
20 position: &SyntaxNode,
21 ctx: &AssistContext,
22) -> Option<Either<ast::ItemList, ast::SourceFile>> {
23 ctx.sema.ancestors_with_macros(position.clone()).find_map(|n| {
24 if let Some(module) = ast::Module::cast(n.clone()) {
25 return module.item_list().map(|it| Either::Left(it));
26 }
27 Some(Either::Right(ast::SourceFile::cast(n)?))
28 })
29}
30 16
31/// Creates and inserts a use statement for the given path to import. 17#[derive(Debug)]
32/// The use statement is inserted in the scope most appropriate to the 18pub enum ImportScope {
33/// the cursor position given, additionally merged with the existing use imports. 19 File(ast::SourceFile),
34pub(crate) fn insert_use_statement( 20 Module(ast::ItemList),
35 // Ideally the position of the cursor, used to
36 position: &SyntaxNode,
37 path_to_import: &str,
38 ctx: &AssistContext,
39 builder: &mut TextEditBuilder,
40) {
41 let target = path_to_import.split("::").map(SmolStr::new).collect::<Vec<_>>();
42 let container = find_insert_use_container(position, ctx);
43
44 if let Some(container) = container {
45 let syntax = container.either(|l| l.syntax().clone(), |r| r.syntax().clone());
46 let action = best_action_for_target(syntax, position.clone(), &target);
47 make_assist(&action, &target, builder);
48 }
49} 21}
50 22
51fn collect_path_segments_raw( 23impl ImportScope {
52 segments: &mut Vec<ast::PathSegment>, 24 pub fn from(syntax: SyntaxNode) -> Option<Self> {
53 mut path: ast::Path, 25 if let Some(module) = ast::Module::cast(syntax.clone()) {
54) -> Option<usize> { 26 module.item_list().map(ImportScope::Module)
55 let oldlen = segments.len(); 27 } else if let this @ Some(_) = ast::SourceFile::cast(syntax.clone()) {
56 loop { 28 this.map(ImportScope::File)
57 let mut children = path.syntax().children_with_tokens(); 29 } else {
58 let (first, second, third) = ( 30 ast::ItemList::cast(syntax).map(ImportScope::Module)
59 children.next().map(|n| (n.clone(), n.kind())),
60 children.next().map(|n| (n.clone(), n.kind())),
61 children.next().map(|n| (n.clone(), n.kind())),
62 );
63 match (first, second, third) {
64 (Some((subpath, PATH)), Some((_, T![::])), Some((segment, PATH_SEGMENT))) => {
65 path = ast::Path::cast(subpath.as_node()?.clone())?;
66 segments.push(ast::PathSegment::cast(segment.as_node()?.clone())?);
67 }
68 (Some((segment, PATH_SEGMENT)), _, _) => {
69 segments.push(ast::PathSegment::cast(segment.as_node()?.clone())?);
70 break;
71 }
72 (_, _, _) => return None,
73 } 31 }
74 } 32 }
75 // We need to reverse only the new added segments
76 let only_new_segments = segments.split_at_mut(oldlen).1;
77 only_new_segments.reverse();
78 Some(segments.len() - oldlen)
79}
80 33
81fn fmt_segments_raw(segments: &[SmolStr], buf: &mut String) { 34 /// Determines the containing syntax node in which to insert a `use` statement affecting `position`.
82 let mut iter = segments.iter(); 35 pub(crate) fn find_insert_use_container(
83 if let Some(s) = iter.next() { 36 position: &SyntaxNode,
84 buf.push_str(s); 37 ctx: &crate::assist_context::AssistContext,
85 } 38 ) -> Option<Self> {
86 for s in iter { 39 ctx.sema.ancestors_with_macros(position.clone()).find_map(Self::from)
87 buf.push_str("::");
88 buf.push_str(s);
89 } 40 }
90}
91
92/// Returns the number of common segments.
93fn compare_path_segments(left: &[SmolStr], right: &[ast::PathSegment]) -> usize {
94 left.iter().zip(right).take_while(|(l, r)| compare_path_segment(l, r)).count()
95}
96 41
97fn compare_path_segment(a: &SmolStr, b: &ast::PathSegment) -> bool { 42 pub(crate) fn as_syntax_node(&self) -> &SyntaxNode {
98 if let Some(kb) = b.kind() { 43 match self {
99 match kb { 44 ImportScope::File(file) => file.syntax(),
100 ast::PathSegmentKind::Name(nameref_b) => a == nameref_b.text(), 45 ImportScope::Module(item_list) => item_list.syntax(),
101 ast::PathSegmentKind::SelfKw => a == "self",
102 ast::PathSegmentKind::SuperKw => a == "super",
103 ast::PathSegmentKind::CrateKw => a == "crate",
104 ast::PathSegmentKind::Type { .. } => false, // not allowed in imports
105 } 46 }
106 } else {
107 false
108 } 47 }
109}
110
111fn compare_path_segment_with_name(a: &SmolStr, b: &ast::Name) -> bool {
112 a == b.text()
113}
114 48
115#[derive(Clone, Debug)] 49 fn indent_level(&self) -> IndentLevel {
116enum ImportAction { 50 match self {
117 Nothing, 51 ImportScope::File(file) => file.indent_level(),
118 // Add a brand new use statement. 52 ImportScope::Module(item_list) => item_list.indent_level() + 1,
119 AddNewUse {
120 anchor: Option<SyntaxNode>, // anchor node
121 add_after_anchor: bool,
122 },
123
124 // To split an existing use statement creating a nested import.
125 AddNestedImport {
126 // how may segments matched with the target path
127 common_segments: usize,
128 path_to_split: ast::Path,
129 // the first segment of path_to_split we want to add into the new nested list
130 first_segment_to_split: Option<ast::PathSegment>,
131 // Wether to add 'self' in addition to the target path
132 add_self: bool,
133 },
134 // To add the target path to an existing nested import tree list.
135 AddInTreeList {
136 common_segments: usize,
137 // The UseTreeList where to add the target path
138 tree_list: ast::UseTreeList,
139 add_self: bool,
140 },
141}
142
143impl ImportAction {
144 fn add_new_use(anchor: Option<SyntaxNode>, add_after_anchor: bool) -> Self {
145 ImportAction::AddNewUse { anchor, add_after_anchor }
146 }
147
148 fn add_nested_import(
149 common_segments: usize,
150 path_to_split: ast::Path,
151 first_segment_to_split: Option<ast::PathSegment>,
152 add_self: bool,
153 ) -> Self {
154 ImportAction::AddNestedImport {
155 common_segments,
156 path_to_split,
157 first_segment_to_split,
158 add_self,
159 } 53 }
160 } 54 }
161 55
162 fn add_in_tree_list( 56 fn first_insert_pos(&self) -> (InsertPosition<SyntaxElement>, AddBlankLine) {
163 common_segments: usize, 57 match self {
164 tree_list: ast::UseTreeList, 58 ImportScope::File(_) => (InsertPosition::First, AddBlankLine::AfterTwice),
165 add_self: bool, 59 // don't insert the imports before the item list's opening curly brace
166 ) -> Self { 60 ImportScope::Module(item_list) => item_list
167 ImportAction::AddInTreeList { common_segments, tree_list, add_self } 61 .l_curly_token()
168 } 62 .map(|b| (InsertPosition::After(b.into()), AddBlankLine::Around))
169 63 .unwrap_or((InsertPosition::First, AddBlankLine::AfterTwice)),
170 fn better(left: ImportAction, right: ImportAction) -> ImportAction {
171 if left.is_better(&right) {
172 left
173 } else {
174 right
175 } 64 }
176 } 65 }
177 66
178 fn is_better(&self, other: &ImportAction) -> bool { 67 fn insert_pos_after_inner_attribute(&self) -> (InsertPosition<SyntaxElement>, AddBlankLine) {
179 match (self, other) { 68 // check if the scope has inner attributes, we dont want to insert in front of them
180 (ImportAction::Nothing, _) => true, 69 match self
181 (ImportAction::AddInTreeList { .. }, ImportAction::Nothing) => false, 70 .as_syntax_node()
182 ( 71 .children()
183 ImportAction::AddNestedImport { common_segments: n, .. }, 72 // no flat_map here cause we want to short circuit the iterator
184 ImportAction::AddInTreeList { common_segments: m, .. }, 73 .map(ast::Attr::cast)
185 ) 74 .take_while(|attr| {
186 | ( 75 attr.as_ref().map(|attr| attr.kind() == ast::AttrKind::Inner).unwrap_or(false)
187 ImportAction::AddInTreeList { common_segments: n, .. }, 76 })
188 ImportAction::AddNestedImport { common_segments: m, .. }, 77 .last()
189 ) 78 .flatten()
190 | ( 79 {
191 ImportAction::AddInTreeList { common_segments: n, .. }, 80 Some(attr) => {
192 ImportAction::AddInTreeList { common_segments: m, .. }, 81 (InsertPosition::After(attr.syntax().clone().into()), AddBlankLine::BeforeTwice)
193 ) 82 }
194 | ( 83 None => self.first_insert_pos(),
195 ImportAction::AddNestedImport { common_segments: n, .. },
196 ImportAction::AddNestedImport { common_segments: m, .. },
197 ) => n > m,
198 (ImportAction::AddInTreeList { .. }, _) => true,
199 (ImportAction::AddNestedImport { .. }, ImportAction::Nothing) => false,
200 (ImportAction::AddNestedImport { .. }, _) => true,
201 (ImportAction::AddNewUse { .. }, _) => false,
202 } 84 }
203 } 85 }
204} 86}
205 87
206// Find out the best ImportAction to import target path against current_use_tree. 88/// Insert an import path into the given file/node. A `merge` value of none indicates that no import merging is allowed to occur.
207// If current_use_tree has a nested import the function gets called recursively on every UseTree inside a UseTreeList. 89pub(crate) fn insert_use(
208fn walk_use_tree_for_best_action( 90 scope: &ImportScope,
209 current_path_segments: &mut Vec<ast::PathSegment>, // buffer containing path segments 91 path: ast::Path,
210 current_parent_use_tree_list: Option<ast::UseTreeList>, // will be Some value if we are in a nested import 92 merge: Option<MergeBehaviour>,
211 current_use_tree: ast::UseTree, // the use tree we are currently examinating 93) -> SyntaxNode {
212 target: &[SmolStr], // the path we want to import 94 let use_item = make::use_(make::use_tree(path.clone(), None, None, false));
213) -> ImportAction { 95 // merge into existing imports if possible
214 // We save the number of segments in the buffer so we can restore the correct segments 96 if let Some(mb) = merge {
215 // before returning. Recursive call will add segments so we need to delete them. 97 for existing_use in scope.as_syntax_node().children().filter_map(ast::Use::cast) {
216 let prev_len = current_path_segments.len(); 98 if let Some(merged) = try_merge_imports(&existing_use, &use_item, mb) {
217 99 let to_delete: SyntaxElement = existing_use.syntax().clone().into();
218 let tree_list = current_use_tree.use_tree_list(); 100 let to_delete = to_delete.clone()..=to_delete;
219 let alias = current_use_tree.rename(); 101 let to_insert = iter::once(merged.syntax().clone().into());
220 102 return algo::replace_children(scope.as_syntax_node(), to_delete, to_insert);
221 let path = match current_use_tree.path() { 103 }
222 Some(path) => path,
223 None => {
224 // If the use item don't have a path, it means it's broken (syntax error)
225 return ImportAction::add_new_use(
226 current_use_tree
227 .syntax()
228 .ancestors()
229 .find_map(ast::Use::cast)
230 .map(|it| it.syntax().clone()),
231 true,
232 );
233 }
234 };
235
236 // This can happen only if current_use_tree is a direct child of a UseItem
237 if let Some(name) = alias.and_then(|it| it.name()) {
238 if compare_path_segment_with_name(&target[0], &name) {
239 return ImportAction::Nothing;
240 } 104 }
241 } 105 }
242 106
243 collect_path_segments_raw(current_path_segments, path.clone()); 107 // either we weren't allowed to merge or there is no import that fits the merge conditions
244 108 // so look for the place we have to insert to
245 // We compare only the new segments added in the line just above. 109 let (insert_position, add_blank) = find_insert_position(scope, path);
246 // The first prev_len segments were already compared in 'parent' recursive calls. 110
247 let left = target.split_at(prev_len).1; 111 let to_insert: Vec<SyntaxElement> = {
248 let right = current_path_segments.split_at(prev_len).1; 112 let mut buf = Vec::new();
249 let common = compare_path_segments(left, &right); 113
250 let mut action = match common { 114 match add_blank {
251 0 => ImportAction::add_new_use( 115 AddBlankLine::Before | AddBlankLine::Around => {
252 // e.g: target is std::fmt and we can have 116 buf.push(make::tokens::single_newline().into())
253 // use foo::bar
254 // We add a brand new use statement
255 current_use_tree
256 .syntax()
257 .ancestors()
258 .find_map(ast::Use::cast)
259 .map(|it| it.syntax().clone()),
260 true,
261 ),
262 common if common == left.len() && left.len() == right.len() => {
263 // e.g: target is std::fmt and we can have
264 // 1- use std::fmt;
265 // 2- use std::fmt::{ ... }
266 if let Some(list) = tree_list {
267 // In case 2 we need to add self to the nested list
268 // unless it's already there
269 let has_self = list.use_trees().map(|it| it.path()).any(|p| {
270 p.and_then(|it| it.segment())
271 .and_then(|it| it.kind())
272 .filter(|k| *k == ast::PathSegmentKind::SelfKw)
273 .is_some()
274 });
275
276 if has_self {
277 ImportAction::Nothing
278 } else {
279 ImportAction::add_in_tree_list(current_path_segments.len(), list, true)
280 }
281 } else {
282 // Case 1
283 ImportAction::Nothing
284 } 117 }
118 AddBlankLine::BeforeTwice => buf.push(make::tokens::blank_line().into()),
119 _ => (),
285 } 120 }
286 common if common != left.len() && left.len() == right.len() => { 121
287 // e.g: target is std::fmt and we have 122 if let ident_level @ 1..=usize::MAX = scope.indent_level().0 as usize {
288 // use std::io; 123 buf.push(make::tokens::whitespace(&" ".repeat(4 * ident_level)).into());
289 // We need to split.
290 let segments_to_split = current_path_segments.split_at(prev_len + common).1;
291 ImportAction::add_nested_import(
292 prev_len + common,
293 path,
294 Some(segments_to_split[0].clone()),
295 false,
296 )
297 } 124 }
298 common if common == right.len() && left.len() > right.len() => { 125 buf.push(use_item.syntax().clone().into());
299 // e.g: target is std::fmt and we can have 126
300 // 1- use std; 127 match add_blank {
301 // 2- use std::{ ... }; 128 AddBlankLine::After | AddBlankLine::Around => {
302 129 buf.push(make::tokens::single_newline().into())
303 // fallback action
304 let mut better_action = ImportAction::add_new_use(
305 current_use_tree
306 .syntax()
307 .ancestors()
308 .find_map(ast::Use::cast)
309 .map(|it| it.syntax().clone()),
310 true,
311 );
312 if let Some(list) = tree_list {
313 // Case 2, check recursively if the path is already imported in the nested list
314 for u in list.use_trees() {
315 let child_action = walk_use_tree_for_best_action(
316 current_path_segments,
317 Some(list.clone()),
318 u,
319 target,
320 );
321 if child_action.is_better(&better_action) {
322 better_action = child_action;
323 if let ImportAction::Nothing = better_action {
324 return better_action;
325 }
326 }
327 }
328 } else {
329 // Case 1, split adding self
330 better_action = ImportAction::add_nested_import(prev_len + common, path, None, true)
331 } 130 }
332 better_action 131 AddBlankLine::AfterTwice => buf.push(make::tokens::blank_line().into()),
132 _ => (),
333 } 133 }
334 common if common == left.len() && left.len() < right.len() => {
335 // e.g: target is std::fmt and we can have
336 // use std::fmt::Debug;
337 let segments_to_split = current_path_segments.split_at(prev_len + common).1;
338 ImportAction::add_nested_import(
339 prev_len + common,
340 path,
341 Some(segments_to_split[0].clone()),
342 true,
343 )
344 }
345 common if common < left.len() && common < right.len() => {
346 // e.g: target is std::fmt::nested::Debug
347 // use std::fmt::Display
348 let segments_to_split = current_path_segments.split_at(prev_len + common).1;
349 ImportAction::add_nested_import(
350 prev_len + common,
351 path,
352 Some(segments_to_split[0].clone()),
353 false,
354 )
355 }
356 _ => unreachable!(),
357 };
358 134
359 // If we are inside a UseTreeList adding a use statement become adding to the existing 135 buf
360 // tree list.
361 action = match (current_parent_use_tree_list, action.clone()) {
362 (Some(use_tree_list), ImportAction::AddNewUse { .. }) => {
363 ImportAction::add_in_tree_list(prev_len, use_tree_list, false)
364 }
365 (_, _) => action,
366 }; 136 };
367 137
368 // We remove the segments added 138 algo::insert_children(scope.as_syntax_node(), insert_position, to_insert)
369 current_path_segments.truncate(prev_len);
370 action
371} 139}
372 140
373fn best_action_for_target( 141fn eq_visibility(vis0: Option<ast::Visibility>, vis1: Option<ast::Visibility>) -> bool {
374 container: SyntaxNode, 142 match (vis0, vis1) {
375 anchor: SyntaxNode, 143 (None, None) => true,
376 target: &[SmolStr], 144 // FIXME: Don't use the string representation to check for equality
377) -> ImportAction { 145 // spaces inside of the node would break this comparison
378 let mut storage = Vec::with_capacity(16); // this should be the only allocation 146 (Some(vis0), Some(vis1)) => vis0.to_string() == vis1.to_string(),
379 let best_action = container 147 _ => false,
380 .children() 148 }
381 .filter_map(ast::Use::cast) 149}
382 .filter(|u| u.visibility().is_none())
383 .filter_map(|it| it.use_tree())
384 .map(|u| walk_use_tree_for_best_action(&mut storage, None, u, target))
385 .fold(None, |best, a| match best {
386 Some(best) => Some(ImportAction::better(best, a)),
387 None => Some(a),
388 });
389
390 match best_action {
391 Some(action) => action,
392 None => {
393 // We have no action and no UseItem was found in container so we find
394 // another item and we use it as anchor.
395 // If there are no items above, we choose the target path itself as anchor.
396 // todo: we should include even whitespace blocks as anchor candidates
397 let anchor = container.children().next().or_else(|| Some(anchor));
398 150
399 let add_after_anchor = anchor 151pub(crate) fn try_merge_imports(
400 .clone() 152 lhs: &ast::Use,
401 .and_then(ast::Attr::cast) 153 rhs: &ast::Use,
402 .map(|attr| attr.kind() == ast::AttrKind::Inner) 154 merge_behaviour: MergeBehaviour,
403 .unwrap_or(false); 155) -> Option<ast::Use> {
404 ImportAction::add_new_use(anchor, add_after_anchor) 156 // don't merge imports with different visibilities
405 } 157 if !eq_visibility(lhs.visibility(), rhs.visibility()) {
158 return None;
406 } 159 }
160 let lhs_tree = lhs.use_tree()?;
161 let rhs_tree = rhs.use_tree()?;
162 let merged = try_merge_trees(&lhs_tree, &rhs_tree, merge_behaviour)?;
163 Some(lhs.with_use_tree(merged))
407} 164}
408 165
409fn make_assist(action: &ImportAction, target: &[SmolStr], edit: &mut TextEditBuilder) { 166pub(crate) fn try_merge_trees(
410 match action { 167 lhs: &ast::UseTree,
411 ImportAction::AddNewUse { anchor, add_after_anchor } => { 168 rhs: &ast::UseTree,
412 make_assist_add_new_use(anchor, *add_after_anchor, target, edit) 169 merge: MergeBehaviour,
413 } 170) -> Option<ast::UseTree> {
414 ImportAction::AddInTreeList { common_segments, tree_list, add_self } => { 171 let lhs_path = lhs.path()?;
415 // We know that the fist n segments already exists in the use statement we want 172 let rhs_path = rhs.path()?;
416 // to modify, so we want to add only the last target.len() - n segments. 173
417 let segments_to_add = target.split_at(*common_segments).1; 174 let (lhs_prefix, rhs_prefix) = common_prefix(&lhs_path, &rhs_path)?;
418 make_assist_add_in_tree_list(tree_list, segments_to_add, *add_self, edit) 175 let lhs = lhs.split_prefix(&lhs_prefix);
419 } 176 let rhs = rhs.split_prefix(&rhs_prefix);
420 ImportAction::AddNestedImport { 177 recursive_merge(&lhs, &rhs, merge).map(|(merged, _)| merged)
421 common_segments, 178}
422 path_to_split, 179
423 first_segment_to_split, 180/// Recursively "zips" together lhs and rhs.
424 add_self, 181fn recursive_merge(
425 } => { 182 lhs: &ast::UseTree,
426 let segments_to_add = target.split_at(*common_segments).1; 183 rhs: &ast::UseTree,
427 make_assist_add_nested_import( 184 merge: MergeBehaviour,
428 path_to_split, 185) -> Option<(ast::UseTree, bool)> {
429 first_segment_to_split, 186 let mut use_trees = lhs
430 segments_to_add, 187 .use_tree_list()
431 *add_self, 188 .into_iter()
432 edit, 189 .flat_map(|list| list.use_trees())
433 ) 190 // check if any of the use trees are nested, if they are and the behaviour is `last` we are not allowed to merge this
191 // so early exit the iterator by using Option's Intoiterator impl
192 .map(|tree| match merge == MergeBehaviour::Last && tree.use_tree_list().is_some() {
193 true => None,
194 false => Some(tree),
195 })
196 .collect::<Option<Vec<_>>>()?;
197 use_trees.sort_unstable_by(|a, b| path_cmp_opt(a.path(), b.path()));
198 for rhs_t in rhs.use_tree_list().into_iter().flat_map(|list| list.use_trees()) {
199 let rhs_path = rhs_t.path();
200 match use_trees.binary_search_by(|p| path_cmp_opt(p.path(), rhs_path.clone())) {
201 Ok(idx) => {
202 let lhs_t = &mut use_trees[idx];
203 let lhs_path = lhs_t.path()?;
204 let rhs_path = rhs_path?;
205 let (lhs_prefix, rhs_prefix) = common_prefix(&lhs_path, &rhs_path)?;
206 if lhs_prefix == lhs_path && rhs_prefix == rhs_path {
207 let tree_is_self = |tree: ast::UseTree| {
208 tree.path().as_ref().map(path_is_self).unwrap_or(false)
209 };
210 // check if only one of the two trees has a tree list, and whether that then contains `self` or not.
211 // If this is the case we can skip this iteration since the path without the list is already included in the other one via `self`
212 let tree_contains_self = |tree: &ast::UseTree| {
213 tree.use_tree_list()
214 .map(|tree_list| tree_list.use_trees().any(tree_is_self))
215 .unwrap_or(false)
216 };
217 match (tree_contains_self(&lhs_t), tree_contains_self(&rhs_t)) {
218 (true, false) => continue,
219 (false, true) => {
220 *lhs_t = rhs_t;
221 continue;
222 }
223 _ => (),
224 }
225
226 // glob imports arent part of the use-tree lists so we need to special handle them here as well
227 // this special handling is only required for when we merge a module import into a glob import of said module
228 // see the `merge_self_glob` or `merge_mod_into_glob` tests
229 if lhs_t.star_token().is_some() || rhs_t.star_token().is_some() {
230 *lhs_t = make::use_tree(
231 make::path_unqualified(make::path_segment_self()),
232 None,
233 None,
234 false,
235 );
236 use_trees.insert(idx, make::glob_use_tree());
237 continue;
238 }
239 }
240 let lhs = lhs_t.split_prefix(&lhs_prefix);
241 let rhs = rhs_t.split_prefix(&rhs_prefix);
242 let this_has_children = use_trees.len() > 0;
243 match recursive_merge(&lhs, &rhs, merge) {
244 Some((_, has_multiple_children))
245 if merge == MergeBehaviour::Last
246 && this_has_children
247 && has_multiple_children =>
248 {
249 return None
250 }
251 Some((use_tree, _)) => use_trees[idx] = use_tree,
252 None => use_trees.insert(idx, rhs_t),
253 }
254 }
255 Err(_)
256 if merge == MergeBehaviour::Last
257 && use_trees.len() > 0
258 && rhs_t.use_tree_list().is_some() =>
259 {
260 return None
261 }
262 Err(idx) => {
263 use_trees.insert(idx, rhs_t);
264 }
434 } 265 }
435 _ => {}
436 } 266 }
267 let has_multiple_children = use_trees.len() > 1;
268 Some((lhs.with_use_tree_list(make::use_tree_list(use_trees)), has_multiple_children))
437} 269}
438 270
439fn make_assist_add_new_use( 271/// Traverses both paths until they differ, returning the common prefix of both.
440 anchor: &Option<SyntaxNode>, 272fn common_prefix(lhs: &ast::Path, rhs: &ast::Path) -> Option<(ast::Path, ast::Path)> {
441 after: bool, 273 let mut res = None;
442 target: &[SmolStr], 274 let mut lhs_curr = first_path(&lhs);
443 edit: &mut TextEditBuilder, 275 let mut rhs_curr = first_path(&rhs);
444) { 276 loop {
445 if let Some(anchor) = anchor { 277 match (lhs_curr.segment(), rhs_curr.segment()) {
446 let indent = leading_indent(anchor); 278 (Some(lhs), Some(rhs)) if lhs.syntax().text() == rhs.syntax().text() => (),
447 let mut buf = String::new(); 279 _ => break res,
448 if after {
449 buf.push_str("\n");
450 if let Some(spaces) = &indent {
451 buf.push_str(spaces);
452 }
453 } 280 }
454 buf.push_str("use "); 281 res = Some((lhs_curr.clone(), rhs_curr.clone()));
455 fmt_segments_raw(target, &mut buf); 282
456 buf.push_str(";"); 283 match lhs_curr.parent_path().zip(rhs_curr.parent_path()) {
457 if !after { 284 Some((lhs, rhs)) => {
458 buf.push_str("\n\n"); 285 lhs_curr = lhs;
459 if let Some(spaces) = &indent { 286 rhs_curr = rhs;
460 buf.push_str(&spaces);
461 } 287 }
288 _ => break res,
462 } 289 }
463 let position = if after { anchor.text_range().end() } else { anchor.text_range().start() };
464 edit.insert(position, buf);
465 } 290 }
466} 291}
467 292
468fn make_assist_add_in_tree_list( 293fn path_is_self(path: &ast::Path) -> bool {
469 tree_list: &ast::UseTreeList, 294 path.segment().and_then(|seg| seg.self_token()).is_some() && path.qualifier().is_none()
470 target: &[SmolStr], 295}
471 add_self: bool, 296
472 edit: &mut TextEditBuilder, 297#[inline]
473) { 298fn first_segment(path: &ast::Path) -> Option<ast::PathSegment> {
474 let last = tree_list.use_trees().last(); 299 first_path(path).segment()
475 if let Some(last) = last { 300}
476 let mut buf = String::new(); 301
477 let comma = last.syntax().siblings(Direction::Next).find(|n| n.kind() == T![,]); 302fn first_path(path: &ast::Path) -> ast::Path {
478 let offset = if let Some(comma) = comma { 303 successors(Some(path.clone()), ast::Path::qualifier).last().unwrap()
479 comma.text_range().end() 304}
480 } else { 305
481 buf.push_str(","); 306fn segment_iter(path: &ast::Path) -> impl Iterator<Item = ast::PathSegment> + Clone {
482 last.syntax().text_range().end() 307 // cant make use of SyntaxNode::siblings, because the returned Iterator is not clone
483 }; 308 successors(first_segment(path), |p| p.parent_path().parent_path().and_then(|p| p.segment()))
484 if add_self { 309}
485 buf.push_str(" self") 310
486 } else { 311/// Orders paths in the following way:
487 buf.push_str(" "); 312/// the sole self token comes first, after that come uppercase identifiers, then lowercase identifiers
313// FIXME: rustfmt sort lowercase idents before uppercase, in general we want to have the same ordering rustfmt has
314// which is `self` and `super` first, then identifier imports with lowercase ones first, then glob imports and at last list imports.
315// Example foo::{self, foo, baz, Baz, Qux, *, {Bar}}
316fn path_cmp(a: &ast::Path, b: &ast::Path) -> Ordering {
317 match (path_is_self(a), path_is_self(b)) {
318 (true, true) => Ordering::Equal,
319 (true, false) => Ordering::Less,
320 (false, true) => Ordering::Greater,
321 (false, false) => {
322 let a = segment_iter(a);
323 let b = segment_iter(b);
324 // cmp_by would be useful for us here but that is currently unstable
325 // cmp doesnt work due the lifetimes on text's return type
326 a.zip(b)
327 .flat_map(|(seg, seg2)| seg.name_ref().zip(seg2.name_ref()))
328 .find_map(|(a, b)| match a.text().cmp(b.text()) {
329 ord @ Ordering::Greater | ord @ Ordering::Less => Some(ord),
330 Ordering::Equal => None,
331 })
332 .unwrap_or(Ordering::Equal)
488 } 333 }
489 fmt_segments_raw(target, &mut buf);
490 edit.insert(offset, buf);
491 } else {
492 } 334 }
493} 335}
494 336
495fn make_assist_add_nested_import( 337fn path_cmp_opt(a: Option<ast::Path>, b: Option<ast::Path>) -> Ordering {
496 path: &ast::Path, 338 match (a, b) {
497 first_segment_to_split: &Option<ast::PathSegment>, 339 (None, None) => Ordering::Equal,
498 target: &[SmolStr], 340 (None, Some(_)) => Ordering::Less,
499 add_self: bool, 341 (Some(_), None) => Ordering::Greater,
500 edit: &mut TextEditBuilder, 342 (Some(a), Some(b)) => path_cmp(&a, &b),
501) { 343 }
502 let use_tree = path.syntax().ancestors().find_map(ast::UseTree::cast); 344}
503 if let Some(use_tree) = use_tree { 345
504 let (start, add_colon_colon) = if let Some(first_segment_to_split) = first_segment_to_split 346/// What type of merges are allowed.
505 { 347#[derive(Copy, Clone, Debug, PartialEq, Eq)]
506 (first_segment_to_split.syntax().text_range().start(), false) 348pub enum MergeBehaviour {
507 } else { 349 /// Merge everything together creating deeply nested imports.
508 (use_tree.syntax().text_range().end(), true) 350 Full,
351 /// Only merge the last import level, doesn't allow import nesting.
352 Last,
353}
354
355#[derive(Eq, PartialEq, PartialOrd, Ord)]
356enum ImportGroup {
357 // the order here defines the order of new group inserts
358 Std,
359 ExternCrate,
360 ThisCrate,
361 ThisModule,
362 SuperModule,
363}
364
365impl ImportGroup {
366 fn new(path: &ast::Path) -> ImportGroup {
367 let default = ImportGroup::ExternCrate;
368
369 let first_segment = match first_segment(path) {
370 Some(it) => it,
371 None => return default,
509 }; 372 };
510 let end = use_tree.syntax().text_range().end();
511 373
512 let mut buf = String::new(); 374 let kind = first_segment.kind().unwrap_or(PathSegmentKind::SelfKw);
513 if add_colon_colon { 375 match kind {
514 buf.push_str("::"); 376 PathSegmentKind::SelfKw => ImportGroup::ThisModule,
377 PathSegmentKind::SuperKw => ImportGroup::SuperModule,
378 PathSegmentKind::CrateKw => ImportGroup::ThisCrate,
379 PathSegmentKind::Name(name) => match name.text().as_str() {
380 "std" => ImportGroup::Std,
381 "core" => ImportGroup::Std,
382 // FIXME: can be ThisModule as well
383 _ => ImportGroup::ExternCrate,
384 },
385 PathSegmentKind::Type { .. } => unreachable!(),
515 } 386 }
516 buf.push_str("{");
517 if add_self {
518 buf.push_str("self, ");
519 }
520 fmt_segments_raw(target, &mut buf);
521 if !target.is_empty() {
522 buf.push_str(", ");
523 }
524 edit.insert(start, buf);
525 edit.insert(end, "}".to_string());
526 } 387 }
527} 388}
528 389
529/// If the node is on the beginning of the line, calculate indent. 390#[derive(PartialEq, Eq)]
530fn leading_indent(node: &SyntaxNode) -> Option<SmolStr> { 391enum AddBlankLine {
531 for token in prev_tokens(node.first_token()?) { 392 Before,
532 if let Some(ws) = ast::Whitespace::cast(token.clone()) { 393 BeforeTwice,
533 let ws_text = ws.text(); 394 Around,
534 if let Some(pos) = ws_text.rfind('\n') { 395 After,
535 return Some(ws_text[pos + 1..].into()); 396 AfterTwice,
397}
398
399fn find_insert_position(
400 scope: &ImportScope,
401 insert_path: ast::Path,
402) -> (InsertPosition<SyntaxElement>, AddBlankLine) {
403 let group = ImportGroup::new(&insert_path);
404 let path_node_iter = scope
405 .as_syntax_node()
406 .children()
407 .filter_map(|node| ast::Use::cast(node.clone()).zip(Some(node)))
408 .flat_map(|(use_, node)| use_.use_tree().and_then(|tree| tree.path()).zip(Some(node)));
409 // Iterator that discards anything thats not in the required grouping
410 // This implementation allows the user to rearrange their import groups as this only takes the first group that fits
411 let group_iter = path_node_iter
412 .clone()
413 .skip_while(|(path, _)| ImportGroup::new(path) != group)
414 .take_while(|(path, _)| ImportGroup::new(path) == group);
415
416 let segments = segment_iter(&insert_path);
417 // track the last element we iterated over, if this is still None after the iteration then that means we never iterated in the first place
418 let mut last = None;
419 // find the element that would come directly after our new import
420 let post_insert =
421 group_iter.inspect(|(_, node)| last = Some(node.clone())).find(|(path, _)| {
422 let check_segments = segment_iter(&path);
423 segments
424 .clone()
425 .zip(check_segments)
426 .flat_map(|(seg, seg2)| seg.name_ref().zip(seg2.name_ref()))
427 .all(|(l, r)| l.text() <= r.text())
428 });
429 match post_insert {
430 // insert our import before that element
431 Some((_, node)) => (InsertPosition::Before(node.into()), AddBlankLine::After),
432 // there is no element after our new import, so append it to the end of the group
433 None => match last {
434 Some(node) => (InsertPosition::After(node.into()), AddBlankLine::Before),
435 // the group we were looking for actually doesnt exist, so insert
436 None => {
437 // similar concept here to the `last` from above
438 let mut last = None;
439 // find the group that comes after where we want to insert
440 let post_group = path_node_iter
441 .inspect(|(_, node)| last = Some(node.clone()))
442 .find(|(p, _)| ImportGroup::new(p) > group);
443 match post_group {
444 Some((_, node)) => {
445 (InsertPosition::Before(node.into()), AddBlankLine::AfterTwice)
446 }
447 // there is no such group, so append after the last one
448 None => match last {
449 Some(node) => {
450 (InsertPosition::After(node.into()), AddBlankLine::BeforeTwice)
451 }
452 // there are no imports in this file at all
453 None => scope.insert_pos_after_inner_attribute(),
454 },
455 }
536 } 456 }
537 } 457 },
538 if token.text().contains('\n') {
539 break;
540 }
541 } 458 }
542 return None; 459}
543 fn prev_tokens(token: SyntaxToken) -> impl Iterator<Item = SyntaxToken> { 460
544 successors(token.prev_token(), |token| token.prev_token()) 461#[cfg(test)]
462mod tests {
463 use super::*;
464
465 use test_utils::assert_eq_text;
466
467 #[test]
468 fn insert_start() {
469 check_none(
470 "std::bar::AA",
471 r"
472use std::bar::B;
473use std::bar::D;
474use std::bar::F;
475use std::bar::G;",
476 r"
477use std::bar::AA;
478use std::bar::B;
479use std::bar::D;
480use std::bar::F;
481use std::bar::G;",
482 )
483 }
484
485 #[test]
486 fn insert_middle() {
487 check_none(
488 "std::bar::EE",
489 r"
490use std::bar::A;
491use std::bar::D;
492use std::bar::F;
493use std::bar::G;",
494 r"
495use std::bar::A;
496use std::bar::D;
497use std::bar::EE;
498use std::bar::F;
499use std::bar::G;",
500 )
501 }
502
503 #[test]
504 fn insert_end() {
505 check_none(
506 "std::bar::ZZ",
507 r"
508use std::bar::A;
509use std::bar::D;
510use std::bar::F;
511use std::bar::G;",
512 r"
513use std::bar::A;
514use std::bar::D;
515use std::bar::F;
516use std::bar::G;
517use std::bar::ZZ;",
518 )
519 }
520
521 #[test]
522 fn insert_middle_nested() {
523 check_none(
524 "std::bar::EE",
525 r"
526use std::bar::A;
527use std::bar::{D, Z}; // example of weird imports due to user
528use std::bar::F;
529use std::bar::G;",
530 r"
531use std::bar::A;
532use std::bar::EE;
533use std::bar::{D, Z}; // example of weird imports due to user
534use std::bar::F;
535use std::bar::G;",
536 )
537 }
538
539 #[test]
540 fn insert_middle_groups() {
541 check_none(
542 "foo::bar::GG",
543 r"
544use std::bar::A;
545use std::bar::D;
546
547use foo::bar::F;
548use foo::bar::H;",
549 r"
550use std::bar::A;
551use std::bar::D;
552
553use foo::bar::F;
554use foo::bar::GG;
555use foo::bar::H;",
556 )
557 }
558
559 #[test]
560 fn insert_first_matching_group() {
561 check_none(
562 "foo::bar::GG",
563 r"
564use foo::bar::A;
565use foo::bar::D;
566
567use std;
568
569use foo::bar::F;
570use foo::bar::H;",
571 r"
572use foo::bar::A;
573use foo::bar::D;
574use foo::bar::GG;
575
576use std;
577
578use foo::bar::F;
579use foo::bar::H;",
580 )
581 }
582
583 #[test]
584 fn insert_missing_group_std() {
585 check_none(
586 "std::fmt",
587 r"
588use foo::bar::A;
589use foo::bar::D;",
590 r"
591use std::fmt;
592
593use foo::bar::A;
594use foo::bar::D;",
595 )
596 }
597
598 #[test]
599 fn insert_missing_group_self() {
600 check_none(
601 "self::fmt",
602 r"
603use foo::bar::A;
604use foo::bar::D;",
605 r"
606use foo::bar::A;
607use foo::bar::D;
608
609use self::fmt;",
610 )
611 }
612
613 #[test]
614 fn insert_no_imports() {
615 check_full(
616 "foo::bar",
617 "fn main() {}",
618 r"use foo::bar;
619
620fn main() {}",
621 )
622 }
623
624 #[test]
625 fn insert_empty_file() {
626 // empty files will get two trailing newlines
627 // this is due to the test case insert_no_imports above
628 check_full(
629 "foo::bar",
630 "",
631 r"use foo::bar;
632
633",
634 )
635 }
636
637 #[test]
638 fn insert_after_inner_attr() {
639 check_full(
640 "foo::bar",
641 r"#![allow(unused_imports)]",
642 r"#![allow(unused_imports)]
643
644use foo::bar;",
645 )
646 }
647
648 #[test]
649 fn insert_after_inner_attr2() {
650 check_full(
651 "foo::bar",
652 r"#![allow(unused_imports)]
653
654fn main() {}",
655 r"#![allow(unused_imports)]
656
657use foo::bar;
658
659fn main() {}",
660 )
661 }
662
663 #[test]
664 fn merge_groups() {
665 check_last("std::io", r"use std::fmt;", r"use std::{fmt, io};")
666 }
667
668 #[test]
669 fn merge_groups_last() {
670 check_last(
671 "std::io",
672 r"use std::fmt::{Result, Display};",
673 r"use std::fmt::{Result, Display};
674use std::io;",
675 )
676 }
677
678 #[test]
679 fn merge_groups_full() {
680 check_full(
681 "std::io",
682 r"use std::fmt::{Result, Display};",
683 r"use std::{fmt::{Result, Display}, io};",
684 )
685 }
686
687 #[test]
688 fn merge_groups_long_full() {
689 check_full(
690 "std::foo::bar::Baz",
691 r"use std::foo::bar::Qux;",
692 r"use std::foo::bar::{Baz, Qux};",
693 )
694 }
695
696 #[test]
697 fn merge_groups_long_last() {
698 check_last(
699 "std::foo::bar::Baz",
700 r"use std::foo::bar::Qux;",
701 r"use std::foo::bar::{Baz, Qux};",
702 )
703 }
704
705 #[test]
706 fn merge_groups_long_full_list() {
707 check_full(
708 "std::foo::bar::Baz",
709 r"use std::foo::bar::{Qux, Quux};",
710 r"use std::foo::bar::{Baz, Quux, Qux};",
711 )
712 }
713
714 #[test]
715 fn merge_groups_long_last_list() {
716 check_last(
717 "std::foo::bar::Baz",
718 r"use std::foo::bar::{Qux, Quux};",
719 r"use std::foo::bar::{Baz, Quux, Qux};",
720 )
721 }
722
723 #[test]
724 fn merge_groups_long_full_nested() {
725 check_full(
726 "std::foo::bar::Baz",
727 r"use std::foo::bar::{Qux, quux::{Fez, Fizz}};",
728 r"use std::foo::bar::{Baz, Qux, quux::{Fez, Fizz}};",
729 )
730 }
731
732 #[test]
733 fn merge_groups_long_last_nested() {
734 check_last(
735 "std::foo::bar::Baz",
736 r"use std::foo::bar::{Qux, quux::{Fez, Fizz}};",
737 r"use std::foo::bar::Baz;
738use std::foo::bar::{Qux, quux::{Fez, Fizz}};",
739 )
740 }
741
742 #[test]
743 fn merge_groups_full_nested_deep() {
744 check_full(
745 "std::foo::bar::quux::Baz",
746 r"use std::foo::bar::{Qux, quux::{Fez, Fizz}};",
747 r"use std::foo::bar::{Qux, quux::{Baz, Fez, Fizz}};",
748 )
749 }
750
751 #[test]
752 fn merge_groups_skip_pub() {
753 check_full(
754 "std::io",
755 r"pub use std::fmt::{Result, Display};",
756 r"pub use std::fmt::{Result, Display};
757use std::io;",
758 )
759 }
760
761 #[test]
762 fn merge_groups_skip_pub_crate() {
763 check_full(
764 "std::io",
765 r"pub(crate) use std::fmt::{Result, Display};",
766 r"pub(crate) use std::fmt::{Result, Display};
767use std::io;",
768 )
769 }
770
771 #[test]
772 #[ignore] // FIXME: Support this
773 fn split_out_merge() {
774 check_last(
775 "std::fmt::Result",
776 r"use std::{fmt, io};",
777 r"use std::fmt::{self, Result};
778use std::io;",
779 )
780 }
781
782 #[test]
783 fn merge_into_module_import() {
784 check_full(
785 "std::fmt::Result",
786 r"use std::{fmt, io};",
787 r"use std::{fmt::{self, Result}, io};",
788 )
789 }
790
791 #[test]
792 fn merge_groups_self() {
793 check_full("std::fmt::Debug", r"use std::fmt;", r"use std::fmt::{self, Debug};")
794 }
795
796 #[test]
797 fn merge_mod_into_glob() {
798 check_full(
799 "token::TokenKind",
800 r"use token::TokenKind::*;",
801 r"use token::TokenKind::{*, self};",
802 )
803 // FIXME: have it emit `use token::TokenKind::{self, *}`?
804 }
805
806 #[test]
807 fn merge_self_glob() {
808 check_full("self", r"use self::*;", r"use self::{*, self};")
809 // FIXME: have it emit `use {self, *}`?
810 }
811
812 #[test]
813 #[ignore] // FIXME: Support this
814 fn merge_partial_path() {
815 check_full(
816 "ast::Foo",
817 r"use syntax::{ast, algo};",
818 r"use syntax::{ast::{self, Foo}, algo};",
819 )
820 }
821
822 #[test]
823 fn merge_glob_nested() {
824 check_full(
825 "foo::bar::quux::Fez",
826 r"use foo::bar::{Baz, quux::*};",
827 r"use foo::bar::{Baz, quux::{self::*, Fez}};",
828 )
829 }
830
831 #[test]
832 fn merge_last_too_long() {
833 check_last("foo::bar", r"use foo::bar::baz::Qux;", r"use foo::bar::{self, baz::Qux};");
834 }
835
836 #[test]
837 fn insert_short_before_long() {
838 check_none(
839 "foo::bar",
840 r"use foo::bar::baz::Qux;",
841 r"use foo::bar;
842use foo::bar::baz::Qux;",
843 );
844 }
845
846 #[test]
847 fn merge_last_fail() {
848 check_merge_only_fail(
849 r"use foo::bar::{baz::{Qux, Fez}};",
850 r"use foo::bar::{baaz::{Quux, Feez}};",
851 MergeBehaviour::Last,
852 );
853 }
854
855 #[test]
856 fn merge_last_fail1() {
857 check_merge_only_fail(
858 r"use foo::bar::{baz::{Qux, Fez}};",
859 r"use foo::bar::baaz::{Quux, Feez};",
860 MergeBehaviour::Last,
861 );
862 }
863
864 #[test]
865 fn merge_last_fail2() {
866 check_merge_only_fail(
867 r"use foo::bar::baz::{Qux, Fez};",
868 r"use foo::bar::{baaz::{Quux, Feez}};",
869 MergeBehaviour::Last,
870 );
871 }
872
873 #[test]
874 fn merge_last_fail3() {
875 check_merge_only_fail(
876 r"use foo::bar::baz::{Qux, Fez};",
877 r"use foo::bar::baaz::{Quux, Feez};",
878 MergeBehaviour::Last,
879 );
880 }
881
882 fn check(
883 path: &str,
884 ra_fixture_before: &str,
885 ra_fixture_after: &str,
886 mb: Option<MergeBehaviour>,
887 ) {
888 let file = super::ImportScope::from(
889 ast::SourceFile::parse(ra_fixture_before).tree().syntax().clone(),
890 )
891 .unwrap();
892 let path = ast::SourceFile::parse(&format!("use {};", path))
893 .tree()
894 .syntax()
895 .descendants()
896 .find_map(ast::Path::cast)
897 .unwrap();
898
899 let result = insert_use(&file, path, mb).to_string();
900 assert_eq_text!(&result, ra_fixture_after);
901 }
902
903 fn check_full(path: &str, ra_fixture_before: &str, ra_fixture_after: &str) {
904 check(path, ra_fixture_before, ra_fixture_after, Some(MergeBehaviour::Full))
905 }
906
907 fn check_last(path: &str, ra_fixture_before: &str, ra_fixture_after: &str) {
908 check(path, ra_fixture_before, ra_fixture_after, Some(MergeBehaviour::Last))
909 }
910
911 fn check_none(path: &str, ra_fixture_before: &str, ra_fixture_after: &str) {
912 check(path, ra_fixture_before, ra_fixture_after, None)
913 }
914
915 fn check_merge_only_fail(ra_fixture0: &str, ra_fixture1: &str, mb: MergeBehaviour) {
916 let use0 = ast::SourceFile::parse(ra_fixture0)
917 .tree()
918 .syntax()
919 .descendants()
920 .find_map(ast::Use::cast)
921 .unwrap();
922
923 let use1 = ast::SourceFile::parse(ra_fixture1)
924 .tree()
925 .syntax()
926 .descendants()
927 .find_map(ast::Use::cast)
928 .unwrap();
929
930 let result = try_merge_imports(&use0, &use1, mb);
931 assert_eq!(result.map(|u| u.to_string()), None);
545 } 932 }
546} 933}
diff --git a/crates/base_db/src/input.rs b/crates/base_db/src/input.rs
index f3d65cdf0..9a61f1d56 100644
--- a/crates/base_db/src/input.rs
+++ b/crates/base_db/src/input.rs
@@ -12,7 +12,7 @@ use cfg::CfgOptions;
12use rustc_hash::{FxHashMap, FxHashSet}; 12use rustc_hash::{FxHashMap, FxHashSet};
13use syntax::SmolStr; 13use syntax::SmolStr;
14use tt::TokenExpander; 14use tt::TokenExpander;
15use vfs::file_set::FileSet; 15use vfs::{file_set::FileSet, VfsPath};
16 16
17pub use vfs::FileId; 17pub use vfs::FileId;
18 18
@@ -43,6 +43,12 @@ impl SourceRoot {
43 pub fn new_library(file_set: FileSet) -> SourceRoot { 43 pub fn new_library(file_set: FileSet) -> SourceRoot {
44 SourceRoot { is_library: true, file_set } 44 SourceRoot { is_library: true, file_set }
45 } 45 }
46 pub fn path_for_file(&self, file: &FileId) -> Option<&VfsPath> {
47 self.file_set.path_for_file(file)
48 }
49 pub fn file_for_path(&self, path: &VfsPath) -> Option<&FileId> {
50 self.file_set.file_for_path(path)
51 }
46 pub fn iter(&self) -> impl Iterator<Item = FileId> + '_ { 52 pub fn iter(&self) -> impl Iterator<Item = FileId> + '_ {
47 self.file_set.iter() 53 self.file_set.iter()
48 } 54 }
diff --git a/crates/hir/src/code_model.rs b/crates/hir/src/code_model.rs
index dc3a1699f..a2a166e0a 100644
--- a/crates/hir/src/code_model.rs
+++ b/crates/hir/src/code_model.rs
@@ -926,12 +926,12 @@ impl MacroDef {
926 926
927 /// Indicate it is a proc-macro 927 /// Indicate it is a proc-macro
928 pub fn is_proc_macro(&self) -> bool { 928 pub fn is_proc_macro(&self) -> bool {
929 matches!(self.id.kind, MacroDefKind::CustomDerive(_)) 929 matches!(self.id.kind, MacroDefKind::ProcMacro(_))
930 } 930 }
931 931
932 /// Indicate it is a derive macro 932 /// Indicate it is a derive macro
933 pub fn is_derive_macro(&self) -> bool { 933 pub fn is_derive_macro(&self) -> bool {
934 matches!(self.id.kind, MacroDefKind::CustomDerive(_) | MacroDefKind::BuiltInDerive(_)) 934 matches!(self.id.kind, MacroDefKind::ProcMacro(_) | MacroDefKind::BuiltInDerive(_))
935 } 935 }
936} 936}
937 937
@@ -1309,6 +1309,8 @@ impl Type {
1309 /// Checks that particular type `ty` implements `std::future::Future`. 1309 /// Checks that particular type `ty` implements `std::future::Future`.
1310 /// This function is used in `.await` syntax completion. 1310 /// This function is used in `.await` syntax completion.
1311 pub fn impls_future(&self, db: &dyn HirDatabase) -> bool { 1311 pub fn impls_future(&self, db: &dyn HirDatabase) -> bool {
1312 // No special case for the type of async block, since Chalk can figure it out.
1313
1312 let krate = self.krate; 1314 let krate = self.krate;
1313 1315
1314 let std_future_trait = 1316 let std_future_trait =
@@ -1626,6 +1628,11 @@ impl Type {
1626 cb(type_.derived(ty.clone())); 1628 cb(type_.derived(ty.clone()));
1627 } 1629 }
1628 } 1630 }
1631 TypeCtor::OpaqueType(..) => {
1632 if let Some(bounds) = ty.impl_trait_bounds(db) {
1633 walk_bounds(db, &type_.derived(ty.clone()), &bounds, cb);
1634 }
1635 }
1629 _ => (), 1636 _ => (),
1630 } 1637 }
1631 1638
diff --git a/crates/hir/src/semantics.rs b/crates/hir/src/semantics.rs
index 1594d4f0f..c61a430e1 100644
--- a/crates/hir/src/semantics.rs
+++ b/crates/hir/src/semantics.rs
@@ -207,8 +207,8 @@ impl<'db, DB: HirDatabase> Semantics<'db, DB> {
207 self.imp.resolve_record_field(field) 207 self.imp.resolve_record_field(field)
208 } 208 }
209 209
210 pub fn resolve_record_field_pat(&self, field: &ast::RecordPatField) -> Option<Field> { 210 pub fn resolve_record_pat_field(&self, field: &ast::RecordPatField) -> Option<Field> {
211 self.imp.resolve_record_field_pat(field) 211 self.imp.resolve_record_pat_field(field)
212 } 212 }
213 213
214 pub fn resolve_macro_call(&self, macro_call: &ast::MacroCall) -> Option<MacroDef> { 214 pub fn resolve_macro_call(&self, macro_call: &ast::MacroCall) -> Option<MacroDef> {
@@ -433,8 +433,8 @@ impl<'db> SemanticsImpl<'db> {
433 self.analyze(field.syntax()).resolve_record_field(self.db, field) 433 self.analyze(field.syntax()).resolve_record_field(self.db, field)
434 } 434 }
435 435
436 fn resolve_record_field_pat(&self, field: &ast::RecordPatField) -> Option<Field> { 436 fn resolve_record_pat_field(&self, field: &ast::RecordPatField) -> Option<Field> {
437 self.analyze(field.syntax()).resolve_record_field_pat(self.db, field) 437 self.analyze(field.syntax()).resolve_record_pat_field(self.db, field)
438 } 438 }
439 439
440 fn resolve_macro_call(&self, macro_call: &ast::MacroCall) -> Option<MacroDef> { 440 fn resolve_macro_call(&self, macro_call: &ast::MacroCall) -> Option<MacroDef> {
@@ -697,6 +697,25 @@ fn find_root(node: &SyntaxNode) -> SyntaxNode {
697 node.ancestors().last().unwrap() 697 node.ancestors().last().unwrap()
698} 698}
699 699
700/// `SemanticScope` encapsulates the notion of a scope (the set of visible
701/// names) at a particular program point.
702///
703/// It is a bit tricky, as scopes do not really exist inside the compiler.
704/// Rather, the compiler directly computes for each reference the definition it
705/// refers to. It might transiently compute the explicit scope map while doing
706/// so, but, generally, this is not something left after the analysis.
707///
708/// However, we do very much need explicit scopes for IDE purposes --
709/// completion, at its core, lists the contents of the current scope. The notion
710/// of scope is also useful to answer questions like "what would be the meaning
711/// of this piece of code if we inserted it into this position?".
712///
713/// So `SemanticsScope` is constructed from a specific program point (a syntax
714/// node or just a raw offset) and provides access to the set of visible names
715/// on a somewhat best-effort basis.
716///
717/// Note that if you are wondering "what does this specific existing name mean?",
718/// you'd better use the `resolve_` family of methods.
700#[derive(Debug)] 719#[derive(Debug)]
701pub struct SemanticsScope<'a> { 720pub struct SemanticsScope<'a> {
702 pub db: &'a dyn HirDatabase, 721 pub db: &'a dyn HirDatabase,
diff --git a/crates/hir/src/source_analyzer.rs b/crates/hir/src/source_analyzer.rs
index 1d13c4f1d..1aef0f33f 100644
--- a/crates/hir/src/source_analyzer.rs
+++ b/crates/hir/src/source_analyzer.rs
@@ -179,13 +179,13 @@ impl SourceAnalyzer {
179 Some((struct_field.into(), local)) 179 Some((struct_field.into(), local))
180 } 180 }
181 181
182 pub(crate) fn resolve_record_field_pat( 182 pub(crate) fn resolve_record_pat_field(
183 &self, 183 &self,
184 _db: &dyn HirDatabase, 184 _db: &dyn HirDatabase,
185 field: &ast::RecordPatField, 185 field: &ast::RecordPatField,
186 ) -> Option<Field> { 186 ) -> Option<Field> {
187 let pat_id = self.pat_id(&field.pat()?)?; 187 let pat_id = self.pat_id(&field.pat()?)?;
188 let struct_field = self.infer.as_ref()?.record_field_pat_resolution(pat_id)?; 188 let struct_field = self.infer.as_ref()?.record_pat_field_resolution(pat_id)?;
189 Some(struct_field.into()) 189 Some(struct_field.into())
190 } 190 }
191 191
diff --git a/crates/hir_def/src/body/lower.rs b/crates/hir_def/src/body/lower.rs
index 30ac12a12..2d91bb21f 100644
--- a/crates/hir_def/src/body/lower.rs
+++ b/crates/hir_def/src/body/lower.rs
@@ -239,7 +239,10 @@ impl ExprCollector<'_> {
239 None => self.missing_expr(), 239 None => self.missing_expr(),
240 }, 240 },
241 // FIXME: we need to record these effects somewhere... 241 // FIXME: we need to record these effects somewhere...
242 ast::Effect::Async(_) => self.collect_block_opt(e.block_expr()), 242 ast::Effect::Async(_) => {
243 let body = self.collect_block_opt(e.block_expr());
244 self.alloc_expr(Expr::Async { body }, syntax_ptr)
245 }
243 }, 246 },
244 ast::Expr::BlockExpr(e) => self.collect_block(e), 247 ast::Expr::BlockExpr(e) => self.collect_block(e),
245 ast::Expr::LoopExpr(e) => { 248 ast::Expr::LoopExpr(e) => {
@@ -835,8 +838,12 @@ impl ExprCollector<'_> {
835 838
836 Pat::Missing 839 Pat::Missing
837 } 840 }
841 ast::Pat::BoxPat(boxpat) => {
842 let inner = self.collect_pat_opt(boxpat.pat());
843 Pat::Box { inner }
844 }
838 // FIXME: implement 845 // FIXME: implement
839 ast::Pat::BoxPat(_) | ast::Pat::RangePat(_) | ast::Pat::MacroPat(_) => Pat::Missing, 846 ast::Pat::RangePat(_) | ast::Pat::MacroPat(_) => Pat::Missing,
840 }; 847 };
841 let ptr = AstPtr::new(&pat); 848 let ptr = AstPtr::new(&pat);
842 self.alloc_pat(pattern, Either::Left(ptr)) 849 self.alloc_pat(pattern, Either::Left(ptr))
diff --git a/crates/hir_def/src/data.rs b/crates/hir_def/src/data.rs
index 9a8eb4ede..6190906da 100644
--- a/crates/hir_def/src/data.rs
+++ b/crates/hir_def/src/data.rs
@@ -54,6 +54,7 @@ pub struct TypeAliasData {
54 pub name: Name, 54 pub name: Name,
55 pub type_ref: Option<TypeRef>, 55 pub type_ref: Option<TypeRef>,
56 pub visibility: RawVisibility, 56 pub visibility: RawVisibility,
57 pub is_extern: bool,
57 /// Bounds restricting the type alias itself (eg. `type Ty: Bound;` in a trait or impl). 58 /// Bounds restricting the type alias itself (eg. `type Ty: Bound;` in a trait or impl).
58 pub bounds: Vec<TypeBound>, 59 pub bounds: Vec<TypeBound>,
59} 60}
@@ -71,6 +72,7 @@ impl TypeAliasData {
71 name: typ.name.clone(), 72 name: typ.name.clone(),
72 type_ref: typ.type_ref.clone(), 73 type_ref: typ.type_ref.clone(),
73 visibility: item_tree[typ.visibility].clone(), 74 visibility: item_tree[typ.visibility].clone(),
75 is_extern: typ.is_extern,
74 bounds: typ.bounds.to_vec(), 76 bounds: typ.bounds.to_vec(),
75 }) 77 })
76 } 78 }
diff --git a/crates/hir_def/src/diagnostics.rs b/crates/hir_def/src/diagnostics.rs
index 3e19d9117..2ec0fd3fb 100644
--- a/crates/hir_def/src/diagnostics.rs
+++ b/crates/hir_def/src/diagnostics.rs
@@ -28,3 +28,45 @@ impl Diagnostic for UnresolvedModule {
28 self 28 self
29 } 29 }
30} 30}
31
32#[derive(Debug)]
33pub struct UnresolvedExternCrate {
34 pub file: HirFileId,
35 pub item: AstPtr<ast::ExternCrate>,
36}
37
38impl Diagnostic for UnresolvedExternCrate {
39 fn code(&self) -> DiagnosticCode {
40 DiagnosticCode("unresolved-extern-crate")
41 }
42 fn message(&self) -> String {
43 "unresolved extern crate".to_string()
44 }
45 fn display_source(&self) -> InFile<SyntaxNodePtr> {
46 InFile::new(self.file, self.item.clone().into())
47 }
48 fn as_any(&self) -> &(dyn Any + Send + 'static) {
49 self
50 }
51}
52
53#[derive(Debug)]
54pub struct UnresolvedImport {
55 pub file: HirFileId,
56 pub node: AstPtr<ast::UseTree>,
57}
58
59impl Diagnostic for UnresolvedImport {
60 fn code(&self) -> DiagnosticCode {
61 DiagnosticCode("unresolved-import")
62 }
63 fn message(&self) -> String {
64 "unresolved import".to_string()
65 }
66 fn display_source(&self) -> InFile<SyntaxNodePtr> {
67 InFile::new(self.file, self.node.clone().into())
68 }
69 fn as_any(&self) -> &(dyn Any + Send + 'static) {
70 self
71 }
72}
diff --git a/crates/hir_def/src/expr.rs b/crates/hir_def/src/expr.rs
index c94b3a36f..e5d740a36 100644
--- a/crates/hir_def/src/expr.rs
+++ b/crates/hir_def/src/expr.rs
@@ -111,6 +111,9 @@ pub enum Expr {
111 TryBlock { 111 TryBlock {
112 body: ExprId, 112 body: ExprId,
113 }, 113 },
114 Async {
115 body: ExprId,
116 },
114 Cast { 117 Cast {
115 expr: ExprId, 118 expr: ExprId,
116 type_ref: TypeRef, 119 type_ref: TypeRef,
@@ -250,7 +253,7 @@ impl Expr {
250 f(*expr); 253 f(*expr);
251 } 254 }
252 } 255 }
253 Expr::TryBlock { body } | Expr::Unsafe { body } => f(*body), 256 Expr::TryBlock { body } | Expr::Unsafe { body } | Expr::Async { body } => f(*body),
254 Expr::Loop { body, .. } => f(*body), 257 Expr::Loop { body, .. } => f(*body),
255 Expr::While { condition, body, .. } => { 258 Expr::While { condition, body, .. } => {
256 f(*condition); 259 f(*condition);
@@ -395,6 +398,7 @@ pub enum Pat {
395 Bind { mode: BindingAnnotation, name: Name, subpat: Option<PatId> }, 398 Bind { mode: BindingAnnotation, name: Name, subpat: Option<PatId> },
396 TupleStruct { path: Option<Path>, args: Vec<PatId>, ellipsis: Option<usize> }, 399 TupleStruct { path: Option<Path>, args: Vec<PatId>, ellipsis: Option<usize> },
397 Ref { pat: PatId, mutability: Mutability }, 400 Ref { pat: PatId, mutability: Mutability },
401 Box { inner: PatId },
398} 402}
399 403
400impl Pat { 404impl Pat {
@@ -415,6 +419,7 @@ impl Pat {
415 Pat::Record { args, .. } => { 419 Pat::Record { args, .. } => {
416 args.iter().map(|f| f.pat).for_each(f); 420 args.iter().map(|f| f.pat).for_each(f);
417 } 421 }
422 Pat::Box { inner } => f(*inner),
418 } 423 }
419 } 424 }
420} 425}
diff --git a/crates/hir_def/src/item_tree.rs b/crates/hir_def/src/item_tree.rs
index e14722cae..0fd91b9d0 100644
--- a/crates/hir_def/src/item_tree.rs
+++ b/crates/hir_def/src/item_tree.rs
@@ -291,7 +291,6 @@ pub enum AttrOwner {
291 291
292 Variant(Idx<Variant>), 292 Variant(Idx<Variant>),
293 Field(Idx<Field>), 293 Field(Idx<Field>),
294 // FIXME: Store variant and field attrs, and stop reparsing them in `attrs_query`.
295} 294}
296 295
297macro_rules! from_attrs { 296macro_rules! from_attrs {
@@ -483,11 +482,16 @@ pub struct Import {
483 /// AST ID of the `use` or `extern crate` item this import was derived from. Note that many 482 /// AST ID of the `use` or `extern crate` item this import was derived from. Note that many
484 /// `Import`s can map to the same `use` item. 483 /// `Import`s can map to the same `use` item.
485 pub ast_id: FileAstId<ast::Use>, 484 pub ast_id: FileAstId<ast::Use>,
485 /// Index of this `Import` when the containing `Use` is visited via `ModPath::expand_use_item`.
486 ///
487 /// This can be used to get the `UseTree` this `Import` corresponds to and allows emitting
488 /// precise diagnostics.
489 pub index: usize,
486} 490}
487 491
488#[derive(Debug, Clone, Eq, PartialEq)] 492#[derive(Debug, Clone, Eq, PartialEq)]
489pub struct ExternCrate { 493pub struct ExternCrate {
490 pub path: ModPath, 494 pub name: Name,
491 pub alias: Option<ImportAlias>, 495 pub alias: Option<ImportAlias>,
492 pub visibility: RawVisibilityId, 496 pub visibility: RawVisibilityId,
493 /// Whether this is a `#[macro_use] extern crate ...`. 497 /// Whether this is a `#[macro_use] extern crate ...`.
@@ -592,6 +596,7 @@ pub struct TypeAlias {
592 pub bounds: Box<[TypeBound]>, 596 pub bounds: Box<[TypeBound]>,
593 pub generic_params: GenericParamsId, 597 pub generic_params: GenericParamsId,
594 pub type_ref: Option<TypeRef>, 598 pub type_ref: Option<TypeRef>,
599 pub is_extern: bool,
595 pub ast_id: FileAstId<ast::TypeAlias>, 600 pub ast_id: FileAstId<ast::TypeAlias>,
596} 601}
597 602
diff --git a/crates/hir_def/src/item_tree/lower.rs b/crates/hir_def/src/item_tree/lower.rs
index 6a503d785..54814f141 100644
--- a/crates/hir_def/src/item_tree/lower.rs
+++ b/crates/hir_def/src/item_tree/lower.rs
@@ -364,6 +364,7 @@ impl Ctx {
364 generic_params, 364 generic_params,
365 type_ref, 365 type_ref,
366 ast_id, 366 ast_id,
367 is_extern: false,
367 }; 368 };
368 Some(id(self.data().type_aliases.alloc(res))) 369 Some(id(self.data().type_aliases.alloc(res)))
369 } 370 }
@@ -482,7 +483,7 @@ impl Ctx {
482 ModPath::expand_use_item( 483 ModPath::expand_use_item(
483 InFile::new(self.file, use_item.clone()), 484 InFile::new(self.file, use_item.clone()),
484 &self.hygiene, 485 &self.hygiene,
485 |path, _tree, is_glob, alias| { 486 |path, _use_tree, is_glob, alias| {
486 imports.push(id(tree.imports.alloc(Import { 487 imports.push(id(tree.imports.alloc(Import {
487 path, 488 path,
488 alias, 489 alias,
@@ -490,6 +491,7 @@ impl Ctx {
490 is_glob, 491 is_glob,
491 is_prelude, 492 is_prelude,
492 ast_id, 493 ast_id,
494 index: imports.len(),
493 }))); 495 })));
494 }, 496 },
495 ); 497 );
@@ -501,7 +503,7 @@ impl Ctx {
501 &mut self, 503 &mut self,
502 extern_crate: &ast::ExternCrate, 504 extern_crate: &ast::ExternCrate,
503 ) -> Option<FileItemTreeId<ExternCrate>> { 505 ) -> Option<FileItemTreeId<ExternCrate>> {
504 let path = ModPath::from_name_ref(&extern_crate.name_ref()?); 506 let name = extern_crate.name_ref()?.as_name();
505 let alias = extern_crate.rename().map(|a| { 507 let alias = extern_crate.rename().map(|a| {
506 a.name().map(|it| it.as_name()).map_or(ImportAlias::Underscore, ImportAlias::Alias) 508 a.name().map(|it| it.as_name()).map_or(ImportAlias::Underscore, ImportAlias::Alias)
507 }); 509 });
@@ -510,7 +512,7 @@ impl Ctx {
510 // FIXME: cfg_attr 512 // FIXME: cfg_attr
511 let is_macro_use = extern_crate.has_atom_attr("macro_use"); 513 let is_macro_use = extern_crate.has_atom_attr("macro_use");
512 514
513 let res = ExternCrate { path, alias, visibility, is_macro_use, ast_id }; 515 let res = ExternCrate { name, alias, visibility, is_macro_use, ast_id };
514 Some(id(self.data().extern_crates.alloc(res))) 516 Some(id(self.data().extern_crates.alloc(res)))
515 } 517 }
516 518
@@ -558,8 +560,9 @@ impl Ctx {
558 statik.into() 560 statik.into()
559 } 561 }
560 ast::ExternItem::TypeAlias(ty) => { 562 ast::ExternItem::TypeAlias(ty) => {
561 let id = self.lower_type_alias(&ty)?; 563 let foreign_ty = self.lower_type_alias(&ty)?;
562 id.into() 564 self.data().type_aliases[foreign_ty.index].is_extern = true;
565 foreign_ty.into()
563 } 566 }
564 ast::ExternItem::MacroCall(_) => return None, 567 ast::ExternItem::MacroCall(_) => return None,
565 }; 568 };
diff --git a/crates/hir_def/src/item_tree/tests.rs b/crates/hir_def/src/item_tree/tests.rs
index 620e697d4..1a806cda5 100644
--- a/crates/hir_def/src/item_tree/tests.rs
+++ b/crates/hir_def/src/item_tree/tests.rs
@@ -228,15 +228,15 @@ fn smoke() {
228 228
229 top-level items: 229 top-level items:
230 #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("attr_on_use"))] }, input: None }]) }] 230 #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("attr_on_use"))] }, input: None }]) }]
231 Import { path: ModPath { kind: Plain, segments: [Name(Text("a"))] }, alias: None, visibility: RawVisibilityId("pub(self)"), is_glob: false, is_prelude: false, ast_id: FileAstId::<syntax::ast::generated::nodes::Use>(0) } 231 Import { path: ModPath { kind: Plain, segments: [Name(Text("a"))] }, alias: None, visibility: RawVisibilityId("pub(self)"), is_glob: false, is_prelude: false, ast_id: FileAstId::<syntax::ast::generated::nodes::Use>(0), index: 0 }
232 #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("attr_on_use"))] }, input: None }]) }] 232 #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("attr_on_use"))] }, input: None }]) }]
233 Import { path: ModPath { kind: Plain, segments: [Name(Text("b"))] }, alias: None, visibility: RawVisibilityId("pub(self)"), is_glob: true, is_prelude: false, ast_id: FileAstId::<syntax::ast::generated::nodes::Use>(0) } 233 Import { path: ModPath { kind: Plain, segments: [Name(Text("b"))] }, alias: None, visibility: RawVisibilityId("pub(self)"), is_glob: true, is_prelude: false, ast_id: FileAstId::<syntax::ast::generated::nodes::Use>(0), index: 1 }
234 #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("ext_crate"))] }, input: None }]) }] 234 #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("ext_crate"))] }, input: None }]) }]
235 ExternCrate { path: ModPath { kind: Plain, segments: [Name(Text("krate"))] }, alias: None, visibility: RawVisibilityId("pub(self)"), is_macro_use: false, ast_id: FileAstId::<syntax::ast::generated::nodes::ExternCrate>(1) } 235 ExternCrate { name: Name(Text("krate")), alias: None, visibility: RawVisibilityId("pub(self)"), is_macro_use: false, ast_id: FileAstId::<syntax::ast::generated::nodes::ExternCrate>(1) }
236 #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("on_trait"))] }, input: None }]) }] 236 #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("on_trait"))] }, input: None }]) }]
237 Trait { name: Name(Text("Tr")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(0), auto: false, items: [TypeAlias(Idx::<TypeAlias>(0)), Const(Idx::<Const>(0)), Function(Idx::<Function>(0)), Function(Idx::<Function>(1))], ast_id: FileAstId::<syntax::ast::generated::nodes::Trait>(2) } 237 Trait { name: Name(Text("Tr")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(0), auto: false, items: [TypeAlias(Idx::<TypeAlias>(0)), Const(Idx::<Const>(0)), Function(Idx::<Function>(0)), Function(Idx::<Function>(1))], ast_id: FileAstId::<syntax::ast::generated::nodes::Trait>(2) }
238 > #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("assoc_ty"))] }, input: None }]) }] 238 > #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("assoc_ty"))] }, input: None }]) }]
239 > TypeAlias { name: Name(Text("AssocTy")), visibility: RawVisibilityId("pub(self)"), bounds: [Path(Path { type_anchor: None, mod_path: ModPath { kind: Plain, segments: [Name(Text("Tr"))] }, generic_args: [Some(GenericArgs { args: [Type(Tuple([]))], has_self_type: false, bindings: [] })] })], generic_params: GenericParamsId(4294967295), type_ref: None, ast_id: FileAstId::<syntax::ast::generated::nodes::TypeAlias>(8) } 239 > TypeAlias { name: Name(Text("AssocTy")), visibility: RawVisibilityId("pub(self)"), bounds: [Path(Path { type_anchor: None, mod_path: ModPath { kind: Plain, segments: [Name(Text("Tr"))] }, generic_args: [Some(GenericArgs { args: [Type(Tuple([]))], has_self_type: false, bindings: [] })] })], generic_params: GenericParamsId(4294967295), type_ref: None, is_extern: false, ast_id: FileAstId::<syntax::ast::generated::nodes::TypeAlias>(8) }
240 > #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("assoc_const"))] }, input: None }]) }] 240 > #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("assoc_const"))] }, input: None }]) }]
241 > Const { name: Some(Name(Text("CONST"))), visibility: RawVisibilityId("pub(self)"), type_ref: Path(Path { type_anchor: None, mod_path: ModPath { kind: Plain, segments: [Name(Text("u8"))] }, generic_args: [None] }), ast_id: FileAstId::<syntax::ast::generated::nodes::Const>(9) } 241 > Const { name: Some(Name(Text("CONST"))), visibility: RawVisibilityId("pub(self)"), type_ref: Path(Path { type_anchor: None, mod_path: ModPath { kind: Plain, segments: [Name(Text("u8"))] }, generic_args: [None] }), ast_id: FileAstId::<syntax::ast::generated::nodes::Const>(9) }
242 > #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("assoc_method"))] }, input: None }]) }] 242 > #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("assoc_method"))] }, input: None }]) }]
diff --git a/crates/hir_def/src/nameres.rs b/crates/hir_def/src/nameres.rs
index bf302172d..5e4d73c1f 100644
--- a/crates/hir_def/src/nameres.rs
+++ b/crates/hir_def/src/nameres.rs
@@ -288,31 +288,70 @@ pub enum ModuleSource {
288 288
289mod diagnostics { 289mod diagnostics {
290 use hir_expand::diagnostics::DiagnosticSink; 290 use hir_expand::diagnostics::DiagnosticSink;
291 use hir_expand::hygiene::Hygiene;
292 use hir_expand::InFile;
291 use syntax::{ast, AstPtr}; 293 use syntax::{ast, AstPtr};
292 294
293 use crate::{db::DefDatabase, diagnostics::UnresolvedModule, nameres::LocalModuleId, AstId}; 295 use crate::path::ModPath;
296 use crate::{db::DefDatabase, diagnostics::*, nameres::LocalModuleId, AstId};
294 297
295 #[derive(Debug, PartialEq, Eq)] 298 #[derive(Debug, PartialEq, Eq)]
296 pub(super) enum DefDiagnostic { 299 enum DiagnosticKind {
297 UnresolvedModule { 300 UnresolvedModule { declaration: AstId<ast::Module>, candidate: String },
298 module: LocalModuleId, 301
299 declaration: AstId<ast::Module>, 302 UnresolvedExternCrate { ast: AstId<ast::ExternCrate> },
300 candidate: String, 303
301 }, 304 UnresolvedImport { ast: AstId<ast::Use>, index: usize },
305 }
306
307 #[derive(Debug, PartialEq, Eq)]
308 pub(super) struct DefDiagnostic {
309 in_module: LocalModuleId,
310 kind: DiagnosticKind,
302 } 311 }
303 312
304 impl DefDiagnostic { 313 impl DefDiagnostic {
314 pub(super) fn unresolved_module(
315 container: LocalModuleId,
316 declaration: AstId<ast::Module>,
317 candidate: String,
318 ) -> Self {
319 Self {
320 in_module: container,
321 kind: DiagnosticKind::UnresolvedModule { declaration, candidate },
322 }
323 }
324
325 pub(super) fn unresolved_extern_crate(
326 container: LocalModuleId,
327 declaration: AstId<ast::ExternCrate>,
328 ) -> Self {
329 Self {
330 in_module: container,
331 kind: DiagnosticKind::UnresolvedExternCrate { ast: declaration },
332 }
333 }
334
335 pub(super) fn unresolved_import(
336 container: LocalModuleId,
337 ast: AstId<ast::Use>,
338 index: usize,
339 ) -> Self {
340 Self { in_module: container, kind: DiagnosticKind::UnresolvedImport { ast, index } }
341 }
342
305 pub(super) fn add_to( 343 pub(super) fn add_to(
306 &self, 344 &self,
307 db: &dyn DefDatabase, 345 db: &dyn DefDatabase,
308 target_module: LocalModuleId, 346 target_module: LocalModuleId,
309 sink: &mut DiagnosticSink, 347 sink: &mut DiagnosticSink,
310 ) { 348 ) {
311 match self { 349 if self.in_module != target_module {
312 DefDiagnostic::UnresolvedModule { module, declaration, candidate } => { 350 return;
313 if *module != target_module { 351 }
314 return; 352
315 } 353 match &self.kind {
354 DiagnosticKind::UnresolvedModule { declaration, candidate } => {
316 let decl = declaration.to_node(db.upcast()); 355 let decl = declaration.to_node(db.upcast());
317 sink.push(UnresolvedModule { 356 sink.push(UnresolvedModule {
318 file: declaration.file_id, 357 file: declaration.file_id,
@@ -320,6 +359,36 @@ mod diagnostics {
320 candidate: candidate.clone(), 359 candidate: candidate.clone(),
321 }) 360 })
322 } 361 }
362
363 DiagnosticKind::UnresolvedExternCrate { ast } => {
364 let item = ast.to_node(db.upcast());
365 sink.push(UnresolvedExternCrate {
366 file: ast.file_id,
367 item: AstPtr::new(&item),
368 });
369 }
370
371 DiagnosticKind::UnresolvedImport { ast, index } => {
372 let use_item = ast.to_node(db.upcast());
373 let hygiene = Hygiene::new(db.upcast(), ast.file_id);
374 let mut cur = 0;
375 let mut tree = None;
376 ModPath::expand_use_item(
377 InFile::new(ast.file_id, use_item),
378 &hygiene,
379 |_mod_path, use_tree, _is_glob, _alias| {
380 if cur == *index {
381 tree = Some(use_tree.clone());
382 }
383
384 cur += 1;
385 },
386 );
387
388 if let Some(tree) = tree {
389 sink.push(UnresolvedImport { file: ast.file_id, node: AstPtr::new(&tree) });
390 }
391 }
323 } 392 }
324 } 393 }
325 } 394 }
diff --git a/crates/hir_def/src/nameres/collector.rs b/crates/hir_def/src/nameres/collector.rs
index 3e99c8773..4c3993ff0 100644
--- a/crates/hir_def/src/nameres/collector.rs
+++ b/crates/hir_def/src/nameres/collector.rs
@@ -3,8 +3,11 @@
3//! `DefCollector::collect` contains the fixed-point iteration loop which 3//! `DefCollector::collect` contains the fixed-point iteration loop which
4//! resolves imports and expands macros. 4//! resolves imports and expands macros.
5 5
6use std::iter;
7
6use base_db::{CrateId, FileId, ProcMacroId}; 8use base_db::{CrateId, FileId, ProcMacroId};
7use cfg::CfgOptions; 9use cfg::CfgOptions;
10use hir_expand::InFile;
8use hir_expand::{ 11use hir_expand::{
9 ast_id_map::FileAstId, 12 ast_id_map::FileAstId,
10 builtin_derive::find_builtin_derive, 13 builtin_derive::find_builtin_derive,
@@ -14,6 +17,7 @@ use hir_expand::{
14 HirFileId, MacroCallId, MacroDefId, MacroDefKind, 17 HirFileId, MacroCallId, MacroDefId, MacroDefKind,
15}; 18};
16use rustc_hash::FxHashMap; 19use rustc_hash::FxHashMap;
20use rustc_hash::FxHashSet;
17use syntax::ast; 21use syntax::ast;
18use test_utils::mark; 22use test_utils::mark;
19 23
@@ -21,9 +25,7 @@ use crate::{
21 attr::Attrs, 25 attr::Attrs,
22 db::DefDatabase, 26 db::DefDatabase,
23 item_scope::{ImportType, PerNsGlobImports}, 27 item_scope::{ImportType, PerNsGlobImports},
24 item_tree::{ 28 item_tree::{self, ItemTree, ItemTreeId, MacroCall, Mod, ModItem, ModKind, StructDefKind},
25 self, FileItemTreeId, ItemTree, ItemTreeId, MacroCall, Mod, ModItem, ModKind, StructDefKind,
26 },
27 nameres::{ 29 nameres::{
28 diagnostics::DefDiagnostic, mod_resolution::ModDir, path_resolution::ReachedFixedPoint, 30 diagnostics::DefDiagnostic, mod_resolution::ModDir, path_resolution::ReachedFixedPoint,
29 BuiltinShadowMode, CrateDefMap, ModuleData, ModuleOrigin, ResolveMode, 31 BuiltinShadowMode, CrateDefMap, ModuleData, ModuleOrigin, ResolveMode,
@@ -112,6 +114,12 @@ impl PartialResolvedImport {
112} 114}
113 115
114#[derive(Clone, Debug, Eq, PartialEq)] 116#[derive(Clone, Debug, Eq, PartialEq)]
117enum ImportSource {
118 Import(ItemTreeId<item_tree::Import>),
119 ExternCrate(ItemTreeId<item_tree::ExternCrate>),
120}
121
122#[derive(Clone, Debug, Eq, PartialEq)]
115struct Import { 123struct Import {
116 pub path: ModPath, 124 pub path: ModPath,
117 pub alias: Option<ImportAlias>, 125 pub alias: Option<ImportAlias>,
@@ -120,11 +128,12 @@ struct Import {
120 pub is_prelude: bool, 128 pub is_prelude: bool,
121 pub is_extern_crate: bool, 129 pub is_extern_crate: bool,
122 pub is_macro_use: bool, 130 pub is_macro_use: bool,
131 source: ImportSource,
123} 132}
124 133
125impl Import { 134impl Import {
126 fn from_use(tree: &ItemTree, id: FileItemTreeId<item_tree::Import>) -> Self { 135 fn from_use(tree: &ItemTree, id: ItemTreeId<item_tree::Import>) -> Self {
127 let it = &tree[id]; 136 let it = &tree[id.value];
128 let visibility = &tree[it.visibility]; 137 let visibility = &tree[it.visibility];
129 Self { 138 Self {
130 path: it.path.clone(), 139 path: it.path.clone(),
@@ -134,20 +143,22 @@ impl Import {
134 is_prelude: it.is_prelude, 143 is_prelude: it.is_prelude,
135 is_extern_crate: false, 144 is_extern_crate: false,
136 is_macro_use: false, 145 is_macro_use: false,
146 source: ImportSource::Import(id),
137 } 147 }
138 } 148 }
139 149
140 fn from_extern_crate(tree: &ItemTree, id: FileItemTreeId<item_tree::ExternCrate>) -> Self { 150 fn from_extern_crate(tree: &ItemTree, id: ItemTreeId<item_tree::ExternCrate>) -> Self {
141 let it = &tree[id]; 151 let it = &tree[id.value];
142 let visibility = &tree[it.visibility]; 152 let visibility = &tree[it.visibility];
143 Self { 153 Self {
144 path: it.path.clone(), 154 path: ModPath::from_segments(PathKind::Plain, iter::once(it.name.clone())),
145 alias: it.alias.clone(), 155 alias: it.alias.clone(),
146 visibility: visibility.clone(), 156 visibility: visibility.clone(),
147 is_glob: false, 157 is_glob: false,
148 is_prelude: false, 158 is_prelude: false,
149 is_extern_crate: true, 159 is_extern_crate: true,
150 is_macro_use: it.is_macro_use, 160 is_macro_use: it.is_macro_use,
161 source: ImportSource::ExternCrate(id),
151 } 162 }
152 } 163 }
153} 164}
@@ -245,9 +256,10 @@ impl DefCollector<'_> {
245 256
246 let unresolved_imports = std::mem::replace(&mut self.unresolved_imports, Vec::new()); 257 let unresolved_imports = std::mem::replace(&mut self.unresolved_imports, Vec::new());
247 // show unresolved imports in completion, etc 258 // show unresolved imports in completion, etc
248 for directive in unresolved_imports { 259 for directive in &unresolved_imports {
249 self.record_resolved_import(&directive) 260 self.record_resolved_import(directive)
250 } 261 }
262 self.unresolved_imports = unresolved_imports;
251 263
252 // Record proc-macros 264 // Record proc-macros
253 self.collect_proc_macro(); 265 self.collect_proc_macro();
@@ -261,7 +273,7 @@ impl DefCollector<'_> {
261 let macro_id = MacroDefId { 273 let macro_id = MacroDefId {
262 ast_id: None, 274 ast_id: None,
263 krate: Some(krate), 275 krate: Some(krate),
264 kind: MacroDefKind::CustomDerive(expander), 276 kind: MacroDefKind::ProcMacro(expander),
265 local_inner: false, 277 local_inner: false,
266 }; 278 };
267 279
@@ -346,20 +358,15 @@ impl DefCollector<'_> {
346 fn import_macros_from_extern_crate( 358 fn import_macros_from_extern_crate(
347 &mut self, 359 &mut self,
348 current_module_id: LocalModuleId, 360 current_module_id: LocalModuleId,
349 import: &item_tree::ExternCrate, 361 extern_crate: &item_tree::ExternCrate,
350 ) { 362 ) {
351 log::debug!( 363 log::debug!(
352 "importing macros from extern crate: {:?} ({:?})", 364 "importing macros from extern crate: {:?} ({:?})",
353 import, 365 extern_crate,
354 self.def_map.edition, 366 self.def_map.edition,
355 ); 367 );
356 368
357 let res = self.def_map.resolve_name_in_extern_prelude( 369 let res = self.def_map.resolve_name_in_extern_prelude(&extern_crate.name);
358 &import
359 .path
360 .as_ident()
361 .expect("extern crate should have been desugared to one-element path"),
362 );
363 370
364 if let Some(ModuleDefId::ModuleId(m)) = res.take_types() { 371 if let Some(ModuleDefId::ModuleId(m)) = res.take_types() {
365 mark::hit!(macro_rules_from_other_crates_are_visible_with_macro_use); 372 mark::hit!(macro_rules_from_other_crates_are_visible_with_macro_use);
@@ -420,7 +427,11 @@ impl DefCollector<'_> {
420 .as_ident() 427 .as_ident()
421 .expect("extern crate should have been desugared to one-element path"), 428 .expect("extern crate should have been desugared to one-element path"),
422 ); 429 );
423 PartialResolvedImport::Resolved(res) 430 if res.is_none() {
431 PartialResolvedImport::Unresolved
432 } else {
433 PartialResolvedImport::Resolved(res)
434 }
424 } else { 435 } else {
425 let res = self.def_map.resolve_path_fp_with_macro( 436 let res = self.def_map.resolve_path_fp_with_macro(
426 self.db, 437 self.db,
@@ -774,7 +785,51 @@ impl DefCollector<'_> {
774 .collect(item_tree.top_level_items()); 785 .collect(item_tree.top_level_items());
775 } 786 }
776 787
777 fn finish(self) -> CrateDefMap { 788 fn finish(mut self) -> CrateDefMap {
789 // Emit diagnostics for all remaining unresolved imports.
790
791 // We'd like to avoid emitting a diagnostics avalanche when some `extern crate` doesn't
792 // resolve. We first emit diagnostics for unresolved extern crates and collect the missing
793 // crate names. Then we emit diagnostics for unresolved imports, but only if the import
794 // doesn't start with an unresolved crate's name. Due to renaming and reexports, this is a
795 // heuristic, but it works in practice.
796 let mut diagnosed_extern_crates = FxHashSet::default();
797 for directive in &self.unresolved_imports {
798 if let ImportSource::ExternCrate(krate) = directive.import.source {
799 let item_tree = self.db.item_tree(krate.file_id);
800 let extern_crate = &item_tree[krate.value];
801
802 diagnosed_extern_crates.insert(extern_crate.name.clone());
803
804 self.def_map.diagnostics.push(DefDiagnostic::unresolved_extern_crate(
805 directive.module_id,
806 InFile::new(krate.file_id, extern_crate.ast_id),
807 ));
808 }
809 }
810
811 for directive in &self.unresolved_imports {
812 if let ImportSource::Import(import) = &directive.import.source {
813 let item_tree = self.db.item_tree(import.file_id);
814 let import_data = &item_tree[import.value];
815
816 match (import_data.path.segments.first(), &import_data.path.kind) {
817 (Some(krate), PathKind::Plain) | (Some(krate), PathKind::Abs) => {
818 if diagnosed_extern_crates.contains(krate) {
819 continue;
820 }
821 }
822 _ => {}
823 }
824
825 self.def_map.diagnostics.push(DefDiagnostic::unresolved_import(
826 directive.module_id,
827 InFile::new(import.file_id, import_data.ast_id),
828 import_data.index,
829 ));
830 }
831 }
832
778 self.def_map 833 self.def_map
779 } 834 }
780} 835}
@@ -819,179 +874,184 @@ impl ModCollector<'_, '_> {
819 874
820 for &item in items { 875 for &item in items {
821 let attrs = self.item_tree.attrs(item.into()); 876 let attrs = self.item_tree.attrs(item.into());
822 if self.is_cfg_enabled(attrs) { 877 if !self.is_cfg_enabled(attrs) {
823 let module = 878 continue;
824 ModuleId { krate: self.def_collector.def_map.krate, local_id: self.module_id }; 879 }
825 let container = ContainerId::ModuleId(module); 880 let module =
826 881 ModuleId { krate: self.def_collector.def_map.krate, local_id: self.module_id };
827 let mut def = None; 882 let container = ContainerId::ModuleId(module);
828 match item { 883
829 ModItem::Mod(m) => self.collect_module(&self.item_tree[m], attrs), 884 let mut def = None;
830 ModItem::Import(import_id) => { 885 match item {
831 self.def_collector.unresolved_imports.push(ImportDirective { 886 ModItem::Mod(m) => self.collect_module(&self.item_tree[m], attrs),
832 module_id: self.module_id, 887 ModItem::Import(import_id) => {
833 import: Import::from_use(&self.item_tree, import_id), 888 self.def_collector.unresolved_imports.push(ImportDirective {
834 status: PartialResolvedImport::Unresolved, 889 module_id: self.module_id,
835 }) 890 import: Import::from_use(
836 } 891 &self.item_tree,
837 ModItem::ExternCrate(import_id) => { 892 InFile::new(self.file_id, import_id),
838 self.def_collector.unresolved_imports.push(ImportDirective { 893 ),
839 module_id: self.module_id, 894 status: PartialResolvedImport::Unresolved,
840 import: Import::from_extern_crate(&self.item_tree, import_id), 895 })
841 status: PartialResolvedImport::Unresolved, 896 }
842 }) 897 ModItem::ExternCrate(import_id) => {
843 } 898 self.def_collector.unresolved_imports.push(ImportDirective {
844 ModItem::MacroCall(mac) => self.collect_macro(&self.item_tree[mac]), 899 module_id: self.module_id,
845 ModItem::Impl(imp) => { 900 import: Import::from_extern_crate(
846 let module = ModuleId { 901 &self.item_tree,
847 krate: self.def_collector.def_map.krate, 902 InFile::new(self.file_id, import_id),
848 local_id: self.module_id, 903 ),
849 }; 904 status: PartialResolvedImport::Unresolved,
850 let container = ContainerId::ModuleId(module); 905 })
851 let impl_id = ImplLoc { container, id: ItemTreeId::new(self.file_id, imp) } 906 }
852 .intern(self.def_collector.db); 907 ModItem::MacroCall(mac) => self.collect_macro(&self.item_tree[mac]),
853 self.def_collector.def_map.modules[self.module_id] 908 ModItem::Impl(imp) => {
854 .scope 909 let module = ModuleId {
855 .define_impl(impl_id) 910 krate: self.def_collector.def_map.krate,
856 } 911 local_id: self.module_id,
857 ModItem::Function(id) => { 912 };
858 let func = &self.item_tree[id]; 913 let container = ContainerId::ModuleId(module);
859 def = Some(DefData { 914 let impl_id = ImplLoc { container, id: ItemTreeId::new(self.file_id, imp) }
860 id: FunctionLoc { 915 .intern(self.def_collector.db);
861 container: container.into(), 916 self.def_collector.def_map.modules[self.module_id].scope.define_impl(impl_id)
862 id: ItemTreeId::new(self.file_id, id), 917 }
863 } 918 ModItem::Function(id) => {
864 .intern(self.def_collector.db) 919 let func = &self.item_tree[id];
865 .into(), 920 def = Some(DefData {
866 name: &func.name, 921 id: FunctionLoc {
867 visibility: &self.item_tree[func.visibility], 922 container: container.into(),
868 has_constructor: false, 923 id: ItemTreeId::new(self.file_id, id),
869 }); 924 }
870 } 925 .intern(self.def_collector.db)
871 ModItem::Struct(id) => { 926 .into(),
872 let it = &self.item_tree[id]; 927 name: &func.name,
873 928 visibility: &self.item_tree[func.visibility],
874 // FIXME: check attrs to see if this is an attribute macro invocation; 929 has_constructor: false,
875 // in which case we don't add the invocation, just a single attribute 930 });
876 // macro invocation 931 }
877 self.collect_derives(attrs, it.ast_id.upcast()); 932 ModItem::Struct(id) => {
878 933 let it = &self.item_tree[id];
879 def = Some(DefData {
880 id: StructLoc { container, id: ItemTreeId::new(self.file_id, id) }
881 .intern(self.def_collector.db)
882 .into(),
883 name: &it.name,
884 visibility: &self.item_tree[it.visibility],
885 has_constructor: it.kind != StructDefKind::Record,
886 });
887 }
888 ModItem::Union(id) => {
889 let it = &self.item_tree[id];
890 934
891 // FIXME: check attrs to see if this is an attribute macro invocation; 935 // FIXME: check attrs to see if this is an attribute macro invocation;
892 // in which case we don't add the invocation, just a single attribute 936 // in which case we don't add the invocation, just a single attribute
893 // macro invocation 937 // macro invocation
894 self.collect_derives(attrs, it.ast_id.upcast()); 938 self.collect_derives(attrs, it.ast_id.upcast());
895 939
896 def = Some(DefData { 940 def = Some(DefData {
897 id: UnionLoc { container, id: ItemTreeId::new(self.file_id, id) } 941 id: StructLoc { container, id: ItemTreeId::new(self.file_id, id) }
898 .intern(self.def_collector.db) 942 .intern(self.def_collector.db)
899 .into(), 943 .into(),
900 name: &it.name, 944 name: &it.name,
901 visibility: &self.item_tree[it.visibility], 945 visibility: &self.item_tree[it.visibility],
902 has_constructor: false, 946 has_constructor: it.kind != StructDefKind::Record,
903 }); 947 });
904 } 948 }
905 ModItem::Enum(id) => { 949 ModItem::Union(id) => {
906 let it = &self.item_tree[id]; 950 let it = &self.item_tree[id];
907 951
908 // FIXME: check attrs to see if this is an attribute macro invocation; 952 // FIXME: check attrs to see if this is an attribute macro invocation;
909 // in which case we don't add the invocation, just a single attribute 953 // in which case we don't add the invocation, just a single attribute
910 // macro invocation 954 // macro invocation
911 self.collect_derives(attrs, it.ast_id.upcast()); 955 self.collect_derives(attrs, it.ast_id.upcast());
912 956
913 def = Some(DefData { 957 def = Some(DefData {
914 id: EnumLoc { container, id: ItemTreeId::new(self.file_id, id) } 958 id: UnionLoc { container, id: ItemTreeId::new(self.file_id, id) }
915 .intern(self.def_collector.db) 959 .intern(self.def_collector.db)
916 .into(), 960 .into(),
917 name: &it.name, 961 name: &it.name,
918 visibility: &self.item_tree[it.visibility], 962 visibility: &self.item_tree[it.visibility],
919 has_constructor: false, 963 has_constructor: false,
920 }); 964 });
921 } 965 }
922 ModItem::Const(id) => { 966 ModItem::Enum(id) => {
923 let it = &self.item_tree[id]; 967 let it = &self.item_tree[id];
924
925 if let Some(name) = &it.name {
926 def = Some(DefData {
927 id: ConstLoc {
928 container: container.into(),
929 id: ItemTreeId::new(self.file_id, id),
930 }
931 .intern(self.def_collector.db)
932 .into(),
933 name,
934 visibility: &self.item_tree[it.visibility],
935 has_constructor: false,
936 });
937 }
938 }
939 ModItem::Static(id) => {
940 let it = &self.item_tree[id];
941 968
942 def = Some(DefData { 969 // FIXME: check attrs to see if this is an attribute macro invocation;
943 id: StaticLoc { container, id: ItemTreeId::new(self.file_id, id) } 970 // in which case we don't add the invocation, just a single attribute
944 .intern(self.def_collector.db) 971 // macro invocation
945 .into(), 972 self.collect_derives(attrs, it.ast_id.upcast());
946 name: &it.name,
947 visibility: &self.item_tree[it.visibility],
948 has_constructor: false,
949 });
950 }
951 ModItem::Trait(id) => {
952 let it = &self.item_tree[id];
953 973
954 def = Some(DefData { 974 def = Some(DefData {
955 id: TraitLoc { container, id: ItemTreeId::new(self.file_id, id) } 975 id: EnumLoc { container, id: ItemTreeId::new(self.file_id, id) }
956 .intern(self.def_collector.db) 976 .intern(self.def_collector.db)
957 .into(), 977 .into(),
958 name: &it.name, 978 name: &it.name,
959 visibility: &self.item_tree[it.visibility], 979 visibility: &self.item_tree[it.visibility],
960 has_constructor: false, 980 has_constructor: false,
961 }); 981 });
962 } 982 }
963 ModItem::TypeAlias(id) => { 983 ModItem::Const(id) => {
964 let it = &self.item_tree[id]; 984 let it = &self.item_tree[id];
965 985
986 if let Some(name) = &it.name {
966 def = Some(DefData { 987 def = Some(DefData {
967 id: TypeAliasLoc { 988 id: ConstLoc {
968 container: container.into(), 989 container: container.into(),
969 id: ItemTreeId::new(self.file_id, id), 990 id: ItemTreeId::new(self.file_id, id),
970 } 991 }
971 .intern(self.def_collector.db) 992 .intern(self.def_collector.db)
972 .into(), 993 .into(),
973 name: &it.name, 994 name,
974 visibility: &self.item_tree[it.visibility], 995 visibility: &self.item_tree[it.visibility],
975 has_constructor: false, 996 has_constructor: false,
976 }); 997 });
977 } 998 }
978 } 999 }
1000 ModItem::Static(id) => {
1001 let it = &self.item_tree[id];
979 1002
980 if let Some(DefData { id, name, visibility, has_constructor }) = def { 1003 def = Some(DefData {
981 self.def_collector.def_map.modules[self.module_id].scope.define_def(id); 1004 id: StaticLoc { container, id: ItemTreeId::new(self.file_id, id) }
982 let vis = self 1005 .intern(self.def_collector.db)
983 .def_collector 1006 .into(),
984 .def_map 1007 name: &it.name,
985 .resolve_visibility(self.def_collector.db, self.module_id, visibility) 1008 visibility: &self.item_tree[it.visibility],
986 .unwrap_or(Visibility::Public); 1009 has_constructor: false,
987 self.def_collector.update( 1010 });
988 self.module_id, 1011 }
989 &[(Some(name.clone()), PerNs::from_def(id, vis, has_constructor))], 1012 ModItem::Trait(id) => {
990 vis, 1013 let it = &self.item_tree[id];
991 ImportType::Named, 1014
992 ) 1015 def = Some(DefData {
1016 id: TraitLoc { container, id: ItemTreeId::new(self.file_id, id) }
1017 .intern(self.def_collector.db)
1018 .into(),
1019 name: &it.name,
1020 visibility: &self.item_tree[it.visibility],
1021 has_constructor: false,
1022 });
1023 }
1024 ModItem::TypeAlias(id) => {
1025 let it = &self.item_tree[id];
1026
1027 def = Some(DefData {
1028 id: TypeAliasLoc {
1029 container: container.into(),
1030 id: ItemTreeId::new(self.file_id, id),
1031 }
1032 .intern(self.def_collector.db)
1033 .into(),
1034 name: &it.name,
1035 visibility: &self.item_tree[it.visibility],
1036 has_constructor: false,
1037 });
993 } 1038 }
994 } 1039 }
1040
1041 if let Some(DefData { id, name, visibility, has_constructor }) = def {
1042 self.def_collector.def_map.modules[self.module_id].scope.define_def(id);
1043 let vis = self
1044 .def_collector
1045 .def_map
1046 .resolve_visibility(self.def_collector.db, self.module_id, visibility)
1047 .unwrap_or(Visibility::Public);
1048 self.def_collector.update(
1049 self.module_id,
1050 &[(Some(name.clone()), PerNs::from_def(id, vis, has_constructor))],
1051 vis,
1052 ImportType::Named,
1053 )
1054 }
995 } 1055 }
996 } 1056 }
997 1057
@@ -1051,13 +1111,11 @@ impl ModCollector<'_, '_> {
1051 self.import_all_legacy_macros(module_id); 1111 self.import_all_legacy_macros(module_id);
1052 } 1112 }
1053 } 1113 }
1054 Err(candidate) => self.def_collector.def_map.diagnostics.push( 1114 Err(candidate) => {
1055 DefDiagnostic::UnresolvedModule { 1115 self.def_collector.def_map.diagnostics.push(
1056 module: self.module_id, 1116 DefDiagnostic::unresolved_module(self.module_id, ast_id, candidate),
1057 declaration: ast_id, 1117 );
1058 candidate, 1118 }
1059 },
1060 ),
1061 }; 1119 };
1062 } 1120 }
1063 } 1121 }
diff --git a/crates/hir_def/src/nameres/tests.rs b/crates/hir_def/src/nameres/tests.rs
index 5ca30dac9..11d84f808 100644
--- a/crates/hir_def/src/nameres/tests.rs
+++ b/crates/hir_def/src/nameres/tests.rs
@@ -2,6 +2,7 @@ mod globs;
2mod incremental; 2mod incremental;
3mod macros; 3mod macros;
4mod mod_resolution; 4mod mod_resolution;
5mod diagnostics;
5mod primitives; 6mod primitives;
6 7
7use std::sync::Arc; 8use std::sync::Arc;
diff --git a/crates/hir_def/src/nameres/tests/diagnostics.rs b/crates/hir_def/src/nameres/tests/diagnostics.rs
new file mode 100644
index 000000000..576b813d2
--- /dev/null
+++ b/crates/hir_def/src/nameres/tests/diagnostics.rs
@@ -0,0 +1,131 @@
1use base_db::fixture::WithFixture;
2use base_db::FileId;
3use base_db::SourceDatabaseExt;
4use hir_expand::db::AstDatabase;
5use rustc_hash::FxHashMap;
6use syntax::TextRange;
7use syntax::TextSize;
8
9use crate::test_db::TestDB;
10
11fn check_diagnostics(ra_fixture: &str) {
12 let db: TestDB = TestDB::with_files(ra_fixture);
13 let annotations = db.extract_annotations();
14 assert!(!annotations.is_empty());
15
16 let mut actual: FxHashMap<FileId, Vec<(TextRange, String)>> = FxHashMap::default();
17 db.diagnostics(|d| {
18 let src = d.display_source();
19 let root = db.parse_or_expand(src.file_id).unwrap();